Shaheer Sarfaraz 5ed74bb59c
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
2026-02-18 22:05:15 +00:00

484 lines
18 KiB
TypeScript

/**
* Database schema using Drizzle ORM with SQLite.
*/
import {
APPLICATION_OUTCOMES,
APPLICATION_STAGES,
APPLICATION_TASK_TYPES,
INTERVIEW_OUTCOMES,
INTERVIEW_TYPES,
JOB_CHAT_MESSAGE_ROLES,
JOB_CHAT_MESSAGE_STATUSES,
JOB_CHAT_RUN_STATUSES,
POST_APPLICATION_INTEGRATION_STATUSES,
POST_APPLICATION_MESSAGE_TYPES,
POST_APPLICATION_PROCESSING_STATUSES,
POST_APPLICATION_PROVIDERS,
POST_APPLICATION_RELEVANCE_DECISIONS,
POST_APPLICATION_SYNC_RUN_STATUSES,
} from "@shared/types";
import { sql } from "drizzle-orm";
import {
index,
integer,
real,
sqliteTable,
text,
uniqueIndex,
} from "drizzle-orm/sqlite-core";
export const jobs = sqliteTable("jobs", {
id: text("id").primaryKey(),
// From crawler
source: text("source", {
enum: [
"gradcracker",
"indeed",
"linkedin",
"glassdoor",
"ukvisajobs",
"adzuna",
"manual",
],
})
.notNull()
.default("gradcracker"),
sourceJobId: text("source_job_id"),
jobUrlDirect: text("job_url_direct"),
datePosted: text("date_posted"),
title: text("title").notNull(),
employer: text("employer").notNull(),
employerUrl: text("employer_url"),
jobUrl: text("job_url").notNull().unique(),
applicationLink: text("application_link"),
disciplines: text("disciplines"),
deadline: text("deadline"),
salary: text("salary"),
location: text("location"),
degreeRequired: text("degree_required"),
starting: text("starting"),
jobDescription: text("job_description"),
// JobSpy fields (nullable for other sources)
jobType: text("job_type"),
salarySource: text("salary_source"),
salaryInterval: text("salary_interval"),
salaryMinAmount: real("salary_min_amount"),
salaryMaxAmount: real("salary_max_amount"),
salaryCurrency: text("salary_currency"),
isRemote: integer("is_remote", { mode: "boolean" }),
jobLevel: text("job_level"),
jobFunction: text("job_function"),
listingType: text("listing_type"),
emails: text("emails"),
companyIndustry: text("company_industry"),
companyLogo: text("company_logo"),
companyUrlDirect: text("company_url_direct"),
companyAddresses: text("company_addresses"),
companyNumEmployees: text("company_num_employees"),
companyRevenue: text("company_revenue"),
companyDescription: text("company_description"),
skills: text("skills"),
experienceRange: text("experience_range"),
companyRating: real("company_rating"),
companyReviewsCount: integer("company_reviews_count"),
vacancyCount: integer("vacancy_count"),
workFromHomeType: text("work_from_home_type"),
// Orchestrator enrichments
status: text("status", {
enum: [
"discovered",
"processing",
"ready",
"applied",
"in_progress",
"skipped",
"expired",
],
})
.notNull()
.default("discovered"),
outcome: text("outcome", { enum: APPLICATION_OUTCOMES }),
closedAt: integer("closed_at", { mode: "number" }),
suitabilityScore: real("suitability_score"),
suitabilityReason: text("suitability_reason"),
tailoredSummary: text("tailored_summary"),
tailoredHeadline: text("tailored_headline"),
tailoredSkills: text("tailored_skills"),
selectedProjectIds: text("selected_project_ids"),
pdfPath: text("pdf_path"),
tracerLinksEnabled: integer("tracer_links_enabled", { mode: "boolean" })
.notNull()
.default(false),
sponsorMatchScore: real("sponsor_match_score"),
sponsorMatchNames: text("sponsor_match_names"),
// Timestamps
discoveredAt: text("discovered_at").notNull().default(sql`(datetime('now'))`),
processedAt: text("processed_at"),
appliedAt: text("applied_at"),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
});
export const stageEvents = sqliteTable("stage_events", {
id: text("id").primaryKey(),
applicationId: text("application_id")
.notNull()
.references(() => jobs.id, { onDelete: "cascade" }),
title: text("title").notNull(),
groupId: text("group_id"),
fromStage: text("from_stage", { enum: APPLICATION_STAGES }),
toStage: text("to_stage", { enum: APPLICATION_STAGES }).notNull(),
occurredAt: integer("occurred_at", { mode: "number" }).notNull(),
metadata: text("metadata", { mode: "json" }),
outcome: text("outcome", { enum: APPLICATION_OUTCOMES }),
});
export const tasks = sqliteTable("tasks", {
id: text("id").primaryKey(),
applicationId: text("application_id")
.notNull()
.references(() => jobs.id, { onDelete: "cascade" }),
type: text("type", { enum: APPLICATION_TASK_TYPES }).notNull(),
title: text("title").notNull(),
dueDate: integer("due_date", { mode: "number" }),
isCompleted: integer("is_completed", { mode: "boolean" })
.notNull()
.default(false),
notes: text("notes"),
});
export const interviews = sqliteTable("interviews", {
id: text("id").primaryKey(),
applicationId: text("application_id")
.notNull()
.references(() => jobs.id, { onDelete: "cascade" }),
scheduledAt: integer("scheduled_at", { mode: "number" }).notNull(),
durationMins: integer("duration_mins"),
type: text("type", { enum: INTERVIEW_TYPES }).notNull(),
outcome: text("outcome", { enum: INTERVIEW_OUTCOMES }),
});
export const pipelineRuns = sqliteTable("pipeline_runs", {
id: text("id").primaryKey(),
startedAt: text("started_at").notNull().default(sql`(datetime('now'))`),
completedAt: text("completed_at"),
status: text("status", {
enum: ["running", "completed", "failed", "cancelled"],
})
.notNull()
.default("running"),
jobsDiscovered: integer("jobs_discovered").notNull().default(0),
jobsProcessed: integer("jobs_processed").notNull().default(0),
errorMessage: text("error_message"),
});
export const jobChatThreads = sqliteTable(
"job_chat_threads",
{
id: text("id").primaryKey(),
jobId: text("job_id")
.notNull()
.references(() => jobs.id, { onDelete: "cascade" }),
title: text("title"),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
lastMessageAt: text("last_message_at"),
},
(table) => ({
jobUpdatedIndex: index("idx_job_chat_threads_job_updated").on(
table.jobId,
table.updatedAt,
),
}),
);
export const jobChatMessages = sqliteTable(
"job_chat_messages",
{
id: text("id").primaryKey(),
threadId: text("thread_id")
.notNull()
.references(() => jobChatThreads.id, { onDelete: "cascade" }),
jobId: text("job_id")
.notNull()
.references(() => jobs.id, { onDelete: "cascade" }),
role: text("role", { enum: JOB_CHAT_MESSAGE_ROLES }).notNull(),
content: text("content").notNull().default(""),
status: text("status", { enum: JOB_CHAT_MESSAGE_STATUSES })
.notNull()
.default("partial"),
tokensIn: integer("tokens_in"),
tokensOut: integer("tokens_out"),
version: integer("version").notNull().default(1),
replacesMessageId: text("replaces_message_id"),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
},
(table) => ({
threadCreatedIndex: index("idx_job_chat_messages_thread_created").on(
table.threadId,
table.createdAt,
),
}),
);
export const jobChatRuns = sqliteTable(
"job_chat_runs",
{
id: text("id").primaryKey(),
threadId: text("thread_id")
.notNull()
.references(() => jobChatThreads.id, { onDelete: "cascade" }),
jobId: text("job_id")
.notNull()
.references(() => jobs.id, { onDelete: "cascade" }),
status: text("status", { enum: JOB_CHAT_RUN_STATUSES })
.notNull()
.default("running"),
model: text("model"),
provider: text("provider"),
errorCode: text("error_code"),
errorMessage: text("error_message"),
startedAt: integer("started_at", { mode: "number" }).notNull(),
completedAt: integer("completed_at", { mode: "number" }),
requestId: text("request_id"),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
},
(table) => ({
threadStatusIndex: index("idx_job_chat_runs_thread_status").on(
table.threadId,
table.status,
),
}),
);
export const settings = sqliteTable("settings", {
key: text("key").primaryKey(),
value: text("value").notNull(),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
});
export const postApplicationIntegrations = sqliteTable(
"post_application_integrations",
{
id: text("id").primaryKey(),
provider: text("provider", { enum: POST_APPLICATION_PROVIDERS }).notNull(),
accountKey: text("account_key").notNull().default("default"),
displayName: text("display_name"),
status: text("status", { enum: POST_APPLICATION_INTEGRATION_STATUSES })
.notNull()
.default("disconnected"),
credentials: text("credentials", { mode: "json" }),
lastConnectedAt: integer("last_connected_at", { mode: "number" }),
lastSyncedAt: integer("last_synced_at", { mode: "number" }),
lastError: text("last_error"),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
},
(table) => ({
providerAccountUnique: uniqueIndex(
"idx_post_app_integrations_provider_account_unique",
).on(table.provider, table.accountKey),
}),
);
export const postApplicationSyncRuns = sqliteTable(
"post_application_sync_runs",
{
id: text("id").primaryKey(),
provider: text("provider", { enum: POST_APPLICATION_PROVIDERS }).notNull(),
accountKey: text("account_key").notNull().default("default"),
integrationId: text("integration_id").references(
() => postApplicationIntegrations.id,
{ onDelete: "set null" },
),
status: text("status", { enum: POST_APPLICATION_SYNC_RUN_STATUSES })
.notNull()
.default("running"),
startedAt: integer("started_at", { mode: "number" }).notNull(),
completedAt: integer("completed_at", { mode: "number" }),
messagesDiscovered: integer("messages_discovered").notNull().default(0),
messagesRelevant: integer("messages_relevant").notNull().default(0),
messagesClassified: integer("messages_classified").notNull().default(0),
messagesMatched: integer("messages_matched").notNull().default(0),
messagesApproved: integer("messages_approved").notNull().default(0),
messagesDenied: integer("messages_denied").notNull().default(0),
messagesErrored: integer("messages_errored").notNull().default(0),
errorCode: text("error_code"),
errorMessage: text("error_message"),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
},
(table) => ({
providerAccountStartedAtIndex: index(
"idx_post_app_sync_runs_provider_account_started_at",
).on(table.provider, table.accountKey, table.startedAt),
}),
);
export const postApplicationMessages = sqliteTable(
"post_application_messages",
{
id: text("id").primaryKey(),
provider: text("provider", { enum: POST_APPLICATION_PROVIDERS }).notNull(),
accountKey: text("account_key").notNull().default("default"),
integrationId: text("integration_id").references(
() => postApplicationIntegrations.id,
{ onDelete: "set null" },
),
syncRunId: text("sync_run_id").references(
() => postApplicationSyncRuns.id,
{
onDelete: "set null",
},
),
externalMessageId: text("external_message_id").notNull(),
externalThreadId: text("external_thread_id"),
fromAddress: text("from_address").notNull().default(""),
fromDomain: text("from_domain"),
senderName: text("sender_name"),
subject: text("subject").notNull().default(""),
receivedAt: integer("received_at", { mode: "number" }).notNull(),
snippet: text("snippet").notNull().default(""),
classificationLabel: text("classification_label"),
classificationConfidence: real("classification_confidence"),
classificationPayload: text("classification_payload", { mode: "json" }),
relevanceLlmScore: real("relevance_llm_score"),
relevanceDecision: text("relevance_decision", {
enum: POST_APPLICATION_RELEVANCE_DECISIONS,
})
.notNull()
.default("needs_llm"),
matchConfidence: integer("match_confidence"),
messageType: text("message_type", {
enum: POST_APPLICATION_MESSAGE_TYPES,
})
.notNull()
.default("other"),
stageEventPayload: text("stage_event_payload", { mode: "json" }),
processingStatus: text("processing_status", {
enum: POST_APPLICATION_PROCESSING_STATUSES,
})
.notNull()
.default("pending_user"),
matchedJobId: text("matched_job_id").references(() => jobs.id, {
onDelete: "set null",
}),
decidedAt: integer("decided_at", { mode: "number" }),
decidedBy: text("decided_by"),
errorCode: text("error_code"),
errorMessage: text("error_message"),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
},
(table) => ({
providerAccountExternalMessageUnique: uniqueIndex(
"idx_post_app_messages_provider_account_external_unique",
).on(table.provider, table.accountKey, table.externalMessageId),
providerAccountReviewStatusIndex: index(
"idx_post_app_messages_provider_account_processing_status",
).on(table.provider, table.accountKey, table.processingStatus),
}),
);
export const tracerLinks = sqliteTable(
"tracer_links",
{
id: text("id").primaryKey(),
token: text("token").notNull().unique(),
jobId: text("job_id")
.notNull()
.references(() => jobs.id, { onDelete: "cascade" }),
sourcePath: text("source_path").notNull(),
sourceLabel: text("source_label").notNull(),
destinationUrl: text("destination_url").notNull(),
destinationUrlHash: text("destination_url_hash").notNull(),
isActive: integer("is_active", { mode: "boolean" }).notNull().default(true),
createdAt: text("created_at").notNull().default(sql`(datetime('now'))`),
updatedAt: text("updated_at").notNull().default(sql`(datetime('now'))`),
},
(table) => ({
jobPathDestinationUnique: uniqueIndex(
"idx_tracer_links_job_source_destination_unique",
).on(table.jobId, table.sourcePath, table.destinationUrlHash),
jobIndex: index("idx_tracer_links_job_id").on(table.jobId),
}),
);
export const tracerClickEvents = sqliteTable(
"tracer_click_events",
{
id: text("id").primaryKey(),
tracerLinkId: text("tracer_link_id")
.notNull()
.references(() => tracerLinks.id, { onDelete: "cascade" }),
clickedAt: integer("clicked_at", { mode: "number" }).notNull(),
requestId: text("request_id"),
isLikelyBot: integer("is_likely_bot", { mode: "boolean" })
.notNull()
.default(false),
deviceType: text("device_type").notNull().default("unknown"),
uaFamily: text("ua_family").notNull().default("unknown"),
osFamily: text("os_family").notNull().default("unknown"),
referrerHost: text("referrer_host"),
ipHash: text("ip_hash"),
uniqueFingerprintHash: text("unique_fingerprint_hash"),
},
(table) => ({
tracerLinkIndex: index("idx_tracer_click_events_tracer_link_id").on(
table.tracerLinkId,
),
clickedAtIndex: index("idx_tracer_click_events_clicked_at").on(
table.clickedAt,
),
botIndex: index("idx_tracer_click_events_is_likely_bot").on(
table.isLikelyBot,
),
uniqueFingerprintIndex: index(
"idx_tracer_click_events_unique_fingerprint_hash",
).on(table.uniqueFingerprintHash),
}),
);
export type JobRow = typeof jobs.$inferSelect;
export type NewJobRow = typeof jobs.$inferInsert;
export type StageEventRow = typeof stageEvents.$inferSelect;
export type NewStageEventRow = typeof stageEvents.$inferInsert;
export type TaskRow = typeof tasks.$inferSelect;
export type NewTaskRow = typeof tasks.$inferInsert;
export type InterviewRow = typeof interviews.$inferSelect;
export type NewInterviewRow = typeof interviews.$inferInsert;
export type PipelineRunRow = typeof pipelineRuns.$inferSelect;
export type NewPipelineRunRow = typeof pipelineRuns.$inferInsert;
export type JobChatThreadRow = typeof jobChatThreads.$inferSelect;
export type NewJobChatThreadRow = typeof jobChatThreads.$inferInsert;
export type JobChatMessageRow = typeof jobChatMessages.$inferSelect;
export type NewJobChatMessageRow = typeof jobChatMessages.$inferInsert;
export type JobChatRunRow = typeof jobChatRuns.$inferSelect;
export type NewJobChatRunRow = typeof jobChatRuns.$inferInsert;
export type SettingsRow = typeof settings.$inferSelect;
export type NewSettingsRow = typeof settings.$inferInsert;
export type PostApplicationIntegrationRow =
typeof postApplicationIntegrations.$inferSelect;
export type NewPostApplicationIntegrationRow =
typeof postApplicationIntegrations.$inferInsert;
export type PostApplicationSyncRunRow =
typeof postApplicationSyncRuns.$inferSelect;
export type NewPostApplicationSyncRunRow =
typeof postApplicationSyncRuns.$inferInsert;
export type PostApplicationMessageRow =
typeof postApplicationMessages.$inferSelect;
export type NewPostApplicationMessageRow =
typeof postApplicationMessages.$inferInsert;
export type TracerLinkRow = typeof tracerLinks.$inferSelect;
export type NewTracerLinkRow = typeof tracerLinks.$inferInsert;
export type TracerClickEventRow = typeof tracerClickEvents.$inferSelect;
export type NewTracerClickEventRow = typeof tracerClickEvents.$inferInsert;