Appearance
Epic 48 — Admin Off-Platform Job Ingestion & Claim
New. Depends on Epic 39 (Email Delivery — not built yet; the invite email needs it). Decision (user, 2026-06-16): Admins can create job postings on behalf of other users by scraping an external page. If a producer user already exists with the scraped email, that user owns the job. Otherwise, email the address that a job was created on their behalf and invite them to create a free account to claim the posting and unlock system features.
✅ SHIPPED 2026-06-19. Epic 39 unblocked this. Decision (user, 2026-06-19): unclaimed jobs are NOT hidden —
Job.produceris fully nullable and unclaimed imports are OPEN + swipeable by talents (shown with the scraped company/contact name, or nothing). Backend:services/jobImport/scrape.tsparseJobPosting(JSON-LDJobPosting+ OG + email regex, zero deps — no cheerio) +scrapeJobUrl;Job.producerIdnullable +pendingOwnerEmail/claimToken @unique/claimedAt(migration20260619100000); GraphQLJob.producer/producerIdnullable; the 4 non-null-producer spots guarded (tryFormMatchskips producer-less jobs — talent likes are held as JobInteractions and become warm leads on claim);createJobForEmail(owned →JOB_CREATED_ON_BEHALFnotification/email; unknown email → unclaimed + token +jobClaimInviteemail),claimJob(token)(race-safe, makes a ProducerProfile if needed),listClaimableJobs; GraphQLscrapeJobPosting/adminCreateJobForEmail(admin) +claimJob/claimableJobs(auth);JOB_CREATED_ON_BEHALFadded to all 4 notification places + registry + authz-matrix. Frontend: null-producer fallback across DiscoverPage/TalentJobsPage/JobDetailPage/PublicJobPage/flier pages (producer?.companyName ?? contactPersonName ?? '', producer chrome only when present);AdminJobImportPage(paste URL → scrape → preview/edit → create-for-email, reached from an "Import a job" button on/admin/jobs);ClaimPage(/claim?token=) +ClaimBanner(post-login email-matched catch-all on the feed); routes/pageTitles/i18n (claim.*en/pt/es). Tests: scrape fixtures, owned-vs-unclaimed branching, claim/token validation, ClaimPage flow. BE 1468 / FE app 580 / DS 44 green; typecheck + lint + authz-matrix (399) clean. Deploy: run the Prisma migration. Completes the 2026-06-16 realignment wave (Epics 40–48).
Why: Seeds the marketplace with real off-platform postings and a growth loop (claim-to-onboard). The building blocks exist — admin auth (requireAdmin), createJob (sets producerId), user-lookup-by-email, createNotification — but there's no scraper, no admin create-for-user path, no unclaimed-job/claim model, and no email (Epic 39).
Architecture: An admin-only scrape utility fetches a URL and extracts job fields + a contact email (native fetch + a lightweight HTML/OG parser — add cheerio or parse OG/JSON-LD). adminCreateJobForEmail either assigns ownership to the matching producer's ProducerProfile, or creates an unclaimed job: Job.producerId nullable + pendingOwnerEmail + claimToken + claimedAt, with an invite email (Epic 39) + a new JOB_CREATED_ON_BEHALF notification. On register/login with that email (or via the signed claim link), the user claims the job and becomes its producer owner.
BE-JOBIMPORT-001 — Admin scrape/parse utility
Files:
- Create:
castyou-backend/src/services/jobImport/scrape.ts— admin-only fetch + parse (title, description, location, contact email, …) from a URL via OG/JSON-LD +cheerio; returns a draft job + detected email. Addcheeriotopackage.json.
Acceptance criteria: Given a sample posting URL/HTML, returns structured draft fields + the detected contact email (unit-tested against fixtures, no live network in tests).
BE-JOBIMPORT-002 — Create-on-behalf + unclaimed ownership
Files:
- Edit:
castyou-backend/prisma/schema.prisma—Job.producerIdnullable +pendingOwnerEmail String?,claimToken String? @unique,claimedAt DateTime?. Requiresprisma migrate. - Create/Edit: admin resolver
adminCreateJobForEmail(draft, sourceUrl)(requireAdmin) — if a producer exists for the email, set owner; else create unclaimed (signedclaimToken), enqueue invite email (Epic 39), and fireJOB_CREATED_ON_BEHALF(newNotificationTypeinmodels/mongo/Notification.ts+NotificationTypes).
Acceptance criteria: Existing-producer email → owned job; unknown email → unclaimed job + invite email queued + claim token minted; admin-only.
BE-JOBIMPORT-003 — Claim flow
Files:
- Create/Edit:
claimJob(token)mutation (auth) + register/login hook — assigns the caller'sProducerProfile(creating one if needed) as the job's owner, clearspendingOwnerEmail/claimToken, setsclaimedAt; rejects expired/used tokens.
Acceptance criteria: A user with the matching email claims the job via the signed link and becomes its owner; reused/forged tokens are rejected.
FE-JOBIMPORT-001 — Admin import page
Files:
- Create:
castyou-frontend/apps/app/src/pages/admin/AdminJobImportPage.tsx— paste URL → preview scraped fields → edit → "Create for email"; DS + i18n +pageTitles.
Acceptance criteria: Admin pastes a URL, reviews/edits parsed fields, and creates the on-behalf job; shows whether it was owned or unclaimed+invited.
FE-JOBIMPORT-002 — Claim banner/page
Files:
- Create: a claim landing (from the email link) + a post-login "claim this job" banner for invited users.
Acceptance criteria: Invited user lands on a claim page, authenticates/registers (free), and claims the posting.
TEST-JOBIMPORT-001 — Ingestion + claim coverage
Acceptance criteria: Scrape parsing (fixtures), owned-vs-unclaimed branching, the email/notification trigger, and the claim/token validation are tested.