feat: add scoped context payload and rollbackable document changes
This commit is contained in:
@@ -41,16 +41,28 @@ type DealSnapshotRow = {
|
||||
summary: string | null;
|
||||
};
|
||||
|
||||
type WorkspaceDocumentSnapshotRow = {
|
||||
id: string;
|
||||
teamId: string;
|
||||
title: string;
|
||||
type: string;
|
||||
owner: string;
|
||||
scope: string;
|
||||
summary: string;
|
||||
body: string;
|
||||
};
|
||||
|
||||
export type SnapshotState = {
|
||||
calendarById: Map<string, CalendarSnapshotRow>;
|
||||
noteByContactId: Map<string, ContactNoteSnapshotRow>;
|
||||
messageById: Map<string, MessageSnapshotRow>;
|
||||
dealById: Map<string, DealSnapshotRow>;
|
||||
documentById: Map<string, WorkspaceDocumentSnapshotRow>;
|
||||
};
|
||||
|
||||
export type ChangeItem = {
|
||||
id: string;
|
||||
entity: "calendar_event" | "contact_note" | "message" | "deal";
|
||||
entity: "calendar_event" | "contact_note" | "message" | "deal" | "workspace_document";
|
||||
entityId: string | null;
|
||||
action: "created" | "updated" | "deleted";
|
||||
title: string;
|
||||
@@ -65,7 +77,9 @@ type UndoOp =
|
||||
| { kind: "delete_contact_message"; id: string }
|
||||
| { kind: "restore_contact_message"; data: MessageSnapshotRow }
|
||||
| { kind: "restore_contact_note"; contactId: string; content: string | null }
|
||||
| { kind: "restore_deal"; id: string; stage: string; nextStep: string | null; summary: string | null };
|
||||
| { kind: "restore_deal"; id: string; stage: string; nextStep: string | null; summary: string | null }
|
||||
| { kind: "delete_workspace_document"; id: string }
|
||||
| { kind: "restore_workspace_document"; data: WorkspaceDocumentSnapshotRow };
|
||||
|
||||
export type ChangeSet = {
|
||||
id: string;
|
||||
@@ -95,8 +109,12 @@ function toDealText(row: DealSnapshotRow) {
|
||||
return `${row.title} (${row.contactName}) · ${row.stage}${row.nextStep ? ` · next: ${row.nextStep}` : ""}`;
|
||||
}
|
||||
|
||||
function toWorkspaceDocumentText(row: WorkspaceDocumentSnapshotRow) {
|
||||
return `${row.title} · ${row.type} · ${row.owner} · ${row.scope} · ${row.summary}`;
|
||||
}
|
||||
|
||||
export async function captureSnapshot(prisma: PrismaClient, teamId: string): Promise<SnapshotState> {
|
||||
const [calendar, notes, messages, deals] = await Promise.all([
|
||||
const [calendar, notes, messages, deals, documents] = await Promise.all([
|
||||
prisma.calendarEvent.findMany({
|
||||
where: { teamId },
|
||||
select: {
|
||||
@@ -129,6 +147,20 @@ export async function captureSnapshot(prisma: PrismaClient, teamId: string): Pro
|
||||
include: { contact: { select: { name: true } } },
|
||||
take: 4000,
|
||||
}),
|
||||
prisma.workspaceDocument.findMany({
|
||||
where: { teamId },
|
||||
select: {
|
||||
id: true,
|
||||
teamId: true,
|
||||
title: true,
|
||||
type: true,
|
||||
owner: true,
|
||||
scope: true,
|
||||
summary: true,
|
||||
body: true,
|
||||
},
|
||||
take: 4000,
|
||||
}),
|
||||
]);
|
||||
|
||||
return {
|
||||
@@ -188,6 +220,21 @@ export async function captureSnapshot(prisma: PrismaClient, teamId: string): Pro
|
||||
},
|
||||
]),
|
||||
),
|
||||
documentById: new Map(
|
||||
documents.map((row) => [
|
||||
row.id,
|
||||
{
|
||||
id: row.id,
|
||||
teamId: row.teamId,
|
||||
title: row.title,
|
||||
type: row.type,
|
||||
owner: row.owner,
|
||||
scope: row.scope,
|
||||
summary: row.summary,
|
||||
body: row.body,
|
||||
},
|
||||
]),
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -326,6 +373,53 @@ export function buildChangeSet(before: SnapshotState, after: SnapshotState): Cha
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, row] of after.documentById) {
|
||||
const prev = before.documentById.get(id);
|
||||
if (!prev) {
|
||||
pushItem({
|
||||
entity: "workspace_document",
|
||||
entityId: row.id,
|
||||
action: "created",
|
||||
title: `Document created: ${row.title}`,
|
||||
before: "",
|
||||
after: toWorkspaceDocumentText(row),
|
||||
undo: [{ kind: "delete_workspace_document", id }],
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
prev.title !== row.title ||
|
||||
prev.type !== row.type ||
|
||||
prev.owner !== row.owner ||
|
||||
prev.scope !== row.scope ||
|
||||
prev.summary !== row.summary ||
|
||||
prev.body !== row.body
|
||||
) {
|
||||
pushItem({
|
||||
entity: "workspace_document",
|
||||
entityId: row.id,
|
||||
action: "updated",
|
||||
title: `Document updated: ${row.title}`,
|
||||
before: toWorkspaceDocumentText(prev),
|
||||
after: toWorkspaceDocumentText(row),
|
||||
undo: [{ kind: "restore_workspace_document", data: prev }],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
for (const [id, row] of before.documentById) {
|
||||
if (after.documentById.has(id)) continue;
|
||||
pushItem({
|
||||
entity: "workspace_document",
|
||||
entityId: row.id,
|
||||
action: "deleted",
|
||||
title: `Document deleted: ${row.title}`,
|
||||
before: toWorkspaceDocumentText(row),
|
||||
after: "",
|
||||
undo: [{ kind: "restore_workspace_document", data: row }],
|
||||
});
|
||||
}
|
||||
|
||||
if (items.length === 0) return null;
|
||||
|
||||
const created = items.filter((x) => x.action === "created").length;
|
||||
@@ -440,6 +534,38 @@ async function applyUndoOps(prisma: PrismaClient, teamId: string, undoOps: UndoO
|
||||
summary: op.summary,
|
||||
},
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (op.kind === "delete_workspace_document") {
|
||||
await tx.workspaceDocument.deleteMany({ where: { id: op.id, teamId } });
|
||||
continue;
|
||||
}
|
||||
|
||||
if (op.kind === "restore_workspace_document") {
|
||||
const row = op.data;
|
||||
await tx.workspaceDocument.upsert({
|
||||
where: { id: row.id },
|
||||
update: {
|
||||
teamId: row.teamId,
|
||||
title: row.title,
|
||||
type: row.type as any,
|
||||
owner: row.owner,
|
||||
scope: row.scope,
|
||||
summary: row.summary,
|
||||
body: row.body,
|
||||
},
|
||||
create: {
|
||||
id: row.id,
|
||||
teamId: row.teamId,
|
||||
title: row.title,
|
||||
type: row.type as any,
|
||||
owner: row.owner,
|
||||
scope: row.scope,
|
||||
summary: row.summary,
|
||||
body: row.body,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user