import type { UniqueID } from "@repo/rdx-ddd"; import { AggregateRoot } from "@repo/rdx-ddd"; import type { Maybe } from "@repo/rdx-utils"; import { Result } from "@repo/rdx-utils"; import type { Person } from "../person.entity"; import { AccessError } from "./access-error"; import type { AccessItem, AuditContext } from "./access-item.entity"; import { AccessPolicy, accessConflict, checkCatalog, checkConfiguration, checkRelations, } from "./access-policy"; import type { AccessValues } from "./access-values.vo"; export type AccessContext = Readonly<{ catalog: readonly AccessItem[]; people: readonly Person[]; today: string; }>; export class AccessWorkspace extends AggregateRoot<{ companyId: Maybe; items: readonly AccessItem[]; }> { protected constructor( props: { companyId: Maybe; items: readonly AccessItem[] }, id: UniqueID ) { super({ ...props, items: Object.freeze([...props.items]) }, id); } static create(companyId: Maybe, id: UniqueID) { return new AccessWorkspace({ companyId, items: [] }, id); } static rehydrate(companyId: Maybe, items: readonly AccessItem[], id: UniqueID) { return new AccessWorkspace({ companyId, items }, id); } get companyId() { return this.props.companyId; } get items() { return this.props.items; } save( candidate: AccessItem, context: AccessContext, previous?: AccessItem ): Result { const value = candidate.values.data; if ( this.companyId.getOrUndefined()?.toPrimitive() !== candidate.companyId.getOrUndefined()?.toPrimitive() ) { return Result.fail(new AccessError("SCOPE_MISMATCH")); } const global = ["system", "resource_type", "permission", "template"]; if (!(this.companyId.getOrUndefined() || global.includes(value.kind))) { return Result.fail(new AccessError("SCOPE_MISMATCH")); } if (value.kind === "template" && this.companyId.getOrUndefined()) { return Result.fail(new AccessError("SCOPE_MISMATCH")); } const all = [...context.catalog, ...this.items.filter((item) => !item.id.equals(candidate.id))]; const isNewOrReopen = !previous || ("endDate" in value && value.endDate === null && "endDate" in previous.values.data && previous.values.data.endDate !== null); const policy = new AccessPolicy(this.companyId, all, context, isNewOrReopen); const failure = checkCatalog(value, policy) ?? checkRelations(value, policy) ?? checkConfiguration(value, previous); if (failure) { return failure; } const duplicates = all.some((other) => accessConflict(candidate, other, all)); if (duplicates) { return Result.fail(new AccessError("ACCESS_CONFLICT")); } if (previous === candidate) { return Result.ok(this); } return Result.ok( new AccessWorkspace( { companyId: this.companyId, items: previous ? this.items.map((item) => (item.id.equals(candidate.id) ? candidate : item)) : [...this.items, candidate], }, this.id ) ); } update( id: UniqueID, values: AccessValues, audit: AuditContext, context: AccessContext ): Result { const previous = this.items.find((item) => item.id.equals(id)); if (!previous) { return Result.fail(new AccessError("ACCESS_NOT_FOUND")); } const candidate = previous.update(values, audit); return candidate.isSuccess ? this.save(candidate.data, context, previous) : candidate; } }