123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781 |
- // 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/client/users/auth/services/session-service.js";
- import { apiTypes } from "#api/current-api.js";
- import { validateActValidators } from "./validators/act-validators.js";
- 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";
- import { generateRandomNumber } from "#utils/other-utils.js";
- import { PeMemberWithIdentityShema } 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 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,
- 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,
- }),
- ),
- isUserReg: DbSchema.act.activities.isUserReg,
- }),
- )`
- 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",
- a.is_user_reg "isUserReg"
- from
- act.act_with_validators a
- where
- a.event_inst_id = ${event.eventInstId}
- and a.category_id is null
- `);
- const validatedActivities = await Promise.all(
- activities.map(async (activity) => {
- const validatorsWithData = await cActService.addDataToActValidators({
- validators: activity.validators,
- activityId: activity.activityId,
- });
- const validatedActivity = validateActValidators(validatorsWithData);
- return {
- activityId: activity.activityId,
- code: activity.code,
- publicName: activity.publicName,
- categoryId: activity.categoryId,
- categoryCode: activity.categoryCode,
- peTypes: activity.peTypes,
- isUserReg: activity.isUserReg,
- isOpen: validatedActivity.isOpen,
- messages: validatedActivity.messages,
- };
- }),
- );
- RouterUtils.validAndSendResponse(
- api.client.activities.GET_EventActivities.res,
- res,
- {
- code: "success",
- categories: [...categories],
- activities: [...validatedActivities],
- },
- );
- }
- 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,
- 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,
- }),
- ),
- isUserReg: DbSchema.act.activities.isUserReg,
- }),
- )`
- 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",
- a.is_user_reg "isUserReg"
- from
- act.act_with_validators a
- where
- a.category_code = ${categoryCode}
- `);
- const validatedActivities = await Promise.all(
- activities.map(async (activity) => {
- const validatorsWithData = await cActService.addDataToActValidators({
- validators: activity.validators,
- activityId: activity.activityId,
- });
- const validatedActivity = validateActValidators(validatorsWithData);
- return {
- activityId: activity.activityId,
- code: activity.code,
- publicName: activity.publicName,
- categoryId: activity.categoryId,
- categoryCode: activity.categoryCode,
- peTypes: activity.peTypes,
- isUserReg: activity.isUserReg,
- isOpen: validatedActivity.isOpen,
- messages: validatedActivity.messages,
- };
- }),
- );
- RouterUtils.validAndSendResponse(
- api.client.activities.GET_Category.res,
- res,
- {
- code: "success",
- categories: [...categories],
- activities: [...validatedActivities],
- },
- );
- }
- 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 cActService.getActRegDataWithUserCopyValuesAndActValidators(
- user.userId,
- event.eventId,
- activityCode,
- );
- // TODO: заменить все BadRequest
- if (!actRegData)
- throw ApiError.BadRequest(
- "actRegDataNotFound",
- "Данные для регистрации на мероприятии не найдены",
- );
- // validate act
- const validatorsWithData = await cActService.addDataToActValidators({
- validators: actRegData.validators,
- activityId: actRegData.activityId,
- });
- const validatedActivity = validateActValidators(validatorsWithData);
- if (!validatedActivity.isOpen)
- throw ApiError.BadRequest(
- "actValidatorFailed",
- validatedActivity.messages.join(", "),
- );
- //
- // validate pes
- const pes = await cPeService.getUserOwnerPesWithFieldsAndMembers({
- userId: user.userId,
- eventInstId: event.eventInstId,
- });
- const validatedPes = await Promise.all(
- pes.map(async (pe) => {
- const validationResult = await validatePeForAct(actRegData, pe);
- return {
- peId: pe.peId,
- peTypeId: pe.peTypeId,
- peTypeCode: pe.peTypeCode,
- peTypeName: pe.peTypeName,
- name: pe.name,
- isValid: validationResult.isValid,
- messages: validationResult.messages,
- };
- }),
- );
- RouterUtils.validAndSendResponse(
- api.client.activities.GET_ActRegData.res,
- res,
- {
- code: "success",
- actRegData: {
- isOpen: validatedActivity.isOpen,
- messages: validatedActivity.messages,
- activityId: actRegData.activityId,
- code: actRegData.code,
- publicName: actRegData.publicName,
- eventInstId: actRegData.eventInstId,
- categoryId: actRegData.categoryId,
- categoryCode: actRegData.categoryCode,
- isUserReg: actRegData.isUserReg,
- peTypes: actRegData.peTypes,
- fields: actRegData.fields,
- validatedPes: validatedPes,
- },
- },
- );
- }
- async registerToAct(req: Request, res: Response) {
- const { fields, peId } =
- api.client.activities.POST_RegisterToAct.req.body.parse(
- JSON.parse(req.body.body),
- );
- const { activityCode } =
- api.client.activities.POST_RegisterToAct.req.params.parse(req.params);
- const files = req.files;
- const user = sessionService.getUserFromReq(req);
- const actRegData =
- await cActService.getActRegDataWithFieldsAndValidatorsAndActValidators(
- activityCode,
- );
- if (!actRegData)
- throw ApiError.BadRequest(
- "actRegDataNotFound",
- "Данные для регистрации на мероприятии не найдены",
- );
- //
- // проверка доступа
- if (!actRegData.isUserReg) {
- if (!peId)
- throw ApiError.BadRequest(
- "peIdNotFound",
- "ID сущности участия не передан",
- );
- const isOwner = await cPeService.checkPeOwner(user.userId, peId);
- if (!isOwner) throw ApiError.ForbiddenError();
- }
- //
- // -- ВАЛИДАЦИЯ --
- // валидация активности
- const validatorsWithData = await cActService.addDataToActValidators({
- validators: actRegData.validators,
- activityId: actRegData.activityId,
- });
- const validatedActivity = validateActValidators(validatorsWithData);
- if (!validatedActivity.isOpen)
- throw ApiError.BadRequest(
- "actValidatorFailed",
- validatedActivity.messages.join(", "),
- );
- //
- // валидация pe
- if (!actRegData.isUserReg) {
- if (!peId)
- throw ApiError.BadRequest(
- "peIdNotFound",
- "ID сущности участия не передан",
- );
- const pe = await cPeService.getPeWithValues(peId);
- const members = await cPeService.getPeMembersWithFields(peId);
- if (!pe)
- throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена");
- const fullPe = {
- ...pe,
- members: [...members],
- };
- const validationResult = await validatePeForAct(actRegData, fullPe);
- if (!validationResult.isValid)
- throw ApiError.BadRequest(
- "peValidatorFailed",
- validationResult.messages.join(", "),
- );
- }
- //
- // валидация формы
- 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();
- let activityRegNumber = generateRandomNumber();
- while (await cActService.checkActivityRegNumber(activityRegNumber)) {
- activityRegNumber = generateRandomNumber();
- }
- await updPool.transaction(async (tr) => {
- if (actRegData.isUserReg) {
- // для user
- // сама регистрация
- await tr.query(sql.unsafe`
- insert into act.activity_regs
- (activity_reg_id, activity_id, user_id, number)
- values
- (${activityRegId}, ${actRegData.activityId}, ${user.userId}, ${activityRegNumber})
- `);
- } else {
- // для pe
- if (!peId)
- throw ApiError.BadRequest(
- "peIdNotFound",
- "ID сущности участия не передан",
- );
- // сама регистрация
- await tr.query(sql.unsafe`
- insert into act.activity_regs
- (activity_reg_id, activity_id, pe_id, number)
- values
- (${activityRegId}, ${actRegData.activityId}, ${peId}, ${activityRegNumber})
- `);
- }
- const initialRegStatusId = await cActService.getInitialRegStatusId(
- actRegData.activityId,
- );
- // устанавливаем начальный статус
- // FIXME: проверить во всех транзациях что я дождаюсь ответа
- await tr.query(sql.unsafe`
- insert into act.act_reg_status_history
- (status_history_id, activity_reg_id, act_reg_status_id, note)
- values
- (${v7()}, ${activityRegId}, ${initialRegStatusId}, 'Отправлена заявка на регистрацию')
- `);
- await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({
- tr,
- parentId: activityRegId,
- action: "activityPeReg",
- inputFields: validatedFields,
- files,
- isDeleteBefore: false,
- });
- // TODO: отправка уведомления
- });
- RouterUtils.validAndSendResponse(
- api.client.activities.POST_RegisterToAct.res,
- res,
- {
- code: "success",
- activityRegId,
- },
- );
- }
- async getActRegs(req: Request, res: Response) {
- const user = sessionService.getUserFromReq(req);
- const actRegs = await cActService.getActRegs(user.userId);
- RouterUtils.validAndSendResponse(
- api.client.activities.GET_ActRegs.res,
- res,
- {
- code: "success",
- actRegs: [...actRegs],
- },
- );
- }
- async getActReg(req: Request, res: Response) {
- const { activityRegId } = api.client.activities.GET_ActReg.req.params.parse(
- req.params,
- );
- const user = sessionService.getUserFromReq(req);
- const role = await cActService.checkActRegOwner(user.userId, activityRegId);
- if (!role) throw ApiError.ForbiddenError();
- let actReg: z.infer<
- typeof api.client.activities.GET_ActReg.res.shape.actReg
- > | null;
- if (role === "owner") {
- const r = await cActService.getActRegWithValues(activityRegId);
- if (!r)
- throw ApiError.BadRequest(
- "actRegNotFound",
- "Регистрация на мероприятии не найдена",
- );
- let peMembers:
- | (z.infer<typeof PeMemberWithIdentityShema> & { isPaid: boolean })[]
- | undefined = undefined;
- if (r.peId) {
- const m = [...(await cPeService.getPeMembersWithIdentity(r.peId))];
- peMembers = await Promise.all(
- m.map(async (m) => ({
- ...m,
- // TODO: слишком много операций, проще одним запросом вместо getPeMembersWithIdentity
- isPaid: await cActService.checkPeMemberActivityRegPayment({
- peMemberId: m.peMemberId,
- activityRegId: activityRegId,
- }),
- })),
- );
- }
- actReg = {
- ...r,
- peMembers,
- role,
- };
- } else {
- const r = await cActService.getActRegForPeMember(activityRegId);
- if (!r)
- throw ApiError.BadRequest(
- "actRegNotFound",
- "Регистрация на мероприятии не найдена",
- );
- actReg = {
- ...r,
- role,
- };
- }
- RouterUtils.validAndSendResponse(
- api.client.activities.GET_ActReg.res,
- res,
- {
- code: "success",
- actReg,
- },
- );
- }
- async getActivity(req: Request, res: Response) {
- const { activityCode } =
- api.client.activities.GET_Activity.req.params.parse(req.params);
- const act = await cActService.getActivity(activityCode);
- if (!act)
- throw ApiError.BadRequest("actNotFound", "Мероприятие не найдено");
- RouterUtils.validAndSendResponse(
- api.client.activities.GET_Activity.res,
- res,
- {
- code: "success",
- act,
- },
- );
- }
- async getActRegForPatch(req: Request, res: Response) {
- const { activityRegId } =
- api.client.activities.GET_ActRegForPatch.req.params.parse(req.params);
- const actReg =
- await cActService.getActRegWithFieldsAndValuesWithValidators(
- activityRegId,
- );
- if (!actReg)
- throw ApiError.BadRequest(
- "actRegNotFound",
- "Регистрация на мероприятии не найдена",
- );
- const user = sessionService.getUserFromReq(req);
- if (actReg.userId !== user.userId && actReg.peOwnerId !== user.userId)
- throw ApiError.ForbiddenError();
- RouterUtils.validAndSendResponse(
- api.client.activities.GET_ActRegForPatch.res,
- res,
- {
- code: "success",
- actReg,
- },
- );
- }
- async patchActReg(req: Request, res: Response) {
- const { activityRegId } =
- api.client.activities.PATCH_ActReg.req.params.parse(req.params);
- const { peId, fields } =
- api.client.activities.PATCH_ActReg.req.formData.body.parse(
- JSON.parse(req.body.body),
- );
- const files = req.files;
- const user = sessionService.getUserFromReq(req);
- const actReg = await cActService.getActRegForPeMember(activityRegId);
- if (!actReg)
- throw ApiError.BadRequest(
- "actRegNotFound",
- "Регистрация на мероприятии не найдена",
- );
- if (actReg.userId !== user.userId && actReg.peOwnerId !== user.userId)
- throw ApiError.ForbiddenError();
- const actRegData =
- await cActService.getActRegDataWithFieldsAndValidatorsAndActValidators(
- actReg.activityCode,
- );
- const oldActRegWithValues = await cActService.getActRegWithValues(
- actReg.activityRegId,
- );
- if (!actRegData || !oldActRegWithValues)
- throw ApiError.BadRequest(
- "actRegDataNotFound",
- "Данные для регистрации на мероприятии не найдены",
- );
- //
- //
- // -- ВАЛИДАЦИЯ --
- const validatorsWithData = await cActService.addDataToActValidators({
- validators: actRegData.validators,
- activityId: actRegData.activityId,
- });
- // валидация активности
- const validatedActivity = validateActValidators(validatorsWithData);
- if (!validatedActivity.isOpen)
- throw ApiError.BadRequest(
- "actValidatorFailed",
- validatedActivity.messages.join(", "),
- );
- //
- // валидация pe
- if (
- !actRegData.isUserReg &&
- peId // может не быть потому что patch
- ) {
- const pe = await cPeService.getPeWithValues(peId);
- const members = await cPeService.getPeMembersWithFields(peId);
- if (!pe)
- throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена");
- const fullPe = {
- ...pe,
- members: [...members],
- };
- const validationResult = await validatePeForAct(actRegData, fullPe);
- if (!validationResult.isValid)
- throw ApiError.BadRequest(
- "peValidatorFailed",
- validationResult.messages.join(", "),
- );
- }
- //
- // валидация формы
- const refFields = actRegData.fields
- .map((f) => ({
- ...f,
- idKey: "arffId",
- value:
- oldActRegWithValues.fields.find((v) => v.arffId === f.arffId)
- ?.value || null,
- isChangeResetStatus:
- oldActRegWithValues.fields.find((v) => v.arffId === f.arffId)
- ?.isChangeResetStatus || false,
- }))
- // только изменяемые
- .filter((f) => fields.some((ff) => ff.arffId === f.arffId));
- const validationResult =
- await cCustomFieldsValidateService.processAndValidateFields({
- inputFields: fields,
- referenceFields: refFields,
- files,
- idKey: "arffId",
- addOldValue: true,
- });
- if (!validationResult.isValid)
- throw ApiError.BadRequest(
- "fieldsValidationFailed",
- JSON.stringify(validationResult.messages),
- );
- const validatedFields = validationResult.checkedfields;
- //
- const isNeedReset = refFields.some((f) => f.isChangeResetStatus);
- //
- // вставляем в базу и сохраняем файлы
- await updPool.transaction(async (tr) => {
- if (!actRegData.isUserReg && peId) {
- await tr.query(sql.unsafe`
- update act.activity_regs
- set
- pe_id = ${peId}
- where
- activity_reg_id = ${activityRegId}
- `);
- }
- const initialRegStatusId = await cActService.getInitialRegStatusId(
- actRegData.activityId,
- );
- // устанавливаем начальный статус если были измены поля сбрасывающие регистрацию
- if (isNeedReset) {
- await tr.query(sql.unsafe`
- insert into act.act_reg_status_history
- (status_history_id, activity_reg_id, act_reg_status_id, note)
- values
- (${v7()}, ${activityRegId}, ${initialRegStatusId}, 'Изменена заявка на регистрацию')
- `);
- }
- await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({
- tr,
- parentId: activityRegId,
- action: "activityPeReg",
- inputFields: validatedFields,
- files,
- isDeleteBefore: true,
- });
- });
- RouterUtils.validAndSendResponse(
- api.client.activities.PATCH_ActReg.res,
- res,
- {
- code: "success",
- },
- );
- }
- async cancelActivityReg(req: Request, res: Response) {
- const { activityRegId } =
- api.client.activities.DELETE_ActivityReg.req.params.parse(req.params);
- const user = sessionService.getUserFromReq(req);
- const role = await cActService.checkActRegOwner(user.userId, activityRegId);
- if (role !== "owner") throw ApiError.ForbiddenError();
- await updPool.transaction(async (tr) => {
- await tr.query(sql.unsafe`
- update act.activity_regs
- set
- is_canceled = true
- where
- activity_reg_id = ${activityRegId}
- `);
- await tr.query(sql.unsafe`
- insert into act.act_reg_status_history
- (status_history_id, activity_reg_id, act_reg_status_id, note)
- values
- (${v7()}, ${activityRegId}, 'e324fddb-81fe-45f4-90fe-074c0beaae45', 'Заявка отменена')
- `);
- });
- // возвраты
- await cActService.refundByActivityRegId(activityRegId);
- RouterUtils.validAndSendResponse(
- api.client.activities.DELETE_ActivityReg.res,
- res,
- {
- code: "success",
- },
- );
- }
- }
- export const clientActController = new ClientActivitiesController();
|