194 lines
7.7 KiB
TypeScript
194 lines
7.7 KiB
TypeScript
import type { UniqueID } from "@repo/rdx-ddd";
|
|
import { Result } from "@repo/rdx-utils";
|
|
import type { Sequelize, Transaction } from "sequelize";
|
|
import { QueryTypes } from "sequelize";
|
|
|
|
import type {
|
|
FolderContext,
|
|
IFolderMappingRepository,
|
|
} from "../../application/folder-mapping.repository";
|
|
import { folderReferences } from "../../application/folder-mapping-policy";
|
|
import type { IWorkProfileInventoryService } from "../../application/work-profile.repository";
|
|
import { AccessDefinitionSchema } from "../../domain/access/access-definition";
|
|
import { AccessValues } from "../../domain/access/access-values.vo";
|
|
import { FolderMappingError } from "../../domain/folder-mapping-values";
|
|
|
|
import type { FolderRevisionRow, FolderRow } from "./folder-mapping-persistence.mapper";
|
|
import { FolderMappingPersistenceMapper } from "./folder-mapping-persistence.mapper";
|
|
import type { ProfileRevisionRow, ProfileRow } from "./work-profile-persistence.mapper";
|
|
import {
|
|
WorkProfilePersistenceMapper,
|
|
persistedJson,
|
|
profileIdentity,
|
|
} from "./work-profile-persistence.mapper";
|
|
|
|
export class SequelizeFolderMappingRepository implements IFolderMappingRepository {
|
|
private readonly mapper = new FolderMappingPersistenceMapper();
|
|
|
|
constructor(
|
|
private readonly database: Sequelize,
|
|
private readonly inventory: IWorkProfileInventoryService
|
|
) {}
|
|
|
|
private async load(
|
|
company: UniqueID,
|
|
transaction: Transaction,
|
|
write: boolean
|
|
): Promise<Result<FolderContext, FolderMappingError>> {
|
|
const replacements = { companyId: company.toPrimitive() };
|
|
const options = { replacements, transaction, type: QueryTypes.SELECT };
|
|
const companies = await this.database.query<{ id: string }>(
|
|
`SELECT id FROM companies WHERE id=:companyId${write ? " FOR UPDATE" : ""}`,
|
|
{ ...options, type: QueryTypes.SELECT }
|
|
);
|
|
|
|
if (!companies.length) {
|
|
return Result.fail(new FolderMappingError("COMPANY_NOT_FOUND"));
|
|
}
|
|
const rows = await this.database.query<FolderRow>(
|
|
"SELECT * FROM folder_mappings WHERE companyId=:companyId",
|
|
{ ...options, type: QueryTypes.SELECT }
|
|
);
|
|
const revisions = await this.database.query<FolderRevisionRow>(
|
|
"SELECT r.* FROM folder_mapping_revisions r JOIN folder_mappings m ON m.id=r.mappingId WHERE m.companyId=:companyId",
|
|
{ ...options, type: QueryTypes.SELECT }
|
|
);
|
|
const profiles = await this.database.query<ProfileRow>(
|
|
"SELECT * FROM work_profiles WHERE companyId=:companyId",
|
|
{ ...options, type: QueryTypes.SELECT }
|
|
);
|
|
const profileRevisions = await this.database.query<ProfileRevisionRow>(
|
|
"SELECT r.* FROM work_profile_revisions r JOIN work_profiles p ON p.id=r.profileId WHERE p.companyId=:companyId",
|
|
{ ...options, type: QueryTypes.SELECT }
|
|
);
|
|
const items = await this.database.query<{ id: string; values: unknown }>(
|
|
"SELECT i.id,i.values FROM access_items i JOIN access_workspaces w ON w.id=i.workspaceId WHERE w.companyId=:companyId OR w.companyId IS NULL",
|
|
{ ...options, type: QueryTypes.SELECT }
|
|
);
|
|
const people = await this.database.query<{ id: string; active: boolean }>(
|
|
"SELECT id,active FROM company_contacts WHERE companyId=:companyId",
|
|
{ ...options, type: QueryTypes.SELECT }
|
|
);
|
|
const actors = await this.database.query<{ id: string; name: string }>(
|
|
"SELECT id,name FROM users",
|
|
{ transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const context: FolderContext = {
|
|
mappings: rows.map((row) =>
|
|
this.mapper.toDomain(
|
|
row,
|
|
revisions.filter((r) => r.mappingId === row.id)
|
|
)
|
|
),
|
|
profiles: profiles.map((row) =>
|
|
new WorkProfilePersistenceMapper().toDomain(
|
|
row,
|
|
profileRevisions.filter((r) => r.profileId === row.id)
|
|
)
|
|
),
|
|
items: items.map((item) => {
|
|
const values = AccessValues.create(
|
|
AccessDefinitionSchema.parse(persistedJson(item.values))
|
|
);
|
|
|
|
if (!values.isSuccess) {
|
|
throw new Error("Recurso de carpeta persistido inconsistente.");
|
|
}
|
|
|
|
return { id: profileIdentity(item.id), values: values.data };
|
|
}),
|
|
people: people.map((item) => ({
|
|
id: profileIdentity(item.id),
|
|
active: Boolean(item.active),
|
|
})),
|
|
assets: await this.inventory.list(company),
|
|
actors: new Map(actors.map((actor) => [actor.id, actor.name])),
|
|
today: new Date().toISOString().slice(0, 10),
|
|
};
|
|
|
|
if (
|
|
context.profiles.some(
|
|
(profile) =>
|
|
!(
|
|
context.people.some((person) => person.id.equals(profile.values.personId)) &&
|
|
context.assets.some((asset) => asset.id.equals(profile.values.assetId))
|
|
)
|
|
) ||
|
|
context.mappings.some((mapping) => !folderReferences(mapping.values, context))
|
|
) {
|
|
throw new Error("Ámbito de carpeta persistido inconsistente.");
|
|
}
|
|
|
|
return Result.ok(context);
|
|
}
|
|
|
|
get(company: UniqueID) {
|
|
return this.database.transaction((t) => this.load(company, t, false));
|
|
}
|
|
|
|
remove(company: UniqueID, id: UniqueID): Promise<Result<void, FolderMappingError>> {
|
|
return this.database.transaction(async (transaction) => {
|
|
const replacements = { companyId: company.toPrimitive(), id: id.toPrimitive() };
|
|
|
|
await this.database.query("SELECT id FROM companies WHERE id=:companyId FOR UPDATE", {
|
|
replacements,
|
|
transaction,
|
|
type: QueryTypes.SELECT,
|
|
});
|
|
const rows = await this.database.query<{ id: string }>(
|
|
"SELECT id FROM folder_mappings WHERE companyId=:companyId AND id=:id FOR UPDATE",
|
|
{ replacements, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
|
|
if (!rows.length) {
|
|
return Result.fail(new FolderMappingError("FOLDER_MAPPING_NOT_FOUND"));
|
|
}
|
|
await this.database.query("DELETE FROM folder_mapping_revisions WHERE mappingId=:id", {
|
|
replacements,
|
|
transaction,
|
|
});
|
|
await this.database.query(
|
|
"DELETE FROM folder_mappings WHERE companyId=:companyId AND id=:id",
|
|
{ replacements, transaction }
|
|
);
|
|
|
|
return Result.ok(undefined);
|
|
});
|
|
}
|
|
|
|
save(company: UniqueID, action: Parameters<IFolderMappingRepository["save"]>[1]) {
|
|
return this.database.transaction(async (transaction) => {
|
|
const loaded = await this.load(company, transaction, true);
|
|
|
|
if (!loaded.isSuccess) {
|
|
return loaded;
|
|
}
|
|
const candidate = action(loaded.data);
|
|
|
|
if (!candidate.isSuccess) {
|
|
return candidate;
|
|
}
|
|
const previous = loaded.data.mappings.find((item) => item.id.equals(candidate.data.id));
|
|
|
|
if (previous !== candidate.data) {
|
|
const record = this.mapper.toPersistence(candidate.data);
|
|
|
|
await this.database.query(
|
|
"INSERT INTO folder_mappings (id,companyId,profileId,needId,path,driveLetter,state,notes,currentKey,driveKey) VALUES (:id,:companyId,:profileId,:needId,:path,:driveLetter,:state,:notes,:currentKey,:driveKey) ON DUPLICATE KEY UPDATE path=VALUES(path),driveLetter=VALUES(driveLetter),state=VALUES(state),notes=VALUES(notes),currentKey=VALUES(currentKey),driveKey=VALUES(driveKey)",
|
|
{ replacements: record.root, transaction }
|
|
);
|
|
const stored = new Set(previous?.history.map((r) => r.id.toPrimitive()));
|
|
|
|
for (const revision of record.revisions.filter((r) => !stored.has(r.id))) {
|
|
await this.database.query(
|
|
"INSERT INTO folder_mapping_revisions (id,mappingId,position,actorId,recordedAt,profileId,needId,path,driveLetter,state,notes) VALUES (:id,:mappingId,:position,:actorId,:recordedAt,:profileId,:needId,:path,:driveLetter,:state,:notes)",
|
|
{ replacements: revision, transaction }
|
|
);
|
|
}
|
|
}
|
|
|
|
return Result.ok({ mapping: candidate.data, context: loaded.data });
|
|
});
|
|
}
|
|
}
|