urlController.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. const urlRegex = require('url-regex');
  2. const URL = require('url');
  3. const generate = require('nanoid/generate');
  4. const useragent = require('useragent');
  5. const geoip = require('geoip-lite');
  6. const bcrypt = require('bcryptjs');
  7. const subDay = require('date-fns/sub_days');
  8. const {
  9. createShortUrl,
  10. createVisit,
  11. deleteCustomDomain,
  12. deleteUrl,
  13. findUrl,
  14. getCountUrls,
  15. getCustomDomain,
  16. getStats,
  17. getUrls,
  18. setCustomDomain,
  19. urlCountFromDate,
  20. } = require('../db/url');
  21. const { addProtocol, generateShortUrl } = require('../utils');
  22. const config = require('../config');
  23. const generateId = async () => {
  24. const id = generate('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', 6);
  25. const urls = await findUrl({ id });
  26. if (!urls.length) return id;
  27. return generateId();
  28. };
  29. exports.urlShortener = async ({ body, user }, res) => {
  30. // Check if user has passed daily limit
  31. if (user) {
  32. const { count } = await urlCountFromDate({
  33. email: user.email,
  34. date: subDay(new Date(), 1).toJSON(),
  35. });
  36. if (count > config.USER_LIMIT_PER_DAY) {
  37. return res.status(429).json({
  38. error: `You have reached your daily limit (${config.USER_LIMIT_PER_DAY}). Please wait 24h.`,
  39. });
  40. }
  41. }
  42. // if "reuse" is true, try to return
  43. // the existent URL without creating one
  44. if (user && body.reuse) {
  45. const urls = await findUrl({ target: addProtocol(body.target) });
  46. if (urls.length) {
  47. urls.sort((a, b) => a.createdAt > b.createdAt);
  48. const { domain: d, user: u, ...url } = urls[urls.length - 1];
  49. const data = {
  50. ...url,
  51. password: !!url.password,
  52. reuse: true,
  53. shortUrl: generateShortUrl(url.id, user.domain),
  54. };
  55. return res.json(data);
  56. }
  57. }
  58. // Check if custom URL already exists
  59. if (user && body.customurl) {
  60. const urls = await findUrl({ id: body.customurl || '' });
  61. if (urls.length) {
  62. const urlWithNoDomain = !user.domain && urls.some(url => !url.domain);
  63. const urlWithDmoain = user.domain && urls.some(url => url.domain === user.domain);
  64. if (urlWithNoDomain || urlWithDmoain) {
  65. return res.status(400).json({ error: 'Custom URL is already in use.' });
  66. }
  67. }
  68. }
  69. // Create new URL
  70. const id = (user && body.customurl) || (await generateId());
  71. const target = addProtocol(body.target);
  72. const url = await createShortUrl({ ...body, id, target, user });
  73. return res.json(url);
  74. };
  75. const browsersList = ['IE', 'Firefox', 'Chrome', 'Opera', 'Safari', 'Edge'];
  76. const osList = ['Windows', 'Mac Os X', 'Linux', 'Chrome OS', 'Android', 'iOS'];
  77. const botList = ['bot', 'dataminr', 'pinterest', 'yahoo', 'facebook', 'crawl'];
  78. const filterInBrowser = agent => item =>
  79. agent.family.toLowerCase().includes(item.toLocaleLowerCase());
  80. const filterInOs = agent => item =>
  81. agent.os.family.toLowerCase().includes(item.toLocaleLowerCase());
  82. exports.goToUrl = async (req, res, next) => {
  83. const { host } = req.headers;
  84. const reqestedId = req.params.id || req.body.id;
  85. const id = reqestedId.replace('+', '');
  86. const domain = host !== config.DEFAULT_DOMAIN && host;
  87. const agent = useragent.parse(req.headers['user-agent']);
  88. const [browser = 'Other'] = browsersList.filter(filterInBrowser(agent));
  89. const [os = 'Other'] = osList.filter(filterInOs(agent));
  90. const referrer = req.header('Referer') && URL.parse(req.header('Referer')).hostname;
  91. const location = geoip.lookup(req.realIp);
  92. const country = location && location.country;
  93. const urls = await findUrl({ id, domain });
  94. const isBot =
  95. botList.some(bot => agent.source.toLowerCase().includes(bot)) || agent.family === 'Other';
  96. if (!urls && !urls.length) return next();
  97. const url = urls.find(item => (domain ? item.domain === domain : !item.domain));
  98. const doesRequestInfo = /.*\+$/gi.test(reqestedId);
  99. if (doesRequestInfo && !url.password) {
  100. req.urlTarget = url.target;
  101. req.pageType = 'info';
  102. return next();
  103. }
  104. if (url.password && !req.body.password) {
  105. req.protectedUrl = id;
  106. req.pageType = 'password';
  107. return next();
  108. }
  109. if (url.password) {
  110. const isMatch = await bcrypt.compare(req.body.password, url.password);
  111. if (!isMatch) {
  112. return res.status(401).json({ error: 'Password is not correct' });
  113. }
  114. if (url.user && !isBot) {
  115. await createVisit({
  116. browser,
  117. country: country || 'Unknown',
  118. domain,
  119. id: url.id,
  120. os,
  121. referrer: referrer || 'Direct',
  122. });
  123. }
  124. return res.status(200).json({ target: url.target });
  125. }
  126. if (url.user && !isBot) {
  127. await createVisit({
  128. browser,
  129. country: country || 'Unknown',
  130. domain,
  131. id: url.id,
  132. os,
  133. referrer: referrer || 'Direct',
  134. });
  135. }
  136. return res.redirect(url.target);
  137. };
  138. exports.getUrls = async ({ query, user }, res) => {
  139. const { countAll } = await getCountUrls({ user });
  140. const urlsList = await getUrls({ options: query, user });
  141. return res.json({ ...urlsList, countAll });
  142. };
  143. exports.setCustomDomain = async ({ body: { customDomain }, user }, res) => {
  144. if (customDomain.length > 40) {
  145. return res.status(400).json({ error: 'Maximum custom domain length is 40.' });
  146. }
  147. if (customDomain === config.DEFAULT_DOMAIN) {
  148. return res.status(400).json({ error: "You can't use default domain." });
  149. }
  150. const isValidDomain = urlRegex({ exact: true, strict: false }).test(customDomain);
  151. if (!isValidDomain) return res.status(400).json({ error: 'Domain is not valid.' });
  152. const isOwned = await getCustomDomain({ customDomain });
  153. if (isOwned && isOwned.email !== user.email) {
  154. return res
  155. .status(400)
  156. .json({ error: 'Domain is already taken. Contact us for multiple users.' });
  157. }
  158. const userCustomDomain = await setCustomDomain({ user, customDomain });
  159. if (userCustomDomain) return res.status(201).json({ customDomain: userCustomDomain.name });
  160. return res.status(400).json({ error: "Couldn't set custom domain." });
  161. };
  162. exports.deleteCustomDomain = async ({ user }, res) => {
  163. const response = await deleteCustomDomain({ user });
  164. if (response) return res.status(200).json({ message: 'Domain deleted successfully' });
  165. return res.status(400).json({ error: "Couldn't delete custom domain." });
  166. };
  167. exports.deleteUrl = async ({ body: { id, domain }, user }, res) => {
  168. if (!id) return res.status(400).json({ error: 'No id has been provided.' });
  169. const customDomain = domain !== config.DEFAULT_DOMAIN && domain;
  170. const urls = await findUrl({ id, domain: customDomain });
  171. if (!urls && !urls.length) return res.status(400).json({ error: "Couldn't find the short URL." });
  172. const response = await deleteUrl({ id, domain: customDomain, user });
  173. if (response) return res.status(200).json({ message: 'Sort URL deleted successfully' });
  174. return res.status(400).json({ error: "Couldn't delete short URL." });
  175. };
  176. exports.getStats = async ({ query: { id, domain }, user }, res) => {
  177. if (!id) return res.status(400).json({ error: 'No id has been provided.' });
  178. const customDomain = domain !== config.DEFAULT_DOMAIN && domain;
  179. const stats = await getStats({ id, domain: customDomain, user });
  180. if (!stats) return res.status(400).json({ error: 'Could not get the short URL stats.' });
  181. return res.status(200).json(stats);
  182. };