56 lines
5.4 KiB
TypeScript
56 lines
5.4 KiB
TypeScript
|
|
import type {UniqueID} from "@repo/rdx-ddd";
|
||
|
|
import {Result} from "@repo/rdx-utils";
|
||
|
|
import {QueryTypes,type Sequelize,type Transaction} from "sequelize";
|
||
|
|
import type {FolderContext,IFolderMappingRepository} from "../../application/folder-mapping.repository";
|
||
|
|
import type {IWorkProfileInventoryService} from "../../application/work-profile.repository";
|
||
|
|
import {folderReferences} from "../../application/folder-mapping-policy";
|
||
|
|
import {AccessDefinitionSchema} from "../../domain/access/access-definition";
|
||
|
|
import {AccessValues} from "../../domain/access/access-values";
|
||
|
|
import {FolderMappingError} from "../../domain/folder-mapping-values";
|
||
|
|
import {FolderMappingPersistenceMapper,type FolderRow,type FolderRevisionRow} from "./folder-mapping-persistence.mapper";
|
||
|
|
import {WorkProfilePersistenceMapper,type ProfileRow,type ProfileRevisionRow,profileIdentity,persistedJson} 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()},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));}
|
||
|
|
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,`values`,currentKey,driveKey) VALUES (:id,:companyId,:profileId,:needId,:values,:currentKey,:driveKey) ON DUPLICATE KEY UPDATE `values`=VALUES(`values`),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,`values`) VALUES (:id,:mappingId,:position,:actorId,:recordedAt,:values)",{replacements:revision,transaction});
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return Result.ok({mapping:candidate.data,context:loaded.data});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
}
|