Skip to content

Database & Prisma

All schema, migrations, and the generated client live in packages/database. The database is the canonical source of truth for the platform — every app reads its types from @repo/database and never re-declares them.

  • Schema source: packages/database/prisma/schema.prisma.
  • Migrations: packages/database/prisma/migrations/<YYYYMMDDHHmmss>_<snake_case>/.
  • Generated client is the Prisma client — apps import it via @repo/database.
  • New models follow the existing style: id String @id @default(cuid()), createdAt DateTime @default(now()), updatedAt DateTime @updatedAt, and indexes on every column commonly used for filtering or scoping (especially organizationId).
  • Soft / “legacy” columns are kept around with a comment when a structured replacement supersedes them; the rename is sequenced over multiple migrations.
  • Organization — a tenant. Owns candidate profiles (when staffed), job postings, applications, and audit logs.
  • WorkOsUser — local mirror of WorkOS user metadata, populated by user.created / user.updated webhooks. Used for relational integrity when local rows need a foreign key to a user.
  • CandidateProfile — the canonical candidate record. Owns resume, skills, work prefs, links, and discoverability state.
  • CandidateFileObject — S3-backed file references for profile picture and resume.
  • ResumeConversion — async resume → markdown extraction state and result.
  • PublicResumeLink — token-gated shareable URL for a candidate’s resume.
  • CandidateInvitation — token-gated invite for a candidate to claim or onboard.
  • JobPosting — an open role at an organization.
  • JobPostingPublicLink — token-gated public URL to a job posting.
  • JobPostingRequest — the company intake form (a request that produces a JobPosting once submitted).
  • Application — a candidate’s application to a posting; unique per (jobPostingId, candidateProfileId).
  • ApplicationStageEvent — every advance/reject/withdraw event, with actor and timestamp.
  • CandidateNote — internal / application-scoped notes left on candidates.
  • NormalizedSkill — canonical skill registry with parent/child grouping and approval state.
  • NormalizedSkillSource — link table between a CandidateProfile’s raw skill string and a NormalizedSkill.
  • AuditLog — append-only record of privileged actions, indexed by organizationId, actorSub, action, targetType, and createdAt.
  1. Edit packages/database/prisma/schema.prisma.
  2. Run pnpm run db:migrate:dev from the repo root (or npx prisma migrate dev from packages/database).
  3. Commit the generated migration in packages/database/prisma/migrations/.
  4. Regenerate clients via pnpm run db:generate.
  5. If the change renames or removes a field, update every consuming app in the same PR.

For the most permission-sensitive models, the matrix below documents who can read and who can write each field. Until field-level extraction is automated, this is hand-maintained and should be updated when a service changes its field filtering.

FieldReadWrite
firstName, lastName, headline, bio, location*owner member, company actors w/ app context, teros-ops*owner member, teros-ops*
skills, workPreferences, portfolioLinks, yearsOfExperiencesame as abovesame as above
linkedinUrl, githubUrlsame as abovesame as above
phoneNumber, phoneCountryCodeowner member, teros-ops*owner member, teros-ops*
profileImage*, resume*owner member, company actors w/ app context, teros-ops*owner member, teros-ops*
isDiscoverableowner member, teros-ops*owner member, teros-ops*
approvalStatus, approvedAt, approvedByWorkosUserSubteros-ops*teros-ops*
salaryExpectation*, currentSalaryowner member, teros-ops*owner member, teros-ops*
FieldReadWrite
coverNotecandidate (own), company actors of posting org, teros-ops*candidate at submission only
statuscandidate (own), company actors, teros-ops*company-admin, teros-ops*; company-member limited
offerStartDate, offeredSalarycandidate (own), company-admin, teros-ops*company-admin, teros-ops*
withdrawnAtcandidate (own), company actors, teros-ops*candidate (own withdraw), teros-ops*
terminatedAtcompany-admin, teros-ops*company-admin, teros-ops*
FieldReadWrite
title, description, location*, employmentType, isRemote, currencypublic (via public link or list), all rolescompany-admin, teros-ops*
minCompensation, maxCompensationpublic listing, all internal rolescompany-admin, teros-ops*
status, openedAt, closedAtpublic listing, all internal rolescompany-admin, teros-ops*
createdBySub, updatedBySubcompany-admin, teros-ops*system-managed
FieldReadWrite
action, actorSub, targetType, targetId, createdAt, metadatacompany-admin (scoped to own organizationId), teros-ops* (cross-org)system
organizationId, actorOrgIdsame as abovesystem
FieldReadWrite
content (visibility = INTERNAL)teros-ops* onlyteros-ops*
content (visibility = APPLICATION)company actors of the posting org, teros-ops*company actors of the posting org, teros-ops*
authorSubsame as content for that visibilitysystem-managed
FieldReadWrite
name, slug, descriptionmembers of org, teros-ops*company-admin (own org), teros-ops*
workosOrganizationId, ownerWorkosUserIdteros-ops*system-managed (WorkOS sync)
  • API — how services enforce these access decisions.
  • Migrations — the loop for changing the schema safely.
  • Permissions Matrix — endpoint-level view of the same model.