From 5c4e521bd8e503747ff93ba2b6884e4e7b3dbd24 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Thu, 3 Oct 2024 13:31:05 +0200 Subject: [PATCH 01/22] get protocol item --- src/controller/admin/protocolController.ts | 22 ++++++++++ src/data-source.ts | 4 ++ src/entity/protocol.ts | 13 ++++++ src/factory/admin/protocol.ts | 26 +++++++++++ src/migrations/1727953803404-protocol-init.ts | 26 +++++++++++ src/routes/admin/index.ts | 3 ++ src/routes/admin/protocol.ts | 10 +++++ src/service/protocolService.ts | 43 +++++++++++++++++++ src/type/permissionTypes.ts | 6 +-- src/viewmodel/admin/protocol.models.ts | 5 +++ 10 files changed, 155 insertions(+), 3 deletions(-) create mode 100644 src/controller/admin/protocolController.ts create mode 100644 src/entity/protocol.ts create mode 100644 src/factory/admin/protocol.ts create mode 100644 src/migrations/1727953803404-protocol-init.ts create mode 100644 src/routes/admin/protocol.ts create mode 100644 src/service/protocolService.ts create mode 100644 src/viewmodel/admin/protocol.models.ts diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts new file mode 100644 index 0000000..6df7366 --- /dev/null +++ b/src/controller/admin/protocolController.ts @@ -0,0 +1,22 @@ +import { Request, Response } from "express"; +import ProtocolService from "../../service/protocolService"; +import ProtocolFactory from "../../factory/admin/protocol"; + +/** + * @description get all protocols + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllProtocols(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let [protocols, total] = await ProtocolService.getAll(offset, count); + + res.json({ + protocols: ProtocolFactory.mapToBase(protocols), + total: total, + offset: offset, + count: count, + }); +} diff --git a/src/data-source.ts b/src/data-source.ts index a78ab3c..4b01f48 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -29,6 +29,8 @@ import { memberQualifications } from "./entity/memberQualifications"; import { membership } from "./entity/membership"; import { Memberdata1726301836849 } from "./migrations/1726301836849-memberdata"; import { CommunicationFields1727439800630 } from "./migrations/1727439800630-communicationFields"; +import { protocol } from "./entity/protocol"; +import { ProtocolInit1727953803404 } from "./migrations/1727953803404-protocol-init"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -58,6 +60,7 @@ const dataSource = new DataSource({ memberExecutivePositions, memberQualifications, membership, + protocol, ], migrations: [ Initial1724317398939, @@ -68,6 +71,7 @@ const dataSource = new DataSource({ MemberBaseData1725435669492, Memberdata1726301836849, CommunicationFields1727439800630, + ProtocolInit1727953803404, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/protocol.ts b/src/entity/protocol.ts new file mode 100644 index 0000000..9285e0f --- /dev/null +++ b/src/entity/protocol.ts @@ -0,0 +1,13 @@ +import { Column, Entity, PrimaryColumn } from "typeorm"; + +@Entity() +export class protocol { + @PrimaryColumn({ generated: "increment", type: "int" }) + id: number; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "date" }) + date: Date; +} diff --git a/src/factory/admin/protocol.ts b/src/factory/admin/protocol.ts new file mode 100644 index 0000000..ef5a873 --- /dev/null +++ b/src/factory/admin/protocol.ts @@ -0,0 +1,26 @@ +import { protocol } from "../../entity/protocol"; +import { ProtocolViewModel } from "../../viewmodel/admin/protocol.models"; + +export default abstract class ProtocolFactory { + /** + * @description map record to protocol + * @param {protocol} record + * @returns {ProtocolViewModel} + */ + public static mapToSingle(record: protocol): ProtocolViewModel { + return { + id: record.id, + title: record.title, + date: record.date, + }; + } + + /** + * @description map records to protocol + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/migrations/1727953803404-protocol-init.ts b/src/migrations/1727953803404-protocol-init.ts new file mode 100644 index 0000000..f7bd435 --- /dev/null +++ b/src/migrations/1727953803404-protocol-init.ts @@ -0,0 +1,26 @@ +import { MigrationInterface, QueryRunner, Table } from "typeorm"; +import { DB_TYPE } from "../env.defaults"; + +export class ProtocolInit1727953803404 implements MigrationInterface { + name = "ProtocolInit1727953803404"; + + public async up(queryRunner: QueryRunner): Promise { + const variableType_int = DB_TYPE == "mysql" ? "int" : "integer"; + + await queryRunner.createTable( + new Table({ + name: "protocol", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "title", type: "varchar", length: "255", isNullable: false }, + { name: "date", type: "date", isNullable: false }, + ], + }), + true + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable("protocol"); + } +} diff --git a/src/routes/admin/index.ts b/src/routes/admin/index.ts index c0c1ab9..6c891e1 100644 --- a/src/routes/admin/index.ts +++ b/src/routes/admin/index.ts @@ -8,6 +8,7 @@ import membershipStatus from "./membershipStatus"; import qualification from "./qualification"; import member from "./member"; +import protocol from "./protocol"; import role from "./role"; import user from "./user"; @@ -34,6 +35,8 @@ router.use("/qualification", PermissionHelper.passCheckMiddleware("read", "setti router.use("/member", PermissionHelper.passCheckMiddleware("read", "club", "member"), member); +router.use("/protocol", PermissionHelper.passCheckMiddleware("read", "club", "protocol"), protocol); + router.use("/role", PermissionHelper.passCheckMiddleware("read", "user", "role"), role); router.use("/user", PermissionHelper.passCheckMiddleware("read", "user", "user"), user); diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts new file mode 100644 index 0000000..e737ea5 --- /dev/null +++ b/src/routes/admin/protocol.ts @@ -0,0 +1,10 @@ +import express, { Request, Response } from "express"; +import { getAllProtocols } from "../../controller/admin/protocolController"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllProtocols(req, res); +}); + +export default router; diff --git a/src/service/protocolService.ts b/src/service/protocolService.ts new file mode 100644 index 0000000..553b550 --- /dev/null +++ b/src/service/protocolService.ts @@ -0,0 +1,43 @@ +import { dataSource } from "../data-source"; +import { protocol } from "../entity/protocol"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolService { + /** + * @description get all protocols + * @returns {Promise<[Array, number]>} + */ + static async getAll(offset: number = 0, count: number = 25): Promise<[Array, number]> { + return await dataSource + .getRepository(protocol) + .createQueryBuilder("protocol") + .offset(offset) + .limit(count) + .orderBy("date") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocols not found", err); + }); + } + + /** + * @description get protocol by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocol) + .createQueryBuilder("protocol") + .where("protocol.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocol not found by id", err); + }); + } +} diff --git a/src/type/permissionTypes.ts b/src/type/permissionTypes.ts index 3bd54c8..cb8a989 100644 --- a/src/type/permissionTypes.ts +++ b/src/type/permissionTypes.ts @@ -4,7 +4,7 @@ export type PermissionModule = | "member" | "calendar" | "newsletter" - | "protocoll" + | "protocol" | "qualification" | "award" | "executive_position" @@ -39,7 +39,7 @@ export const permissionModules: Array = [ "member", "calendar", "newsletter", - "protocoll", + "protocol", "qualification", "award", "executive_position", @@ -50,7 +50,7 @@ export const permissionModules: Array = [ ]; export const permissionTypes: Array = ["read", "create", "update", "delete"]; export const sectionsAndModules: SectionsAndModulesObject = { - club: ["member", "calendar", "newsletter", "protocoll"], + club: ["member", "calendar", "newsletter", "protocol"], settings: ["qualification", "award", "executive_position", "communication", "membership_status"], user: ["user", "role"], }; diff --git a/src/viewmodel/admin/protocol.models.ts b/src/viewmodel/admin/protocol.models.ts new file mode 100644 index 0000000..051b832 --- /dev/null +++ b/src/viewmodel/admin/protocol.models.ts @@ -0,0 +1,5 @@ +export interface ProtocolViewModel { + id: number; + title: string; + date: Date; +} From edc35f2f878e4d348086a411b85337684296d78e Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Fri, 4 Oct 2024 12:47:13 +0200 Subject: [PATCH 02/22] protocol base data --- src/controller/admin/protocolController.ts | 13 +++++++ src/data-source.ts | 2 ++ src/entity/protocol.ts | 9 +++++ src/factory/admin/protocol.ts | 3 ++ src/migrations/1728037129072-protocolBase.ts | 38 ++++++++++++++++++++ src/routes/admin/protocol.ts | 6 +++- src/viewmodel/admin/protocol.models.ts | 3 ++ 7 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 src/migrations/1728037129072-protocolBase.ts diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index 6df7366..b94ac1f 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -20,3 +20,16 @@ export async function getAllProtocols(req: Request, res: Response): Promise count: count, }); } + +/** + * @description get protocol by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolById(req: Request, res: Response): Promise { + let id = parseInt(req.params.id); + let protocol = await ProtocolService.getById(id); + + res.json(ProtocolFactory.mapToSingle(protocol)); +} diff --git a/src/data-source.ts b/src/data-source.ts index 4b01f48..e1de44b 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -31,6 +31,7 @@ import { Memberdata1726301836849 } from "./migrations/1726301836849-memberdata"; import { CommunicationFields1727439800630 } from "./migrations/1727439800630-communicationFields"; import { protocol } from "./entity/protocol"; import { ProtocolInit1727953803404 } from "./migrations/1727953803404-protocol-init"; +import { ProtocolBase1728037129072 } from "./migrations/1728037129072-protocolBase"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -72,6 +73,7 @@ const dataSource = new DataSource({ Memberdata1726301836849, CommunicationFields1727439800630, ProtocolInit1727953803404, + ProtocolBase1728037129072, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/protocol.ts b/src/entity/protocol.ts index 9285e0f..ff25531 100644 --- a/src/entity/protocol.ts +++ b/src/entity/protocol.ts @@ -10,4 +10,13 @@ export class protocol { @Column({ type: "date" }) date: Date; + + @Column({ type: "timestamp" }) + starttime: Date; + + @Column({ type: "timestamp" }) + endtime: Date; + + @Column({ type: "text" }) + summary: string; } diff --git a/src/factory/admin/protocol.ts b/src/factory/admin/protocol.ts index ef5a873..9b86894 100644 --- a/src/factory/admin/protocol.ts +++ b/src/factory/admin/protocol.ts @@ -12,6 +12,9 @@ export default abstract class ProtocolFactory { id: record.id, title: record.title, date: record.date, + starttime: record.starttime, + endtime: record.endtime, + summary: record.summary, }; } diff --git a/src/migrations/1728037129072-protocolBase.ts b/src/migrations/1728037129072-protocolBase.ts new file mode 100644 index 0000000..e1f9a71 --- /dev/null +++ b/src/migrations/1728037129072-protocolBase.ts @@ -0,0 +1,38 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class ProtocolBase1728037129072 implements MigrationInterface { + name = "ProtocolBase1728037129072"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumn( + "protocol", + new TableColumn({ + name: "starttime", + type: "timestamp", + isNullable: false, + }) + ); + await queryRunner.addColumn( + "protocol", + new TableColumn({ + name: "endtime", + type: "timestamp", + isNullable: false, + }) + ); + await queryRunner.addColumn( + "protocol", + new TableColumn({ + name: "summary", + type: "text", + isNullable: false, + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumn("protocol", "summary"); + await queryRunner.dropColumn("protocol", "endtime"); + await queryRunner.dropColumn("protocol", "starttime"); + } +} diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts index e737ea5..e353582 100644 --- a/src/routes/admin/protocol.ts +++ b/src/routes/admin/protocol.ts @@ -1,5 +1,5 @@ import express, { Request, Response } from "express"; -import { getAllProtocols } from "../../controller/admin/protocolController"; +import { getAllProtocols, getProtocolById } from "../../controller/admin/protocolController"; var router = express.Router({ mergeParams: true }); @@ -7,4 +7,8 @@ router.get("/", async (req: Request, res: Response) => { await getAllProtocols(req, res); }); +router.get("/:id", async (req: Request, res: Response) => { + await getProtocolById(req, res); +}); + export default router; diff --git a/src/viewmodel/admin/protocol.models.ts b/src/viewmodel/admin/protocol.models.ts index 051b832..c02e60c 100644 --- a/src/viewmodel/admin/protocol.models.ts +++ b/src/viewmodel/admin/protocol.models.ts @@ -2,4 +2,7 @@ export interface ProtocolViewModel { id: number; title: string; date: Date; + starttime: Date; + endtime: Date; + summary: string; } From dd74005043063f022181d2a5701d1f284e845db8 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Thu, 10 Oct 2024 14:44:41 +0200 Subject: [PATCH 03/22] protocol tables --- src/data-source.ts | 10 ++ src/entity/protocolAgenda.ts | 21 +++ src/entity/protocolDecisions.ts | 21 +++ src/entity/protocolPresence.ts | 26 +++ src/entity/protocolVotings.ts | 30 ++++ .../1728563204766-protocolTables.ts | 154 ++++++++++++++++++ 6 files changed, 262 insertions(+) create mode 100644 src/entity/protocolAgenda.ts create mode 100644 src/entity/protocolDecisions.ts create mode 100644 src/entity/protocolPresence.ts create mode 100644 src/entity/protocolVotings.ts create mode 100644 src/migrations/1728563204766-protocolTables.ts diff --git a/src/data-source.ts b/src/data-source.ts index e1de44b..f3f08a0 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -32,6 +32,11 @@ import { CommunicationFields1727439800630 } from "./migrations/1727439800630-com import { protocol } from "./entity/protocol"; import { ProtocolInit1727953803404 } from "./migrations/1727953803404-protocol-init"; import { ProtocolBase1728037129072 } from "./migrations/1728037129072-protocolBase"; +import { protocolAgenda } from "./entity/protocolAgenda"; +import { protocolDecisions } from "./entity/protocolDecisions"; +import { protocolPresence } from "./entity/protocolPresence"; +import { protocolVotings } from "./entity/protocolVotings"; +import { ProtocolTables1728563204766 } from "./migrations/1728563204766-protocolTables"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -62,6 +67,10 @@ const dataSource = new DataSource({ memberQualifications, membership, protocol, + protocolAgenda, + protocolDecisions, + protocolPresence, + protocolVotings, ], migrations: [ Initial1724317398939, @@ -74,6 +83,7 @@ const dataSource = new DataSource({ CommunicationFields1727439800630, ProtocolInit1727953803404, ProtocolBase1728037129072, + ProtocolTables1728563204766, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/protocolAgenda.ts b/src/entity/protocolAgenda.ts new file mode 100644 index 0000000..c3396c7 --- /dev/null +++ b/src/entity/protocolAgenda.ts @@ -0,0 +1,21 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { protocol } from "./protocol"; + +@Entity() +export class protocolAgenda { + @PrimaryGeneratedColumn("increment") + id: string; + + @Column({ type: "varchar", length: 255 }) + topic: string; + + @Column({ type: "varchar", length: 255, default: "" }) + context: string; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/entity/protocolDecisions.ts b/src/entity/protocolDecisions.ts new file mode 100644 index 0000000..fed4e2e --- /dev/null +++ b/src/entity/protocolDecisions.ts @@ -0,0 +1,21 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { protocol } from "./protocol"; + +@Entity() +export class protocolDecisions { + @PrimaryGeneratedColumn("increment") + id: string; + + @Column({ type: "varchar", length: 255 }) + topic: string; + + @Column({ type: "varchar", length: 255, default: "" }) + context: string; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/entity/protocolPresence.ts b/src/entity/protocolPresence.ts new file mode 100644 index 0000000..975ee0c --- /dev/null +++ b/src/entity/protocolPresence.ts @@ -0,0 +1,26 @@ +import { Column, Entity, ManyToOne, PrimaryColumn } from "typeorm"; +import { protocol } from "./protocol"; +import { member } from "./member"; + +@Entity() +export class protocolPresence { + @PrimaryColumn() + memberId: string; + + @PrimaryColumn() + protocolId: string; + + @ManyToOne(() => member, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + member: member; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/entity/protocolVotings.ts b/src/entity/protocolVotings.ts new file mode 100644 index 0000000..b2a03c7 --- /dev/null +++ b/src/entity/protocolVotings.ts @@ -0,0 +1,30 @@ +import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { protocol } from "./protocol"; + +@Entity() +export class protocolVotings { + @PrimaryGeneratedColumn("increment") + id: string; + + @Column({ type: "varchar", length: 255 }) + topic: string; + + @Column({ type: "varchar", length: 255, default: "" }) + context: string; + + @Column({ type: "int", default: 0 }) + favour: number; + + @Column({ type: "int", default: 0 }) + abstain: number; + + @Column({ type: "int", default: 0 }) + against: number; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/migrations/1728563204766-protocolTables.ts b/src/migrations/1728563204766-protocolTables.ts new file mode 100644 index 0000000..92b6d19 --- /dev/null +++ b/src/migrations/1728563204766-protocolTables.ts @@ -0,0 +1,154 @@ +import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm"; +import { DB_TYPE } from "../env.defaults"; + +export class ProtocolTables1728563204766 implements MigrationInterface { + name = "ProtocolTables1728563204766"; + + public async up(queryRunner: QueryRunner): Promise { + const variableType_int = DB_TYPE == "mysql" ? "int" : "integer"; + + await queryRunner.createTable( + new Table({ + name: "protocol_agenda", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "topic", type: "varchar", length: "255", isNullable: false }, + { name: "context", type: "varchar", length: "255", default: "''", isNullable: false }, + { name: "protocolId", type: variableType_int, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "protocol_decisions", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "topic", type: "varchar", length: "255", isNullable: false }, + { name: "context", type: "varchar", length: "255", default: "''", isNullable: false }, + { name: "protocolId", type: variableType_int, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "protocol_presence", + columns: [ + { name: "memberId", type: variableType_int, isPrimary: true, isNullable: false }, + { name: "protocolId", type: variableType_int, isPrimary: true, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createTable( + new Table({ + name: "protocol_votings", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "topic", type: "varchar", length: "255", isNullable: false }, + { name: "context", type: "varchar", length: "255", default: "''", isNullable: false }, + { name: "favour", type: variableType_int, default: 0, isNullable: false }, + { name: "abstain", type: variableType_int, default: 0, isNullable: false }, + { name: "against", type: variableType_int, default: 0, isNullable: false }, + { name: "protocolId", type: variableType_int, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createForeignKey( + "protocol_agenda", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + + await queryRunner.createForeignKey( + "protocol_decisions", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + + await queryRunner.createForeignKey( + "protocol_votings", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + + await queryRunner.createForeignKey( + "protocol_presence", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + await queryRunner.createForeignKey( + "protocol_presence", + new TableForeignKey({ + columnNames: ["memberId"], + referencedColumnNames: ["id"], + referencedTableName: "member", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + const tableProtocolVotings = await queryRunner.getTable("protocol_votings"); + const foreignKeyProtocolVotings = tableProtocolVotings.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_votings", foreignKeyProtocolVotings); + + const tableProtocolDecisions = await queryRunner.getTable("protocol_decisions"); + const foreignKeyProtocolDecisions = tableProtocolDecisions.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_decisions", foreignKeyProtocolDecisions); + + const tableProtocolAgenda = await queryRunner.getTable("protocol_agenda"); + const foreignKeyProtocolAgenda = tableProtocolAgenda.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_agenda", foreignKeyProtocolAgenda); + + const tableProtocolPresence_protcol = await queryRunner.getTable("protocol_presence"); + const foreignKeyProtocolPresence_protcol = tableProtocolPresence_protcol.foreignKeys.find( + (fk) => fk.columnNames.indexOf("protocolId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_presence", foreignKeyProtocolPresence_protcol); + + const tableProtocolPresence_member = await queryRunner.getTable("protocol_presence"); + const foreignKeyProtocolPresence_member = tableProtocolPresence_member.foreignKeys.find( + (fk) => fk.columnNames.indexOf("memberId") !== -1 + ); + await queryRunner.dropForeignKey("protocol_presence", foreignKeyProtocolPresence_member); + + await queryRunner.dropTable("protocol_votings"); + await queryRunner.dropTable("protocol_presence"); + await queryRunner.dropTable("protocol_decisions"); + await queryRunner.dropTable("protocol_agenda"); + } +} From 475a13ce36915cc2a36bc917f817bfbe048537dd Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Fri, 11 Oct 2024 14:44:09 +0200 Subject: [PATCH 04/22] protcol table services --- src/controller/admin/protocolController.ts | 124 ++++++++++++++++++ src/data-source.ts | 10 +- src/entity/protocolAgenda.ts | 2 +- ...otocolDecisions.ts => protocolDecision.ts} | 4 +- .../{protocolVotings.ts => protocolVoting.ts} | 4 +- src/factory/admin/protocolAgenda.ts | 27 ++++ src/factory/admin/protocolDecision.ts | 27 ++++ src/factory/admin/protocolPresence.ts | 27 ++++ src/factory/admin/protocolVoting.ts | 30 +++++ .../1728645611919-protocolTableRename.ts | 15 +++ src/routes/admin/protocol.ts | 50 ++++++- src/service/protocolAgendaService.ts | 41 ++++++ src/service/protocolDecisionService.ts | 41 ++++++ src/service/protocolPrecenseService.ts | 41 ++++++ src/service/protocolVotingService.ts | 41 ++++++ src/viewmodel/admin/protocolAgenda.models.ts | 6 + .../admin/protocolDecision.models.ts | 6 + .../admin/protocolPresence.models.ts | 7 + src/viewmodel/admin/protocolVoting.models.ts | 9 ++ 19 files changed, 502 insertions(+), 10 deletions(-) rename src/entity/{protocolDecisions.ts => protocolDecision.ts} (90%) rename src/entity/{protocolVotings.ts => protocolVoting.ts} (93%) create mode 100644 src/factory/admin/protocolAgenda.ts create mode 100644 src/factory/admin/protocolDecision.ts create mode 100644 src/factory/admin/protocolPresence.ts create mode 100644 src/factory/admin/protocolVoting.ts create mode 100644 src/migrations/1728645611919-protocolTableRename.ts create mode 100644 src/service/protocolAgendaService.ts create mode 100644 src/service/protocolDecisionService.ts create mode 100644 src/service/protocolPrecenseService.ts create mode 100644 src/service/protocolVotingService.ts create mode 100644 src/viewmodel/admin/protocolAgenda.models.ts create mode 100644 src/viewmodel/admin/protocolDecision.models.ts create mode 100644 src/viewmodel/admin/protocolPresence.models.ts create mode 100644 src/viewmodel/admin/protocolVoting.models.ts diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index b94ac1f..53444a5 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -1,6 +1,14 @@ import { Request, Response } from "express"; import ProtocolService from "../../service/protocolService"; import ProtocolFactory from "../../factory/admin/protocol"; +import ProtocolAgendaService from "../../service/protocolAgendaService"; +import ProtocolAgendaFactory from "../../factory/admin/protocolAgenda"; +import ProtocolDecisionService from "../../service/protocolDecisionService"; +import ProtocolDecisionFactory from "../../factory/admin/protocolDecision"; +import ProtocolPresenceService from "../../service/protocolPrecenseService"; +import ProtocolPresenceFactory from "../../factory/admin/protocolPresence"; +import ProtocolVotingService from "../../service/protocolVotingService"; +import ProtocolVotingFactory from "../../factory/admin/protocolVoting"; /** * @description get all protocols @@ -33,3 +41,119 @@ export async function getProtocolById(req: Request, res: Response): Promise res.json(ProtocolFactory.mapToSingle(protocol)); } + +/** + * @description get protocol agenda by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolAgendaById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let agenda = await ProtocolAgendaService.getAll(protocolId); + + res.json(ProtocolAgendaFactory.mapToBase(agenda)); +} + +/** + * @description get protocol decisions by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolDecisonsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let decisions = await ProtocolDecisionService.getAll(protocolId); + + res.json(ProtocolDecisionFactory.mapToBase(decisions)); +} + +/** + * @description get protocol precense by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolPrecenseById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let presence = await ProtocolPresenceService.getAll(protocolId); + + res.json(ProtocolPresenceFactory.mapToBase(presence)); +} + +/** + * @description get protocol votings by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolVotingsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let votings = await ProtocolVotingService.getAll(protocolId); + + res.json(ProtocolVotingFactory.mapToBase(votings)); +} + +/** + * @description synchronize protocol by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolById(req: Request, res: Response): Promise { + let id = parseInt(req.params.id); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol agenda by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolAgendaById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol decisions by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolDecisonsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol precense by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolPrecenseById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol votings by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolVotingsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + res.sendStatus(204); +} diff --git a/src/data-source.ts b/src/data-source.ts index f3f08a0..f08af33 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -33,10 +33,11 @@ import { protocol } from "./entity/protocol"; import { ProtocolInit1727953803404 } from "./migrations/1727953803404-protocol-init"; import { ProtocolBase1728037129072 } from "./migrations/1728037129072-protocolBase"; import { protocolAgenda } from "./entity/protocolAgenda"; -import { protocolDecisions } from "./entity/protocolDecisions"; +import { protocolDecision } from "./entity/protocolDecision"; import { protocolPresence } from "./entity/protocolPresence"; -import { protocolVotings } from "./entity/protocolVotings"; +import { protocolVoting } from "./entity/protocolVoting"; import { ProtocolTables1728563204766 } from "./migrations/1728563204766-protocolTables"; +import { ProtocolTableRename1728645611919 } from "./migrations/1728645611919-protocolTableRename"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -68,9 +69,9 @@ const dataSource = new DataSource({ membership, protocol, protocolAgenda, - protocolDecisions, + protocolDecision, protocolPresence, - protocolVotings, + protocolVoting, ], migrations: [ Initial1724317398939, @@ -84,6 +85,7 @@ const dataSource = new DataSource({ ProtocolInit1727953803404, ProtocolBase1728037129072, ProtocolTables1728563204766, + ProtocolTableRename1728645611919, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/protocolAgenda.ts b/src/entity/protocolAgenda.ts index c3396c7..dcfbde4 100644 --- a/src/entity/protocolAgenda.ts +++ b/src/entity/protocolAgenda.ts @@ -4,7 +4,7 @@ import { protocol } from "./protocol"; @Entity() export class protocolAgenda { @PrimaryGeneratedColumn("increment") - id: string; + id: number; @Column({ type: "varchar", length: 255 }) topic: string; diff --git a/src/entity/protocolDecisions.ts b/src/entity/protocolDecision.ts similarity index 90% rename from src/entity/protocolDecisions.ts rename to src/entity/protocolDecision.ts index fed4e2e..b40d3aa 100644 --- a/src/entity/protocolDecisions.ts +++ b/src/entity/protocolDecision.ts @@ -2,9 +2,9 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; import { protocol } from "./protocol"; @Entity() -export class protocolDecisions { +export class protocolDecision { @PrimaryGeneratedColumn("increment") - id: string; + id: number; @Column({ type: "varchar", length: 255 }) topic: string; diff --git a/src/entity/protocolVotings.ts b/src/entity/protocolVoting.ts similarity index 93% rename from src/entity/protocolVotings.ts rename to src/entity/protocolVoting.ts index b2a03c7..4f3b461 100644 --- a/src/entity/protocolVotings.ts +++ b/src/entity/protocolVoting.ts @@ -2,9 +2,9 @@ import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; import { protocol } from "./protocol"; @Entity() -export class protocolVotings { +export class protocolVoting { @PrimaryGeneratedColumn("increment") - id: string; + id: number; @Column({ type: "varchar", length: 255 }) topic: string; diff --git a/src/factory/admin/protocolAgenda.ts b/src/factory/admin/protocolAgenda.ts new file mode 100644 index 0000000..d48c0ef --- /dev/null +++ b/src/factory/admin/protocolAgenda.ts @@ -0,0 +1,27 @@ +import { protocolAgenda } from "../../entity/protocolAgenda"; +import { ProtocolAgendaViewModel } from "../../viewmodel/admin/protocolAgenda.models"; + +export default abstract class ProtocolAgendaFactory { + /** + * @description map record to protocolAgenda + * @param {protocol} record + * @returns {ProtocolAgendaViewModel} + */ + public static mapToSingle(record: protocolAgenda): ProtocolAgendaViewModel { + return { + id: record.id, + topic: record.topic, + context: record.context, + protocolId: record.protocol.id, + }; + } + + /** + * @description map records to protocolAgenda + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/protocolDecision.ts b/src/factory/admin/protocolDecision.ts new file mode 100644 index 0000000..a23f524 --- /dev/null +++ b/src/factory/admin/protocolDecision.ts @@ -0,0 +1,27 @@ +import { protocolDecision } from "../../entity/protocolDecision"; +import { ProtocolDecisionViewModel } from "../../viewmodel/admin/protocolDecision.models"; + +export default abstract class ProtocolDecisionFactory { + /** + * @description map record to protocolDecision + * @param {protocol} record + * @returns {ProtocolDecisionViewModel} + */ + public static mapToSingle(record: protocolDecision): ProtocolDecisionViewModel { + return { + id: record.id, + topic: record.topic, + context: record.context, + protocolId: record.protocol.id, + }; + } + + /** + * @description map records to protocolDecision + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/protocolPresence.ts b/src/factory/admin/protocolPresence.ts new file mode 100644 index 0000000..3772be8 --- /dev/null +++ b/src/factory/admin/protocolPresence.ts @@ -0,0 +1,27 @@ +import { protocolPresence } from "../../entity/protocolPresence"; +import { ProtocolPresenceViewModel } from "../../viewmodel/admin/protocolPresence.models"; +import MemberFactory from "./member"; + +export default abstract class ProtocolPresenceFactory { + /** + * @description map record to protocolPresence + * @param {protocol} record + * @returns {ProtocolPresenceViewModel} + */ + public static mapToSingle(record: protocolPresence): ProtocolPresenceViewModel { + return { + memberId: record.member.id, + member: MemberFactory.mapToSingle(record.member), + protocolId: record.protocol.id, + }; + } + + /** + * @description map records to protocolPresence + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/factory/admin/protocolVoting.ts b/src/factory/admin/protocolVoting.ts new file mode 100644 index 0000000..076fe59 --- /dev/null +++ b/src/factory/admin/protocolVoting.ts @@ -0,0 +1,30 @@ +import { protocolVoting } from "../../entity/protocolVoting"; +import { ProtocolVotingViewModel } from "../../viewmodel/admin/protocolVoting.models"; + +export default abstract class ProtocolVotingFactory { + /** + * @description map record to protocolVoting + * @param {protocol} record + * @returns {ProtocolVotingViewModel} + */ + public static mapToSingle(record: protocolVoting): ProtocolVotingViewModel { + return { + id: record.id, + topic: record.topic, + context: record.context, + favour: record.favour, + abstain: record.abstain, + against: record.against, + protocolId: record.protocol.id, + }; + } + + /** + * @description map records to protocolVoting + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/migrations/1728645611919-protocolTableRename.ts b/src/migrations/1728645611919-protocolTableRename.ts new file mode 100644 index 0000000..4a79c51 --- /dev/null +++ b/src/migrations/1728645611919-protocolTableRename.ts @@ -0,0 +1,15 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class ProtocolTableRename1728645611919 implements MigrationInterface { + name = "ProtocolTableRename1728645611919"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.renameTable("protocol_decisions", "protocol_decision"); + await queryRunner.renameTable("protocol_votings", "protocol_voting"); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.renameTable("protocol_decision", "protocol_decisions"); + await queryRunner.renameTable("protocol_voting", "protocol_votings"); + } +} diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts index e353582..1870d50 100644 --- a/src/routes/admin/protocol.ts +++ b/src/routes/admin/protocol.ts @@ -1,5 +1,17 @@ import express, { Request, Response } from "express"; -import { getAllProtocols, getProtocolById } from "../../controller/admin/protocolController"; +import { + getAllProtocols, + getProtocolAgendaById, + getProtocolById, + getProtocolDecisonsById, + getProtocolPrecenseById, + getProtocolVotingsById, + synchronizeProtocolAgendaById, + synchronizeProtocolById, + synchronizeProtocolDecisonsById, + synchronizeProtocolPrecenseById, + synchronizeProtocolVotingsById, +} from "../../controller/admin/protocolController"; var router = express.Router({ mergeParams: true }); @@ -11,4 +23,40 @@ router.get("/:id", async (req: Request, res: Response) => { await getProtocolById(req, res); }); +router.get("/:protocolId/agenda", async (req: Request, res: Response) => { + await getProtocolAgendaById(req, res); +}); + +router.get("/:protocolId/decisions", async (req: Request, res: Response) => { + await getProtocolDecisonsById(req, res); +}); + +router.get("/:protocolId/presence", async (req: Request, res: Response) => { + await getProtocolPrecenseById(req, res); +}); + +router.get("/:protocolId/votings", async (req: Request, res: Response) => { + await getProtocolVotingsById(req, res); +}); + +router.get("/:id/synchronize", async (req: Request, res: Response) => { + await synchronizeProtocolById(req, res); +}); + +router.get("/:protocolId/synchronize/agenda", async (req: Request, res: Response) => { + await synchronizeProtocolAgendaById(req, res); +}); + +router.get("/:protocolId/synchronize/decisions", async (req: Request, res: Response) => { + await synchronizeProtocolDecisonsById(req, res); +}); + +router.get("/:protocolId/synchronize/presence", async (req: Request, res: Response) => { + await synchronizeProtocolPrecenseById(req, res); +}); + +router.get("/:protocolId/synchronize/votings", async (req: Request, res: Response) => { + await synchronizeProtocolVotingsById(req, res); +}); + export default router; diff --git a/src/service/protocolAgendaService.ts b/src/service/protocolAgendaService.ts new file mode 100644 index 0000000..4409d05 --- /dev/null +++ b/src/service/protocolAgendaService.ts @@ -0,0 +1,41 @@ +import { dataSource } from "../data-source"; +import { protocolAgenda } from "../entity/protocolAgenda"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolAgendaService { + /** + * @description get all protocolAgendas + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolAgenda) + .createQueryBuilder("protocolAgenda") + .where("protocolAgenda.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolAgendas not found", err); + }); + } + + /** + * @description get protocolAgenda by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocolAgenda) + .createQueryBuilder("protocolAgenda") + .where("protocolAgenda.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolAgenda not found by id", err); + }); + } +} diff --git a/src/service/protocolDecisionService.ts b/src/service/protocolDecisionService.ts new file mode 100644 index 0000000..b7a9051 --- /dev/null +++ b/src/service/protocolDecisionService.ts @@ -0,0 +1,41 @@ +import { dataSource } from "../data-source"; +import { protocolDecision } from "../entity/protocolDecision"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolDecisionService { + /** + * @description get all protocolDecisionss + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolDecision) + .createQueryBuilder("protocolDecisions") + .where("protocolAgenda.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolDecisions not found", err); + }); + } + + /** + * @description get protocolDecision by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocolDecision) + .createQueryBuilder("protocolDecisions") + .where("protocolDecisions.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolDecision not found by id", err); + }); + } +} diff --git a/src/service/protocolPrecenseService.ts b/src/service/protocolPrecenseService.ts new file mode 100644 index 0000000..bc32993 --- /dev/null +++ b/src/service/protocolPrecenseService.ts @@ -0,0 +1,41 @@ +import { dataSource } from "../data-source"; +import { protocolPresence } from "../entity/protocolPresence"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolPresenceService { + /** + * @description get all protocolPresences + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolPresence) + .createQueryBuilder("protocolPresence") + .where("protocolAgenda.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolPresence not found", err); + }); + } + + /** + * @description get protocolDecision by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocolPresence) + .createQueryBuilder("protocolPresence") + .where("protocolPresence.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolDecision not found by id", err); + }); + } +} diff --git a/src/service/protocolVotingService.ts b/src/service/protocolVotingService.ts new file mode 100644 index 0000000..4f806d9 --- /dev/null +++ b/src/service/protocolVotingService.ts @@ -0,0 +1,41 @@ +import { dataSource } from "../data-source"; +import { protocolVoting } from "../entity/protocolVoting"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolVotingService { + /** + * @description get all protocolVotingss + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolVoting) + .createQueryBuilder("protocolVotings") + .where("protocolAgenda.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolVotings not found", err); + }); + } + + /** + * @description get protocolVoting by id + * @returns {Promise} + */ + static async getById(id: number): Promise { + return await dataSource + .getRepository(protocolVoting) + .createQueryBuilder("protocolVotings") + .where("protocolVotings.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolVoting not found by id", err); + }); + } +} diff --git a/src/viewmodel/admin/protocolAgenda.models.ts b/src/viewmodel/admin/protocolAgenda.models.ts new file mode 100644 index 0000000..3a59327 --- /dev/null +++ b/src/viewmodel/admin/protocolAgenda.models.ts @@ -0,0 +1,6 @@ +export interface ProtocolAgendaViewModel { + id: number; + topic: string; + context: string; + protocolId: number; +} diff --git a/src/viewmodel/admin/protocolDecision.models.ts b/src/viewmodel/admin/protocolDecision.models.ts new file mode 100644 index 0000000..4a7212c --- /dev/null +++ b/src/viewmodel/admin/protocolDecision.models.ts @@ -0,0 +1,6 @@ +export interface ProtocolDecisionViewModel { + id: number; + topic: string; + context: string; + protocolId: number; +} diff --git a/src/viewmodel/admin/protocolPresence.models.ts b/src/viewmodel/admin/protocolPresence.models.ts new file mode 100644 index 0000000..9c10f59 --- /dev/null +++ b/src/viewmodel/admin/protocolPresence.models.ts @@ -0,0 +1,7 @@ +import { MemberViewModel } from "./member.models"; + +export interface ProtocolPresenceViewModel { + memberId: number; + member: MemberViewModel; + protocolId: number; +} diff --git a/src/viewmodel/admin/protocolVoting.models.ts b/src/viewmodel/admin/protocolVoting.models.ts new file mode 100644 index 0000000..686f423 --- /dev/null +++ b/src/viewmodel/admin/protocolVoting.models.ts @@ -0,0 +1,9 @@ +export interface ProtocolVotingViewModel { + id: number; + topic: string; + context: string; + favour: number; + abstain: number; + against: number; + protocolId: number; +} From b9b258a1f6d6724f1051957096c89ff4eaad5277 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Sun, 13 Oct 2024 15:48:01 +0200 Subject: [PATCH 05/22] protcol data commands --- src/command/protocolAgendaCommand.ts | 6 ++ src/command/protocolAgendaCommandHandler.ts | 25 ++++++ src/command/protocolCommand.ts | 13 +++ src/command/protocolCommandHandler.ts | 53 +++++++++++ src/command/protocolDecisionCommand.ts | 6 ++ src/command/protocolDecisionCommandHandler.ts | 25 ++++++ src/command/protocolPresenceCommand.ts | 4 + src/command/protocolPresenceCommandHandler.ts | 65 ++++++++++++++ src/command/protocolVotingCommand.ts | 9 ++ src/command/protocolVotingCommandHandler.ts | 25 ++++++ src/controller/admin/protocolController.ts | 89 +++++++++++++++++++ src/entity/protocolPresence.ts | 4 +- src/routes/admin/protocol.ts | 15 ++-- src/service/protocolDecisionService.ts | 2 +- src/service/protocolPrecenseService.ts | 2 +- src/service/protocolVotingService.ts | 2 +- 16 files changed, 335 insertions(+), 10 deletions(-) create mode 100644 src/command/protocolAgendaCommand.ts create mode 100644 src/command/protocolAgendaCommandHandler.ts create mode 100644 src/command/protocolCommand.ts create mode 100644 src/command/protocolCommandHandler.ts create mode 100644 src/command/protocolDecisionCommand.ts create mode 100644 src/command/protocolDecisionCommandHandler.ts create mode 100644 src/command/protocolPresenceCommand.ts create mode 100644 src/command/protocolPresenceCommandHandler.ts create mode 100644 src/command/protocolVotingCommand.ts create mode 100644 src/command/protocolVotingCommandHandler.ts diff --git a/src/command/protocolAgendaCommand.ts b/src/command/protocolAgendaCommand.ts new file mode 100644 index 0000000..749390d --- /dev/null +++ b/src/command/protocolAgendaCommand.ts @@ -0,0 +1,6 @@ +export interface SynchronizeProtocolAgendaCommand { + id?: number; + topic: string; + context: string; + protocolId: number; +} diff --git a/src/command/protocolAgendaCommandHandler.ts b/src/command/protocolAgendaCommandHandler.ts new file mode 100644 index 0000000..d132558 --- /dev/null +++ b/src/command/protocolAgendaCommandHandler.ts @@ -0,0 +1,25 @@ +import { dataSource } from "../data-source"; +import { protocolAgenda } from "../entity/protocolAgenda"; +import InternalException from "../exceptions/internalException"; +import { SynchronizeProtocolAgendaCommand } from "./protocolAgendaCommand"; + +export default abstract class ProtocolAgendaCommandHandler { + /** + * @description sync protocolAgenda + * @param {Array} + * @returns {Promise} + */ + static async sync(syncProtocolAgenda: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolAgenda) + .values(syncProtocolAgenda) + .orUpdate(["topic", "context"], ["id"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/command/protocolCommand.ts b/src/command/protocolCommand.ts new file mode 100644 index 0000000..42e9ce9 --- /dev/null +++ b/src/command/protocolCommand.ts @@ -0,0 +1,13 @@ +export interface CreateProtocolCommand { + title: string; + date: Date; +} + +export interface SynchronizeProtocolCommand { + id: number; + title: string; + date: Date; + starttime: Date; + endtime: Date; + summary: string; +} diff --git a/src/command/protocolCommandHandler.ts b/src/command/protocolCommandHandler.ts new file mode 100644 index 0000000..f2f293e --- /dev/null +++ b/src/command/protocolCommandHandler.ts @@ -0,0 +1,53 @@ +import { dataSource } from "../data-source"; +import { protocol } from "../entity/protocol"; +import InternalException from "../exceptions/internalException"; +import { CreateProtocolCommand, SynchronizeProtocolCommand } from "./protocolCommand"; + +export default abstract class ProtocolCommandHandler { + /** + * @description create protocol + * @param CreateProtocolCommand + * @returns {Promise} + */ + static async create(createProtocol: CreateProtocolCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocol) + .values({ + title: createProtocol.title, + date: createProtocol.date, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } + + /** + * @description sync protocol + * @param SynchronizeProtocolCommand + * @returns {Promise} + */ + static async sync(syncProtocol: SynchronizeProtocolCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(protocol) + .set({ + title: syncProtocol.title, + date: syncProtocol.date, + starttime: syncProtocol.starttime, + endtime: syncProtocol.endtime, + summary: syncProtocol.summary, + }) + .where("id = :id", { id: syncProtocol.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/command/protocolDecisionCommand.ts b/src/command/protocolDecisionCommand.ts new file mode 100644 index 0000000..61eb6f0 --- /dev/null +++ b/src/command/protocolDecisionCommand.ts @@ -0,0 +1,6 @@ +export interface SynchronizeProtocolDecisionCommand { + id?: number; + topic: string; + context: string; + protocolId: number; +} diff --git a/src/command/protocolDecisionCommandHandler.ts b/src/command/protocolDecisionCommandHandler.ts new file mode 100644 index 0000000..3925884 --- /dev/null +++ b/src/command/protocolDecisionCommandHandler.ts @@ -0,0 +1,25 @@ +import { dataSource } from "../data-source"; +import { protocolDecision } from "../entity/protocolDecision"; +import InternalException from "../exceptions/internalException"; +import { SynchronizeProtocolDecisionCommand } from "./protocolDecisionCommand"; + +export default abstract class ProtocolDecisionCommandHandler { + /** + * @description sync protocolDecision + * @param {Array} + * @returns {Promise} + */ + static async sync(syncProtocolDecisions: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolDecision) + .values(syncProtocolDecisions) + .orUpdate(["topic", "context"], ["id"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/command/protocolPresenceCommand.ts b/src/command/protocolPresenceCommand.ts new file mode 100644 index 0000000..b39878b --- /dev/null +++ b/src/command/protocolPresenceCommand.ts @@ -0,0 +1,4 @@ +export interface SynchronizeProtocolPresenceCommand { + memberIds: Array; + protocolId: number; +} diff --git a/src/command/protocolPresenceCommandHandler.ts b/src/command/protocolPresenceCommandHandler.ts new file mode 100644 index 0000000..7d7a5f7 --- /dev/null +++ b/src/command/protocolPresenceCommandHandler.ts @@ -0,0 +1,65 @@ +import { DeleteResult, EntityManager, InsertResult } from "typeorm"; +import { dataSource } from "../data-source"; +import { protocolPresence } from "../entity/protocolPresence"; +import InternalException from "../exceptions/internalException"; +import ProtocolPresenceService from "../service/protocolPrecenseService"; +import { SynchronizeProtocolPresenceCommand } from "./protocolPresenceCommand"; + +export default abstract class ProtocolPresenceCommandHandler { + /** + * @description sync protocolPresence + * @param {SynchronizeProtocolPresenceCommand} + * @returns {Promise} + */ + static async sync(syncProtocolPresences: SynchronizeProtocolPresenceCommand): Promise { + let currentPresence = (await ProtocolPresenceService.getAll(syncProtocolPresences.protocolId)).map( + (r) => r.memberId + ); + + return await dataSource.manager + .transaction(async (manager) => { + let newMembers = syncProtocolPresences.memberIds.filter((r) => !currentPresence.includes(r)); + let removeMembers = currentPresence.filter((r) => !syncProtocolPresences.memberIds.includes(r)); + + await this.syncPresenceAdd(manager, syncProtocolPresences.protocolId, newMembers); + + await this.syncPresenceRemove(manager, syncProtocolPresences.protocolId, removeMembers); + }) + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed saving user roles", err); + }); + } + + private static async syncPresenceAdd( + manager: EntityManager, + protocolId: number, + memberIds: Array + ): Promise { + return await manager + .createQueryBuilder() + .insert() + .into(protocolPresence) + .values( + memberIds.map((m) => ({ + protocolId, + memberIds: m, + })) + ) + .execute(); + } + + private static async syncPresenceRemove( + manager: EntityManager, + protocolId: number, + memberIds: Array + ): Promise { + return await manager + .createQueryBuilder() + .delete() + .from(protocolPresence) + .where("memberId IN (:...ids)", { ids: memberIds }) + .andWhere("protcolId = :protocolId", { protocolId }) + .execute(); + } +} diff --git a/src/command/protocolVotingCommand.ts b/src/command/protocolVotingCommand.ts new file mode 100644 index 0000000..a707b64 --- /dev/null +++ b/src/command/protocolVotingCommand.ts @@ -0,0 +1,9 @@ +export interface SynchronizeProtocolVotingCommand { + id: number; + topic: string; + context: string; + favour: number; + abstain: number; + against: number; + protocolId: number; +} diff --git a/src/command/protocolVotingCommandHandler.ts b/src/command/protocolVotingCommandHandler.ts new file mode 100644 index 0000000..6ade2f1 --- /dev/null +++ b/src/command/protocolVotingCommandHandler.ts @@ -0,0 +1,25 @@ +import { dataSource } from "../data-source"; +import { protocolVoting } from "../entity/protocolVoting"; +import InternalException from "../exceptions/internalException"; +import { SynchronizeProtocolVotingCommand } from "./protocolVotingCommand"; + +export default abstract class ProtocolVotingCommandHandler { + /** + * @description sync protocolVoting + * @param {Array} + * @returns {Promise} + */ + static async sync(syncProtocolVotings: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolVoting) + .values(syncProtocolVotings) + .orUpdate(["topic", "context", "favour", "abstain", "against"], ["id"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index 53444a5..51bf599 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -9,6 +9,18 @@ import ProtocolPresenceService from "../../service/protocolPrecenseService"; import ProtocolPresenceFactory from "../../factory/admin/protocolPresence"; import ProtocolVotingService from "../../service/protocolVotingService"; import ProtocolVotingFactory from "../../factory/admin/protocolVoting"; +import { CreateProtocolCommand, SynchronizeProtocolCommand } from "../../command/protocolCommand"; +import ProtocolCommandHandler from "../../command/protocolCommandHandler"; +import { SynchronizeProtocolAgendaCommand } from "../../command/protocolAgendaCommand"; +import ProtocolAgendaCommandHandler from "../../command/protocolAgendaCommandHandler"; +import { ProtocolAgendaViewModel } from "../../viewmodel/admin/protocolAgenda.models"; +import ProtocolDecisionCommandHandler from "../../command/protocolDecisionCommandHandler"; +import { ProtocolDecisionViewModel } from "../../viewmodel/admin/protocolDecision.models"; +import ProtocolPresenceCommandHandler from "../../command/protocolPresenceCommandHandler"; +import { SynchronizeProtocolPresenceCommand } from "../../command/protocolPresenceCommand"; +import { SynchronizeProtocolDecisionCommand } from "../../command/protocolDecisionCommand"; +import { SynchronizeProtocolVotingCommand } from "../../command/protocolVotingCommand"; +import { ProtocolVotingViewModel } from "../../viewmodel/admin/protocolVoting.models"; /** * @description get all protocols @@ -98,6 +110,25 @@ export async function getProtocolVotingsById(req: Request, res: Response): Promi res.json(ProtocolVotingFactory.mapToBase(votings)); } +/** + * @description create protocol + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocol(req: Request, res: Response): Promise { + let title = req.body.title; + let date = req.body.date; + + let createProtocol: CreateProtocolCommand = { + title, + date, + }; + let id = await ProtocolCommandHandler.create(createProtocol); + + res.send(id); +} + /** * @description synchronize protocol by id * @param req {Request} Express req object @@ -106,6 +137,21 @@ export async function getProtocolVotingsById(req: Request, res: Response): Promi */ export async function synchronizeProtocolById(req: Request, res: Response): Promise { let id = parseInt(req.params.id); + let title = req.body.title; + let date = req.body.date; + let starttime = req.body.starttime; + let endtime = req.body.endtime; + let summary = req.body.summary; + + let syncProtocol: SynchronizeProtocolCommand = { + id, + title, + date, + starttime, + endtime, + summary, + }; + await ProtocolCommandHandler.sync(syncProtocol); res.sendStatus(204); } @@ -118,6 +164,17 @@ export async function synchronizeProtocolById(req: Request, res: Response): Prom */ export async function synchronizeProtocolAgendaById(req: Request, res: Response): Promise { let protocolId = parseInt(req.params.protocolId); + let agenda = req.body.agenda as Array; + + let syncAgenda: Array = agenda.map( + (a: ProtocolAgendaViewModel): SynchronizeProtocolAgendaCommand => ({ + id: a.id ?? null, + topic: a.topic, + context: a.context, + protocolId, + }) + ); + await ProtocolAgendaCommandHandler.sync(syncAgenda); res.sendStatus(204); } @@ -130,6 +187,17 @@ export async function synchronizeProtocolAgendaById(req: Request, res: Response) */ export async function synchronizeProtocolDecisonsById(req: Request, res: Response): Promise { let protocolId = parseInt(req.params.protocolId); + let decisions = req.body.decisions as Array; + + let syncDecision: Array = decisions.map( + (d: ProtocolDecisionViewModel): SynchronizeProtocolDecisionCommand => ({ + id: d.id ?? null, + topic: d.topic, + context: d.context, + protocolId, + }) + ); + await ProtocolDecisionCommandHandler.sync(syncDecision); res.sendStatus(204); } @@ -142,6 +210,13 @@ export async function synchronizeProtocolDecisonsById(req: Request, res: Respons */ export async function synchronizeProtocolPrecenseById(req: Request, res: Response): Promise { let protocolId = parseInt(req.params.protocolId); + let presence = req.body.precense as Array; + + let syncPresence: SynchronizeProtocolPresenceCommand = { + memberIds: presence, + protocolId, + }; + await ProtocolPresenceCommandHandler.sync(syncPresence); res.sendStatus(204); } @@ -154,6 +229,20 @@ export async function synchronizeProtocolPrecenseById(req: Request, res: Respons */ export async function synchronizeProtocolVotingsById(req: Request, res: Response): Promise { let protocolId = parseInt(req.params.protocolId); + let decisions = req.body.decisions as Array; + + let syncDecision: Array = decisions.map( + (d: ProtocolVotingViewModel): SynchronizeProtocolVotingCommand => ({ + id: d.id ?? null, + topic: d.topic, + context: d.context, + favour: d.favour, + abstain: d.abstain, + against: d.abstain, + protocolId, + }) + ); + await ProtocolDecisionCommandHandler.sync(syncDecision); res.sendStatus(204); } diff --git a/src/entity/protocolPresence.ts b/src/entity/protocolPresence.ts index 975ee0c..5d80b10 100644 --- a/src/entity/protocolPresence.ts +++ b/src/entity/protocolPresence.ts @@ -5,10 +5,10 @@ import { member } from "./member"; @Entity() export class protocolPresence { @PrimaryColumn() - memberId: string; + memberId: number; @PrimaryColumn() - protocolId: string; + protocolId: number; @ManyToOne(() => member, { nullable: false, diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts index 1870d50..5edbeee 100644 --- a/src/routes/admin/protocol.ts +++ b/src/routes/admin/protocol.ts @@ -1,5 +1,6 @@ import express, { Request, Response } from "express"; import { + createProtocol, getAllProtocols, getProtocolAgendaById, getProtocolById, @@ -39,23 +40,27 @@ router.get("/:protocolId/votings", async (req: Request, res: Response) => { await getProtocolVotingsById(req, res); }); -router.get("/:id/synchronize", async (req: Request, res: Response) => { +router.post("/", async (req: Request, res: Response) => { + await createProtocol(req, res); +}); + +router.put("/:id/synchronize", async (req: Request, res: Response) => { await synchronizeProtocolById(req, res); }); -router.get("/:protocolId/synchronize/agenda", async (req: Request, res: Response) => { +router.put("/:protocolId/synchronize/agenda", async (req: Request, res: Response) => { await synchronizeProtocolAgendaById(req, res); }); -router.get("/:protocolId/synchronize/decisions", async (req: Request, res: Response) => { +router.put("/:protocolId/synchronize/decisions", async (req: Request, res: Response) => { await synchronizeProtocolDecisonsById(req, res); }); -router.get("/:protocolId/synchronize/presence", async (req: Request, res: Response) => { +router.put("/:protocolId/synchronize/presence", async (req: Request, res: Response) => { await synchronizeProtocolPrecenseById(req, res); }); -router.get("/:protocolId/synchronize/votings", async (req: Request, res: Response) => { +router.put("/:protocolId/synchronize/votings", async (req: Request, res: Response) => { await synchronizeProtocolVotingsById(req, res); }); diff --git a/src/service/protocolDecisionService.ts b/src/service/protocolDecisionService.ts index b7a9051..b818313 100644 --- a/src/service/protocolDecisionService.ts +++ b/src/service/protocolDecisionService.ts @@ -11,7 +11,7 @@ export default abstract class ProtocolDecisionService { return await dataSource .getRepository(protocolDecision) .createQueryBuilder("protocolDecisions") - .where("protocolAgenda.protocolId = :protocolId", { protocolId }) + .where("protocolDecisions.protocolId = :protocolId", { protocolId }) .getMany() .then((res) => { return res; diff --git a/src/service/protocolPrecenseService.ts b/src/service/protocolPrecenseService.ts index bc32993..e66c946 100644 --- a/src/service/protocolPrecenseService.ts +++ b/src/service/protocolPrecenseService.ts @@ -11,7 +11,7 @@ export default abstract class ProtocolPresenceService { return await dataSource .getRepository(protocolPresence) .createQueryBuilder("protocolPresence") - .where("protocolAgenda.protocolId = :protocolId", { protocolId }) + .where("protocolPresence.protocolId = :protocolId", { protocolId }) .getMany() .then((res) => { return res; diff --git a/src/service/protocolVotingService.ts b/src/service/protocolVotingService.ts index 4f806d9..55792fe 100644 --- a/src/service/protocolVotingService.ts +++ b/src/service/protocolVotingService.ts @@ -11,7 +11,7 @@ export default abstract class ProtocolVotingService { return await dataSource .getRepository(protocolVoting) .createQueryBuilder("protocolVotings") - .where("protocolAgenda.protocolId = :protocolId", { protocolId }) + .where("protocolVotings.protocolId = :protocolId", { protocolId }) .getMany() .then((res) => { return res; From 5f434c943e7050fdd98481e88097c7ea484a92aa Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Mon, 14 Oct 2024 17:03:33 +0200 Subject: [PATCH 06/22] protocol optional fields --- src/entity/protocol.ts | 6 +++--- src/migrations/1728037129072-protocolBase.ts | 6 +++--- src/service/protocolService.ts | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/entity/protocol.ts b/src/entity/protocol.ts index ff25531..c82dd88 100644 --- a/src/entity/protocol.ts +++ b/src/entity/protocol.ts @@ -11,12 +11,12 @@ export class protocol { @Column({ type: "date" }) date: Date; - @Column({ type: "timestamp" }) + @Column({ type: "timestamp", nullable: true }) starttime: Date; - @Column({ type: "timestamp" }) + @Column({ type: "timestamp", nullable: true }) endtime: Date; - @Column({ type: "text" }) + @Column({ type: "text", nullable: true }) summary: string; } diff --git a/src/migrations/1728037129072-protocolBase.ts b/src/migrations/1728037129072-protocolBase.ts index e1f9a71..23f56ea 100644 --- a/src/migrations/1728037129072-protocolBase.ts +++ b/src/migrations/1728037129072-protocolBase.ts @@ -9,7 +9,7 @@ export class ProtocolBase1728037129072 implements MigrationInterface { new TableColumn({ name: "starttime", type: "timestamp", - isNullable: false, + isNullable: true, }) ); await queryRunner.addColumn( @@ -17,7 +17,7 @@ export class ProtocolBase1728037129072 implements MigrationInterface { new TableColumn({ name: "endtime", type: "timestamp", - isNullable: false, + isNullable: true, }) ); await queryRunner.addColumn( @@ -25,7 +25,7 @@ export class ProtocolBase1728037129072 implements MigrationInterface { new TableColumn({ name: "summary", type: "text", - isNullable: false, + isNullable: true, }) ); } diff --git a/src/service/protocolService.ts b/src/service/protocolService.ts index 553b550..53e0baf 100644 --- a/src/service/protocolService.ts +++ b/src/service/protocolService.ts @@ -13,7 +13,7 @@ export default abstract class ProtocolService { .createQueryBuilder("protocol") .offset(offset) .limit(count) - .orderBy("date") + .orderBy("date", "DESC") .getManyAndCount() .then((res) => { return res; From e1ad491e680d505067c90730927cc6a7f76936e4 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Tue, 15 Oct 2024 16:25:42 +0200 Subject: [PATCH 07/22] sync and create change --- src/command/protocolAgendaCommandHandler.ts | 24 +++++ src/command/protocolDecisionCommandHandler.ts | 23 ++++ src/command/protocolPresenceCommandHandler.ts | 14 ++- src/command/protocolVotingCommandHandler.ts | 23 ++++ src/controller/admin/protocolController.ts | 83 ++++++++++---- src/data-source.ts | 2 + src/entity/protocolAgenda.ts | 5 +- src/entity/protocolDecision.ts | 5 +- src/entity/protocolVoting.ts | 5 +- src/factory/admin/protocolAgenda.ts | 2 +- src/factory/admin/protocolDecision.ts | 2 +- src/factory/admin/protocolPresence.ts | 2 +- src/factory/admin/protocolVoting.ts | 2 +- .../1728999487170-protocolTableTypes.ts | 101 ++++++++++++++++++ src/routes/admin/protocol.ts | 29 +++-- src/service/protocolPrecenseService.ts | 2 + 16 files changed, 285 insertions(+), 39 deletions(-) create mode 100644 src/migrations/1728999487170-protocolTableTypes.ts diff --git a/src/command/protocolAgendaCommandHandler.ts b/src/command/protocolAgendaCommandHandler.ts index d132558..91c7dea 100644 --- a/src/command/protocolAgendaCommandHandler.ts +++ b/src/command/protocolAgendaCommandHandler.ts @@ -4,6 +4,30 @@ import InternalException from "../exceptions/internalException"; import { SynchronizeProtocolAgendaCommand } from "./protocolAgendaCommand"; export default abstract class ProtocolAgendaCommandHandler { + /** + * @description create protocolAgenda + * @param {number} + * @returns {Promise} + */ + static async create(protocolId: number): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolAgenda) + .values({ + topic: "", + context: "", + protocolId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } + /** * @description sync protocolAgenda * @param {Array} diff --git a/src/command/protocolDecisionCommandHandler.ts b/src/command/protocolDecisionCommandHandler.ts index 3925884..750a40e 100644 --- a/src/command/protocolDecisionCommandHandler.ts +++ b/src/command/protocolDecisionCommandHandler.ts @@ -4,6 +4,29 @@ import InternalException from "../exceptions/internalException"; import { SynchronizeProtocolDecisionCommand } from "./protocolDecisionCommand"; export default abstract class ProtocolDecisionCommandHandler { + /** + * @description create protocolDecision + * @param {number} + * @returns {Promise} + */ + static async create(protocolId: number): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolDecision) + .values({ + topic: "", + context: "", + protocolId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } /** * @description sync protocolDecision * @param {Array} diff --git a/src/command/protocolPresenceCommandHandler.ts b/src/command/protocolPresenceCommandHandler.ts index 7d7a5f7..b773cc6 100644 --- a/src/command/protocolPresenceCommandHandler.ts +++ b/src/command/protocolPresenceCommandHandler.ts @@ -21,13 +21,17 @@ export default abstract class ProtocolPresenceCommandHandler { let newMembers = syncProtocolPresences.memberIds.filter((r) => !currentPresence.includes(r)); let removeMembers = currentPresence.filter((r) => !syncProtocolPresences.memberIds.includes(r)); - await this.syncPresenceAdd(manager, syncProtocolPresences.protocolId, newMembers); + if (newMembers.length != 0) { + await this.syncPresenceAdd(manager, syncProtocolPresences.protocolId, newMembers); + } - await this.syncPresenceRemove(manager, syncProtocolPresences.protocolId, removeMembers); + if (removeMembers.length != 0) { + await this.syncPresenceRemove(manager, syncProtocolPresences.protocolId, removeMembers); + } }) .then(() => {}) .catch((err) => { - throw new InternalException("Failed saving user roles", err); + throw new InternalException("Failed saving protocol presence", err); }); } @@ -43,7 +47,7 @@ export default abstract class ProtocolPresenceCommandHandler { .values( memberIds.map((m) => ({ protocolId, - memberIds: m, + memberId: m, })) ) .execute(); @@ -59,7 +63,7 @@ export default abstract class ProtocolPresenceCommandHandler { .delete() .from(protocolPresence) .where("memberId IN (:...ids)", { ids: memberIds }) - .andWhere("protcolId = :protocolId", { protocolId }) + .andWhere("protocolId = :protocolId", { protocolId }) .execute(); } } diff --git a/src/command/protocolVotingCommandHandler.ts b/src/command/protocolVotingCommandHandler.ts index 6ade2f1..bbc9660 100644 --- a/src/command/protocolVotingCommandHandler.ts +++ b/src/command/protocolVotingCommandHandler.ts @@ -4,6 +4,29 @@ import InternalException from "../exceptions/internalException"; import { SynchronizeProtocolVotingCommand } from "./protocolVotingCommand"; export default abstract class ProtocolVotingCommandHandler { + /** + * @description create protocolVoting + * @param {number} + * @returns {Promise} + */ + static async create(protocolId: number): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolVoting) + .values({ + topic: "", + context: "", + protocolId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } /** * @description sync protocolVoting * @param {Array} diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index 51bf599..3316ec8 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -21,6 +21,7 @@ import { SynchronizeProtocolPresenceCommand } from "../../command/protocolPresen import { SynchronizeProtocolDecisionCommand } from "../../command/protocolDecisionCommand"; import { SynchronizeProtocolVotingCommand } from "../../command/protocolVotingCommand"; import { ProtocolVotingViewModel } from "../../viewmodel/admin/protocolVoting.models"; +import ProtocolVotingCommandHandler from "../../command/protocolVotingCommandHandler"; /** * @description get all protocols @@ -129,6 +130,48 @@ export async function createProtocol(req: Request, res: Response): Promise res.send(id); } +/** + * @description create protocol agenda by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocolAgendaById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let agenda = await ProtocolAgendaCommandHandler.create(protocolId); + + res.send(agenda); +} + +/** + * @description create protocol decisions by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocolDecisonsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let decision = await ProtocolDecisionCommandHandler.create(protocolId); + + res.send(decision); +} + +/** + * @description create protocol votings by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocolVotingsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let voting = await ProtocolVotingCommandHandler.create(protocolId); + + res.send(voting); +} + /** * @description synchronize protocol by id * @param req {Request} Express req object @@ -202,25 +245,6 @@ export async function synchronizeProtocolDecisonsById(req: Request, res: Respons res.sendStatus(204); } -/** - * @description synchronize protocol precense by id - * @param req {Request} Express req object - * @param res {Response} Express res object - * @returns {Promise<*>} - */ -export async function synchronizeProtocolPrecenseById(req: Request, res: Response): Promise { - let protocolId = parseInt(req.params.protocolId); - let presence = req.body.precense as Array; - - let syncPresence: SynchronizeProtocolPresenceCommand = { - memberIds: presence, - protocolId, - }; - await ProtocolPresenceCommandHandler.sync(syncPresence); - - res.sendStatus(204); -} - /** * @description synchronize protocol votings by id * @param req {Request} Express req object @@ -242,7 +266,26 @@ export async function synchronizeProtocolVotingsById(req: Request, res: Response protocolId, }) ); - await ProtocolDecisionCommandHandler.sync(syncDecision); + await ProtocolVotingCommandHandler.sync(syncDecision); + + res.sendStatus(204); +} + +/** + * @description synchronize protocol precense by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function synchronizeProtocolPrecenseById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let presence = req.body.presence as Array; + + let syncPresence: SynchronizeProtocolPresenceCommand = { + memberIds: presence, + protocolId, + }; + await ProtocolPresenceCommandHandler.sync(syncPresence); res.sendStatus(204); } diff --git a/src/data-source.ts b/src/data-source.ts index f08af33..5a1c1f3 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -38,6 +38,7 @@ import { protocolPresence } from "./entity/protocolPresence"; import { protocolVoting } from "./entity/protocolVoting"; import { ProtocolTables1728563204766 } from "./migrations/1728563204766-protocolTables"; import { ProtocolTableRename1728645611919 } from "./migrations/1728645611919-protocolTableRename"; +import { ProtocolTableTypes1728999487170 } from "./migrations/1728999487170-protocolTableTypes"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -86,6 +87,7 @@ const dataSource = new DataSource({ ProtocolBase1728037129072, ProtocolTables1728563204766, ProtocolTableRename1728645611919, + ProtocolTableTypes1728999487170, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/protocolAgenda.ts b/src/entity/protocolAgenda.ts index dcfbde4..7ce7556 100644 --- a/src/entity/protocolAgenda.ts +++ b/src/entity/protocolAgenda.ts @@ -9,9 +9,12 @@ export class protocolAgenda { @Column({ type: "varchar", length: 255 }) topic: string; - @Column({ type: "varchar", length: 255, default: "" }) + @Column({ type: "text", default: "" }) context: string; + @Column() + protocolId: number; + @ManyToOne(() => protocol, { nullable: false, onDelete: "CASCADE", diff --git a/src/entity/protocolDecision.ts b/src/entity/protocolDecision.ts index b40d3aa..4978d17 100644 --- a/src/entity/protocolDecision.ts +++ b/src/entity/protocolDecision.ts @@ -9,9 +9,12 @@ export class protocolDecision { @Column({ type: "varchar", length: 255 }) topic: string; - @Column({ type: "varchar", length: 255, default: "" }) + @Column({ type: "text", default: "" }) context: string; + @Column() + protocolId: number; + @ManyToOne(() => protocol, { nullable: false, onDelete: "CASCADE", diff --git a/src/entity/protocolVoting.ts b/src/entity/protocolVoting.ts index 4f3b461..8e8f7a1 100644 --- a/src/entity/protocolVoting.ts +++ b/src/entity/protocolVoting.ts @@ -9,7 +9,7 @@ export class protocolVoting { @Column({ type: "varchar", length: 255 }) topic: string; - @Column({ type: "varchar", length: 255, default: "" }) + @Column({ type: "text", default: "" }) context: string; @Column({ type: "int", default: 0 }) @@ -21,6 +21,9 @@ export class protocolVoting { @Column({ type: "int", default: 0 }) against: number; + @Column() + protocolId: number; + @ManyToOne(() => protocol, { nullable: false, onDelete: "CASCADE", diff --git a/src/factory/admin/protocolAgenda.ts b/src/factory/admin/protocolAgenda.ts index d48c0ef..432c479 100644 --- a/src/factory/admin/protocolAgenda.ts +++ b/src/factory/admin/protocolAgenda.ts @@ -12,7 +12,7 @@ export default abstract class ProtocolAgendaFactory { id: record.id, topic: record.topic, context: record.context, - protocolId: record.protocol.id, + protocolId: record.protocolId, }; } diff --git a/src/factory/admin/protocolDecision.ts b/src/factory/admin/protocolDecision.ts index a23f524..3e33823 100644 --- a/src/factory/admin/protocolDecision.ts +++ b/src/factory/admin/protocolDecision.ts @@ -12,7 +12,7 @@ export default abstract class ProtocolDecisionFactory { id: record.id, topic: record.topic, context: record.context, - protocolId: record.protocol.id, + protocolId: record.protocolId, }; } diff --git a/src/factory/admin/protocolPresence.ts b/src/factory/admin/protocolPresence.ts index 3772be8..59a70ad 100644 --- a/src/factory/admin/protocolPresence.ts +++ b/src/factory/admin/protocolPresence.ts @@ -12,7 +12,7 @@ export default abstract class ProtocolPresenceFactory { return { memberId: record.member.id, member: MemberFactory.mapToSingle(record.member), - protocolId: record.protocol.id, + protocolId: record.protocolId, }; } diff --git a/src/factory/admin/protocolVoting.ts b/src/factory/admin/protocolVoting.ts index 076fe59..19865fb 100644 --- a/src/factory/admin/protocolVoting.ts +++ b/src/factory/admin/protocolVoting.ts @@ -15,7 +15,7 @@ export default abstract class ProtocolVotingFactory { favour: record.favour, abstain: record.abstain, against: record.against, - protocolId: record.protocol.id, + protocolId: record.protocolId, }; } diff --git a/src/migrations/1728999487170-protocolTableTypes.ts b/src/migrations/1728999487170-protocolTableTypes.ts new file mode 100644 index 0000000..ef739f0 --- /dev/null +++ b/src/migrations/1728999487170-protocolTableTypes.ts @@ -0,0 +1,101 @@ +import { MigrationInterface, QueryRunner, TableColumn } from "typeorm"; + +export class ProtocolTableTypes1728999487170 implements MigrationInterface { + name = "ProtocolTableTypes1728999487170"; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.changeColumn( + "protocol", + "summary", + new TableColumn({ + name: "summary", + type: "text", + default: "''", + isNullable: false, + }) + ); + + await queryRunner.changeColumn( + "protocol_agenda", + "context", + new TableColumn({ + name: "context", + type: "text", + default: "''", + isNullable: false, + }) + ); + + await queryRunner.changeColumn( + "protocol_decision", + "context", + new TableColumn({ + name: "context", + type: "text", + default: "''", + isNullable: false, + }) + ); + + await queryRunner.changeColumn( + "protocol_voting", + "context", + new TableColumn({ + name: "context", + type: "text", + default: "''", + isNullable: false, + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.changeColumn( + "protocol", + "summary", + new TableColumn({ + name: "summary", + type: "varchar", + length: "255", + default: "''", + isNullable: false, + }) + ); + + await queryRunner.changeColumn( + "protocol_agenda", + "context", + new TableColumn({ + name: "context", + type: "varchar", + length: "255", + default: "''", + isNullable: false, + }) + ); + + await queryRunner.changeColumn( + "protocol_decision", + "context", + new TableColumn({ + name: "context", + type: "varchar", + length: "255", + default: "''", + isNullable: false, + }) + ); + + await queryRunner.changeColumn( + "protocol_voting", + "context", + new TableColumn({ + name: "context", + type: "varchar", + length: "255", + default: "''", + isNullable: false, + }) + ); + } +} diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts index 5edbeee..f7ed9b6 100644 --- a/src/routes/admin/protocol.ts +++ b/src/routes/admin/protocol.ts @@ -1,6 +1,9 @@ import express, { Request, Response } from "express"; import { createProtocol, + createProtocolAgendaById, + createProtocolDecisonsById, + createProtocolVotingsById, getAllProtocols, getProtocolAgendaById, getProtocolById, @@ -44,24 +47,36 @@ router.post("/", async (req: Request, res: Response) => { await createProtocol(req, res); }); -router.put("/:id/synchronize", async (req: Request, res: Response) => { +router.post("/:protocolId/agenda", async (req: Request, res: Response) => { + await createProtocolAgendaById(req, res); +}); + +router.post("/:protocolId/decision", async (req: Request, res: Response) => { + await createProtocolDecisonsById(req, res); +}); + +router.post("/:protocolId/voting", async (req: Request, res: Response) => { + await createProtocolVotingsById(req, res); +}); + +router.patch("/:id/synchronize", async (req: Request, res: Response) => { await synchronizeProtocolById(req, res); }); -router.put("/:protocolId/synchronize/agenda", async (req: Request, res: Response) => { +router.patch("/:protocolId/synchronize/agenda", async (req: Request, res: Response) => { await synchronizeProtocolAgendaById(req, res); }); -router.put("/:protocolId/synchronize/decisions", async (req: Request, res: Response) => { +router.patch("/:protocolId/synchronize/decisions", async (req: Request, res: Response) => { await synchronizeProtocolDecisonsById(req, res); }); +router.patch("/:protocolId/synchronize/votings", async (req: Request, res: Response) => { + await synchronizeProtocolVotingsById(req, res); +}); + router.put("/:protocolId/synchronize/presence", async (req: Request, res: Response) => { await synchronizeProtocolPrecenseById(req, res); }); -router.put("/:protocolId/synchronize/votings", async (req: Request, res: Response) => { - await synchronizeProtocolVotingsById(req, res); -}); - export default router; diff --git a/src/service/protocolPrecenseService.ts b/src/service/protocolPrecenseService.ts index e66c946..c8840c3 100644 --- a/src/service/protocolPrecenseService.ts +++ b/src/service/protocolPrecenseService.ts @@ -11,6 +11,7 @@ export default abstract class ProtocolPresenceService { return await dataSource .getRepository(protocolPresence) .createQueryBuilder("protocolPresence") + .leftJoinAndSelect("protocolPresence.member", "member") .where("protocolPresence.protocolId = :protocolId", { protocolId }) .getMany() .then((res) => { @@ -29,6 +30,7 @@ export default abstract class ProtocolPresenceService { return await dataSource .getRepository(protocolPresence) .createQueryBuilder("protocolPresence") + .leftJoinAndSelect("protocolPresence.member", "member") .where("protocolPresence.id = :id", { id: id }) .getOneOrFail() .then((res) => { From da219eb5f4ecb7c4e5596a4b8281af5ad40d2428 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Fri, 18 Oct 2024 14:20:48 +0200 Subject: [PATCH 08/22] rename --- src/controller/admin/protocolController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index 3316ec8..4a4a61a 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -253,9 +253,9 @@ export async function synchronizeProtocolDecisonsById(req: Request, res: Respons */ export async function synchronizeProtocolVotingsById(req: Request, res: Response): Promise { let protocolId = parseInt(req.params.protocolId); - let decisions = req.body.decisions as Array; + let votings = req.body.votings as Array; - let syncDecision: Array = decisions.map( + let syncVoting: Array = votings.map( (d: ProtocolVotingViewModel): SynchronizeProtocolVotingCommand => ({ id: d.id ?? null, topic: d.topic, @@ -266,7 +266,7 @@ export async function synchronizeProtocolVotingsById(req: Request, res: Response protocolId, }) ); - await ProtocolVotingCommandHandler.sync(syncDecision); + await ProtocolVotingCommandHandler.sync(syncVoting); res.sendStatus(204); } From 58213923e58b10b277a55f6a20aa2cecdfc99a02 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Fri, 18 Oct 2024 15:23:51 +0200 Subject: [PATCH 09/22] render protocol by template --- .gitignore | 1 + package-lock.json | 804 ++++++++++++++++++++- package.json | 1 + src/controller/admin/protocolController.ts | 45 ++ src/helpers/pdfExport.ts | 34 + src/routes/admin/protocol.ts | 5 + src/templates/protocol.template.html | 36 + src/types/pdf-creator-node.d.ts | 27 + 8 files changed, 950 insertions(+), 3 deletions(-) create mode 100644 src/helpers/pdfExport.ts create mode 100644 src/templates/protocol.template.html create mode 100644 src/types/pdf-creator-node.d.ts diff --git a/.gitignore b/.gitignore index ceaea36..a7fe266 100644 --- a/.gitignore +++ b/.gitignore @@ -130,3 +130,4 @@ dist .yarn/install-state.gz .pnp.* +export \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 807bfd9..76052dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "mysql": "^2.18.1", "node-schedule": "^2.1.1", "nodemailer": "^6.9.14", + "pdf-creator-node": "^2.3.5", "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "socket.io": "^4.7.5", @@ -404,6 +405,23 @@ "node": ">=0.4.0" } }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "license": "MIT", + "optional": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -450,6 +468,50 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz", "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA==" }, + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT", + "optional": true + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz", + "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==", + "license": "MIT", + "optional": true + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -488,6 +550,16 @@ "node": "^4.5.0 || >= 5.9" } }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, "node_modules/better-sqlite3": { "version": "9.6.0", "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-9.6.0.tgz", @@ -636,12 +708,29 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", "license": "BSD-3-Clause" }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT", + "optional": true + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -676,6 +765,13 @@ "node": ">=6" } }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", + "license": "Apache-2.0", + "optional": true + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -803,6 +899,35 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "optional": true, + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -882,6 +1007,19 @@ "node": ">= 8" } }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/dayjs": { "version": "1.11.12", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.12.tgz", @@ -951,6 +1089,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -1008,6 +1156,17 @@ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "license": "MIT", + "optional": true, + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, "node_modules/ecdsa-sig-formatter": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", @@ -1121,6 +1280,13 @@ "node": ">= 0.4" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT", + "optional": true + }, "node_modules/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -1209,6 +1375,76 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", + "license": "MIT", + "optional": true + }, + "node_modules/extract-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-1.7.0.tgz", + "integrity": "sha512-xoh5G1W/PB0/27lXgMQyIhP5DSY/LhoCsOyZgb+6iMmRtCwVBo55uKaMoEYrDCKQhWvqEip5ZPKAc6eFNyf/MA==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "concat-stream": "^1.6.2", + "debug": "^2.6.9", + "mkdirp": "^0.5.4", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + } + }, + "node_modules/extract-zip/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT", + "optional": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "license": "MIT", + "optional": true + }, + "node_modules/fd-slicer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", + "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "license": "MIT", + "optional": true, + "dependencies": { + "pend": "~1.2.0" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -1260,6 +1496,31 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, "node_modules/forwarded": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", @@ -1283,6 +1544,18 @@ "optional": true, "peer": true }, + "node_modules/fs-extra": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "integrity": "sha512-VerQV6vEKuhDWD2HGOybV6v5I73syoc/cXAbKlgTC7M/oFVEtklWlp9QH2Ijw3IaWDOQcMkldSPa7zXy79Z/UQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^2.1.0", + "klaw": "^1.0.0" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -1317,6 +1590,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0" + } + }, "node_modules/github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", @@ -1354,6 +1637,59 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, + "node_modules/handlebars": { + "version": "4.7.8", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", + "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==", + "license": "ISC", + "optional": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", + "license": "MIT", + "optional": true, + "dependencies": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -1395,6 +1731,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hasha": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-2.2.0.tgz", + "integrity": "sha512-jZ38TU/EBiGKrmyTNNZgnvCZHNowiRI4+w/I9noMlekHTZH3KyGgvJLmhSgykeAQ9j2SYPDosM0Bg3wHfzibAQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "is-stream": "^1.0.1", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -1414,6 +1764,22 @@ "node": "*" } }, + "node_modules/html-pdf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-pdf/-/html-pdf-3.0.1.tgz", + "integrity": "sha512-CKNSacmQn+CKJ2GNfT4UYKaPy/T3Ndj82yJ2aju/UPmnvWNjIpyumqRqkFU0mwT6BTHBFhFGTnXN8dBn4Bdj0Q==", + "deprecated": "Please migrate your projects to a newer library like puppeteer", + "license": "MIT", + "bin": { + "html-pdf": "bin/index.js" + }, + "engines": { + "node": ">=4.0.0" + }, + "optionalDependencies": { + "phantomjs-prebuilt": "^2.1.16" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -1429,6 +1795,22 @@ "node": ">= 0.8" } }, + "node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, "node_modules/iconv-lite": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz", @@ -1492,6 +1874,23 @@ "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, + "node_modules/is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", + "license": "MIT", + "optional": true + }, "node_modules/isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1502,6 +1901,13 @@ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==", + "license": "MIT", + "optional": true + }, "node_modules/jackspeak": { "version": "3.4.3", "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", @@ -1516,6 +1922,44 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "license": "MIT", + "optional": true + }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "license": "(AFL-2.1 OR BSD-3-Clause)", + "optional": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "license": "MIT", + "optional": true + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC", + "optional": true + }, + "node_modules/jsonfile": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "integrity": "sha512-PKllAqbgLgxHaj8TElYymKCAgrASebJrWpTnEkOaTowt23VKXXN0sUeriJ+eh7y6ufb/CC5ap11pz71/cM0hUw==", + "license": "MIT", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/jsonwebtoken": { "version": "9.0.2", "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz", @@ -1538,6 +1982,22 @@ "npm": ">=6" } }, + "node_modules/jsprim": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", + "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.4.0", + "verror": "1.10.0" + }, + "engines": { + "node": ">=0.6.0" + } + }, "node_modules/jwa": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jwa/-/jwa-1.4.1.tgz", @@ -1559,6 +2019,23 @@ "safe-buffer": "^5.0.1" } }, + "node_modules/kew": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "integrity": "sha512-IG6nm0+QtAMdXt9KvbgbGdvY50RSrw+U4sGZg+KlrSKPJEwVE5JVoI3d7RWfSMdBQneRheeAOj3lIjX5VL/9RQ==", + "license": "Apache-2.0", + "optional": true + }, + "node_modules/klaw": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", + "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "license": "MIT", + "optional": true, + "optionalDependencies": { + "graceful-fs": "^4.1.9" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", @@ -1707,8 +2184,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "optional": true, - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1792,6 +2267,12 @@ "node": ">= 0.6" } }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "license": "MIT" + }, "node_modules/node-abi": { "version": "3.65.0", "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.65.0.tgz", @@ -1826,6 +2307,16 @@ "node": ">=6.0.0" } }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "license": "Apache-2.0", + "optional": true, + "engines": { + "node": "*" + } + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -1974,6 +2465,89 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz", "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA==" }, + "node_modules/pdf-creator-node": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/pdf-creator-node/-/pdf-creator-node-2.3.5.tgz", + "integrity": "sha512-dUnYiYTGBkC60M1hBrHjlZs+gsSZZvXP33OLDUDOUPhNNhnHXQrzXFC3leo57ujzS/3rpbrqFcXxWpB5fgwkpw==", + "license": "ISC", + "dependencies": { + "handlebars": "^4.7.7", + "html-pdf": "^3.0.1" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "license": "MIT", + "optional": true + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==", + "license": "MIT", + "optional": true + }, + "node_modules/phantomjs-prebuilt": { + "version": "2.1.16", + "resolved": "https://registry.npmjs.org/phantomjs-prebuilt/-/phantomjs-prebuilt-2.1.16.tgz", + "integrity": "sha512-PIiRzBhW85xco2fuj41FmsyuYHKjKuXWmhjy3A/Y+CMpN/63TV+s9uzfVhsUwFe0G77xWtHBG8xmXf5BqEUEuQ==", + "deprecated": "this package is now deprecated", + "hasInstallScript": true, + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "es6-promise": "^4.0.3", + "extract-zip": "^1.6.5", + "fs-extra": "^1.0.0", + "hasha": "^2.2.0", + "kew": "^0.7.0", + "progress": "^1.1.8", + "request": "^2.81.0", + "request-progress": "^2.0.1", + "which": "^1.2.10" + }, + "bin": { + "phantomjs": "bin/phantomjs" + } + }, + "node_modules/phantomjs-prebuilt/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "license": "MIT", + "optional": true, + "dependencies": { + "pinkie": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", @@ -2014,6 +2588,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha512-UdA8mJ4weIkUBO224tIarHzuHs4HuYiJvsuGT7j/SPQiUJVjYvNDBIPa0hAorduOfjGohB/qHWRa/lrrWX/mXw==", + "optional": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -2026,6 +2609,13 @@ "node": ">= 0.10" } }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "license": "MIT", + "optional": true + }, "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", @@ -2037,6 +2627,16 @@ "once": "^1.3.1" } }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, "node_modules/qrcode": { "version": "1.5.4", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz", @@ -2129,6 +2729,70 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==" }, + "node_modules/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-progress": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-2.0.1.tgz", + "integrity": "sha512-dxdraeZVUNEn9AvLrxkgB2k6buTlym71dJk1fk4v8j3Ou3RKNm07BcgbHdj2lLgYGfqX71F+awb1MR+tWPFJzA==", + "license": "MIT", + "optional": true, + "dependencies": { + "throttleit": "^1.0.0" + } + }, + "node_modules/request/node_modules/qs": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", + "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/request/node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "license": "MIT", + "optional": true, + "bin": { + "uuid": "bin/uuid" + } + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -2481,6 +3145,15 @@ "resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.3.0.tgz", "integrity": "sha512-2sqgzeFlid6N4Z2fUQ1cvFmTOLRi/sEDzSQ0OKYchqgoPmQBVyM3959qYx3fpS6Esef80KjmpgPeEr028dP3OA==" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/speakeasy": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/speakeasy/-/speakeasy-2.0.0.tgz", @@ -2501,6 +3174,32 @@ "node": ">= 0.6" } }, + "node_modules/sshpk": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", + "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/statuses": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", @@ -2657,6 +3356,16 @@ "node": ">=0.8" } }, + "node_modules/throttleit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", + "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -2665,6 +3374,20 @@ "node": ">=0.6" } }, + "node_modules/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "license": "BSD-3-Clause", + "optional": true, + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/ts-node": { "version": "10.7.0", "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.7.0.tgz", @@ -2718,7 +3441,6 @@ "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", "optional": true, - "peer": true, "dependencies": { "safe-buffer": "^5.0.1" }, @@ -2726,6 +3448,13 @@ "node": "*" } }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA==", + "license": "Unlicense", + "optional": true + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -2738,6 +3467,13 @@ "node": ">= 0.6" } }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT", + "optional": true + }, "node_modules/typeorm": { "version": "0.3.20", "resolved": "https://registry.npmjs.org/typeorm/-/typeorm-0.3.20.tgz", @@ -2951,6 +3687,19 @@ "node": ">=4.2.0" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", + "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -2959,6 +3708,16 @@ "node": ">= 0.8" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "optional": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2998,6 +3757,28 @@ "node": ">= 0.8" } }, + "node_modules/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw==", + "engines": [ + "node >=0.6.0" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/verror/node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==", + "license": "MIT", + "optional": true + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -3017,6 +3798,12 @@ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==" }, + "node_modules/wordwrap": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", + "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", @@ -3112,6 +3899,17 @@ "node": ">=6" } }, + "node_modules/yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + } + }, "node_modules/yn": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", diff --git a/package.json b/package.json index a7988ad..035be71 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "mysql": "^2.18.1", "node-schedule": "^2.1.1", "nodemailer": "^6.9.14", + "pdf-creator-node": "^2.3.5", "qrcode": "^1.5.4", "reflect-metadata": "^0.2.2", "socket.io": "^4.7.5", diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index 4a4a61a..f936274 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -22,6 +22,7 @@ import { SynchronizeProtocolDecisionCommand } from "../../command/protocolDecisi import { SynchronizeProtocolVotingCommand } from "../../command/protocolVotingCommand"; import { ProtocolVotingViewModel } from "../../viewmodel/admin/protocolVoting.models"; import ProtocolVotingCommandHandler from "../../command/protocolVotingCommandHandler"; +import { PdfExport } from "../../helpers/pdfExport"; /** * @description get all protocols @@ -289,3 +290,47 @@ export async function synchronizeProtocolPrecenseById(req: Request, res: Respons res.sendStatus(204); } + +/** + * @description render protocol to file by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function printPdf(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let protocol = await ProtocolService.getById(protocolId); + let agenda = await ProtocolAgendaService.getAll(protocolId); + let decisions = await ProtocolDecisionService.getAll(protocolId); + let presence = await ProtocolPresenceService.getAll(protocolId); + let votings = await ProtocolVotingService.getAll(protocolId); + + await PdfExport.renderFile({ + template: "protocol.template.html", + title: protocol.title, + data: { + title: protocol.title, + summary: protocol.summary, + date: new Date(protocol.date).toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "2-digit", + year: "numeric", + }), + start: new Date(protocol.starttime).toLocaleTimeString("de-DE", { + minute: "2-digit", + hour: "2-digit", + }), + end: new Date(protocol.endtime).toLocaleTimeString("de-DE", { + minute: "2-digit", + hour: "2-digit", + }), + agenda, + decisions, + presence: presence.map((p) => p.member), + votings, + }, + }); + + res.sendStatus(204); +} diff --git a/src/helpers/pdfExport.ts b/src/helpers/pdfExport.ts new file mode 100644 index 0000000..4048f5e --- /dev/null +++ b/src/helpers/pdfExport.ts @@ -0,0 +1,34 @@ +import { readFileSync } from "fs"; +import pdf, { Options } from "pdf-creator-node"; + +var options = (title: string = "pdf-export Mitgliederverwaltung"): Options => ({ + format: "A4", + orientation: "portrait", + border: "10mm", + header: { + height: "10mm", + contents: `

${title}

`, + }, + footer: { + height: "5mm", + contents: { + default: '{{page}}/{{pages}}', + }, + }, +}); + +export abstract class PdfExport { + static getTemplate(template: string) { + return readFileSync(process.cwd() + "/src/templates/" + template, "utf8"); + } + + static async renderFile({ template, title, data }: { template: string; title: string; data: any }) { + let document = { + html: this.getTemplate(template), + data, + path: process.cwd() + `/export/${title}.pdf`, + }; + + await pdf.create(document, options(title)); + } +} diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts index f7ed9b6..4b07da7 100644 --- a/src/routes/admin/protocol.ts +++ b/src/routes/admin/protocol.ts @@ -10,6 +10,7 @@ import { getProtocolDecisonsById, getProtocolPrecenseById, getProtocolVotingsById, + printPdf, synchronizeProtocolAgendaById, synchronizeProtocolById, synchronizeProtocolDecisonsById, @@ -59,6 +60,10 @@ router.post("/:protocolId/voting", async (req: Request, res: Response) => { await createProtocolVotingsById(req, res); }); +router.post("/:protocolId/render", async (req: Request, res: Response) => { + await printPdf(req, res); +}); + router.patch("/:id/synchronize", async (req: Request, res: Response) => { await synchronizeProtocolById(req, res); }); diff --git a/src/templates/protocol.template.html b/src/templates/protocol.template.html new file mode 100644 index 0000000..e5e22b3 --- /dev/null +++ b/src/templates/protocol.template.html @@ -0,0 +1,36 @@ + + + + + Protokoll + + +

{{title}}

+

Am {{date}} von {{start}} Uhr bis {{end}} Uhr

+

{{sumary}}

+

Anwesenheit

+
    + {{#each presence}} +
  • {{this.firstname}} {{this.lastname}}
  • + {{/each}} +
+

Agenda

+
    + {{#each agenda}} +
  • {{this}}
  • + {{/each}} +
+

Entscheidungen

+
    + {{#each decisions}} +
  • {{this}}
  • + {{/each}} +
+

Abstimmungen

+
    + {{#each votings}} +
  • {{this}}
  • + {{/each}} +
+ + diff --git a/src/types/pdf-creator-node.d.ts b/src/types/pdf-creator-node.d.ts new file mode 100644 index 0000000..4df4491 --- /dev/null +++ b/src/types/pdf-creator-node.d.ts @@ -0,0 +1,27 @@ +// types/pdf-creator-node.d.ts +declare module "pdf-creator-node" { + interface Document { + html: string; + data: any; + path: string; + type?: string; + } + + interface Options { + format: string; + orientation: string; + border: string; + header?: { + height: string; + contents: string; + }; + footer?: { + height: string; + contents: string | { [key: string]: string | number }; + }; + } + + function create(document: Document, options: Options): Promise; + + export { create, Document, Options }; +} From 9da2a98f55e6d7158d983da105ac8db0b7fb68bd Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Sat, 19 Oct 2024 16:24:41 +0200 Subject: [PATCH 10/22] printout --- src/command/protocolPrintoutCommand.ts | 6 + src/command/protocolPrintoutCommandHandler.ts | 31 ++++ src/controller/admin/protocolController.ts | 140 ++++++++++++------ src/data-source.ts | 4 + src/entity/protocolPrintout.ts | 30 ++++ src/factory/admin/protocolPrintout.ts | 28 ++++ src/helpers/pdfExport.ts | 14 +- .../1729344771434-protocolPrintout.ts | 44 ++++++ src/routes/admin/protocol.ts | 16 +- src/service/protocolPrintoutService.ts | 60 ++++++++ src/templates/protocol.template.html | 15 +- .../admin/protocolPrintout.models.ts | 7 + 12 files changed, 341 insertions(+), 54 deletions(-) create mode 100644 src/command/protocolPrintoutCommand.ts create mode 100644 src/command/protocolPrintoutCommandHandler.ts create mode 100644 src/entity/protocolPrintout.ts create mode 100644 src/factory/admin/protocolPrintout.ts create mode 100644 src/migrations/1729344771434-protocolPrintout.ts create mode 100644 src/service/protocolPrintoutService.ts create mode 100644 src/viewmodel/admin/protocolPrintout.models.ts diff --git a/src/command/protocolPrintoutCommand.ts b/src/command/protocolPrintoutCommand.ts new file mode 100644 index 0000000..6491f9d --- /dev/null +++ b/src/command/protocolPrintoutCommand.ts @@ -0,0 +1,6 @@ +export interface CreateProtocolPrintoutCommand { + title: string; + iteration: number; + filename: string; + protocolId: number; +} diff --git a/src/command/protocolPrintoutCommandHandler.ts b/src/command/protocolPrintoutCommandHandler.ts new file mode 100644 index 0000000..c1cdcbe --- /dev/null +++ b/src/command/protocolPrintoutCommandHandler.ts @@ -0,0 +1,31 @@ +import { dataSource } from "../data-source"; +import { protocolPrintout } from "../entity/protocolPrintout"; +import InternalException from "../exceptions/internalException"; +import { CreateProtocolPrintoutCommand } from "./protocolPrintoutCommand"; + +export default abstract class ProtocolPrintoutCommandHandler { + /** + * @description create protocolPrintout + * @param {number} + * @returns {Promise} + */ + static async create(printout: CreateProtocolPrintoutCommand): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(protocolPrintout) + .values({ + title: printout.title, + iteration: printout.iteration, + filename: printout.filename, + protocolId: printout.protocolId, + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new InternalException("Failed creating protocol", err); + }); + } +} diff --git a/src/controller/admin/protocolController.ts b/src/controller/admin/protocolController.ts index f936274..d6f0310 100644 --- a/src/controller/admin/protocolController.ts +++ b/src/controller/admin/protocolController.ts @@ -23,6 +23,10 @@ import { SynchronizeProtocolVotingCommand } from "../../command/protocolVotingCo import { ProtocolVotingViewModel } from "../../viewmodel/admin/protocolVoting.models"; import ProtocolVotingCommandHandler from "../../command/protocolVotingCommandHandler"; import { PdfExport } from "../../helpers/pdfExport"; +import ProtocolPrintoutService from "../../service/protocolPrintoutService"; +import ProtocolPrintoutFactory from "../../factory/admin/protocolPrintout"; +import { CreateProtocolPrintoutCommand } from "../../command/protocolPrintoutCommand"; +import ProtocolPrintoutCommandHandler from "../../command/protocolPrintoutCommandHandler"; /** * @description get all protocols @@ -112,6 +116,35 @@ export async function getProtocolVotingsById(req: Request, res: Response): Promi res.json(ProtocolVotingFactory.mapToBase(votings)); } +/** + * @description get protocol printouts by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolPrintoutsById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + + let printouts = await ProtocolPrintoutService.getAll(protocolId); + + res.json(ProtocolPrintoutFactory.mapToBase(printouts)); +} + +/** + * @description get protocol printout by id and print + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getProtocolPrintoutByIdAndPrint(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let printoutId = parseInt(req.params.printoutId); + + let printouts = await ProtocolPrintoutService.getById(printoutId, protocolId); + + res.json(ProtocolPrintoutFactory.mapToSingle(printouts)); +} + /** * @description create protocol * @param req {Request} Express req object @@ -173,6 +206,69 @@ export async function createProtocolVotingsById(req: Request, res: Response): Pr res.send(voting); } +/** + * @description create protocol printout by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createProtocolPrintoutById(req: Request, res: Response): Promise { + let protocolId = parseInt(req.params.protocolId); + let protocol = await ProtocolService.getById(protocolId); + let agenda = await ProtocolAgendaService.getAll(protocolId); + let decisions = await ProtocolDecisionService.getAll(protocolId); + let presence = await ProtocolPresenceService.getAll(protocolId); + let votings = await ProtocolVotingService.getAll(protocolId); + let iteration = await ProtocolPrintoutService.getCount(protocolId); + + let title = `Sitzungsprotokoll - ${new Date(protocol.date).toLocaleDateString("de-DE", { + day: "2-digit", + month: "long", + year: "numeric", + })}`; + + let filename = `P_${protocol.title.replace(/[^a-zA-Z0-9]/g, "")}_${iteration + 1}_${new Date().toLocaleDateString()}`; + + await PdfExport.renderFile({ + template: "protocol.template.html", + title, + filename, + data: { + title: protocol.title, + summary: protocol.summary, + iteration: iteration + 1, + date: new Date(protocol.date).toLocaleDateString("de-DE", { + weekday: "long", + day: "2-digit", + month: "2-digit", + year: "numeric", + }), + start: new Date(protocol.starttime).toLocaleTimeString("de-DE", { + minute: "2-digit", + hour: "2-digit", + }), + end: new Date(protocol.endtime).toLocaleTimeString("de-DE", { + minute: "2-digit", + hour: "2-digit", + }), + agenda, + decisions, + presence: presence.map((p) => p.member), + votings, + }, + }); + + let printout: CreateProtocolPrintoutCommand = { + title, + iteration: iteration + 1, + filename, + protocolId, + }; + await ProtocolPrintoutCommandHandler.create(printout); + + res.sendStatus(204); +} + /** * @description synchronize protocol by id * @param req {Request} Express req object @@ -290,47 +386,3 @@ export async function synchronizeProtocolPrecenseById(req: Request, res: Respons res.sendStatus(204); } - -/** - * @description render protocol to file by id - * @param req {Request} Express req object - * @param res {Response} Express res object - * @returns {Promise<*>} - */ -export async function printPdf(req: Request, res: Response): Promise { - let protocolId = parseInt(req.params.protocolId); - let protocol = await ProtocolService.getById(protocolId); - let agenda = await ProtocolAgendaService.getAll(protocolId); - let decisions = await ProtocolDecisionService.getAll(protocolId); - let presence = await ProtocolPresenceService.getAll(protocolId); - let votings = await ProtocolVotingService.getAll(protocolId); - - await PdfExport.renderFile({ - template: "protocol.template.html", - title: protocol.title, - data: { - title: protocol.title, - summary: protocol.summary, - date: new Date(protocol.date).toLocaleDateString("de-DE", { - weekday: "long", - day: "2-digit", - month: "2-digit", - year: "numeric", - }), - start: new Date(protocol.starttime).toLocaleTimeString("de-DE", { - minute: "2-digit", - hour: "2-digit", - }), - end: new Date(protocol.endtime).toLocaleTimeString("de-DE", { - minute: "2-digit", - hour: "2-digit", - }), - agenda, - decisions, - presence: presence.map((p) => p.member), - votings, - }, - }); - - res.sendStatus(204); -} diff --git a/src/data-source.ts b/src/data-source.ts index 5a1c1f3..10b2991 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -39,6 +39,8 @@ import { protocolVoting } from "./entity/protocolVoting"; import { ProtocolTables1728563204766 } from "./migrations/1728563204766-protocolTables"; import { ProtocolTableRename1728645611919 } from "./migrations/1728645611919-protocolTableRename"; import { ProtocolTableTypes1728999487170 } from "./migrations/1728999487170-protocolTableTypes"; +import { protocolPrintout } from "./entity/protocolPrintout"; +import { ProtocolPrintout1729344771434 } from "./migrations/1729344771434-protocolPrintout"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -73,6 +75,7 @@ const dataSource = new DataSource({ protocolDecision, protocolPresence, protocolVoting, + protocolPrintout, ], migrations: [ Initial1724317398939, @@ -88,6 +91,7 @@ const dataSource = new DataSource({ ProtocolTables1728563204766, ProtocolTableRename1728645611919, ProtocolTableTypes1728999487170, + ProtocolPrintout1729344771434, ], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/protocolPrintout.ts b/src/entity/protocolPrintout.ts new file mode 100644 index 0000000..311b407 --- /dev/null +++ b/src/entity/protocolPrintout.ts @@ -0,0 +1,30 @@ +import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm"; +import { protocol } from "./protocol"; + +@Entity() +export class protocolPrintout { + @PrimaryGeneratedColumn("increment") + id: number; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "int" }) + iteration: number; + + @Column({ type: "varchar", length: 255 }) + filename: string; + + @CreateDateColumn() + createdAt: Date; + + @Column() + protocolId: number; + + @ManyToOne(() => protocol, { + nullable: false, + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + protocol: protocol; +} diff --git a/src/factory/admin/protocolPrintout.ts b/src/factory/admin/protocolPrintout.ts new file mode 100644 index 0000000..a93fad1 --- /dev/null +++ b/src/factory/admin/protocolPrintout.ts @@ -0,0 +1,28 @@ +import { protocolPrintout } from "../../entity/protocolPrintout"; +import { ProtocolPrintoutViewModel } from "../../viewmodel/admin/protocolPrintout.models"; + +export default abstract class ProtocolPrintoutFactory { + /** + * @description map record to protocolPrintout + * @param {protocol} record + * @returns {ProtocolPrintoutViewModel} + */ + public static mapToSingle(record: protocolPrintout): ProtocolPrintoutViewModel { + return { + id: record.id, + title: record.title, + iteration: record.iteration, + createdAt: record.createdAt, + protocolId: record.protocolId, + }; + } + + /** + * @description map records to protocolPrintout + * @param {Array} records + * @returns {Array} + */ + public static mapToBase(records: Array): Array { + return records.map((r) => this.mapToSingle(r)); + } +} diff --git a/src/helpers/pdfExport.ts b/src/helpers/pdfExport.ts index 4048f5e..353d8dd 100644 --- a/src/helpers/pdfExport.ts +++ b/src/helpers/pdfExport.ts @@ -22,11 +22,21 @@ export abstract class PdfExport { return readFileSync(process.cwd() + "/src/templates/" + template, "utf8"); } - static async renderFile({ template, title, data }: { template: string; title: string; data: any }) { + static async renderFile({ + template, + title, + filename, + data, + }: { + template: string; + title: string; + filename: string; + data: any; + }) { let document = { html: this.getTemplate(template), data, - path: process.cwd() + `/export/${title}.pdf`, + path: process.cwd() + `/export/${filename}.pdf`, }; await pdf.create(document, options(title)); diff --git a/src/migrations/1729344771434-protocolPrintout.ts b/src/migrations/1729344771434-protocolPrintout.ts new file mode 100644 index 0000000..25c50fe --- /dev/null +++ b/src/migrations/1729344771434-protocolPrintout.ts @@ -0,0 +1,44 @@ +import { MigrationInterface, QueryRunner, Table, TableForeignKey } from "typeorm"; +import { DB_TYPE } from "../env.defaults"; + +export class ProtocolPrintout1729344771434 implements MigrationInterface { + name = "ProtocolPrintout1729344771434"; + + public async up(queryRunner: QueryRunner): Promise { + const variableType_int = DB_TYPE == "mysql" ? "int" : "integer"; + + await queryRunner.createTable( + new Table({ + name: "protocol_printout", + columns: [ + { name: "id", type: variableType_int, isPrimary: true, isGenerated: true, generationStrategy: "increment" }, + { name: "title", type: "varchar", length: "255", isNullable: false }, + { name: "iteration", type: variableType_int, default: 1, isNullable: false }, + { name: "filename", type: "varchar", length: "255", isNullable: false }, + { name: "createdAt", type: "datetime(6)", isNullable: false, default: "CURRENT_TIMESTAMP(6)" }, + { name: "protocolId", type: variableType_int, isNullable: false }, + ], + }), + true + ); + + await queryRunner.createForeignKey( + "protocol_printout", + new TableForeignKey({ + columnNames: ["protocolId"], + referencedColumnNames: ["id"], + referencedTableName: "protocol", + onDelete: "CASCADE", + onUpdate: "RESTRICT", + }) + ); + } + + public async down(queryRunner: QueryRunner): Promise { + const table = await queryRunner.getTable("protocol_printout"); + const foreignKey = table.foreignKeys.find((fk) => fk.columnNames.indexOf("protocolId") !== -1); + await queryRunner.dropForeignKey("protocol_printout", foreignKey); + + await queryRunner.dropTable("protocol_printout"); + } +} diff --git a/src/routes/admin/protocol.ts b/src/routes/admin/protocol.ts index 4b07da7..7cc29a4 100644 --- a/src/routes/admin/protocol.ts +++ b/src/routes/admin/protocol.ts @@ -3,14 +3,16 @@ import { createProtocol, createProtocolAgendaById, createProtocolDecisonsById, + createProtocolPrintoutById, createProtocolVotingsById, getAllProtocols, getProtocolAgendaById, getProtocolById, getProtocolDecisonsById, getProtocolPrecenseById, + getProtocolPrintoutByIdAndPrint, + getProtocolPrintoutsById, getProtocolVotingsById, - printPdf, synchronizeProtocolAgendaById, synchronizeProtocolById, synchronizeProtocolDecisonsById, @@ -44,6 +46,14 @@ router.get("/:protocolId/votings", async (req: Request, res: Response) => { await getProtocolVotingsById(req, res); }); +router.get("/:protocolId/printouts", async (req: Request, res: Response) => { + await getProtocolPrintoutsById(req, res); +}); + +router.get("/:protocolId/printout/:printoutId", async (req: Request, res: Response) => { + await getProtocolPrintoutByIdAndPrint(req, res); +}); + router.post("/", async (req: Request, res: Response) => { await createProtocol(req, res); }); @@ -60,8 +70,8 @@ router.post("/:protocolId/voting", async (req: Request, res: Response) => { await createProtocolVotingsById(req, res); }); -router.post("/:protocolId/render", async (req: Request, res: Response) => { - await printPdf(req, res); +router.post("/:protocolId/printout", async (req: Request, res: Response) => { + await createProtocolPrintoutById(req, res); }); router.patch("/:id/synchronize", async (req: Request, res: Response) => { diff --git a/src/service/protocolPrintoutService.ts b/src/service/protocolPrintoutService.ts new file mode 100644 index 0000000..aaf39ed --- /dev/null +++ b/src/service/protocolPrintoutService.ts @@ -0,0 +1,60 @@ +import { dataSource } from "../data-source"; +import { protocolPrintout } from "../entity/protocolPrintout"; +import InternalException from "../exceptions/internalException"; + +export default abstract class ProtocolPrintoutService { + /** + * @description get all protocolPrintouts + * @returns {Promise>} + */ + static async getAll(protocolId: number): Promise> { + return await dataSource + .getRepository(protocolPrintout) + .createQueryBuilder("protocolPrintout") + .where("protocolPrintout.protocolId = :protocolId", { protocolId }) + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolPrintouts not found", err); + }); + } + + /** + * @description get protocolPrintout by id + * @returns {Promise} + */ + static async getById(id: number, protocolId: number): Promise { + return await dataSource + .getRepository(protocolPrintout) + .createQueryBuilder("protocolPrintout") + .where("protocolPrintout.protocolId = :protocolId", { protocolId }) + .andWhere("protocolPrintout.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolPrintout not found by id", err); + }); + } + + /** + * @description get count of printouts by id + * @returns {Promise} + */ + static async getCount(protocolId: number): Promise { + return await dataSource + .getRepository(protocolPrintout) + .createQueryBuilder("protocolPrintout") + .where("protocolPrintout.protocolId = :protocolId", { protocolId }) + .getCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new InternalException("protocolPrintout not found by id", err); + }); + } +} diff --git a/src/templates/protocol.template.html b/src/templates/protocol.template.html index e5e22b3..4a49c61 100644 --- a/src/templates/protocol.template.html +++ b/src/templates/protocol.template.html @@ -7,7 +7,9 @@

{{title}}

Am {{date}} von {{start}} Uhr bis {{end}} Uhr

-

{{sumary}}

+

Ausdruck Nr {{iteration}}

+
+

{{{summary}}}

Anwesenheit