340 lines
11 KiB
PL/PgSQL
340 lines
11 KiB
PL/PgSQL
-- Multi-Event Invitation App Database Schema
|
|
-- PostgreSQL Migration Script
|
|
-- Created: 2026-02-23
|
|
|
|
-- ============================================
|
|
-- STEP 1: Enable UUID Extension
|
|
-- ============================================
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- ============================================
|
|
-- STEP 2: Create Users Table
|
|
-- ============================================
|
|
-- Track users who can manage events
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
email TEXT NOT NULL UNIQUE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_users_email ON users(email);
|
|
|
|
-- Migrate existing owners from guests table to users (optional, run after creating guests table)
|
|
-- INSERT INTO users (email) SELECT DISTINCT owner FROM guests WHERE owner IS NOT NULL AND owner != 'self-service' ON CONFLICT DO NOTHING;
|
|
|
|
-- ============================================
|
|
-- STEP 3: Create Events Table
|
|
-- ============================================
|
|
-- Store multiple events
|
|
CREATE TABLE IF NOT EXISTS events (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
name TEXT NOT NULL,
|
|
date TIMESTAMP WITH TIME ZONE,
|
|
location TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
CREATE INDEX idx_events_created_at ON events(created_at);
|
|
|
|
-- ============================================
|
|
-- STEP 4: Create Event Members Table (Authorization)
|
|
-- ============================================
|
|
-- Track which users are members of which events and their roles
|
|
CREATE TABLE IF NOT EXISTS event_members (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
event_id UUID NOT NULL REFERENCES events(id) ON DELETE CASCADE,
|
|
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
role TEXT NOT NULL DEFAULT 'admin' CHECK (role IN ('admin', 'editor', 'viewer')),
|
|
display_name TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
UNIQUE(event_id, user_id)
|
|
);
|
|
|
|
CREATE INDEX idx_event_members_event_id ON event_members(event_id);
|
|
CREATE INDEX idx_event_members_user_id ON event_members(user_id);
|
|
CREATE INDEX idx_event_members_event_user ON event_members(event_id, user_id);
|
|
|
|
-- ============================================
|
|
-- STEP 5: Create Guests Table (Refactored)
|
|
-- ============================================
|
|
-- Store guest information scoped by event
|
|
CREATE TABLE IF NOT EXISTS guests_v2 (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
event_id UUID NOT NULL REFERENCES events(id) ON DELETE CASCADE,
|
|
added_by_user_id UUID NOT NULL REFERENCES users(id),
|
|
|
|
-- Guest Information
|
|
first_name TEXT NOT NULL,
|
|
last_name TEXT NOT NULL,
|
|
email TEXT,
|
|
phone_number TEXT,
|
|
|
|
-- RSVP & Preferences
|
|
rsvp_status TEXT NOT NULL DEFAULT 'invited' CHECK (rsvp_status IN ('invited', 'confirmed', 'declined')),
|
|
meal_preference TEXT,
|
|
|
|
-- Plus One
|
|
has_plus_one BOOLEAN DEFAULT FALSE,
|
|
plus_one_name TEXT,
|
|
|
|
-- Event Details
|
|
table_number TEXT,
|
|
side TEXT, -- e.g. "groom side" / "bride side" / "Dvir side" / "Vered side"
|
|
|
|
-- Source Information
|
|
owner_email TEXT, -- Email of person who added this guest
|
|
source TEXT DEFAULT 'manual' CHECK (source IN ('google', 'manual', 'self-service')),
|
|
|
|
-- Notes & Metadata
|
|
notes TEXT,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Indexes for performance
|
|
CREATE INDEX idx_guests_event_id ON guests_v2(event_id);
|
|
CREATE INDEX idx_guests_added_by_user_id ON guests_v2(added_by_user_id);
|
|
CREATE INDEX idx_guests_event_user ON guests_v2(event_id, added_by_user_id);
|
|
CREATE INDEX idx_guests_phone_number ON guests_v2(phone_number);
|
|
CREATE INDEX idx_guests_event_phone ON guests_v2(event_id, phone_number);
|
|
CREATE INDEX idx_guests_event_status ON guests_v2(event_id, rsvp_status);
|
|
CREATE INDEX idx_guests_owner_email ON guests_v2(event_id, owner_email);
|
|
CREATE INDEX idx_guests_source ON guests_v2(event_id, source);
|
|
|
|
-- Trigger for auto-updating updated_at on guests_v2
|
|
CREATE OR REPLACE FUNCTION update_guests_v2_updated_at()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE 'plpgsql';
|
|
|
|
CREATE TRIGGER update_guests_v2_timestamp
|
|
BEFORE UPDATE ON guests_v2
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION update_guests_v2_updated_at();
|
|
|
|
-- ============================================
|
|
-- STEP 6: Migration from Old Schema (Optional)
|
|
-- ============================================
|
|
-- This section is optional and only needed if migrating existing data
|
|
|
|
-- Create migration function to handle existing guests table
|
|
-- Run this if you have existing data in the old guests table
|
|
|
|
/*
|
|
DO $$
|
|
DECLARE
|
|
default_event_id UUID;
|
|
default_user_id UUID;
|
|
BEGIN
|
|
-- Create a default event for migration
|
|
INSERT INTO events (name, date, location)
|
|
VALUES ('Migrated Wedding', NOW(), 'Unknown')
|
|
RETURNING id INTO default_event_id;
|
|
|
|
-- Create default user for unmapped owners
|
|
INSERT INTO users (email)
|
|
VALUES ('admin@example.com')
|
|
ON CONFLICT DO NOTHING;
|
|
|
|
SELECT id INTO default_user_id FROM users WHERE email = 'admin@example.com';
|
|
|
|
-- Migrate data from old guests table to new one
|
|
INSERT INTO guests_v2 (event_id, added_by_user_id, first_name, last_name, phone, side, status, notes)
|
|
SELECT
|
|
default_event_id,
|
|
COALESCE(
|
|
(SELECT id FROM users WHERE email = guests.owner),
|
|
default_user_id
|
|
),
|
|
guests.first_name,
|
|
guests.last_name,
|
|
COALESCE(guests.phone_number, ''),
|
|
NULL,
|
|
CASE
|
|
WHEN guests.rsvp_status = 'accepted' THEN 'confirmed'
|
|
WHEN guests.rsvp_status = 'declined' THEN 'declined'
|
|
ELSE 'invited'
|
|
END,
|
|
COALESCE(
|
|
'Meal: ' || guests.meal_preference || '; Plus-one: ' || guests.plus_one_name,
|
|
notes
|
|
)
|
|
FROM guests;
|
|
|
|
-- Create event_members record for default user
|
|
INSERT INTO event_members (event_id, user_id, role, display_name)
|
|
VALUES (default_event_id, default_user_id, 'admin', 'Admin')
|
|
ON CONFLICT DO NOTHING;
|
|
END $$;
|
|
*/
|
|
|
|
-- ============================================
|
|
-- STEP 7: ALTER Guests Table (if already exists - add missing columns)
|
|
-- ============================================
|
|
-- Safe migration that handles existing data
|
|
|
|
-- Migration logic using a PL/pgSQL block
|
|
DO $$
|
|
BEGIN
|
|
-- Check if column exists before creating/renaming
|
|
-- If rsvp_status doesn't exist, handle the rename or creation
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'rsvp_status'
|
|
) THEN
|
|
-- If old status column exists, rename it
|
|
IF EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'status'
|
|
) THEN
|
|
ALTER TABLE guests_v2 RENAME COLUMN status TO rsvp_status;
|
|
ELSE
|
|
-- Add rsvp_status column with default
|
|
ALTER TABLE guests_v2 ADD COLUMN rsvp_status TEXT DEFAULT 'invited';
|
|
END IF;
|
|
END IF;
|
|
|
|
-- Handle phone_number migration
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'phone_number'
|
|
) THEN
|
|
IF EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'phone'
|
|
) THEN
|
|
ALTER TABLE guests_v2 RENAME COLUMN phone TO phone_number;
|
|
ELSE
|
|
ALTER TABLE guests_v2 ADD COLUMN phone_number TEXT;
|
|
END IF;
|
|
END IF;
|
|
|
|
-- Add other missing columns
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'email'
|
|
) THEN
|
|
ALTER TABLE guests_v2 ADD COLUMN email TEXT;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'meal_preference'
|
|
) THEN
|
|
ALTER TABLE guests_v2 ADD COLUMN meal_preference TEXT;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'has_plus_one'
|
|
) THEN
|
|
ALTER TABLE guests_v2 ADD COLUMN has_plus_one BOOLEAN DEFAULT FALSE;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'plus_one_name'
|
|
) THEN
|
|
ALTER TABLE guests_v2 ADD COLUMN plus_one_name TEXT;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'table_number'
|
|
) THEN
|
|
ALTER TABLE guests_v2 ADD COLUMN table_number TEXT;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'owner_email'
|
|
) THEN
|
|
ALTER TABLE guests_v2 ADD COLUMN owner_email TEXT;
|
|
END IF;
|
|
|
|
IF NOT EXISTS (
|
|
SELECT FROM information_schema.columns
|
|
WHERE table_name = 'guests_v2' AND column_name = 'source'
|
|
) THEN
|
|
ALTER TABLE guests_v2 ADD COLUMN source TEXT DEFAULT 'manual';
|
|
END IF;
|
|
|
|
RAISE NOTICE 'Migration completed successfully';
|
|
END $$;
|
|
|
|
-- Update CHECK constraint for rsvp_status if needed
|
|
DO $$
|
|
BEGIN
|
|
-- Drop old constraint if it exists
|
|
ALTER TABLE guests_v2 DROP CONSTRAINT IF EXISTS guests_v2_status_check;
|
|
-- Add new constraint
|
|
ALTER TABLE guests_v2 ADD CONSTRAINT guests_v2_rsvp_status_check
|
|
CHECK (rsvp_status IN ('invited', 'confirmed', 'declined'));
|
|
EXCEPTION WHEN OTHERS THEN
|
|
-- Constraint might already exist, that's okay
|
|
NULL;
|
|
END $$;
|
|
|
|
-- Add source constraint
|
|
DO $$
|
|
BEGIN
|
|
ALTER TABLE guests_v2 ADD CONSTRAINT guests_v2_source_check
|
|
CHECK (source IN ('google', 'manual', 'self-service'));
|
|
EXCEPTION WHEN OTHERS THEN
|
|
NULL;
|
|
END $$;
|
|
|
|
-- Add missing indexes if they don't exist
|
|
CREATE INDEX IF NOT EXISTS idx_guests_owner_email ON guests_v2(event_id, owner_email);
|
|
CREATE INDEX IF NOT EXISTS idx_guests_source ON guests_v2(event_id, source);
|
|
CREATE INDEX IF NOT EXISTS idx_guests_phone_number ON guests_v2(phone_number);
|
|
CREATE INDEX IF NOT EXISTS idx_guests_event_phone_new ON guests_v2(event_id, phone_number);
|
|
|
|
|
|
-- ============================================
|
|
-- STEP 15: Add WhatsApp Template Fields (Migration)
|
|
-- ============================================
|
|
-- Add WhatsApp-related columns to events table for wedding invitation template
|
|
|
|
DO $$
|
|
BEGIN
|
|
ALTER TABLE events ADD COLUMN partner1_name TEXT;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
NULL;
|
|
END $$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
ALTER TABLE events ADD COLUMN partner2_name TEXT;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
NULL;
|
|
END $$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
ALTER TABLE events ADD COLUMN venue TEXT;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
NULL;
|
|
END $$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
ALTER TABLE events ADD COLUMN event_time TEXT;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
NULL;
|
|
END $$;
|
|
|
|
DO $$
|
|
BEGIN
|
|
ALTER TABLE events ADD COLUMN guest_link TEXT;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
NULL;
|
|
END $$;
|
|
|
|
-- Create index for query efficiency
|
|
CREATE INDEX IF NOT EXISTS idx_events_guest_link ON events(guest_link);
|