Track HiveGrid Cloud planning reference
This commit is contained in:
1 parent
b3d5ea6764
commit
e341c5938a
5784 files changed
+1104929
-3
No files matched your search
@@ -0,0 +1,109 @@
|
||||
// P4 EntityLink (AK-P4b) §5c — Gate W1 generic cross-entity link service.
|
||||
//
|
||||
// Tenant-scoped, idempotent CRUD over the polymorphic `EntityLink` table. Both
|
||||
// sides (source + target) are polymorphic, so any module docks onto this one
|
||||
// service instead of building its own. No CRM coupling here — wiring concrete
|
||||
// entities (Task<->Contact, etc.) happens in P6. NO React/UI imports.
|
||||
//
|
||||
// RLS-ready (AK-P4b "Teil A", mirrors fileService): every EntityLink access runs
|
||||
// inside withTenantRLS() so the tenant GUC is set before the query. Functional
|
||||
// no-op until the EntityLink RLS policy is activated (prisma/rls/task-isolation.sql,
|
||||
// P5 cutover). EntityLink is the only table this service touches, so each function
|
||||
// is a single withTenantRLS() block (no nested transactions to worry about).
|
||||
|
||||
import { withTenantRLS } from "~/utils/db/.server/tenantRls.server";
|
||||
|
||||
export type LinkableEntityType = "contact" | "company" | "lead" | "deal" | "project" | "task" | "file" | "user" | "order" | "appointment" | "form_submission";
|
||||
|
||||
export type EntityLinkRow = {
|
||||
id: string;
|
||||
sourceType: LinkableEntityType;
|
||||
sourceId: string;
|
||||
targetType: LinkableEntityType;
|
||||
targetId: string;
|
||||
linkRole: string | null;
|
||||
createdAt: Date;
|
||||
};
|
||||
|
||||
export type EntityRef = { entityType: LinkableEntityType; entityId: string };
|
||||
|
||||
/**
|
||||
* Create (or update the role of) a directed link between two entities. Idempotent
|
||||
* on the unique tuple (tenantId, source*, target*) — a repeated call only
|
||||
* refreshes `linkRole`.
|
||||
*/
|
||||
export async function linkEntities(input: {
|
||||
tenantId: string;
|
||||
userId: string;
|
||||
sourceType: LinkableEntityType;
|
||||
sourceId: string;
|
||||
targetType: LinkableEntityType;
|
||||
targetId: string;
|
||||
linkRole?: string | null;
|
||||
}): Promise<void> {
|
||||
await withTenantRLS(input.tenantId, (tx) =>
|
||||
tx.entityLink.upsert({
|
||||
where: {
|
||||
tenantId_sourceType_sourceId_targetType_targetId: {
|
||||
tenantId: input.tenantId,
|
||||
sourceType: input.sourceType,
|
||||
sourceId: input.sourceId,
|
||||
targetType: input.targetType,
|
||||
targetId: input.targetId,
|
||||
},
|
||||
},
|
||||
update: { linkRole: input.linkRole ?? null },
|
||||
create: {
|
||||
tenantId: input.tenantId,
|
||||
createdByUserId: input.userId,
|
||||
sourceType: input.sourceType,
|
||||
sourceId: input.sourceId,
|
||||
targetType: input.targetType,
|
||||
targetId: input.targetId,
|
||||
linkRole: input.linkRole ?? null,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** Remove a directed link between two entities (no-op if absent). */
|
||||
export async function unlinkEntities(input: {
|
||||
tenantId: string;
|
||||
sourceType: LinkableEntityType;
|
||||
sourceId: string;
|
||||
targetType: LinkableEntityType;
|
||||
targetId: string;
|
||||
}): Promise<void> {
|
||||
await withTenantRLS(input.tenantId, (tx) =>
|
||||
tx.entityLink.deleteMany({
|
||||
where: {
|
||||
tenantId: input.tenantId,
|
||||
sourceType: input.sourceType,
|
||||
sourceId: input.sourceId,
|
||||
targetType: input.targetType,
|
||||
targetId: input.targetId,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* List all links touching an entity (as either source or target), tenant-scoped.
|
||||
* Returns rows oriented as stored; callers normalize direction as needed.
|
||||
*/
|
||||
export async function listLinksFor(input: { tenantId: string; entityType: LinkableEntityType; entityId: string }): Promise<EntityLinkRow[]> {
|
||||
const rows = await withTenantRLS(input.tenantId, (tx) =>
|
||||
tx.entityLink.findMany({
|
||||
where: {
|
||||
tenantId: input.tenantId,
|
||||
OR: [
|
||||
{ sourceType: input.entityType, sourceId: input.entityId },
|
||||
{ targetType: input.entityType, targetId: input.entityId },
|
||||
],
|
||||
},
|
||||
select: { id: true, sourceType: true, sourceId: true, targetType: true, targetId: true, linkRole: true, createdAt: true },
|
||||
orderBy: { createdAt: "asc" },
|
||||
}),
|
||||
);
|
||||
return rows as EntityLinkRow[];
|
||||
}
|
||||
Reference in new issue
Block a user