link.ts 4.5 KB

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