invy/backend/migrations.sql

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);