This commit is contained in:
Carl-Gerhard Lindesvärd
2026-01-09 19:58:24 +01:00
parent ca15717885
commit ba79ac570c
27 changed files with 1826 additions and 108 deletions

View File

@@ -0,0 +1,53 @@
-- CreateTable
CREATE TABLE "public"."share_dashboards" (
"id" TEXT NOT NULL,
"dashboardId" TEXT NOT NULL,
"organizationId" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"public" BOOLEAN NOT NULL DEFAULT false,
"password" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateTable
CREATE TABLE "public"."share_reports" (
"id" TEXT NOT NULL,
"reportId" UUID NOT NULL,
"organizationId" TEXT NOT NULL,
"projectId" TEXT NOT NULL,
"public" BOOLEAN NOT NULL DEFAULT false,
"password" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP
);
-- CreateIndex
CREATE UNIQUE INDEX "share_dashboards_id_key" ON "public"."share_dashboards"("id");
-- CreateIndex
CREATE UNIQUE INDEX "share_dashboards_dashboardId_key" ON "public"."share_dashboards"("dashboardId");
-- CreateIndex
CREATE UNIQUE INDEX "share_reports_id_key" ON "public"."share_reports"("id");
-- CreateIndex
CREATE UNIQUE INDEX "share_reports_reportId_key" ON "public"."share_reports"("reportId");
-- AddForeignKey
ALTER TABLE "public"."share_dashboards" ADD CONSTRAINT "share_dashboards_dashboardId_fkey" FOREIGN KEY ("dashboardId") REFERENCES "public"."dashboards"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."share_dashboards" ADD CONSTRAINT "share_dashboards_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "public"."organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."share_dashboards" ADD CONSTRAINT "share_dashboards_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "public"."projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."share_reports" ADD CONSTRAINT "share_reports_reportId_fkey" FOREIGN KEY ("reportId") REFERENCES "public"."reports"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."share_reports" ADD CONSTRAINT "share_reports_organizationId_fkey" FOREIGN KEY ("organizationId") REFERENCES "public"."organizations"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "public"."share_reports" ADD CONSTRAINT "share_reports_projectId_fkey" FOREIGN KEY ("projectId") REFERENCES "public"."projects"("id") ON DELETE CASCADE ON UPDATE CASCADE;

View File

@@ -46,16 +46,18 @@ model Chat {
}
model Organization {
id String @id @default(dbgenerated("gen_random_uuid()"))
id String @id @default(dbgenerated("gen_random_uuid()"))
name String
projects Project[]
members Member[]
createdByUserId String?
createdBy User? @relation(name: "organizationCreatedBy", fields: [createdByUserId], references: [id], onDelete: SetNull)
createdBy User? @relation(name: "organizationCreatedBy", fields: [createdByUserId], references: [id], onDelete: SetNull)
ProjectAccess ProjectAccess[]
Client Client[]
Dashboard Dashboard[]
ShareOverview ShareOverview[]
ShareDashboard ShareDashboard[]
ShareReport ShareReport[]
integrations Integration[]
invites Invite[]
timezone String?
@@ -185,13 +187,15 @@ model Project {
/// [IPrismaProjectFilters]
filters Json @default("[]")
clients Client[]
reports Report[]
dashboards Dashboard[]
share ShareOverview?
meta EventMeta[]
references Reference[]
access ProjectAccess[]
clients Client[]
reports Report[]
dashboards Dashboard[]
share ShareOverview?
shareDashboards ShareDashboard[]
shareReports ShareReport[]
meta EventMeta[]
references Reference[]
access ProjectAccess[]
notificationRules NotificationRule[]
notifications Notification[]
@@ -283,13 +287,14 @@ enum ChartType {
}
model Dashboard {
id String @id @default(dbgenerated("gen_random_uuid()"))
id String @id @default(dbgenerated("gen_random_uuid()"))
name String
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
reports Report[]
share ShareDashboard?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@ -328,6 +333,7 @@ model Report {
dashboardId String
dashboard Dashboard @relation(fields: [dashboardId], references: [id], onDelete: Cascade)
layout ReportLayout?
share ShareReport?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@ -372,6 +378,38 @@ model ShareOverview {
@@map("shares")
}
model ShareDashboard {
id String @unique
dashboardId String @unique
dashboard Dashboard @relation(fields: [dashboardId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
public Boolean @default(false)
password String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@map("share_dashboards")
}
model ShareReport {
id String @unique
reportId String @unique @db.Uuid
report Report @relation(fields: [reportId], references: [id], onDelete: Cascade)
organization Organization @relation(fields: [organizationId], references: [id], onDelete: Cascade)
organizationId String
projectId String
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
public Boolean @default(false)
password String?
createdAt DateTime @default(now())
updatedAt DateTime @default(now()) @updatedAt
@@map("share_reports")
}
model EventMeta {
id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid
name String

View File

@@ -18,3 +18,100 @@ export function getShareByProjectId(projectId: string) {
},
});
}
// Dashboard sharing functions
export function getShareDashboardById(id: string) {
return db.shareDashboard.findFirst({
where: {
id,
},
include: {
dashboard: {
include: {
project: true,
},
},
},
});
}
export function getShareDashboardByDashboardId(dashboardId: string) {
return db.shareDashboard.findUnique({
where: {
dashboardId,
},
});
}
// Report sharing functions
export function getShareReportById(id: string) {
return db.shareReport.findFirst({
where: {
id,
},
include: {
report: {
include: {
project: true,
},
},
},
});
}
export function getShareReportByReportId(reportId: string) {
return db.shareReport.findUnique({
where: {
reportId,
},
});
}
// Validation for secure endpoints
export async function validateReportAccess(
reportId: string,
shareId: string,
shareType: 'dashboard' | 'report',
) {
if (shareType === 'dashboard') {
const share = await db.shareDashboard.findUnique({
where: { id: shareId },
include: {
dashboard: {
include: {
reports: {
where: { id: reportId },
},
},
},
},
});
if (!share || !share.public) {
throw new Error('Share not found or not public');
}
if (!share.dashboard.reports.some((r) => r.id === reportId)) {
throw new Error('Report does not belong to this dashboard');
}
return share;
} else {
const share = await db.shareReport.findUnique({
where: { id: shareId },
include: {
report: true,
},
});
if (!share || !share.public) {
throw new Error('Share not found or not public');
}
if (share.reportId !== reportId) {
throw new Error('Report ID mismatch');
}
return share;
}
}