From e56aa1aa034333f7e54b87d26f5412fce0fc2ba4 Mon Sep 17 00:00:00 2001 From: DaKheera47 Date: Fri, 16 Jan 2026 15:00:46 +0000 Subject: [PATCH] umami tracking --- orchestrator/src/client/api/client.ts | 14 ++++++++ orchestrator/src/client/lib/analytics.ts | 14 ++++++++ orchestrator/src/server/app.ts | 42 ++++++++++++++++++++---- 3 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 orchestrator/src/client/lib/analytics.ts diff --git a/orchestrator/src/client/api/client.ts b/orchestrator/src/client/api/client.ts index ccb0769..56111d7 100644 --- a/orchestrator/src/client/api/client.ts +++ b/orchestrator/src/client/api/client.ts @@ -19,6 +19,7 @@ import type { VisaSponsorStatusResponse, VisaSponsor, } from '../../shared/types'; +import { trackEvent } from '../lib/analytics'; const API_BASE = '/api'; @@ -116,6 +117,12 @@ export async function searchUkVisaJobs(input: { searchTerm?: string; page?: number; }): Promise { + if (input.searchTerm?.trim()) { + trackEvent('ukvisajobs_search', { + searchTerm: input.searchTerm.trim(), + page: input.page ?? 1, + }); + } return fetchApi('/ukvisajobs/search', { method: 'POST', body: JSON.stringify(input), @@ -202,6 +209,13 @@ export async function searchVisaSponsors(input: { limit?: number; minScore?: number; }): Promise { + if (input.query?.trim()) { + trackEvent('visa_sponsor_search', { + query: input.query.trim(), + limit: input.limit, + minScore: input.minScore, + }); + } return fetchApi('/visa-sponsors/search', { method: 'POST', body: JSON.stringify(input), diff --git a/orchestrator/src/client/lib/analytics.ts b/orchestrator/src/client/lib/analytics.ts new file mode 100644 index 0000000..4779eda --- /dev/null +++ b/orchestrator/src/client/lib/analytics.ts @@ -0,0 +1,14 @@ +type UmamiTracker = { + track: (event: string, data?: Record) => void; +}; + +declare global { + interface Window { + umami?: UmamiTracker; + } +} + +export function trackEvent(event: string, data?: Record) { + if (typeof window === 'undefined') return; + window.umami?.track(event, data); +} diff --git a/orchestrator/src/server/app.ts b/orchestrator/src/server/app.ts index ae2768d..1d4f57e 100644 --- a/orchestrator/src/server/app.ts +++ b/orchestrator/src/server/app.ts @@ -6,17 +6,18 @@ import express from 'express'; import cors from 'cors'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; +import { readFile } from 'fs/promises'; import { apiRouter } from './api/index.js'; const __dirname = dirname(fileURLToPath(import.meta.url)); -function buildBasicAuthMiddleware() { +function createBasicAuthGuard() { const BASIC_AUTH_USER = process.env.BASIC_AUTH_USER || ''; const BASIC_AUTH_PASSWORD = process.env.BASIC_AUTH_PASSWORD || ''; const basicAuthEnabled = BASIC_AUTH_USER.length > 0 && BASIC_AUTH_PASSWORD.length > 0; function isAuthorized(req: express.Request): boolean { - if (!basicAuthEnabled) return true; + if (!basicAuthEnabled) return false; const authHeader = req.headers.authorization || ''; if (!authHeader.startsWith('Basic ')) return false; const encoded = authHeader.slice('Basic '.length).trim(); @@ -46,16 +47,33 @@ function buildBasicAuthMiddleware() { return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase()); } - return (req: express.Request, res: express.Response, next: express.NextFunction) => { + const middleware = (req: express.Request, res: express.Response, next: express.NextFunction) => { if (!basicAuthEnabled || !requiresAuth(req.method, req.path)) return next(); if (isAuthorized(req)) return next(); res.setHeader('WWW-Authenticate', 'Basic realm="Job Ops"'); res.status(401).send('Authentication required'); }; + + return { + middleware, + isAuthorized, + basicAuthEnabled, + }; +} + +function injectUmami(html: string): string { + const snippet = + ''; + if (html.includes(snippet)) return html; + if (html.includes('')) { + return html.replace('', `${snippet}\n`); + } + return `${html}\n${snippet}`; } export function createApp() { const app = express(); + const authGuard = createBasicAuthGuard(); app.use(cors()); app.use(express.json()); @@ -71,7 +89,7 @@ export function createApp() { }); // Optional Basic Auth for write access (read-only by default) - app.use(buildBasicAuthMiddleware()); + app.use(authGuard.middleware); // API routes app.use('/api', apiRouter); @@ -93,8 +111,20 @@ export function createApp() { app.use(express.static(clientDir)); // SPA fallback - app.get('*', (_req, res) => { - res.sendFile(join(clientDir, 'index.html')); + const indexPath = join(clientDir, 'index.html'); + let cachedIndexHtml: string | null = null; + app.get('*', async (req, res) => { + if (!req.accepts('html')) { + res.status(404).end(); + return; + } + if (!cachedIndexHtml) { + cachedIndexHtml = await readFile(indexPath, 'utf-8'); + } + const isAuthenticated = authGuard.basicAuthEnabled && authGuard.isAuthorized(req); + const html = isAuthenticated ? cachedIndexHtml : injectUmami(cachedIndexHtml); + res.setHeader('Content-Type', 'text/html; charset=utf-8'); + res.send(html); }); }