From 4be7c7d4db16d3288d8a3d17ed87d1b203330736 Mon Sep 17 00:00:00 2001 From: progof <34421475+progof@users.noreply.github.com> Date: Sat, 28 Dec 2024 20:03:55 +0100 Subject: [PATCH] upd --- backend/package-lock.json | 161 ++++++++++++++++++++++++++++- backend/package.json | 1 + backend/src/admin/admin.service.ts | 137 +++++++++++++----------- backend/src/user/user.service.ts | 20 ++-- pnpm-lock.yaml | 9 ++ 5 files changed, 258 insertions(+), 70 deletions(-) diff --git a/backend/package-lock.json b/backend/package-lock.json index a83ac77..520c5c7 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -20,6 +20,7 @@ "form-data": "^4.0.1", "formidable": "^3.5.1", "jsonwebtoken": "^9.0.2", + "multer": "1.4.5-lts.1", "node-cron": "3.0.3", "nodemailer": "^6.9.14", "postgres": "3.4.4", @@ -34,6 +35,7 @@ "@types/express": "^4.17.21", "@types/express-session": "^1.18.0", "@types/jsonwebtoken": "^9.0.6", + "@types/multer": "^1.4.12", "@types/node-cron": "^3.0.11", "@types/pg": "^8.11.6", "drizzle-kit": "0.24.0", @@ -3621,6 +3623,15 @@ "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", "dev": true }, + "node_modules/@types/multer": { + "version": "1.4.12", + "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.12.tgz", + "integrity": "sha512-pQ2hoqvXiJt2FP9WQVLPRO+AmiIm/ZYkavPlIQnx282u4ZrVdztx0pkh3jjpQt0Kz+YI0YhSG264y08UJKoUQg==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, "node_modules/@types/node": { "version": "22.7.5", "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", @@ -3703,6 +3714,11 @@ "node": ">= 8" } }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" + }, "node_modules/arctic": { "resolved": "../node_modules/.pnpm/arctic@1.9.2/node_modules/arctic", "link": true @@ -3805,8 +3821,18 @@ "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } }, "node_modules/bytes": { "version": "3.1.2", @@ -3912,6 +3938,20 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -3956,6 +3996,11 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, "node_modules/cors": { "resolved": "../node_modules/.pnpm/cors@2.8.5/node_modules/cors", "link": true @@ -4679,6 +4724,11 @@ "node": ">=0.12.0" } }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + }, "node_modules/jsonwebtoken": { "resolved": "../node_modules/.pnpm/jsonwebtoken@9.0.2/node_modules/jsonwebtoken", "link": true @@ -4749,11 +4799,47 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, + "node_modules/multer": { + "version": "1.4.5-lts.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", + "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -4807,6 +4893,14 @@ "node": ">=0.10.0" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", @@ -4866,6 +4960,11 @@ "resolved": "../node_modules/.pnpm/postgres@3.4.4/node_modules/postgres", "link": true }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -4928,6 +5027,25 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5166,6 +5284,27 @@ "node": ">= 0.8" } }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, "node_modules/strnum": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", @@ -5241,6 +5380,11 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==" + }, "node_modules/typescript": { "version": "5.6.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", @@ -5284,6 +5428,11 @@ "node": ">= 0.8" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -5312,6 +5461,14 @@ "node": ">= 0.8" } }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, "node_modules/zod": { "resolved": "../node_modules/.pnpm/zod@3.23.8/node_modules/zod", "link": true diff --git a/backend/package.json b/backend/package.json index e5a554a..a04ee6c 100644 --- a/backend/package.json +++ b/backend/package.json @@ -26,6 +26,7 @@ "express": "^4.19.2", "express-session": "^1.18.0", "form-data": "^4.0.1", + "formdata-node": "^6.0.3", "formidable": "^3.5.1", "jsonwebtoken": "^9.0.2", "multer": "1.4.5-lts.1", diff --git a/backend/src/admin/admin.service.ts b/backend/src/admin/admin.service.ts index e1062d8..083202f 100644 --- a/backend/src/admin/admin.service.ts +++ b/backend/src/admin/admin.service.ts @@ -1,74 +1,95 @@ -import { createPagingObject, Database, Transaction, PagingObject, dateToUTCTimestamp } from "@/utils.js"; -import { adminsTable, usersTable } from "@db/schema.js"; +import { + createPagingObject, + Database, + Transaction, + PagingObject, + dateToUTCTimestamp, +} from "@/utils.js"; +import { + adminsTable, + usersTable, + googleAccountsTable, + passwordAccountsTable, +} from "@db/schema.js"; import { User } from "@shared/index.js"; import { z, ZodType } from "zod"; import { eq, count } from "drizzle-orm"; - export const userSchema = z.object({ - id: z.string(), - username: z.string(), - email: z.string(), - emailVerified: z.boolean(), - avatarKey: z.string().nullable(), - createdAt: z.number(), - role: z.string(), - score: z.number(), - level: z.number(), - currency: z.number(), - isAdmin: z.boolean(), + id: z.string(), + username: z.string(), + email: z.string(), + emailVerified: z.boolean(), + avatarKey: z.string().nullable(), + createdAt: z.number(), + role: z.string(), + score: z.number(), + level: z.number(), + currency: z.number(), + isAdmin: z.boolean(), + authMethods: z.array(z.union([z.literal("password"), z.literal("google")])), }) satisfies ZodType; export const getUsersPageQuerySchema = z.object({ - pageSize: z.coerce.number().int().positive(), - page: z.coerce.number().int().positive(), + pageSize: z.coerce.number().int().positive(), + page: z.coerce.number().int().positive(), }); export type GetUsersPageQuery = z.infer; export class AdminService { - constructor() {} - - - getUsersPage( - db: Database | Transaction, - options: GetUsersPageQuery, - ): Promise> { - const { page = 1, pageSize = 10 } = options; + constructor() {} - return db.transaction(async (tx) => { - const [totalItems, users] = await Promise.all([ - tx - .select({ count: count() }) - .from(usersTable) - .then((result) => result.at(0)?.count || 0), - tx - .select() - .from(usersTable) - .leftJoin(adminsTable, eq(usersTable.email, adminsTable.email)) - .limit(pageSize) - .offset((page - 1) * pageSize), - ]); + getUsersPage( + db: Database | Transaction, + options: GetUsersPageQuery + ): Promise> { + const { page = 1, pageSize = 10 } = options; - return createPagingObject({ - items: users.map((user) => ({ - id: user.users.id, - username: user.users.username, - email: user.users.email, - emailVerified: user.users.emailVerified, - avatarKey: user.users.avatarKey, - createdAt: dateToUTCTimestamp(user.users.createdAt), - role: user.users.role, - score: user.users.score, - level: user.users.level, - currency: user.users.currency, - isAdmin: user.admins !== null, - })), - page, - pageSize, - totalItems, - }); - }); - } + return db.transaction(async (tx) => { + const [totalItems, users] = await Promise.all([ + tx + .select({ count: count() }) + .from(usersTable) + .then((result) => result.at(0)?.count || 0), + tx + .select() + .from(usersTable) + .leftJoin(adminsTable, eq(usersTable.email, adminsTable.email)) + .leftJoin( + googleAccountsTable, + eq(usersTable.id, googleAccountsTable.userId) + ) + .leftJoin( + passwordAccountsTable, + eq(usersTable.id, passwordAccountsTable.userId) + ) + .limit(pageSize) + .offset((page - 1) * pageSize), + ]); -} \ No newline at end of file + return createPagingObject({ + items: users.map((user) => ({ + id: user.users.id, + username: user.users.username, + email: user.users.email, + emailVerified: user.users.emailVerified, + avatarKey: user.users.avatarKey, + createdAt: dateToUTCTimestamp(user.users.createdAt), + role: user.users.role, + score: user.users.score, + level: user.users.level, + currency: user.users.currency, + isAdmin: user.admins !== null, + authMethods: [ + user.google_accounts ? "google" : null, + user.password_accounts ? "password" : null, + ].filter((method) => method !== null) as Array<"password" | "google">, + })), + page, + pageSize, + totalItems, + }); + }); + } +} diff --git a/backend/src/user/user.service.ts b/backend/src/user/user.service.ts index c9885a3..dcc23f3 100644 --- a/backend/src/user/user.service.ts +++ b/backend/src/user/user.service.ts @@ -5,7 +5,7 @@ import { passwordAccountsTable, } from "@db/schema.js"; import { z, ZodType } from "zod"; -import { DeleteObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"; +import { PutObjectCommand } from "@aws-sdk/client-s3"; import { eq } from "drizzle-orm"; import { HTTPError, dateToUTCTimestamp } from "@/utils.js"; import { User } from "@shared/index.js"; @@ -14,7 +14,7 @@ import { Database } from "@/utils.js"; import { config } from "@/config.js"; import sharp from "sharp"; import { s3 } from "@/s3.js"; -import multer from "multer"; +// import multer from "multer"; export const userSchema = z.object({ id: z.string(), @@ -79,14 +79,14 @@ const uploadUserAvatar = async (key: string, avatar: File) => { ); }; -const deleteUserAvatar = async (key: string) => { - await s3.send( - new DeleteObjectCommand({ - Bucket: config.MINIO_BUCKET, - Key: key, - }) - ); -}; +// const deleteUserAvatar = async (key: string) => { +// await s3.send( +// new DeleteObjectCommand({ +// Bucket: config.MINIO_BUCKET, +// Key: key, +// }) +// ); +// }; export class UserService { constructor(private readonly db: Database | Transaction) {} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02f2ce2..541aa67 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,6 +46,9 @@ importers: form-data: specifier: ^4.0.1 version: 4.0.1 + formdata-node: + specifier: ^6.0.3 + version: 6.0.3 formidable: specifier: ^3.5.1 version: 3.5.1 @@ -2360,6 +2363,10 @@ packages: resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} engines: {node: '>= 6'} + formdata-node@6.0.3: + resolution: {integrity: sha512-8e1++BCiTzUno9v5IZ2J6bv4RU+3UKDmqWUQD0MIMVCd9AdhWkO1gw57oo1mNEX1dMq2EGI+FbWz4B92pscSQg==} + engines: {node: '>= 18'} + formidable@3.5.1: resolution: {integrity: sha512-WJWKelbRHN41m5dumb0/k8TeAx7Id/y3a+Z7QfhxP/htI9Js5zYaEDtG8uMgG0vM0lOlqnmjE99/kfpOYi/0Og==} @@ -5794,6 +5801,8 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + formdata-node@6.0.3: {} + formidable@3.5.1: dependencies: dezalgo: 1.0.4