diff --git a/packages/core/prisma/migrations/20260517110000_add_profile_skill_and_skill_category/migration.sql b/packages/core/prisma/migrations/20260517110000_add_profile_skill_and_skill_category/migration.sql new file mode 100644 index 0000000..3ac9f59 --- /dev/null +++ b/packages/core/prisma/migrations/20260517110000_add_profile_skill_and_skill_category/migration.sql @@ -0,0 +1,59 @@ +-- AlterTable +ALTER TABLE "skills" ADD COLUMN "categoryId" TEXT; + +-- CreateTable +CREATE TABLE "skill_categories" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "description" TEXT, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "skill_categories_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "profile_skills" ( + "id" TEXT NOT NULL, + "profileId" TEXT NOT NULL, + "skillId" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "profile_skills_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "skill_categories_name_key" ON "skill_categories"("name"); + +-- CreateIndex +CREATE INDEX "profile_skills_profileId_idx" ON "profile_skills"("profileId"); + +-- CreateIndex +CREATE UNIQUE INDEX "profile_skills_profileId_skillId_key" ON "profile_skills"("profileId", "skillId"); + +-- AddForeignKey +ALTER TABLE "skills" ADD CONSTRAINT "skills_categoryId_fkey" FOREIGN KEY ("categoryId") REFERENCES "skill_categories"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "profile_skills" ADD CONSTRAINT "profile_skills_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES "profiles"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "profile_skills" ADD CONSTRAINT "profile_skills_skillId_fkey" FOREIGN KEY ("skillId") REFERENCES "skills"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + + +-- Data backfill: derive ProfileSkill rows from existing skill references on +-- job experiences and education entries. Union the two relations and dedupe. +-- The @@unique([profileId, skillId]) constraint catches anything UNION misses. +INSERT INTO "profile_skills" ("id", "profileId", "skillId", "createdAt", "updatedAt") +SELECT gen_random_uuid()::text, derived."profileId", derived."skillId", NOW(), NOW() +FROM ( + SELECT uje."profileId", st."A" AS "skillId" + FROM "_SkillToUserJobExperience" st + JOIN "user_job_experiences" uje ON uje."id" = st."B" + UNION + SELECT e."profileId", ets."B" AS "skillId" + FROM "_EducationToSkill" ets + JOIN "educations" e ON e."id" = ets."A" +) AS derived +ON CONFLICT ("profileId", "skillId") DO NOTHING; diff --git a/packages/core/prisma/models/job-experience.prisma b/packages/core/prisma/models/job-experience.prisma index 89f9326..2106fb5 100644 --- a/packages/core/prisma/models/job-experience.prisma +++ b/packages/core/prisma/models/job-experience.prisma @@ -1,10 +1,28 @@ +/// Broad skill category (Technical, Business, Creative, …). Seeded as +/// reference data so adding a new category is a row insert, not a +/// migration. Nullable on Skill — uncategorized is fine. +model SkillCategory { + id String @id @default(cuid()) + name String @unique + description String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + skills Skill[] + + @@map("skill_categories") +} + model Skill { id String @id @default(cuid()) name String @unique description String? + categoryId String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + category SkillCategory? @relation(fields: [categoryId], references: [id], onDelete: SetNull) + // Job experiences that use this skill jobExperiences UserJobExperience[] @@ -14,6 +32,10 @@ model Skill { // Education entries that use this skill educations Education[] + // Per-user "I have this skill" claims. Catalog row stays shared; the + // claim itself lives on ProfileSkill. + profileSkills ProfileSkill[] + @@map("skills") } diff --git a/packages/core/prisma/models/profile-skill.prisma b/packages/core/prisma/models/profile-skill.prisma new file mode 100644 index 0000000..d267799 --- /dev/null +++ b/packages/core/prisma/models/profile-skill.prisma @@ -0,0 +1,18 @@ +/// Per-user "I have this skill" claim. The `Skill` row stays a shared +/// catalog (unique-on-name, used as a controlled vocabulary across users +/// + vacancies); ProfileSkill is the user-owned link with claim-time +/// metadata. +model ProfileSkill { + id String @id @default(cuid()) + profileId String + skillId String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + profile Profile @relation(fields: [profileId], references: [id], onDelete: Cascade) + skill Skill @relation(fields: [skillId], references: [id], onDelete: Restrict) + + @@unique([profileId, skillId]) + @@index([profileId]) + @@map("profile_skills") +} diff --git a/packages/core/prisma/models/profile.prisma b/packages/core/prisma/models/profile.prisma index cdb4cf0..74b2c58 100644 --- a/packages/core/prisma/models/profile.prisma +++ b/packages/core/prisma/models/profile.prisma @@ -18,6 +18,7 @@ model Profile { location Location? @relation(fields: [locationId], references: [id], onDelete: SetNull) educations Education[] jobExperiences UserJobExperience[] + skills ProfileSkill[] cvs CV[] applications Application[] userFiles UserFile[]