link.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. import bcrypt from "bcryptjs";
  2. import { CustomError } from "../utils";
  3. import * as redis from "../redis";
  4. import knex from "../knex";
  5. const selectable = [
  6. "links.id",
  7. "links.address",
  8. "links.banned",
  9. "links.created_at",
  10. "links.domain_id",
  11. "links.updated_at",
  12. "links.password",
  13. "links.target",
  14. "links.visit_count",
  15. "links.user_id",
  16. "links.uuid",
  17. "domains.address as domain"
  18. ];
  19. const normalizeMatch = (match: Partial<Link>): Partial<Link> => {
  20. const newMatch = { ...match };
  21. if (newMatch.address) {
  22. newMatch["links.address"] = newMatch.address;
  23. delete newMatch.address;
  24. }
  25. if (newMatch.user_id) {
  26. newMatch["links.user_id"] = newMatch.user_id;
  27. delete newMatch.user_id;
  28. }
  29. if (newMatch.uuid) {
  30. newMatch["links.uuid"] = newMatch.uuid;
  31. delete newMatch.uuid;
  32. }
  33. return newMatch;
  34. };
  35. interface TotalParams {
  36. search?: string;
  37. }
  38. export const total = async (match: Match<Link>, params: TotalParams = {}) => {
  39. const query = knex<Link>("links");
  40. Object.entries(match).forEach(([key, value]) => {
  41. query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
  42. });
  43. if (params.search) {
  44. query.andWhereRaw("links.address || ' ' || target ILIKE '%' || ? || '%'", [
  45. params.search
  46. ]);
  47. }
  48. const [{ count }] = await query.count("id");
  49. return typeof count === "number" ? count : parseInt(count);
  50. };
  51. interface GetParams {
  52. limit: number;
  53. search?: string;
  54. skip: number;
  55. }
  56. export const get = async (match: Partial<Link>, params: GetParams) => {
  57. const query = knex<LinkJoinedDomain>("links")
  58. .select(...selectable)
  59. .where(normalizeMatch(match))
  60. .offset(params.skip)
  61. .limit(params.limit)
  62. .orderBy("created_at", "desc");
  63. if (params.search) {
  64. query.andWhereRaw("links.address || ' ' || target ILIKE '%' || ? || '%'", [
  65. params.search
  66. ]);
  67. }
  68. query.leftJoin("domains", "links.domain_id", "domains.id");
  69. const links: LinkJoinedDomain[] = await query;
  70. return links;
  71. };
  72. export const find = async (match: Partial<Link>): Promise<Link> => {
  73. if (match.address && match.domain_id) {
  74. const key = redis.key.link(match.address, match.domain_id);
  75. const cachedLink = await redis.get(key);
  76. if (cachedLink) return JSON.parse(cachedLink);
  77. }
  78. const link = await knex<Link>("links")
  79. .select(...selectable)
  80. .where(normalizeMatch(match))
  81. .leftJoin("domains", "links.domain_id", "domains.id")
  82. .first();
  83. if (link) {
  84. const key = redis.key.link(link.address, link.domain_id);
  85. redis.set(key, JSON.stringify(link), "EX", 60 * 60 * 2);
  86. }
  87. return link;
  88. };
  89. interface Create extends Partial<Link> {
  90. address: string;
  91. target: string;
  92. }
  93. export const create = async (params: Create) => {
  94. let encryptedPassword: string = null;
  95. if (params.password) {
  96. const salt = await bcrypt.genSalt(12);
  97. encryptedPassword = await bcrypt.hash(params.password, salt);
  98. }
  99. const [link]: LinkJoinedDomain[] = await knex<LinkJoinedDomain>(
  100. "links"
  101. ).insert(
  102. {
  103. password: encryptedPassword,
  104. domain_id: params.domain_id || null,
  105. user_id: params.user_id || null,
  106. address: params.address,
  107. target: params.target
  108. },
  109. "*"
  110. );
  111. return link;
  112. };
  113. export const remove = async (match: Partial<Link>) => {
  114. const link = await knex<Link>("links")
  115. .where(match)
  116. .first();
  117. if (!link) {
  118. throw new CustomError("Link was not found.");
  119. }
  120. await knex<Visit>("visits")
  121. .where("link_id", link.id)
  122. .delete();
  123. const deletedLink = await knex<Link>("links")
  124. .where("id", link.id)
  125. .delete();
  126. redis.remove.link(link);
  127. return !!deletedLink;
  128. };
  129. export const update = async (match: Partial<Link>, update: Partial<Link>) => {
  130. const links = await knex<Link>("links")
  131. .where(match)
  132. .update({ ...update, updated_at: new Date().toISOString() }, "*");
  133. links.forEach(redis.remove.link);
  134. return links;
  135. };
  136. export const increamentVisit = async (match: Partial<Link>) => {
  137. return knex<Link>("links")
  138. .where(match)
  139. .increment("visit_count", 1);
  140. };