forked from jappel/leistungsbilanz-ts
259 lines
7.6 KiB
TypeScript
259 lines
7.6 KiB
TypeScript
import crypto from "node:crypto";
|
|
import type { SerializedProjectCommand } from "./project-command.model.js";
|
|
|
|
export const projectFloorInsertCommandType = "project-floor.insert" as const;
|
|
export const projectFloorDeleteCommandType = "project-floor.delete" as const;
|
|
export const projectRoomInsertCommandType = "project-room.insert" as const;
|
|
export const projectRoomDeleteCommandType = "project-room.delete" as const;
|
|
export const projectLocationStructureCommandSchemaVersion = 1 as const;
|
|
|
|
export interface ProjectFloorSnapshot {
|
|
id: string;
|
|
projectId: string;
|
|
name: string;
|
|
sortOrder: number;
|
|
}
|
|
|
|
export interface ProjectRoomSnapshot {
|
|
id: string;
|
|
projectId: string;
|
|
floorId: string | null;
|
|
roomNumber: string;
|
|
roomName: string;
|
|
}
|
|
|
|
interface ProjectFloorStructureCommandPayload {
|
|
floor: ProjectFloorSnapshot;
|
|
}
|
|
|
|
interface ProjectRoomStructureCommandPayload {
|
|
room: ProjectRoomSnapshot;
|
|
}
|
|
|
|
export interface ProjectFloorInsertProjectCommand
|
|
extends SerializedProjectCommand<ProjectFloorStructureCommandPayload> {
|
|
schemaVersion: typeof projectLocationStructureCommandSchemaVersion;
|
|
type: typeof projectFloorInsertCommandType;
|
|
}
|
|
|
|
export interface ProjectFloorDeleteProjectCommand
|
|
extends SerializedProjectCommand<ProjectFloorStructureCommandPayload> {
|
|
schemaVersion: typeof projectLocationStructureCommandSchemaVersion;
|
|
type: typeof projectFloorDeleteCommandType;
|
|
}
|
|
|
|
export interface ProjectRoomInsertProjectCommand
|
|
extends SerializedProjectCommand<ProjectRoomStructureCommandPayload> {
|
|
schemaVersion: typeof projectLocationStructureCommandSchemaVersion;
|
|
type: typeof projectRoomInsertCommandType;
|
|
}
|
|
|
|
export interface ProjectRoomDeleteProjectCommand
|
|
extends SerializedProjectCommand<ProjectRoomStructureCommandPayload> {
|
|
schemaVersion: typeof projectLocationStructureCommandSchemaVersion;
|
|
type: typeof projectRoomDeleteCommandType;
|
|
}
|
|
|
|
export type ProjectLocationStructureProjectCommand =
|
|
| ProjectFloorInsertProjectCommand
|
|
| ProjectFloorDeleteProjectCommand
|
|
| ProjectRoomInsertProjectCommand
|
|
| ProjectRoomDeleteProjectCommand;
|
|
|
|
export function createProjectFloorSnapshot(
|
|
projectId: string,
|
|
name: string,
|
|
sortOrder: number
|
|
): ProjectFloorSnapshot {
|
|
const floor: ProjectFloorSnapshot = {
|
|
id: crypto.randomUUID(),
|
|
projectId,
|
|
name: name.trim(),
|
|
sortOrder,
|
|
};
|
|
assertProjectFloorSnapshot(floor);
|
|
return floor;
|
|
}
|
|
|
|
export function createProjectRoomSnapshot(
|
|
projectId: string,
|
|
input: {
|
|
floorId?: string;
|
|
roomNumber: string;
|
|
roomName: string;
|
|
}
|
|
): ProjectRoomSnapshot {
|
|
const room: ProjectRoomSnapshot = {
|
|
id: crypto.randomUUID(),
|
|
projectId,
|
|
floorId: input.floorId?.trim() || null,
|
|
roomNumber: input.roomNumber.trim(),
|
|
roomName: input.roomName.trim(),
|
|
};
|
|
assertProjectRoomSnapshot(room);
|
|
return room;
|
|
}
|
|
|
|
export function createProjectFloorInsertProjectCommand(
|
|
floor: ProjectFloorSnapshot
|
|
): ProjectFloorInsertProjectCommand {
|
|
assertProjectFloorSnapshot(floor);
|
|
return {
|
|
schemaVersion: projectLocationStructureCommandSchemaVersion,
|
|
type: projectFloorInsertCommandType,
|
|
payload: { floor },
|
|
};
|
|
}
|
|
|
|
export function createProjectFloorDeleteProjectCommand(
|
|
floor: ProjectFloorSnapshot
|
|
): ProjectFloorDeleteProjectCommand {
|
|
assertProjectFloorSnapshot(floor);
|
|
return {
|
|
schemaVersion: projectLocationStructureCommandSchemaVersion,
|
|
type: projectFloorDeleteCommandType,
|
|
payload: { floor },
|
|
};
|
|
}
|
|
|
|
export function createProjectRoomInsertProjectCommand(
|
|
room: ProjectRoomSnapshot
|
|
): ProjectRoomInsertProjectCommand {
|
|
assertProjectRoomSnapshot(room);
|
|
return {
|
|
schemaVersion: projectLocationStructureCommandSchemaVersion,
|
|
type: projectRoomInsertCommandType,
|
|
payload: { room },
|
|
};
|
|
}
|
|
|
|
export function createProjectRoomDeleteProjectCommand(
|
|
room: ProjectRoomSnapshot
|
|
): ProjectRoomDeleteProjectCommand {
|
|
assertProjectRoomSnapshot(room);
|
|
return {
|
|
schemaVersion: projectLocationStructureCommandSchemaVersion,
|
|
type: projectRoomDeleteCommandType,
|
|
payload: { room },
|
|
};
|
|
}
|
|
|
|
export function assertProjectFloorInsertProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is ProjectFloorInsertProjectCommand {
|
|
assertProjectFloorStructureProjectCommand(
|
|
command,
|
|
projectFloorInsertCommandType
|
|
);
|
|
}
|
|
|
|
export function assertProjectFloorDeleteProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is ProjectFloorDeleteProjectCommand {
|
|
assertProjectFloorStructureProjectCommand(
|
|
command,
|
|
projectFloorDeleteCommandType
|
|
);
|
|
}
|
|
|
|
export function assertProjectRoomInsertProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is ProjectRoomInsertProjectCommand {
|
|
assertProjectRoomStructureProjectCommand(
|
|
command,
|
|
projectRoomInsertCommandType
|
|
);
|
|
}
|
|
|
|
export function assertProjectRoomDeleteProjectCommand(
|
|
command: SerializedProjectCommand<unknown>
|
|
): asserts command is ProjectRoomDeleteProjectCommand {
|
|
assertProjectRoomStructureProjectCommand(
|
|
command,
|
|
projectRoomDeleteCommandType
|
|
);
|
|
}
|
|
|
|
export function assertProjectFloorSnapshot(
|
|
floor: unknown
|
|
): asserts floor is ProjectFloorSnapshot {
|
|
if (!isPlainObject(floor) || Object.keys(floor).length !== 4) {
|
|
throw new Error("Project-floor snapshot is invalid.");
|
|
}
|
|
assertNormalizedNonEmptyString(floor.id, "floor.id");
|
|
assertNormalizedNonEmptyString(floor.projectId, "floor.projectId");
|
|
assertNormalizedNonEmptyString(floor.name, "floor.name");
|
|
if (
|
|
typeof floor.sortOrder !== "number" ||
|
|
!Number.isSafeInteger(floor.sortOrder) ||
|
|
floor.sortOrder < 0
|
|
) {
|
|
throw new Error("floor.sortOrder must be a non-negative integer.");
|
|
}
|
|
}
|
|
|
|
export function assertProjectRoomSnapshot(
|
|
room: unknown
|
|
): asserts room is ProjectRoomSnapshot {
|
|
if (!isPlainObject(room) || Object.keys(room).length !== 5) {
|
|
throw new Error("Project-room snapshot is invalid.");
|
|
}
|
|
assertNormalizedNonEmptyString(room.id, "room.id");
|
|
assertNormalizedNonEmptyString(room.projectId, "room.projectId");
|
|
if (room.floorId !== null) {
|
|
assertNormalizedNonEmptyString(room.floorId, "room.floorId");
|
|
}
|
|
assertNormalizedNonEmptyString(room.roomNumber, "room.roomNumber");
|
|
assertNormalizedNonEmptyString(room.roomName, "room.roomName");
|
|
}
|
|
|
|
function assertProjectFloorStructureProjectCommand(
|
|
command: SerializedProjectCommand<unknown>,
|
|
expectedType:
|
|
| typeof projectFloorInsertCommandType
|
|
| typeof projectFloorDeleteCommandType
|
|
) {
|
|
if (
|
|
command.schemaVersion !== projectLocationStructureCommandSchemaVersion ||
|
|
command.type !== expectedType ||
|
|
!isPlainObject(command.payload) ||
|
|
Object.keys(command.payload).length !== 1
|
|
) {
|
|
throw new Error("Unsupported project-floor structure command.");
|
|
}
|
|
assertProjectFloorSnapshot(command.payload.floor);
|
|
}
|
|
|
|
function assertProjectRoomStructureProjectCommand(
|
|
command: SerializedProjectCommand<unknown>,
|
|
expectedType:
|
|
| typeof projectRoomInsertCommandType
|
|
| typeof projectRoomDeleteCommandType
|
|
) {
|
|
if (
|
|
command.schemaVersion !== projectLocationStructureCommandSchemaVersion ||
|
|
command.type !== expectedType ||
|
|
!isPlainObject(command.payload) ||
|
|
Object.keys(command.payload).length !== 1
|
|
) {
|
|
throw new Error("Unsupported project-room structure command.");
|
|
}
|
|
assertProjectRoomSnapshot(command.payload.room);
|
|
}
|
|
|
|
function assertNormalizedNonEmptyString(
|
|
value: unknown,
|
|
field: string
|
|
): asserts value is string {
|
|
if (
|
|
typeof value !== "string" ||
|
|
!value ||
|
|
value !== value.trim()
|
|
) {
|
|
throw new Error(`${field} must be a normalized non-empty string.`);
|
|
}
|
|
}
|
|
|
|
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
}
|