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