- Add SoftwareInstallationValues value object for encapsulating software installation data. - Create HTTP routes for managing software installations, including GET, POST, and DELETE operations. - Implement Sequelize repository for software installations, handling database interactions. - Add migration script for creating necessary database tables for software installations. - Create persistence mapper for transforming between domain models and database rows. - Define snapshot schema for software installations to ensure data consistency. - Introduce common schemas for software installations to validate API requests and responses. - Develop UI components for displaying and managing software installations, including history dialogs and forms. - Implement API client for interacting with software installation endpoints. - Write tests for software installations panel and form components to ensure functionality.
103 lines
2.8 KiB
TypeScript
103 lines
2.8 KiB
TypeScript
import type { UniqueID } from "@repo/rdx-ddd";
|
|
import { AggregateRoot } from "@repo/rdx-ddd";
|
|
import { Result } from "@repo/rdx-utils";
|
|
import { z } from "zod/v4";
|
|
|
|
import { SoftwareInstallationRevision } from "./software-installation-revision.entity";
|
|
import {
|
|
SOFTWARE_STATE_TRANSITIONS,
|
|
SoftwareInstallationError,
|
|
} from "./software-installation-values";
|
|
import type { SoftwareInstallationValues } from "./value-objects/software-installation-values.vo";
|
|
|
|
type Props = Readonly<{
|
|
companyId: UniqueID;
|
|
values: SoftwareInstallationValues;
|
|
history: readonly SoftwareInstallationRevision[];
|
|
}>;
|
|
|
|
export class SoftwareInstallation extends AggregateRoot<Props> {
|
|
private constructor(props: Props, id?: UniqueID) {
|
|
super(Object.freeze({ ...props, history: Object.freeze([...props.history]) }), id);
|
|
}
|
|
|
|
static create(
|
|
companyId: UniqueID,
|
|
values: SoftwareInstallationValues,
|
|
actorId: UniqueID,
|
|
recordedAt: string
|
|
): Result<SoftwareInstallation, SoftwareInstallationError> {
|
|
if (
|
|
!(
|
|
z.iso.datetime().safeParse(recordedAt).success &&
|
|
["pending", "configured"].includes(values.state)
|
|
)
|
|
) {
|
|
return Result.fail(new SoftwareInstallationError("INVALID_SOFTWARE_TRANSITION"));
|
|
}
|
|
|
|
return Result.ok(
|
|
new SoftwareInstallation({
|
|
companyId,
|
|
values,
|
|
history: [SoftwareInstallationRevision.create({ values, actorId, recordedAt })],
|
|
})
|
|
);
|
|
}
|
|
|
|
static rehydrate(props: Props, id: UniqueID) {
|
|
return new SoftwareInstallation(props, id);
|
|
}
|
|
|
|
get companyId() {
|
|
return this.props.companyId;
|
|
}
|
|
|
|
get values() {
|
|
return this.props.values;
|
|
}
|
|
|
|
get history() {
|
|
return this.props.history;
|
|
}
|
|
|
|
update(
|
|
values: SoftwareInstallationValues,
|
|
actorId: UniqueID,
|
|
recordedAt: string
|
|
): Result<SoftwareInstallation, SoftwareInstallationError> {
|
|
if (this.values.equals(values)) {
|
|
return Result.ok(this);
|
|
}
|
|
if (!this.values.sameTarget(values)) {
|
|
return Result.fail(new SoftwareInstallationError("SOFTWARE_TARGET_IMMUTABLE"));
|
|
}
|
|
if (
|
|
!SOFTWARE_STATE_TRANSITIONS[this.values.state].some((state) => state === values.state) ||
|
|
(this.values.closed && !this.values.equals(values))
|
|
) {
|
|
return Result.fail(new SoftwareInstallationError("INVALID_SOFTWARE_TRANSITION"));
|
|
}
|
|
if (
|
|
!z.iso.datetime().safeParse(recordedAt).success ||
|
|
recordedAt < (this.history.at(-1)?.recordedAt ?? "")
|
|
) {
|
|
return Result.fail(new SoftwareInstallationError("INVALID_REQUEST"));
|
|
}
|
|
|
|
return Result.ok(
|
|
new SoftwareInstallation(
|
|
{
|
|
...this.props,
|
|
values,
|
|
history: [
|
|
...this.history,
|
|
SoftwareInstallationRevision.create({ values, actorId, recordedAt }),
|
|
],
|
|
},
|
|
this.id
|
|
)
|
|
);
|
|
}
|
|
}
|