156 lines
6.1 KiB
TypeScript
156 lines
6.1 KiB
TypeScript
import type { IResourceInventoryService } from "@metis/resources/services";
|
|
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 {
|
|
IRemoteConnectionRepository,
|
|
RemoteContext,
|
|
} from "../../application/remote-connection.repository";
|
|
import { remoteReferences } from "../../application/remote-connection-policy";
|
|
import type { IWorkProfileInventoryService } from "../../application/work-profile.repository";
|
|
import { RemoteConnectionError } from "../../domain/remote-connection-values";
|
|
|
|
import { remoteSnapshot } from "./mail-remote-snapshots";
|
|
import type { RemoteRevisionRow, RemoteRow } from "./remote-connection-persistence.mapper";
|
|
import { RemoteConnectionPersistenceMapper } from "./remote-connection-persistence.mapper";
|
|
import { writeSnapshotRow } from "./scalar-snapshot-writer";
|
|
import type { TeleworkRevisionRow, TeleworkRow } from "./telework-authorization-persistence.mapper";
|
|
import {
|
|
TeleworkAuthorizationPersistenceMapper,
|
|
teleworkIdentity,
|
|
} from "./telework-authorization-persistence.mapper";
|
|
|
|
export class SequelizeRemoteConnectionRepository implements IRemoteConnectionRepository {
|
|
private readonly mapper = new RemoteConnectionPersistenceMapper();
|
|
|
|
constructor(
|
|
private readonly database: Sequelize,
|
|
private readonly inventory: IWorkProfileInventoryService,
|
|
private readonly resources: IResourceInventoryService
|
|
) {}
|
|
|
|
private async load(
|
|
company: UniqueID,
|
|
transaction: Transaction,
|
|
write: boolean
|
|
): Promise<Result<RemoteContext, RemoteConnectionError>> {
|
|
const companyId = company.toPrimitive();
|
|
const companies = await this.database.query<{ id: string }>(
|
|
`SELECT id FROM companies WHERE id=:companyId${write ? " FOR UPDATE" : ""}`,
|
|
{ replacements: { companyId }, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
|
|
if (!companies.length) {
|
|
return Result.fail(new RemoteConnectionError("COMPANY_NOT_FOUND"));
|
|
}
|
|
const rows = await this.database.query<RemoteRow>(
|
|
"SELECT * FROM remote_connections WHERE companyId=:companyId",
|
|
{ replacements: { companyId }, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const revisions = await this.database.query<RemoteRevisionRow>(
|
|
"SELECT r.* FROM remote_connection_revisions r JOIN remote_connections c ON c.id=r.connectionId WHERE c.companyId=:companyId",
|
|
{ replacements: { companyId }, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const people = await this.database.query<{ id: string; active: boolean }>(
|
|
"SELECT id,active FROM company_contacts WHERE companyId=:companyId",
|
|
{ replacements: { companyId }, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const sites = await this.database.query<{ id: string; active: boolean }>(
|
|
"SELECT id,active FROM company_sites WHERE companyId=:companyId",
|
|
{ replacements: { companyId }, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const authorizations = await this.database.query<TeleworkRow>(
|
|
"SELECT * FROM telework_authorizations WHERE companyId=:companyId",
|
|
{ replacements: { companyId }, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const authRevisions = await this.database.query<TeleworkRevisionRow>(
|
|
"SELECT r.* FROM telework_authorization_revisions r JOIN telework_authorizations a ON a.id=r.authorizationId WHERE a.companyId=:companyId",
|
|
{ replacements: { companyId }, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const actors = await this.database.query<{ id: string; name: string }>(
|
|
"SELECT id,name FROM users",
|
|
{ transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const context: RemoteContext = {
|
|
connections: rows.map((row) =>
|
|
this.mapper.toDomain(
|
|
row,
|
|
revisions.filter((r) => r.connectionId === row.id)
|
|
)
|
|
),
|
|
authorizations: authorizations.map((row) =>
|
|
new TeleworkAuthorizationPersistenceMapper().toDomain(
|
|
row,
|
|
authRevisions.filter((r) => r.authorizationId === row.id)
|
|
)
|
|
),
|
|
people: people.map((p) => ({ id: teleworkIdentity(p.id), active: Boolean(p.active) })),
|
|
sites: sites.map((s) => ({ id: teleworkIdentity(s.id), active: Boolean(s.active) })),
|
|
assets: await this.inventory.list(company),
|
|
resources: await this.resources.list(company),
|
|
actors: new Map(actors.map((a) => [a.id, a.name])),
|
|
today: new Date().toISOString().slice(0, 10),
|
|
};
|
|
|
|
if (
|
|
context.connections.some((item) => !remoteReferences(item.values, context)) ||
|
|
context.authorizations.some(
|
|
(item) => !context.people.some((p) => p.id.equals(item.values.personId))
|
|
)
|
|
) {
|
|
throw new Error("Ámbito de acceso remoto persistido inconsistente.");
|
|
}
|
|
|
|
return Result.ok(context);
|
|
}
|
|
|
|
get(company: UniqueID) {
|
|
return this.database.transaction((t) => this.load(company, t, false));
|
|
}
|
|
|
|
save(company: UniqueID, action: Parameters<IRemoteConnectionRepository["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.connections.find((item) => item.id.equals(candidate.data.id));
|
|
|
|
if (previous !== candidate.data) {
|
|
const record = this.mapper.toPersistence(candidate.data);
|
|
|
|
await writeSnapshotRow(
|
|
this.database,
|
|
"remote_connections",
|
|
remoteSnapshot,
|
|
record.root,
|
|
transaction,
|
|
previous ? "update" : "insert"
|
|
);
|
|
const stored = new Set(previous?.history.map((r) => r.id.toPrimitive()));
|
|
|
|
for (const revision of record.revisions.filter((r) => !stored.has(r.id))) {
|
|
await writeSnapshotRow(
|
|
this.database,
|
|
"remote_connection_revisions",
|
|
remoteSnapshot,
|
|
revision,
|
|
transaction,
|
|
"insert"
|
|
);
|
|
}
|
|
}
|
|
|
|
return Result.ok({ connection: candidate.data, context: loaded.data });
|
|
});
|
|
}
|
|
}
|