// db import { selPool, updPool } from "#db"; import { DbSchema } from "#db-schema"; import { sql } from "slonik"; // api import { api, apiTypes } 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 { ApiError } from "#exceptions/api-error.js"; import { v4, v7 } from "uuid"; import { cPeService } from "./c-pe-service.js"; import { cCustomFieldsValidateService } from "#modules/client/custom-fields/c-cf-validate-service.js"; class ClientPeController { async getEventPeTypes( req: Request, res: Response, // next: NextFunction ) { const event = await sessionService.getCurrentEventFromReq(req); const peTypes = await selPool.any(sql.type( z.object({ peTypeId: DbSchema.act.peTypes.peTypeId, code: DbSchema.act.peTypes.code, name: DbSchema.act.peTypes.name, }), )` select pe_type_id as "peTypeId", code, "name" from act.pe_types pt where pt.event_inst_id = ${event.eventInstId} `); RouterUtils.validAndSendResponse(api.client.pe.GET_EventPeTypes.res, res, { code: "success", peTypes: [...peTypes], }); } async getPeTypeForCreate( req: Request, res: Response, // next: NextFunction ) { const event = await sessionService.getCurrentEventFromReq(req); const user = sessionService.getUserFromReq(req); const { peTypeCode } = api.client.pe.GET_PeType.req.parse(req.params); const eventId = event.eventId; const userId = user.userId; const peType = await cPeService.getPeTypeWithFieldsAndUserCopyValues( userId, peTypeCode, eventId, ); if (!peType) throw ApiError.BadRequest( "peTypeNotFound", "Тип сущности участия не найден", ); RouterUtils.validAndSendResponse(api.client.pe.GET_PeType.res, res, { code: "success", peType: peType, }); } async createPe(req: Request, res: Response) { const event = await sessionService.getCurrentEventFromReq(req); const user = sessionService.getUserFromReq(req); const { fields, peTypeCode, name } = api.client.pe.POST_PartEntity.req.formData.body.parse( JSON.parse(req.body.body), ); const files = req.files; const peType = await cPeService.getPeTypeWithFields(peTypeCode); if (!peType) throw ApiError.BadRequest( "peTypeNotFound", "Тип сущности участия не найден", ); const refFields = peType.fields.map((f) => ({ ...f, idKey: "peFfId", })); // валидация const validationResult = await cCustomFieldsValidateService.processAndValidateFields({ inputFields: fields, referenceFields: refFields, files, idKey: "peFfId", addOldValue: false, }); if (!validationResult.isValid) throw ApiError.BadRequest( "fieldsValidationFailed", JSON.stringify(validationResult.messages), ); const validatedFields = validationResult.checkedfields; // // // вставляем в базу и сохраняем файлы const peId = v7(); await updPool.transaction(async (tr) => { await tr.query(sql.unsafe` insert into act.part_entities (pe_id, pe_type_id, event_inst_id, owner_id, name) values (${peId}, ${peType.peTypeId}, ${event.eventInstId}, ${user.userId}, ${name}) `); await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({ tr, parentId: peId, action: "peCreate", inputFields: validatedFields, files, isDeleteBefore: false, }); }); RouterUtils.validAndSendResponse(api.client.pe.POST_PartEntity.res, res, { code: "success", peId, }); } async getPeForPatch(req: Request, res: Response) { const { peId } = api.client.pe.GET_PeForPatch.req.parse(req.params); const user = sessionService.getUserFromReq(req); const pe = await cPeService.getPeWithValidatorsAndValues(peId); if (!pe) throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена"); if (pe.ownerId !== user.userId) throw ApiError.ForbiddenError(); RouterUtils.validAndSendResponse(api.client.pe.GET_PeForPatch.res, res, { code: "success", pe: pe, }); } async patchPe(req: Request, res: Response) { const { peId } = api.client.pe.PATCH_PartEntity.req.params.parse( req.params, ); const { name, fields } = api.client.pe.PATCH_PartEntity.req.formData.body.parse( JSON.parse(req.body.body), ); const files = req.files; const user = sessionService.getUserFromReq(req); const peType = await cPeService.getPeWithValidatorsAndValues(peId); if (!peType) throw ApiError.BadRequest( "peTypeNotFound", "Тип сущности участия не найден", ); // проверка доступа if (peType.ownerId !== user.userId) throw ApiError.ForbiddenError(); const refFields = peType.fields .map((f) => ({ ...f, idKey: "peFfId", })) // только изменяемые .filter((f) => fields.some((ff) => ff.peFfId === f.peFfId)); // валидация const validationResult = await cCustomFieldsValidateService.processAndValidateFields({ inputFields: fields, referenceFields: refFields, files, idKey: "peFfId", addOldValue: true, }); if (!validationResult.isValid) throw ApiError.BadRequest( "fieldsValidationFailed", JSON.stringify(validationResult.messages), ); const validatedFields = validationResult.checkedfields; // // // вставляем в базу и сохраняем файлы await updPool.transaction(async (tr) => { if (name) { await tr.query(sql.unsafe` update act.part_entities set name = ${name} where pe_id = ${peId} `); } await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({ tr, parentId: peId, action: "peCreate", inputFields: validatedFields, files, isDeleteBefore: true, }); }); RouterUtils.validAndSendResponse(api.client.pe.PATCH_PartEntity.res, res, { code: "success", }); } async getMyPes(req: Request, res: Response) { const user = sessionService.getUserFromReq(req); const event = await sessionService.getCurrentEventFromReq(req); const ownerPes = await selPool.any(sql.type( z.object({ peId: DbSchema.act.partEntities.peId, peTypeId: DbSchema.act.partEntities.peTypeId, peTypeCode: DbSchema.act.peTypes.code, peTypeName: DbSchema.act.peTypes.name, eventInstId: DbSchema.act.partEntities.eventInstId, ownerId: DbSchema.act.partEntities.ownerId, name: DbSchema.act.partEntities.name, }), )` select pe.pe_id "peId", pe.event_inst_id "eventInstId", pe.owner_id "ownerId", pe.pe_type_id "peTypeId", pt.code "peTypeCode", pt."name" "peTypeName", pe."name" from act.part_entities pe join act.pe_types pt on pt.pe_type_id = pe.pe_type_id where pe.event_inst_id = ${event.eventInstId} and pe.owner_id = ${user.userId} `); const memberPes = await selPool.any(sql.type( z.object({ peMemberId: DbSchema.act.peMembers.peMemberId, peId: DbSchema.act.partEntities.peId, peTypeId: DbSchema.act.partEntities.peTypeId, peTypeCode: DbSchema.act.peTypes.code, peTypeName: DbSchema.act.peTypes.name, ownerId: DbSchema.act.partEntities.ownerId, name: DbSchema.act.partEntities.name, }), )` select pm.pe_member_id "peMemberId", pe.pe_id "peId", pe.event_inst_id "peInstId", pe.owner_id "ownerId", pe.pe_type_id "peTypeId", pt.code "peTypeCode", pt."name" "peTypeName", pe."name" from act.pe_members pm join act.part_entities pe on pe.pe_id = pm.pe_id join act.pe_types pt on pt.pe_type_id = pe.pe_type_id where pe.event_inst_id = ${event.eventInstId} and pm.user_id = ${user.userId} `); RouterUtils.validAndSendResponse(api.client.pe.GET_MyPes.res, res, { code: "success", owner: [...ownerPes], memeber: [...memberPes], }); } async getPe(req: Request, res: Response) { const user = sessionService.getUserFromReq(req); const { peId } = api.client.pe.GET_PartEntity.req.parse(req.params); const isOwner = await selPool.exists( sql.unsafe` select pe_id from act.part_entities pe where pe.owner_id = ${user.userId} and pe.pe_id = ${peId} `, ); // валделец if (isOwner) { const pe = await cPeService.getPeWithValues(peId); const members = await cPeService.getMembers(peId); const invites = await cPeService.getInvites(peId); if (!pe) throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена"); if (pe.ownerId !== user.userId) throw ApiError.ForbiddenError(); RouterUtils.validAndSendResponse(api.client.pe.GET_PartEntity.res, res, { code: "success", pe: { ...pe, members: [...members], invites: [...invites], userRole: "owner", }, }); return; } // участник const isMember = await selPool.exists( sql.unsafe` select pm.pe_id from act.pe_members pm where pm.user_id = ${user.userId} and pm.pe_id = ${peId} `, ); if (!isMember) throw ApiError.ForbiddenError(); const pe = await cPeService.getPeForMember(peId); if (!pe) throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена"); RouterUtils.validAndSendResponse(api.client.pe.GET_PartEntity.res, res, { code: "success", pe: { ...pe, userRole: "member" }, }); } async createInvite(req: Request, res: Response) { const user = sessionService.getUserFromReq(req); const { name, limitVal, expirationDate } = api.client.pe.POST_Invite.req.body.parse(req.body); const { peId } = api.client.pe.POST_Invite.req.params.parse(req.params); const isOwner = await cPeService.checkPeOwner(user.userId, peId); if (!isOwner) throw ApiError.ForbiddenError(); const peInviteId = v7(); const peInviteUuid = v4(); await updPool.transaction(async (t) => { await t.query(sql.unsafe` insert into act.pe_invites (pe_invite_id, pe_invite_uuid, pe_id, limit_val, name, expiration_date) values (${peInviteId}, ${peInviteUuid}, ${peId}, ${limitVal}, ${name}, ${expirationDate}) `); }); RouterUtils.validAndSendResponse(api.client.pe.POST_Invite.res, res, { code: "success", peInviteId, }); } async getInvites(req: Request, res: Response) { const user = sessionService.getUserFromReq(req); const { peId } = api.client.pe.GET_Invites.req.parse(req.params); const isOwner = await cPeService.checkPeOwner(user.userId, peId); if (!isOwner) throw ApiError.ForbiddenError(); const invites = await cPeService.getInvites(peId); RouterUtils.validAndSendResponse(api.client.pe.GET_Invites.res, res, { code: "success", invites: [...invites], }); } async getInviteInfo(req: Request, res: Response) { const { peInviteUuid } = api.client.pe.GET_InviteInfo.req.params.parse( req.params, ); const invite = await cPeService.getInviteInfo(peInviteUuid); if (!invite) throw ApiError.BadRequest("inviteNotFound", "Приглашение не найдено"); RouterUtils.validAndSendResponse(api.client.pe.GET_InviteInfo.res, res, { code: "success", invite, }); } async acceptInvite(req: Request, res: Response) { const user = sessionService.getUserFromReq(req); const { peInviteUuid } = api.client.pe.POST_AcceptInvite.req.params.parse( req.params, ); const invite = await cPeService.getInviteInfo(peInviteUuid); if (!invite) { RouterUtils.validAndSendResponse( api.client.pe.POST_AcceptInvite.res, res, { code: "inviteNotFound", }, 400, ); return; } if (invite.limitVal && invite.countVal >= invite.limitVal) { RouterUtils.validAndSendResponse( api.client.pe.POST_AcceptInvite.res, res, { code: "inviteLimitExceeded", }, 400, ); return; } // TODO: много лишних данных const peMembers = await cPeService.getMembers(invite.peId); const isFound = peMembers.find((m) => m.userId === user.userId); if (isFound) { RouterUtils.validAndSendResponse( api.client.pe.POST_AcceptInvite.res, res, { code: "peMemberAlreadyExists", }, 400, ); return; } await updPool.transaction(async (t) => { t.query(sql.unsafe` insert into act.pe_members (pe_member_id, pe_id, user_id) values (${v7()}, ${invite.peId}, ${user.userId}) `); t.query(sql.unsafe` update act.pe_invites set count_val = count_val + 1 where pe_id = ${invite.peId} `); }); RouterUtils.validAndSendResponse(api.client.pe.POST_AcceptInvite.res, res, { code: "success", peId: invite.peId, }); } async getMyPesForActivity(req: Request, res: Response) { const user = sessionService.getUserFromReq(req); const event = await sessionService.getCurrentEventFromReq(req); const pes = await selPool.any(sql.type(apiTypes.activities.PeForActivity)` select pe.pe_id "peId", pe.event_inst_id "eventInstId", pe.owner_id "ownerId", pe.pe_type_id "peTypeId", pt.code "peTypeCode", pt."name" "peTypeName", pe."name", coalesce(m.members, '[]'::jsonb) members, v.fields from act.part_entities pe join act.pe_types pt on pt.pe_type_id = pe.pe_type_id -- members left join lateral ( select jsonb_agg(jsonb_build_object( 'peMemberId', m.pe_member_id, 'userId', m.user_id, 'email', m.email, 'fields', m.fields )) as members from act.pe_members_with_fields_and_values m where m.pe_id = pe.pe_id ) m on true -- fields left join act.pe_with_fields_and_values v on v.pe_id = pe.pe_id where pe.event_inst_id = ${event.eventInstId} and pe.owner_id = ${user.userId} `); RouterUtils.validAndSendResponse( api.client.pe.GET_MyPesForActivity.res, res, { code: "success", pes: [...pes], }, ); } } export const clientPeController = new ClientPeController();