create new schema for reports

This commit is contained in:
Nate Kelley 2025-08-04 14:03:52 -06:00
parent 9173fad35d
commit aae819e151
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
9 changed files with 6960 additions and 4 deletions

View File

@ -3,6 +3,7 @@ import {
getCollectionTitle,
getDashboardTitle,
getMetricTitle,
getReportTitle,
getUserOrganizationId,
} from '@buster/database';
import { GetTitleRequestSchema, type GetTitleResponse } from '@buster/server-shared/title';
@ -37,7 +38,8 @@ const app = new Hono()
title = await getDashboardTitle({ assetId, organizationId: userOrg?.organizationId });
break;
case 'report':
throw new HTTPException(400, { message: 'Report titles are not supported yet' });
title = await getReportTitle({ assetId, organizationId: userOrg?.organizationId });
break;
default: {
const _exhaustive: never = assetType;
throw new HTTPException(400, { message: `Unsupported asset type: ${assetType}` });

View File

@ -0,0 +1,28 @@
CREATE TABLE "report_files" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"name" varchar NOT NULL,
"content" jsonb NOT NULL,
"organization_id" uuid NOT NULL,
"created_by" uuid NOT NULL,
"created_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"updated_at" timestamp with time zone DEFAULT CURRENT_TIMESTAMP NOT NULL,
"deleted_at" timestamp with time zone,
"publicly_accessible" boolean DEFAULT false NOT NULL,
"publicly_enabled_by" uuid,
"public_expiry_date" timestamp with time zone,
"version_history" jsonb DEFAULT '{}'::jsonb NOT NULL,
"public_password" text,
"workspace_sharing" "workspace_sharing_enum" DEFAULT 'none' NOT NULL,
"workspace_sharing_enabled_by" uuid,
"workspace_sharing_enabled_at" timestamp with time zone
);
--> statement-breakpoint
ALTER TABLE "report_files" ADD CONSTRAINT "report_files_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "report_files" ADD CONSTRAINT "report_files_publicly_enabled_by_fkey" FOREIGN KEY ("publicly_enabled_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE cascade;--> statement-breakpoint
ALTER TABLE "report_files" ADD CONSTRAINT "report_files_workspace_sharing_enabled_by_fkey" FOREIGN KEY ("workspace_sharing_enabled_by") REFERENCES "public"."users"("id") ON DELETE no action ON UPDATE cascade;--> statement-breakpoint
CREATE INDEX "report_files_created_by_idx" ON "report_files" USING btree ("created_by" uuid_ops);--> statement-breakpoint
CREATE INDEX "report_files_deleted_at_idx" ON "report_files" USING btree ("deleted_at" timestamptz_ops);--> statement-breakpoint
CREATE INDEX "report_files_organization_id_idx" ON "report_files" USING btree ("organization_id" uuid_ops);

File diff suppressed because it is too large Load Diff

View File

@ -596,6 +596,13 @@
"when": 1753076918000,
"tag": "0084_github_integrations",
"breakpoints": true
},
{
"idx": 85,
"version": "7",
"when": 1754337436368,
"tag": "0085_create_report_table",
"breakpoints": true
}
]
}
}

View File

@ -8,3 +8,4 @@ export * from './organizations';
export * from './dashboards';
export * from './metrics';
export * from './collections';
export * from './reports';

View File

@ -11,7 +11,7 @@ export const GetMetricTitleInputSchema = z.object({
export type GetMetricTitleInput = z.infer<typeof GetMetricTitleInputSchema>;
// Updated return type to remove null since we now throw an error instead
export async function getMetricTitle(input: GetMetricTitleInput): Promise<string> {
export async function getMetricTitle(input: GetMetricTitleInput, isAdmin = false): Promise<string> {
const validated = GetMetricTitleInputSchema.parse(input);
const [metric] = await db
@ -30,7 +30,11 @@ export async function getMetricTitle(input: GetMetricTitleInput): Promise<string
}
// Throw error for permission failure instead of returning null
if (!metric.publiclyAccessible && metric.organizationId !== validated.organizationId) {
if (
!isAdmin &&
!metric.publiclyAccessible &&
metric.organizationId !== validated.organizationId
) {
throw new Error(
`Access denied: Metric with ID ${validated.assetId} is not publicly accessible and does not belong to the specified organization`
);

View File

@ -0,0 +1,40 @@
import { and, eq, isNull } from 'drizzle-orm';
import { z } from 'zod';
import { db } from '../../connection';
import { reportFiles } from '../../schema';
export const GetReportTitleInputSchema = z.object({
assetId: z.string().uuid(),
organizationId: z.string().uuid().optional(),
});
export type GetReportTitleInput = z.infer<typeof GetReportTitleInputSchema>;
// Updated return type to remove null since we now throw an error instead
export async function getReportTitle(input: GetReportTitleInput): Promise<string> {
const validated = GetReportTitleInputSchema.parse(input);
const [report] = await db
.select({
name: reportFiles.name,
publiclyAccessible: reportFiles.publiclyAccessible,
organizationId: reportFiles.organizationId,
})
.from(reportFiles)
.where(and(eq(reportFiles.id, validated.assetId), isNull(reportFiles.deletedAt)))
.limit(1);
// Throw error instead of returning null
if (!report) {
throw new Error(`Report with ID ${validated.assetId} not found`);
}
// Throw error for permission failure instead of returning null
if (!report.publiclyAccessible && report.organizationId !== validated.organizationId) {
throw new Error(
`Access denied: Report with ID ${validated.assetId} is not publicly accessible and does not belong to the specified organization`
);
}
return report.name;
}

View File

@ -0,0 +1 @@
export * from './get-report-title';

View File

@ -988,6 +988,67 @@ export const dashboardFiles = pgTable(
]
);
export const reportFiles = pgTable(
'report_files',
{
id: uuid().defaultRandom().primaryKey().notNull(),
name: varchar().notNull(),
content: jsonb().notNull(),
organizationId: uuid('organization_id').notNull(),
createdBy: uuid('created_by').notNull(),
createdAt: timestamp('created_at', { withTimezone: true, mode: 'string' })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
updatedAt: timestamp('updated_at', { withTimezone: true, mode: 'string' })
.default(sql`CURRENT_TIMESTAMP`)
.notNull(),
deletedAt: timestamp('deleted_at', { withTimezone: true, mode: 'string' }),
publiclyAccessible: boolean('publicly_accessible').default(false).notNull(),
publiclyEnabledBy: uuid('publicly_enabled_by'),
publicExpiryDate: timestamp('public_expiry_date', {
withTimezone: true,
mode: 'string',
}),
versionHistory: jsonb('version_history').default({}).notNull(),
publicPassword: text('public_password'),
workspaceSharing: workspaceSharingEnum('workspace_sharing').default('none').notNull(),
workspaceSharingEnabledBy: uuid('workspace_sharing_enabled_by'),
workspaceSharingEnabledAt: timestamp('workspace_sharing_enabled_at', {
withTimezone: true,
mode: 'string',
}),
},
(table) => [
index('report_files_created_by_idx').using(
'btree',
table.createdBy.asc().nullsLast().op('uuid_ops')
),
index('report_files_deleted_at_idx').using(
'btree',
table.deletedAt.asc().nullsLast().op('timestamptz_ops')
),
index('report_files_organization_id_idx').using(
'btree',
table.organizationId.asc().nullsLast().op('uuid_ops')
),
foreignKey({
columns: [table.createdBy],
foreignColumns: [users.id],
name: 'report_files_created_by_fkey',
}).onUpdate('cascade'),
foreignKey({
columns: [table.publiclyEnabledBy],
foreignColumns: [users.id],
name: 'report_files_publicly_enabled_by_fkey',
}).onUpdate('cascade'),
foreignKey({
columns: [table.workspaceSharingEnabledBy],
foreignColumns: [users.id],
name: 'report_files_workspace_sharing_enabled_by_fkey',
}).onUpdate('cascade'),
]
);
export const chats = pgTable(
'chats',
{