feat: add lighthouse endpoint
This commit is contained in:
parent
91ade2ab55
commit
28d982e497
552
pnpm-lock.yaml
552
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
@ -50,6 +50,7 @@
|
||||
"is-localhost-ip": "^2.0.0",
|
||||
"isolated-vm": "^4.7.2",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lighthouse": "^12.2.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"maxmind": "^4.3.18",
|
||||
"md5": "^2.3.0",
|
||||
|
@ -0,0 +1,21 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "WebsiteLighthouseReportStatus" AS ENUM ('Pending', 'Success', 'Failed');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "WebsiteLighthouseReport" (
|
||||
"id" VARCHAR(30) NOT NULL,
|
||||
"websiteId" VARCHAR(30),
|
||||
"createdAt" TIMESTAMPTZ(6) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMPTZ(6) NOT NULL,
|
||||
"url" TEXT NOT NULL,
|
||||
"result" TEXT NOT NULL,
|
||||
"status" "WebsiteLighthouseReportStatus" NOT NULL DEFAULT 'Pending',
|
||||
|
||||
CONSTRAINT "WebsiteLighthouseReport_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "WebsiteLighthouseReport_createdAt_idx" ON "WebsiteLighthouseReport"("createdAt");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "WebsiteLighthouseReport_websiteId_idx" ON "WebsiteLighthouseReport"("websiteId");
|
@ -249,6 +249,25 @@ model WebsiteSessionData {
|
||||
@@index([sessionId])
|
||||
}
|
||||
|
||||
enum WebsiteLighthouseReportStatus {
|
||||
Pending
|
||||
Success
|
||||
Failed
|
||||
}
|
||||
|
||||
model WebsiteLighthouseReport {
|
||||
id String @id() @default(cuid()) @db.VarChar(30)
|
||||
websiteId String? @db.VarChar(30)
|
||||
createdAt DateTime @default(now()) @db.Timestamptz(6)
|
||||
updatedAt DateTime @updatedAt @db.Timestamptz(6)
|
||||
url String
|
||||
result String // json string
|
||||
status WebsiteLighthouseReportStatus @default(Pending)
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([websiteId])
|
||||
}
|
||||
|
||||
model Telemetry {
|
||||
id String @id @unique @default(cuid()) @db.VarChar(30)
|
||||
workspaceId String @db.VarChar(30)
|
||||
|
@ -9,6 +9,7 @@ export * from "./websitesession.js"
|
||||
export * from "./websiteevent.js"
|
||||
export * from "./websiteeventdata.js"
|
||||
export * from "./websitesessiondata.js"
|
||||
export * from "./websitelighthousereport.js"
|
||||
export * from "./telemetry.js"
|
||||
export * from "./telemetrysession.js"
|
||||
export * from "./telemetryevent.js"
|
||||
|
13
src/server/prisma/zod/websitelighthousereport.ts
Normal file
13
src/server/prisma/zod/websitelighthousereport.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import * as z from "zod"
|
||||
import * as imports from "./schemas/index.js"
|
||||
import { WebsiteLighthouseReportStatus } from "@prisma/client"
|
||||
|
||||
export const WebsiteLighthouseReportModelSchema = z.object({
|
||||
id: z.string(),
|
||||
websiteId: z.string().nullish(),
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
url: z.string(),
|
||||
result: z.string(),
|
||||
status: z.nativeEnum(WebsiteLighthouseReportStatus),
|
||||
})
|
@ -1,5 +1,6 @@
|
||||
import {
|
||||
OpenApiMetaInfo,
|
||||
publicProcedure,
|
||||
router,
|
||||
workspaceOwnerProcedure,
|
||||
workspaceProcedure,
|
||||
@ -32,6 +33,10 @@ import {
|
||||
} from '../../model/_schema/filter.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { WebsiteQueryFilters } from '../../utils/prisma.js';
|
||||
import { WebsiteLighthouseReportStatus } from '@prisma/client';
|
||||
import { generateLighthouse } from '../../utils/screenshot/lighthouse.js';
|
||||
import { WebsiteLighthouseReportModelSchema } from '../../prisma/zod/websitelighthousereport.js';
|
||||
import { method } from 'lodash-es';
|
||||
|
||||
const websiteNameSchema = z.string().max(100);
|
||||
const websiteDomainSchema = z.union([
|
||||
@ -550,6 +555,131 @@ export const websiteRouter = router({
|
||||
|
||||
return websiteInfo;
|
||||
}),
|
||||
generateLighthouseReport: workspaceProcedure
|
||||
.meta(
|
||||
buildWebsiteOpenapi({
|
||||
method: 'POST',
|
||||
path: '/generateLighthouseReport',
|
||||
})
|
||||
)
|
||||
.input(
|
||||
z.object({
|
||||
websiteId: z.string().cuid2(),
|
||||
url: z.string().url(),
|
||||
})
|
||||
)
|
||||
.output(z.string())
|
||||
.mutation(async ({ input }) => {
|
||||
const { websiteId, url } = input;
|
||||
|
||||
const websiteInfo = await prisma.websiteLighthouseReport.create({
|
||||
data: {
|
||||
url,
|
||||
websiteId,
|
||||
status: WebsiteLighthouseReportStatus.Pending,
|
||||
result: '',
|
||||
},
|
||||
});
|
||||
|
||||
generateLighthouse(url)
|
||||
.then(async (result) => {
|
||||
await prisma.websiteLighthouseReport.update({
|
||||
where: {
|
||||
id: websiteInfo.id,
|
||||
},
|
||||
data: {
|
||||
status: WebsiteLighthouseReportStatus.Success,
|
||||
result: JSON.stringify(result),
|
||||
},
|
||||
});
|
||||
})
|
||||
.catch(async () => {
|
||||
await prisma.websiteLighthouseReport.update({
|
||||
where: {
|
||||
id: websiteInfo.id,
|
||||
},
|
||||
data: {
|
||||
status: WebsiteLighthouseReportStatus.Failed,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
return 'success';
|
||||
}),
|
||||
getLighthouseReport: workspaceProcedure
|
||||
.meta(
|
||||
buildWebsiteOpenapi({
|
||||
method: 'GET',
|
||||
path: '/getLighthouseReport',
|
||||
})
|
||||
)
|
||||
.input(
|
||||
z.object({
|
||||
websiteId: z.string().cuid2(),
|
||||
})
|
||||
)
|
||||
.output(
|
||||
z.array(
|
||||
WebsiteLighthouseReportModelSchema.pick({
|
||||
id: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
})
|
||||
)
|
||||
)
|
||||
.query(async ({ input }) => {
|
||||
const { websiteId } = input;
|
||||
|
||||
const list = await prisma.websiteLighthouseReport.findMany({
|
||||
where: {
|
||||
websiteId,
|
||||
},
|
||||
take: 10,
|
||||
orderBy: {
|
||||
createdAt: 'desc',
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
createdAt: true,
|
||||
},
|
||||
});
|
||||
|
||||
return list;
|
||||
}),
|
||||
getLighthouseJSON: publicProcedure
|
||||
.meta({
|
||||
openapi: {
|
||||
tags: [OPENAPI_TAG.WEBSITE],
|
||||
protect: true,
|
||||
method: 'GET',
|
||||
path: '/lighthouse/{lighthouseId}',
|
||||
},
|
||||
})
|
||||
.input(
|
||||
z.object({
|
||||
lighthouseId: z.string().cuid2(),
|
||||
})
|
||||
)
|
||||
.output(z.record(z.string(), z.any()))
|
||||
.query(async ({ input }) => {
|
||||
const { lighthouseId } = input;
|
||||
|
||||
const res = await prisma.websiteLighthouseReport.findFirst({
|
||||
where: {
|
||||
id: lighthouseId,
|
||||
},
|
||||
select: {
|
||||
result: true,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
return JSON.parse(res?.result ?? '{}');
|
||||
} catch (err) {
|
||||
return {};
|
||||
}
|
||||
}),
|
||||
});
|
||||
|
||||
function buildWebsiteOpenapi(meta: OpenApiMetaInfo): OpenApiMeta {
|
||||
|
35
src/server/utils/screenshot/lighthouse.ts
Normal file
35
src/server/utils/screenshot/lighthouse.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import puppeteer from 'puppeteer';
|
||||
import lighthouse, { Result } from 'lighthouse';
|
||||
|
||||
export async function generateLighthouse(url: string): Promise<Result> {
|
||||
// Use Puppeteer to launch headless Chrome
|
||||
// - Omit `--enable-automation` (See https://github.com/GoogleChrome/lighthouse/issues/12988)
|
||||
// - Don't use 800x600 default viewport
|
||||
const browser = await puppeteer.launch({
|
||||
// Set to false if you want to see the script in action.
|
||||
headless: 'new',
|
||||
defaultViewport: null,
|
||||
ignoreDefaultArgs: ['--enable-automation'],
|
||||
});
|
||||
const page = await browser.newPage();
|
||||
|
||||
// Wait for Lighthouse to open url, then inject our stylesheet.
|
||||
browser.on('targetchanged', async (target) => {
|
||||
if (page && page.url() === url) {
|
||||
await page.addStyleTag({ content: '* {color: red}' });
|
||||
}
|
||||
});
|
||||
|
||||
// Lighthouse will open the URL.
|
||||
// Puppeteer will observe `targetchanged` and inject our stylesheet.
|
||||
const res = await lighthouse(url, undefined, undefined, page);
|
||||
if (!res) {
|
||||
throw new Error('Lighthouse failed to generate report');
|
||||
}
|
||||
|
||||
const { lhr } = res;
|
||||
|
||||
await browser.close();
|
||||
|
||||
return lhr;
|
||||
}
|
Loading…
Reference in New Issue
Block a user