feat: telemetry add workspaceId and eventName

This commit is contained in:
moonrailgun 2023-09-18 00:13:03 +08:00
parent bd15e47765
commit 66f713e8f6
4 changed files with 91 additions and 41 deletions

View File

@ -106,7 +106,6 @@ model WebsiteEvent {
id String @id() @default(uuid()) @db.Uuid id String @id() @default(uuid()) @db.Uuid
websiteId String @db.Uuid websiteId String @db.Uuid
sessionId String @db.Uuid sessionId String @db.Uuid
createdAt DateTime? @default(now()) @db.Timestamptz(6)
urlPath String @db.VarChar(500) urlPath String @db.VarChar(500)
urlQuery String? @db.VarChar(500) urlQuery String? @db.VarChar(500)
referrerPath String? @db.VarChar(500) referrerPath String? @db.VarChar(500)
@ -115,6 +114,7 @@ model WebsiteEvent {
pageTitle String? @db.VarChar(500) pageTitle String? @db.VarChar(500)
eventType Int @default(1) @db.Integer eventType Int @default(1) @db.Integer
eventName String? @db.VarChar(50) eventName String? @db.VarChar(50)
createdAt DateTime? @default(now()) @db.Timestamptz(6)
eventData WebsiteEventData[] eventData WebsiteEventData[]
session WebsiteSession @relation(fields: [sessionId], references: [id]) session WebsiteSession @relation(fields: [sessionId], references: [id])
@ -173,6 +173,7 @@ model WebsiteSessionData {
model TelemetrySession { model TelemetrySession {
id String @id @unique @db.Uuid id String @id @unique @db.Uuid
workspaceId String @db.Uuid
hostname String? @db.VarChar(100) hostname String? @db.VarChar(100)
browser String? @db.VarChar(20) browser String? @db.VarChar(20)
os String? @db.VarChar(20) os String? @db.VarChar(20)
@ -185,17 +186,22 @@ model TelemetrySession {
telemetryEvent TelemetryEvent[] telemetryEvent TelemetryEvent[]
@@index([createdAt]) @@index([createdAt])
@@index([workspaceId, createdAt])
} }
model TelemetryEvent { model TelemetryEvent {
id String @id() @default(uuid()) @db.Uuid id String @id() @default(uuid()) @db.Uuid
sessionId String @db.Uuid sessionId String @db.Uuid
createdAt DateTime? @default(now()) @db.Timestamptz(6) workspaceId String @db.Uuid
urlOrigin String @db.VarChar(500) eventName String? @db.VarChar(100)
urlPath String @db.VarChar(500) urlOrigin String @db.VarChar(500)
urlPath String @db.VarChar(500)
createdAt DateTime? @default(now()) @db.Timestamptz(6)
session TelemetrySession @relation(fields: [sessionId], references: [id]) session TelemetrySession @relation(fields: [sessionId], references: [id])
@@index([createdAt]) @@index([createdAt])
@@index([sessionId]) @@index([sessionId])
@@index([workspaceId])
@@index([workspaceId, createdAt])
} }

View File

@ -9,17 +9,25 @@ export async function recordTelemetryEvent(req: Request) {
if (!(url && typeof url === 'string')) { if (!(url && typeof url === 'string')) {
return; return;
} }
const eventName = req.query.name ? String(req.query.name) : undefined;
const session = await findSession(req, url); const session = await findSession(req, url);
if (!session) { if (!session) {
return; return;
} }
const workspaceId = req.params.workspaceId;
if (!workspaceId) {
return;
}
const { origin, pathname } = new URL(url); const { origin, pathname } = new URL(url);
await prisma.telemetryEvent.create({ await prisma.telemetryEvent.create({
data: { data: {
sessionId: session.id, sessionId: session.id,
workspaceId,
eventName,
urlOrigin: origin, urlOrigin: origin,
urlPath: pathname, urlPath: pathname,
}, },
@ -32,10 +40,19 @@ export async function sumTelemetryEvent(req: Request): Promise<number> {
return 0; return 0;
} }
const eventName = req.query.name ? String(req.query.name) : undefined;
const workspaceId = req.params.workspaceId;
if (!workspaceId) {
return 0;
}
const { origin, pathname } = new URL(url); const { origin, pathname } = new URL(url);
const number = await prisma.telemetryEvent.count({ const number = await prisma.telemetryEvent.count({
where: { where: {
workspaceId,
eventName,
urlOrigin: origin, urlOrigin: origin,
urlPath: pathname, urlPath: pathname,
}, },
@ -46,6 +63,10 @@ export async function sumTelemetryEvent(req: Request): Promise<number> {
async function findSession(req: Request, url: string) { async function findSession(req: Request, url: string) {
const { hostname } = new URL(url); const { hostname } = new URL(url);
const workspaceId = req.params.workspaceId;
if (!workspaceId) {
throw new Error('Not found workspaceId');
}
const { const {
userAgent, userAgent,
@ -58,7 +79,7 @@ async function findSession(req: Request, url: string) {
city, city,
} = await getRequestInfo(req); } = await getRequestInfo(req);
const sessionId = hashUuid(hostname, ip, userAgent!); const sessionId = hashUuid(workspaceId, hostname, ip, userAgent!);
let session = await loadSession(sessionId); let session = await loadSession(sessionId);
if (!session) { if (!session) {
@ -66,6 +87,7 @@ async function findSession(req: Request, url: string) {
session = await prisma.telemetrySession.create({ session = await prisma.telemetrySession.create({
data: { data: {
id: sessionId, id: sessionId,
workspaceId,
hostname, hostname,
browser, browser,
os, os,

View File

@ -1,7 +1,6 @@
import { prisma } from './_client'; import { prisma } from './_client';
import { QueryFilters, parseFilters, getDateQuery } from '../utils/prisma'; import { QueryFilters, parseFilters, getDateQuery } from '../utils/prisma';
import { DEFAULT_RESET_DATE, EVENT_TYPE } from '../utils/const'; import { DEFAULT_RESET_DATE, EVENT_TYPE } from '../utils/const';
import { Prisma } from '@prisma/client';
export async function getWorkspaceUser(workspaceId: string, userId: string) { export async function getWorkspaceUser(workspaceId: string, userId: string) {
const info = await prisma.workspacesOnUsers.findFirst({ const info = await prisma.workspacesOnUsers.findFirst({
@ -27,6 +26,14 @@ export async function checkIsWorkspaceUser(
} }
} }
export async function getWorkspace(workspaceId: string) {
return prisma.workspace.findUnique({
where: {
id: workspaceId,
},
});
}
export async function getWorkspaceWebsites(workspaceId: string) { export async function getWorkspaceWebsites(workspaceId: string) {
const workspace = await prisma.workspace.findUnique({ const workspace = await prisma.workspace.findUnique({
where: { where: {

View File

@ -1,42 +1,57 @@
import { Router } from 'express'; import { Router } from 'express';
import { query, validate } from '../middleware/validate';
import { recordTelemetryEvent, sumTelemetryEvent } from '../model/telemetry'; import { recordTelemetryEvent, sumTelemetryEvent } from '../model/telemetry';
import { numify } from '../utils/common'; import { numify } from '../utils/common';
const openBadge = require('openbadge'); const openBadge = require('openbadge');
export const telemetryRouter = Router(); export const telemetryRouter = Router();
telemetryRouter.get('/blank.gif', async (req, res) => { telemetryRouter.get(
const buffer = Buffer.from( '/:workspaceId/blank.gif',
'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7', validate(
'base64' query('name').optional().isString(),
); query('url').optional().isURL()
),
recordTelemetryEvent(req); async (req, res) => {
const buffer = Buffer.from(
res.header('Content-Type', 'image/gif').send(buffer); 'R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
}); 'base64'
telemetryRouter.get('/badge.svg', async (req, res) => {
const title = req.query.title || 'visitor';
const start = req.query.start ? Number(req.query.start) : 0;
recordTelemetryEvent(req);
const num = await sumTelemetryEvent(req);
const svg = await new Promise((resolve, reject) => {
openBadge(
{
text: [title, numify(num + start)],
},
(err: any, badgeSvg: string) => {
if (err) {
reject(err);
} else {
resolve(badgeSvg);
}
}
); );
});
res.header('Content-Type', 'image/svg+xml').send(svg); recordTelemetryEvent(req);
});
res.header('Content-Type', 'image/gif').send(buffer);
}
);
telemetryRouter.get(
'/:workspaceId/badge.svg',
validate(
query('name').optional().isString(),
query('url').optional().isURL()
),
async (req, res) => {
const title = req.query.title || 'visitor';
const start = req.query.start ? Number(req.query.start) : 0;
recordTelemetryEvent(req);
const num = await sumTelemetryEvent(req);
const svg = await new Promise((resolve, reject) => {
openBadge(
{
text: [title, numify(num + start)],
},
(err: any, badgeSvg: string) => {
if (err) {
reject(err);
} else {
resolve(badgeSvg);
}
}
);
});
res.header('Content-Type', 'image/svg+xml').send(svg);
}
);