CREATE TABLE role ( role_id SERIAL PRIMARY KEY, name VARCHAR(50) UNIQUE NOT NULL, -- admin, volunteer, team_lead, manager, terry created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); -- COMBINED: users + admin_settings in one table CREATE TABLE users ( user_id SERIAL PRIMARY KEY, first_name VARCHAR(100), last_name VARCHAR(100), email VARCHAR(150) UNIQUE NOT NULL, phone VARCHAR(20) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE address_database ( address_id SERIAL PRIMARY KEY, address VARCHAR(255), street_name VARCHAR(100), street_type VARCHAR(50), street_quadrant VARCHAR(50), house_number VARCHAR(20), house_alpha VARCHAR(10), postal_code VARCHAR(10), longitude DECIMAL(9,6), latitude DECIMAL(9,6), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE team ( team_id SERIAL PRIMARY KEY, team_lead_id INT REFERENCES users(user_id) ON DELETE SET NULL, volunteer_id INT REFERENCES users(user_id) ON DELETE SET NULL, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE admin_volunteers ( admin_id INT REFERENCES users(user_id) ON DELETE CASCADE, volunteer_id INT REFERENCES users(user_id) ON DELETE CASCADE, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), PRIMARY KEY (admin_id, volunteer_id) ); CREATE TABLE appointment ( sched_id SERIAL PRIMARY KEY, user_id INT REFERENCES users(user_id) ON DELETE CASCADE, address_id INT REFERENCES address_database(address_id) ON DELETE CASCADE, appointment_date DATE, appointment_time TIME, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE poll( poll_id SERIAL PRIMARY KEY, user_id INTEGER REFERENCES users ON DELETE CASCADE, address_id INTEGER REFERENCES address_database ON DELETE CASCADE, donation_amount integer default 0, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE poll_response ( poll_response_id SERIAL PRIMARY KEY, poll_id INTEGER REFERENCES poll(poll_id) ON DELETE CASCADE, respondent_postal_code VARCHAR(10), -- Postal code of respondent question1_voted_before BOOLEAN, -- Have you voted before? question2_vote_again BOOLEAN, -- Will you vote again for this candidate? question3_lawn_signs INTEGER DEFAULT 0, -- How many lawn signs needed question4_banner_signs INTEGER DEFAULT 0, -- How many banner signs needed question5_thoughts TEXT, -- Write your thoughts signage_status VARCHAR(50) DEFAULT 'requested' CHECK (signage_status IN ('requested', 'delivered', 'cancelled')), is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE post ( post_id SERIAL PRIMARY KEY, author_id INT REFERENCES users(user_id) ON DELETE CASCADE, content TEXT, image_url TEXT, created_at TIMESTAMP DEFAULT NOW() ); CREATE TABLE availability ( sched_id SERIAL PRIMARY KEY, user_id INT REFERENCES users(user_id) ON DELETE CASCADE, day_of_week VARCHAR(20), start_time TIME, end_time TIME, created_at TIMESTAMP DEFAULT NOW() ); -- Indexes for better performance CREATE INDEX idx_poll_response_poll_id ON poll_response(poll_id); CREATE INDEX idx_poll_response_postal_code ON poll_response(respondent_postal_code); CREATE INDEX idx_poll_response_signage_status ON poll_response(signage_status); CREATE INDEX idx_poll_user_id ON poll(user_id); -- Function to generate a 6-character random admin code CREATE OR REPLACE FUNCTION generate_admin_code() RETURNS trigger AS $$ BEGIN IF (SELECT name FROM role WHERE role_id = NEW.role_id) = 'admin' THEN NEW.admin_code := substring(md5(random()::text) FROM 1 FOR 6); ELSE NEW.admin_code := NULL; END IF; RETURN NEW; END; $$ LANGUAGE plpgsql; -- Trigger to automatically generate admin_code on INSERT CREATE TRIGGER set_admin_code BEFORE INSERT ON users FOR EACH ROW EXECUTE PROCEDURE generate_admin_code(); INSERT INTO role (role_id, name) VALUES (1, 'admin'), (2, 'team_lead'), (3, 'volunteer') ON CONFLICT DO NOTHING; ALTER TABLE availability ADD CONSTRAINT availability_user_day_unique UNIQUE (user_id, day_of_week);