source being extracted

This commit is contained in:
DaKheera47
2025-12-14 19:12:58 +00:00
parent acc015ea7f
commit 4a00b3b900
6 changed files with 32 additions and 1 deletions
@@ -16,6 +16,7 @@ import {
XCircle, XCircle,
} from "lucide-react"; } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card";
import type { Job } from "../../shared/types"; import type { Job } from "../../shared/types";
@@ -52,6 +53,10 @@ export const JobCard: React.FC<JobCardProps> = ({
onProcess, onProcess,
isProcessing, isProcessing,
}) => { }) => {
const sourceLabel: Record<Job["source"], string> = {
gradcracker: "Gradcracker",
};
const hasPdf = !!job.pdfPath; const hasPdf = !!job.pdfPath;
const canApply = job.status === "ready"; const canApply = job.status === "ready";
const canProcess = job.status === "discovered"; const canProcess = job.status === "discovered";
@@ -72,6 +77,9 @@ export const JobCard: React.FC<JobCardProps> = ({
<div className="flex flex-wrap items-center gap-3"> <div className="flex flex-wrap items-center gap-3">
<ScoreIndicator score={job.suitabilityScore} /> <ScoreIndicator score={job.suitabilityScore} />
<Badge variant="outline" className="uppercase tracking-wide">
{sourceLabel[job.source]}
</Badge>
<StatusBadge status={job.status} /> <StatusBadge status={job.status} />
</div> </div>
</div> </div>
@@ -181,4 +189,3 @@ export const JobCard: React.FC<JobCardProps> = ({
</Card> </Card>
); );
}; };
+15
View File
@@ -25,6 +25,7 @@ const sqlite = new Database(DB_PATH);
const migrations = [ const migrations = [
`CREATE TABLE IF NOT EXISTS jobs ( `CREATE TABLE IF NOT EXISTS jobs (
id TEXT PRIMARY KEY, id TEXT PRIMARY KEY,
source TEXT NOT NULL DEFAULT 'gradcracker',
title TEXT NOT NULL, title TEXT NOT NULL,
employer TEXT NOT NULL, employer TEXT NOT NULL,
employer_url TEXT, employer_url TEXT,
@@ -60,6 +61,10 @@ const migrations = [
error_message TEXT error_message TEXT
)`, )`,
// Add source column for existing databases (safe to skip if already present)
`ALTER TABLE jobs ADD COLUMN source TEXT NOT NULL DEFAULT 'gradcracker'`,
`UPDATE jobs SET source = 'gradcracker' WHERE source IS NULL OR source = ''`,
`CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status)`, `CREATE INDEX IF NOT EXISTS idx_jobs_status ON jobs(status)`,
`CREATE INDEX IF NOT EXISTS idx_jobs_discovered_at ON jobs(discovered_at)`, `CREATE INDEX IF NOT EXISTS idx_jobs_discovered_at ON jobs(discovered_at)`,
`CREATE INDEX IF NOT EXISTS idx_pipeline_runs_started_at ON pipeline_runs(started_at)`, `CREATE INDEX IF NOT EXISTS idx_pipeline_runs_started_at ON pipeline_runs(started_at)`,
@@ -72,6 +77,16 @@ for (const migration of migrations) {
sqlite.exec(migration); sqlite.exec(migration);
console.log('✅ Migration applied'); console.log('✅ Migration applied');
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : String(error);
const isDuplicateSourceColumn =
migration.includes('ALTER TABLE jobs ADD COLUMN source') &&
message.toLowerCase().includes('duplicate column name');
if (isDuplicateSourceColumn) {
console.log('↩️ Migration skipped (source column already exists)');
continue;
}
console.error('❌ Migration failed:', error); console.error('❌ Migration failed:', error);
process.exit(1); process.exit(1);
} }
+1
View File
@@ -9,6 +9,7 @@ export const jobs = sqliteTable('jobs', {
id: text('id').primaryKey(), id: text('id').primaryKey(),
// From crawler // From crawler
source: text('source', { enum: ['gradcracker'] }).notNull().default('gradcracker'),
title: text('title').notNull(), title: text('title').notNull(),
employer: text('employer').notNull(), employer: text('employer').notNull(),
employerUrl: text('employer_url'), employerUrl: text('employer_url'),
@@ -60,6 +60,7 @@ export async function createJob(input: CreateJobInput): Promise<Job> {
await db.insert(jobs).values({ await db.insert(jobs).values({
id, id,
source: input.source,
title: input.title, title: input.title,
employer: input.employer, employer: input.employer,
employerUrl: input.employerUrl ?? null, employerUrl: input.employerUrl ?? null,
@@ -171,6 +172,7 @@ export async function getJobsForProcessing(limit: number = 10): Promise<Job[]> {
function mapRowToJob(row: typeof jobs.$inferSelect): Job { function mapRowToJob(row: typeof jobs.$inferSelect): Job {
return { return {
id: row.id, id: row.id,
source: row.source as Job['source'],
title: row.title, title: row.title,
employer: row.employer, employer: row.employer,
employerUrl: row.employerUrl, employerUrl: row.employerUrl,
@@ -144,6 +144,7 @@ async function readCrawledJobs(): Promise<CreateJobInput[]> {
// Map crawler output to our job input format // Map crawler output to our job input format
jobs.push({ jobs.push({
source: 'gradcracker',
title: data.title || 'Unknown Title', title: data.title || 'Unknown Title',
employer: data.employer || 'Unknown Employer', employer: data.employer || 'Unknown Employer',
employerUrl: data.employerUrl, employerUrl: data.employerUrl,
+5
View File
@@ -10,10 +10,14 @@ export type JobStatus =
| 'rejected' // User rejected this job | 'rejected' // User rejected this job
| 'expired'; // Deadline passed | 'expired'; // Deadline passed
export type JobSource =
| 'gradcracker';
export interface Job { export interface Job {
id: string; id: string;
// From crawler // From crawler
source: JobSource;
title: string; title: string;
employer: string; employer: string;
employerUrl: string | null; employerUrl: string | null;
@@ -44,6 +48,7 @@ export interface Job {
} }
export interface CreateJobInput { export interface CreateJobInput {
source: JobSource;
title: string; title: string;
employer: string; employer: string;
employerUrl?: string; employerUrl?: string;