add migrations

This commit is contained in:
Nate Kelley 2025-10-08 10:17:58 -06:00
parent c434ecde65
commit 9a589931a0
No known key found for this signature in database
GPG Key ID: FD90372AB8D98B4F
14 changed files with 6373 additions and 103 deletions

View File

@ -41,6 +41,7 @@
"@supabase/supabase-js": "catalog:",
"@trigger.dev/sdk": "4.0.2",
"ai": "catalog:",
"dayjs": "^1.11.18",
"drizzle-orm": "catalog:",
"hono": "catalog:",
"hono-pino": "^0.10.2",

View File

@ -1,62 +0,0 @@
import { checkPermission } from '@buster/access-controls';
import { getMetricFileById } from '@buster/database/queries';
import {
PutMetricScreenshotParamsSchema,
PutMetricScreenshotRequestSchema,
type PutScreenshotResponse,
} from '@buster/server-shared/screenshots';
import { zValidator } from '@hono/zod-validator';
import { Hono } from 'hono';
import { HTTPException } from 'hono/http-exception';
import { uploadScreenshotHandler } from '../../../../../shared-helpers/upload-screenshot-handler';
import { getMetricScreenshotHandler } from './getMetricScreenshotHandler';
const app = new Hono().put(
'/',
zValidator('param', PutMetricScreenshotParamsSchema),
zValidator('json', PutMetricScreenshotRequestSchema),
async (c) => {
const assetId = c.req.valid('param').id;
const user = c.get('busterUser');
const metric = await getMetricFileById(assetId);
if (!metric) {
throw new HTTPException(404, { message: 'Metric not found' });
}
const permission = await checkPermission({
userId: user.id,
assetId,
assetType: 'metric_file',
requiredRole: 'can_edit',
workspaceSharing: metric.workspaceSharing,
organizationId: metric.organizationId,
});
if (!permission.hasAccess) {
throw new HTTPException(403, {
message: 'You do not have permission to upload a screenshot for this metric',
});
}
const screenshotBuffer = await getMetricScreenshotHandler({
params: { id: assetId },
search: { version_number: 1, width: 800, height: 600, type: 'png' },
context: c,
});
//TODO: we need to upload the screenshot to the database
const image = screenshotBuffer.toString('base64');
const result: PutScreenshotResponse = await uploadScreenshotHandler({
assetType: 'metric_file',
assetId,
image,
organizationId: metric.organizationId,
});
return c.json(result);
}
);
export default app;

View File

@ -3,23 +3,24 @@ import type {
GetMetricScreenshotQuery,
} from '@buster/server-shared/screenshots';
import type { Context } from 'hono';
import type { BrowserParams } from '../../../../../shared-helpers/browser-login';
import { createHrefFromLink } from '../../../../../shared-helpers/create-href-from-link';
export const getMetricScreenshotHandler = async ({
params,
search,
context,
...rest
}: {
params: GetMetricScreenshotParams;
search: GetMetricScreenshotQuery;
context: Context;
}) => {
} & BrowserParams) => {
const { width, height, type, version_number } = search;
const { id: metricId } = params;
const { browserLogin } = await import('../../../../../shared-helpers/browser-login');
const { result: screenshotBuffer } = await browserLogin({
...rest,
width,
height,
fullPath: createHrefFromLink({
@ -27,7 +28,6 @@ export const getMetricScreenshotHandler = async ({
params: { metricId },
search: { version_number, type, width, height },
}),
context,
callback: async ({ page }) => {
return await page.screenshot({ type });
},

View File

@ -0,0 +1,49 @@
import { hasMetricScreenshotBeenTakenWithin } from '@buster/database/queries';
import type {
GetMetricScreenshotParams,
GetMetricScreenshotQuery,
} from '@buster/server-shared/screenshots';
import dayjs from 'dayjs';
import type { Context } from 'hono';
import type { BrowserParams } from '../../../../../shared-helpers/browser-login';
import { getMetricScreenshotHandler } from './getMetricScreenshotHandler';
const shouldTakenNewScreenshot = async ({
metricId,
isOnSaveEvent,
}: { metricId: string; isOnSaveEvent: boolean }) => {
const isScreenshotExpired = await hasMetricScreenshotBeenTakenWithin(
metricId,
dayjs().subtract(6, 'hours')
);
return !isScreenshotExpired;
};
export const saveMetricScreenshotHandler = async ({
params,
search,
...rest
}: {
params: GetMetricScreenshotParams;
search: GetMetricScreenshotQuery;
} & BrowserParams<Buffer<ArrayBufferLike>>) => {
const shouldTakeNewScreenshot = await shouldTakenNewScreenshot({
metricId: params.id,
isOnSaveEvent: true,
});
if (!shouldTakeNewScreenshot) {
return;
}
const browserParams = rest as BrowserParams<Buffer<ArrayBufferLike>>;
const screenshotBuffer = await getMetricScreenshotHandler({
params,
search,
...browserParams,
});
console.log('screenshotBuffer', screenshotBuffer);
//TODO: save the screenshot to the database
};

View File

@ -19,7 +19,9 @@ type BrowserParamsDirectRequest<T> = BrowserParamsBase<T> & {
accessToken: string;
};
type BrowserParams<T> = BrowserParamsContext<T> | BrowserParamsDirectRequest<T>;
export type BrowserParams<T = Buffer<ArrayBufferLike>> =
| BrowserParamsContext<T>
| BrowserParamsDirectRequest<T>;
export const browserLogin = async <T = Buffer<ArrayBufferLike>>({
width,

View File

@ -0,0 +1,5 @@
ALTER TABLE "chats" ADD COLUMN "screenshot_taken_at" timestamp with time zone;--> statement-breakpoint
ALTER TABLE "collections" ADD COLUMN "screenshot_taken_at" timestamp with time zone;--> statement-breakpoint
ALTER TABLE "dashboard_files" ADD COLUMN "screenshot_taken_at" timestamp with time zone;--> statement-breakpoint
ALTER TABLE "metric_files" ADD COLUMN "screenshot_taken_at" timestamp with time zone;--> statement-breakpoint
ALTER TABLE "report_files" ADD COLUMN "screenshot_taken_at" timestamp with time zone;

File diff suppressed because it is too large Load Diff

View File

@ -841,6 +841,13 @@
"when": 1759868882572,
"tag": "0120_aberrant_xavin",
"breakpoints": true
},
{
"idx": 121,
"version": "7",
"when": 1759938996590,
"tag": "0121_same_calypso",
"breakpoints": true
}
]
}

View File

@ -47,11 +47,12 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@buster-app/supabase": "workspace:*",
"@buster/env-utils": "workspace:*",
"@buster/typescript-config": "workspace:*",
"@buster/vitest-config": "workspace:*",
"@buster-app/supabase": "workspace:*",
"ai": "catalog:",
"dayjs": "^1.11.18",
"drizzle-kit": "^0.31.4",
"drizzle-orm": "catalog:",
"drizzle-zod": "^0.8.3",

View File

@ -54,6 +54,7 @@ export const updateAssetScreenshotBucketKey = async (
.update(table)
.set({
screenshotBucketKey,
screenshotTakenAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
.where(and(eq(table.id, assetId), isNull(table.deletedAt)));

View File

@ -41,3 +41,5 @@ export {
type AssociatedAsset,
type AssetsAssociatedWithMetric,
} from './get-permissioned-asset-associations';
export { hasMetricScreenshotBeenTakenWithin } from './metric-screenshots';

View File

@ -0,0 +1,21 @@
import dayjs from 'dayjs';
import type { Dayjs } from 'dayjs';
import { and, eq, isNull } from 'drizzle-orm';
import { db } from '../../connection';
import { metricFiles } from '../../schema';
export const hasMetricScreenshotBeenTakenWithin = async (
metricId: string,
afterDate: Date | Dayjs
): Promise<boolean> => {
const [metric] = await db
.select()
.from(metricFiles)
.where(and(eq(metricFiles.id, metricId), isNull(metricFiles.deletedAt)));
if (!metric || !metric.screenshotTakenAt) {
return false;
}
return dayjs(metric.screenshotTakenAt).isAfter(dayjs(afterDate));
};

View File

@ -233,6 +233,7 @@ export const collections = pgTable(
mode: 'string',
}),
screenshotBucketKey: text('screenshot_bucket_key'),
screenshotTakenAt: timestamp('screenshot_taken_at', { withTimezone: true, mode: 'string' }),
},
(table) => [
foreignKey({
@ -743,6 +744,7 @@ export const dashboardFiles = pgTable(
mode: 'string',
}),
screenshotBucketKey: text('screenshot_bucket_key'),
screenshotTakenAt: timestamp('screenshot_taken_at', { withTimezone: true, mode: 'string' }),
savedToLibrary: boolean('saved_to_library').default(false).notNull(),
},
(table) => [
@ -818,6 +820,7 @@ export const reportFiles = pgTable(
mode: 'string',
}),
screenshotBucketKey: text('screenshot_bucket_key'),
screenshotTakenAt: timestamp('screenshot_taken_at', { withTimezone: true, mode: 'string' }),
savedToLibrary: boolean('saved_to_library').default(false).notNull(),
},
(table) => [
@ -891,6 +894,7 @@ export const chats = pgTable(
mode: 'string',
}),
screenshotBucketKey: text('screenshot_bucket_key'),
screenshotTakenAt: timestamp('screenshot_taken_at', { withTimezone: true, mode: 'string' }),
savedToLibrary: boolean('saved_to_library').default(false).notNull(),
},
(table) => [
@ -1043,6 +1047,7 @@ export const metricFiles = pgTable(
mode: 'string',
}),
screenshotBucketKey: text('screenshot_bucket_key'),
screenshotTakenAt: timestamp('screenshot_taken_at', { withTimezone: true, mode: 'string' }),
savedToLibrary: boolean('saved_to_library').default(false).notNull(),
},
(table) => [

View File

@ -297,6 +297,9 @@ importers:
ai:
specifier: 'catalog:'
version: 5.0.44(zod@3.25.76)
dayjs:
specifier: ^1.11.18
version: 1.11.18
drizzle-orm:
specifier: 'catalog:'
version: 0.44.5(@opentelemetry/api@1.9.0)(@types/pg@8.15.4)(bun-types@1.2.21(@types/react@19.1.13))(mysql2@3.14.1)(pg@8.16.3)(postgres@3.4.7)
@ -1200,6 +1203,9 @@ importers:
ai:
specifier: 'catalog:'
version: 5.0.44(zod@3.25.76)
dayjs:
specifier: ^1.11.18
version: 1.11.18
drizzle-kit:
specifier: ^0.31.4
version: 0.31.4
@ -19971,11 +19977,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@vitest/browser@3.2.4(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)':
'@vitest/browser@3.2.4(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.4(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)':
dependencies:
'@testing-library/dom': 10.4.1
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
'@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))
'@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(vite@7.1.4(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))
'@vitest/utils': 3.2.4
magic-string: 0.30.17
sirv: 3.0.1
@ -20000,6 +20006,25 @@ snapshots:
magic-string: 0.30.17
sirv: 3.0.1
tinyrainbow: 2.0.0
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
ws: 8.18.3
optionalDependencies:
playwright: 1.55.1
transitivePeerDependencies:
- bufferutil
- msw
- utf-8-validate
- vite
'@vitest/browser@3.2.4(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)':
dependencies:
'@testing-library/dom': 10.4.1
'@testing-library/user-event': 14.6.1(@testing-library/dom@10.4.1)
'@vitest/mocker': 3.2.4(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(vite@7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))
'@vitest/utils': 3.2.4
magic-string: 0.30.17
sirv: 3.0.1
tinyrainbow: 2.0.0
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
ws: 8.18.3
optionalDependencies:
@ -20009,6 +20034,7 @@ snapshots:
- msw
- utf-8-validate
- vite
optional: true
'@vitest/coverage-v8@3.2.4(@vitest/browser@3.2.4)(vitest@3.2.4)':
dependencies:
@ -20025,7 +20051,7 @@ snapshots:
std-env: 3.9.0
test-exclude: 7.0.1
tinyrainbow: 2.0.0
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@26.1.0)(lightningcss@1.30.1)(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
optionalDependencies:
'@vitest/browser': 3.2.4(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)
transitivePeerDependencies:
@ -20048,16 +20074,6 @@ snapshots:
msw: 2.11.3(@types/node@22.18.1)(typescript@5.9.2)
vite: 7.1.4(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
'@vitest/mocker@3.2.4(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
msw: 2.11.3(@types/node@22.18.1)(typescript@5.9.2)
vite: 7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
optional: true
'@vitest/mocker@3.2.4(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(vite@7.1.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))':
dependencies:
'@vitest/spy': 3.2.4
@ -20067,6 +20083,16 @@ snapshots:
msw: 2.11.3(@types/node@24.3.1)(typescript@5.9.2)
vite: 7.1.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
'@vitest/mocker@3.2.4(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(vite@7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.17
optionalDependencies:
msw: 2.11.3(@types/node@24.3.1)(typescript@5.9.2)
vite: 7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
optional: true
'@vitest/pretty-format@3.2.4':
dependencies:
tinyrainbow: 2.0.0
@ -20096,7 +20122,7 @@ snapshots:
sirv: 3.0.1
tinyglobby: 0.2.14
tinyrainbow: 2.0.0
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@24.3.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
vitest: 3.2.4(@edge-runtime/vm@3.2.0)(@types/debug@4.1.12)(@types/node@22.18.1)(@vitest/browser@3.2.4)(@vitest/ui@3.2.4)(jiti@2.6.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1)
'@vitest/utils@3.2.4':
dependencies:
@ -27357,25 +27383,6 @@ snapshots:
tsx: 4.20.5
yaml: 2.8.1
vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1):
dependencies:
esbuild: 0.25.9
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.50.0
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 22.18.1
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.30.1
sass: 1.93.2
terser: 5.43.1
tsx: 4.20.5
yaml: 2.8.1
optional: true
vite@7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1):
dependencies:
esbuild: 0.25.9
@ -27437,7 +27444,7 @@ snapshots:
'@edge-runtime/vm': 3.2.0
'@types/debug': 4.1.12
'@types/node': 22.18.1
'@vitest/browser': 3.2.4(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.9(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)
'@vitest/browser': 3.2.4(msw@2.11.3(@types/node@22.18.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.4(@types/node@22.18.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)
'@vitest/ui': 3.2.4(vitest@3.2.4)
jsdom: 27.0.0(postcss@8.5.6)
transitivePeerDependencies:
@ -27529,7 +27536,7 @@ snapshots:
'@edge-runtime/vm': 3.2.0
'@types/debug': 4.1.12
'@types/node': 24.3.1
'@vitest/browser': 3.2.4(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.4(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)
'@vitest/browser': 3.2.4(msw@2.11.3(@types/node@24.3.1)(typescript@5.9.2))(playwright@1.55.1)(vite@7.1.9(@types/node@24.3.1)(jiti@2.6.1)(lightningcss@1.30.1)(sass@1.93.2)(terser@5.43.1)(tsx@4.20.5)(yaml@2.8.1))(vitest@3.2.4)
'@vitest/ui': 3.2.4(vitest@3.2.4)
jsdom: 27.0.0(postcss@8.5.6)
transitivePeerDependencies: