From 7e96b6ca0ca412b220660d2d47b8c29386cbc4eb Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Thu, 20 Feb 2025 12:01:02 +0100 Subject: [PATCH] mission base CRUD --- .../equipment/equipmentCommandHandler.ts | 4 +- .../force/forceCommandHandler.ts | 4 +- .../vehicle/vehicleCommandHandler.ts | 4 +- .../operation/mission/missionCommand.ts | 9 ++ .../mission/missionCommandHandler.ts | 66 ++++++++++++++ .../admin/operation/missionController.ts | 91 +++++++++++++++++++ src/data-source.ts | 3 +- src/entity/operation/mission.ts | 16 ++++ src/factory/admin/operation/mission.ts | 27 ++++++ src/migrations/1739697068682-CreateSchema.ts | 5 + src/migrations/baseSchemaTables/operation.ts | 12 +++ src/routes/admin/index.ts | 4 + src/routes/admin/operation/mission.ts | 45 +++++++++ src/service/operation/missionService.ts | 54 +++++++++++ .../admin/operation/mission.models.ts | 6 ++ 15 files changed, 343 insertions(+), 7 deletions(-) create mode 100644 src/command/operation/mission/missionCommand.ts create mode 100644 src/command/operation/mission/missionCommandHandler.ts create mode 100644 src/controller/admin/operation/missionController.ts create mode 100644 src/entity/operation/mission.ts create mode 100644 src/factory/admin/operation/mission.ts create mode 100644 src/migrations/baseSchemaTables/operation.ts create mode 100644 src/routes/admin/operation/mission.ts create mode 100644 src/service/operation/missionService.ts create mode 100644 src/viewmodel/admin/operation/mission.models.ts diff --git a/src/command/configuration/equipment/equipmentCommandHandler.ts b/src/command/configuration/equipment/equipmentCommandHandler.ts index 8e6bda2..96619dc 100644 --- a/src/command/configuration/equipment/equipmentCommandHandler.ts +++ b/src/command/configuration/equipment/equipmentCommandHandler.ts @@ -7,9 +7,9 @@ export default abstract class EquipmentCommandHandler { /** * @description create equipment * @param {CreateEquipmentCommand} createEquipment - * @returns {Promise} + * @returns {Promise} */ - static async create(createEquipment: CreateEquipmentCommand): Promise { + static async create(createEquipment: CreateEquipmentCommand): Promise { return await dataSource .createQueryBuilder() .insert() diff --git a/src/command/configuration/force/forceCommandHandler.ts b/src/command/configuration/force/forceCommandHandler.ts index fff8989..d789d89 100644 --- a/src/command/configuration/force/forceCommandHandler.ts +++ b/src/command/configuration/force/forceCommandHandler.ts @@ -7,9 +7,9 @@ export default abstract class ForceCommandHandler { /** * @description create force * @param {CreateForceCommand} createForce - * @returns {Promise} + * @returns {Promise} */ - static async create(createForce: CreateForceCommand): Promise { + static async create(createForce: CreateForceCommand): Promise { return await dataSource .createQueryBuilder() .insert() diff --git a/src/command/configuration/vehicle/vehicleCommandHandler.ts b/src/command/configuration/vehicle/vehicleCommandHandler.ts index 7922a38..d69f85d 100644 --- a/src/command/configuration/vehicle/vehicleCommandHandler.ts +++ b/src/command/configuration/vehicle/vehicleCommandHandler.ts @@ -7,9 +7,9 @@ export default abstract class VehicleCommandHandler { /** * @description create vehicle * @param {CreateVehicleCommand} createVehicle - * @returns {Promise} + * @returns {Promise} */ - static async create(createVehicle: CreateVehicleCommand): Promise { + static async create(createVehicle: CreateVehicleCommand): Promise { return await dataSource .createQueryBuilder() .insert() diff --git a/src/command/operation/mission/missionCommand.ts b/src/command/operation/mission/missionCommand.ts new file mode 100644 index 0000000..c26d0f9 --- /dev/null +++ b/src/command/operation/mission/missionCommand.ts @@ -0,0 +1,9 @@ +export interface UpdateMissionCommand { + id: string; + title: string; + keyword?: string; +} + +export interface DeleteMissionCommand { + id: string; +} diff --git a/src/command/operation/mission/missionCommandHandler.ts b/src/command/operation/mission/missionCommandHandler.ts new file mode 100644 index 0000000..281f44f --- /dev/null +++ b/src/command/operation/mission/missionCommandHandler.ts @@ -0,0 +1,66 @@ +import { dataSource } from "../../../data-source"; +import { mission } from "../../../entity/operation/mission"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { DeleteMissionCommand, UpdateMissionCommand } from "./missionCommand"; + +export default abstract class MissionCommandHandler { + /** + * @description create mission + * @returns {Promise} + */ + static async create(): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(mission) + .values({ + title: "", + }) + .execute() + .then((result) => { + return result.identifiers[0].id; + }) + .catch((err) => { + throw new DatabaseActionException("CREATE", "mission", err); + }); + } + + /** + * @description update mission + * @param {UpdateMissionCommand} updateMission + * @returns {Promise} + */ + static async update(updateMission: UpdateMissionCommand): Promise { + return await dataSource + .createQueryBuilder() + .update(mission) + .set({ + title: updateMission.title, + keyword: updateMission.keyword, + }) + .where("id = :id", { id: updateMission.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "mission", err); + }); + } + + /** + * @description delete mission + * @param {DeleteMissionCommand} deleteMission + * @returns {Promise} + */ + static async delete(deleteMission: DeleteMissionCommand): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(mission) + .where("id = :id", { id: deleteMission.id }) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "mission", err); + }); + } +} diff --git a/src/controller/admin/operation/missionController.ts b/src/controller/admin/operation/missionController.ts new file mode 100644 index 0000000..e89d7f2 --- /dev/null +++ b/src/controller/admin/operation/missionController.ts @@ -0,0 +1,91 @@ +import { Request, Response } from "express"; +import MissionService from "../../../service/operation/missionService"; +import MissionFactory from "../../../factory/admin/operation/mission"; +import { DeleteMissionCommand, UpdateMissionCommand } from "../../../command/operation/mission/missionCommand"; +import MissionCommandHandler from "../../../command/operation/mission/missionCommandHandler"; + +/** + * @description get all missions + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getAllMissions(req: Request, res: Response): Promise { + let offset = parseInt((req.query.offset as string) ?? "0"); + let count = parseInt((req.query.count as string) ?? "25"); + let noLimit = req.query.noLimit === "true"; + + let [missions, total] = await MissionService.getAll({ offset, count, noLimit }); + + res.json({ + missions: MissionFactory.mapToBaseShort(missions), + total: total, + offset: offset, + count: count, + }); +} + +/** + * @description get mission by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function getMissionById(req: Request, res: Response): Promise { + const missionId = req.params.id; + let mission = await MissionService.getById(missionId); + + res.json(MissionFactory.mapToSingleShort(mission)); +} + +/** + * @description create mission + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createMission(req: Request, res: Response): Promise { + let missionId = await MissionCommandHandler.create(); + + // TODO: push notification to clients that new mission was created + + res.status(200).send(missionId); +} + +/** + * @description update mission by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function updateMissionById(req: Request, res: Response): Promise { + const missionId = req.params.id; + const title = req.body.code; + const keyword = req.body.type || null; + + let updateMission: UpdateMissionCommand = { + id: missionId, + title, + keyword, + }; + await MissionCommandHandler.update(updateMission); + + res.sendStatus(204); +} + +/** + * @description delete mission by id + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function deleteMissionById(req: Request, res: Response): Promise { + const missionId = req.params.id; + + let deleteMission: DeleteMissionCommand = { + id: missionId, + }; + await MissionCommandHandler.delete(deleteMission); + + res.sendStatus(204); +} diff --git a/src/data-source.ts b/src/data-source.ts index c82df98..41128fa 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -15,6 +15,7 @@ import { reset } from "./entity/reset"; import { CreateSchema1739697068682 } from "./migrations/1739697068682-CreateSchema"; import { vehicle } from "./entity/configuration/vehicle"; import { equipment } from "./entity/configuration/equipment"; +import { mission } from "./entity/operation/mission"; const dataSource = new DataSource({ type: DB_TYPE as any, @@ -26,7 +27,7 @@ const dataSource = new DataSource({ synchronize: false, logging: process.env.NODE_ENV ? true : ["schema", "error", "warn", "log", "migration"], bigNumberStrings: false, - entities: [user, refresh, invite, reset, userPermission, role, rolePermission, force, vehicle, equipment], + entities: [user, refresh, invite, reset, userPermission, role, rolePermission, force, vehicle, equipment, mission], migrations: [CreateSchema1739697068682], migrationsRun: true, migrationsTransactionMode: "each", diff --git a/src/entity/operation/mission.ts b/src/entity/operation/mission.ts new file mode 100644 index 0000000..ee5be2c --- /dev/null +++ b/src/entity/operation/mission.ts @@ -0,0 +1,16 @@ +import { Column, CreateDateColumn, Entity, PrimaryColumn } from "typeorm"; + +@Entity() +export class mission { + @PrimaryColumn({ generated: "uuid" }) + id: string; + + @Column({ type: "varchar", length: 255 }) + title: string; + + @Column({ type: "varchar", length: 255, nullable: true }) + keyword?: string; + + @CreateDateColumn() + createdAt: Date; +} diff --git a/src/factory/admin/operation/mission.ts b/src/factory/admin/operation/mission.ts new file mode 100644 index 0000000..c723cfc --- /dev/null +++ b/src/factory/admin/operation/mission.ts @@ -0,0 +1,27 @@ +import { mission } from "../../../entity/operation/mission"; +import { MissionShortViewModel } from "../../../viewmodel/admin/operation/mission.models"; + +export default abstract class MissionFactory { + /** + * @description map record to mission + * @param {mission} record + * @returns {MissionShortViewModel} + */ + public static mapToSingleShort(record: mission): MissionShortViewModel { + return { + id: record.id, + title: record.title, + keyword: record.keyword, + createdAt: record.createdAt, + }; + } + + /** + * @description map records to mission + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseShort(records: Array): Array { + return records.map((r) => this.mapToSingleShort(r)); + } +} diff --git a/src/migrations/1739697068682-CreateSchema.ts b/src/migrations/1739697068682-CreateSchema.ts index f23f746..8afca2e 100644 --- a/src/migrations/1739697068682-CreateSchema.ts +++ b/src/migrations/1739697068682-CreateSchema.ts @@ -10,6 +10,7 @@ import { user_table, } from "./baseSchemaTables/admin"; import { equipment_table, force_table, vehicle_table } from "./baseSchemaTables/configuration"; +import { mission_table } from "./baseSchemaTables/operation"; export class CreateSchema1739697068682 implements MigrationInterface { name = "CreateSchema1739697068682"; @@ -27,9 +28,13 @@ export class CreateSchema1739697068682 implements MigrationInterface { await queryRunner.createTable(force_table, true, true, true); await queryRunner.createTable(equipment_table, true, true, true); await queryRunner.createTable(vehicle_table, true, true, true); + + await queryRunner.createTable(mission_table, true, true, true); } public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable("mission", true, true, true); + await queryRunner.dropTable("vehicle", true, true, true); await queryRunner.dropTable("equipment", true, true, true); await queryRunner.dropTable("force", true, true, true); diff --git a/src/migrations/baseSchemaTables/operation.ts b/src/migrations/baseSchemaTables/operation.ts new file mode 100644 index 0000000..e2f3c81 --- /dev/null +++ b/src/migrations/baseSchemaTables/operation.ts @@ -0,0 +1,12 @@ +import { Table } from "typeorm"; +import { getDefaultByORM, getTypeByORM, isUUIDPrimary } from "../ormHelper"; + +export const mission_table = new Table({ + name: "mission", + columns: [ + { name: "id", ...getTypeByORM("uuid"), ...isUUIDPrimary }, + { name: "title", ...getTypeByORM("varchar") }, + { name: "keyword", ...getTypeByORM("varchar", true), default: getDefaultByORM("null") }, + { name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp") }, + ], +}); diff --git a/src/routes/admin/index.ts b/src/routes/admin/index.ts index 3156c22..4a6bedd 100644 --- a/src/routes/admin/index.ts +++ b/src/routes/admin/index.ts @@ -1,6 +1,8 @@ import express from "express"; import PermissionHelper from "../../helpers/permissionHelper"; +import mission from "./operation/mission"; + import equipment from "./configuration/equipment"; import force from "./configuration/force"; import vehicle from "./configuration/vehicle"; @@ -12,6 +14,8 @@ import backup from "./management/backup"; var router = express.Router({ mergeParams: true }); +router.use("/mission", PermissionHelper.passCheckMiddleware("read", "operation", "mission"), mission); + router.use("/equipment", PermissionHelper.passCheckMiddleware("read", "configuration", "equipment"), equipment); router.use("/force", PermissionHelper.passCheckMiddleware("read", "configuration", "force"), force); router.use("/vehicle", PermissionHelper.passCheckMiddleware("read", "configuration", "vehicle"), vehicle); diff --git a/src/routes/admin/operation/mission.ts b/src/routes/admin/operation/mission.ts new file mode 100644 index 0000000..eb4748e --- /dev/null +++ b/src/routes/admin/operation/mission.ts @@ -0,0 +1,45 @@ +import express, { Request, Response } from "express"; +import { + createMission, + deleteMissionById, + getAllMissions, + getMissionById, + updateMissionById, +} from "../../../controller/admin/operation/missionController"; +import PermissionHelper from "../../../helpers/permissionHelper"; + +var router = express.Router({ mergeParams: true }); + +router.get("/", async (req: Request, res: Response) => { + await getAllMissions(req, res); +}); + +router.get("/:id", async (req: Request, res: Response) => { + await getMissionById(req, res); +}); + +router.post( + "/", + PermissionHelper.passCheckMiddleware("create", "operation", "mission"), + async (req: Request, res: Response) => { + await createMission(req, res); + } +); + +router.patch( + "/:id", + PermissionHelper.passCheckMiddleware("update", "operation", "mission"), + async (req: Request, res: Response) => { + await updateMissionById(req, res); + } +); + +router.delete( + "/:id", + PermissionHelper.passCheckMiddleware("delete", "operation", "mission"), + async (req: Request, res: Response) => { + await deleteMissionById(req, res); + } +); + +export default router; diff --git a/src/service/operation/missionService.ts b/src/service/operation/missionService.ts new file mode 100644 index 0000000..54724fe --- /dev/null +++ b/src/service/operation/missionService.ts @@ -0,0 +1,54 @@ +import { dataSource } from "../../data-source"; +import { mission } from "../../entity/operation/mission"; +import DatabaseActionException from "../../exceptions/databaseActionException"; + +export default abstract class MissionService { + /** + * @description get all missions + * @returns {Promise<[Array, number]>} + */ + static async getAll({ + offset = 0, + count = 25, + noLimit = false, + }: { + offset?: number; + count?: number; + noLimit?: boolean; + }): Promise<[Array, number]> { + let query = dataSource.getRepository(mission).createQueryBuilder("mission"); + + if (!noLimit) { + query = query.offset(offset).limit(count); + } + + return await query + .orderBy("mission.createdAt") + .getManyAndCount() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "mission", err); + }); + } + + /** + * @description get mission by id + * @param {string} id + * @returns {Promise} + */ + static async getById(id: string): Promise { + return dataSource + .getRepository(mission) + .createQueryBuilder("mission") + .where("mission.id = :id", { id: id }) + .getOneOrFail() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "mission", err); + }); + } +} diff --git a/src/viewmodel/admin/operation/mission.models.ts b/src/viewmodel/admin/operation/mission.models.ts new file mode 100644 index 0000000..60372e4 --- /dev/null +++ b/src/viewmodel/admin/operation/mission.models.ts @@ -0,0 +1,6 @@ +export interface MissionShortViewModel { + id: string; + title: string; + keyword?: string; + createdAt: Date; +}