source being extracted
This commit is contained in:
@@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user