urlController.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. const urlRegex = require('url-regex');
  2. const URL = require('url');
  3. const useragent = require('useragent');
  4. const geoip = require('geoip-lite');
  5. const bcrypt = require('bcryptjs');
  6. const {
  7. createShortUrl,
  8. createVisit,
  9. findUrl,
  10. getStats,
  11. getUrls,
  12. getCustomDomain,
  13. setCustomDomain,
  14. deleteCustomDomain,
  15. deleteUrl,
  16. } = require('../db/url');
  17. const config = require('../config');
  18. const preservedUrls = [
  19. 'login',
  20. 'logout',
  21. 'signup',
  22. 'reset-password',
  23. 'resetpassword',
  24. 'url-password',
  25. 'settings',
  26. 'stats',
  27. 'verify',
  28. 'api',
  29. '404',
  30. 'static',
  31. 'images',
  32. ];
  33. exports.preservedUrls = preservedUrls;
  34. exports.urlShortener = async ({ body, user }, res) => {
  35. if (!body.target) return res.status(400).json({ error: 'No target has been provided.' });
  36. if (body.target.length > 1024) {
  37. return res.status(400).json({ error: 'Maximum URL length is 1024.' });
  38. }
  39. const isValidUrl = urlRegex({ exact: true, strict: false }).test(body.target);
  40. if (!isValidUrl) return res.status(400).json({ error: 'URL is not valid.' });
  41. const target = URL.parse(body.target).protocol ? body.target : `http://${body.target}`;
  42. if (body.password && body.password.length > 64) {
  43. return res.status(400).json({ error: 'Maximum password length is 64.' });
  44. }
  45. if (user && body.customurl) {
  46. if (!/^[a-zA-Z1-9-_]+$/g.test(body.customurl.trim())) {
  47. return res.status(400).json({ error: 'Custom URL is not valid.' });
  48. }
  49. if (preservedUrls.some(url => url === body.customurl)) {
  50. return res.status(400).json({ error: "You can't use this custom URL name." });
  51. }
  52. if (body.customurl.length > 64) {
  53. return res.status(400).json({ error: 'Maximum custom URL length is 64.' });
  54. }
  55. const urls = await findUrl({ id: body.customurl || '' });
  56. if (urls.length) {
  57. const urlWithNoDomain = !user.domain && urls.some(url => !url.domain);
  58. const urlWithDmoain = user.domain && urls.some(url => url.domain === user.domain);
  59. if (urlWithNoDomain || urlWithDmoain) {
  60. return res.status(400).json({ error: 'Custom URL is already in use.' });
  61. }
  62. }
  63. }
  64. const url = await createShortUrl({ ...body, target, user });
  65. return res.json(url);
  66. };
  67. const browsersList = ['IE', 'Firefox', 'Chrome', 'Opera', 'Safari', 'Edge'];
  68. const osList = ['Windows', 'Mac Os X', 'Linux', 'Chrome OS', 'Android', 'iOS'];
  69. const filterInBrowser = agent => item =>
  70. agent.family.toLowerCase().includes(item.toLocaleLowerCase());
  71. const filterInOs = agent => item =>
  72. agent.os.family.toLowerCase().includes(item.toLocaleLowerCase());
  73. exports.goToUrl = async (req, res, next) => {
  74. const { host } = req.headers;
  75. const id = req.params.id || req.body.id;
  76. const domain = host !== config.DEFAULT_DOMAIN && host;
  77. const agent = useragent.parse(req.headers['user-agent']);
  78. const [browser = 'Other'] = browsersList.filter(filterInBrowser(agent));
  79. const [os = 'Other'] = osList.filter(filterInOs(agent));
  80. const referrer = req.header('Referer') && URL.parse(req.header('Referer')).hostname;
  81. const location = geoip.lookup(req.realIp);
  82. const country = location && location.country;
  83. const urls = await findUrl({ id, domain });
  84. if (!urls && !urls.length) return next();
  85. const [url] = urls;
  86. if (url.password && !req.body.password) {
  87. req.protectedUrl = id;
  88. return next();
  89. }
  90. if (url.password) {
  91. const isMatch = await bcrypt.compare(req.body.password, url.password);
  92. if (!isMatch) {
  93. return res.status(401).json({ error: 'Password is not correct' });
  94. }
  95. if (url.user) {
  96. await createVisit({
  97. browser,
  98. country: country || 'Unknown',
  99. domain,
  100. id: url.id,
  101. os,
  102. referrer: referrer || 'Direct',
  103. });
  104. }
  105. return res.status(200).json({ target: url.target });
  106. }
  107. if (url.user) {
  108. await createVisit({
  109. browser,
  110. country: country || 'Unknown',
  111. domain,
  112. id: url.id,
  113. os,
  114. referrer: referrer || 'Direct',
  115. });
  116. }
  117. return res.redirect(url.target);
  118. };
  119. exports.getUrls = async ({ body, user }, res) => {
  120. const urlsList = await getUrls({ options: body, user });
  121. return res.json(urlsList);
  122. };
  123. exports.setCustomDomain = async ({ body: { customDomain }, user }, res) => {
  124. if (customDomain.length > 40) {
  125. return res.status(400).json({ error: 'Maximum custom domain length is 40.' });
  126. }
  127. if (customDomain === config.DEFAULT_DOMAIN) {
  128. return res.status(400).json({ error: "You can't use default domain." });
  129. }
  130. const isValidDomain = urlRegex({ exact: true, strict: false }).test(customDomain);
  131. if (!isValidDomain) return res.status(400).json({ error: 'Domain is not valid.' });
  132. const isOwned = await getCustomDomain({ customDomain });
  133. if (isOwned && isOwned.email !== user.email) {
  134. return res
  135. .status(400)
  136. .json({ error: 'Domain is already taken. Contact us for multiple users.' });
  137. }
  138. const userCustomDomain = await setCustomDomain({ user, customDomain });
  139. if (userCustomDomain) return res.status(201).json({ customDomain: userCustomDomain.name });
  140. return res.status(400).json({ error: "Couldn't set custom domain." });
  141. };
  142. exports.deleteCustomDomain = async ({ user }, res) => {
  143. const response = await deleteCustomDomain({ user });
  144. if (response) return res.status(200).json({ message: 'Domain deleted successfully' });
  145. return res.status(400).json({ error: "Couldn't delete custom domain." });
  146. };
  147. exports.deleteUrl = async ({ body: { id, domain }, user }, res) => {
  148. if (!id) return res.status(400).json({ error: 'No id has been provided.' });
  149. const customDomain = domain !== config.DEFAULT_DOMAIN && domain;
  150. const urls = await findUrl({ id, domain: customDomain });
  151. if (!urls && !urls.length) return res.status(400).json({ error: "Couldn't find the short URL." });
  152. const response = await deleteUrl({ id, domain: customDomain, user });
  153. if (response) return res.status(200).json({ message: 'Sort URL deleted successfully' });
  154. return res.status(400).json({ error: "Couldn't delete short URL." });
  155. };
  156. exports.getStats = async ({ body: { id, domain }, user }, res) => {
  157. if (!id) return res.status(400).json({ error: 'No id has been provided.' });
  158. const customDomain = domain !== config.DEFAULT_DOMAIN && domain;
  159. const stats = await getStats({ id, domain: customDomain, user });
  160. if (!stats) return res.status(400).json({ error: 'Could not get the short URL stats.' });
  161. return res.status(200).json(stats);
  162. };