- Add mailbox persistence mapper for domain object mapping. - Create Sequelize repository for mailbox data access. - Define common mailbox schemas and types for validation. - Implement mailbox API for frontend communication. - Develop mailbox form component for creating and editing mailboxes. - Add internationalization support for mailbox labels and messages. - Create mailbox use form for managing mailbox usage periods. - Build mailboxes page to display and manage mailboxes and their usage.
196 lines
7.8 KiB
TypeScript
196 lines
7.8 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 { IMailboxRepository, MailboxContext } from "../../application/mailbox.repository";
|
|
import { AccessDefinitionSchema } from "../../domain/access/access-definition";
|
|
import { Mailbox } from "../../domain/mailbox.aggregate";
|
|
import type { MailboxUse } from "../../domain/mailbox-use.aggregate";
|
|
import { MailboxError } from "../../domain/mailbox-values";
|
|
|
|
import {
|
|
MailboxPersistenceMapper,
|
|
type MailboxRevisionRow,
|
|
type MailboxRow,
|
|
type MailboxUseRow,
|
|
mailboxIdentity,
|
|
} from "./mailbox-persistence.mapper";
|
|
|
|
export class SequelizeMailboxRepository implements IMailboxRepository {
|
|
private readonly mapper = new MailboxPersistenceMapper();
|
|
constructor(private readonly database: Sequelize) {}
|
|
async load(
|
|
companyId: UniqueID,
|
|
transaction: Transaction,
|
|
write: boolean
|
|
): Promise<Result<MailboxContext, MailboxError>> {
|
|
const replacements = { companyId: companyId.toPrimitive() };
|
|
const companies = await this.database.query<{ id: string }>(
|
|
`SELECT id FROM companies WHERE id=:companyId${write ? " FOR UPDATE" : ""}`,
|
|
{ replacements, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
if (!companies.length) {
|
|
return Result.fail(new MailboxError("COMPANY_NOT_FOUND"));
|
|
}
|
|
const rows = await this.database.query<MailboxRow>(
|
|
"SELECT * FROM mailboxes WHERE companyId=:companyId",
|
|
{ replacements, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const revisions = await this.database.query<MailboxRevisionRow>(
|
|
"SELECT r.* FROM mailbox_revisions r JOIN mailboxes m ON m.id=r.itemId WHERE m.companyId=:companyId",
|
|
{ replacements, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const useRows = await this.database.query<MailboxUseRow>(
|
|
"SELECT * FROM mailbox_uses WHERE companyId=:companyId",
|
|
{ replacements, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const useRevisions = await this.database.query<MailboxRevisionRow>(
|
|
"SELECT r.* FROM mailbox_use_revisions r JOIN mailbox_uses m ON m.id=r.itemId WHERE m.companyId=:companyId",
|
|
{ replacements, transaction, type: QueryTypes.SELECT }
|
|
);
|
|
const people = await this.database.query<{ id: string; active: boolean }>(
|
|
"SELECT id,active FROM company_contacts WHERE companyId=:companyId",
|
|
{ replacements, transaction, 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",
|
|
{ replacements, 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: MailboxContext = {
|
|
mailboxes: rows.map((row) =>
|
|
this.mapper.toDomain(
|
|
row,
|
|
revisions.filter((revision) => revision.itemId === row.id)
|
|
)
|
|
),
|
|
uses: useRows.map((row) =>
|
|
this.mapper.toUseDomain(
|
|
row,
|
|
useRevisions.filter((revision) => revision.itemId === row.id)
|
|
)
|
|
),
|
|
people: people.map((person) => ({
|
|
id: mailboxIdentity(person.id),
|
|
active: Boolean(person.active),
|
|
})),
|
|
items: items.map((item) => ({
|
|
id: mailboxIdentity(item.id),
|
|
values: AccessDefinitionSchema.parse(
|
|
typeof item.values === "string" ? JSON.parse(item.values) : item.values
|
|
),
|
|
})),
|
|
actors: new Map(actors.map((actor) => [actor.id, actor.name])),
|
|
today: new Date().toISOString().slice(0, 10),
|
|
};
|
|
if (
|
|
context.mailboxes.some((mailbox) =>
|
|
mailbox.history.some((revision) => {
|
|
const account = revision.values.accountId.getOrUndefined();
|
|
return (
|
|
account &&
|
|
!context.items.some((item) => item.id.equals(account) && item.values.kind === "account")
|
|
);
|
|
})
|
|
) ||
|
|
context.uses.some(
|
|
(use) =>
|
|
!(
|
|
context.mailboxes.some((item) => item.id.equals(use.values.mailboxId)) &&
|
|
context.people.some((item) => item.id.equals(use.values.personId))
|
|
)
|
|
)
|
|
) {
|
|
throw new Error("Ámbito de buzón persistido inconsistente.");
|
|
}
|
|
return Result.ok(context);
|
|
}
|
|
private async saveRevisions(
|
|
item: Mailbox | MailboxUse,
|
|
previous: Mailbox | MailboxUse | undefined,
|
|
transaction: Transaction
|
|
) {
|
|
const row = this.mapper.toPersistence(item);
|
|
const existing = new Set(previous?.history.map((revision) => revision.id.toPrimitive()));
|
|
for (const revision of row.revisions.filter((revision) => !existing.has(revision.id))) {
|
|
const table = item instanceof Mailbox ? "mailbox_revisions" : "mailbox_use_revisions";
|
|
await this.database.query(
|
|
`INSERT INTO ${table} (id,itemId,position,actorId,recordedAt,\`values\`) VALUES (:id,:itemId,:position,:actorId,:recordedAt,:values)`,
|
|
{ replacements: revision, transaction }
|
|
);
|
|
}
|
|
}
|
|
get(companyId: UniqueID) {
|
|
return this.database.transaction((transaction) => this.load(companyId, transaction, false));
|
|
}
|
|
save(companyId: UniqueID, action: Parameters<IMailboxRepository["save"]>[1]) {
|
|
return this.database.transaction(async (transaction) => {
|
|
const loaded = await this.load(companyId, transaction, true);
|
|
if (!loaded.isSuccess) {
|
|
return loaded;
|
|
}
|
|
const candidate = action(loaded.data);
|
|
if (!candidate.isSuccess) {
|
|
return candidate;
|
|
}
|
|
const item = candidate.data;
|
|
const previous =
|
|
item instanceof Mailbox
|
|
? loaded.data.mailboxes.find((old) => old.id.equals(item.id))
|
|
: loaded.data.uses.find((old) => old.id.equals(item.id));
|
|
if (previous !== item) {
|
|
const row = this.mapper.toPersistence(item);
|
|
if (item instanceof Mailbox) {
|
|
const values = item.values.toPrimitive();
|
|
await this.database.query(
|
|
"INSERT INTO mailboxes (id,companyId,accountId,addressKey,`values`) VALUES (:id,:companyId,:accountId,:addressKey,:values) ON DUPLICATE KEY UPDATE accountId=VALUES(accountId),addressKey=VALUES(addressKey),`values`=VALUES(`values`)",
|
|
{
|
|
replacements: {
|
|
id: row.id,
|
|
companyId: row.companyId,
|
|
values: row.values,
|
|
accountId: values.accountId,
|
|
addressKey: `${row.companyId}:${values.address}`,
|
|
},
|
|
transaction,
|
|
}
|
|
);
|
|
} else {
|
|
const values = item.values.toPrimitive();
|
|
await this.database.query(
|
|
"INSERT INTO mailbox_uses (id,companyId,mailboxId,personId,`values`) VALUES (:id,:companyId,:mailboxId,:personId,:values) ON DUPLICATE KEY UPDATE `values`=VALUES(`values`)",
|
|
{
|
|
replacements: {
|
|
id: row.id,
|
|
companyId: row.companyId,
|
|
values: row.values,
|
|
mailboxId: values.mailboxId,
|
|
personId: values.personId,
|
|
},
|
|
transaction,
|
|
}
|
|
);
|
|
}
|
|
await this.saveRevisions(item, previous, transaction);
|
|
}
|
|
return Result.ok({
|
|
item,
|
|
context: {
|
|
...loaded.data,
|
|
mailboxes:
|
|
item instanceof Mailbox
|
|
? [...loaded.data.mailboxes.filter((old) => !old.id.equals(item.id)), item]
|
|
: loaded.data.mailboxes,
|
|
uses:
|
|
item instanceof Mailbox
|
|
? loaded.data.uses
|
|
: [...loaded.data.uses.filter((old) => !old.id.equals(item.id)), item],
|
|
},
|
|
});
|
|
});
|
|
}
|
|
}
|