Browse Source

унификация валидоторов активности

Vadim 3 months ago
parent
commit
6ae43e5c97

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

@@ -14,10 +14,7 @@ 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 { validateActValidators } from "./validators/act-validators.js";
 import {
   CustomFieldWithUserCopyValue,
   CustomFieldWithValidators,
@@ -26,6 +23,9 @@ import { ApiError } from "#exceptions/api-error.js";
 
 import { cCustomFieldsValidateService } from "../custom-fields/c-cf-validate-service.js";
 import { v7 } from "uuid";
+import { cPeService } from "./participant-entities/c-pe-service.js";
+import { cActService } from "./c-act-service.js";
+import { validatePeForAct } from "./validators/act-pe-validators.js";
 
 class ClientActivitiesController {
   async getEventActivities(
@@ -95,7 +95,7 @@ class ClientActivitiesController {
           ...activity,
           validators: await Promise.all(
             activity.validators.map(async (validator) =>
-              addDataToValidator(validator, activity.activityId),
+              cActService.addDataToActValidator(validator, activity.activityId),
             ),
           ),
         };
@@ -175,7 +175,7 @@ class ClientActivitiesController {
           ...activity,
           validators: await Promise.all(
             activity.validators.map(async (validator) =>
-              addDataToValidator(validator, activity.activityId),
+              cActService.addDataToActValidator(validator, activity.activityId),
             ),
           ),
         };
@@ -292,9 +292,19 @@ class ClientActivitiesController {
 
     const files = req.files;
 
+    // проверка доступа
+    const user = sessionService.getUserFromReq(req);
+    const isOwner = await cPeService.checkPeOwner(user.userId, peId);
+    if (!isOwner) throw ApiError.ForbiddenError();
+
     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({
@@ -310,6 +320,11 @@ class ClientActivitiesController {
     )`
         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"
@@ -343,56 +358,42 @@ class ClientActivitiesController {
         "Данные для регистрации на мероприятии не найдены",
       );
 
-    const validators = await Promise.all(
+    //
+    // -- ВАЛИДАЦИЯ --
+    const validatorsWithData = await Promise.all(
       actRegData.validators.map(
         async (validator) =>
-          await addDataToValidator(validator, actRegData.activityId),
+          await cActService.addDataToActValidator(
+            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)
+    const validatedActivity = validateActValidators(validatorsWithData);
+    if (!validatedActivity.isOpen)
       throw ApiError.BadRequest(
-        "peTypeNotFound",
-        "Тип сущности участия не найден",
+        "actValidatorFailed",
+        validatedActivity.messages.join(", "),
       );
-
-    const isPeTypeValid = actRegData.peTypes.find((t) => t.code === peTypeCode);
-    if (!isPeTypeValid)
+    //
+    // валидация pe
+    const pe = await cPeService.getPeWithValues(peId);
+    const members = await cPeService.getMembers(peId);
+    if (!pe)
+      throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена");
+
+    const fullPe = {
+      ...pe,
+      members: [...members],
+    };
+    const validatedPe = validatePeForAct(actRegData, fullPe);
+    if (!validatedPe.isValid)
       throw ApiError.BadRequest(
-        "peTypeNotFound",
-        `Тип сущности участия ${peTypeCode} не подходит к мероприятию ${activityCode}. Нужны ${actRegData.peTypes.map((v) => v.code).join(", ")}`,
+        "peValidatorFailed",
+        validatedPe.messages.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);
-      }
-    }
-    //
     //
 
     // валидация формы

+ 34 - 23
src/modules/client/activities/c-act-service.ts

@@ -1,30 +1,41 @@
+import { apiTypes } from "#api/current-api.js";
+import { selPool } from "#db/db.js";
+import { sql } from "slonik";
+import { z } from "zod";
+
 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[] = [];
+  addDataToActValidator = 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,
+          },
+        };
+      }
 
-    for (const validator of validators) {
-      for (const field of form) {
-        
+      default: {
+        return validator;
       }
     }
-  }
+  };
 }
 
 export const cActService = new CActService();

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

@@ -0,0 +1,77 @@
+import { apiTypes } from "#api/current-api.js";
+import { z } from "zod";
+
+export const validatePeForAct = (
+  activity: z.infer<typeof apiTypes.activities.Activity>,
+  pe: z.infer<typeof apiTypes.activities.PeForActivity>,
+) => {
+  let isValid = true;
+  const messages: string[] = [];
+
+  // тип активности
+  const peType = activity.peTypes.find((pt) => pt.peTypeId === pe.peTypeId);
+  if (!peType) {
+    isValid = false;
+    messages.push(
+      `На ${activity.publicName} можно зарегистрироваться только с ${activity.peTypes.map((pt) => pt.name).join(", ")}`,
+    );
+  }
+
+  // валидаторы
+  const validators = activity.validators;
+  for (const validator of validators.filter((v) => v.isPeValidator)) {
+    const validatorData = validatePeOnValidator(validator, pe);
+    if (!validatorData.isValid) {
+      isValid = false;
+      messages.push(validatorData.message);
+    }
+  }
+
+  return {
+    isValid,
+    messages,
+  };
+};
+
+const validatePeOnValidator = (
+  validator: z.infer<typeof apiTypes.activities.ActValidatorWithData>,
+  pe: z.infer<typeof apiTypes.activities.PeForActivity>,
+): { isValid: true } | { isValid: false; message: string } => {
+  switch (validator.code) {
+    case "min-part-members": {
+      const isValid = pe.members.length >= Number(validator.value);
+
+      if (isValid) {
+        return {
+          isValid: true,
+        };
+      } else {
+        return {
+          isValid: false,
+          message: `Минимальное количество участников - ${validator.value}`,
+        };
+      }
+    }
+
+    case "max-part-members": {
+      const isValid = pe.members.length <= Number(validator.value);
+
+      if (isValid) {
+        return {
+          isValid: true,
+        };
+      } else {
+        return {
+          isValid: false,
+          message: `Максимальное количество участников - ${validator.value}`,
+        };
+      }
+    }
+
+    default: {
+      return {
+        isValid: true,
+      };
+    }
+  }
+};

+ 25 - 32
src/modules/client/activities/validators/act-validators.ts

@@ -1,44 +1,37 @@
-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}
-      `);
+export const validateActValidators = (
+  validators: z.infer<typeof apiTypes.activities.ActValidatorWithData>[],
+): {
+  isOpen: boolean;
+  messages: string[];
+} => {
+  let isOpen = true;
+  const messages: string[] = [];
 
-      return {
-        ...validator,
-        serverData: {
-          currentRegs,
-        },
-      };
-    }
+  // только валидаторы активности
+  const actValidators = validators.filter(
+    (validator) => !validator.isPeValidator,
+  );
 
-    default: {
-      return validator;
+  actValidators.forEach((validator) => {
+    const validatorData = validateActValidator(validator);
+    if (!validatorData.isValid) {
+      isOpen = false;
+      messages.push(validatorData.message);
     }
-  }
+  });
+
+  return {
+    isOpen: isOpen,
+    messages: messages,
+  };
 };
 
-// TODO: подумать как объеденить с клиентом
-export const validateAct = (
+/** Валидировать активность конкретным валидатором */
+const validateActValidator = (
   validator: z.infer<typeof apiTypes.activities.ActValidatorWithData>,
 ): { isValid: true } | { isValid: false; message: string } => {
   switch (validator.code) {

+ 10 - 8
src/modules/client/users/c-users-controller.ts

@@ -19,10 +19,11 @@ class ClientUsersController {
     const event = await sessionService.getCurrentEventFromReq(req);
     const user = sessionService.getUserFromReq(req);
 
-    const userData = await cUsersService.getUserEventDataWithValues(
-      event.eventId,
-      user.userId,
-    );
+    const userData =
+      await cUsersService.getUserEventDataWithValuesAndValidators(
+        event.eventId,
+        user.userId,
+      );
 
     RouterUtils.validAndSendResponse(
       api.client.users.GET_UserEventData.res,
@@ -52,10 +53,11 @@ class ClientUsersController {
 
     const files = req.files;
 
-    const userData = await cUsersService.getUserEventDataWithValues(
-      event.eventId,
-      user.userId,
-    );
+    const userData =
+      await cUsersService.getUserEventDataWithValuesAndValidators(
+        event.eventId,
+        user.userId,
+      );
 
     const refFields = userData
       .map((f) => ({

+ 4 - 1
src/modules/client/users/c-users-service.ts

@@ -4,7 +4,10 @@ import { selPool } from "#db/db.js";
 import { sql } from "slonik";
 
 class CUsersService {
-  async getUserEventDataWithValues(eventId: string, userId: string) {
+  async getUserEventDataWithValuesAndValidators(
+    eventId: string,
+    userId: string,
+  ) {
     return await selPool.any(sql.type(
       CustomFieldWithValidatorsAndValue.extend({
         userEfId: DbSchema.ev.userEventFields.userEfId,