c-pe-controller.ts 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562
  1. // db
  2. import { selPool, updPool } from "#db";
  3. import { DbSchema } from "#db-schema";
  4. import { sql } from "slonik";
  5. // api
  6. import { api, apiTypes } from "#api";
  7. // other
  8. import { z } from "zod";
  9. import { RouterUtils } from "#utils/router-utils.js";
  10. import { Request, Response } from "express";
  11. import sessionService from "#modules/users/auth/services/session-service.js";
  12. import { ApiError } from "#exceptions/api-error.js";
  13. import { v4, v7 } from "uuid";
  14. import { cPeService } from "./c-pe-service.js";
  15. import { cCustomFieldsValidateService } from "#modules/client/custom-fields/c-cf-validate-service.js";
  16. class ClientPeController {
  17. async getEventPeTypes(
  18. req: Request,
  19. res: Response,
  20. // next: NextFunction
  21. ) {
  22. const event = await sessionService.getCurrentEventFromReq(req);
  23. const peTypes = await selPool.any(sql.type(
  24. z.object({
  25. peTypeId: DbSchema.act.peTypes.peTypeId,
  26. code: DbSchema.act.peTypes.code,
  27. name: DbSchema.act.peTypes.name,
  28. }),
  29. )`
  30. select
  31. pe_type_id as "peTypeId",
  32. code,
  33. "name"
  34. from
  35. act.pe_types pt
  36. where
  37. pt.event_inst_id = ${event.eventInstId}
  38. `);
  39. RouterUtils.validAndSendResponse(api.client.pe.GET_EventPeTypes.res, res, {
  40. code: "success",
  41. peTypes: [...peTypes],
  42. });
  43. }
  44. async getPeTypeForCreate(
  45. req: Request,
  46. res: Response,
  47. // next: NextFunction
  48. ) {
  49. const event = await sessionService.getCurrentEventFromReq(req);
  50. const user = sessionService.getUserFromReq(req);
  51. const { peTypeCode } = api.client.pe.GET_PeType.req.parse(req.params);
  52. const eventId = event.eventId;
  53. const userId = user.userId;
  54. const peType = await cPeService.getPeTypeWithFieldsAndUserCopyValues(
  55. userId,
  56. peTypeCode,
  57. eventId,
  58. );
  59. if (!peType)
  60. throw ApiError.BadRequest(
  61. "peTypeNotFound",
  62. "Тип сущности участия не найден",
  63. );
  64. RouterUtils.validAndSendResponse(api.client.pe.GET_PeType.res, res, {
  65. code: "success",
  66. peType: peType,
  67. });
  68. }
  69. async createPe(req: Request, res: Response) {
  70. const event = await sessionService.getCurrentEventFromReq(req);
  71. const user = sessionService.getUserFromReq(req);
  72. const { fields, peTypeCode, name } =
  73. api.client.pe.POST_PartEntity.req.formData.body.parse(
  74. JSON.parse(req.body.body),
  75. );
  76. const files = req.files;
  77. const peType = await cPeService.getPeTypeWithFields(peTypeCode);
  78. if (!peType)
  79. throw ApiError.BadRequest(
  80. "peTypeNotFound",
  81. "Тип сущности участия не найден",
  82. );
  83. const refFields = peType.fields.map((f) => ({
  84. ...f,
  85. idKey: "peFfId",
  86. }));
  87. // валидация
  88. const validationResult =
  89. await cCustomFieldsValidateService.processAndValidateFields({
  90. inputFields: fields,
  91. referenceFields: refFields,
  92. files,
  93. idKey: "peFfId",
  94. addOldValue: false,
  95. });
  96. if (!validationResult.isValid)
  97. throw ApiError.BadRequest(
  98. "fieldsValidationFailed",
  99. JSON.stringify(validationResult.messages),
  100. );
  101. const validatedFields = validationResult.checkedfields;
  102. //
  103. //
  104. // вставляем в базу и сохраняем файлы
  105. const peId = v7();
  106. await updPool.transaction(async (tr) => {
  107. await tr.query(sql.unsafe`
  108. insert into act.part_entities
  109. (pe_id, pe_type_id, event_inst_id, owner_id, name)
  110. values
  111. (${peId}, ${peType.peTypeId}, ${event.eventInstId}, ${user.userId}, ${name})
  112. `);
  113. await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({
  114. tr,
  115. parentId: peId,
  116. action: "peCreate",
  117. inputFields: validatedFields,
  118. files,
  119. isDeleteBefore: false,
  120. });
  121. });
  122. RouterUtils.validAndSendResponse(api.client.pe.POST_PartEntity.res, res, {
  123. code: "success",
  124. peId,
  125. });
  126. }
  127. async getPeForPatch(req: Request, res: Response) {
  128. const { peId } = api.client.pe.GET_PeForPatch.req.parse(req.params);
  129. const user = sessionService.getUserFromReq(req);
  130. const pe = await cPeService.getPeWithValidatorsAndValues(peId);
  131. if (!pe)
  132. throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена");
  133. if (pe.ownerId !== user.userId) throw ApiError.ForbiddenError();
  134. RouterUtils.validAndSendResponse(api.client.pe.GET_PeForPatch.res, res, {
  135. code: "success",
  136. pe: pe,
  137. });
  138. }
  139. async patchPe(req: Request, res: Response) {
  140. const { peId } = api.client.pe.PATCH_PartEntity.req.params.parse(
  141. req.params,
  142. );
  143. const { name, fields } =
  144. api.client.pe.PATCH_PartEntity.req.formData.body.parse(
  145. JSON.parse(req.body.body),
  146. );
  147. const files = req.files;
  148. const user = sessionService.getUserFromReq(req);
  149. const peType = await cPeService.getPeWithValidatorsAndValues(peId);
  150. if (!peType)
  151. throw ApiError.BadRequest(
  152. "peTypeNotFound",
  153. "Тип сущности участия не найден",
  154. );
  155. // проверка доступа
  156. if (peType.ownerId !== user.userId) throw ApiError.ForbiddenError();
  157. const refFields = peType.fields
  158. .map((f) => ({
  159. ...f,
  160. idKey: "peFfId",
  161. }))
  162. // только изменяемые
  163. .filter((f) => fields.some((ff) => ff.peFfId === f.peFfId));
  164. // валидация
  165. const validationResult =
  166. await cCustomFieldsValidateService.processAndValidateFields({
  167. inputFields: fields,
  168. referenceFields: refFields,
  169. files,
  170. idKey: "peFfId",
  171. addOldValue: true,
  172. });
  173. if (!validationResult.isValid)
  174. throw ApiError.BadRequest(
  175. "fieldsValidationFailed",
  176. JSON.stringify(validationResult.messages),
  177. );
  178. const validatedFields = validationResult.checkedfields;
  179. //
  180. //
  181. // вставляем в базу и сохраняем файлы
  182. await updPool.transaction(async (tr) => {
  183. if (name) {
  184. await tr.query(sql.unsafe`
  185. update act.part_entities
  186. set
  187. name = ${name}
  188. where
  189. pe_id = ${peId}
  190. `);
  191. }
  192. await cCustomFieldsValidateService.saveCustomFieldValuesInTransaction({
  193. tr,
  194. parentId: peId,
  195. action: "peCreate",
  196. inputFields: validatedFields,
  197. files,
  198. isDeleteBefore: true,
  199. });
  200. });
  201. RouterUtils.validAndSendResponse(api.client.pe.PATCH_PartEntity.res, res, {
  202. code: "success",
  203. });
  204. }
  205. async getMyPes(req: Request, res: Response) {
  206. const user = sessionService.getUserFromReq(req);
  207. const event = await sessionService.getCurrentEventFromReq(req);
  208. const ownerPes = await selPool.any(sql.type(
  209. z.object({
  210. peId: DbSchema.act.partEntities.peId,
  211. peTypeId: DbSchema.act.partEntities.peTypeId,
  212. peTypeCode: DbSchema.act.peTypes.code,
  213. peTypeName: DbSchema.act.peTypes.name,
  214. eventInstId: DbSchema.act.partEntities.eventInstId,
  215. ownerId: DbSchema.act.partEntities.ownerId,
  216. name: DbSchema.act.partEntities.name,
  217. }),
  218. )`
  219. select
  220. pe.pe_id "peId",
  221. pe.event_inst_id "eventInstId",
  222. pe.owner_id "ownerId",
  223. pe.pe_type_id "peTypeId",
  224. pt.code "peTypeCode",
  225. pt."name" "peTypeName",
  226. pe."name"
  227. from
  228. act.part_entities pe
  229. join act.pe_types pt on
  230. pt.pe_type_id = pe.pe_type_id
  231. where
  232. pe.event_inst_id = ${event.eventInstId}
  233. and pe.owner_id = ${user.userId}
  234. `);
  235. const memberPes = await selPool.any(sql.type(
  236. z.object({
  237. peMemberId: DbSchema.act.peMembers.peMemberId,
  238. peId: DbSchema.act.partEntities.peId,
  239. peTypeId: DbSchema.act.partEntities.peTypeId,
  240. peTypeCode: DbSchema.act.peTypes.code,
  241. peTypeName: DbSchema.act.peTypes.name,
  242. ownerId: DbSchema.act.partEntities.ownerId,
  243. name: DbSchema.act.partEntities.name,
  244. }),
  245. )`
  246. select
  247. pm.pe_member_id "peMemberId",
  248. pe.pe_id "peId",
  249. pe.event_inst_id "peInstId",
  250. pe.owner_id "ownerId",
  251. pe.pe_type_id "peTypeId",
  252. pt.code "peTypeCode",
  253. pt."name" "peTypeName",
  254. pe."name"
  255. from
  256. act.pe_members pm
  257. join act.part_entities pe on
  258. pe.pe_id = pm.pe_id
  259. join act.pe_types pt on
  260. pt.pe_type_id = pe.pe_type_id
  261. where
  262. pe.event_inst_id = ${event.eventInstId}
  263. and pm.user_id = ${user.userId}
  264. `);
  265. RouterUtils.validAndSendResponse(api.client.pe.GET_MyPes.res, res, {
  266. code: "success",
  267. owner: [...ownerPes],
  268. memeber: [...memberPes],
  269. });
  270. }
  271. async getPe(req: Request, res: Response) {
  272. const user = sessionService.getUserFromReq(req);
  273. const { peId } = api.client.pe.GET_PartEntity.req.parse(req.params);
  274. const isOwner = await selPool.exists(
  275. sql.unsafe`
  276. select
  277. pe_id
  278. from
  279. act.part_entities pe
  280. where
  281. pe.owner_id = ${user.userId}
  282. and pe.pe_id = ${peId}
  283. `,
  284. );
  285. // валделец
  286. if (isOwner) {
  287. const pe = await cPeService.getPeWithValues(peId);
  288. const members = await cPeService.getMembers(peId);
  289. const invites = await cPeService.getInvites(peId);
  290. if (!pe)
  291. throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена");
  292. if (pe.ownerId !== user.userId) throw ApiError.ForbiddenError();
  293. RouterUtils.validAndSendResponse(api.client.pe.GET_PartEntity.res, res, {
  294. code: "success",
  295. pe: {
  296. ...pe,
  297. members: [...members],
  298. invites: [...invites],
  299. userRole: "owner",
  300. },
  301. });
  302. return;
  303. }
  304. // участник
  305. const isMember = await selPool.exists(
  306. sql.unsafe`
  307. select
  308. pm.pe_id
  309. from
  310. act.pe_members pm
  311. where
  312. pm.user_id = ${user.userId}
  313. and pm.pe_id = ${peId}
  314. `,
  315. );
  316. if (!isMember) throw ApiError.ForbiddenError();
  317. const pe = await cPeService.getPeForMember(peId);
  318. if (!pe)
  319. throw ApiError.BadRequest("peNotFound", "Сущность участия не найдена");
  320. RouterUtils.validAndSendResponse(api.client.pe.GET_PartEntity.res, res, {
  321. code: "success",
  322. pe: { ...pe, userRole: "member" },
  323. });
  324. }
  325. async createInvite(req: Request, res: Response) {
  326. const user = sessionService.getUserFromReq(req);
  327. const { name, limitVal, expirationDate } =
  328. api.client.pe.POST_Invite.req.body.parse(req.body);
  329. const { peId } = api.client.pe.POST_Invite.req.params.parse(req.params);
  330. const isOwner = await cPeService.checkPeOwner(user.userId, peId);
  331. if (!isOwner) throw ApiError.ForbiddenError();
  332. const peInviteId = v7();
  333. const peInviteUuid = v4();
  334. await updPool.transaction(async (t) => {
  335. await t.query(sql.unsafe`
  336. insert into act.pe_invites
  337. (pe_invite_id, pe_invite_uuid, pe_id, limit_val, name, expiration_date)
  338. values
  339. (${peInviteId}, ${peInviteUuid}, ${peId}, ${limitVal}, ${name}, ${expirationDate})
  340. `);
  341. });
  342. RouterUtils.validAndSendResponse(api.client.pe.POST_Invite.res, res, {
  343. code: "success",
  344. peInviteId,
  345. });
  346. }
  347. async getInvites(req: Request, res: Response) {
  348. const user = sessionService.getUserFromReq(req);
  349. const { peId } = api.client.pe.GET_Invites.req.parse(req.params);
  350. const isOwner = await cPeService.checkPeOwner(user.userId, peId);
  351. if (!isOwner) throw ApiError.ForbiddenError();
  352. const invites = await cPeService.getInvites(peId);
  353. RouterUtils.validAndSendResponse(api.client.pe.GET_Invites.res, res, {
  354. code: "success",
  355. invites: [...invites],
  356. });
  357. }
  358. async getInviteInfo(req: Request, res: Response) {
  359. const { peInviteUuid } = api.client.pe.GET_InviteInfo.req.params.parse(
  360. req.params,
  361. );
  362. const invite = await cPeService.getInviteInfo(peInviteUuid);
  363. if (!invite)
  364. throw ApiError.BadRequest("inviteNotFound", "Приглашение не найдено");
  365. RouterUtils.validAndSendResponse(api.client.pe.GET_InviteInfo.res, res, {
  366. code: "success",
  367. invite,
  368. });
  369. }
  370. async acceptInvite(req: Request, res: Response) {
  371. const user = sessionService.getUserFromReq(req);
  372. const { peInviteUuid } = api.client.pe.POST_AcceptInvite.req.params.parse(
  373. req.params,
  374. );
  375. const invite = await cPeService.getInviteInfo(peInviteUuid);
  376. if (!invite) {
  377. RouterUtils.validAndSendResponse(
  378. api.client.pe.POST_AcceptInvite.res,
  379. res,
  380. {
  381. code: "inviteNotFound",
  382. },
  383. 400,
  384. );
  385. return;
  386. }
  387. if (invite.limitVal && invite.countVal >= invite.limitVal) {
  388. RouterUtils.validAndSendResponse(
  389. api.client.pe.POST_AcceptInvite.res,
  390. res,
  391. {
  392. code: "inviteLimitExceeded",
  393. },
  394. 400,
  395. );
  396. return;
  397. }
  398. // TODO: много лишних данных
  399. const peMembers = await cPeService.getMembers(invite.peId);
  400. const isFound = peMembers.find((m) => m.userId === user.userId);
  401. if (isFound) {
  402. RouterUtils.validAndSendResponse(
  403. api.client.pe.POST_AcceptInvite.res,
  404. res,
  405. {
  406. code: "peMemberAlreadyExists",
  407. },
  408. 400,
  409. );
  410. return;
  411. }
  412. await updPool.transaction(async (t) => {
  413. t.query(sql.unsafe`
  414. insert into act.pe_members
  415. (pe_member_id, pe_id, user_id)
  416. values
  417. (${v7()}, ${invite.peId}, ${user.userId})
  418. `);
  419. t.query(sql.unsafe`
  420. update act.pe_invites
  421. set
  422. count_val = count_val + 1
  423. where
  424. pe_id = ${invite.peId}
  425. `);
  426. });
  427. RouterUtils.validAndSendResponse(api.client.pe.POST_AcceptInvite.res, res, {
  428. code: "success",
  429. peId: invite.peId,
  430. });
  431. }
  432. async getMyPesForActivity(req: Request, res: Response) {
  433. const user = sessionService.getUserFromReq(req);
  434. const event = await sessionService.getCurrentEventFromReq(req);
  435. const pes = await selPool.any(sql.type(apiTypes.activities.PeForActivity)`
  436. select
  437. pe.pe_id "peId",
  438. pe.event_inst_id "eventInstId",
  439. pe.owner_id "ownerId",
  440. pe.pe_type_id "peTypeId",
  441. pt.code "peTypeCode",
  442. pt."name" "peTypeName",
  443. pe."name",
  444. coalesce(m.members, '[]'::jsonb) members,
  445. v.fields
  446. from
  447. act.part_entities pe
  448. join act.pe_types pt on
  449. pt.pe_type_id = pe.pe_type_id
  450. -- members
  451. left join lateral (
  452. select
  453. jsonb_agg(jsonb_build_object(
  454. 'peMemberId', m.pe_member_id,
  455. 'userId', m.user_id,
  456. 'email', m.email,
  457. 'fields', m.fields
  458. )) as members
  459. from
  460. act.pe_members_with_fields_and_values m
  461. where
  462. m.pe_id = pe.pe_id
  463. ) m on
  464. true
  465. -- fields
  466. left join act.pe_with_fields_and_values v on
  467. v.pe_id = pe.pe_id
  468. where
  469. pe.event_inst_id = ${event.eventInstId}
  470. and pe.owner_id = ${user.userId}
  471. `);
  472. RouterUtils.validAndSendResponse(
  473. api.client.pe.GET_MyPesForActivity.res,
  474. res,
  475. {
  476. code: "success",
  477. pes: [...pes],
  478. },
  479. );
  480. }
  481. }
  482. export const clientPeController = new ClientPeController();