feat: basic telemetry api for badge and blank image
This commit is contained in:
parent
4f4a1cfcef
commit
bd15e47765
@ -28,7 +28,7 @@ model Workspace {
|
||||
createdAt DateTime? @default(now()) @db.Timestamptz(6)
|
||||
updatedAt DateTime? @updatedAt @db.Timestamptz(6)
|
||||
|
||||
users WorkspacesOnUsers[]
|
||||
users WorkspacesOnUsers[]
|
||||
websites Website[]
|
||||
|
||||
// for user currentWorkspace
|
||||
@ -170,3 +170,32 @@ model WebsiteSessionData {
|
||||
@@index([websiteId])
|
||||
@@index([sessionId])
|
||||
}
|
||||
|
||||
model TelemetrySession {
|
||||
id String @id @unique @db.Uuid
|
||||
hostname String? @db.VarChar(100)
|
||||
browser String? @db.VarChar(20)
|
||||
os String? @db.VarChar(20)
|
||||
country String? @db.Char(2)
|
||||
subdivision1 String? @db.VarChar(20)
|
||||
subdivision2 String? @db.VarChar(50)
|
||||
city String? @db.VarChar(50)
|
||||
createdAt DateTime? @default(now()) @db.Timestamptz(6)
|
||||
|
||||
telemetryEvent TelemetryEvent[]
|
||||
|
||||
@@index([createdAt])
|
||||
}
|
||||
|
||||
model TelemetryEvent {
|
||||
id String @id() @default(uuid()) @db.Uuid
|
||||
sessionId String @db.Uuid
|
||||
createdAt DateTime? @default(now()) @db.Timestamptz(6)
|
||||
urlOrigin String @db.VarChar(500)
|
||||
urlPath String @db.VarChar(500)
|
||||
|
||||
session TelemetrySession @relation(fields: [sessionId], references: [id])
|
||||
|
||||
@@index([createdAt])
|
||||
@@index([sessionId])
|
||||
}
|
||||
|
102
src/server/model/telemetry.ts
Normal file
102
src/server/model/telemetry.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { TelemetrySession } from '@prisma/client';
|
||||
import { Request } from 'express';
|
||||
import { hashUuid } from '../utils/common';
|
||||
import { getRequestInfo } from '../utils/detect';
|
||||
import { prisma } from './_client';
|
||||
|
||||
export async function recordTelemetryEvent(req: Request) {
|
||||
const url = req.query.url ?? req.headers.referer;
|
||||
if (!(url && typeof url === 'string')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const session = await findSession(req, url);
|
||||
if (!session) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { origin, pathname } = new URL(url);
|
||||
|
||||
await prisma.telemetryEvent.create({
|
||||
data: {
|
||||
sessionId: session.id,
|
||||
urlOrigin: origin,
|
||||
urlPath: pathname,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export async function sumTelemetryEvent(req: Request): Promise<number> {
|
||||
const url = req.query.url ?? req.headers.referer;
|
||||
if (!(url && typeof url === 'string')) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const { origin, pathname } = new URL(url);
|
||||
|
||||
const number = await prisma.telemetryEvent.count({
|
||||
where: {
|
||||
urlOrigin: origin,
|
||||
urlPath: pathname,
|
||||
},
|
||||
});
|
||||
|
||||
return number;
|
||||
}
|
||||
|
||||
async function findSession(req: Request, url: string) {
|
||||
const { hostname } = new URL(url);
|
||||
|
||||
const {
|
||||
userAgent,
|
||||
browser,
|
||||
os,
|
||||
ip,
|
||||
country,
|
||||
subdivision1,
|
||||
subdivision2,
|
||||
city,
|
||||
} = await getRequestInfo(req);
|
||||
|
||||
const sessionId = hashUuid(hostname, ip, userAgent!);
|
||||
|
||||
let session = await loadSession(sessionId);
|
||||
if (!session) {
|
||||
try {
|
||||
session = await prisma.telemetrySession.create({
|
||||
data: {
|
||||
id: sessionId,
|
||||
hostname,
|
||||
browser,
|
||||
os,
|
||||
country,
|
||||
subdivision1,
|
||||
subdivision2,
|
||||
city,
|
||||
},
|
||||
});
|
||||
} catch (e: any) {
|
||||
if (!e.message.toLowerCase().includes('unique constraint')) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
||||
|
||||
async function loadSession(
|
||||
sessionId: string
|
||||
): Promise<TelemetrySession | null> {
|
||||
const session = await prisma.telemetrySession.findUnique({
|
||||
where: {
|
||||
id: sessionId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return session;
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
import { Router } from 'express';
|
||||
import { recordTelemetryEvent, sumTelemetryEvent } from '../model/telemetry';
|
||||
import { numify } from '../utils/common';
|
||||
const openBadge = require('openbadge');
|
||||
|
||||
@ -10,18 +11,22 @@ telemetryRouter.get('/blank.gif', async (req, res) => {
|
||||
'base64'
|
||||
);
|
||||
|
||||
recordTelemetryEvent(req);
|
||||
|
||||
res.header('Content-Type', 'image/gif').send(buffer);
|
||||
});
|
||||
|
||||
telemetryRouter.get('/badge.svg', async (req, res) => {
|
||||
const title = req.query.title || 'visitor';
|
||||
const start = req.query.start ? Number(req.query.start) : 0;
|
||||
|
||||
const num = numify(11123243);
|
||||
recordTelemetryEvent(req);
|
||||
const num = await sumTelemetryEvent(req);
|
||||
|
||||
const svg = await new Promise((resolve, reject) => {
|
||||
openBadge(
|
||||
{
|
||||
text: [title, num],
|
||||
text: [title, numify(num + start)],
|
||||
},
|
||||
(err: any, badgeSvg: string) => {
|
||||
if (err) {
|
||||
|
@ -16,10 +16,7 @@ import maxmind, { Reader, CityResponse } from 'maxmind';
|
||||
|
||||
let lookup: Reader<CityResponse>;
|
||||
|
||||
export async function getClientInfo(
|
||||
req: Request,
|
||||
payload: WebsiteEventPayload
|
||||
) {
|
||||
export async function getRequestInfo(req: Request) {
|
||||
const userAgent = req.headers['user-agent'];
|
||||
const ip = getIpAddress(req);
|
||||
const location = await getLocation(ip, req);
|
||||
@ -29,7 +26,6 @@ export async function getClientInfo(
|
||||
const city = location?.city;
|
||||
const browser = browserName(userAgent ?? '');
|
||||
const os = detectOS(userAgent ?? '');
|
||||
const device = getDevice(payload.screen, os);
|
||||
|
||||
return {
|
||||
userAgent,
|
||||
@ -40,6 +36,18 @@ export async function getClientInfo(
|
||||
subdivision1,
|
||||
subdivision2,
|
||||
city,
|
||||
};
|
||||
}
|
||||
|
||||
export async function getClientInfo(
|
||||
req: Request,
|
||||
payload: WebsiteEventPayload
|
||||
) {
|
||||
const requestInfo = await getRequestInfo(req);
|
||||
const device = getDevice(payload.screen, requestInfo.os);
|
||||
|
||||
return {
|
||||
...requestInfo,
|
||||
device,
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user