From cc811717e12b7331e21c5344672b3aee793efd2a Mon Sep 17 00:00:00 2001 From: Julian Krauser Date: Sun, 9 Mar 2025 09:54:56 +0100 Subject: [PATCH] sync doc to database --- package-lock.json | 30 +++++ package.json | 4 + .../operation/mission/missionCommand.ts | 3 +- .../mission/missionCommandHandler.ts | 6 +- .../mission/missionContactCommand.ts | 14 ++ .../mission/missionContactCommandHandler.ts | 43 ++++++ .../mission/missionEquipmentCommand.ts | 10 ++ .../mission/missionEquipmentCommandHandler.ts | 43 ++++++ .../mission/missionPresenceCommand.ts | 9 ++ .../mission/missionPresenceCommandHandler.ts | 44 +++++++ .../mission/missionVehicleCommand.ts | 13 ++ .../mission/missionVehicleCommandHandler.ts | 43 ++++++ .../admin/operation/missionController.ts | 2 +- src/data-source.ts | 4 +- src/entity/operation/mission.ts | 9 +- .../{mission_force.ts => mission_presence.ts} | 2 +- src/helpers/missionDocHelper.ts | 123 +++++++++++++++--- src/migrations/1739697068682-CreateSchema.ts | 6 +- src/migrations/baseSchemaTables/operation.ts | 5 +- src/service/operation/missionService.ts | 4 +- src/websocket/endpoints/missionManagement.ts | 2 +- 21 files changed, 379 insertions(+), 40 deletions(-) create mode 100644 src/command/operation/mission/missionContactCommand.ts create mode 100644 src/command/operation/mission/missionContactCommandHandler.ts create mode 100644 src/command/operation/mission/missionEquipmentCommand.ts create mode 100644 src/command/operation/mission/missionEquipmentCommandHandler.ts create mode 100644 src/command/operation/mission/missionPresenceCommand.ts create mode 100644 src/command/operation/mission/missionPresenceCommandHandler.ts create mode 100644 src/command/operation/mission/missionVehicleCommand.ts create mode 100644 src/command/operation/mission/missionVehicleCommandHandler.ts rename src/entity/operation/{mission_force.ts => mission_presence.ts} (94%) diff --git a/package-lock.json b/package-lock.json index 7bd5415..abc3c61 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,8 @@ "ics": "^3.8.1", "ip": "^2.0.1", "jsonwebtoken": "^9.0.2", + "lodash.differencewith": "^4.5.0", + "lodash.isequal": "^4.5.0", "lodash.uniqby": "^4.7.0", "moment": "^2.30.1", "morgan": "^1.10.0", @@ -46,6 +48,8 @@ "@types/express": "^4.17.17", "@types/ip": "^1.1.3", "@types/jsonwebtoken": "^9.0.6", + "@types/lodash.differencewith": "^4.5.9", + "@types/lodash.isequal": "^4.5.8", "@types/lodash.uniqby": "^4.7.9", "@types/morgan": "^1.9.9", "@types/ms": "^0.7.34", @@ -392,6 +396,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/lodash.differencewith": { + "version": "4.5.9", + "resolved": "https://registry.npmjs.org/@types/lodash.differencewith/-/lodash.differencewith-4.5.9.tgz", + "integrity": "sha512-nMaREKoe7J3WvnsO7HDRxvnPT3mWmZD3EAECpy7gBGJ6S5nQ66uVlkRe+ZXs6261ZNb2fH9Ny4oUUiSOCmTnLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/lodash.isequal": { + "version": "4.5.8", + "resolved": "https://registry.npmjs.org/@types/lodash.isequal/-/lodash.isequal-4.5.8.tgz", + "integrity": "sha512-uput6pg4E/tj2LGxCZo9+y27JNyB2OZuuI/T5F+ylVDYuqICLG2/ktjxx0v6GvVntAf8TvEzeQLcV0ffRirXuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, "node_modules/@types/lodash.uniqby": { "version": "4.7.9", "resolved": "https://registry.npmjs.org/@types/lodash.uniqby/-/lodash.uniqby-4.7.9.tgz", @@ -2370,6 +2394,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "license": "MIT" }, + "node_modules/lodash.differencewith": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.differencewith/-/lodash.differencewith-4.5.0.tgz", + "integrity": "sha512-/8JFjydAS+4bQuo3CpLMBv7WxGFyk7/etOAsrQUCu0a9QVDemxv0YQ0rFyeZvqlUD314SERfNlgnlqqHmaQ0Cg==", + "license": "MIT" + }, "node_modules/lodash.includes": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", diff --git a/package.json b/package.json index a888775..8d6f71b 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,8 @@ "ics": "^3.8.1", "ip": "^2.0.1", "jsonwebtoken": "^9.0.2", + "lodash.differencewith": "^4.5.0", + "lodash.isequal": "^4.5.0", "lodash.uniqby": "^4.7.0", "moment": "^2.30.1", "morgan": "^1.10.0", @@ -61,6 +63,8 @@ "@types/express": "^4.17.17", "@types/ip": "^1.1.3", "@types/jsonwebtoken": "^9.0.6", + "@types/lodash.differencewith": "^4.5.9", + "@types/lodash.isequal": "^4.5.8", "@types/lodash.uniqby": "^4.7.9", "@types/morgan": "^1.9.9", "@types/ms": "^0.7.34", diff --git a/src/command/operation/mission/missionCommand.ts b/src/command/operation/mission/missionCommand.ts index 54e6876..2bca75a 100644 --- a/src/command/operation/mission/missionCommand.ts +++ b/src/command/operation/mission/missionCommand.ts @@ -1,4 +1,4 @@ -export interface UpdateMissionCommand { +export interface SyncMissionCommand { id: string; title: string; commandId?: string | null; @@ -11,6 +11,7 @@ export interface UpdateMissionCommand { rescued: number; recovered: number; description: string; + last_update: number; } export interface DeleteMissionCommand { diff --git a/src/command/operation/mission/missionCommandHandler.ts b/src/command/operation/mission/missionCommandHandler.ts index d85bcb7..969dfff 100644 --- a/src/command/operation/mission/missionCommandHandler.ts +++ b/src/command/operation/mission/missionCommandHandler.ts @@ -1,7 +1,7 @@ import { dataSource } from "../../../data-source"; import { mission } from "../../../entity/operation/mission"; import DatabaseActionException from "../../../exceptions/databaseActionException"; -import { DeleteMissionCommand, UpdateMissionCommand } from "./missionCommand"; +import { DeleteMissionCommand, SyncMissionCommand } from "./missionCommand"; export default abstract class MissionCommandHandler { /** @@ -27,10 +27,10 @@ export default abstract class MissionCommandHandler { /** * @description update mission - * @param {UpdateMissionCommand} updateMission + * @param {SyncMissionCommand} updateMission * @returns {Promise} */ - static async sync(updateMission: UpdateMissionCommand): Promise { + static async sync(updateMission: SyncMissionCommand): Promise { return await dataSource .createQueryBuilder() .update(mission) diff --git a/src/command/operation/mission/missionContactCommand.ts b/src/command/operation/mission/missionContactCommand.ts new file mode 100644 index 0000000..20ca391 --- /dev/null +++ b/src/command/operation/mission/missionContactCommand.ts @@ -0,0 +1,14 @@ +export interface SyncMissionContactCommand { + contactId: string; + missionId: string; + firstname: string; + lastname: string; + phone: string; + address: string; + note: string; +} + +export interface DeleteMissionContactCommand { + contactId: string; + missionId: string; +} diff --git a/src/command/operation/mission/missionContactCommandHandler.ts b/src/command/operation/mission/missionContactCommandHandler.ts new file mode 100644 index 0000000..7cbfc7e --- /dev/null +++ b/src/command/operation/mission/missionContactCommandHandler.ts @@ -0,0 +1,43 @@ +import { dataSource } from "../../../data-source"; +import { mission_contact } from "../../../entity/operation/mission_contact"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { DeleteMissionContactCommand, SyncMissionContactCommand } from "./missionContactCommand"; + +export default abstract class MissionContactCommandHandler { + /** + * @description update mission_contact + * @param {Array} updateMissionContact + * @returns {Promise} + */ + static async sync(updateMissionContact: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(mission_contact) + .values(updateMissionContact) + .orUpdate(["firstname", "lastname", "phone", "address", "note"], ["missionId", "contactId"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "mission_contact", err); + }); + } + + /** + * @description delete mission_contact + * @param {Array} deleteMissionContact + * @returns {Promise} + */ + static async delete(deleteMissionContact: Array): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(mission_contact) + .whereInIds(deleteMissionContact) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "mission_contact", err); + }); + } +} diff --git a/src/command/operation/mission/missionEquipmentCommand.ts b/src/command/operation/mission/missionEquipmentCommand.ts new file mode 100644 index 0000000..6df71ee --- /dev/null +++ b/src/command/operation/mission/missionEquipmentCommand.ts @@ -0,0 +1,10 @@ +export interface SyncMissionEquipmentCommand { + equipmentId: string; + missionId: string; + note: string; +} + +export interface DeleteMissionEquipmentCommand { + equipmentId: string; + missionId: string; +} diff --git a/src/command/operation/mission/missionEquipmentCommandHandler.ts b/src/command/operation/mission/missionEquipmentCommandHandler.ts new file mode 100644 index 0000000..6cd7c7b --- /dev/null +++ b/src/command/operation/mission/missionEquipmentCommandHandler.ts @@ -0,0 +1,43 @@ +import { dataSource } from "../../../data-source"; +import { mission_equipment } from "../../../entity/operation/mission_equipment"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { DeleteMissionEquipmentCommand, SyncMissionEquipmentCommand } from "./missionEquipmentCommand"; + +export default abstract class MissionEquipmentCommandHandler { + /** + * @description update mission_equipment + * @param {Array} updateMissionEquipment + * @returns {Promise} + */ + static async sync(updateMissionEquipment: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(mission_equipment) + .values(updateMissionEquipment) + .orUpdate(["note"], ["missionId", "equipmentId"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "mission_equipment", err); + }); + } + + /** + * @description delete mission_equipment + * @param {Array} deleteMissionEquipment + * @returns {Promise} + */ + static async delete(deleteMissionEquipment: Array): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(mission_equipment) + .whereInIds(deleteMissionEquipment) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "mission_equipment", err); + }); + } +} diff --git a/src/command/operation/mission/missionPresenceCommand.ts b/src/command/operation/mission/missionPresenceCommand.ts new file mode 100644 index 0000000..4c35dd5 --- /dev/null +++ b/src/command/operation/mission/missionPresenceCommand.ts @@ -0,0 +1,9 @@ +export interface SyncMissionPresenceCommand { + forceId: string; + missionId: string; +} + +export interface DeleteMissionPresenceCommand { + forceId: string; + missionId: string; +} diff --git a/src/command/operation/mission/missionPresenceCommandHandler.ts b/src/command/operation/mission/missionPresenceCommandHandler.ts new file mode 100644 index 0000000..34a36c6 --- /dev/null +++ b/src/command/operation/mission/missionPresenceCommandHandler.ts @@ -0,0 +1,44 @@ +import { dataSource } from "../../../data-source"; +import { mission_presence } from "../../../entity/operation/mission_presence"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { DeleteMissionPresenceCommand, SyncMissionPresenceCommand } from "./missionPresenceCommand"; + +export default abstract class MissionPresenceCommandHandler { + /** + * @description update mission-presence + * @param {Array} updateMissionpresence + * @returns {Promise} + */ + static async sync(updateMissionpresence: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(mission_presence) + .values(updateMissionpresence) + .orIgnore() + // .orUpdate([], ["missionId", "forceId"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "mission-presence", err); + }); + } + + /** + * @description delete mission-presence + * @param {Array} deleteMissionpresence + * @returns {Promise} + */ + static async delete(deleteMissionpresence: Array): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(mission_presence) + .whereInIds(deleteMissionpresence) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "mission-presence", err); + }); + } +} diff --git a/src/command/operation/mission/missionVehicleCommand.ts b/src/command/operation/mission/missionVehicleCommand.ts new file mode 100644 index 0000000..e9b7901 --- /dev/null +++ b/src/command/operation/mission/missionVehicleCommand.ts @@ -0,0 +1,13 @@ +export interface SyncMissionVehicleCommand { + vehicleId: string; + missionId: string; + driverId: string | null; + leaderId: string | null; + mileage_start: number | null; + mileage_end: number | null; +} + +export interface DeleteMissionVehicleCommand { + vehicleId: string; + missionId: string; +} diff --git a/src/command/operation/mission/missionVehicleCommandHandler.ts b/src/command/operation/mission/missionVehicleCommandHandler.ts new file mode 100644 index 0000000..6f03aec --- /dev/null +++ b/src/command/operation/mission/missionVehicleCommandHandler.ts @@ -0,0 +1,43 @@ +import { dataSource } from "../../../data-source"; +import { mission_vehicle } from "../../../entity/operation/mission_vehicle"; +import DatabaseActionException from "../../../exceptions/databaseActionException"; +import { DeleteMissionVehicleCommand, SyncMissionVehicleCommand } from "./missionVehicleCommand"; + +export default abstract class MissionVehicleCommandHandler { + /** + * @description update mission_vehicle + * @param {Array} updateMissionVehicle + * @returns {Promise} + */ + static async sync(updateMissionVehicle: Array): Promise { + return await dataSource + .createQueryBuilder() + .insert() + .into(mission_vehicle) + .values(updateMissionVehicle) + .orUpdate(["driverId", "leaderId", "mileage_start", "mileage_end"], ["missionId", "vehicleId"]) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("UPDATE", "mission_vehicle", err); + }); + } + + /** + * @description delete mission_vehicle + * @param {Array} deleteMissionVehicle + * @returns {Promise} + */ + static async delete(deleteMissionVehicle: Array): Promise { + return await dataSource + .createQueryBuilder() + .delete() + .from(mission_vehicle) + .whereInIds(deleteMissionVehicle) + .execute() + .then(() => {}) + .catch((err) => { + throw new DatabaseActionException("DELETE", "mission_vehicle", err); + }); + } +} diff --git a/src/controller/admin/operation/missionController.ts b/src/controller/admin/operation/missionController.ts index cdf453d..955c985 100644 --- a/src/controller/admin/operation/missionController.ts +++ b/src/controller/admin/operation/missionController.ts @@ -1,7 +1,7 @@ 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 { DeleteMissionCommand, SyncMissionCommand } from "../../../command/operation/mission/missionCommand"; import MissionCommandHandler from "../../../command/operation/mission/missionCommandHandler"; import SocketServer from "../../../websocket"; diff --git a/src/data-source.ts b/src/data-source.ts index 780dc54..cf638e5 100644 --- a/src/data-source.ts +++ b/src/data-source.ts @@ -16,7 +16,7 @@ import { CreateSchema1739697068682 } from "./migrations/1739697068682-CreateSche import { vehicle } from "./entity/configuration/vehicle"; import { equipment } from "./entity/configuration/equipment"; import { mission } from "./entity/operation/mission"; -import { mission_force } from "./entity/operation/mission_force"; +import { mission_presence } from "./entity/operation/mission_presence"; import { mission_equipment } from "./entity/operation/mission_equipment"; import { mission_vehicle } from "./entity/operation/mission_vehicle"; import { mission_contact } from "./entity/operation/mission_contact"; @@ -43,7 +43,7 @@ const dataSource = new DataSource({ vehicle, equipment, mission, - mission_force, + mission_presence, mission_equipment, mission_vehicle, mission_contact, diff --git a/src/entity/operation/mission.ts b/src/entity/operation/mission.ts index 9cfdc8f..9332535 100644 --- a/src/entity/operation/mission.ts +++ b/src/entity/operation/mission.ts @@ -1,6 +1,6 @@ import { Column, CreateDateColumn, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm"; import { force } from "../configuration/force"; -import { mission_force } from "./mission_force"; +import { mission_presence } from "./mission_presence"; import { mission_vehicle } from "./mission_vehicle"; import { mission_equipment } from "./mission_equipment"; import { mission_contact } from "./mission_contact"; @@ -43,6 +43,9 @@ export class mission { @Column({ type: "text", default: "[]" }) description: string; + @Column({ type: "bigint", default: 0 }) + last_update: number; + @CreateDateColumn() createdAt: Date; @@ -58,8 +61,8 @@ export class mission { }) secretary: force; - @OneToMany(() => mission_force, (mf) => mf.mission, { cascade: ["insert"] }) - forces: mission_force[]; + @OneToMany(() => mission_presence, (mf) => mf.mission, { cascade: ["insert"] }) + presence: mission_presence[]; @OneToMany(() => mission_vehicle, (mv) => mv.mission, { cascade: ["insert"] }) vehicles: mission_vehicle[]; diff --git a/src/entity/operation/mission_force.ts b/src/entity/operation/mission_presence.ts similarity index 94% rename from src/entity/operation/mission_force.ts rename to src/entity/operation/mission_presence.ts index b0db0f7..2ddc5e6 100644 --- a/src/entity/operation/mission_force.ts +++ b/src/entity/operation/mission_presence.ts @@ -3,7 +3,7 @@ import { force } from "../configuration/force"; import { mission } from "./mission"; @Entity() -export class mission_force { +export class mission_presence { @PrimaryColumn({ type: "varchar", length: 36 }) missionId: string; diff --git a/src/helpers/missionDocHelper.ts b/src/helpers/missionDocHelper.ts index 215ab56..81a59c7 100644 --- a/src/helpers/missionDocHelper.ts +++ b/src/helpers/missionDocHelper.ts @@ -1,8 +1,14 @@ import * as Y from "yjs"; import { MissionMap } from "../storage/missionMap"; import MissionService from "../service/operation/missionService"; -import { UpdateMissionCommand } from "../command/operation/mission/missionCommand"; +import { SyncMissionCommand } from "../command/operation/mission/missionCommand"; import MissionCommandHandler from "../command/operation/mission/missionCommandHandler"; +import MissionVehicleCommandHandler from "../command/operation/mission/missionVehicleCommandHandler"; +import MissionEquipmentCommandHandler from "../command/operation/mission/missionEquipmentCommandHandler"; +import MissionContactCommandHandler from "../command/operation/mission/missionContactCommandHandler"; +import MissionPresenceCommandHandler from "../command/operation/mission/missionPresenceCommandHandler"; +import differenceWith from "lodash.differencewith"; +import isEqual from "lodash.isequal"; export default abstract class MissionDocHelper { public static async populateDoc(missionId: string) { @@ -19,14 +25,15 @@ export default abstract class MissionDocHelper { doc.getMap("form").set("others", mission.others); doc.getMap("form").set("rescued", mission.rescued.toString()); doc.getMap("form").set("recovered", mission.recovered.toString()); - doc.getText("editor").applyDelta(JSON.parse(mission.description)); //.insert(0, mission.description); + doc.getText("editor").applyDelta(JSON.parse(mission.description)); + for (const vehicle of mission.vehicles) { doc.getMap>("vehicle").set(vehicle.vehicleId, new Y.Map()); for (const key of ["driverId", "leaderId", "mileage_start", "mileage_end"] as (keyof typeof vehicle)[]) { doc .getMap>("vehicle") .get(vehicle.vehicleId) - .set(key, vehicle[key].toString()); + .set(key.replace("Id", ""), (vehicle[key] ?? "").toString()); } } for (const equipment of mission.equipments) { @@ -35,7 +42,7 @@ export default abstract class MissionDocHelper { doc .getMap>("equipment") .get(equipment.equipmentId) - .set(key, equipment[key].toString()); + .set(key, (equipment[key] ?? "").toString()); } } for (const contact of mission.contacts) { @@ -44,10 +51,10 @@ export default abstract class MissionDocHelper { doc .getMap>("contact") .get(contact.contactId) - .set(key, contact[key].toString()); + .set(key, (contact[key] ?? "").toString()); } } - for (const force of mission.forces) { + for (const force of mission.presence) { doc.getMap("presence").set(force.forceId, true); } @@ -56,7 +63,7 @@ export default abstract class MissionDocHelper { doc, editors: new Map(), editorStates: new Map(), - timestamp: 0, + timestamp: mission.last_update, }); console.log(`created local doc ${missionId}`); } @@ -86,13 +93,45 @@ export default abstract class MissionDocHelper { return Array.from(mission.editors.entries()); } - public static async saveDoc(missionId: string, update: Uint8Array) { + public static async saveDoc(missionId: string, update: Uint8Array, timestamp: number) { const oldMission = MissionMap.read(missionId); const oldVehicles = Array.from(oldMission.doc.getMap("presence").keys()); const oldEquipments = Array.from(oldMission.doc.getMap("presence").keys()); const oldContact = Array.from(oldMission.doc.getMap("presence").keys()); const oldPresence = Array.from(oldMission.doc.getMap("presence").keys()); + const oldMissionVehicles = oldMission.doc.getMap>("vehicle").toJSON(); + const oldMissionEquipment = oldMission.doc.getMap>("equipment").toJSON(); + const oldMissionContact = oldMission.doc.getMap>("contact").toJSON(); + const oldMissionPresence = oldMission.doc.getMap>("presence").toJSON(); + + const oldMissionVehiclesArray = Object.keys(oldMissionVehicles).map((mv) => ({ + missionId: missionId, + vehicleId: mv, + driverId: oldMissionVehicles[mv]?.driver || null, + leaderId: oldMissionVehicles[mv]?.leader || null, + mileage_start: parseInt(oldMissionVehicles[mv]?.mileage_start) || null, + mileage_end: parseInt(oldMissionVehicles[mv]?.mileage_end) || null, + })); + const oldMissionEquipmentArray = Object.keys(oldMissionEquipment).map((me) => ({ + missionId: missionId, + equipmentId: me, + note: oldMissionEquipment[me]?.note ?? "", + })); + const oldMissionContactArray = Object.keys(oldMissionContact).map((mc) => ({ + missionId: missionId, + contactId: mc, + firstname: oldMissionContact[mc]?.firstname ?? "", + lastname: oldMissionContact[mc]?.lastname ?? "", + phone: oldMissionContact[mc]?.phone ?? "", + address: oldMissionContact[mc]?.address ?? "", + note: oldMissionContact[mc]?.note ?? "", + })); + const oldMissionPresenceArray = Object.keys(oldMissionPresence).map((mc) => ({ + missionId: missionId, + forceId: mc, + })); + MissionMap.updateState(missionId, update); const mission = MissionMap.read(missionId); @@ -106,8 +145,7 @@ export default abstract class MissionDocHelper { const removedContacts = oldContact.filter((item) => !contact.includes(item)); const removedPresence = oldPresence.filter((item) => !presence.includes(item)); - //editor.setContents(mission.doc.getText("editor").toDelta()); //new QuillDeltaToHtmlConverter(mission.doc.getText("editor").toDelta()).convert(), - const missionDetails: UpdateMissionCommand = { + const missionDetails: SyncMissionCommand = { id: missionId, title: mission.doc.getMap("form").toJSON().title, ...(mission.doc.getMap("form").toJSON().command != "" @@ -122,25 +160,68 @@ export default abstract class MissionDocHelper { ...(mission.doc.getMap("form").get("end") != "" ? { mission_end: new Date(mission.doc.getMap("form").get("end") as string) } : { mission_end: null }), - keyword: mission.doc.getMap("form").toJSON().mission_short, - location: mission.doc.getMap("form").toJSON().location, - others: mission.doc.getMap("form").toJSON().others, - rescued: parseInt(mission.doc.getMap("form").toJSON().rescued), - recovered: parseInt(mission.doc.getMap("form").toJSON().recovered), + keyword: mission.doc.getMap("form").toJSON().mission_short ?? "", + location: mission.doc.getMap("form").toJSON().location ?? "", + others: mission.doc.getMap("form").toJSON().others ?? "", + rescued: parseInt(mission.doc.getMap("form").toJSON().rescued) || 0, + recovered: parseInt(mission.doc.getMap("form").toJSON().recovered) || 0, description: JSON.stringify(mission.doc.getText("editor").toDelta()), + last_update: timestamp, }; const missionVehicles = mission.doc.getMap>("vehicle").toJSON(); const missionEquipment = mission.doc.getMap>("equipment").toJSON(); const missionContact = mission.doc.getMap>("contact").toJSON(); const missionPresence = mission.doc.getMap>("presence").toJSON(); - // store Data to database - - // Object.keys(tmp).map(t => ({ - // id:t, - // ...tmp[t] - // })) + const missionVehiclesArray = Object.keys(missionVehicles).map((mv) => ({ + missionId: missionId, + vehicleId: mv, + driverId: missionVehicles[mv]?.driver || null, + leaderId: missionVehicles[mv]?.leader || null, + mileage_start: parseInt(missionVehicles[mv]?.mileage_start) || null, + mileage_end: parseInt(missionVehicles[mv]?.mileage_end) || null, + })); + const missionEquipmentArray = Object.keys(missionEquipment).map((me) => ({ + missionId: missionId, + equipmentId: me, + note: missionEquipment[me]?.note ?? "", + })); + const missionContactArray = Object.keys(missionContact).map((mc) => ({ + missionId: missionId, + contactId: mc, + firstname: missionContact[mc]?.firstname ?? "", + lastname: missionContact[mc]?.lastname ?? "", + phone: missionContact[mc]?.phone ?? "", + address: missionContact[mc]?.address ?? "", + note: missionContact[mc]?.note ?? "", + })); + const missionPresenceArray = Object.keys(missionPresence).map((mc) => ({ + missionId: missionId, + forceId: mc, + })); await MissionCommandHandler.sync(missionDetails); + + if (differenceWith(missionVehiclesArray, oldMissionVehiclesArray, isEqual).length != 0) + await MissionVehicleCommandHandler.sync(differenceWith(missionVehiclesArray, oldMissionVehiclesArray, isEqual)); + if (removedVehicles.length != 0) + await MissionVehicleCommandHandler.delete(removedVehicles.map((rv) => ({ missionId, vehicleId: rv }))); + + if (differenceWith(missionEquipmentArray, oldMissionEquipmentArray, isEqual).length != 0) + await MissionEquipmentCommandHandler.sync( + differenceWith(missionEquipmentArray, oldMissionEquipmentArray, isEqual) + ); + if (removedEquipments.length != 0) + await MissionEquipmentCommandHandler.delete(removedEquipments.map((re) => ({ missionId, equipmentId: re }))); + + if (differenceWith(missionContactArray, oldMissionContactArray, isEqual).length != 0) + await MissionContactCommandHandler.sync(differenceWith(missionContactArray, oldMissionContactArray, isEqual)); + if (removedContacts.length != 0) + await MissionContactCommandHandler.delete(removedContacts.map((rc) => ({ missionId, contactId: rc }))); + + if (differenceWith(missionPresenceArray, oldMissionPresenceArray, isEqual).length != 0) + await MissionPresenceCommandHandler.sync(differenceWith(missionPresenceArray, oldMissionPresenceArray, isEqual)); + if (removedPresence.length != 0) + await MissionPresenceCommandHandler.delete(removedPresence.map((mp) => ({ missionId, forceId: mp }))); } } diff --git a/src/migrations/1739697068682-CreateSchema.ts b/src/migrations/1739697068682-CreateSchema.ts index 1a5e155..a6dfff7 100644 --- a/src/migrations/1739697068682-CreateSchema.ts +++ b/src/migrations/1739697068682-CreateSchema.ts @@ -13,7 +13,7 @@ import { equipment_table, force_table, vehicle_table } from "./baseSchemaTables/ import { mission_contact_table, mission_equipment_table, - mission_force_table, + mission_presence_table, mission_table, mission_vehicle_table, } from "./baseSchemaTables/operation"; @@ -36,7 +36,7 @@ export class CreateSchema1739697068682 implements MigrationInterface { await queryRunner.createTable(vehicle_table, true, true, true); await queryRunner.createTable(mission_table, true, true, true); - await queryRunner.createTable(mission_force_table, true, true, true); + await queryRunner.createTable(mission_presence_table, true, true, true); await queryRunner.createTable(mission_vehicle_table, true, true, true); await queryRunner.createTable(mission_equipment_table, true, true, true); await queryRunner.createTable(mission_contact_table, true, true, true); @@ -46,7 +46,7 @@ export class CreateSchema1739697068682 implements MigrationInterface { await queryRunner.dropTable("mission_contact", true, true, true); await queryRunner.dropTable("mission_equipment", true, true, true); await queryRunner.dropTable("mission_vehicle", true, true, true); - await queryRunner.dropTable("mission_force", true, true, true); + await queryRunner.dropTable("mission_presence", true, true, true); await queryRunner.dropTable("mission", true, true, true); await queryRunner.dropTable("vehicle", true, true, true); diff --git a/src/migrations/baseSchemaTables/operation.ts b/src/migrations/baseSchemaTables/operation.ts index a3fd209..fa7999e 100644 --- a/src/migrations/baseSchemaTables/operation.ts +++ b/src/migrations/baseSchemaTables/operation.ts @@ -16,12 +16,13 @@ export const mission_table = new Table({ { name: "rescued", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) }, { name: "recovered", ...getTypeByORM("int"), default: getDefaultByORM("number", 0) }, { name: "description", ...getTypeByORM("text"), default: getDefaultByORM("string", "[]") }, + { name: "last_update", ...getTypeByORM("bigint"), default: getDefaultByORM("number", 0) }, { name: "createdAt", ...getTypeByORM("datetime", false, 6), default: getDefaultByORM("currentTimestamp") }, ], }); -export const mission_force_table = new Table({ - name: "mission_force", +export const mission_presence_table = new Table({ + name: "mission_presence", columns: [ { name: "missionId", ...getTypeByORM("uuid"), isPrimary: true }, { name: "forceId", ...getTypeByORM("uuid"), isPrimary: true }, diff --git a/src/service/operation/missionService.ts b/src/service/operation/missionService.ts index b66e385..7d4a941 100644 --- a/src/service/operation/missionService.ts +++ b/src/service/operation/missionService.ts @@ -64,8 +64,8 @@ export default abstract class MissionService { .createQueryBuilder("mission") .leftJoinAndSelect("mission.command", "command") .leftJoinAndSelect("mission.secretary", "secretary") - .leftJoinAndSelect("mission.forces", "forces") - .leftJoinAndSelect("forces.force", "force") + .leftJoinAndSelect("mission.presence", "presence") + .leftJoinAndSelect("presence.force", "force") .leftJoinAndSelect("mission.vehicles", "vehicles") .leftJoinAndSelect("vehicles.driver", "driver") .leftJoinAndSelect("vehicles.leader", "leader") diff --git a/src/websocket/endpoints/missionManagement.ts b/src/websocket/endpoints/missionManagement.ts index e50ea89..ebf9bb6 100644 --- a/src/websocket/endpoints/missionManagement.ts +++ b/src/websocket/endpoints/missionManagement.ts @@ -98,7 +98,7 @@ export default (io: Server, socket: Socket) => { async (data: { update: Array; timestamp: number }) => { const socketRooms = Array.from(socket.rooms).filter((room) => room !== socket.id && room !== "home"); try { - MissionDocHelper.saveDoc(socketRooms[0], new Uint8Array(data.update)); + MissionDocHelper.saveDoc(socketRooms[0], new Uint8Array(data.update), data.timestamp); return { type: "package-sync",