diff --git a/drizzle/0002_rare_polaris.sql b/drizzle/0002_rare_polaris.sql new file mode 100644 index 0000000..2b89b29 --- /dev/null +++ b/drizzle/0002_rare_polaris.sql @@ -0,0 +1,55 @@ +CREATE TYPE "public"."hours_status" AS ENUM('pending', 'approved', 'rejected');--> statement-breakpoint +ALTER TYPE "public"."proficiency_level" ADD VALUE 'no_selection' BEFORE 'beginner';--> statement-breakpoint +CREATE TABLE "onboarding_documents" ( + "id" serial PRIMARY KEY NOT NULL, + "title" text NOT NULL, + "type" text NOT NULL, + "action_type" text DEFAULT 'sign' NOT NULL, + "url" text NOT NULL, + "description" text, + "required" boolean DEFAULT false NOT NULL, + "sort_order" integer DEFAULT 0 NOT NULL, + "is_active" boolean DEFAULT true NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL +); +--> statement-breakpoint +CREATE TABLE "volunteer_document_signatures" ( + "volunteer_id" integer NOT NULL, + "document_id" integer NOT NULL, + "signed_at" timestamp DEFAULT now() NOT NULL, + "consent_given" boolean, + CONSTRAINT "volunteer_document_signatures_volunteer_id_document_id_pk" PRIMARY KEY("volunteer_id","document_id") +); +--> statement-breakpoint +CREATE TABLE "volunteer_types" ( + "id" serial PRIMARY KEY NOT NULL, + "name" text NOT NULL, + "is_active" boolean DEFAULT true NOT NULL, + "created_at" timestamp DEFAULT now() NOT NULL, + "updated_at" timestamp DEFAULT now() NOT NULL, + CONSTRAINT "volunteer_types_name_unique" UNIQUE("name") +); +--> statement-breakpoint +ALTER TABLE "account" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint +ALTER TABLE "session" DISABLE ROW LEVEL SECURITY;--> statement-breakpoint +DROP TABLE "account" CASCADE;--> statement-breakpoint +DROP TABLE "session" CASCADE;--> statement-breakpoint +ALTER TABLE "users" DROP CONSTRAINT "users_nextauth_id_unique";--> statement-breakpoint +ALTER TABLE "volunteer_hours" ADD COLUMN "status" "hours_status" DEFAULT 'pending' NOT NULL;--> statement-breakpoint +ALTER TABLE "volunteer_hours" ADD COLUMN "rejection_reason" text;--> statement-breakpoint +ALTER TABLE "volunteers" ADD COLUMN "employer" text;--> statement-breakpoint +ALTER TABLE "volunteers" ADD COLUMN "job_title" text;--> statement-breakpoint +ALTER TABLE "volunteers" ADD COLUMN "city" text;--> statement-breakpoint +ALTER TABLE "volunteers" ADD COLUMN "state" text;--> statement-breakpoint +ALTER TABLE "volunteers" ADD COLUMN "referral_source" text;--> statement-breakpoint +ALTER TABLE "volunteer_document_signatures" ADD CONSTRAINT "volunteer_document_signatures_volunteer_id_volunteers_id_fk" FOREIGN KEY ("volunteer_id") REFERENCES "public"."volunteers"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +ALTER TABLE "volunteer_document_signatures" ADD CONSTRAINT "volunteer_document_signatures_document_id_onboarding_documents_id_fk" FOREIGN KEY ("document_id") REFERENCES "public"."onboarding_documents"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint +CREATE INDEX "vds_document_id_idx" ON "volunteer_document_signatures" USING btree ("document_id");--> statement-breakpoint +ALTER TABLE "users" DROP COLUMN "nextauth_id";--> statement-breakpoint +ALTER TABLE "volunteers" DROP COLUMN "media_release";--> statement-breakpoint +ALTER TABLE "interests" ADD CONSTRAINT "interests_name_unique" UNIQUE("name");--> statement-breakpoint +ALTER TABLE "skills" ADD CONSTRAINT "skills_name_unique" UNIQUE("name");--> statement-breakpoint +ALTER TABLE "staff" ADD CONSTRAINT "staff_user_id_unique" UNIQUE("user_id");--> statement-breakpoint +ALTER TABLE "volunteers" ADD CONSTRAINT "volunteers_user_id_unique" UNIQUE("user_id");--> statement-breakpoint +DROP TYPE "public"."volunteer_role"; \ No newline at end of file diff --git a/drizzle/0003_normal_blindfold.sql b/drizzle/0003_normal_blindfold.sql new file mode 100644 index 0000000..7a6f76d --- /dev/null +++ b/drizzle/0003_normal_blindfold.sql @@ -0,0 +1 @@ +ALTER TYPE "public"."rsvp_status" ADD VALUE 'cancelled'; \ No newline at end of file diff --git a/drizzle/meta/0002_snapshot.json b/drizzle/meta/0002_snapshot.json new file mode 100644 index 0000000..8c536f5 --- /dev/null +++ b/drizzle/meta/0002_snapshot.json @@ -0,0 +1,1634 @@ +{ + "id": "d441cfbf-aa7c-4947-9c3e-a9c18e0b0deb", + "prevId": "a4ceadc4-6f78-4e3f-b2b4-88bf944d33cd", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.admin_dashboard_actions": { + "name": "admin_dashboard_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "admin_id": { + "name": "admin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "admin_dashboard_actions_admin_id_users_id_fk": { + "name": "admin_dashboard_actions_admin_id_users_id_fk", + "tableFrom": "admin_dashboard_actions", + "tableTo": "users", + "columnsFrom": [ + "admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_id": { + "name": "updated_by_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "system_settings_updated_by_id_users_id_fk": { + "name": "system_settings_updated_by_id_users_id_fk", + "tableFrom": "system_settings", + "tableTo": "users", + "columnsFrom": [ + "updated_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "system_settings_key_unique": { + "name": "system_settings_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bulk_communication_logs": { + "name": "bulk_communication_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "sender_id": { + "name": "sender_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_type": { + "name": "recipient_type", + "type": "recipient_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sent'" + } + }, + "indexes": {}, + "foreignKeys": { + "bulk_communication_logs_sender_id_users_id_fk": { + "name": "bulk_communication_logs_sender_id_users_id_fk", + "tableFrom": "bulk_communication_logs", + "tableTo": "users", + "columnsFrom": [ + "sender_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.communication_templates": { + "name": "communication_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.onboarding_documents": { + "name": "onboarding_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sign'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_document_signatures": { + "name": "volunteer_document_signatures", + "schema": "", + "columns": { + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "signed_at": { + "name": "signed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "consent_given": { + "name": "consent_given", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "vds_document_id_idx": { + "name": "vds_document_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "volunteer_document_signatures_volunteer_id_volunteers_id_fk": { + "name": "volunteer_document_signatures_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_document_signatures", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_document_signatures_document_id_onboarding_documents_id_fk": { + "name": "volunteer_document_signatures_document_id_onboarding_documents_id_fk", + "tableFrom": "volunteer_document_signatures", + "tableTo": "onboarding_documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "volunteer_document_signatures_volunteer_id_document_id_pk": { + "name": "volunteer_document_signatures_volunteer_id_document_id_pk", + "columns": [ + "volunteer_id", + "document_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opportunities": { + "name": "opportunities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "opportunity_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "max_volunteers": { + "name": "max_volunteers", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by_id": { + "name": "created_by_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "recurrence_pattern": { + "name": "recurrence_pattern", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_recurring": { + "name": "is_recurring", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "opportunities_created_by_id_users_id_fk": { + "name": "opportunities_created_by_id_users_id_fk", + "tableFrom": "opportunities", + "tableTo": "users", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opportunity_interests": { + "name": "opportunity_interests", + "schema": "", + "columns": { + "opportunity_id": { + "name": "opportunity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "opportunity_interests_opportunity_id_opportunities_id_fk": { + "name": "opportunity_interests_opportunity_id_opportunities_id_fk", + "tableFrom": "opportunity_interests", + "tableTo": "opportunities", + "columnsFrom": [ + "opportunity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opportunity_interests_interest_id_interests_id_fk": { + "name": "opportunity_interests_interest_id_interests_id_fk", + "tableFrom": "opportunity_interests", + "tableTo": "interests", + "columnsFrom": [ + "interest_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "opportunity_interests_opportunity_id_interest_id_pk": { + "name": "opportunity_interests_opportunity_id_interest_id_pk", + "columns": [ + "opportunity_id", + "interest_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opportunity_required_skills": { + "name": "opportunity_required_skills", + "schema": "", + "columns": { + "opportunity_id": { + "name": "opportunity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "opportunity_required_skills_opportunity_id_opportunities_id_fk": { + "name": "opportunity_required_skills_opportunity_id_opportunities_id_fk", + "tableFrom": "opportunity_required_skills", + "tableTo": "opportunities", + "columnsFrom": [ + "opportunity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opportunity_required_skills_skill_id_skills_id_fk": { + "name": "opportunity_required_skills_skill_id_skills_id_fk", + "tableFrom": "opportunity_required_skills", + "tableTo": "skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "opportunity_required_skills_opportunity_id_skill_id_pk": { + "name": "opportunity_required_skills_opportunity_id_skill_id_pk", + "columns": [ + "opportunity_id", + "skill_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_hours": { + "name": "volunteer_hours", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "opportunity_id": { + "name": "opportunity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "hours": { + "name": "hours", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "hours_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_by": { + "name": "verified_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "volunteer_hours_volunteer_id_volunteers_id_fk": { + "name": "volunteer_hours_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_hours", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_hours_opportunity_id_opportunities_id_fk": { + "name": "volunteer_hours_opportunity_id_opportunities_id_fk", + "tableFrom": "volunteer_hours", + "tableTo": "opportunities", + "columnsFrom": [ + "opportunity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_hours_verified_by_users_id_fk": { + "name": "volunteer_hours_verified_by_users_id_fk", + "tableFrom": "volunteer_hours", + "tableTo": "users", + "columnsFrom": [ + "verified_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_rsvps": { + "name": "volunteer_rsvps", + "schema": "", + "columns": { + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "opportunity_id": { + "name": "opportunity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "rsvp_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "rsvp_at": { + "name": "rsvp_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "volunteer_rsvps_volunteer_id_volunteers_id_fk": { + "name": "volunteer_rsvps_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_rsvps", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_rsvps_opportunity_id_opportunities_id_fk": { + "name": "volunteer_rsvps_opportunity_id_opportunities_id_fk", + "tableFrom": "volunteer_rsvps", + "tableTo": "opportunities", + "columnsFrom": [ + "opportunity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "volunteer_rsvps_volunteer_id_opportunity_id_pk": { + "name": "volunteer_rsvps_volunteer_id_opportunity_id_pk", + "columns": [ + "volunteer_id", + "opportunity_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.admin": { + "name": "admin", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "staff_id": { + "name": "staff_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_login_at": { + "name": "last_login_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": { + "admin_staff_id_staff_id_fk": { + "name": "admin_staff_id_staff_id_fk", + "tableFrom": "admin", + "tableTo": "staff", + "columnsFrom": [ + "staff_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interests": { + "name": "interests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "interests_name_unique": { + "name": "interests_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "skills_name_unique": { + "name": "skills_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.staff": { + "name": "staff", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "notification_preference": { + "name": "notification_preference", + "type": "notification_preference", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'email'" + }, + "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": { + "staff_user_id_users_id_fk": { + "name": "staff_user_id_users_id_fk", + "tableFrom": "staff", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "staff_user_id_unique": { + "name": "staff_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "profile_picture": { + "name": "profile_picture", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_email_verified": { + "name": "is_email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_verified_at": { + "name": "email_verified_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": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_interests": { + "name": "volunteer_interests", + "schema": "", + "columns": { + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "volunteer_interests_volunteer_id_volunteers_id_fk": { + "name": "volunteer_interests_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_interests", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_interests_interest_id_interests_id_fk": { + "name": "volunteer_interests_interest_id_interests_id_fk", + "tableFrom": "volunteer_interests", + "tableTo": "interests", + "columnsFrom": [ + "interest_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "volunteer_interests_volunteer_id_interest_id_pk": { + "name": "volunteer_interests_volunteer_id_interest_id_pk", + "columns": [ + "volunteer_id", + "interest_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_skills": { + "name": "volunteer_skills", + "schema": "", + "columns": { + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "proficiency_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "volunteer_skills_volunteer_id_volunteers_id_fk": { + "name": "volunteer_skills_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_skills", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_skills_skill_id_skills_id_fk": { + "name": "volunteer_skills_skill_id_skills_id_fk", + "tableFrom": "volunteer_skills", + "tableTo": "skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "volunteer_skills_volunteer_id_skill_id_pk": { + "name": "volunteer_skills_volunteer_id_skill_id_pk", + "columns": [ + "volunteer_id", + "skill_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_types": { + "name": "volunteer_types", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": { + "volunteer_types_name_unique": { + "name": "volunteer_types_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteers": { + "name": "volunteers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "volunteer_type": { + "name": "volunteer_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_alumni": { + "name": "is_alumni", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "background_check_status": { + "name": "background_check_status", + "type": "background_check_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "availability": { + "name": "availability", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "notification_preference": { + "name": "notification_preference", + "type": "notification_preference", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'email'" + }, + "employer": { + "name": "employer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "referral_source": { + "name": "referral_source", + "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": { + "volunteers_user_id_users_id_fk": { + "name": "volunteers_user_id_users_id_fk", + "tableFrom": "volunteers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "volunteers_user_id_unique": { + "name": "volunteers_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.background_check_status": { + "name": "background_check_status", + "schema": "public", + "values": [ + "not_required", + "pending", + "approved", + "rejected" + ] + }, + "public.hours_status": { + "name": "hours_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected" + ] + }, + "public.notification_preference": { + "name": "notification_preference", + "schema": "public", + "values": [ + "email", + "sms", + "both", + "none" + ] + }, + "public.opportunity_status": { + "name": "opportunity_status", + "schema": "public", + "values": [ + "open", + "full", + "completed", + "canceled" + ] + }, + "public.proficiency_level": { + "name": "proficiency_level", + "schema": "public", + "values": [ + "no_selection", + "beginner", + "intermediate", + "advanced" + ] + }, + "public.recipient_type": { + "name": "recipient_type", + "schema": "public", + "values": [ + "volunteers", + "staff", + "both" + ] + }, + "public.rsvp_status": { + "name": "rsvp_status", + "schema": "public", + "values": [ + "pending", + "confirmed", + "declined", + "attended", + "no_show" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/0003_snapshot.json b/drizzle/meta/0003_snapshot.json new file mode 100644 index 0000000..de1c393 --- /dev/null +++ b/drizzle/meta/0003_snapshot.json @@ -0,0 +1,1635 @@ +{ + "id": "586f6b29-8bbd-4c4e-890d-5ed7d776d2ee", + "prevId": "d441cfbf-aa7c-4947-9c3e-a9c18e0b0deb", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.admin_dashboard_actions": { + "name": "admin_dashboard_actions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "admin_id": { + "name": "admin_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_id": { + "name": "target_id", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "admin_dashboard_actions_admin_id_users_id_fk": { + "name": "admin_dashboard_actions_admin_id_users_id_fk", + "tableFrom": "admin_dashboard_actions", + "tableTo": "users", + "columnsFrom": [ + "admin_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "value": { + "name": "value", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "updated_by_id": { + "name": "updated_by_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "system_settings_updated_by_id_users_id_fk": { + "name": "system_settings_updated_by_id_users_id_fk", + "tableFrom": "system_settings", + "tableTo": "users", + "columnsFrom": [ + "updated_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "system_settings_key_unique": { + "name": "system_settings_key_unique", + "nullsNotDistinct": false, + "columns": [ + "key" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.bulk_communication_logs": { + "name": "bulk_communication_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "sender_id": { + "name": "sender_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipient_type": { + "name": "recipient_type", + "type": "recipient_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sent'" + } + }, + "indexes": {}, + "foreignKeys": { + "bulk_communication_logs_sender_id_users_id_fk": { + "name": "bulk_communication_logs_sender_id_users_id_fk", + "tableFrom": "bulk_communication_logs", + "tableTo": "users", + "columnsFrom": [ + "sender_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.communication_templates": { + "name": "communication_templates", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "body": { + "name": "body", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.onboarding_documents": { + "name": "onboarding_documents", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action_type": { + "name": "action_type", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'sign'" + }, + "url": { + "name": "url", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "required": { + "name": "required", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "sort_order": { + "name": "sort_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "default": 0 + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_document_signatures": { + "name": "volunteer_document_signatures", + "schema": "", + "columns": { + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "document_id": { + "name": "document_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "signed_at": { + "name": "signed_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "consent_given": { + "name": "consent_given", + "type": "boolean", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "vds_document_id_idx": { + "name": "vds_document_id_idx", + "columns": [ + { + "expression": "document_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "volunteer_document_signatures_volunteer_id_volunteers_id_fk": { + "name": "volunteer_document_signatures_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_document_signatures", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_document_signatures_document_id_onboarding_documents_id_fk": { + "name": "volunteer_document_signatures_document_id_onboarding_documents_id_fk", + "tableFrom": "volunteer_document_signatures", + "tableTo": "onboarding_documents", + "columnsFrom": [ + "document_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "volunteer_document_signatures_volunteer_id_document_id_pk": { + "name": "volunteer_document_signatures_volunteer_id_document_id_pk", + "columns": [ + "volunteer_id", + "document_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opportunities": { + "name": "opportunities", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "location": { + "name": "location", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "start_date": { + "name": "start_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "end_date": { + "name": "end_date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "opportunity_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "max_volunteers": { + "name": "max_volunteers", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "created_by_id": { + "name": "created_by_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "recurrence_pattern": { + "name": "recurrence_pattern", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_recurring": { + "name": "is_recurring", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": 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": { + "opportunities_created_by_id_users_id_fk": { + "name": "opportunities_created_by_id_users_id_fk", + "tableFrom": "opportunities", + "tableTo": "users", + "columnsFrom": [ + "created_by_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opportunity_interests": { + "name": "opportunity_interests", + "schema": "", + "columns": { + "opportunity_id": { + "name": "opportunity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "opportunity_interests_opportunity_id_opportunities_id_fk": { + "name": "opportunity_interests_opportunity_id_opportunities_id_fk", + "tableFrom": "opportunity_interests", + "tableTo": "opportunities", + "columnsFrom": [ + "opportunity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opportunity_interests_interest_id_interests_id_fk": { + "name": "opportunity_interests_interest_id_interests_id_fk", + "tableFrom": "opportunity_interests", + "tableTo": "interests", + "columnsFrom": [ + "interest_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "opportunity_interests_opportunity_id_interest_id_pk": { + "name": "opportunity_interests_opportunity_id_interest_id_pk", + "columns": [ + "opportunity_id", + "interest_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.opportunity_required_skills": { + "name": "opportunity_required_skills", + "schema": "", + "columns": { + "opportunity_id": { + "name": "opportunity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "opportunity_required_skills_opportunity_id_opportunities_id_fk": { + "name": "opportunity_required_skills_opportunity_id_opportunities_id_fk", + "tableFrom": "opportunity_required_skills", + "tableTo": "opportunities", + "columnsFrom": [ + "opportunity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "opportunity_required_skills_skill_id_skills_id_fk": { + "name": "opportunity_required_skills_skill_id_skills_id_fk", + "tableFrom": "opportunity_required_skills", + "tableTo": "skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "opportunity_required_skills_opportunity_id_skill_id_pk": { + "name": "opportunity_required_skills_opportunity_id_skill_id_pk", + "columns": [ + "opportunity_id", + "skill_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_hours": { + "name": "volunteer_hours", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "opportunity_id": { + "name": "opportunity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "date": { + "name": "date", + "type": "timestamp", + "primaryKey": false, + "notNull": true + }, + "hours": { + "name": "hours", + "type": "real", + "primaryKey": false, + "notNull": true + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "hours_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "rejection_reason": { + "name": "rejection_reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "verified_by": { + "name": "verified_by", + "type": "integer", + "primaryKey": false, + "notNull": false + }, + "verified_at": { + "name": "verified_at", + "type": "timestamp", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "volunteer_hours_volunteer_id_volunteers_id_fk": { + "name": "volunteer_hours_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_hours", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_hours_opportunity_id_opportunities_id_fk": { + "name": "volunteer_hours_opportunity_id_opportunities_id_fk", + "tableFrom": "volunteer_hours", + "tableTo": "opportunities", + "columnsFrom": [ + "opportunity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_hours_verified_by_users_id_fk": { + "name": "volunteer_hours_verified_by_users_id_fk", + "tableFrom": "volunteer_hours", + "tableTo": "users", + "columnsFrom": [ + "verified_by" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_rsvps": { + "name": "volunteer_rsvps", + "schema": "", + "columns": { + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "opportunity_id": { + "name": "opportunity_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "rsvp_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "rsvp_at": { + "name": "rsvp_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "notes": { + "name": "notes", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "volunteer_rsvps_volunteer_id_volunteers_id_fk": { + "name": "volunteer_rsvps_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_rsvps", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_rsvps_opportunity_id_opportunities_id_fk": { + "name": "volunteer_rsvps_opportunity_id_opportunities_id_fk", + "tableFrom": "volunteer_rsvps", + "tableTo": "opportunities", + "columnsFrom": [ + "opportunity_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "volunteer_rsvps_volunteer_id_opportunity_id_pk": { + "name": "volunteer_rsvps_volunteer_id_opportunity_id_pk", + "columns": [ + "volunteer_id", + "opportunity_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.admin": { + "name": "admin", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "staff_id": { + "name": "staff_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "last_login_at": { + "name": "last_login_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": { + "admin_staff_id_staff_id_fk": { + "name": "admin_staff_id_staff_id_fk", + "tableFrom": "admin", + "tableTo": "staff", + "columnsFrom": [ + "staff_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.interests": { + "name": "interests", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "interests_name_unique": { + "name": "interests_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.skills": { + "name": "skills", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "category": { + "name": "category", + "type": "text", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "skills_name_unique": { + "name": "skills_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.staff": { + "name": "staff", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "notification_preference": { + "name": "notification_preference", + "type": "notification_preference", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'email'" + }, + "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": { + "staff_user_id_users_id_fk": { + "name": "staff_user_id_users_id_fk", + "tableFrom": "staff", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "staff_user_id_unique": { + "name": "staff_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "phone": { + "name": "phone", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "bio": { + "name": "bio", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "profile_picture": { + "name": "profile_picture", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "is_email_verified": { + "name": "is_email_verified", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "email_verified_at": { + "name": "email_verified_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": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": [ + "email" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.verificationToken": { + "name": "verificationToken", + "schema": "", + "columns": { + "identifier": { + "name": "identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "token": { + "name": "token", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires": { + "name": "expires", + "type": "timestamp", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": { + "verificationToken_identifier_token_pk": { + "name": "verificationToken_identifier_token_pk", + "columns": [ + "identifier", + "token" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_interests": { + "name": "volunteer_interests", + "schema": "", + "columns": { + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "interest_id": { + "name": "interest_id", + "type": "integer", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "volunteer_interests_volunteer_id_volunteers_id_fk": { + "name": "volunteer_interests_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_interests", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_interests_interest_id_interests_id_fk": { + "name": "volunteer_interests_interest_id_interests_id_fk", + "tableFrom": "volunteer_interests", + "tableTo": "interests", + "columnsFrom": [ + "interest_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "volunteer_interests_volunteer_id_interest_id_pk": { + "name": "volunteer_interests_volunteer_id_interest_id_pk", + "columns": [ + "volunteer_id", + "interest_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_skills": { + "name": "volunteer_skills", + "schema": "", + "columns": { + "volunteer_id": { + "name": "volunteer_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "skill_id": { + "name": "skill_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "level": { + "name": "level", + "type": "proficiency_level", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "volunteer_skills_volunteer_id_volunteers_id_fk": { + "name": "volunteer_skills_volunteer_id_volunteers_id_fk", + "tableFrom": "volunteer_skills", + "tableTo": "volunteers", + "columnsFrom": [ + "volunteer_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "volunteer_skills_skill_id_skills_id_fk": { + "name": "volunteer_skills_skill_id_skills_id_fk", + "tableFrom": "volunteer_skills", + "tableTo": "skills", + "columnsFrom": [ + "skill_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "volunteer_skills_volunteer_id_skill_id_pk": { + "name": "volunteer_skills_volunteer_id_skill_id_pk", + "columns": [ + "volunteer_id", + "skill_id" + ] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteer_types": { + "name": "volunteer_types", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "is_active": { + "name": "is_active", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "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": { + "volunteer_types_name_unique": { + "name": "volunteer_types_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.volunteers": { + "name": "volunteers", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "volunteer_type": { + "name": "volunteer_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "is_alumni": { + "name": "is_alumni", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "background_check_status": { + "name": "background_check_status", + "type": "background_check_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'not_required'" + }, + "availability": { + "name": "availability", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "notification_preference": { + "name": "notification_preference", + "type": "notification_preference", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'email'" + }, + "employer": { + "name": "employer", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "job_title": { + "name": "job_title", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "city": { + "name": "city", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "state": { + "name": "state", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "referral_source": { + "name": "referral_source", + "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": { + "volunteers_user_id_users_id_fk": { + "name": "volunteers_user_id_users_id_fk", + "tableFrom": "volunteers", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "volunteers_user_id_unique": { + "name": "volunteers_user_id_unique", + "nullsNotDistinct": false, + "columns": [ + "user_id" + ] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.background_check_status": { + "name": "background_check_status", + "schema": "public", + "values": [ + "not_required", + "pending", + "approved", + "rejected" + ] + }, + "public.hours_status": { + "name": "hours_status", + "schema": "public", + "values": [ + "pending", + "approved", + "rejected" + ] + }, + "public.notification_preference": { + "name": "notification_preference", + "schema": "public", + "values": [ + "email", + "sms", + "both", + "none" + ] + }, + "public.opportunity_status": { + "name": "opportunity_status", + "schema": "public", + "values": [ + "open", + "full", + "completed", + "canceled" + ] + }, + "public.proficiency_level": { + "name": "proficiency_level", + "schema": "public", + "values": [ + "no_selection", + "beginner", + "intermediate", + "advanced" + ] + }, + "public.recipient_type": { + "name": "recipient_type", + "schema": "public", + "values": [ + "volunteers", + "staff", + "both" + ] + }, + "public.rsvp_status": { + "name": "rsvp_status", + "schema": "public", + "values": [ + "pending", + "confirmed", + "declined", + "attended", + "no_show", + "cancelled" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} \ No newline at end of file diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json index 79d990d..48cfeef 100644 --- a/drizzle/meta/_journal.json +++ b/drizzle/meta/_journal.json @@ -15,6 +15,20 @@ "when": 1769017316740, "tag": "0001_nasty_wonder_man", "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1776477577840, + "tag": "0002_rare_polaris", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1776511347486, + "tag": "0003_normal_blindfold", + "breakpoints": true } ] } \ No newline at end of file diff --git a/src/app/api/staff/rsvps/route.ts b/src/app/api/staff/rsvps/route.ts index de96d05..4492818 100644 --- a/src/app/api/staff/rsvps/route.ts +++ b/src/app/api/staff/rsvps/route.ts @@ -13,7 +13,7 @@ import { parseBodyOrError } from "@/utils/server/route-helpers"; const updateRsvpSchema = z.object({ volunteerId: z.number().int().positive(), opportunityId: z.number().int().positive(), - status: z.enum(["confirmed", "declined", "attended", "no_show"]), + status: z.enum(["confirmed", "declined", "attended", "no_show", "cancelled"]), }); /** diff --git a/src/app/api/staff/volunteers/[id]/route.ts b/src/app/api/staff/volunteers/[id]/route.ts index 969bf55..3a04454 100644 --- a/src/app/api/staff/volunteers/[id]/route.ts +++ b/src/app/api/staff/volunteers/[id]/route.ts @@ -28,7 +28,6 @@ const volunteerUpdateSchema = z.object({ backgroundCheckStatus: z .enum(["not_required", "pending", "approved", "rejected"]) .optional(), - mediaRelease: z.boolean().optional(), availability: z .record( z.string(), diff --git a/src/app/api/staff/volunteers/route.ts b/src/app/api/staff/volunteers/route.ts index 3137a45..4292361 100644 --- a/src/app/api/staff/volunteers/route.ts +++ b/src/app/api/staff/volunteers/route.ts @@ -28,7 +28,6 @@ const volunteerCreateSchema = z.object({ backgroundCheckStatus: z .enum(["not_required", "pending", "approved", "rejected"]) .optional(), - mediaRelease: z.boolean().optional(), availability: z .record( z.string(), diff --git a/src/app/api/volunteer/profile/route.ts b/src/app/api/volunteer/profile/route.ts index f9537c4..2549a8e 100644 --- a/src/app/api/volunteer/profile/route.ts +++ b/src/app/api/volunteer/profile/route.ts @@ -44,6 +44,7 @@ const volunteerSelfUpdateSchema = z.object({ city: z.string().max(100).optional(), state: z.string().max(100).optional(), referralSource: z.string().max(500).optional(), + isAlumni: z.boolean().optional(), }); export async function GET(): Promise { @@ -151,6 +152,7 @@ export async function PUT(request: Request): Promise { city: data.city, state: data.state, referralSource: data.referralSource, + isAlumni: data.isAlumni, }); if (updatedVolunteer === null) { diff --git a/src/app/api/volunteer/rsvps/route.ts b/src/app/api/volunteer/rsvps/route.ts index db47afa..fdd4e59 100644 --- a/src/app/api/volunteer/rsvps/route.ts +++ b/src/app/api/volunteer/rsvps/route.ts @@ -23,10 +23,16 @@ export async function GET(): Promise { // Separate into upcoming and past const now = new Date(); const upcoming = rsvps.filter( - (r) => r.opportunityStartDate && new Date(r.opportunityStartDate) > now, + (r) => + r.rsvpStatus !== "cancelled" && + r.opportunityStartDate && + new Date(r.opportunityStartDate) > now, ); const past = rsvps.filter( - (r) => r.opportunityStartDate && new Date(r.opportunityStartDate) <= now, + (r) => + r.rsvpStatus !== "cancelled" && + r.opportunityStartDate && + new Date(r.opportunityStartDate) <= now, ); return NextResponse.json({ diff --git a/src/app/volunteer/opportunities/page.tsx b/src/app/volunteer/opportunities/page.tsx index ad8d718..1e6b866 100644 --- a/src/app/volunteer/opportunities/page.tsx +++ b/src/app/volunteer/opportunities/page.tsx @@ -426,6 +426,11 @@ export default function OpportunitiesPage(): JSX.Element { ? false : rsvpedIds.has(selectedOpportunityId) } + rsvpStatus={ + selectedOpportunityId === null + ? undefined + : rsvpStatusMap.get(selectedOpportunityId) + } open={selectedOpportunityId !== null} onClose={() => { setSelectedOpportunityId(null); diff --git a/src/components/staff/approvals/attendance-modal.tsx b/src/components/staff/approvals/attendance-modal.tsx index 0a10fbc..98512e1 100644 --- a/src/components/staff/approvals/attendance-modal.tsx +++ b/src/components/staff/approvals/attendance-modal.tsx @@ -1,5 +1,6 @@ "use client"; +import BlockIcon from "@mui/icons-material/Block"; import CheckCircleOutlineIcon from "@mui/icons-material/CheckCircleOutline"; import DoNotDisturbIcon from "@mui/icons-material/DoNotDisturb"; import { @@ -25,7 +26,6 @@ import type { AttendanceRsvp, } from "@/hooks/use-approvals-attendance"; -// Only declined RSVPs cannot be updated — attended/no_show can toggle between each other function isLocked(status: string): boolean { return status === "declined"; } @@ -40,7 +40,7 @@ type AttendanceModalProps = { onMark: ( volunteerId: number, opportunityId: number, - status: "attended" | "no_show", + status: "attended" | "no_show" | "confirmed" | "cancelled", ) => Promise; }; @@ -53,6 +53,7 @@ const RSVP_STATUS_COLORS: Record< attended: "success", no_show: "error", declined: "default", + cancelled: "default", }; export default function AttendanceModal({ @@ -133,38 +134,88 @@ export default function AttendanceModal({ spacing={1} justifyContent="flex-end" > - - + {rsvp.rsvpStatus === "cancelled" ? ( + + ) : ( + <> + + + {(rsvp.rsvpStatus === "pending" || + rsvp.rsvpStatus === "confirmed" || + rsvp.rsvpStatus === "attended" || + rsvp.rsvpStatus === "no_show") && ( + + )} + + )} diff --git a/src/components/staff/approvals/attendance-tab.tsx b/src/components/staff/approvals/attendance-tab.tsx index 1b302a3..9b18c24 100644 --- a/src/components/staff/approvals/attendance-tab.tsx +++ b/src/components/staff/approvals/attendance-tab.tsx @@ -33,7 +33,7 @@ type AttendanceTabProps = { onMark: ( volunteerId: number, opportunityId: number, - status: "attended" | "no_show", + status: "attended" | "no_show" | "confirmed" | "cancelled", ) => Promise; }; diff --git a/src/components/staff/volunteer-management/volunteer-add-modal.tsx b/src/components/staff/volunteer-management/volunteer-add-modal.tsx index 5ff7764..1a6dbae 100644 --- a/src/components/staff/volunteer-management/volunteer-add-modal.tsx +++ b/src/components/staff/volunteer-management/volunteer-add-modal.tsx @@ -61,8 +61,6 @@ export default function AddVolunteerModal({ const [volunteerType, setVolunteerType] = useState(""); const [isAlumni, setIsAlumni] = useState(false); const [backgroundCheckStatus, setBackgroundCheckStatus] = useState(""); - const [mediaRelease, setMediaRelease] = useState(false); - const { createVolunteer } = useVolunteers( "", { type: "", alumni: false }, @@ -151,7 +149,6 @@ export default function AddVolunteerModal({ volunteerType?: string; isAlumni: boolean; backgroundCheckStatus?: "not_required" | "pending" | "approved"; - mediaRelease: boolean; } = { firstName: firstName.trim(), lastName: lastName.trim(), @@ -160,7 +157,6 @@ export default function AddVolunteerModal({ volunteerType: volunteerType === "" ? undefined : volunteerType, isAlumni, backgroundCheckStatus: currentBackgroundCheckStatus, - mediaRelease, }; const json = await createVolunteer(payload); @@ -185,7 +181,6 @@ export default function AddVolunteerModal({ setVolunteerType(""); setIsAlumni(false); setBackgroundCheckStatus(""); - setMediaRelease(false); setTouched({}); setSubmitError(null); @@ -218,7 +213,6 @@ export default function AddVolunteerModal({ volunteerType, isAlumni, backgroundCheckStatus, - mediaRelease, isFormValid, markTouched, onCreated, @@ -415,21 +409,6 @@ export default function AddVolunteerModal({ } label={Alumni} /> - - setMediaRelease(e.target.checked)} - sx={{ "& .MuiSvgIcon-root": { fontSize: 20 } }} - /> - } - label={ - - Media Release Signed - - } - /> diff --git a/src/components/staff/volunteer-management/volunteer-overview-tab.tsx b/src/components/staff/volunteer-management/volunteer-overview-tab.tsx index 77a63fa..e9ac506 100644 --- a/src/components/staff/volunteer-management/volunteer-overview-tab.tsx +++ b/src/components/staff/volunteer-management/volunteer-overview-tab.tsx @@ -5,7 +5,6 @@ import AssignmentTurnedInIcon from "@mui/icons-material/AssignmentTurnedIn"; import CalendarMonthIcon from "@mui/icons-material/CalendarMonth"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import DeleteIcon from "@mui/icons-material/Delete"; -import DescriptionIcon from "@mui/icons-material/Description"; import DrawIcon from "@mui/icons-material/Draw"; import EditIcon from "@mui/icons-material/Edit"; import ExpandLessIcon from "@mui/icons-material/ExpandLess"; @@ -267,7 +266,6 @@ type EditData = { volunteerType?: string; isAlumni?: boolean; backgroundCheckStatus?: "not_required" | "pending" | "approved" | "rejected"; - mediaRelease?: boolean; availability?: Record; notificationPreference?: "email" | "sms" | "both" | "none"; employer?: string; @@ -302,7 +300,6 @@ function StaffEditVolunteerModal({ isActive: user?.isActive ?? true, volunteerType: vol.volunteerType ?? "", isAlumni: vol.isAlumni ?? false, - mediaRelease: vol.mediaRelease ?? false, employer: vol.employer ?? "", jobTitle: vol.jobTitle ?? "", city: vol.city ?? "", @@ -321,7 +318,6 @@ function StaffEditVolunteerModal({ isActive: user?.isActive ?? true, volunteerType: vol.volunteerType ?? "", isAlumni: vol.isAlumni ?? false, - mediaRelease: vol.mediaRelease ?? false, employer: vol.employer ?? "", jobTitle: vol.jobTitle ?? "", city: vol.city ?? "", @@ -526,18 +522,6 @@ function StaffEditVolunteerModal({ } label="Alumni Status" /> - - handleChange("mediaRelease", e.target.checked) - } - disabled={saving} - /> - } - label="Media Release Signed" - /> @@ -897,20 +881,6 @@ export function VolunteerOverviewTab({ icon={} /> - - - Media Release - - } - /> - diff --git a/src/components/ui/status-badge.tsx b/src/components/ui/status-badge.tsx index 10fc125..0200576 100644 --- a/src/components/ui/status-badge.tsx +++ b/src/components/ui/status-badge.tsx @@ -49,7 +49,8 @@ export function getRsvpStatusColor( case "pending": { return "warning"; } - case "no_show": { + case "no_show": + case "cancelled": { return "default"; } default: { diff --git a/src/components/volunteer/my-rsvps.tsx b/src/components/volunteer/my-rsvps.tsx index 3bbf93e..3837156 100644 --- a/src/components/volunteer/my-rsvps.tsx +++ b/src/components/volunteer/my-rsvps.tsx @@ -102,6 +102,7 @@ export default function MyRsvps(): JSX.Element { opportunityId={rsvp.opportunityId} isRsvped={true} isFull={false} + rsvpStatus={rsvp.rsvpStatus} onRsvpChange={() => void loadRsvps()} /> diff --git a/src/components/volunteer/onboarding/onboarding-checklist.tsx b/src/components/volunteer/onboarding/onboarding-checklist.tsx index 270a8d3..b2803e3 100644 --- a/src/components/volunteer/onboarding/onboarding-checklist.tsx +++ b/src/components/volunteer/onboarding/onboarding-checklist.tsx @@ -33,8 +33,8 @@ export default function OnboardingChecklist({ error, }: OnboardingChecklistProps): JSX.Element | null { const [minimized, setMinimized] = useState(false); - const [visible, setVisible] = useState(true); - const [mounted, setMounted] = useState(true); + const [visible, setVisible] = useState(false); + const [mounted, setMounted] = useState(false); const prevCompleteRef = useRef(null); useEffect((): (() => void) | void => { @@ -44,9 +44,9 @@ export default function OnboardingChecklist({ if (prevCompleteRef.current === null) { prevCompleteRef.current = isComplete; - if (isComplete) { - setVisible(false); - setMounted(false); + if (!isComplete) { + setVisible(true); + setMounted(true); } return; } diff --git a/src/components/volunteer/opportunity-detail-modal.tsx b/src/components/volunteer/opportunity-detail-modal.tsx index c4ffff1..ed2808c 100644 --- a/src/components/volunteer/opportunity-detail-modal.tsx +++ b/src/components/volunteer/opportunity-detail-modal.tsx @@ -19,6 +19,7 @@ import { JSX, useEffect, useState } from "react"; import { AsyncContent } from "@/components/shared"; import RsvpButton from "@/components/volunteer/rsvp-button"; import { SpotsChip } from "@/components/volunteer/spots-chip"; +import type { RsvpStatus } from "@/components/volunteer/types"; import { formatDate, formatTime } from "@/components/volunteer/utils"; import { useAttendees } from "@/hooks/use-attendees"; import { useOpportunity } from "@/hooks/use-opportunity"; @@ -26,6 +27,7 @@ import { useOpportunity } from "@/hooks/use-opportunity"; type Props = { opportunityId: number | null; isRsvped: boolean; + rsvpStatus?: RsvpStatus; open: boolean; onClose: () => void; onRsvpChange: (newIsRsvped: boolean) => void; @@ -34,6 +36,7 @@ type Props = { export default function OpportunityDetailModal({ opportunityId, isRsvped, + rsvpStatus, open, onClose, onRsvpChange, @@ -251,6 +254,7 @@ export default function OpportunityDetailModal({ opportunityId={opportunityId} isRsvped={isRsvped} isFull={isFull} + rsvpStatus={rsvpStatus} onRsvpChange={onRsvpChange} /> )} diff --git a/src/components/volunteer/profile-edit-form.tsx b/src/components/volunteer/profile-edit-form.tsx index b16c7df..2ec6b67 100644 --- a/src/components/volunteer/profile-edit-form.tsx +++ b/src/components/volunteer/profile-edit-form.tsx @@ -10,6 +10,7 @@ import { Button, Card, CardContent, + Checkbox, CircularProgress, Divider, FormControlLabel, @@ -61,6 +62,7 @@ type ProfileData = { city?: string | null; state?: string | null; referralSource?: string | null; + isAlumni?: boolean | null; }; type ProfileEditFormProps = { @@ -199,6 +201,7 @@ export default function ProfileEditForm({ city: initialData.city || "", state: initialData.state || "", referralSource: initialData.referralSource || "", + isAlumni: initialData.isAlumni ?? false, }); // Local skills state: map of skillId -> checked @@ -311,17 +314,33 @@ export default function ProfileEditForm({ icon={} title="About Me" > - setFormData({ ...formData, bio: e.target.value })} - fullWidth - multiline - rows={4} - placeholder="Tell us about yourself..." - disabled={isSaving} - size="small" - /> + + + setFormData({ ...formData, bio: e.target.value }) + } + fullWidth + multiline + rows={4} + placeholder="Tell us about yourself..." + disabled={isSaving} + size="small" + /> + + setFormData({ ...formData, isAlumni: e.target.checked }) + } + disabled={isSaving} + /> + } + label="I am a Persevere alumni" + /> + {/* ── Two-column grid ──────────────────────────── */} diff --git a/src/components/volunteer/rsvp-button.tsx b/src/components/volunteer/rsvp-button.tsx index b1cd3ac..089b26b 100644 --- a/src/components/volunteer/rsvp-button.tsx +++ b/src/components/volunteer/rsvp-button.tsx @@ -2,13 +2,18 @@ import Button from "@mui/material/Button"; import CircularProgress from "@mui/material/CircularProgress"; +import { useState } from "react"; +import { ConfirmDialog } from "@/components/shared"; +import { getRsvpStatusColor, StatusBadge } from "@/components/ui"; +import type { RsvpStatus } from "@/components/volunteer/types"; import { useRsvpMutation } from "@/hooks/use-rsvp-mutation"; type RsvpButtonProps = { opportunityId: number; isRsvped: boolean; isFull: boolean; + rsvpStatus?: RsvpStatus; onRsvpChange?: (newIsRsvped: boolean) => void; }; @@ -16,17 +21,96 @@ export default function RsvpButton({ opportunityId, isRsvped, isFull, + rsvpStatus, onRsvpChange, }: RsvpButtonProps): React.JSX.Element { const { toggleRsvp, isMutating } = useRsvpMutation(); + const [confirmOpen, setConfirmOpen] = useState(false); - const handleClick = async (): Promise => { - const success = await toggleRsvp(opportunityId, isRsvped); + const handleCancelConfirm = async (): Promise => { + setConfirmOpen(false); + const success = await toggleRsvp(opportunityId, true); if (success) { - onRsvpChange?.(!isRsvped); + onRsvpChange?.(false); } }; + // Terminal / read-only states + if (rsvpStatus === "cancelled" || rsvpStatus === "declined") { + return ( + + ); + } + + // Pending: can cancel (hard delete, re-RSVP allowed), no dialog needed + if (rsvpStatus === "pending") { + const handlePendingCancel = async (): Promise => { + const success = await toggleRsvp(opportunityId, true); + if (success) onRsvpChange?.(false); + }; + return ( + + ); + } + + // Confirmed: show Cancel RSVP with confirmation dialog + if (rsvpStatus === "confirmed") { + return ( + <> + + { + void handleCancelConfirm(); + }} + onClose={() => { + setConfirmOpen(false); + }} + /> + + ); + } + + // No RSVP yet if (isFull && !isRsvped) { return ( ); } diff --git a/src/components/volunteer/types.ts b/src/components/volunteer/types.ts index c925cbb..31f2dbe 100644 --- a/src/components/volunteer/types.ts +++ b/src/components/volunteer/types.ts @@ -4,7 +4,8 @@ export type RsvpStatus = | "confirmed" | "declined" | "attended" - | "no_show"; + | "no_show" + | "cancelled"; export type Opportunity = { id: number; diff --git a/src/db/schema/enums.ts b/src/db/schema/enums.ts index 96addc0..eb1f196 100644 --- a/src/db/schema/enums.ts +++ b/src/db/schema/enums.ts @@ -15,6 +15,7 @@ export const rsvpStatusEnum = pgEnum("rsvp_status", [ "declined", "attended", "no_show", + "cancelled", ]); // Background check verification status diff --git a/src/db/schema/users.ts b/src/db/schema/users.ts index e6b165c..493c6b5 100644 --- a/src/db/schema/users.ts +++ b/src/db/schema/users.ts @@ -45,7 +45,6 @@ export const volunteers = pgTable("volunteers", { backgroundCheckStatus: backgroundCheckStatusEnum("background_check_status") .default("not_required") .notNull(), - mediaRelease: boolean("media_release").default(false).notNull(), availability: jsonb("availability"), notificationPreference: notificationPreferenceEnum("notification_preference") .default("email") diff --git a/src/hooks/use-approvals-attendance.ts b/src/hooks/use-approvals-attendance.ts index 0a1ed2f..7408df0 100644 --- a/src/hooks/use-approvals-attendance.ts +++ b/src/hooks/use-approvals-attendance.ts @@ -29,7 +29,7 @@ export function useApprovalsAttendance(): { markAttendance: ( volunteerId: number, opportunityId: number, - status: "attended" | "no_show", + status: "attended" | "no_show" | "confirmed" | "cancelled", ) => Promise; mutating: boolean; } { @@ -84,13 +84,13 @@ export function useApprovalsAttendance(): { async ( volunteerId: number, opportunityId: number, - status: "attended" | "no_show", + status: "attended" | "no_show" | "confirmed" | "cancelled", ): Promise => { setMutating(true); try { - // Capture previous status before updating const prevRsvp = eventRsvps.find((r) => r.volunteerId === volunteerId); const wasConfirmed = prevRsvp?.rsvpStatus === "confirmed"; + const becomingConfirmed = status === "confirmed"; await apiClient.put("/api/staff/rsvps", { volunteerId, @@ -98,15 +98,13 @@ export function useApprovalsAttendance(): { status, }); - // Update RSVP list in modal setEventRsvps((prev) => prev.map((r) => r.volunteerId === volunteerId ? { ...r, rsvpStatus: status } : r, ), ); - // Optimistically decrement confirmedCount when a confirmed RSVP gets marked - if (wasConfirmed) { + if (wasConfirmed && !becomingConfirmed) { setEvents((prev) => prev.map((e) => e.id === opportunityId @@ -114,6 +112,14 @@ export function useApprovalsAttendance(): { : e, ), ); + } else if (!wasConfirmed && becomingConfirmed) { + setEvents((prev) => + prev.map((e) => + e.id === opportunityId + ? { ...e, confirmedCount: e.confirmedCount + 1 } + : e, + ), + ); } return true; diff --git a/src/hooks/use-volunteer-profile.ts b/src/hooks/use-volunteer-profile.ts index 26f1202..ea8c38f 100644 --- a/src/hooks/use-volunteer-profile.ts +++ b/src/hooks/use-volunteer-profile.ts @@ -15,6 +15,7 @@ type VolunteerProfileData = { city?: string | null; state?: string | null; referralSource?: string | null; + isAlumni?: boolean | null; }; users: { id: number; @@ -55,6 +56,7 @@ type UpdateProfileData = { city?: string | null; state?: string | null; referralSource?: string | null; + isAlumni?: boolean | null; }; export type UseVolunteerProfileResult = { diff --git a/src/services/calendar-events.service.ts b/src/services/calendar-events.service.ts index b727247..fdeae17 100644 --- a/src/services/calendar-events.service.ts +++ b/src/services/calendar-events.service.ts @@ -1,4 +1,4 @@ -import { and, eq, gte, inArray, lte } from "drizzle-orm"; +import { and, eq, gte, inArray, lt, lte } from "drizzle-orm"; import db from "@/db"; import { opportunities } from "@/db/schema"; @@ -90,11 +90,24 @@ function computeOccurrences( return occurrences; } +async function autoCompleteExpiredEvents(): Promise { + await db + .update(opportunities) + .set({ status: "completed", updatedAt: new Date() }) + .where( + and( + inArray(opportunities.status, ["open", "full"]), + lt(opportunities.endDate, new Date()), + ), + ); +} + export async function listCalendarEvents( startDate?: Date, endDate?: Date, statusFilter?: "open" | "full" | "completed" | "canceled", ): Promise { + await autoCompleteExpiredEvents(); const whereClauses = []; if (statusFilter) whereClauses.push(eq(opportunities.status, statusFilter)); if (startDate) whereClauses.push(gte(opportunities.endDate, startDate)); diff --git a/src/services/event-rsvps.service.ts b/src/services/event-rsvps.service.ts index 1d5e63c..a07f5e9 100644 --- a/src/services/event-rsvps.service.ts +++ b/src/services/event-rsvps.service.ts @@ -62,11 +62,12 @@ export async function listRsvpsByStatus( } const VALID_TRANSITIONS: Record = { - pending: ["confirmed", "declined", "attended", "no_show"], - confirmed: ["attended", "no_show", "declined"], + pending: ["confirmed", "declined", "attended", "no_show", "cancelled"], + confirmed: ["attended", "no_show", "declined", "cancelled"], declined: [], - attended: ["no_show"], - no_show: ["attended"], + attended: ["no_show", "cancelled"], + no_show: ["attended", "cancelled"], + cancelled: ["confirmed"], }; /** @@ -76,7 +77,7 @@ const VALID_TRANSITIONS: Record = { export async function updateRsvpStatus( volunteerId: number, opportunityId: number, - newStatus: "confirmed" | "declined" | "attended" | "no_show", + newStatus: "confirmed" | "declined" | "attended" | "no_show" | "cancelled", ): Promise { const [rsvp] = await db .select() diff --git a/src/services/opportunities.service.ts b/src/services/opportunities.service.ts index 539c49b..08197ac 100644 --- a/src/services/opportunities.service.ts +++ b/src/services/opportunities.service.ts @@ -1,4 +1,4 @@ -import { and, count, eq, gt, inArray, sql } from "drizzle-orm"; +import { and, count, eq, gt, inArray, notInArray, sql } from "drizzle-orm"; import db from "@/db"; import { @@ -53,13 +53,14 @@ export async function listOpenOpportunities( gt(opportunities.startDate, now), ); - // Get opportunities with RSVP counts using a subquery + // Get opportunities with RSVP counts using a subquery (excludes declined/cancelled) const rsvpCountSubquery = db .select({ opportunityId: volunteerRsvps.opportunityId, rsvpCount: count(volunteerRsvps.volunteerId).as("rsvp_count"), }) .from(volunteerRsvps) + .where(notInArray(volunteerRsvps.status, ["declined", "cancelled"])) .groupBy(volunteerRsvps.opportunityId) .as("rsvp_counts"); @@ -207,6 +208,7 @@ export async function getOpenOpportunityById( rsvpCount: count(volunteerRsvps.volunteerId).as("rsvp_count"), }) .from(volunteerRsvps) + .where(notInArray(volunteerRsvps.status, ["declined", "cancelled"])) .groupBy(volunteerRsvps.opportunityId) .as("rsvp_counts"); diff --git a/src/services/rsvp.service.ts b/src/services/rsvp.service.ts index 6ff280d..044d69e 100644 --- a/src/services/rsvp.service.ts +++ b/src/services/rsvp.service.ts @@ -1,4 +1,4 @@ -import { and, count, desc, eq, inArray } from "drizzle-orm"; +import { and, count, desc, eq, inArray, notInArray } from "drizzle-orm"; import db from "@/db"; import { users, volunteers } from "@/db/schema"; @@ -13,7 +13,8 @@ export class RsvpError extends Error { | "OPPORTUNITY_IN_PAST" | "ALREADY_RSVPD" | "OPPORTUNITY_FULL" - | "RSVP_NOT_FOUND", + | "RSVP_NOT_FOUND" + | "RSVP_NOT_CANCELLABLE", message: string, ) { super(message); @@ -84,7 +85,12 @@ export async function createRsvp( const rsvpCount = await db .select({ count: count() }) .from(volunteerRsvps) - .where(eq(volunteerRsvps.opportunityId, opportunityId)); + .where( + and( + eq(volunteerRsvps.opportunityId, opportunityId), + notInArray(volunteerRsvps.status, ["declined", "cancelled"]), + ), + ); if (rsvpCount[0].count >= opportunity[0].maxVolunteers) { throw new RsvpError("OPPORTUNITY_FULL", "This opportunity is full"); @@ -133,14 +139,21 @@ export async function cancelRsvp( throw new RsvpError("RSVP_NOT_FOUND", "RSVP not found"); } - await db - .delete(volunteerRsvps) - .where( - and( - eq(volunteerRsvps.volunteerId, volunteerId), - eq(volunteerRsvps.opportunityId, opportunityId), - ), + const { status } = existingRsvp[0]; + + if (status !== "pending" && status !== "confirmed") { + throw new RsvpError( + "RSVP_NOT_CANCELLABLE", + "This RSVP cannot be cancelled", ); + } + + const condition = and( + eq(volunteerRsvps.volunteerId, volunteerId), + eq(volunteerRsvps.opportunityId, opportunityId), + ); + + await db.delete(volunteerRsvps).where(condition); return { volunteerId, opportunityId }; } @@ -153,6 +166,7 @@ export type RsvpWithOpportunity = { | "declined" | "attended" | "no_show" + | "cancelled" | null; rsvpAt: Date | null; notes: string | null; diff --git a/src/services/shared/volunteer-data.ts b/src/services/shared/volunteer-data.ts index c86f214..f5e11b5 100644 --- a/src/services/shared/volunteer-data.ts +++ b/src/services/shared/volunteer-data.ts @@ -42,7 +42,13 @@ export type VolunteerDetailData = { opportunityLocation: string | null; opportunityStartDate: Date | null; opportunityEndDate: Date | null; - rsvpStatus: "pending" | "confirmed" | "declined" | "attended" | "no_show"; + rsvpStatus: + | "pending" + | "confirmed" + | "declined" + | "attended" + | "no_show" + | "cancelled"; rsvpAt: Date; rsvpNotes: string | null; }[]; diff --git a/src/services/volunteer-client.service.ts b/src/services/volunteer-client.service.ts index 5c8d62a..613eaaa 100644 --- a/src/services/volunteer-client.service.ts +++ b/src/services/volunteer-client.service.ts @@ -154,7 +154,6 @@ export type FetchVolunteerByIdResult = { volunteerType: string | null; isAlumni: boolean; backgroundCheckStatus: "not_required" | "pending" | "approved" | "rejected"; - mediaRelease: boolean; availability: unknown; notificationPreference: "email" | "sms" | "both" | "none"; employer: string | null; diff --git a/src/services/volunteer-detail.service.ts b/src/services/volunteer-detail.service.ts index 8e62014..6c001af 100644 --- a/src/services/volunteer-detail.service.ts +++ b/src/services/volunteer-detail.service.ts @@ -34,7 +34,13 @@ export type VolunteerDetail = { opportunityLocation: string | null; opportunityStartDate: Date | null; opportunityEndDate: Date | null; - rsvpStatus: "pending" | "confirmed" | "declined" | "attended" | "no_show"; + rsvpStatus: + | "pending" + | "confirmed" + | "declined" + | "attended" + | "no_show" + | "cancelled"; rsvpAt: Date; rsvpNotes: string | null; }[]; @@ -66,7 +72,6 @@ export type VolunteerDetailUpdateData = { volunteerType?: string; isAlumni?: boolean; backgroundCheckStatus?: "not_required" | "pending" | "approved" | "rejected"; - mediaRelease?: boolean; availability?: Record; notificationPreference?: "email" | "sms" | "both" | "none"; employer?: string; @@ -142,7 +147,6 @@ export async function updateVolunteerDetail( | "pending" | "approved" | "rejected"; - mediaRelease?: boolean; availability?: Record; notificationPreference?: "email" | "sms" | "both" | "none"; employer?: string; @@ -166,8 +170,6 @@ export async function updateVolunteerDetail( if (data.isAlumni !== undefined) volunteerData.isAlumni = data.isAlumni; if (data.backgroundCheckStatus !== undefined) volunteerData.backgroundCheckStatus = data.backgroundCheckStatus; - if (data.mediaRelease !== undefined) - volunteerData.mediaRelease = data.mediaRelease; if (data.availability !== undefined) volunteerData.availability = data.availability; if (data.notificationPreference !== undefined) diff --git a/src/services/volunteer-hours.service.ts b/src/services/volunteer-hours.service.ts index c1a7890..f603e7c 100644 --- a/src/services/volunteer-hours.service.ts +++ b/src/services/volunteer-hours.service.ts @@ -313,9 +313,9 @@ export async function volunteerLogHours( "You can only log hours for events you RSVPed to", ); } - if (rsvp.status === "declined" || rsvp.status === "no_show") { + if (["declined", "no_show", "cancelled"].includes(rsvp.status)) { throw new ValidationError( - "You cannot log hours for events you declined or did not attend", + "You cannot log hours for events you declined, cancelled, or did not attend", ); } const [created] = await db diff --git a/src/services/volunteer.service.ts b/src/services/volunteer.service.ts index 2ca8deb..37d1235 100644 --- a/src/services/volunteer.service.ts +++ b/src/services/volunteer.service.ts @@ -51,7 +51,6 @@ export type CreateVolunteerParams = { volunteerType?: string; isAlumni?: boolean; backgroundCheckStatus?: "not_required" | "pending" | "approved" | "rejected"; - mediaRelease?: boolean; availability?: Record; notificationPreference?: "email" | "sms" | "both" | "none"; employer?: string; @@ -105,6 +104,7 @@ export type GetVolunteerProfileResult = { | "declined" | "attended" | "no_show" + | "cancelled" | null; rsvpAt: Date; rsvpNotes: string | null; @@ -142,6 +142,7 @@ export type VolunteerProfileUpdateParams = { city?: string; state?: string; referralSource?: string; + isAlumni?: boolean; }; // --------------------------------------------------------------------------- @@ -286,7 +287,6 @@ export async function createVolunteer( volunteerType: params.volunteerType, isAlumni: params.isAlumni ?? false, backgroundCheckStatus, - mediaRelease: params.mediaRelease ?? false, availability: params.availability, notificationPreference: params.notificationPreference ?? "email", employer: params.employer, @@ -372,6 +372,7 @@ export async function updateVolunteerProfile( city, state, referralSource, + isAlumni, } = params; const volunteer = await db @@ -396,6 +397,7 @@ export async function updateVolunteerProfile( city?: string; state?: string; referralSource?: string; + isAlumni?: boolean; } = {}; if (phone !== undefined) userData.phone = phone; @@ -410,6 +412,7 @@ export async function updateVolunteerProfile( if (state !== undefined) volunteerData.state = state; if (referralSource !== undefined) volunteerData.referralSource = referralSource; + if (isAlumni !== undefined) volunteerData.isAlumni = isAlumni; // Update both tables sequentially (neon-http doesn't support transactions) if (Object.keys(volunteerData).length > 0) {