Tracer links (#174)

* initial commit

* format links right

jobops.dakheera47.com/cv/shaheer-google-de

* don't support legacy

* remove phishing look

* smaller links

* readiness check in settings

* rework UX

* right col

* pop a modal

* modal improvements

* show links

* documentation disclaimer

* fix(tracer-links): preserve descriptive resume link labels

* fix(tracer-links): classify bot user agents before browser families

* fix(tracer-links): reject non-http redirect destinations

* fix(tracer-redirect): disable caching for tracked redirects

* fix(origin): prefer canonical public base url over forwarded headers

* fix(auth): protect tracer analytics routes behind basic auth

* fix(ui): rename misleading tracer drilldown human metric

* style(tests): format tracer-links invalid-destination assertion

* fix(tests): prevent mocked fs from breaking sqlite data-dir resolution

* style(docs): format versioned docs json for biome

* fix(tests): mock tracer-links in pdf skills validation suite
This commit is contained in:
Shaheer Sarfaraz
2026-02-18 22:05:15 +00:00
committed by GitHub
parent 1146d065f0
commit 5ed74bb59c
44 changed files with 4025 additions and 28 deletions
+1
View File
@@ -35,6 +35,7 @@ export const createJob = (overrides: Partial<Job> = {}): Job => ({
tailoredSkills: null,
selectedProjectIds: null,
pdfPath: null,
tracerLinksEnabled: false,
sponsorMatchScore: null,
sponsorMatchNames: null,
jobType: null,
+101
View File
@@ -162,6 +162,7 @@ export interface Job {
tailoredSkills: string | null; // Generated resume skills (JSON)
selectedProjectIds: string | null; // Comma-separated IDs of selected projects
pdfPath: string | null; // Path to generated PDF
tracerLinksEnabled: boolean; // Rewrite outbound resume links to tracer links on next PDF generation
sponsorMatchScore: number | null; // 0-100 fuzzy match score with visa sponsors
sponsorMatchNames: string | null; // JSON array of matched sponsor names (when 100% matches or top match)
@@ -317,6 +318,7 @@ export interface UpdateJobInput {
tailoredSkills?: string;
selectedProjectIds?: string;
pdfPath?: string;
tracerLinksEnabled?: boolean;
appliedAt?: string;
sponsorMatchScore?: number;
sponsorMatchNames?: string;
@@ -368,6 +370,105 @@ export type ApiResponse<T> =
meta: ApiMeta;
};
export interface TracerAnalyticsTimeseriesPoint {
day: string; // YYYY-MM-DD
clicks: number;
uniqueOpens: number;
botClicks: number;
humanClicks: number;
}
export interface TracerAnalyticsTopJob {
jobId: string;
title: string;
employer: string;
clicks: number;
uniqueOpens: number;
botClicks: number;
humanClicks: number;
lastClickedAt: number | null;
}
export interface TracerAnalyticsTopLink {
tracerLinkId: string;
token: string;
jobId: string;
title: string;
employer: string;
sourcePath: string;
sourceLabel: string;
destinationUrl: string;
clicks: number;
uniqueOpens: number;
botClicks: number;
humanClicks: number;
lastClickedAt: number | null;
}
export interface TracerAnalyticsResponse {
filters: {
jobId: string | null;
from: number | null;
to: number | null;
includeBots: boolean;
limit: number;
};
totals: {
clicks: number;
uniqueOpens: number;
botClicks: number;
humanClicks: number;
};
timeSeries: TracerAnalyticsTimeseriesPoint[];
topJobs: TracerAnalyticsTopJob[];
topLinks: TracerAnalyticsTopLink[];
}
export interface JobTracerLinkAnalyticsItem {
tracerLinkId: string;
token: string;
sourcePath: string;
sourceLabel: string;
destinationUrl: string;
isActive: boolean;
createdAt: string;
updatedAt: string;
clicks: number;
uniqueOpens: number;
botClicks: number;
humanClicks: number;
lastClickedAt: number | null;
}
export interface JobTracerLinksResponse {
job: {
id: string;
title: string;
employer: string;
tracerLinksEnabled: boolean;
};
totals: {
links: number;
clicks: number;
uniqueOpens: number;
botClicks: number;
humanClicks: number;
};
links: JobTracerLinkAnalyticsItem[];
}
export type TracerReadinessStatus = "ready" | "unconfigured" | "unavailable";
export interface TracerReadinessResponse {
status: TracerReadinessStatus;
canEnable: boolean;
publicBaseUrl: string | null;
healthUrl: string | null;
checkedAt: number;
lastSuccessAt: number | null;
reason: string | null;
}
export const POST_APPLICATION_PROVIDERS = ["gmail", "imap"] as const;
export type PostApplicationProvider =
(typeof POST_APPLICATION_PROVIDERS)[number];