|
@@ -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();
|