-- Apply after auth-catalog-migration.sql and before deploying the foundation release. -- Additive domain-schema changes; existing records and numeric IDs are retained. ALTER TABLE blog_articles ADD COLUMN IF NOT EXISTS tags text[] NOT NULL DEFAULT '{}'::text[]; ALTER TABLE lists ADD COLUMN IF NOT EXISTS slug text; UPDATE lists SET slug = COALESCE(NULLIF(trim(both '-' from regexp_replace(lower(name), '[^a-z0-9]+', '-', 'g')), ''), 'list') || '-' || id WHERE slug IS NULL; ALTER TABLE lists ALTER COLUMN slug SET NOT NULL; CREATE UNIQUE INDEX IF NOT EXISTS lists_slug_unique ON lists (slug); ALTER TABLE lists ADD COLUMN IF NOT EXISTS is_progress boolean NOT NULL DEFAULT false; ALTER TABLE list_items ADD COLUMN IF NOT EXISTS picture_url text; ALTER TABLE list_items ADD COLUMN IF NOT EXISTS description text; CREATE TABLE IF NOT EXISTS list_item_completions ( id serial PRIMARY KEY, item_id integer NOT NULL REFERENCES list_items(id) ON DELETE CASCADE, user_id text NOT NULL REFERENCES "user"(id) ON DELETE CASCADE, created_at timestamptz NOT NULL DEFAULT now(), updated_at timestamptz NOT NULL DEFAULT now(), CONSTRAINT list_item_completions_item_id_user_id_unique UNIQUE (item_id, user_id) ); CREATE INDEX IF NOT EXISTS list_item_completions_user_id_idx ON list_item_completions(user_id); -- Join/setting records gain the same numeric identity as other site-owned rows. ALTER TABLE tasks_to_topics ADD COLUMN IF NOT EXISTS id serial; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'tasks_to_topics'::regclass AND conname = 'tasks_to_topics_pkey' AND pg_get_constraintdef(oid) NOT LIKE 'PRIMARY KEY (id)') THEN ALTER TABLE tasks_to_topics DROP CONSTRAINT tasks_to_topics_pkey; END IF; END $$; DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'tasks_to_topics'::regclass AND contype = 'p') THEN ALTER TABLE tasks_to_topics ADD CONSTRAINT tasks_to_topics_pkey PRIMARY KEY (id); END IF; END $$; CREATE UNIQUE INDEX IF NOT EXISTS tasks_to_topics_task_id_topic_id_unique ON tasks_to_topics(task_id, topic_id); ALTER TABLE catch_em_all_settings ADD COLUMN IF NOT EXISTS id serial; DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'catch_em_all_settings'::regclass AND conname = 'catch_em_all_settings_pkey' AND pg_get_constraintdef(oid) NOT LIKE 'PRIMARY KEY (id)') THEN ALTER TABLE catch_em_all_settings DROP CONSTRAINT catch_em_all_settings_pkey; END IF; END $$; DO $$ BEGIN IF NOT EXISTS (SELECT 1 FROM pg_constraint WHERE conrelid = 'catch_em_all_settings'::regclass AND contype = 'p') THEN ALTER TABLE catch_em_all_settings ADD CONSTRAINT catch_em_all_settings_pkey PRIMARY KEY (id); END IF; END $$; CREATE UNIQUE INDEX IF NOT EXISTS catch_em_all_settings_user_id_unique ON catch_em_all_settings(user_id); -- Every site-owned table records creation and latest update. Domain-specific -- event times (submitted_at, seen_at, uploaded_at) remain untouched. DO $$ DECLARE table_name text; BEGIN FOREACH table_name IN ARRAY ARRAY[ 'blog_articles', 'lists', 'list_items', 'list_item_completions', 'animals', 'sightings', 'photos', 'tasks', 'topics', 'tasks_to_topics', 'pokemon', 'games', 'pokemon_game_entries', 'user_entries', 'user_games', 'catch_em_all_settings' ] LOOP EXECUTE format('ALTER TABLE %I ADD COLUMN IF NOT EXISTS created_at timestamptz NOT NULL DEFAULT now()', table_name); EXECUTE format('ALTER TABLE %I ADD COLUMN IF NOT EXISTS updated_at timestamptz NOT NULL DEFAULT now()', table_name); END LOOP; END $$; -- Preserve the best available creation time for older Animaldex rows. UPDATE animals SET created_at = submitted_at WHERE created_at > submitted_at; UPDATE sightings SET created_at = submitted_at WHERE created_at > submitted_at; UPDATE photos SET created_at = uploaded_at WHERE created_at > uploaded_at; CREATE OR REPLACE FUNCTION site_touch_updated_at() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = now(); RETURN NEW; END $$; DO $$ DECLARE table_name text; BEGIN FOREACH table_name IN ARRAY ARRAY[ 'blog_articles', 'lists', 'list_items', 'list_item_completions', 'animals', 'sightings', 'photos', 'tasks', 'topics', 'tasks_to_topics', 'pokemon', 'games', 'pokemon_game_entries', 'user_entries', 'user_games', 'catch_em_all_settings' ] LOOP EXECUTE format('DROP TRIGGER IF EXISTS site_touch_updated_at ON %I', table_name); EXECUTE format('CREATE TRIGGER site_touch_updated_at BEFORE UPDATE ON %I FOR EACH ROW EXECUTE FUNCTION site_touch_updated_at()', table_name); END LOOP; END $$;