mirror of
https://github.com/yangshun/tech-interview-handbook.git
synced 2025-08-02 02:52:40 +08:00
[offers][feat] Add Offers Schema, View Offer Profiles API, and Create Offer Profile API (#353)
* [offers][fix] fix merge conflicts * [offers][chore] Create prisma schema * [offers][feat] add create endpoint for profiles * [offers][feature] Create list offers API with filter functionality * [offers][fix] fix bugs create profile bugs * [offers][fix] fix create profile bugs * [offers][feat] Add sorting functionality to list offers Co-authored-by: Stuart Long Chay Boon <chayboon@gmail.com>
This commit is contained in:

committed by
GitHub

parent
f8031caa2f
commit
356eeb6954
204
apps/portal/prisma/migrations/20221009160601_/migration.sql
Normal file
204
apps/portal/prisma/migrations/20221009160601_/migration.sql
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
-- CreateEnum
|
||||||
|
CREATE TYPE "JobType" AS ENUM ('INTERN', 'FULLTIME');
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersProfile" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"profileName" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"editToken" TEXT NOT NULL,
|
||||||
|
"userId" TEXT,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersProfile_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersBackground" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"totalYoe" INTEGER,
|
||||||
|
"offersProfileId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersBackground_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersSpecificYoe" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"yoe" INTEGER NOT NULL,
|
||||||
|
"domain" TEXT NOT NULL,
|
||||||
|
"backgroundId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersSpecificYoe_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersExperience" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"companyId" TEXT,
|
||||||
|
"jobType" "JobType",
|
||||||
|
"title" TEXT,
|
||||||
|
"durationInMonths" INTEGER,
|
||||||
|
"specialization" TEXT,
|
||||||
|
"level" TEXT,
|
||||||
|
"totalCompensationId" TEXT,
|
||||||
|
"monthlySalaryId" TEXT,
|
||||||
|
"backgroundId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersExperience_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersCurrency" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"value" INTEGER NOT NULL,
|
||||||
|
"currency" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersCurrency_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersEducation" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"type" TEXT,
|
||||||
|
"field" TEXT,
|
||||||
|
"isAttending" BOOLEAN,
|
||||||
|
"school" TEXT,
|
||||||
|
"startDate" TIMESTAMP(3),
|
||||||
|
"endDate" TIMESTAMP(3),
|
||||||
|
"backgroundId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersEducation_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersReply" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"creator" TEXT NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"message" TEXT NOT NULL,
|
||||||
|
"replyingToId" TEXT,
|
||||||
|
"profileId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersReply_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersOffer" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"profileId" TEXT NOT NULL,
|
||||||
|
"companyId" TEXT NOT NULL,
|
||||||
|
"monthYearReceived" TIMESTAMP(3) NOT NULL,
|
||||||
|
"location" TEXT NOT NULL,
|
||||||
|
"negotiationStrategy" TEXT,
|
||||||
|
"comments" TEXT,
|
||||||
|
"jobType" "JobType" NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersOffer_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersIntern" (
|
||||||
|
"offerId" TEXT NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"specialization" TEXT NOT NULL,
|
||||||
|
"internshipCycle" TEXT NOT NULL,
|
||||||
|
"startYear" INTEGER NOT NULL,
|
||||||
|
"monthlySalaryId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersIntern_pkey" PRIMARY KEY ("offerId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "OffersFullTime" (
|
||||||
|
"offerId" TEXT NOT NULL,
|
||||||
|
"title" TEXT NOT NULL,
|
||||||
|
"specialization" TEXT NOT NULL,
|
||||||
|
"level" TEXT NOT NULL,
|
||||||
|
"totalCompensationId" TEXT NOT NULL,
|
||||||
|
"baseSalaryId" TEXT NOT NULL,
|
||||||
|
"bonusId" TEXT NOT NULL,
|
||||||
|
"stocksId" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "OffersFullTime_pkey" PRIMARY KEY ("offerId")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "OffersBackground_offersProfileId_key" ON "OffersBackground"("offersProfileId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "OffersExperience_totalCompensationId_key" ON "OffersExperience"("totalCompensationId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "OffersExperience_monthlySalaryId_key" ON "OffersExperience"("monthlySalaryId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "OffersIntern_monthlySalaryId_key" ON "OffersIntern"("monthlySalaryId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "OffersFullTime_totalCompensationId_key" ON "OffersFullTime"("totalCompensationId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "OffersFullTime_baseSalaryId_key" ON "OffersFullTime"("baseSalaryId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "OffersFullTime_bonusId_key" ON "OffersFullTime"("bonusId");
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "OffersFullTime_stocksId_key" ON "OffersFullTime"("stocksId");
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersProfile" ADD CONSTRAINT "OffersProfile_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersBackground" ADD CONSTRAINT "OffersBackground_offersProfileId_fkey" FOREIGN KEY ("offersProfileId") REFERENCES "OffersProfile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersSpecificYoe" ADD CONSTRAINT "OffersSpecificYoe_backgroundId_fkey" FOREIGN KEY ("backgroundId") REFERENCES "OffersBackground"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersExperience" ADD CONSTRAINT "OffersExperience_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersExperience" ADD CONSTRAINT "OffersExperience_totalCompensationId_fkey" FOREIGN KEY ("totalCompensationId") REFERENCES "OffersCurrency"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersExperience" ADD CONSTRAINT "OffersExperience_monthlySalaryId_fkey" FOREIGN KEY ("monthlySalaryId") REFERENCES "OffersCurrency"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersExperience" ADD CONSTRAINT "OffersExperience_backgroundId_fkey" FOREIGN KEY ("backgroundId") REFERENCES "OffersBackground"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersEducation" ADD CONSTRAINT "OffersEducation_backgroundId_fkey" FOREIGN KEY ("backgroundId") REFERENCES "OffersBackground"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersReply" ADD CONSTRAINT "OffersReply_replyingToId_fkey" FOREIGN KEY ("replyingToId") REFERENCES "OffersReply"("id") ON DELETE SET NULL ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersReply" ADD CONSTRAINT "OffersReply_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES "OffersProfile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersOffer" ADD CONSTRAINT "OffersOffer_profileId_fkey" FOREIGN KEY ("profileId") REFERENCES "OffersProfile"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersOffer" ADD CONSTRAINT "OffersOffer_companyId_fkey" FOREIGN KEY ("companyId") REFERENCES "Company"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersIntern" ADD CONSTRAINT "OffersIntern_offerId_fkey" FOREIGN KEY ("offerId") REFERENCES "OffersOffer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersIntern" ADD CONSTRAINT "OffersIntern_monthlySalaryId_fkey" FOREIGN KEY ("monthlySalaryId") REFERENCES "OffersCurrency"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersFullTime" ADD CONSTRAINT "OffersFullTime_offerId_fkey" FOREIGN KEY ("offerId") REFERENCES "OffersOffer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersFullTime" ADD CONSTRAINT "OffersFullTime_totalCompensationId_fkey" FOREIGN KEY ("totalCompensationId") REFERENCES "OffersCurrency"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersFullTime" ADD CONSTRAINT "OffersFullTime_baseSalaryId_fkey" FOREIGN KEY ("baseSalaryId") REFERENCES "OffersCurrency"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersFullTime" ADD CONSTRAINT "OffersFullTime_bonusId_fkey" FOREIGN KEY ("bonusId") REFERENCES "OffersCurrency"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "OffersFullTime" ADD CONSTRAINT "OffersFullTime_stocksId_fkey" FOREIGN KEY ("stocksId") REFERENCES "OffersCurrency"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
@ -58,6 +58,7 @@ model User {
|
|||||||
questionsAnswerVotes QuestionsAnswerVote[]
|
questionsAnswerVotes QuestionsAnswerVote[]
|
||||||
questionsAnswerComments QuestionsAnswerComment[]
|
questionsAnswerComments QuestionsAnswerComment[]
|
||||||
questionsAnswerCommentVotes QuestionsAnswerCommentVote[]
|
questionsAnswerCommentVotes QuestionsAnswerCommentVote[]
|
||||||
|
OffersProfile OffersProfile[]
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Vote {
|
enum Vote {
|
||||||
@ -89,13 +90,15 @@ enum TodoStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
model Company {
|
model Company {
|
||||||
id String @id @default(cuid())
|
id String @id @default(cuid())
|
||||||
name String @db.Text
|
name String @db.Text
|
||||||
slug String @unique
|
slug String @unique
|
||||||
description String? @db.Text
|
description String? @db.Text
|
||||||
logoUrl String?
|
logoUrl String?
|
||||||
createdAt DateTime @default(now())
|
createdAt DateTime @default(now())
|
||||||
updatedAt DateTime @updatedAt
|
updatedAt DateTime @updatedAt
|
||||||
|
OffersExperience OffersExperience[]
|
||||||
|
OffersOffer OffersOffer[]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start of Resumes project models.
|
// Start of Resumes project models.
|
||||||
@ -170,6 +173,179 @@ model ResumesCommentVote {
|
|||||||
// Add Offers project models here, prefix all models with "Offer",
|
// Add Offers project models here, prefix all models with "Offer",
|
||||||
// use camelCase for field names, and try to name them consistently
|
// use camelCase for field names, and try to name them consistently
|
||||||
// across all models in this file.
|
// across all models in this file.
|
||||||
|
|
||||||
|
model OffersProfile {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
profileName String @unique
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
|
||||||
|
background OffersBackground?
|
||||||
|
|
||||||
|
editToken String
|
||||||
|
|
||||||
|
discussion OffersReply[]
|
||||||
|
|
||||||
|
offers OffersOffer[]
|
||||||
|
|
||||||
|
user User? @relation(fields: [userId], references: [id])
|
||||||
|
userId String?
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersBackground {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
totalYoe Int?
|
||||||
|
specificYoes OffersSpecificYoe[]
|
||||||
|
|
||||||
|
experiences OffersExperience[] // For extensibility in the future
|
||||||
|
|
||||||
|
educations OffersEducation[] // For extensibility in the future
|
||||||
|
|
||||||
|
profile OffersProfile @relation(fields: [offersProfileId], references: [id])
|
||||||
|
offersProfileId String @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersSpecificYoe {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
yoe Int
|
||||||
|
domain String
|
||||||
|
|
||||||
|
background OffersBackground @relation(fields: [backgroundId], references: [id])
|
||||||
|
backgroundId String
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersExperience {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
company Company? @relation(fields: [companyId], references: [id])
|
||||||
|
companyId String?
|
||||||
|
|
||||||
|
jobType JobType?
|
||||||
|
title String?
|
||||||
|
|
||||||
|
// Add more fields
|
||||||
|
durationInMonths Int?
|
||||||
|
specialization String?
|
||||||
|
|
||||||
|
// FULLTIME fields
|
||||||
|
level String?
|
||||||
|
totalCompensation OffersCurrency? @relation("ExperienceTotalCompensation", fields: [totalCompensationId], references: [id])
|
||||||
|
totalCompensationId String? @unique
|
||||||
|
|
||||||
|
// INTERN fields
|
||||||
|
monthlySalary OffersCurrency? @relation("ExperienceMonthlySalary", fields: [monthlySalaryId], references: [id])
|
||||||
|
monthlySalaryId String? @unique
|
||||||
|
|
||||||
|
background OffersBackground @relation(fields: [backgroundId], references: [id])
|
||||||
|
backgroundId String
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersCurrency {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
value Int
|
||||||
|
currency String
|
||||||
|
|
||||||
|
// Experience
|
||||||
|
OffersExperienceTotalCompensation OffersExperience? @relation("ExperienceTotalCompensation")
|
||||||
|
OffersExperienceMonthlySalary OffersExperience? @relation("ExperienceMonthlySalary")
|
||||||
|
|
||||||
|
// Full Time
|
||||||
|
OffersTotalCompensation OffersFullTime? @relation("OfferTotalCompensation")
|
||||||
|
OffersBaseSalary OffersFullTime? @relation("OfferBaseSalary")
|
||||||
|
OffersBonus OffersFullTime? @relation("OfferBonus")
|
||||||
|
OffersStocks OffersFullTime? @relation("OfferStocks")
|
||||||
|
|
||||||
|
// Intern
|
||||||
|
OffersMonthlySalary OffersIntern?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum JobType {
|
||||||
|
INTERN
|
||||||
|
FULLTIME
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersEducation {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
type String?
|
||||||
|
field String?
|
||||||
|
|
||||||
|
// Add more fields
|
||||||
|
school String?
|
||||||
|
startDate DateTime?
|
||||||
|
endDate DateTime?
|
||||||
|
|
||||||
|
background OffersBackground @relation(fields: [backgroundId], references: [id])
|
||||||
|
backgroundId String
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersReply {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
creator String
|
||||||
|
createdAt DateTime @default(now())
|
||||||
|
message String
|
||||||
|
|
||||||
|
replyingToId String?
|
||||||
|
replyingTo OffersReply? @relation("ReplyThread", fields: [replyingToId], references: [id])
|
||||||
|
replies OffersReply[] @relation("ReplyThread")
|
||||||
|
|
||||||
|
profile OffersProfile @relation(fields: [profileId], references: [id])
|
||||||
|
profileId String
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersOffer {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
profile OffersProfile @relation(fields: [profileId], references: [id])
|
||||||
|
profileId String
|
||||||
|
|
||||||
|
company Company @relation(fields: [companyId], references: [id])
|
||||||
|
companyId String
|
||||||
|
|
||||||
|
monthYearReceived DateTime
|
||||||
|
location String
|
||||||
|
negotiationStrategy String?
|
||||||
|
comments String?
|
||||||
|
|
||||||
|
jobType JobType
|
||||||
|
|
||||||
|
OffersIntern OffersIntern? @relation(fields: [offersInternId], references: [id])
|
||||||
|
offersInternId String? @unique
|
||||||
|
|
||||||
|
OffersFullTime OffersFullTime? @relation(fields: [offersFullTimeId], references: [id])
|
||||||
|
offersFullTimeId String? @unique
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersIntern {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
|
||||||
|
title String
|
||||||
|
specialization String
|
||||||
|
internshipCycle String
|
||||||
|
startYear Int
|
||||||
|
monthlySalary OffersCurrency @relation(fields: [monthlySalaryId], references: [id])
|
||||||
|
monthlySalaryId String @unique
|
||||||
|
|
||||||
|
OffersOffer OffersOffer?
|
||||||
|
}
|
||||||
|
|
||||||
|
model OffersFullTime {
|
||||||
|
id String @id @default(cuid())
|
||||||
|
title String
|
||||||
|
specialization String
|
||||||
|
level String
|
||||||
|
totalCompensation OffersCurrency @relation("OfferTotalCompensation", fields: [totalCompensationId], references: [id])
|
||||||
|
totalCompensationId String @unique
|
||||||
|
baseSalary OffersCurrency @relation("OfferBaseSalary", fields: [baseSalaryId], references: [id])
|
||||||
|
baseSalaryId String @unique
|
||||||
|
bonus OffersCurrency @relation("OfferBonus", fields: [bonusId], references: [id])
|
||||||
|
bonusId String @unique
|
||||||
|
stocks OffersCurrency @relation("OfferStocks", fields: [stocksId], references: [id])
|
||||||
|
stocksId String @unique
|
||||||
|
|
||||||
|
OffersOffer OffersOffer?
|
||||||
|
}
|
||||||
|
|
||||||
// End of Offers project models.
|
// End of Offers project models.
|
||||||
|
|
||||||
// Start of Questions project models.
|
// Start of Questions project models.
|
||||||
|
@ -35,9 +35,37 @@ const COMPANIES = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const OFFER_PROFILES = [
|
||||||
|
{
|
||||||
|
id: 'cl91v97ex000109mt7fka5rto',
|
||||||
|
profileName: 'battery-horse-stable-cow',
|
||||||
|
editToken: 'cl91ulmhg000009l86o45aspt',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cl91v9iw2000209mtautgdnxq',
|
||||||
|
profileName: 'house-zebra-fast-giraffe',
|
||||||
|
editToken: 'cl91umigc000109l80f1tcqe8',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cl91v9m3y000309mt1ctw55wi',
|
||||||
|
profileName: 'keyboard-mouse-lazy-cat',
|
||||||
|
editToken: 'cl91ummoa000209l87q2b8hl7',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cl91v9p09000409mt5rvoasf1',
|
||||||
|
profileName: 'router-hen-bright-pig',
|
||||||
|
editToken: 'cl91umqa3000309l87jyefe9k',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'cl91v9uda000509mt5i5fez3v',
|
||||||
|
profileName: 'screen-ant-dirty-bird',
|
||||||
|
editToken: 'cl91umuj9000409l87ez85vmg',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
console.log('Seeding started...');
|
console.log('Seeding started...');
|
||||||
await Promise.all(
|
await Promise.all([
|
||||||
COMPANIES.map(async (company) => {
|
COMPANIES.map(async (company) => {
|
||||||
await prisma.company.upsert({
|
await prisma.company.upsert({
|
||||||
where: { slug: company.slug },
|
where: { slug: company.slug },
|
||||||
@ -45,7 +73,14 @@ async function main() {
|
|||||||
create: company,
|
create: company,
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
);
|
OFFER_PROFILES.map(async (offerProfile) => {
|
||||||
|
await prisma.offersProfile.upsert({
|
||||||
|
where: { profileName: offerProfile.profileName },
|
||||||
|
update: offerProfile,
|
||||||
|
create: offerProfile,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
]);
|
||||||
console.log('Seeding completed.');
|
console.log('Seeding completed.');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
apps/portal/src/pages/offers/test.tsx
Normal file
26
apps/portal/src/pages/offers/test.tsx
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
const data = trpc.useQuery([
|
||||||
|
'offers.list',
|
||||||
|
{
|
||||||
|
limit: 3,
|
||||||
|
location: 'Singapore, Singapore',
|
||||||
|
offset: 0,
|
||||||
|
sortBy: '-monthYearReceived',
|
||||||
|
yoeCategory: 0,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul>
|
||||||
|
{data.data?.map((x) => {
|
||||||
|
return <li key={x.id}>{JSON.stringify(x)}</li>;
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default test;
|
144
apps/portal/src/pages/offers/testCreateProfile.tsx
Normal file
144
apps/portal/src/pages/offers/testCreateProfile.tsx
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
import React, { useState } from 'react';
|
||||||
|
|
||||||
|
import { trpc } from '~/utils/trpc';
|
||||||
|
|
||||||
|
function Test() {
|
||||||
|
// F const data = trpc.useQuery([
|
||||||
|
// 'offers.profile.',
|
||||||
|
// {
|
||||||
|
// limit: 3,
|
||||||
|
// location: 'Singapore, Singapore',
|
||||||
|
// offset: 0,
|
||||||
|
// yoeCategory: 0,
|
||||||
|
// },
|
||||||
|
// ]);
|
||||||
|
|
||||||
|
const [createdData, setCreatedData] = useState("")
|
||||||
|
|
||||||
|
const createMutation = trpc.useMutation(['offers.profile.create'], {
|
||||||
|
onError(error: any) {
|
||||||
|
alert(error)
|
||||||
|
},
|
||||||
|
onSuccess(data) {
|
||||||
|
setCreatedData(JSON.stringify(data))
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
createMutation.mutate({
|
||||||
|
"background": {
|
||||||
|
"educations": [
|
||||||
|
{
|
||||||
|
"endDate": new Date("2018-09-30T07:58:54.000Z"),
|
||||||
|
"field": "Computer Science",
|
||||||
|
"school": "National University of Singapore",
|
||||||
|
"startDate": new Date("2014-09-30T07:58:54.000Z"),
|
||||||
|
"type": "Bachelors"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"experiences": [
|
||||||
|
{
|
||||||
|
"companyId": "cl92ly8xm0000w3mwh5ymyqmx",
|
||||||
|
"durationInMonths": 24,
|
||||||
|
"jobType": "FULLTIME",
|
||||||
|
"level": "Junior",
|
||||||
|
// "monthlySalary": undefined,
|
||||||
|
"specialization": "Front End",
|
||||||
|
"title": "Software Engineer",
|
||||||
|
"totalCompensation": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 104100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"specificYoes": [
|
||||||
|
{
|
||||||
|
"domain": "Front End",
|
||||||
|
"yoe": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"domain": "Full Stack",
|
||||||
|
"yoe": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"totalYoe": 4
|
||||||
|
},
|
||||||
|
"offers": [
|
||||||
|
{
|
||||||
|
"comments": "",
|
||||||
|
"companyId": "cl92ly8xm0000w3mwh5ymyqmx",
|
||||||
|
"job": {
|
||||||
|
"base": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 84000
|
||||||
|
},
|
||||||
|
"bonus": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 20000
|
||||||
|
},
|
||||||
|
"level": "Junior",
|
||||||
|
"specialization": "Front End",
|
||||||
|
"stocks": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 100
|
||||||
|
},
|
||||||
|
"title": "Software Engineer",
|
||||||
|
"totalCompensation": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 104100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobType": "FULLTIME",
|
||||||
|
"location": "Singapore, Singapore",
|
||||||
|
"monthYearReceived": new Date("2022-09-30T07:58:54.000Z"),
|
||||||
|
"negotiationStrategy": "Leveraged having multiple offers"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"comments": "",
|
||||||
|
"companyId": "cl92ly8xm0000w3mwh5ymyqmx",
|
||||||
|
"job": {
|
||||||
|
"base": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 84000
|
||||||
|
},
|
||||||
|
"bonus": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 20000
|
||||||
|
},
|
||||||
|
"level": "Junior",
|
||||||
|
"specialization": "Front End",
|
||||||
|
"stocks": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 100
|
||||||
|
},
|
||||||
|
"title": "Software Engineer",
|
||||||
|
"totalCompensation": {
|
||||||
|
"currency": "SGD",
|
||||||
|
"value": 104100
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"jobType": "FULLTIME",
|
||||||
|
"location": "Singapore, Singapore",
|
||||||
|
"monthYearReceived": new Date("2022-09-30T07:58:54.000Z"),
|
||||||
|
"negotiationStrategy": "Leveraged having multiple offers"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
// <ul>
|
||||||
|
// {createdData.map((x) => {
|
||||||
|
// return <li key={x.id}>{JSON.stringify(x)}</li>;
|
||||||
|
// })}
|
||||||
|
// </ul>
|
||||||
|
<>
|
||||||
|
<div>
|
||||||
|
{createdData}
|
||||||
|
</div>
|
||||||
|
<button type="button" onClick={handleClick}>Click me</button>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Test;
|
@ -2,6 +2,8 @@ import superjson from 'superjson';
|
|||||||
|
|
||||||
import { companiesRouter } from './companies-router';
|
import { companiesRouter } from './companies-router';
|
||||||
import { createRouter } from './context';
|
import { createRouter } from './context';
|
||||||
|
import { offersRouter } from './offers';
|
||||||
|
import { offersProfileRouter } from './offers-profile-router';
|
||||||
import { protectedExampleRouter } from './protected-example-router';
|
import { protectedExampleRouter } from './protected-example-router';
|
||||||
import { questionsAnswerCommentRouter } from './questions-answer-comment-router';
|
import { questionsAnswerCommentRouter } from './questions-answer-comment-router';
|
||||||
import { questionsAnswerRouter } from './questions-answer-router';
|
import { questionsAnswerRouter } from './questions-answer-router';
|
||||||
@ -32,7 +34,9 @@ export const appRouter = createRouter()
|
|||||||
.merge('questions.answers.comments.', questionsAnswerCommentRouter)
|
.merge('questions.answers.comments.', questionsAnswerCommentRouter)
|
||||||
.merge('questions.answers.', questionsAnswerRouter)
|
.merge('questions.answers.', questionsAnswerRouter)
|
||||||
.merge('questions.questions.comments.', questionsQuestionCommentRouter)
|
.merge('questions.questions.comments.', questionsQuestionCommentRouter)
|
||||||
.merge('questions.questions.', questionsQuestionRouter);
|
.merge('questions.questions.', questionsQuestionRouter)
|
||||||
|
.merge('offers.', offersRouter)
|
||||||
|
.merge('offers.profile.', offersProfileRouter);
|
||||||
|
|
||||||
// Export type definition of API
|
// Export type definition of API
|
||||||
export type AppRouter = typeof appRouter;
|
export type AppRouter = typeof appRouter;
|
||||||
|
269
apps/portal/src/server/router/offers-profile-router.ts
Normal file
269
apps/portal/src/server/router/offers-profile-router.ts
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
import crypto, { randomUUID } from "crypto";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { Prisma } from "@prisma/client";
|
||||||
|
|
||||||
|
import { createProtectedRouter } from "./context";
|
||||||
|
|
||||||
|
const valuation = z.object({
|
||||||
|
currency: z.string(),
|
||||||
|
value: z.number(),
|
||||||
|
})
|
||||||
|
|
||||||
|
// TODO: handle both full time and intern
|
||||||
|
const offer = z.object({
|
||||||
|
comments: z.string(),
|
||||||
|
companyId: z.string(),
|
||||||
|
job: z.object({
|
||||||
|
base: valuation.optional(), // Full time
|
||||||
|
bonus: valuation.optional(), // Full time
|
||||||
|
internshipCycle: z.string().optional(), // Intern
|
||||||
|
level: z.string().optional(), // Full time
|
||||||
|
monthlySalary: valuation.optional(), // Intern
|
||||||
|
specialization: z.string(),
|
||||||
|
startYear: z.number().optional(), // Intern
|
||||||
|
stocks: valuation.optional(), // Full time
|
||||||
|
title: z.string(),
|
||||||
|
totalCompensation: valuation.optional(), // Full time
|
||||||
|
}),
|
||||||
|
jobType: z.string(),
|
||||||
|
location: z.string(),
|
||||||
|
monthYearReceived: z.date(),
|
||||||
|
negotiationStrategy: z.string(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const experience = z.object({
|
||||||
|
companyId: z.string().optional(),
|
||||||
|
durationInMonths: z.number().optional(),
|
||||||
|
jobType: z.string().optional(),
|
||||||
|
level: z.string().optional(),
|
||||||
|
monthlySalary: valuation.optional(),
|
||||||
|
specialization: z.string().optional(),
|
||||||
|
title: z.string().optional(),
|
||||||
|
totalCompensation: valuation.optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
const education = z.object({
|
||||||
|
endDate: z.date().optional(),
|
||||||
|
field: z.string().optional(),
|
||||||
|
school: z.string().optional(),
|
||||||
|
startDate: z.date().optional(),
|
||||||
|
type: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
|
export const offersProfileRouter = createProtectedRouter().mutation(
|
||||||
|
'create',
|
||||||
|
{
|
||||||
|
input: z.object({
|
||||||
|
background: z.object({
|
||||||
|
educations: z.array(education),
|
||||||
|
experiences: z.array(experience),
|
||||||
|
specificYoes: z.array(z.object({
|
||||||
|
domain: z.string(),
|
||||||
|
yoe: z.number()
|
||||||
|
})),
|
||||||
|
totalYoe: z.number().optional(),
|
||||||
|
}),
|
||||||
|
offers: z.array(offer)
|
||||||
|
}),
|
||||||
|
async resolve({ ctx, input }) {
|
||||||
|
|
||||||
|
// TODO: add more
|
||||||
|
const token = crypto
|
||||||
|
.createHash("sha256")
|
||||||
|
.update(Date.now().toString())
|
||||||
|
.digest("hex");
|
||||||
|
|
||||||
|
const profile = await ctx.prisma.offersProfile.create({
|
||||||
|
data: {
|
||||||
|
background: {
|
||||||
|
create: {
|
||||||
|
educations: {
|
||||||
|
create:
|
||||||
|
input.background.educations.map((x) => ({
|
||||||
|
endDate: x.endDate,
|
||||||
|
field: x.field,
|
||||||
|
school: x.school,
|
||||||
|
startDate: x.startDate,
|
||||||
|
type: x.type
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
experiences: {
|
||||||
|
create:
|
||||||
|
input.background.experiences.map((x) => {
|
||||||
|
if (x.jobType === "FULLTIME" && x.totalCompensation?.currency !== undefined && x.totalCompensation.value !== undefined) {
|
||||||
|
return {
|
||||||
|
company: {
|
||||||
|
connect: {
|
||||||
|
id: x.companyId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
durationInMonths: x.durationInMonths,
|
||||||
|
jobType: x.jobType,
|
||||||
|
level: x.level,
|
||||||
|
specialization: x.specialization,
|
||||||
|
title: x.title,
|
||||||
|
totalCompensation: {
|
||||||
|
create: {
|
||||||
|
currency: x.totalCompensation?.currency,
|
||||||
|
value: x.totalCompensation?.value,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (x.jobType === "INTERN" && x.monthlySalary?.currency !== undefined && x.monthlySalary.value !== undefined) {
|
||||||
|
return {
|
||||||
|
company: {
|
||||||
|
connect: {
|
||||||
|
id: x.companyId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
durationInMonths: x.durationInMonths,
|
||||||
|
jobType: x.jobType,
|
||||||
|
monthlySalary: {
|
||||||
|
create: {
|
||||||
|
currency: x.monthlySalary?.currency,
|
||||||
|
value: x.monthlySalary?.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specialization: x.specialization,
|
||||||
|
title: x.title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw Prisma.PrismaClientKnownRequestError
|
||||||
|
|
||||||
|
})
|
||||||
|
},
|
||||||
|
specificYoes: {
|
||||||
|
create:
|
||||||
|
input.background.specificYoes.map((x) => ({
|
||||||
|
domain: x.domain,
|
||||||
|
yoe: x.yoe
|
||||||
|
}))
|
||||||
|
},
|
||||||
|
totalYoe: input.background.totalYoe,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
editToken: token,
|
||||||
|
offers: {
|
||||||
|
create:
|
||||||
|
input.offers.map((x) => {
|
||||||
|
if (x.jobType === "INTERN" && x.job.internshipCycle !== undefined && x.job.monthlySalary?.currency !== undefined && x.job.monthlySalary.value !== undefined && x.job.startYear !== undefined) {
|
||||||
|
return {
|
||||||
|
OffersIntern: {
|
||||||
|
create: {
|
||||||
|
internshipCycle: x.job.internshipCycle,
|
||||||
|
monthlySalary: {
|
||||||
|
create: {
|
||||||
|
currency: x.job.monthlySalary?.currency,
|
||||||
|
value: x.job.monthlySalary?.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specialization: x.job.specialization,
|
||||||
|
startYear: x.job.startYear,
|
||||||
|
title: x.job.title,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
comments: x.comments,
|
||||||
|
company: {
|
||||||
|
connect: {
|
||||||
|
id: x.companyId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
jobType: x.jobType,
|
||||||
|
location: x.location,
|
||||||
|
monthYearReceived: x.monthYearReceived,
|
||||||
|
negotiationStrategy: x.negotiationStrategy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (x.jobType === "FULLTIME" && x.job.base?.currency !== undefined && x.job.base?.value !== undefined && x.job.bonus?.currency !== undefined && x.job.bonus?.value !== undefined && x.job.stocks?.currency !== undefined && x.job.stocks?.value !== undefined && x.job.totalCompensation?.currency !== undefined && x.job.totalCompensation?.value !== undefined && x.job.level !== undefined) {
|
||||||
|
return {
|
||||||
|
OffersFullTime: {
|
||||||
|
create: {
|
||||||
|
baseSalary: {
|
||||||
|
create: {
|
||||||
|
currency: x.job.base?.currency,
|
||||||
|
value: x.job.base?.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
bonus: {
|
||||||
|
create: {
|
||||||
|
currency: x.job.bonus?.currency,
|
||||||
|
value: x.job.bonus?.value
|
||||||
|
}
|
||||||
|
},
|
||||||
|
level: x.job.level,
|
||||||
|
specialization: x.job.specialization,
|
||||||
|
stocks: {
|
||||||
|
create: {
|
||||||
|
currency: x.job.stocks?.currency,
|
||||||
|
value: x.job.stocks?.value,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
title: x.job.title,
|
||||||
|
totalCompensation: {
|
||||||
|
create: {
|
||||||
|
currency: x.job.totalCompensation?.currency,
|
||||||
|
value: x.job.totalCompensation?.value,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
comments: x.comments,
|
||||||
|
company: {
|
||||||
|
connect: {
|
||||||
|
id: x.companyId
|
||||||
|
}
|
||||||
|
},
|
||||||
|
jobType: x.jobType,
|
||||||
|
location: x.location,
|
||||||
|
monthYearReceived: x.monthYearReceived,
|
||||||
|
negotiationStrategy: x.negotiationStrategy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Throw error
|
||||||
|
throw Prisma.PrismaClientKnownRequestError
|
||||||
|
})
|
||||||
|
},
|
||||||
|
profileName: randomUUID(),
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
background: {
|
||||||
|
include: {
|
||||||
|
educations: true,
|
||||||
|
experiences: {
|
||||||
|
include: {
|
||||||
|
company: true,
|
||||||
|
monthlySalary: true,
|
||||||
|
totalCompensation: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
specificYoes: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
offers: {
|
||||||
|
include: {
|
||||||
|
OffersFullTime: {
|
||||||
|
include: {
|
||||||
|
baseSalary: true,
|
||||||
|
bonus: true,
|
||||||
|
stocks: true,
|
||||||
|
totalCompensation: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
OffersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: add analysis to profile object then return
|
||||||
|
return profile
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
297
apps/portal/src/server/router/offers.ts
Normal file
297
apps/portal/src/server/router/offers.ts
Normal file
@ -0,0 +1,297 @@
|
|||||||
|
import assert from 'assert';
|
||||||
|
import { z } from 'zod';
|
||||||
|
|
||||||
|
import { createRouter } from './context';
|
||||||
|
|
||||||
|
const yoeCategoryMap: Record<number, string> = {
|
||||||
|
0: 'Internship',
|
||||||
|
1: 'Fresh Grad',
|
||||||
|
2: 'Mid',
|
||||||
|
3: 'Senior',
|
||||||
|
};
|
||||||
|
|
||||||
|
const getYoeRange = (yoeCategory: number) => {
|
||||||
|
return yoeCategoryMap[yoeCategory] === 'Fresh Grad'
|
||||||
|
? { maxYoe: 3, minYoe: 0 }
|
||||||
|
: yoeCategoryMap[yoeCategory] === 'Mid'
|
||||||
|
? { maxYoe: 7, minYoe: 4 }
|
||||||
|
: yoeCategoryMap[yoeCategory] === 'Senior'
|
||||||
|
? { maxYoe: null, minYoe: 8 }
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const ascOrder = '+';
|
||||||
|
const descOrder = '-';
|
||||||
|
const sortingKeys = ['monthYearReceived', 'totalCompensation', 'yoe'];
|
||||||
|
|
||||||
|
const createSortByValidationRegex = () => {
|
||||||
|
const startsWithPlusOrMinusOnly = '^[+-]{1}';
|
||||||
|
const sortingKeysRegex = sortingKeys.join('|');
|
||||||
|
return new RegExp(startsWithPlusOrMinusOnly + '(' + sortingKeysRegex + ')');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const offersRouter = createRouter().query('list', {
|
||||||
|
input: z.object({
|
||||||
|
company: z.string().nullish(),
|
||||||
|
dateEnd: z.date().nullish(),
|
||||||
|
dateStart: z.date().nullish(),
|
||||||
|
limit: z.number().nonnegative(),
|
||||||
|
location: z.string(),
|
||||||
|
offset: z.number().nonnegative(),
|
||||||
|
salaryMax: z.number().nullish(),
|
||||||
|
salaryMin: z.number().nonnegative().nullish(),
|
||||||
|
sortBy: z.string().regex(createSortByValidationRegex()).nullish(),
|
||||||
|
title: z.string().nullish(),
|
||||||
|
yoeCategory: z.number().min(0).max(3),
|
||||||
|
}),
|
||||||
|
async resolve({ ctx, input }) {
|
||||||
|
const yoeRange = getYoeRange(input.yoeCategory);
|
||||||
|
|
||||||
|
let data = !yoeRange
|
||||||
|
? await ctx.prisma.offersOffer.findMany({
|
||||||
|
// Internship
|
||||||
|
include: {
|
||||||
|
OffersFullTime: {
|
||||||
|
include: {
|
||||||
|
baseSalary: true,
|
||||||
|
bonus: true,
|
||||||
|
stocks: true,
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
OffersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
skip: input.limit * input.offset,
|
||||||
|
take: input.limit,
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
location: input.location,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OffersIntern: {
|
||||||
|
isNot: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OffersFullTime: {
|
||||||
|
is: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: yoeRange.maxYoe
|
||||||
|
? await ctx.prisma.offersOffer.findMany({
|
||||||
|
include: {
|
||||||
|
OffersFullTime: {
|
||||||
|
include: {
|
||||||
|
baseSalary: true,
|
||||||
|
bonus: true,
|
||||||
|
stocks: true,
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
OffersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
// Junior, Mid
|
||||||
|
skip: input.limit * input.offset,
|
||||||
|
take: input.limit,
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
location: input.location,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OffersIntern: {
|
||||||
|
is: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OffersFullTime: {
|
||||||
|
isNot: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
profile: {
|
||||||
|
background: {
|
||||||
|
totalYoe: {
|
||||||
|
gte: yoeRange.minYoe,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
profile: {
|
||||||
|
background: {
|
||||||
|
totalYoe: {
|
||||||
|
gte: yoeRange.maxYoe,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: await ctx.prisma.offersOffer.findMany({
|
||||||
|
// Senior
|
||||||
|
include: {
|
||||||
|
OffersFullTime: {
|
||||||
|
include: {
|
||||||
|
baseSalary: true,
|
||||||
|
bonus: true,
|
||||||
|
stocks: true,
|
||||||
|
totalCompensation: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
OffersIntern: {
|
||||||
|
include: {
|
||||||
|
monthlySalary: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
company: true,
|
||||||
|
},
|
||||||
|
skip: input.limit * input.offset,
|
||||||
|
take: input.limit,
|
||||||
|
where: {
|
||||||
|
AND: [
|
||||||
|
{
|
||||||
|
location: input.location,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OffersIntern: {
|
||||||
|
is: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
OffersFullTime: {
|
||||||
|
isNot: null,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
profile: {
|
||||||
|
background: {
|
||||||
|
totalYoe: {
|
||||||
|
gte: yoeRange.minYoe,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
data = data.filter((offer) => {
|
||||||
|
let validRecord = true;
|
||||||
|
|
||||||
|
if (input.company) {
|
||||||
|
validRecord = validRecord && offer.company.name === input.company;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.title) {
|
||||||
|
validRecord =
|
||||||
|
validRecord &&
|
||||||
|
(offer.OffersFullTime?.title === input.title ||
|
||||||
|
offer.OffersIntern?.title === input.title);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.dateStart && input.dateEnd) {
|
||||||
|
validRecord =
|
||||||
|
validRecord &&
|
||||||
|
offer.monthYearReceived.getTime() >= input.dateStart.getTime() &&
|
||||||
|
offer.monthYearReceived.getTime() <= input.dateEnd.getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.salaryMin && input.salaryMax) {
|
||||||
|
const salary = offer.OffersFullTime?.totalCompensation.value
|
||||||
|
? offer.OffersFullTime?.totalCompensation.value
|
||||||
|
: offer.OffersIntern?.monthlySalary.value;
|
||||||
|
|
||||||
|
assert(salary);
|
||||||
|
|
||||||
|
validRecord =
|
||||||
|
validRecord && salary >= input.salaryMin && salary <= input.salaryMax;
|
||||||
|
}
|
||||||
|
|
||||||
|
return validRecord;
|
||||||
|
});
|
||||||
|
|
||||||
|
data = data.sort((offer1, offer2) => {
|
||||||
|
const defaultReturn =
|
||||||
|
offer2.monthYearReceived.getTime() - offer1.monthYearReceived.getTime();
|
||||||
|
|
||||||
|
if (!input.sortBy) {
|
||||||
|
return defaultReturn;
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = input.sortBy.charAt(0);
|
||||||
|
const sortingKey = input.sortBy.substring(1);
|
||||||
|
|
||||||
|
if (order === ascOrder) {
|
||||||
|
return (() => {
|
||||||
|
if (sortingKey === 'monthYearReceived') {
|
||||||
|
return (
|
||||||
|
offer1.monthYearReceived.getTime() -
|
||||||
|
offer2.monthYearReceived.getTime()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortingKey === 'totalCompensation') {
|
||||||
|
const salary1 = offer1.OffersFullTime?.totalCompensation.value
|
||||||
|
? offer1.OffersFullTime?.totalCompensation.value
|
||||||
|
: offer1.OffersIntern?.monthlySalary.value;
|
||||||
|
|
||||||
|
const salary2 = offer2.OffersFullTime?.totalCompensation.value
|
||||||
|
? offer2.OffersFullTime?.totalCompensation.value
|
||||||
|
: offer2.OffersIntern?.monthlySalary.value;
|
||||||
|
|
||||||
|
if (salary1 && salary2) {
|
||||||
|
return salary1 - salary2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultReturn;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (order === descOrder) {
|
||||||
|
return (() => {
|
||||||
|
if (sortingKey === 'monthYearReceived') {
|
||||||
|
return (
|
||||||
|
offer2.monthYearReceived.getTime() -
|
||||||
|
offer1.monthYearReceived.getTime()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortingKey === 'totalCompensation') {
|
||||||
|
const salary1 = offer1.OffersFullTime?.totalCompensation.value
|
||||||
|
? offer1.OffersFullTime?.totalCompensation.value
|
||||||
|
: offer1.OffersIntern?.monthlySalary.value;
|
||||||
|
|
||||||
|
const salary2 = offer2.OffersFullTime?.totalCompensation.value
|
||||||
|
? offer2.OffersFullTime?.totalCompensation.value
|
||||||
|
: offer2.OffersIntern?.monthlySalary.value;
|
||||||
|
|
||||||
|
if (salary1 && salary2) {
|
||||||
|
return salary2 - salary1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultReturn;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
return defaultReturn;
|
||||||
|
});
|
||||||
|
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
});
|
Reference in New Issue
Block a user