umami tracking

This commit is contained in:
DaKheera47 2026-01-16 15:00:46 +00:00
parent 072feaf373
commit e56aa1aa03
3 changed files with 64 additions and 6 deletions

View File

@ -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<UkVisaJobsSearchResponse> {
if (input.searchTerm?.trim()) {
trackEvent('ukvisajobs_search', {
searchTerm: input.searchTerm.trim(),
page: input.page ?? 1,
});
}
return fetchApi<UkVisaJobsSearchResponse>('/ukvisajobs/search', {
method: 'POST',
body: JSON.stringify(input),
@ -202,6 +209,13 @@ export async function searchVisaSponsors(input: {
limit?: number;
minScore?: number;
}): Promise<VisaSponsorSearchResponse> {
if (input.query?.trim()) {
trackEvent('visa_sponsor_search', {
query: input.query.trim(),
limit: input.limit,
minScore: input.minScore,
});
}
return fetchApi<VisaSponsorSearchResponse>('/visa-sponsors/search', {
method: 'POST',
body: JSON.stringify(input),

View File

@ -0,0 +1,14 @@
type UmamiTracker = {
track: (event: string, data?: Record<string, unknown>) => void;
};
declare global {
interface Window {
umami?: UmamiTracker;
}
}
export function trackEvent(event: string, data?: Record<string, unknown>) {
if (typeof window === 'undefined') return;
window.umami?.track(event, data);
}

View File

@ -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 =
'<script defer src="https://umami.dakheera47.com/script.js" data-website-id="0dc42ed1-87c3-4ac0-9409-5a9b9588fe66"></script>';
if (html.includes(snippet)) return html;
if (html.includes('</head>')) {
return html.replace('</head>', `${snippet}\n</head>`);
}
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);
});
}