Переглянути джерело

Добавлено добавление ребенка и вынесен модуль auth

Vadim 2 місяців тому
батько
коміт
dd3117bbc2
29 змінених файлів з 796 додано та 89 видалено
  1. 2 2
      src/api/v_0.1.0/api.ts
  2. 30 5
      src/api/v_0.1.0/client/auth-api.ts
  3. 20 0
      src/api/v_0.1.0/client/client-event-api.ts
  4. 102 0
      src/api/v_0.1.0/client/client-users-api.ts
  5. 10 2
      src/db/db-schema.ts
  6. 6 6
      src/main.ts
  7. 1 1
      src/modules/client/activities/c-act-controller.ts
  8. 1 1
      src/modules/client/activities/participant-entities/c-pe-controller.ts
  9. 3 1
      src/modules/client/custom-fields/validators/general-validators-utils.ts
  10. 0 8
      src/modules/client/custom-fields/validators/specific-validators-utils.ts
  11. 0 8
      src/modules/client/custom-fields/validators/validation-functions.ts
  12. 21 1
      src/modules/client/event/c-event-controller.ts
  13. 7 1
      src/modules/client/event/c-event-router.ts
  14. 59 0
      src/modules/client/event/c-event-service.ts
  15. 1 1
      src/modules/client/shop/c-orders-controller.ts
  16. 1 1
      src/modules/client/shop/cart/c-cart-controller.ts
  17. 108 35
      src/modules/client/users/auth/routers/auth-controller.ts
  18. 7 0
      src/modules/client/users/auth/routers/auth-router.ts
  19. 0 0
      src/modules/client/users/auth/services/perms-service.ts
  20. 1 1
      src/modules/client/users/auth/services/session-service.ts
  21. 0 0
      src/modules/client/users/auth/services/token-service.ts
  22. 0 0
      src/modules/client/users/auth/services/user-auth-service.ts
  23. 3 6
      src/modules/client/users/auth/services/user-registration-service.ts
  24. 0 0
      src/modules/client/users/auth/types/token-playload-type.ts
  25. 35 4
      src/modules/client/users/c-users-controller.ts
  26. 33 0
      src/modules/client/users/c-users-router.ts
  27. 87 2
      src/modules/client/users/c-users-service.ts
  28. 245 0
      src/modules/client/users/children-controller.ts
  29. 13 3
      src/modules/client/users/confirm-pins/confirm-pins-service.ts

+ 2 - 2
src/api/v_0.1.0/api.ts

@@ -1,4 +1,4 @@
-import { authApi } from "./shared/auth-api.js";
+import { authApi } from "./client/auth-api.js";
 import { clientPartEntitiesApi } from "./client/client-pe-api.js";
 import { clientUsersApi } from "./client/client-users-api.js";
 import { clientEventApi } from "./client/client-event-api.js";
@@ -7,7 +7,6 @@ import { actTypes } from "./types/act-types.js";
 import { clientShopApi } from "./client/client-shop-api.js";
 
 export const api = {
-  auth: authApi,
   admin: {},
   client: {
     pe: clientPartEntitiesApi,
@@ -15,6 +14,7 @@ export const api = {
     event: clientEventApi,
     activities: clientActivitiesApi,
     shop: clientShopApi,
+    auth: authApi,
   },
 };
 

+ 30 - 5
src/api/v_0.1.0/shared/auth-api.ts → src/api/v_0.1.0/client/auth-api.ts

@@ -1,6 +1,21 @@
 import { z } from "zod";
+import {
+  CustomFieldWithValidators,
+  InputFieldValue,
+} from "../types/custom-fields-types.js";
 
 class AuthApi {
+  GET_UserRegData = {
+    res: z.object({
+      code: z.enum(["success"]),
+      fields: z.array(
+        CustomFieldWithValidators.extend({
+          userEfId: z.string().uuid(),
+        }),
+      ),
+    }),
+  };
+
   // /auth/registration
   POST_Registration = {
     req: z.object({
@@ -22,11 +37,21 @@ class AuthApi {
 
   // /auth/confirm-registration
   POST_ConfirmRegistration = {
-    req: z.object({
-      password: z.string(),
-      transactionId: z.string().uuid(),
-      confirmPin: z.string(),
-    }),
+    req: {
+      formData: {
+        body: z.object({
+          password: z.string(),
+          transactionId: z.string().uuid(),
+          confirmPin: z.string(),
+          fields: z.array(
+            z.object({
+              userEfId: z.string().uuid(),
+              value: InputFieldValue,
+            }),
+          ),
+        }),
+      },
+    },
     res: z.discriminatedUnion("code", [
       z.object({
         code: z.literal("registered"),

+ 20 - 0
src/api/v_0.1.0/client/client-event-api.ts

@@ -14,6 +14,26 @@ class ClientEventApi {
       }),
     }),
   };
+
+  GET_EventDocs = {
+    req: {
+      query: z.object({
+        codes: z.array(z.string()),
+      }),
+    },
+    res: z.object({
+      code: z.enum(["success"]),
+      docs: z.array(
+        z.object({
+          eventDocId: z.string().uuid(),
+          code: z.string(),
+          eventId: z.string().uuid(),
+          name: z.string(),
+          link: z.string().url(),
+        }),
+      ),
+    }),
+  };
 }
 
 export const clientEventApi = new ClientEventApi();

+ 102 - 0
src/api/v_0.1.0/client/client-users-api.ts

@@ -1,11 +1,26 @@
 import {
   CustomFieldWithValidatorsAndValue,
+  CustomFieldWithValue,
   InputFieldValue,
 } from "../types/custom-fields-types.js";
 import { z } from "zod";
 
 class ClientUsersApi {
   GET_UserEventData = {
+    res: z.object({
+      code: z.enum(["success"]),
+      userData: z.object({
+        fields: z.array(
+          CustomFieldWithValue.extend({
+            userEfId: z.string().uuid(),
+          }),
+        ),
+        isChild: z.boolean(),
+      }),
+    }),
+  };
+
+  GET_UserEventDataForPatch = {
     res: z.object({
       code: z.enum(["success"]),
       userData: z.object({
@@ -36,6 +51,93 @@ class ClientUsersApi {
       code: z.enum(["success"]),
     }),
   };
+
+  POST_Child = {
+    req: {
+      formData: {
+        body: z.object({
+          fields: z.array(
+            z.object({
+              userEfId: z.string().uuid(),
+              value: InputFieldValue,
+            }),
+          ),
+        }),
+      },
+    },
+    res: z.object({
+      code: z.enum(["success"]),
+    }),
+  };
+
+  PATCH_Child = {
+    req: {
+      params: {
+        childId: z.string().uuid(),
+      },
+      formData: {
+        body: z.object({
+          fields: z.array(
+            z.object({
+              userEfId: z.string().uuid(),
+              value: InputFieldValue,
+            }),
+          ),
+        }),
+      },
+    },
+    res: z.object({
+      code: z.enum(["success"]),
+    }),
+  };
+
+  GET_Children = {
+    res: z.object({
+      code: z.enum(["success"]),
+      children: z.array(
+        z.object({
+          userId: z.string().uuid(),
+          userIdentity: z.string(),
+        }),
+      ),
+    }),
+  };
+
+  GET_Child = {
+    req: {
+      params: z.object({
+        childId: z.string().uuid(),
+      }),
+    },
+    res: z.object({
+      code: z.enum(["success"]),
+      userData: z.object({
+        fields: z.array(
+          CustomFieldWithValue.extend({
+            userEfId: z.string().uuid(),
+          }),
+        ),
+      }),
+    }),
+  };
+
+  GET_ChildForPatch = {
+    req: {
+      params: {
+        childId: z.string().uuid(),
+      },
+    },
+    res: z.object({
+      code: z.enum(["success"]),
+      userData: z.object({
+        fields: z.array(
+          CustomFieldWithValidatorsAndValue.extend({
+            userEfId: z.string().uuid(),
+          }),
+        ),
+      }),
+    }),
+  };
 }
 
 export const clientUsersApi = new ClientUsersApi();

+ 10 - 2
src/db/db-schema.ts

@@ -5,10 +5,11 @@ const DbSchema = {
   usr: {
     users: {
       userId: z.string().uuid(),
-      email: z.string().email(),
-      password: z.string(),
+      email: z.string().email().nullable(),
+      password: z.string().nullable(),
       wrongPassTries: z.number().int().default(0),
       isChild: z.boolean(),
+      parentId: z.string().uuid().nullable(),
     },
     roles: {
       roleId: z.string().uuid(),
@@ -80,6 +81,13 @@ const DbSchema = {
       code: z.string(),
       name: z.string(),
     },
+    eventDocs: {
+      eventDocId: z.string().uuid(),
+      code: z.string(),
+      eventId: z.string().uuid(),
+      name: z.string(),
+      link: z.string().url(),
+    },
     userEventFields: {
       // Таблица user_event_fields из новой БД
       userEfId: z.string().uuid(),

+ 6 - 6
src/main.ts

@@ -43,19 +43,19 @@ logger.info("Импорт роутеров...");
 import eventCodeMiddleware from "#middlewares/event-code-middleware.js";
 app.use(eventCodeMiddleware());
 // роутеры
-// import authMiddleware from "./middlewares/auth-middleware.js";
-
 // users-management
-import authRouter from "./modules/users/auth/routers/auth-router.js";
+import authRouter from "./modules/client/users/auth/routers/auth-router.js";
 app.use("/api/auth/", authRouter);
 
+// event
+import cEventRouter from "./modules/client/event/c-event-router.js";
+app.use("/api/client/event/", cEventRouter);
+
+// auth
 import authMiddleware from "#middlewares/auth-middleware.js";
 app.use("/api/", authMiddleware());
 
 // -- CLIENT --
-// event
-import cEventRouter from "./modules/client/event/c-event-router.js";
-app.use("/api/client/event/", cEventRouter);
 
 // user
 import cUsersRouter from "./modules/client/users/c-users-router.js";

+ 1 - 1
src/modules/client/activities/c-act-controller.ts

@@ -12,7 +12,7 @@ import { z } from "zod";
 import { RouterUtils } from "#utils/router-utils.js";
 
 import { Request, Response } from "express";
-import sessionService from "#modules/users/auth/services/session-service.js";
+import sessionService from "#modules/client/users/auth/services/session-service.js";
 import { apiTypes } from "#api/current-api.js";
 import { validateActValidators } from "./validators/act-validators.js";
 

+ 1 - 1
src/modules/client/activities/participant-entities/c-pe-controller.ts

@@ -12,7 +12,7 @@ import { z } from "zod";
 import { RouterUtils } from "#utils/router-utils.js";
 
 import { Request, Response } from "express";
-import sessionService from "#modules/users/auth/services/session-service.js";
+import sessionService from "#modules/client/users/auth/services/session-service.js";
 import { ApiError } from "#exceptions/api-error.js";
 import { v4, v7 } from "uuid";
 import { cPeService } from "./c-pe-service.js";

+ 3 - 1
src/modules/client/custom-fields/validators/general-validators-utils.ts

@@ -1,5 +1,7 @@
 import type { Dayjs } from "dayjs";
 import dayjs from "dayjs";
+import customParseFormat from "dayjs/plugin/customParseFormat.js";
+dayjs.extend(customParseFormat);
 
 class GeneralValidatorUtils {
   validateAge(date: string, years: number): boolean {
@@ -12,7 +14,7 @@ class GeneralValidatorUtils {
     // 2. Парсим дату рождения.
     // Третий аргумент `true` включает строгий режим парсинга для указанного формата.
     // Это означает, что дата должна точно соответствовать 'YYYY-MM-DD'.
-    const birthDate: Dayjs = dayjs(date, "DD.MM.YYYY", true);
+    const birthDate: Dayjs = dayjs(date, "YYYY-MM-DD", true);
 
     // 3. Проверяем валидность даты.
     if (!birthDate.isValid()) {

+ 0 - 8
src/modules/client/custom-fields/validators/specific-validators-utils.ts

@@ -1,5 +1,3 @@
-import { logger } from "#plugins/logger.js";
-
 // Декларация для поддержки webkitAudioContext в TypeScript
 declare global {
   interface Window {
@@ -38,12 +36,6 @@ class SpecificValidatorUtils {
       throw new Error(`Error processing audio in Node.js: ${message}`);
     }
   }
-
-  async validateIsAdultOrChild(date: string): Promise<true | string> {
-    logger.silly(date);
-    // TODO: Реализовать
-    return true;
-  }
 }
 
 export const specificValidatorUtils = new SpecificValidatorUtils();

+ 0 - 8
src/modules/client/custom-fields/validators/validation-functions.ts

@@ -119,14 +119,6 @@ export const getValidationFunc = (
         }
       };
     }
-    case "isAdultOrChild": {
-      return async (v: unknown) => {
-        if (v === null) return true;
-        if (typeof v !== "string")
-          return generalValidatorUtils.getTypeError("string");
-        return await specificValidatorUtils.validateIsAdultOrChild(v);
-      };
-    }
 
     // Числовые валидаторы
     case "max":

+ 21 - 1
src/modules/client/event/c-event-controller.ts

@@ -1,7 +1,8 @@
 import { api } from "#api/current-api.js";
-import sessionService from "#modules/users/auth/services/session-service.js";
+import sessionService from "#modules/client/users/auth/services/session-service.js";
 import { RouterUtils } from "#utils/router-utils.js";
 import type { Request, Response } from "express";
+import { cEventService } from "./c-event-service.js";
 
 class ClientEventController {
   async getEvent(req: Request, res: Response) {
@@ -12,6 +13,25 @@ class ClientEventController {
       event,
     });
   }
+
+  async getEventDocs(req: Request, res: Response) {
+    const codes = api.client.event.GET_EventDocs.req.query.shape.codes.parse(
+      req.query.codes && typeof req.query.codes === "string"
+        ? JSON.parse(req.query.codes)
+        : req.query.codes,
+    );
+    const event = await sessionService.getCurrentEventFromReq(req);
+
+    const docs = await cEventService.getEventDocs({
+      eventId: event.eventId,
+      codes: codes,
+    });
+
+    RouterUtils.validAndSendResponse(api.client.event.GET_EventDocs.res, res, {
+      code: "success",
+      docs: [...docs],
+    });
+  }
 }
 
 export const clientEventController = new ClientEventController();

+ 7 - 1
src/modules/client/event/c-event-router.ts

@@ -1,8 +1,14 @@
 import { Router } from "express";
 import { clientEventController } from "./c-event-controller.js";
+import { RouterUtils } from "#utils/router-utils.js";
 
 const router = Router();
 
-router.get("/", clientEventController.getEvent);
+router.get("/", RouterUtils.asyncHandler(clientEventController.getEvent));
+
+router.get(
+  "/docs",
+  RouterUtils.asyncHandler(clientEventController.getEventDocs),
+);
 
 export default router;

+ 59 - 0
src/modules/client/event/c-event-service.ts

@@ -0,0 +1,59 @@
+import { DbSchema } from "#db/db-schema.js";
+import { selPool } from "#db/db.js";
+import { logger } from "#plugins/logger.js";
+import { sql } from "slonik";
+import { z } from "zod";
+
+class ClientEventService {
+  async getEventDocs({ eventId, codes }: { eventId: string; codes: string[] }) {
+    logger.info({
+      eventId,
+      codes,
+      sql: sql.type(
+        z.object({
+          eventDocId: DbSchema.ev.eventDocs.eventDocId,
+          code: DbSchema.ev.eventDocs.code,
+          eventId: DbSchema.ev.eventDocs.eventId,
+          name: DbSchema.ev.eventDocs.name,
+          link: DbSchema.ev.eventDocs.link,
+        }),
+      )`
+      select
+          event_doc_id as "eventDocId",
+          code,
+          event_id as "eventId",
+          name,
+          link
+      from
+          ev.event_docs
+      where
+          event_id = ${eventId}
+          and code in (${sql.join(codes, sql.fragment`, `)})`.sql,
+    });
+
+    return await selPool.any(
+      sql.type(
+        z.object({
+          eventDocId: DbSchema.ev.eventDocs.eventDocId,
+          code: DbSchema.ev.eventDocs.code,
+          eventId: DbSchema.ev.eventDocs.eventId,
+          name: DbSchema.ev.eventDocs.name,
+          link: DbSchema.ev.eventDocs.link,
+        }),
+      )`
+        select
+            event_doc_id as "eventDocId",
+            code,
+            event_id as "eventId",
+            name,
+            link
+        from
+            ev.event_docs
+        where
+            event_id = ${eventId}
+            and code in (${sql.join(codes, sql.fragment`, `)})`,
+    );
+  }
+}
+
+export const cEventService = new ClientEventService();

+ 1 - 1
src/modules/client/shop/c-orders-controller.ts

@@ -1,4 +1,4 @@
-import sessionService from "#modules/users/auth/services/session-service.js";
+import sessionService from "#modules/client/users/auth/services/session-service.js";
 import { Request, Response } from "express";
 import { cartService } from "./cart/cart-service.js";
 import { ApiError } from "#exceptions/api-error.js";

+ 1 - 1
src/modules/client/shop/cart/c-cart-controller.ts

@@ -1,7 +1,7 @@
 import { api } from "#api/current-api.js";
 import { Request, Response } from "express";
 import { cartService } from "./cart-service.js";
-import sessionService from "#modules/users/auth/services/session-service.js";
+import sessionService from "#modules/client/users/auth/services/session-service.js";
 import { ApiError } from "#exceptions/api-error.js";
 import { RouterUtils } from "#utils/router-utils.js";
 

+ 108 - 35
src/modules/users/auth/routers/auth-controller.ts → src/modules/client/users/auth/routers/auth-controller.ts

@@ -14,29 +14,45 @@ import { z } from "zod";
 import bcript from "bcrypt";
 import { v7 as uuidv7 } from "uuid";
 
-import tokenService from "../services/token-service.js";
 import { UserAuthService } from "../services/user-auth-service.js";
-import { ConfirmPinsService } from "#modules/users/confirm-pins/confirm-pins-service.js";
+import { ConfirmPinsService } from "#modules/client/users/confirm-pins/confirm-pins-service.js";
 import { RouterUtils } from "#utils/router-utils.js";
 import { config } from "#config";
 import { Request, Response } from "express";
+import { cUsersService } from "../../c-users-service.js";
+import sessionService from "../services/session-service.js";
+import tokenService from "../services/token-service.js";
+import { cCustomFieldsValidateService } from "#modules/client/custom-fields/c-cf-validate-service.js";
 
 class authController {
   // --- Регистрация ---
+  async getUserRegData(req: Request, res: Response) {
+    const event = await sessionService.getCurrentEventFromReq(req);
+
+    const regData = await cUsersService.getUserEventFieldsWithValidators(
+      event.eventId,
+    );
+
+    RouterUtils.validAndSendResponse(api.client.auth.GET_UserRegData.res, res, {
+      code: "success",
+      fields: [...regData],
+    });
+  }
+
   async register(
     req: Request,
     res: Response,
     // next: NextFunction
   ) {
     // валидация запроса
-    const { email } = api.auth.POST_Registration.req.parse(req.body);
+    const { email } = api.client.auth.POST_Registration.req.parse(req.body);
 
     const isUserExist = await UserAuthService.checkUserExistByEmail(email);
 
     // если пользователь уже зарегистрирован
     if (isUserExist) {
       RouterUtils.validAndSendResponse(
-        api.auth.POST_Registration.res,
+        api.client.auth.POST_Registration.res,
         res,
         { code: "alreadyExists" },
         400,
@@ -48,10 +64,14 @@ class authController {
     // отправка пина
     const transactionId = uuidv7();
     try {
-      await ConfirmPinsService.sendConfirmPin(transactionId, email);
+      await ConfirmPinsService.sendConfirmPin({
+        transactionId,
+        email,
+        actionType: "registration",
+      });
     } catch {
       RouterUtils.validAndSendResponse(
-        api.auth.POST_Registration.res,
+        api.client.auth.POST_Registration.res,
         res,
         { code: "pinIsNotSent" },
         400,
@@ -59,16 +79,25 @@ class authController {
       return;
     }
 
-    RouterUtils.validAndSendResponse(api.auth.POST_Registration.res, res, {
-      code: "pinIsSent",
-      transactionId: transactionId,
-    });
+    RouterUtils.validAndSendResponse(
+      api.client.auth.POST_Registration.res,
+      res,
+      {
+        code: "pinIsSent",
+        transactionId: transactionId,
+      },
+    );
   }
 
   async confirmRegistration(req: Request, res: Response) {
     // валидация запроса
-    const { password, transactionId, confirmPin } =
-      api.auth.POST_ConfirmRegistration.req.parse(req.body);
+    const { password, transactionId, confirmPin, fields } =
+      api.client.auth.POST_ConfirmRegistration.req.formData.body.parse(
+        JSON.parse(req.body.body),
+      );
+
+    const event = await sessionService.getCurrentEventFromReq(req);
+    const files = req.files;
 
     // проверка пина
     const pinInfo = await ConfirmPinsService.checkConfirmPin(
@@ -79,7 +108,7 @@ class authController {
     switch (pinInfo.status) {
       case "rotten": {
         RouterUtils.validAndSendResponse(
-          api.auth.POST_ConfirmRegistration.res,
+          api.client.auth.POST_ConfirmRegistration.res,
           res,
           { code: "pinIsRotten" },
           400,
@@ -88,7 +117,7 @@ class authController {
       }
       case "tooManyTries": {
         RouterUtils.validAndSendResponse(
-          api.auth.POST_ConfirmRegistration.res,
+          api.client.auth.POST_ConfirmRegistration.res,
           res,
           { code: "tooManyTries" },
           400,
@@ -97,7 +126,7 @@ class authController {
       }
       case "wrong": {
         RouterUtils.validAndSendResponse(
-          api.auth.POST_ConfirmRegistration.res,
+          api.client.auth.POST_ConfirmRegistration.res,
           res,
           {
             code: "pinIsWrong",
@@ -113,15 +142,55 @@ class authController {
     const email = pinInfo.email;
     // регистрация
     const hashPassword = await bcript.hash(password, 3);
-    const userId = uuidv7();
-    await updPool.query(
-      sql.unsafe`
-          insert into usr.users 
-            (user_id, email, password) 
-          values 
-            (${userId}, ${email}, ${hashPassword})`,
+
+    // поля пользователя
+    const userData = await cUsersService.getUserEventFieldsWithValidators(
+      event.eventId,
     );
 
+    const refFields = userData.map((f) => ({
+      ...f,
+      idKey: "userEfId",
+    }));
+
+    // валидация полей
+    const validationResult =
+      await cCustomFieldsValidateService.processAndValidateFields({
+        inputFields: fields,
+        referenceFields: refFields,
+        files,
+        idKey: "userEfId",
+        addOldValue: false,
+      });
+
+    if (!validationResult.isValid)
+      throw ApiError.BadRequest(
+        "fieldsValidationFailed",
+        JSON.stringify(validationResult.messages),
+      );
+
+    const validatedFields = validationResult.checkedfields;
+    // вставляем в базу и сохраняем файлы
+    const userId = uuidv7();
+    await updPool.transaction(async (tr) => {
+      await tr.query(
+        sql.unsafe`
+            insert into usr.users 
+              (user_id, email, password) 
+            values 
+              (${userId}, ${email}, ${hashPassword})`,
+      );
+
+      await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({
+        tr,
+        parentId: userId,
+        action: "userProfile",
+        inputFields: validatedFields,
+        files,
+        isDeleteBefore: false,
+      });
+    });
+
     // токены
     const { accessToken, refreshToken } = tokenService.generateTokens({
       email,
@@ -132,7 +201,7 @@ class authController {
     tokenService.setRefreshTokenInCookie(res, refreshToken);
 
     RouterUtils.validAndSendResponse(
-      api.auth.POST_ConfirmRegistration.res,
+      api.client.auth.POST_ConfirmRegistration.res,
       res,
       {
         code: "registered",
@@ -147,7 +216,7 @@ class authController {
 
   async login(req: Request, res: Response) {
     // валидация запроса
-    const { email, password } = api.auth.POST_Login.req.parse(req.body);
+    const { email, password } = api.client.auth.POST_Login.req.parse(req.body);
 
     // поиск юзера
     const user = await selPool.maybeOne(
@@ -169,7 +238,7 @@ class authController {
     );
     if (!user) {
       RouterUtils.validAndSendResponse(
-        api.auth.POST_Login.res,
+        api.client.auth.POST_Login.res,
         res,
         {
           code: "userNotFound",
@@ -182,7 +251,7 @@ class authController {
     // если количество попыток превышено
     if (user.wrongPassTries > config.PASSWORD_MAX_TRIES - 1) {
       RouterUtils.validAndSendResponse(
-        api.auth.POST_Login.res,
+        api.client.auth.POST_Login.res,
         res,
         {
           code: "tooManyTries",
@@ -195,13 +264,13 @@ class authController {
     }
 
     // проверка пароля
-    const isPassEquals = await bcript.compare(password, user.password);
+    const isPassEquals = await bcript.compare(password, user.password ?? "");
     if (!isPassEquals) {
       await UserAuthService.authTriesIncrement(user.userId);
       const triesRemained = config.PASSWORD_MAX_TRIES - 1 - user.wrongPassTries;
 
       RouterUtils.validAndSendResponse(
-        api.auth.POST_Login.res,
+        api.client.auth.POST_Login.res,
         res,
         {
           code: "passIsWrong",
@@ -225,7 +294,7 @@ class authController {
     tokenService.setRefreshTokenInCookie(res, refreshToken);
 
     RouterUtils.validAndSendResponse(
-      api.auth.POST_Login.res,
+      api.client.auth.POST_Login.res,
       res,
       {
         code: "success",
@@ -244,7 +313,7 @@ class authController {
     await tokenService.removeToken(userData.userId, refreshToken);
     res.clearCookie("refreshToken");
 
-    RouterUtils.validAndSendResponse(api.auth.POST_Logout.res, res, {
+    RouterUtils.validAndSendResponse(api.client.auth.POST_Logout.res, res, {
       code: "success",
     });
   }
@@ -257,9 +326,13 @@ class authController {
     await tokenService.removeAllUserTokens(userData.userId);
     res.clearCookie("refreshToken");
 
-    RouterUtils.validAndSendResponse(api.auth.POST_LogoutAllDevices.res, res, {
-      code: "success",
-    });
+    RouterUtils.validAndSendResponse(
+      api.client.auth.POST_LogoutAllDevices.res,
+      res,
+      {
+        code: "success",
+      },
+    );
   }
 
   async refresh(req: Request, res: Response) {
@@ -286,7 +359,7 @@ class authController {
         }),
       )`select email from usr.users where user_id = ${userData.userId}`,
     );
-    if (!newUserData) {
+    if (!newUserData || !newUserData.email) {
       throw ApiError.UnauthorizedError();
     }
 
@@ -303,7 +376,7 @@ class authController {
 
     tokenService.setRefreshTokenInCookie(res, newTokens.refreshToken);
 
-    RouterUtils.validAndSendResponse(api.auth.POST_Refresh.res, res, {
+    RouterUtils.validAndSendResponse(api.client.auth.POST_Refresh.res, res, {
       code: "success",
 
       accessToken: newTokens.accessToken,

+ 7 - 0
src/modules/users/auth/routers/auth-router.ts → src/modules/client/users/auth/routers/auth-router.ts

@@ -1,3 +1,4 @@
+import { upload } from "#utils/files-utils.js";
 import { RouterUtils } from "#utils/router-utils.js";
 import { AuthController } from "./auth-controller.js";
 
@@ -5,10 +6,16 @@ import express from "express";
 const router = express.Router();
 export default router;
 
+router.get(
+  "/user-reg-data",
+  RouterUtils.asyncHandler(AuthController.getUserRegData),
+);
+
 router.post("/registration", RouterUtils.asyncHandler(AuthController.register));
 
 router.post(
   "/confirm-registration",
+  upload.any(),
   RouterUtils.asyncHandler(AuthController.confirmRegistration),
 );
 

+ 0 - 0
src/modules/users/auth/services/perms-service.ts → src/modules/client/users/auth/services/perms-service.ts


+ 1 - 1
src/modules/users/auth/services/session-service.ts → src/modules/client/users/auth/services/session-service.ts

@@ -1,5 +1,5 @@
 import type { Request } from "express";
-import { TokenPayload } from "#modules/users/auth/types/token-playload-type.js";
+import { TokenPayload } from "#modules/client/users/auth/types/token-playload-type.js";
 import { ApiError } from "#exceptions/api-error.js";
 import { z } from "zod";
 import { selPool } from "#db/db.js";

+ 0 - 0
src/modules/users/auth/services/token-service.ts → src/modules/client/users/auth/services/token-service.ts


+ 0 - 0
src/modules/users/auth/services/user-auth-service.ts → src/modules/client/users/auth/services/user-auth-service.ts


+ 3 - 6
src/modules/users/auth/services/user-registration-service.ts → src/modules/client/users/auth/services/user-registration-service.ts

@@ -6,15 +6,12 @@
 // import { db } from "#db";
 // import { sql } from "slonik";
 // import { AuthMailService } from "../../confirm-pins/confirm-pins-service.js";
+// import { cUsersService } from "../../c-users-service.js";
 
 // class userRegistrationService {
 
-//   async deleteUnconfirmedUser(email: string) {
-//     await db.any(
-//       sql.unsafe`delete from users.unconfirmed_users where email = ${email}`,
-//     );
-
-//     logger.debug("Удален неподтверженный пользователь " + email);
+//   async getUserRegData(eventId: string) {
+//     cUsersService.getUserEventDataWithValidators()_
 //   }
 
 // }

+ 0 - 0
src/modules/users/auth/types/token-playload-type.ts → src/modules/client/users/auth/types/token-playload-type.ts


+ 35 - 4
src/modules/client/users/c-users-controller.ts

@@ -4,7 +4,7 @@ import { api } from "#api";
 import { RouterUtils } from "#utils/router-utils.js";
 
 import { Request, Response } from "express";
-import sessionService from "#modules/users/auth/services/session-service.js";
+import sessionService from "#modules/client/users/auth/services/session-service.js";
 import { cCustomFieldsValidateService } from "../custom-fields/c-cf-validate-service.js";
 import { ApiError } from "#exceptions/api-error.js";
 import { cUsersService } from "./c-users-service.js";
@@ -19,8 +19,39 @@ class ClientUsersController {
     const event = await sessionService.getCurrentEventFromReq(req);
     const userId = sessionService.getUserFromReq(req).userId;
 
+    const userData = await cUsersService.getUserEventFieldsWithValues(
+      event.eventId,
+      userId,
+    );
+
+    const user = await cUsersService.getUser(userId);
+    if (!user) {
+      throw ApiError.UnauthorizedError();
+    }
+
+    RouterUtils.validAndSendResponse(
+      api.client.users.GET_UserEventData.res,
+      res,
+      {
+        code: "success",
+        userData: {
+          fields: [...userData],
+          isChild: user.isChild,
+        },
+      },
+    );
+  }
+
+  async getUserEventDataForPatch(
+    req: Request,
+    res: Response,
+    // next: NextFunction
+  ) {
+    const event = await sessionService.getCurrentEventFromReq(req);
+    const userId = sessionService.getUserFromReq(req).userId;
+
     const userData =
-      await cUsersService.getUserEventDataWithValuesAndValidators(
+      await cUsersService.getUserEventFieldsWithValuesAndValidators(
         event.eventId,
         userId,
       );
@@ -31,7 +62,7 @@ class ClientUsersController {
     }
 
     RouterUtils.validAndSendResponse(
-      api.client.users.GET_UserEventData.res,
+      api.client.users.GET_UserEventDataForPatch.res,
       res,
       {
         code: "success",
@@ -60,7 +91,7 @@ class ClientUsersController {
     const files = req.files;
 
     const userData =
-      await cUsersService.getUserEventDataWithValuesAndValidators(
+      await cUsersService.getUserEventFieldsWithValuesAndValidators(
         event.eventId,
         user.userId,
       );

+ 33 - 0
src/modules/client/users/c-users-router.ts

@@ -3,6 +3,7 @@ import { RouterUtils } from "#utils/router-utils.js";
 import express from "express";
 import { clientUsersController } from "./c-users-controller.js";
 import { upload } from "#utils/files-utils.js";
+import { childrenController } from "./children-controller.js";
 const router = express.Router();
 export default router;
 
@@ -11,12 +12,44 @@ router.get(
   RouterUtils.asyncHandler(clientUsersController.getUserEventData),
 );
 
+router.get(
+  "/userEventData/forPatch",
+  RouterUtils.asyncHandler(clientUsersController.getUserEventDataForPatch),
+);
+
 router.patch(
   "/userEventData",
   upload.any(),
   RouterUtils.asyncHandler(clientUsersController.patchUserEventData),
 );
 
+router.post(
+  "/child",
+  upload.any(),
+  RouterUtils.asyncHandler(childrenController.createChild),
+);
+
+router.get(
+  "/child/:childId",
+  RouterUtils.asyncHandler(childrenController.getChild),
+);
+
+router.get(
+  "/child/:childId/forPatch",
+  RouterUtils.asyncHandler(childrenController.getChildForPatch),
+);
+
+router.patch(
+  "/child/:childId",
+  upload.any(),
+  RouterUtils.asyncHandler(childrenController.patchChild),
+);
+
+router.get(
+  "/children",
+  RouterUtils.asyncHandler(childrenController.getChildren),
+);
+
 // router.get(
 //   "/event/:eventId",
 //   RouterUtils.asyncHandler(EventController.getEvent),

+ 87 - 2
src/modules/client/users/c-users-service.ts

@@ -1,11 +1,45 @@
-import { CustomFieldWithValidatorsAndValue } from "#api/v_0.1.0/types/custom-fields-types.js";
+import {
+  CustomFieldWithValidators,
+  CustomFieldWithValidatorsAndValue,
+  CustomFieldWithValue,
+} from "#api/v_0.1.0/types/custom-fields-types.js";
 import { DbSchema } from "#db/db-schema.js";
 import { selPool } from "#db/db.js";
 import { sql } from "slonik";
 import { z } from "zod";
 
 class CUsersService {
-  async getUserEventDataWithValuesAndValidators(
+  async getUserEventFieldsWithValues(eventId: string, userId: string) {
+    return await selPool.any(sql.type(
+      CustomFieldWithValue.extend({
+        userEfId: DbSchema.ev.userEventFields.userEfId,
+      }),
+    )`
+          select
+            uf.user_ef_id "userEfId",
+            uf.field_definition_id "fieldDefinitionId",
+            cfd.code "code",
+            ft.code "fieldTypeCode",
+            coalesce(uf.field_title_override, cfd.title) as title,
+            cfd.mask,
+            cfd.options,
+            u.value,
+            uf.order_number "orderNumber"
+          from
+            ev.user_event_fields uf
+          left join cf.custom_field_definitions cfd on
+            cfd.field_definition_id = uf.field_definition_id
+          left join cf.field_types ft on
+            ft.field_type_id = cfd.field_type_id
+          left join ev.user_event_field_values u on
+            u.user_ef_id = uf.user_ef_id
+          where
+            uf.event_id = ${eventId} and
+            u.user_id = ${userId}
+        `);
+  }
+
+  async getUserEventFieldsWithValuesAndValidators(
     eventId: string,
     userId: string,
   ) {
@@ -37,6 +71,31 @@ class CUsersService {
         `);
   }
 
+  async getUserEventFieldsWithValidators(eventId: string) {
+    return await selPool.any(sql.type(
+      CustomFieldWithValidators.extend({
+        userEfId: DbSchema.ev.userEventFields.userEfId,
+      }),
+    )`
+          select
+            uef.field_definition_id as "fieldDefinitionId",
+            uef.user_ef_id as "userEfId",
+            cfwv.code,
+            cfwv.field_type_code as "fieldTypeCode",
+            COALESCE(uef.field_title_override, cfwv.title) as "title",
+            cfwv.mask,
+            cfwv."options",
+            cfwv.validators,
+            uef.order_number as "orderNumber" 
+          from
+            ev.user_event_fields uef
+          join cf.custom_fields_with_validators cfwv on
+            uef.field_definition_id = cfwv.field_definition_id
+          where
+            uef.event_id = ${eventId}
+        `);
+  }
+
   async getUser(userId: string) {
     return await selPool.maybeOne(
       sql.type(
@@ -47,6 +106,32 @@ class CUsersService {
       )`select user_id as "userId", is_child as "isChild" from usr.users where user_id = ${userId}`,
     );
   }
+
+  async getChildrens(parentId: string) {
+    return await selPool.any(sql.type(
+      z.object({
+        userId: DbSchema.usr.users.userId,
+        userIdentity: z.string(),
+      }),
+    )`
+      select 
+        ui.user_id as "userId",
+        ui.identity as "userIdentity"
+      from
+        usr.users u
+        left join ev.users_identity ui on
+          ui.user_id = u.user_id
+      where
+        u.parent_id = ${parentId} and
+        u.is_child = true
+        `);
+  }
+
+  async checkChildParent(userId: string, childId: string) {
+    return await selPool.exists(sql.unsafe`
+      select 1 from usr.users where user_id = ${childId} and parent_id = ${userId}
+    `);
+  }
 }
 
 export const cUsersService = new CUsersService();

+ 245 - 0
src/modules/client/users/children-controller.ts

@@ -0,0 +1,245 @@
+import { api } from "#api/current-api.js";
+import { Request, Response } from "express";
+import sessionService from "./auth/services/session-service.js";
+import { cCustomFieldsValidateService } from "../custom-fields/c-cf-validate-service.js";
+import { cUsersService } from "./c-users-service.js";
+import { ApiError } from "#exceptions/api-error.js";
+import { v7 as uuidv7 } from "uuid";
+import { updPool } from "#db";
+import { sql } from "slonik";
+import { RouterUtils } from "#utils/router-utils.js";
+
+class ChildrenController {
+  async createChild(req: Request, res: Response) {
+    const { fields } = api.client.users.POST_Child.req.formData.body.parse(
+      JSON.parse(req.body.body),
+    );
+
+    const event = await sessionService.getCurrentEventFromReq(req);
+    const files = req.files;
+    const user = await sessionService.getUserFromReq(req);
+
+    // поля пользователя
+    const userData = await cUsersService.getUserEventFieldsWithValidators(
+      event.eventId,
+    );
+
+    const refFields = userData.map((f) => {
+      // TODO: костылёк для возраста ребенка
+      if (f.code === "birth-date") {
+        return {
+          ...f,
+          idKey: "userEfId",
+          validators: f.validators.filter((v) => v.code !== "minAge"),
+        };
+      }
+
+      return {
+        ...f,
+        idKey: "userEfId",
+      };
+    });
+
+    // валидация полей
+    const validationResult =
+      await cCustomFieldsValidateService.processAndValidateFields({
+        inputFields: fields,
+        referenceFields: refFields,
+        files,
+        idKey: "userEfId",
+        addOldValue: false,
+      });
+
+    if (!validationResult.isValid)
+      throw ApiError.BadRequest(
+        "fieldsValidationFailed",
+        JSON.stringify(validationResult.messages),
+      );
+
+    const validatedFields = validationResult.checkedfields;
+    // вставляем в базу и сохраняем файлы
+    const userId = uuidv7();
+    await updPool.transaction(async (tr) => {
+      await tr.query(
+        sql.unsafe`
+              insert into usr.users 
+                (user_id, is_child, parent_id) 
+              values 
+                (${userId}, true, ${user.userId})`,
+      );
+
+      await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({
+        tr,
+        parentId: userId,
+        action: "userProfile",
+        inputFields: validatedFields,
+        files,
+        isDeleteBefore: false,
+      });
+    });
+
+    RouterUtils.validAndSendResponse(api.client.users.POST_Child.res, res, {
+      code: "success",
+    });
+  }
+
+  async patchChild(req: Request, res: Response) {
+    const user = sessionService.getUserFromReq(req);
+    const { fields } = api.client.users.PATCH_Child.req.formData.body.parse(
+      JSON.parse(req.body.body),
+    );
+    const childId = api.client.users.PATCH_Child.req.params.childId.parse(
+      req.params.childId,
+    );
+
+    const event = await sessionService.getCurrentEventFromReq(req);
+    const files = req.files;
+
+    const isChildParent = await cUsersService.checkChildParent(
+      user.userId,
+      childId,
+    );
+
+    if (!isChildParent) {
+      throw ApiError.ForbiddenError();
+    }
+
+    const userData =
+      await cUsersService.getUserEventFieldsWithValuesAndValidators(
+        event.eventId,
+        childId,
+      );
+
+    const refFields = userData
+      .map((f) => {
+        // TODO: костылёк для возраста ребенка
+        if (f.code === "birth-date") {
+          return {
+            ...f,
+            idKey: "userEfId",
+            validators: f.validators.filter((v) => v.code !== "minAge"),
+          };
+        }
+
+        return {
+          ...f,
+          idKey: "userEfId",
+        };
+      })
+      // только изменяемые
+      .filter((f) => fields.some((ff) => ff.userEfId === f.userEfId));
+
+    // валидация
+    const validationResult =
+      await cCustomFieldsValidateService.processAndValidateFields({
+        inputFields: fields,
+        referenceFields: refFields,
+        files,
+        idKey: "userEfId",
+        addOldValue: true,
+      });
+
+    if (!validationResult.isValid)
+      throw ApiError.BadRequest(
+        "fieldsValidationFailed",
+        JSON.stringify(validationResult.messages),
+      );
+
+    const validatedFields = validationResult.checkedfields;
+
+    //
+    //
+    // вставляем в базу и сохраняем файлы
+    await updPool.transaction(async (tr) => {
+      await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({
+        tr,
+        parentId: childId,
+        action: "userProfile",
+        inputFields: validatedFields,
+        files,
+        isDeleteBefore: true,
+      });
+    });
+
+    RouterUtils.validAndSendResponse(
+      api.client.users.PATCH_UserEventData.res,
+      res,
+      { code: "success" },
+    );
+  }
+
+  async getChildren(req: Request, res: Response) {
+    const user = sessionService.getUserFromReq(req);
+
+    const children = await cUsersService.getChildrens(user.userId);
+
+    RouterUtils.validAndSendResponse(api.client.users.GET_Children.res, res, {
+      code: "success",
+      children: [...children],
+    });
+  }
+
+  async getChild(req: Request, res: Response) {
+    const user = sessionService.getUserFromReq(req);
+    const event = await sessionService.getCurrentEventFromReq(req);
+
+    const { childId } = api.client.users.GET_Child.req.params.parse(req.params);
+
+    const isChildParent = await cUsersService.checkChildParent(
+      user.userId,
+      childId,
+    );
+    if (!isChildParent) {
+      throw ApiError.ForbiddenError();
+    }
+
+    const childFields = await cUsersService.getUserEventFieldsWithValues(
+      event.eventId,
+      childId,
+    );
+
+    RouterUtils.validAndSendResponse(api.client.users.GET_Child.res, res, {
+      code: "success",
+      userData: {
+        fields: [...childFields],
+      },
+    });
+  }
+
+  async getChildForPatch(req: Request, res: Response) {
+    const user = sessionService.getUserFromReq(req);
+    const event = await sessionService.getCurrentEventFromReq(req);
+
+    const childId = api.client.users.GET_ChildForPatch.req.params.childId.parse(
+      req.params.childId,
+    );
+
+    const isChildParent = await cUsersService.checkChildParent(
+      user.userId,
+      childId,
+    );
+
+    if (!isChildParent) {
+      throw ApiError.ForbiddenError();
+    }
+
+    const childFields =
+      await cUsersService.getUserEventFieldsWithValuesAndValidators(
+        event.eventId,
+        childId,
+      );
+
+    RouterUtils.validAndSendResponse(
+      api.client.users.GET_ChildForPatch.res,
+      res,
+      {
+        code: "success",
+        userData: {
+          fields: [...childFields],
+        },
+      },
+    );
+  }
+}
+
+export const childrenController = new ChildrenController();

+ 13 - 3
src/modules/users/confirm-pins/confirm-pins-service.ts → src/modules/client/users/confirm-pins/confirm-pins-service.ts

@@ -40,7 +40,15 @@ class confirmPinsService {
   //
   // public
   //
-  async sendConfirmPin(transactionId: string, email: string) {
+  async sendConfirmPin({
+    transactionId,
+    email,
+    actionType,
+  }: {
+    transactionId: string;
+    email: string;
+    actionType: "registration";
+  }) {
     const confirmPin = this.genRandom4DigitNumber();
     // удаляем если пин уже есть
     await this.deleteConfirmPin(transactionId);
@@ -59,12 +67,14 @@ class confirmPinsService {
         transaction_id,
         email,
         confirm_pin,
-        create_time)
+        create_time,
+        action_type)
       values (
         ${transactionId}, 
         ${email}, 
         ${confirmPin}, 
-        ${DayjsUtils.createDayjsUtcWithoutOffset().toISOString()})`,
+        ${DayjsUtils.createDayjsUtcWithoutOffset().toISOString()},
+        ${actionType})`,
     );
     logger.info("Отправлен временный код: ", {
       email,