From 41c3093754b469385dd7a99c6969693a8f160e33 Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Wed, 16 Jul 2025 12:24:50 +0200 Subject: [PATCH] enable public report --- src/command/unit/damageReportCommand.ts | 8 ++- .../unit/damageReportCommandHandler.ts | 5 +- .../admin/unit/damageReportController.ts | 7 +- src/controller/publicController.ts | 68 ++++++++++++++++++- src/entity/unit/damageReport.ts | 22 +++++- src/factory/admin/unit/damageReport.ts | 2 +- src/factory/admin/unit/equipment/equipment.ts | 29 +++++++- src/factory/admin/unit/vehicle/vehicle.ts | 26 ++++++- src/factory/admin/unit/wearable/wearable.ts | 29 +++++++- src/helpers/settingsHelper.ts | 2 - src/middleware/multer.ts | 26 +++++++ .../baseSchemaTables/unit_extend.ts | 2 + src/routes/public.ts | 11 ++- .../unit/equipment/equipmentService.ts | 20 ++++++ src/service/unit/vehicle/vehicleService.ts | 20 ++++++ src/service/unit/wearable/wearableService.ts | 20 ++++++ src/type/settingTypes.ts | 3 + .../admin/unit/damageReport.models.ts | 2 +- .../admin/unit/equipment/equipment.models.ts | 8 +++ .../admin/unit/vehicle/vehicle.models.ts | 8 +++ .../admin/unit/wearable/wearable.models.ts | 8 +++ 21 files changed, 307 insertions(+), 19 deletions(-) diff --git a/src/command/unit/damageReportCommand.ts b/src/command/unit/damageReportCommand.ts index 62a4773..c1a5538 100644 --- a/src/command/unit/damageReportCommand.ts +++ b/src/command/unit/damageReportCommand.ts @@ -1,9 +1,11 @@ export interface CreateDamageReportCommand { description: string; + location: string; + note: string; reportedBy: string; - imageCount: number; - affectedId: string; - affected: "equipment" | "vehicle" | "wearable"; + images: string[]; + affectedId?: string; + affected?: "equipment" | "vehicle" | "wearable"; } export interface UpdateDamageReportCommand { diff --git a/src/command/unit/damageReportCommandHandler.ts b/src/command/unit/damageReportCommandHandler.ts index 71a72fa..248d7c9 100644 --- a/src/command/unit/damageReportCommandHandler.ts +++ b/src/command/unit/damageReportCommandHandler.ts @@ -22,8 +22,10 @@ export default abstract class DamageReportCommandHandler { .values({ status: "eingereicht", description: createDamageReport.description, + location: createDamageReport.location, + note: createDamageReport.note, reportedBy: createDamageReport.reportedBy, - imageCount: createDamageReport.imageCount, + images: createDamageReport.images, equipmentId: createDamageReport.affected == "equipment" ? createDamageReport.affectedId : null, vehicleId: createDamageReport.affected == "vehicle" ? createDamageReport.affectedId : null, wearableId: createDamageReport.affected == "wearable" ? createDamageReport.affectedId : null, @@ -86,6 +88,7 @@ export default abstract class DamageReportCommandHandler { * @returns {Promise} */ static async delete(deleteDamageReport: DeleteDamageReportCommand): Promise { + // TODO: remove related images return await dataSource .createQueryBuilder() .delete() diff --git a/src/controller/admin/unit/damageReportController.ts b/src/controller/admin/unit/damageReportController.ts index 443a6cc..88c78d4 100644 --- a/src/controller/admin/unit/damageReportController.ts +++ b/src/controller/admin/unit/damageReportController.ts @@ -79,7 +79,10 @@ export async function getDamageReportById(req: Request, res: Response): Promise< */ export async function createDamageReport(req: Request, res: Response): Promise { const description = req.body.description; + const location = req.body.location; + const note = req.body.note; const reportedBy = req.body.reportedBy; + const images = req.files as Express.Multer.File[]; const affectedId = req.body.affectedId; const affected = req.body.affected; @@ -88,8 +91,10 @@ export async function createDamageReport(req: Request, res: Response): Promise i.filename), affectedId, affected, }; diff --git a/src/controller/publicController.ts b/src/controller/publicController.ts index 943aa73..e385307 100644 --- a/src/controller/publicController.ts +++ b/src/controller/publicController.ts @@ -12,6 +12,17 @@ import { FileSystemHelper } from "../helpers/fileSystemHelper"; import { SocketConnectionTypes } from "../enums/socketEnum"; import SocketServer from "../websocket"; import BadRequestException from "../exceptions/badRequestException"; +import EquipmentService from "../service/unit/equipment/equipmentService"; +import VehicleService from "../service/unit/vehicle/vehicleService"; +import WearableService from "../service/unit/wearable/wearableService"; +import EquipmentFactory from "../factory/admin/unit/equipment/equipment"; +import VehicleFactory from "../factory/admin/unit/vehicle/vehicle"; +import WearableFactory from "../factory/admin/unit/wearable/wearable"; +import { MinifiedEquipmentViewModel } from "../viewmodel/admin/unit/equipment/equipment.models"; +import { MinifiedVehicleViewModel } from "../viewmodel/admin/unit/vehicle/vehicle.models"; +import { MinifiedWearableViewModel } from "../viewmodel/admin/unit/wearable/wearable.models"; +import DamageReportCommandHandler from "../command/unit/damageReportCommandHandler"; +import { CreateDamageReportCommand } from "../command/unit/damageReportCommand"; /** * @description get all calendar items by types or nscdr @@ -58,8 +69,8 @@ export async function getCalendarItemsByTypes(req: Request, res: Response): Prom } /** - * @description get all calendar items by types or nscdr - * @summary passphrase is passed as value pair like `type:passphrase` + * @description check if scanner session exists + * @summary existance is checked by room exists * @param req {Request} Express req object * @param res {Response} Express res object * @returns {Promise<*>} @@ -77,6 +88,58 @@ export async function checkScannerRoomExists(req: Request, res: Response): Promi } } +/** + * @description get equipment, vehicle, wearable by code + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function searchStuffByCode(req: Request, res: Response): Promise { + let code = req.query.code.toString(); + + let e = await EquipmentService.getAllByCode(code); + let v = await VehicleService.getAllByCode(code); + let w = await WearableService.getAllByCode(code); + + res.json([ + ...EquipmentFactory.mapToBaseMinifed(e), + ...VehicleFactory.mapToBaseMinifed(v), + ...WearableFactory.mapToBaseMinifed(w), + ]); +} + +/** + * @description create damagereport to equipment, vehicle, wearable + * @param req {Request} Express req object + * @param res {Response} Express res object + * @returns {Promise<*>} + */ +export async function createDamageReport(req: Request, res: Response): Promise { + const related = (req.body.related ? JSON.parse(req.body.related) : undefined) as + | undefined + | MinifiedEquipmentViewModel + | MinifiedVehicleViewModel + | MinifiedWearableViewModel; + const description = req.body.description; + const location = req.body.location; + const note = req.body.note; + const reportedBy = req.body.reportedBy; + const images = req.files as Express.Multer.File[]; + + let createDamageReport: CreateDamageReportCommand = { + description: description, + location: location, + note: note, + reportedBy: reportedBy, + images: images.map((i) => i.filename), + affectedId: related ? related.id : undefined, + affected: related ? related.assigned : undefined, + }; + await DamageReportCommandHandler.create(createDamageReport); + + res.sendStatus(204); +} + /** * @description get configuration of UI * @param req {Request} Express req object @@ -91,6 +154,7 @@ export async function getApplicationConfig(req: Request, res: Response): Promise "club.website": SettingHelper.getSetting("club.website"), "app.custom_login_message": SettingHelper.getSetting("app.custom_login_message"), "app.show_link_to_calendar": SettingHelper.getSetting("app.show_link_to_calendar"), + "app.show_link_to_damagereport": SettingHelper.getSetting("app.show_link_to_damagereport"), }; res.json(config); diff --git a/src/entity/unit/damageReport.ts b/src/entity/unit/damageReport.ts index 0a4fd7e..c401bf2 100644 --- a/src/entity/unit/damageReport.ts +++ b/src/entity/unit/damageReport.ts @@ -21,12 +21,28 @@ export class damageReport { @Column({ type: "text" }) description: string; + @Column({ type: "text" }) + location: string; + + @Column({ type: "text" }) + note: string; + + @Column({ + type: "text", + transformer: { + from(value: string): Array { + return value.split(","); + }, + to(value: Array): string { + return value.join(","); + }, + }, + }) + images: string[]; + @Column({ type: "varchar", length: 255 }) reportedBy: string; - @Column({ type: "int", default: 0 }) - imageCount: number; - @Column({ nullable: true, default: null }) equipmentId?: string; diff --git a/src/factory/admin/unit/damageReport.ts b/src/factory/admin/unit/damageReport.ts index fb9115e..9c7cdb4 100644 --- a/src/factory/admin/unit/damageReport.ts +++ b/src/factory/admin/unit/damageReport.ts @@ -39,7 +39,7 @@ export default abstract class DamageReportFactory { status: record.status, done: record.done, description: record.description, - imageCount: record.imageCount, + image: record.images, reportedBy: record?.reportedBy, ...assigned, maintenance: record.maintenance ? MaintenanceFactory.mapToSingle(record.maintenance) : null, diff --git a/src/factory/admin/unit/equipment/equipment.ts b/src/factory/admin/unit/equipment/equipment.ts index 8f0b520..a35ebc1 100644 --- a/src/factory/admin/unit/equipment/equipment.ts +++ b/src/factory/admin/unit/equipment/equipment.ts @@ -1,5 +1,8 @@ import { equipment } from "../../../../entity/unit/equipment/equipment"; -import { EquipmentViewModel } from "../../../../viewmodel/admin/unit/equipment/equipment.models"; +import { + EquipmentViewModel, + MinifiedEquipmentViewModel, +} from "../../../../viewmodel/admin/unit/equipment/equipment.models"; import EquipmentTypeFactory from "./equipmentType"; export default abstract class EquipmentFactory { @@ -29,4 +32,28 @@ export default abstract class EquipmentFactory { public static mapToBase(records: Array): Array { return records.map((r) => this.mapToSingle(r)); } + + /** + * @description map record to minifed equipment + * @param {equipment} record + * @returns {MinifiedEquipmentViewModel} + */ + public static mapToSingleMinified(record: equipment): MinifiedEquipmentViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + type: record?.equipmentType.type, + assigned: "equipment", + }; + } + + /** + * @description map records to minified equipment + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseMinifed(records: Array): Array { + return records.map((r) => this.mapToSingleMinified(r)); + } } diff --git a/src/factory/admin/unit/vehicle/vehicle.ts b/src/factory/admin/unit/vehicle/vehicle.ts index fd7a86d..70e0575 100644 --- a/src/factory/admin/unit/vehicle/vehicle.ts +++ b/src/factory/admin/unit/vehicle/vehicle.ts @@ -1,5 +1,5 @@ import { vehicle } from "../../../../entity/unit/vehicle/vehicle"; -import { VehicleViewModel } from "../../../../viewmodel/admin/unit/vehicle/vehicle.models"; +import { MinifiedVehicleViewModel, VehicleViewModel } from "../../../../viewmodel/admin/unit/vehicle/vehicle.models"; import VehicleTypeFactory from "./vehicleType"; export default abstract class VehicleFactory { @@ -29,4 +29,28 @@ export default abstract class VehicleFactory { public static mapToBase(records: Array): Array { return records.map((r) => this.mapToSingle(r)); } + + /** + * @description map record to minifed vehicle + * @param {vehicle} record + * @returns {MinifiedVehicleViewModel} + */ + public static mapToSingleMinified(record: vehicle): MinifiedVehicleViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + type: record?.vehicleType.type, + assigned: "vehicle", + }; + } + + /** + * @description map records to minified vehicle + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseMinifed(records: Array): Array { + return records.map((r) => this.mapToSingleMinified(r)); + } } diff --git a/src/factory/admin/unit/wearable/wearable.ts b/src/factory/admin/unit/wearable/wearable.ts index d3b39af..4f58e2d 100644 --- a/src/factory/admin/unit/wearable/wearable.ts +++ b/src/factory/admin/unit/wearable/wearable.ts @@ -1,5 +1,8 @@ import { wearable } from "../../../../entity/unit/wearable/wearable"; -import { WearableViewModel } from "../../../../viewmodel/admin/unit/wearable/wearable.models"; +import { + MinifiedWearableViewModel, + WearableViewModel, +} from "../../../../viewmodel/admin/unit/wearable/wearable.models"; import MemberFactory from "../../club/member/member"; import WearableTypeFactory from "./wearableType"; @@ -32,4 +35,28 @@ export default abstract class WearableFactory { public static mapToBase(records: Array): Array { return records.map((r) => this.mapToSingle(r)); } + + /** + * @description map record to minifed wearable + * @param {wearable} record + * @returns {MinifiedWearableViewModel} + */ + public static mapToSingleMinified(record: wearable): MinifiedWearableViewModel { + return { + id: record.id, + code: record?.code, + name: record.name, + type: record?.wearableType.type, + assigned: "wearable", + }; + } + + /** + * @description map records to minified wearable + * @param {Array} records + * @returns {Array} + */ + public static mapToBaseMinifed(records: Array): Array { + return records.map((r) => this.mapToSingleMinified(r)); + } } diff --git a/src/helpers/settingsHelper.ts b/src/helpers/settingsHelper.ts index d4c21d8..b3eb5f8 100644 --- a/src/helpers/settingsHelper.ts +++ b/src/helpers/settingsHelper.ts @@ -14,8 +14,6 @@ import { UrlConverter, } from "./convertHelper"; import cloneDeep from "lodash.clonedeep"; -import { rejects } from "assert"; -import InternalException from "../exceptions/internalException"; import MailHelper from "./mailHelper"; export default abstract class SettingHelper { diff --git a/src/middleware/multer.ts b/src/middleware/multer.ts index 84a677c..321439e 100644 --- a/src/middleware/multer.ts +++ b/src/middleware/multer.ts @@ -2,6 +2,7 @@ import multer from "multer"; import { FileSystemHelper } from "../helpers/fileSystemHelper"; import path from "path"; import BadRequestException from "../exceptions/badRequestException"; +import { v4 as uuid } from "uuid"; /**Settings image upload */ export const clubImageStorage = multer.diskStorage({ @@ -58,3 +59,28 @@ export const inspectionFileMulter = multer({ }); export const inspectionFileUpload = inspectionFileMulter.array("files"); + +/**public damage report upload */ +export const pDamageReportFileStorage = multer.diskStorage({ + destination: function (req, file, cb) { + FileSystemHelper.createFolder("damageReport"); + cb(null, FileSystemHelper.formatPath("damageReport")); + }, + filename: function (req, file, cb) { + const fileExtension = path.extname(file.originalname).toLowerCase(); + cb(null, uuid() + fileExtension); + }, +}); + +export const pDamageReportFileMulter = multer({ + storage: pDamageReportFileStorage, + fileFilter(req, file, cb) { + if (file.mimetype.startsWith("image/")) { + cb(null, true); + } else { + cb(new BadRequestException("Wrong file format")); + } + }, +}); + +export const pDamageReportFileUpload = pDamageReportFileMulter.array("images"); diff --git a/src/migrations/baseSchemaTables/unit_extend.ts b/src/migrations/baseSchemaTables/unit_extend.ts index 43bece7..0beef1c 100644 --- a/src/migrations/baseSchemaTables/unit_extend.ts +++ b/src/migrations/baseSchemaTables/unit_extend.ts @@ -9,6 +9,8 @@ export const damage_report_table = new Table({ { name: "status", ...getTypeByORM("varchar") }, { name: "done", ...getTypeByORM("boolean"), default: getDefaultByORM("boolean", false) }, { name: "description", ...getTypeByORM("text") }, + { name: "location", ...getTypeByORM("text") }, + { name: "note", ...getTypeByORM("text") }, { name: "reportedBy", ...getTypeByORM("varchar") }, { name: "imageCount", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) }, { name: "equipmentId", ...getTypeByORM("uuid", true) }, diff --git a/src/routes/public.ts b/src/routes/public.ts index 30750a0..ac39669 100644 --- a/src/routes/public.ts +++ b/src/routes/public.ts @@ -1,13 +1,16 @@ import express from "express"; import { checkScannerRoomExists, + createDamageReport, getApplicationConfig, getApplicationFavicon, getApplicationIcon, getApplicationLogo, getApplicationManifest, getCalendarItemsByTypes, + searchStuffByCode, } from "../controller/publicController"; +import { pDamageReportFileUpload } from "../middleware/multer"; var router = express.Router({ mergeParams: true }); @@ -15,8 +18,12 @@ router.get("/calendar", async (req, res) => { await getCalendarItemsByTypes(req, res); }); -router.post("/reportdamage", async (req, res) => { - res.send("TODO"); +router.get("/reportdamage", async (req, res) => { + await searchStuffByCode(req, res); +}); + +router.post("/reportdamage", pDamageReportFileUpload, async (req, res) => { + await createDamageReport(req, res); }); router.post("/checkscannerroom", async (req, res) => { diff --git a/src/service/unit/equipment/equipmentService.ts b/src/service/unit/equipment/equipmentService.ts index 921f574..cb0cb2c 100644 --- a/src/service/unit/equipment/equipmentService.ts +++ b/src/service/unit/equipment/equipmentService.ts @@ -58,6 +58,26 @@ export default abstract class EquipmentService { }); } + /** + * @description get equipment by code + * @returns {Promise>} + */ + static async getAllByCode(code: string): Promise> { + return await dataSource + .getRepository(equipment) + .createQueryBuilder("equipment") + .leftJoinAndSelect("equipment.equipmentType", "equipmenttype") + .where({ code: Like(`%${code}%`) }) + .orderBy("name", "ASC") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "equipment", err); + }); + } + /** * @description get equipment by id * @returns {Promise} diff --git a/src/service/unit/vehicle/vehicleService.ts b/src/service/unit/vehicle/vehicleService.ts index 61c2b73..5f9c3ca 100644 --- a/src/service/unit/vehicle/vehicleService.ts +++ b/src/service/unit/vehicle/vehicleService.ts @@ -58,6 +58,26 @@ export default abstract class VehicleService { }); } + /** + * @description get vehicle by code + * @returns {Promise>} + */ + static async getAllByCode(code: string): Promise> { + return await dataSource + .getRepository(vehicle) + .createQueryBuilder("vehicle") + .leftJoinAndSelect("vehicle.vehicleType", "vehicletype") + .where({ code: Like(`%${code}%`) }) + .orderBy("name", "ASC") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "vehicle", err); + }); + } + /** * @description get vehicle by id * @returns {Promise} diff --git a/src/service/unit/wearable/wearableService.ts b/src/service/unit/wearable/wearableService.ts index d016b0f..557a9f9 100644 --- a/src/service/unit/wearable/wearableService.ts +++ b/src/service/unit/wearable/wearableService.ts @@ -59,6 +59,26 @@ export default abstract class WearableService { }); } + /** + * @description get wearable by code + * @returns {Promise>} + */ + static async getAllByCode(code: string): Promise> { + return await dataSource + .getRepository(wearable) + .createQueryBuilder("wearable") + .leftJoinAndSelect("wearable.wearableType", "wearabletype") + .where({ code: Like(`%${code}%`) }) + .orderBy("name", "ASC") + .getMany() + .then((res) => { + return res; + }) + .catch((err) => { + throw new DatabaseActionException("SELECT", "wearable", err); + }); + } + /** * @description get wearable by id * @returns {Promise} diff --git a/src/type/settingTypes.ts b/src/type/settingTypes.ts index ff6e728..08d8fbc 100644 --- a/src/type/settingTypes.ts +++ b/src/type/settingTypes.ts @@ -10,6 +10,7 @@ export type SettingString = | "club.website" | "app.custom_login_message" | "app.show_link_to_calendar" + | "app.show_link_to_damagereport" | "session.jwt_expiration" | "session.refresh_expiration" | "session.pwa_refresh_expiration" @@ -34,6 +35,7 @@ export type SettingValueMapping = { "club.website": string; "app.custom_login_message": string; "app.show_link_to_calendar": boolean; + "app.show_link_to_damagereport": boolean; "session.jwt_expiration": ms.StringValue; "session.refresh_expiration": ms.StringValue; "session.pwa_refresh_expiration": ms.StringValue; @@ -68,6 +70,7 @@ export const settingsType: SettingsSchema = { "club.website": { type: "url", optional: true }, "app.custom_login_message": { type: "string", optional: true }, "app.show_link_to_calendar": { type: "boolean", default: true }, + "app.show_link_to_damagereport": { type: "boolean", default: false }, "session.jwt_expiration": { type: "ms", default: "15m" }, "session.refresh_expiration": { type: "ms", default: "1d" }, "session.pwa_refresh_expiration": { type: "ms", default: "5d" }, diff --git a/src/viewmodel/admin/unit/damageReport.models.ts b/src/viewmodel/admin/unit/damageReport.models.ts index 9427d8a..7871d06 100644 --- a/src/viewmodel/admin/unit/damageReport.models.ts +++ b/src/viewmodel/admin/unit/damageReport.models.ts @@ -26,7 +26,7 @@ export type DamageReportViewModel = { status: string; done: boolean; description: string; - imageCount: number; + image: string[]; reportedBy: string; maintenance?: MaintenanceViewModel; } & DamageReportAssigned; diff --git a/src/viewmodel/admin/unit/equipment/equipment.models.ts b/src/viewmodel/admin/unit/equipment/equipment.models.ts index 2990c1d..625d482 100644 --- a/src/viewmodel/admin/unit/equipment/equipment.models.ts +++ b/src/viewmodel/admin/unit/equipment/equipment.models.ts @@ -10,3 +10,11 @@ export interface EquipmentViewModel { equipmentTypeId: string; equipmentType: EquipmentTypeViewModel; } + +export interface MinifiedEquipmentViewModel { + id: string; + code?: string; + name: string; + type: string; + assigned: "equipment"; +} diff --git a/src/viewmodel/admin/unit/vehicle/vehicle.models.ts b/src/viewmodel/admin/unit/vehicle/vehicle.models.ts index b25ab0c..b82b190 100644 --- a/src/viewmodel/admin/unit/vehicle/vehicle.models.ts +++ b/src/viewmodel/admin/unit/vehicle/vehicle.models.ts @@ -10,3 +10,11 @@ export interface VehicleViewModel { vehicleTypeId: string; vehicleType: VehicleTypeViewModel; } + +export interface MinifiedVehicleViewModel { + id: string; + code?: string; + name: string; + type: string; + assigned: "vehicle"; +} diff --git a/src/viewmodel/admin/unit/wearable/wearable.models.ts b/src/viewmodel/admin/unit/wearable/wearable.models.ts index e3bacac..de982e7 100644 --- a/src/viewmodel/admin/unit/wearable/wearable.models.ts +++ b/src/viewmodel/admin/unit/wearable/wearable.models.ts @@ -13,3 +13,11 @@ export interface WearableViewModel { wearableTypeId: string; wearableType: WearableTypeViewModel; } + +export interface MinifiedWearableViewModel { + id: string; + code?: string; + name: string; + type: string; + assigned: "wearable"; +}