link.queries.js 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. const bcrypt = require("bcryptjs");
  2. const utils = require("../utils");
  3. const redis = require("../redis");
  4. const knex = require("../knex");
  5. const env = require("../env");
  6. const CustomError = utils.CustomError;
  7. const selectable = [
  8. "links.id",
  9. "links.address",
  10. "links.banned",
  11. "links.created_at",
  12. "links.domain_id",
  13. "links.updated_at",
  14. "links.password",
  15. "links.description",
  16. "links.expire_in",
  17. "links.target",
  18. "links.visit_count",
  19. "links.user_id",
  20. "links.uuid",
  21. "domains.address as domain"
  22. ];
  23. const selectable_admin = [
  24. ...selectable,
  25. "users.email as email"
  26. ];
  27. function normalizeMatch(match) {
  28. const newMatch = { ...match };
  29. if (newMatch.address) {
  30. newMatch["links.address"] = newMatch.address;
  31. delete newMatch.address;
  32. }
  33. if (newMatch.user_id) {
  34. newMatch["links.user_id"] = newMatch.user_id;
  35. delete newMatch.user_id;
  36. }
  37. if (newMatch.id) {
  38. newMatch["links.id"] = newMatch.id;
  39. delete newMatch.id;
  40. }
  41. if (newMatch.uuid) {
  42. newMatch["links.uuid"] = newMatch.uuid;
  43. delete newMatch.uuid;
  44. }
  45. if (newMatch.banned !== undefined) {
  46. newMatch["links.banned"] = newMatch.banned;
  47. delete newMatch.banned;
  48. }
  49. return newMatch;
  50. }
  51. async function total(match, params) {
  52. const normalizedMatch = normalizeMatch(match);
  53. const query = knex("links");
  54. Object.entries(normalizedMatch).forEach(([key, value]) => {
  55. query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
  56. });
  57. if (params?.search) {
  58. query[knex.compatibleILIKE](
  59. knex.raw("concat_ws(' ', description, links.address, target, domains.address)"),
  60. "%" + params.search + "%"
  61. );
  62. }
  63. query.leftJoin("domains", "links.domain_id", "domains.id");
  64. query.count("* as count");
  65. const [{ count }] = await query;
  66. return typeof count === "number" ? count : parseInt(count);
  67. }
  68. async function totalAdmin(match, params) {
  69. const query = knex("links");
  70. Object.entries(normalizeMatch(match)).forEach(([key, value]) => {
  71. query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
  72. });
  73. if (params?.user) {
  74. const id = parseInt(params?.user);
  75. if (Number.isNaN(id)) {
  76. query[knex.compatibleILIKE]("users.email", "%" + params.user + "%");
  77. } else {
  78. query.andWhere("links.user_id", params.user);
  79. }
  80. }
  81. if (params?.search) {
  82. query[knex.compatibleILIKE](
  83. knex.raw("concat_ws(' ', description, links.address, target)"),
  84. "%" + params.search + "%"
  85. );
  86. }
  87. if (params?.domain) {
  88. query[knex.compatibleILIKE]("domains.address", "%" + params.domain + "%");
  89. }
  90. query.leftJoin("domains", "links.domain_id", "domains.id");
  91. query.leftJoin("users", "links.user_id", "users.id");
  92. query.count("* as count");
  93. const [{ count }] = await query;
  94. return typeof count === "number" ? count : parseInt(count);
  95. }
  96. async function get(match, params) {
  97. const query = knex("links")
  98. .select(...selectable)
  99. .where(normalizeMatch(match))
  100. .offset(params.skip)
  101. .limit(params.limit)
  102. .orderBy("links.id", "desc");
  103. if (params?.search) {
  104. query[knex.compatibleILIKE](
  105. knex.raw("concat_ws(' ', description, links.address, target, domains.address)"),
  106. "%" + params.search + "%"
  107. );
  108. }
  109. query.leftJoin("domains", "links.domain_id", "domains.id");
  110. return query;
  111. }
  112. async function getAdmin(match, params) {
  113. const query = knex("links").select(...selectable_admin);
  114. Object.entries(normalizeMatch(match)).forEach(([key, value]) => {
  115. query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
  116. });
  117. query
  118. .orderBy("links.id", "desc")
  119. .offset(params.skip)
  120. .limit(params.limit)
  121. if (params?.user) {
  122. const id = parseInt(params?.user);
  123. if (Number.isNaN(id)) {
  124. query[knex.compatibleILIKE]("users.email", "%" + params.user + "%");
  125. } else {
  126. query.andWhere("links.user_id", params.user);
  127. }
  128. }
  129. if (params?.search) {
  130. query[knex.compatibleILIKE](
  131. knex.raw("concat_ws(' ', description, links.address, target)"),
  132. "%" + params.search + "%"
  133. );
  134. }
  135. if (params?.domain) {
  136. query[knex.compatibleILIKE]("domains.address", "%" + params.domain + "%");
  137. }
  138. query.leftJoin("domains", "links.domain_id", "domains.id");
  139. query.leftJoin("users", "links.user_id", "users.id");
  140. return query;
  141. }
  142. async function find(match) {
  143. if (match.address && match.domain_id !== undefined && env.REDIS_ENABLED) {
  144. const key = redis.key.link(match.address, match.domain_id);
  145. const cachedLink = await redis.client.get(key);
  146. if (cachedLink) return JSON.parse(cachedLink);
  147. }
  148. const link = await knex("links")
  149. .select(...selectable)
  150. .where(normalizeMatch(match))
  151. .leftJoin("domains", "links.domain_id", "domains.id")
  152. .first();
  153. if (link && env.REDIS_ENABLED) {
  154. const key = redis.key.link(link.address, link.domain_id);
  155. redis.client.set(key, JSON.stringify(link), "EX", 60 * 15);
  156. }
  157. return link;
  158. }
  159. async function create(params) {
  160. let encryptedPassword = null;
  161. if (params.password) {
  162. const salt = await bcrypt.genSalt(12);
  163. encryptedPassword = await bcrypt.hash(params.password, salt);
  164. }
  165. let [link] = await knex(
  166. "links"
  167. ).insert(
  168. {
  169. password: encryptedPassword,
  170. domain_id: params.domain_id || null,
  171. user_id: params.user_id || null,
  172. address: params.address,
  173. description: params.description || null,
  174. expire_in: params.expire_in || null,
  175. target: params.target
  176. },
  177. "*"
  178. );
  179. // mysql doesn't return the whole link, but rather the id number only
  180. // so we need to fetch the link ourselves
  181. if (typeof link === "number") {
  182. link = await knex("links").where("id", link).first();
  183. }
  184. return link;
  185. }
  186. async function remove(match) {
  187. const link = await knex("links").where(match).first();
  188. if (!link) {
  189. return { isRemoved: false, error: "Could not find the link.", link: null }
  190. }
  191. const deletedLink = await knex("links").where("id", link.id).delete();
  192. if (env.REDIS_ENABLED) {
  193. redis.remove.link(link);
  194. }
  195. return { isRemoved: !!deletedLink, link };
  196. }
  197. async function batchRemove(match) {
  198. const query = knex("links");
  199. Object.entries(match).forEach(([key, value]) => {
  200. query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
  201. });
  202. const links = await query.clone();
  203. await query.delete();
  204. if (env.REDIS_ENABLED) {
  205. links.forEach(redis.remove.link);
  206. }
  207. }
  208. async function update(match, update) {
  209. if (update.password) {
  210. const salt = await bcrypt.genSalt(12);
  211. update.password = await bcrypt.hash(update.password, salt);
  212. }
  213. // if the links' adddress or domain is changed,
  214. // make sure to delete the original links from cache
  215. let links = []
  216. if (env.REDIS_ENABLED && (update.address || update.domain_id)) {
  217. links = await knex("links").select('*').where(match);
  218. }
  219. await knex("links")
  220. .where(match)
  221. .update({ ...update, updated_at: utils.dateToUTC(new Date()) });
  222. const updated_links = await knex("links")
  223. .select(selectable)
  224. .where(normalizeMatch(match))
  225. .leftJoin("domains", "links.domain_id", "domains.id");
  226. if (env.REDIS_ENABLED) {
  227. links.forEach(redis.remove.link);
  228. updated_links.forEach(redis.remove.link);
  229. }
  230. return updated_links;
  231. }
  232. function incrementVisit(match) {
  233. return knex("links").where(match).increment("visit_count", 1);
  234. }
  235. module.exports = {
  236. normalizeMatch,
  237. batchRemove,
  238. create,
  239. find,
  240. get,
  241. getAdmin,
  242. incrementVisit,
  243. remove,
  244. total,
  245. totalAdmin,
  246. update,
  247. }