145 lines
5 KiB
TypeScript
145 lines
5 KiB
TypeScript
import {
|
|
parseProjectStateSnapshot,
|
|
type ProjectStateSnapshot,
|
|
} from "./project-state-snapshot.model.js";
|
|
import type { SerializedProjectCommand } from "./project-command.model.js";
|
|
import {
|
|
parseEstimateBalanceSnapshot,
|
|
} from "../../estimate-balance/domain/estimate-balance-command.model.js";
|
|
import type { EstimateBalanceSnapshot } from "../../shared/estimate-balance/estimate-balance.js";
|
|
|
|
export const projectStateRestoreCommandType = "project.restore-state";
|
|
export const projectStateRestoreCommandSchemaVersion = 2 as const;
|
|
const snapshotRestoreCommandSchemaVersion = 1 as const;
|
|
|
|
export interface ProjectStateRestoreCommandPayload {
|
|
expectedStateSha256: string;
|
|
targetState: ProjectStateSnapshot;
|
|
}
|
|
|
|
export type SnapshotProjectStateRestoreCommand = SerializedProjectCommand<
|
|
ProjectStateRestoreCommandPayload
|
|
> & {
|
|
schemaVersion: typeof snapshotRestoreCommandSchemaVersion;
|
|
type: typeof projectStateRestoreCommandType;
|
|
};
|
|
|
|
export interface ProjectTransferRestoreCommandPayload
|
|
extends ProjectStateRestoreCommandPayload {
|
|
expectedEstimateBalances: EstimateBalanceSnapshot[];
|
|
targetEstimateBalances: EstimateBalanceSnapshot[];
|
|
}
|
|
|
|
export type ProjectTransferRestoreCommand = SerializedProjectCommand<
|
|
ProjectTransferRestoreCommandPayload
|
|
> & {
|
|
schemaVersion: typeof projectStateRestoreCommandSchemaVersion;
|
|
type: typeof projectStateRestoreCommandType;
|
|
};
|
|
|
|
export type ProjectStateRestoreCommand =
|
|
| SnapshotProjectStateRestoreCommand
|
|
| ProjectTransferRestoreCommand;
|
|
|
|
export function createProjectStateRestoreCommand(
|
|
expectedStateSha256: string,
|
|
targetState: ProjectStateSnapshot
|
|
): ProjectStateRestoreCommand {
|
|
const command: SnapshotProjectStateRestoreCommand = {
|
|
schemaVersion: snapshotRestoreCommandSchemaVersion,
|
|
type: projectStateRestoreCommandType,
|
|
payload: {
|
|
expectedStateSha256,
|
|
targetState: parseProjectStateSnapshot(targetState),
|
|
},
|
|
};
|
|
assertProjectStateRestoreCommand(command);
|
|
return command;
|
|
}
|
|
|
|
export function createProjectTransferRestoreCommand(
|
|
expectedStateSha256: string,
|
|
targetState: ProjectStateSnapshot,
|
|
expectedEstimateBalances: EstimateBalanceSnapshot[],
|
|
targetEstimateBalances: EstimateBalanceSnapshot[]
|
|
): ProjectTransferRestoreCommand {
|
|
const command: ProjectTransferRestoreCommand = {
|
|
schemaVersion: projectStateRestoreCommandSchemaVersion,
|
|
type: projectStateRestoreCommandType,
|
|
payload: {
|
|
expectedStateSha256,
|
|
targetState: parseProjectStateSnapshot(targetState),
|
|
expectedEstimateBalances: parseEstimateBalances(
|
|
expectedEstimateBalances,
|
|
targetState.project.id
|
|
),
|
|
targetEstimateBalances: parseEstimateBalances(
|
|
targetEstimateBalances,
|
|
targetState.project.id
|
|
),
|
|
},
|
|
};
|
|
assertProjectStateRestoreCommand(command);
|
|
return command;
|
|
}
|
|
|
|
export function assertProjectStateRestoreCommand(
|
|
value: unknown
|
|
): asserts value is ProjectStateRestoreCommand {
|
|
if (!value || typeof value !== "object") {
|
|
throw new Error("Project state restore command must be an object.");
|
|
}
|
|
const command = value as Partial<ProjectStateRestoreCommand>;
|
|
if (command.type !== projectStateRestoreCommandType) {
|
|
throw new Error("Unsupported project state restore command.");
|
|
}
|
|
if (!command.payload || typeof command.payload !== "object") {
|
|
throw new Error("Project state restore payload is required.");
|
|
}
|
|
const payload = command.payload as Partial<ProjectStateRestoreCommandPayload>;
|
|
if (
|
|
typeof payload.expectedStateSha256 !== "string" ||
|
|
!/^[a-f0-9]{64}$/.test(payload.expectedStateSha256)
|
|
) {
|
|
throw new Error("Expected project state checksum is invalid.");
|
|
}
|
|
const targetState = parseProjectStateSnapshot(payload.targetState);
|
|
if (command.schemaVersion === snapshotRestoreCommandSchemaVersion) {
|
|
if (Object.keys(payload).length !== 2) {
|
|
throw new Error("Snapshot restore payload contains unknown fields.");
|
|
}
|
|
return;
|
|
}
|
|
if (command.schemaVersion !== projectStateRestoreCommandSchemaVersion) {
|
|
throw new Error("Unsupported project state restore command.");
|
|
}
|
|
const transferPayload = payload as Partial<ProjectTransferRestoreCommandPayload>;
|
|
parseEstimateBalances(
|
|
transferPayload.expectedEstimateBalances,
|
|
targetState.project.id
|
|
);
|
|
parseEstimateBalances(
|
|
transferPayload.targetEstimateBalances,
|
|
targetState.project.id
|
|
);
|
|
if (Object.keys(payload).length !== 4) {
|
|
throw new Error("Project transfer restore payload contains unknown fields.");
|
|
}
|
|
}
|
|
|
|
function parseEstimateBalances(
|
|
value: unknown,
|
|
projectId: string
|
|
): EstimateBalanceSnapshot[] {
|
|
if (!Array.isArray(value)) {
|
|
throw new Error("Project transfer estimate balances must be an array.");
|
|
}
|
|
const balances = value.map(parseEstimateBalanceSnapshot);
|
|
if (balances.some((balance) => balance.projectId !== projectId)) {
|
|
throw new Error("Project transfer estimate balance belongs to another project.");
|
|
}
|
|
if (new Set(balances.map((balance) => balance.id)).size !== balances.length) {
|
|
throw new Error("Project transfer contains duplicate estimate balances.");
|
|
}
|
|
return balances;
|
|
}
|