Bladeren bron

Добавлены активности

Vadim 3 maanden geleden
bovenliggende
commit
bf829336f8

+ 11 - 4
src/api/v_0.1.0/api.ts

@@ -1,7 +1,9 @@
-import { authApi } from './shared/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';
+import { authApi } from "./shared/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";
+import { clientActivitiesApi } from "./client/client-activities-api.js";
+import { actTypes } from "./types/act-types.js";
 
 export const api = {
   auth: authApi,
@@ -10,5 +12,10 @@ export const api = {
     pe: clientPartEntitiesApi,
     users: clientUsersApi,
     event: clientEventApi,
+    activities: clientActivitiesApi,
   },
 };
+
+export const apiTypes = {
+  activities: actTypes,
+};

+ 84 - 0
src/api/v_0.1.0/client/client-activities-api.ts

@@ -0,0 +1,84 @@
+import { z } from "zod";
+import { actTypes } from "../types/act-types.js";
+import {
+  CustomFieldWithUserCopyValue,
+  InputFieldValue,
+} from "../types/custom-fields-types.js";
+
+class ClientActivitiesApi {
+  GET_EventActivities = {
+    res: z.object({
+      code: z.enum(["success"]),
+      categories: z.array(actTypes.ActCategory),
+      activities: z.array(actTypes.Activity),
+    }),
+  };
+
+  GET_Category = {
+    req: {
+      params: z.object({
+        categoryCode: z.string(),
+      }),
+    },
+    res: z.object({
+      code: z.enum(["success"]),
+      categories: z.array(actTypes.ActCategory),
+      activities: z.array(actTypes.Activity),
+    }),
+  };
+
+  GET_ActRegData = {
+    req: {
+      params: z.object({
+        activityCode: z.string(),
+      }),
+    },
+    res: z.object({
+      code: z.enum(["success"]),
+      actRegData: z.object({
+        activityId: z.string().uuid(),
+        code: z.string(),
+        publicName: z.string(),
+        eventInstId: z.string(),
+        categoryId: z.string().nullable(),
+        categoryCode: z.string(),
+        validators: z.array(actTypes.ActValidator),
+        peTypes: z.array(
+          z.object({
+            peTypeId: z.string().uuid(),
+            code: z.string(),
+            name: z.string(),
+          }),
+        ),
+        fields: z.array(
+          CustomFieldWithUserCopyValue.extend({
+            arffId: z.string().uuid(),
+          }),
+        ),
+      }),
+    }),
+  };
+
+  POST_RegisterPe = {
+    req: {
+      params: z.object({
+        activityCode: z.string(),
+      }),
+      body: z.object({
+        peId: z.string().uuid(),
+        fields: z.array(
+          z.object({
+            value: InputFieldValue,
+            arffId: z.string().uuid(),
+          }),
+        ),
+      }),
+    },
+    res: z.object({
+      code: z.enum(["success"]),
+      activityRegId: z.string().uuid(),
+    }),
+  };
+}
+
+export const clientActivitiesApi = new ClientActivitiesApi();

+ 43 - 0
src/api/v_0.1.0/types/act-types.ts

@@ -0,0 +1,43 @@
+import { z } from "zod";
+
+class ActTypes {
+  ActCategory = z.object({
+    categoryId: z.string().uuid(),
+    code: z.string(),
+    name: z.string(),
+    parentId: z.string().uuid().nullable(),
+    parentCode: z.string().nullable(),
+  });
+
+  ActValidator = z.object({
+    actValidatorId: z.string().uuid(),
+    code: z.string(),
+    name: z.string(),
+    description: z.string().nullable(),
+    value: z.string().nullable(),
+    value2: z.string().nullable(),
+    errorMessage: z.string().nullable(),
+    isPeValidator: z.boolean(),
+  });
+
+  ActValidatorWithData = this.ActValidator.extend({
+    serverData: z.unknown(),
+  });
+
+  Activity = z.object({
+    activityId: z.string().uuid(),
+    code: z.string(),
+    publicName: z.string(),
+    categoryId: z.string().uuid().nullable(),
+    validators: z.array(this.ActValidatorWithData),
+    peTypes: z.array(
+      z.object({
+        peTypeId: z.string().uuid(),
+        code: z.string(),
+        name: z.string(),
+      }),
+    ),
+  });
+}
+
+export const actTypes = new ActTypes();

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

@@ -1,4 +1,4 @@
-import { FieldTypeCode } from "#api/v_0.1.0/types/pe-types.js"; // Удалено, т.к. field_types.code теперь varchar
+import { FieldTypeCode } from "#api/v_0.1.0/types/custom-fields-types.js"; // Удалено, т.к. field_types.code теперь varchar
 import { z } from "zod";
 
 const DbSchema = {
@@ -131,6 +131,7 @@ const DbSchema = {
       code: z.string(), // Новое поле
       name: z.string(),
       eventInstId: z.string().uuid(),
+      parentId: z.string().uuid().nullable(),
     },
     peTypes: {
       peTypeId: z.string().uuid(),
@@ -138,6 +139,21 @@ const DbSchema = {
       code: z.string(),
       name: z.string(),
     },
+    actValidators: {
+      actValidatorId: z.string().uuid(),
+      code: z.string(),
+      name: z.string(),
+      description: z.string(),
+      isPeValidator: z.boolean(),
+    },
+    actRegValidators: {
+      actValidatorId: z.string().uuid(),
+      activityId: z.string().uuid(),
+      value: z.string().nullable(),
+      value2: z.string().nullable(),
+      errorMessage: z.string().nullable(),
+    },
+
     activities: {
       activityId: z.string().uuid(),
       code: z.string(), // Новое поле
@@ -189,7 +205,7 @@ const DbSchema = {
     arFieldValues: {
       // Таблица ar_field_values из новой БД
       activityRegId: z.string().uuid(),
-      fieldDefinitionId: z.string().uuid(),
+      arffId: z.string().uuid(),
       value: z.string().nullable(),
     },
     peFormFields: {

+ 4 - 1
src/db/db-service.ts

@@ -6,7 +6,10 @@ import { DbSchema } from "./db-schema.js";
 import { logger } from "#logger";
 
 function camelToSnake(str: string) {
-  return str.replace(/([A-Z])/g, "_$1").toLowerCase();
+  return str
+    .replace(/([A-Z])/g, "_$1")
+    .replace(/([0-9])/g, "_$1")
+    .toLowerCase();
 }
 
 // база данных

+ 4 - 0
src/main.ts

@@ -62,6 +62,10 @@ app.use("/api/client/users/", cUsersRouter);
 import cPeRouter from "./modules/client/activities/participant-entities/c-pe-router.js";
 app.use("/api/client/pe/", cPeRouter);
 
+// act
+import cActRouter from "./modules/client/activities/c-act-router.js";
+app.use("/api/client/act/", cActRouter);
+
 // events-management
 // import EventsRouter from "./modules/management/events-router.js";
 // app.use("/api/events-management/", authMiddleware(), EventsRouter);

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

@@ -0,0 +1,453 @@
+// db
+import { selPool, updPool } from "#db";
+import { DbSchema } from "#db-schema";
+import { sql } from "slonik";
+
+// api
+import { api } from "#api";
+
+// other
+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 { apiTypes } from "#api/current-api.js";
+import {
+  addDataToValidator,
+  validateAct,
+} from "./validators/act-validators.js";
+import {
+  CustomFieldWithUserCopyValue,
+  CustomFieldWithValidators,
+} from "#api/v_0.1.0/types/custom-fields-types.js";
+import { ApiError } from "#exceptions/api-error.js";
+
+import { cCustomFieldsValidateService } from "../custom-fields/c-cf-validate-service.js";
+import { v7 } from "uuid";
+
+class ClientActivitiesController {
+  async getEventActivities(
+    req: Request,
+    res: Response,
+    // next: NextFunction
+  ) {
+    const event = await sessionService.getCurrentEventFromReq(req);
+
+    const categories = await selPool.any(sql.type(
+      apiTypes.activities.ActCategory,
+    )`
+      select
+        c.category_id "categoryId" ,
+        c.code,
+        c."name",
+        c.parent_id "parentId",
+        p.code "parentCode"
+      from
+        act.activity_categories c
+      left join act.activity_categories p
+        on c.parent_id = p.category_id
+      where
+        c.event_inst_id = ${event.eventInstId}
+        and c.parent_id is null
+    `);
+
+    const activities = await selPool.any(sql.type(
+      z.object({
+        activityId: DbSchema.act.activities.activityId,
+        code: DbSchema.act.activities.code,
+        publicName: DbSchema.act.activities.publicName,
+        eventInstId: DbSchema.act.activities.eventInstId,
+        categoryId: DbSchema.act.activities.categoryId,
+        categoryCode: DbSchema.act.activityCategories.code,
+        blockId: DbSchema.act.activities.blockId,
+        validators: z.array(apiTypes.activities.ActValidator),
+        peTypes: z.array(
+          z.object({
+            peTypeId: DbSchema.act.peTypes.peTypeId,
+            code: DbSchema.act.peTypes.code,
+            name: DbSchema.act.peTypes.name,
+          }),
+        ),
+      }),
+    )`
+        select
+          a.activity_id "activityId",
+          a.code,
+          a.public_name "publicName",
+          a.event_inst_id "eventInstId",
+          a.category_id "categoryId",
+          a.category_code "categoryCode",
+          a.block_id "blockId",
+          a.validators,
+          a.pe_types "peTypes"
+        from
+          act.act_with_validators a
+        where
+          a.event_inst_id = ${event.eventInstId}
+          and a.category_id is null
+    `);
+
+    const activitiesWithValidatorsData = await Promise.all(
+      activities.map(async (activity) => {
+        return {
+          ...activity,
+          validators: await Promise.all(
+            activity.validators.map(async (validator) =>
+              addDataToValidator(validator, activity.activityId),
+            ),
+          ),
+        };
+      }),
+    );
+
+    RouterUtils.validAndSendResponse(
+      api.client.activities.GET_EventActivities.res,
+      res,
+      {
+        code: "success",
+        categories: [...categories],
+        activities: [...activitiesWithValidatorsData],
+      },
+    );
+  }
+
+  async getCategory(req: Request, res: Response) {
+    const { categoryCode } =
+      api.client.activities.GET_Category.req.params.parse(req.params);
+
+    const categories = await selPool.any(sql.type(
+      apiTypes.activities.ActCategory,
+    )`
+        select
+          c.category_id "categoryId" ,
+          c.code,
+          c."name",
+          c.parent_id "parentId",
+          p.code "parentCode"
+        from
+          act.activity_categories c
+        left join act.activity_categories p
+          on c.parent_id = p.category_id
+        where
+          p.code = ${categoryCode}
+      `);
+
+    const activities = await selPool.any(sql.type(
+      z.object({
+        activityId: DbSchema.act.activities.activityId,
+        code: DbSchema.act.activities.code,
+        publicName: DbSchema.act.activities.publicName,
+        eventInstId: DbSchema.act.activities.eventInstId,
+        categoryId: DbSchema.act.activities.categoryId,
+        categoryCode: DbSchema.act.activityCategories.code,
+        blockId: DbSchema.act.activities.blockId,
+        validators: z.array(apiTypes.activities.ActValidator),
+        peTypes: z.array(
+          z.object({
+            peTypeId: DbSchema.act.peTypes.peTypeId,
+            code: DbSchema.act.peTypes.code,
+            name: DbSchema.act.peTypes.name,
+          }),
+        ),
+      }),
+    )`
+          select
+            a.activity_id "activityId",
+            a.code,
+            a.public_name "publicName",
+            a.event_inst_id "eventInstId",
+            a.category_id "categoryId",
+            a.category_code "categoryCode",
+            a.block_id "blockId",
+            a.validators,
+            a.pe_types "peTypes"
+          from
+            act.act_with_validators a
+          where
+            a.category_code = ${categoryCode}
+      `);
+
+    const activitiesWithValidatorsData = await Promise.all(
+      activities.map(async (activity) => {
+        return {
+          ...activity,
+          validators: await Promise.all(
+            activity.validators.map(async (validator) =>
+              addDataToValidator(validator, activity.activityId),
+            ),
+          ),
+        };
+      }),
+    );
+
+    RouterUtils.validAndSendResponse(
+      api.client.activities.GET_Category.res,
+      res,
+      {
+        code: "success",
+        categories: [...categories],
+        activities: [...activitiesWithValidatorsData],
+      },
+    );
+  }
+
+  async getActRegData(req: Request, res: Response) {
+    const event = await sessionService.getCurrentEventFromReq(req);
+    const user = sessionService.getUserFromReq(req);
+
+    const { activityCode } =
+      api.client.activities.GET_ActRegData.req.params.parse(req.params);
+
+    const actRegData = await selPool.maybeOne(sql.type(
+      z.object({
+        activityId: DbSchema.act.activities.activityId,
+        code: DbSchema.act.activities.code,
+        publicName: DbSchema.act.activities.publicName,
+        eventInstId: DbSchema.act.activities.eventInstId,
+        categoryId: DbSchema.act.activities.categoryId,
+        categoryCode: DbSchema.act.activityCategories.code,
+        validators: z.array(apiTypes.activities.ActValidator),
+        peTypes: z.array(
+          z.object({
+            peTypeId: DbSchema.act.peTypes.peTypeId,
+            code: DbSchema.act.peTypes.code,
+            name: DbSchema.act.peTypes.name,
+          }),
+        ),
+        fields: z.array(
+          CustomFieldWithUserCopyValue.extend({ arffId: z.string() }),
+        ),
+      }),
+    )`
+        select
+          a.activity_id "activityId",
+          a.code,
+          a.public_name "publicName",
+          a.event_inst_id "eventInstId",
+          a.category_id "categoryId",
+          a.category_code "categoryCode",
+          a.validators,
+          a.pe_types "peTypes",
+          coalesce(f.fields, '[]'::jsonb) "fields"
+        from
+          act.act_with_validators a
+        left join lateral (
+          select
+            jsonb_agg(jsonb_build_object(
+              'fieldDefinitionId', f.field_definition_id,
+              'arffId', af.arff_id,
+              'isCopyUserValue', af.is_copy_user_value,
+              'code', f.code,
+              'userCopyValue', ufwv.value,
+              'fieldTypeCode', f.field_type_code,
+              'title', COALESCE(af.field_title_override, f.title),
+              'mask', f.mask,
+              'options', f."options",
+              'validators', f.validators
+            )) as fields
+          from
+            act.activity_reg_form_fields af
+          left join cf.custom_fields_with_validators f on
+            f.field_definition_id = af.field_definition_id
+              -- значение из профиля юзера
+          left join ev.user_fields_with_values ufwv on
+              af.field_definition_id = ufwv.field_definition_id
+              and ufwv.user_id = ${user.userId}
+              and ufwv.event_id = ${event.eventId}
+              and ufwv.value is not null
+              -- только если нужно копировать
+              and af.is_copy_user_value = true
+          where
+            af.activity_id = a.activity_id
+        ) f on true
+          where a.code = ${activityCode}
+      `);
+
+    // TODO: заменить все BadRequest
+    if (!actRegData)
+      throw ApiError.BadRequest(
+        "actRegDataNotFound",
+        "Данные для регистрации на мероприятии не найдены",
+      );
+
+    RouterUtils.validAndSendResponse(
+      api.client.activities.GET_ActRegData.res,
+      res,
+      {
+        code: "success",
+        actRegData: actRegData,
+      },
+    );
+  }
+
+  async registerPe(req: Request, res: Response) {
+    const { fields, peId } =
+      api.client.activities.POST_RegisterPe.req.body.parse(
+        JSON.parse(req.body.body),
+      );
+    const { activityCode } =
+      api.client.activities.POST_RegisterPe.req.params.parse(req.params);
+
+    const files = req.files;
+
+    const actRegData = await selPool.maybeOne(sql.type(
+      z.object({
+        activityId: DbSchema.act.activities.activityId,
+        validators: z.array(apiTypes.activities.ActValidator),
+        peTypes: z.array(
+          z.object({
+            peTypeId: DbSchema.act.peTypes.peTypeId,
+            code: DbSchema.act.peTypes.code,
+            name: DbSchema.act.peTypes.name,
+          }),
+        ),
+        fields: z.array(
+          CustomFieldWithValidators.extend({ arffId: z.string() }),
+        ),
+      }),
+    )`
+        select
+          a.activity_id "activityId",
+          a.validators,
+          a.pe_types "peTypes",
+          coalesce(f.fields, '[]'::jsonb) "fields"
+        from
+          act.act_with_validators a
+        left join lateral (
+          select
+            jsonb_agg(jsonb_build_object(
+              'fieldDefinitionId', f.field_definition_id,
+              'arffId', af.arff_id,
+              'isCopyUserValue', af.is_copy_user_value,
+              'code', f.code,
+              'fieldTypeCode', f.field_type_code,
+              'title', COALESCE(af.field_title_override, f.title),
+              'mask', f.mask,
+              'options', f."options",
+              'validators', f.validators
+            )) as fields
+          from
+            act.activity_reg_form_fields af
+          left join cf.custom_fields_with_validators f on
+            f.field_definition_id = af.field_definition_id
+          where
+            af.activity_id = a.activity_id
+        ) f on true
+          where a.code = ${activityCode}
+      `);
+    if (!actRegData)
+      throw ApiError.BadRequest(
+        "actRegDataNotFound",
+        "Данные для регистрации на мероприятии не найдены",
+      );
+
+    const validators = await Promise.all(
+      actRegData.validators.map(
+        async (validator) =>
+          await addDataToValidator(validator, actRegData.activityId),
+      ),
+    );
+
+    // валидация типа pe
+
+    const peTypeCode = await selPool.maybeOneFirst(sql.type(
+      z.object({
+        code: z.string(),
+      }),
+    )`
+      select
+        pt.code
+      from
+        act.part_entities p
+      left join act.pe_types pt on
+        p.pe_type_id = pt.pe_type_id
+      where
+        p.pe_id = ${peId}
+    `);
+    if (!peTypeCode)
+      throw ApiError.BadRequest(
+        "peTypeNotFound",
+        "Тип сущности участия не найден",
+      );
+
+    const isPeTypeValid = actRegData.peTypes.find((t) => t.code === peTypeCode);
+    if (!isPeTypeValid)
+      throw ApiError.BadRequest(
+        "peTypeNotFound",
+        `Тип сущности участия ${peTypeCode} не подходит к мероприятию ${activityCode}. Нужны ${actRegData.peTypes.map((v) => v.code).join(", ")}`,
+      );
+
+    //
+    //
+
+    // валидация активности
+    const actValidators = validators.filter(
+      (validator) => !validator.isPeValidator,
+    );
+    for (const validator of actValidators) {
+      const result = validateAct(validator);
+      if (!result.isValid) {
+        throw ApiError.BadRequest("actValidatorFailed", result.message);
+      }
+    }
+    //
+    //
+
+    // валидация формы
+    const refFields = actRegData.fields.map((f) => ({
+      ...f,
+      idKey: "arffId",
+    }));
+
+    const validationResult =
+      await cCustomFieldsValidateService.processAndValidateFields({
+        inputFields: fields,
+        referenceFields: refFields,
+        files,
+        idKey: "arffId",
+        addOldValue: false,
+      });
+
+    if (!validationResult.isValid)
+      throw ApiError.BadRequest(
+        "fieldsValidationFailed",
+        JSON.stringify(validationResult.messages),
+      );
+    const validatedFields = validationResult.checkedfields;
+    //
+    //
+    // вставляем в базу и сохраняем файлы
+    const activityRegId = v7();
+    await updPool.transaction(async (tr) => {
+      tr.query(sql.unsafe`
+        insert into act.activity_regs
+          (activity_reg_id, activity_id, pe_id)
+        values
+          (${activityRegId}, ${actRegData.activityId}, ${peId})
+      `);
+
+      await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({
+        tr,
+        parentId: activityRegId,
+        action: "activityPeReg",
+        inputFields: validatedFields,
+        files,
+        isDeleteBefore: false,
+      });
+
+      // TODO: отправка уведомления
+    });
+    RouterUtils.validAndSendResponse(
+      api.client.activities.POST_RegisterPe.res,
+      res,
+      {
+        code: "success",
+        activityRegId,
+      },
+    );
+  }
+}
+
+export const clientActController = new ClientActivitiesController();

+ 27 - 0
src/modules/client/activities/c-act-router.ts

@@ -0,0 +1,27 @@
+import { RouterUtils } from "#utils/router-utils.js";
+
+import express from "express";
+const router = express.Router();
+export default router;
+
+import { clientActController } from "./c-act-controller.js";
+import { upload } from "#utils/files-utils.js";
+
+router.get(
+  "/eventActivities",
+  RouterUtils.asyncHandler(clientActController.getEventActivities),
+);
+router.get(
+  "/cat/:categoryCode",
+  RouterUtils.asyncHandler(clientActController.getCategory),
+);
+router.get(
+  "/:activityCode/regData",
+  RouterUtils.asyncHandler(clientActController.getActRegData),
+);
+
+router.post(
+  "/:activityCode/registerPe",
+  upload.any(),
+  RouterUtils.asyncHandler(clientActController.registerPe),
+);

+ 30 - 0
src/modules/client/activities/c-act-service.ts

@@ -0,0 +1,30 @@
+class CActService {
+  async validatePeForm(
+    validators: {
+        actValidatorId: string;
+        code: string;
+        name: string;
+        description: string | null;
+        value: string | null;
+        value2: string | null;
+        errorMessage: string | null;
+        isPeValidator: true;
+    },
+    form: {
+      value: string | null;
+      arffId: string;
+      fieldType: string;
+    }[],
+  ) {
+    let isValid = true;
+    const messages: string[] = [];
+
+    for (const validator of validators) {
+      for (const field of form) {
+        
+      }
+    }
+  }
+}
+
+export const cActService = new CActService();

+ 0 - 120
src/modules/client/activities/c-activities-controller.ts

@@ -1,120 +0,0 @@
-// // db
-// import { selPool, updPool } from "#db";
-// import { DbSchema } from "#db-schema";
-// import { sql } from "slonik";
-
-// // api
-// import { api } from "#api";
-
-// // other
-// 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 { Validator } from "#api/v_0.1.0/types/pe-types.js";
-
-// class ClientActivitiesController {
-//   async getEventActivities(
-//     req: Request,
-//     res: Response,
-//     // next: NextFunction
-//   ) {
-//     const event = await sessionService.getCurrentEventFromReq(req);
-
-//     const activities = await selPool.any(sql.type(
-//       z.object({
-//         // fieldDefinitionId: DbSchema.cf.customFieldDefinitions.fieldDefinitionId,
-//         // code: DbSchema.cf.customFieldDefinitions.code,
-//         // fieldTypeCode: DbSchema.cf.fieldTypes.code,
-//         // title: DbSchema.cf.customFieldDefinitions.title,
-//         // mask: DbSchema.cf.customFieldDefinitions.mask,
-//         // options: DbSchema.cf.customFieldDefinitions.options,
-//         // validators: z.array(Validator),
-//         // userValue: DbSchema.ev.userEventFieldValues.value,
-//       }),
-//     )`
-//       select
-//         uef.field_definition_id as "fieldDefinitionId",
-//         cfwv.code,
-//         cfwv.field_type_code as "fieldTypeCode",
-//         cfwv.title,
-//         cfwv.mask,
-//         cfwv."options",
-//         cfwv.validators,
-//         uefv.value as "userValue"
-//       from
-//         ev.user_event_fields uef
-//       join cf.custom_fields_with_validators cfwv on
-//         uef.field_definition_id = cfwv.field_definition_id
-//       left join ev.user_event_field_values uefv on
-//         uef.field_definition_id = uefv.field_definition_id
-//         and uefv.user_id = ${user.userId}
-//       where
-//         uef.event_id = ${event.eventId}
-//     `);
-
-//     RouterUtils.validAndSendResponse(
-//       api.client.users.GET_UserEventData.res,
-//       res,
-//       {
-//         code: "success",
-//         userData: [...userData],
-//       },
-//     );
-//   }
-
-//   // async getUserEventData(
-//   //   req: Request,
-//   //   res: Response,
-//   //   // next: NextFunction
-//   // ) {
-//   //   const event = await sessionService.getCurrentEventFromReq(req);
-//   //   const user = sessionService.getUserFromReq(req);
-
-//   //   const userData = await selPool.any(sql.type(
-//   //     z.object({
-//   //       fieldDefinitionId: DbSchema.cf.customFieldDefinitions.fieldDefinitionId,
-//   //       code: DbSchema.cf.customFieldDefinitions.code,
-//   //       fieldTypeCode: DbSchema.cf.fieldTypes.code,
-//   //       title: DbSchema.cf.customFieldDefinitions.title,
-//   //       mask: DbSchema.cf.customFieldDefinitions.mask,
-//   //       options: DbSchema.cf.customFieldDefinitions.options,
-//   //       validators: z.array(Validator),
-//   //       userValue: DbSchema.ev.userEventFieldValues.value,
-//   //     }),
-//   //   )`
-//   //     select
-//   //       uef.field_definition_id as "fieldDefinitionId",
-//   //       cfwv.code,
-//   //       cfwv.field_type_code as "fieldTypeCode",
-//   //       cfwv.title,
-//   //       cfwv.mask,
-//   //       cfwv."options",
-//   //       cfwv.validators,
-//   //       uefv.value as "userValue"
-//   //     from
-//   //       ev.user_event_fields uef
-//   //     join cf.custom_fields_with_validators cfwv on
-//   //       uef.field_definition_id = cfwv.field_definition_id
-//   //     left join ev.user_event_field_values uefv on
-//   //       uef.field_definition_id = uefv.field_definition_id
-//   //       and uefv.user_id = ${user.userId}
-//   //     where
-//   //       uef.event_id = ${event.eventId}
-//   //   `);
-
-//   //   RouterUtils.validAndSendResponse(
-//   //     api.client.users.GET_UserEventData.res,
-//   //     res,
-//   //     {
-//   //       code: "success",
-//   //       userData: [...userData],
-//   //     },
-//   //   );
-//   // }
-
-// }
-
-// export const clientUsersController = new ClientUsersController();

+ 71 - 0
src/modules/client/activities/validators/act-validators.ts

@@ -0,0 +1,71 @@
+import { selPool } from "#db";
+import { sql } from "slonik";
+import { z } from "zod";
+
+import { apiTypes } from "#api/current-api.js";
+
+export const addDataToValidator = async (
+  validator: z.infer<typeof apiTypes.activities.ActValidator>,
+  activityId: string,
+): Promise<z.infer<typeof apiTypes.activities.ActValidatorWithData>> => {
+  switch (validator.code) {
+    case "max-regs": {
+      const currentRegs = await selPool.oneFirst(sql.type(
+        z.object({
+          count: z.number(),
+        }),
+      )`
+        select
+            count(*)
+        from
+            act.activity_regs
+        where
+            activity_id = ${activityId}
+      `);
+
+      return {
+        ...validator,
+        serverData: {
+          currentRegs,
+        },
+      };
+    }
+
+    default: {
+      return validator;
+    }
+  }
+};
+
+// TODO: подумать как объеденить с клиентом
+export const validateAct = (
+  validator: z.infer<typeof apiTypes.activities.ActValidatorWithData>,
+): { isValid: true } | { isValid: false; message: string } => {
+  switch (validator.code) {
+    case "max-regs": {
+      const serverData = validator.serverData as
+        | { currentRegs?: number }
+        | undefined;
+
+      const isValid =
+        Number(serverData?.currentRegs ?? 0) < Number(validator.value);
+
+      if (isValid) {
+        return {
+          isValid: true,
+        };
+      } else {
+        return {
+          isValid: false,
+          message: `Активность закрыта, превышено количество регистраций`,
+        };
+      }
+    }
+
+    default: {
+      return {
+        isValid: true,
+      };
+    }
+  }
+};