user.queries.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. const { addMinutes } = require("date-fns");
  2. const { randomUUID } = require("node:crypto");
  3. const { ROLES } = require("../consts");
  4. const utils = require("../utils");
  5. const redis = require("../redis");
  6. const knex = require("../knex");
  7. const env = require("../env");
  8. async function find(match) {
  9. if ((match.id || match.apikey) && env.REDIS_ENABLED) {
  10. const key = redis.key.user(match.id || match.apikey);
  11. const cachedUser = await redis.client.get(key);
  12. if (cachedUser) return JSON.parse(cachedUser);
  13. }
  14. const query = knex("users");
  15. Object.entries(match).forEach(([key, value]) => {
  16. query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
  17. });
  18. const user = await query.first();
  19. if (user && env.REDIS_ENABLED) {
  20. if (match.id) {
  21. const idKey = redis.key.user(user.id);
  22. redis.client.set(idKey, JSON.stringify(user), "EX", 60 * 15);
  23. }
  24. if (match.apikey) {
  25. const apikeyKey = redis.key.user(user.apikey);
  26. redis.client.set(apikeyKey, JSON.stringify(user), "EX", 60 * 15);
  27. }
  28. }
  29. return user;
  30. }
  31. async function add(params, user) {
  32. const data = {
  33. email: params.email,
  34. password: params.password,
  35. ...(params.role && { role: params.role }),
  36. ...(params.verified !== undefined && { verified: params.verified }),
  37. verification_token: randomUUID(),
  38. verification_expires: utils.dateToUTC(addMinutes(new Date(), 60))
  39. };
  40. if (user) {
  41. await knex("users")
  42. .where("id", user.id)
  43. .update({ ...data, updated_at: utils.dateToUTC(new Date()) });
  44. } else {
  45. await knex("users").insert(data);
  46. }
  47. if (env.REDIS_ENABLED) {
  48. redis.remove.user(user);
  49. }
  50. return {
  51. ...user,
  52. ...data
  53. };
  54. }
  55. async function update(match, update, methods) {
  56. const { user, updated_user } = await knex.transaction(async function(trx) {
  57. const query = trx("users");
  58. Object.entries(match).forEach(([key, value]) => {
  59. query.andWhere(key, ...(Array.isArray(value) ? value : [value]));
  60. });
  61. const user = await query.select("id").first();
  62. if (!user) return {};
  63. const updateQuery = trx("users").where("id", user.id);
  64. if (methods?.increments) {
  65. methods.increments.forEach(columnName => {
  66. updateQuery.increment(columnName);
  67. });
  68. }
  69. await updateQuery.update({ ...update, updated_at: utils.dateToUTC(new Date()) });
  70. const updated_user = await trx("users").where("id", user.id).first();
  71. return { user, updated_user };
  72. });
  73. if (env.REDIS_ENABLED && user) {
  74. redis.remove.user(user);
  75. redis.remove.user(updated_user);
  76. }
  77. return updated_user;
  78. }
  79. async function remove(user) {
  80. const deletedUser = await knex("users").where("id", user.id).delete();
  81. if (env.REDIS_ENABLED) {
  82. redis.remove.user(user);
  83. }
  84. return !!deletedUser;
  85. }
  86. const selectable_admin = [
  87. "users.id",
  88. "users.email",
  89. "users.verified",
  90. "users.role",
  91. "users.banned",
  92. "users.banned_by_id",
  93. "users.created_at",
  94. "users.updated_at"
  95. ];
  96. function normalizeMatch(match) {
  97. const newMatch = { ...match }
  98. if (newMatch.banned !== undefined) {
  99. newMatch["users.banned"] = newMatch.banned;
  100. delete newMatch.banned;
  101. }
  102. return newMatch;
  103. }
  104. async function getAdmin(match, params) {
  105. const query = knex("users")
  106. .select(...selectable_admin)
  107. .select("l.links_count")
  108. .select("d.domains")
  109. .fromRaw("users")
  110. .where(normalizeMatch(match))
  111. .offset(params.skip)
  112. .limit(params.limit)
  113. .orderBy("users.id", "desc")
  114. .groupBy(1)
  115. .groupBy("l.links_count")
  116. .groupBy("d.domains");
  117. if (params?.search) {
  118. const id = parseInt(params?.search);
  119. if (Number.isNaN(id)) {
  120. query[knex.compatibleILIKE]("users.email", "%" + params?.search + "%");
  121. } else {
  122. query.andWhere("users.id", params?.search);
  123. }
  124. }
  125. if (params?.domains !== undefined) {
  126. query.andWhere("d.domains", params?.domains ? "is not" : "is", null);
  127. }
  128. if (params?.links !== undefined) {
  129. query.andWhere("links_count", params?.links ? "is not" : "is", null);
  130. }
  131. query.leftJoin(
  132. knex("domains")
  133. .select("user_id", knex.isMySQL
  134. ? knex.raw("group_concat(address SEPARATOR ', ') AS domains")
  135. : knex.raw("string_agg(address, ', ') AS domains")
  136. )
  137. .groupBy("user_id").as("d"),
  138. "users.id",
  139. "d.user_id"
  140. )
  141. query.leftJoin(
  142. knex("links").select("user_id").count("* as links_count").groupBy("user_id").as("l"),
  143. "users.id",
  144. "l.user_id"
  145. );
  146. return query;
  147. }
  148. async function totalAdmin(match, params) {
  149. const query = knex("users")
  150. .count("* as count")
  151. .fromRaw("users")
  152. .where(normalizeMatch(match));
  153. if (params?.search) {
  154. const id = parseInt(params?.search);
  155. if (Number.isNaN(id)) {
  156. query[knex.compatibleILIKE]("users.email", "%" + params?.search + "%");
  157. } else {
  158. query.andWhere("users.id", params?.search);
  159. }
  160. }
  161. if (params?.domains !== undefined) {
  162. query.andWhere("domains", params?.domains ? "is not" : "is", null);
  163. query.leftJoin(
  164. knex("domains")
  165. .select("user_id", knex.isMySQL
  166. ? knex.raw("group_concat(address SEPARATOR ', ') AS domains")
  167. : knex.raw("string_agg(address, ', ') AS domains")
  168. )
  169. .groupBy("user_id").as("d"),
  170. "users.id",
  171. "d.user_id"
  172. );
  173. }
  174. if (params?.links !== undefined) {
  175. query.andWhere("links", params?.links ? "is not" : "is", null);
  176. query.leftJoin(
  177. knex("links").select("user_id").count("* as links").groupBy("user_id").as("l"),
  178. "users.id",
  179. "l.user_id"
  180. );
  181. }
  182. const [{ count }] = await query;
  183. return typeof count === "number" ? count : parseInt(count);
  184. }
  185. async function create(params) {
  186. let [user] = await knex("users").insert({
  187. email: params.email,
  188. password: params.password,
  189. role: params.role ?? ROLES.USER,
  190. verified: params.verified ?? false,
  191. banned: params.banned ?? false,
  192. }, "*");
  193. // mysql doesn't return the whole user, but rather the id number only
  194. // so we need to fetch the user ourselves
  195. if (typeof user === "number") {
  196. user = await knex("users").where("id", user).first();
  197. }
  198. return user;
  199. }
  200. // check if there exists a user
  201. async function findAny() {
  202. if (env.REDIS_ENABLED) {
  203. const anyuser = await redis.client.get("any-user");
  204. if (anyuser) return true;
  205. }
  206. const anyuser = await knex("users").select("id").first();
  207. if (env.REDIS_ENABLED && anyuser) {
  208. redis.client.set("any-user", JSON.stringify(anyuser), "EX", 60 * 5);
  209. }
  210. return !!anyuser;
  211. }
  212. module.exports = {
  213. add,
  214. create,
  215. find,
  216. findAny,
  217. getAdmin,
  218. remove,
  219. totalAdmin,
  220. update,
  221. }