diff --git a/server/db/migrations/0011_gifted_silvermane.sql b/server/db/migrations/0011_gifted_silvermane.sql new file mode 100644 index 0000000..dc4ce16 --- /dev/null +++ b/server/db/migrations/0011_gifted_silvermane.sql @@ -0,0 +1,10 @@ +DO $$ BEGIN + CREATE TYPE "public"."employment_type" AS ENUM('FULL_TIME', 'PART_TIME', 'CONTRACTOR', 'TEMPORARY', 'INTERN', 'VOLUNTEER', 'PER_DIEM', 'OTHER'); +EXCEPTION + WHEN duplicate_object THEN null; +END $$; +--> statement-breakpoint +ALTER TABLE "job_postings" ADD COLUMN "employment_type" employment_type[];--> statement-breakpoint +ALTER TABLE "job_postings" ADD COLUMN "job_locations" json;--> statement-breakpoint +ALTER TABLE "job_postings" ADD COLUMN "is_remote" boolean DEFAULT false NOT NULL;--> statement-breakpoint +ALTER TABLE "job_postings" ADD COLUMN "base_salary" json; \ No newline at end of file diff --git a/server/db/migrations/meta/0011_snapshot.json b/server/db/migrations/meta/0011_snapshot.json new file mode 100644 index 0000000..8723424 --- /dev/null +++ b/server/db/migrations/meta/0011_snapshot.json @@ -0,0 +1,416 @@ +{ + "id": "04769c47-be9e-418a-996d-fcb0a2c2c770", + "prevId": "da7c8cd2-dd89-4312-9ba2-1d15bd5e13c0", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.hooks": { + "name": "hooks", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(40)", + "primaryKey": false, + "notNull": true + }, + "url": { + "name": "url", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "prefs": { + "name": "prefs", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "last_executed_at": { + "name": "last_executed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.job_postings": { + "name": "job_postings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(150)", + "primaryKey": false, + "notNull": true + }, + "contents": { + "name": "contents", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags_csv": { + "name": "tags_csv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "is_published": { + "name": "is_published", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "total_applicants": { + "name": "total_applicants", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "employment_type": { + "name": "employment_type", + "type": "employment_type[]", + "primaryKey": false, + "notNull": false + }, + "job_locations": { + "name": "job_locations", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "is_remote": { + "name": "is_remote", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "base_salary": { + "name": "base_salary", + "type": "json", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "job_postings_owner_id_users_id_fk": { + "name": "job_postings_owner_id_users_id_fk", + "tableFrom": "job_postings", + "tableTo": "users", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.metadata_entries": { + "name": "metadata_entries", + "schema": "", + "columns": { + "key": { + "name": "key", + "type": "varchar(48)", + "primaryKey": true, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.posting_applicants": { + "name": "posting_applicants", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "owner_id": { + "name": "owner_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "job_id": { + "name": "job_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "status_id": { + "name": "status_id", + "type": "integer", + "primaryKey": false, + "notNull": false, + "default": -1 + }, + "status_type": { + "name": "status_type", + "type": "smallint", + "primaryKey": false, + "notNull": false, + "default": 0 + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "posting_applicants_owner_id_users_id_fk": { + "name": "posting_applicants_owner_id_users_id_fk", + "tableFrom": "posting_applicants", + "tableTo": "users", + "columnsFrom": ["owner_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "posting_applicants_job_id_job_postings_id_fk": { + "name": "posting_applicants_job_id_job_postings_id_fk", + "tableFrom": "posting_applicants", + "tableTo": "job_postings", + "columnsFrom": ["job_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.review_tags": { + "name": "review_tags", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "varchar(32)", + "primaryKey": false, + "notNull": true + }, + "parent": { + "name": "parent", + "type": "smallint", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.user_handles": { + "name": "user_handles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "key": { + "name": "key", + "type": "varchar(15)", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_handles_user_id_users_id_fk": { + "name": "user_handles_user_id_users_id_fk", + "tableFrom": "user_handles", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {} + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "varchar(50)", + "primaryKey": false, + "notNull": false + }, + "picture": { + "name": "picture", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "email": { + "name": "email", + "type": "varchar(255)", + "primaryKey": false, + "notNull": true + }, + "permission": { + "name": "permission", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "top_5_skills_csv": { + "name": "top_5_skills_csv", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_admin": { + "name": "is_admin", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + } + } + }, + "enums": { + "public.employment_type": { + "name": "employment_type", + "schema": "public", + "values": ["FULL_TIME", "PART_TIME", "CONTRACTOR", "TEMPORARY", "INTERN", "VOLUNTEER", "PER_DIEM", "OTHER"] + } + }, + "schemas": {}, + "sequences": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/server/db/migrations/meta/_journal.json b/server/db/migrations/meta/_journal.json index ebdfab4..f81990b 100644 --- a/server/db/migrations/meta/_journal.json +++ b/server/db/migrations/meta/_journal.json @@ -78,6 +78,13 @@ "when": 1726641900159, "tag": "0010_amused_hairball", "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1727332547874, + "tag": "0011_gifted_silvermane", + "breakpoints": true } ] } diff --git a/server/db/schema.ts b/server/db/schema.ts index a3fa61d..ceda029 100644 --- a/server/db/schema.ts +++ b/server/db/schema.ts @@ -1,5 +1,17 @@ import { sql } from 'drizzle-orm'; -import { boolean, integer, pgTable, text, timestamp, uuid, varchar, smallint, serial } from 'drizzle-orm/pg-core'; +import { + boolean, + integer, + pgTable, + text, + timestamp, + uuid, + varchar, + smallint, + serial, + pgEnum, + json, +} from 'drizzle-orm/pg-core'; const defaultUuidPkField = () => uuid('id') @@ -8,6 +20,17 @@ const defaultUuidPkField = () => const defaultSerialPkField = () => serial('id').primaryKey(); +export const employmentTypeEnum = pgEnum('employment_type', [ + 'FULL_TIME', + 'PART_TIME', + 'CONTRACTOR', + 'TEMPORARY', + 'INTERN', + 'VOLUNTEER', + 'PER_DIEM', + 'OTHER', +]); + //---------------**************---------------- export const usersTable = pgTable('users', { @@ -48,6 +71,23 @@ export const jobPostingsTable = pgTable('job_postings', { }), isPublished: boolean('is_published').default(false).notNull(), totalApplicants: integer('total_applicants').default(0).notNull(), + employmentType: employmentTypeEnum('employment_type').array(), + jobLocations: json('job_locations').$type< + { + streetAddress?: string; + addressLocality?: string; + addressRegion?: string; + postalCode?: string; + addressCountry: string; + }[] + >(), + isRemote: boolean('is_remote').default(false).notNull(), + baseSalary: json('base_salary').$type<{ + unitText: 'Hour' | 'Day' | 'Week' | 'Month' | 'Year'; + currency: string; + minValue: number; + maxValue: number; + }>(), createdAt: timestamp('created_at').defaultNow().notNull(), updatedAt: timestamp('updated_at').defaultNow().notNull(), }); diff --git a/shared/schemas/posting.ts b/shared/schemas/posting.ts index 9e784b2..9170929 100644 --- a/shared/schemas/posting.ts +++ b/shared/schemas/posting.ts @@ -1,9 +1,39 @@ import { z } from 'zod'; +const employmentTypeEnum = z.enum([ + 'FULL_TIME', + 'PART_TIME', + 'CONTRACTOR', + 'TEMPORARY', + 'INTERN', + 'VOLUNTEER', + 'PER_DIEM', + 'OTHER', +]); + +const jobLocationSchema = z.object({ + streetAddress: z.string().optional(), + addressLocality: z.string().optional(), + addressRegion: z.string().optional(), + postalCode: z.string().optional(), + addressCountry: z.string(), +}); + +const baseSalarySchema = z.object({ + unitText: z.enum(['Hour', 'Day', 'Week', 'Month', 'Year']), + currency: z.string().length(3), + minValue: z.number(), + maxValue: z.number(), +}); + export const listJobPostingsFilterSchema = z .object({ id: z.string().uuid().optional(), ownerId: z.string().uuid().optional(), + employmentType: z.array(employmentTypeEnum).optional(), + jobLocation: z.array(jobLocationSchema).optional(), + isRemote: z.boolean().optional(), + baseSalary: baseSalarySchema.optional(), }) .optional(); @@ -16,6 +46,10 @@ export const createJobPostingSchema = z.object({ contents: z.string().optional(), tagsCSV: z.string().optional(), isPublished: z.boolean(), + employmentType: z.array(employmentTypeEnum).optional(), + jobLocation: z.array(jobLocationSchema).optional(), + isRemote: z.boolean().optional(), + baseSalary: baseSalarySchema.optional(), }); export const updateJobPostingSchema = z.object({ @@ -24,6 +58,10 @@ export const updateJobPostingSchema = z.object({ contents: z.string().optional(), tagsCSV: z.string().optional(), isPublished: z.boolean(), + employmentType: z.array(employmentTypeEnum).optional(), + jobLocation: z.array(jobLocationSchema).optional(), + isRemote: z.boolean().optional(), + baseSalary: baseSalarySchema.optional(), }); export const deleteJobPostingSchema = z.object({