CREATE SCHEMA "wildlife"; --> statement-breakpoint CREATE TABLE "wildlife"."groups" ( "id" serial PRIMARY KEY, "slug" text NOT NULL, "name" text NOT NULL, "name_nl" text NOT NULL, "emoji" text NOT NULL, "sort_order" smallint DEFAULT 0 NOT NULL, "species_count" integer DEFAULT 0 NOT NULL, "nl_count" integer DEFAULT 0 NOT NULL, "photo" text, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL ); --> statement-breakpoint CREATE TABLE "wildlife"."photos" ( "id" serial PRIMARY KEY, "species_id" integer NOT NULL, "position" smallint NOT NULL, "kind" text NOT NULL, "photo_id" integer NOT NULL, "url" text NOT NULL, "width" integer, "height" integer, "author" text, "license" text NOT NULL, "page_url" text, "place" text ); --> statement-breakpoint CREATE TABLE "wildlife"."species" ( "id" serial PRIMARY KEY, "dex" integer NOT NULL, "group_id" integer NOT NULL, "slug" text NOT NULL, "scientific_name" text NOT NULL, "name_en" text, "name_nl" text, "search" text NOT NULL, "inat_id" integer NOT NULL, "gbif_key" integer, "wikidata" text, "wikipedia_en" text, "wikipedia_nl" text, "class_name" text, "order_name" text, "family_name" text, "iucn" text, "nl_status" text, "nl_establishment" text, "extinct" boolean DEFAULT false NOT NULL, "nl_obs" integer DEFAULT 0 NOT NULL, "europe_obs" integer DEFAULT 0 NOT NULL, "world_obs" integer DEFAULT 0 NOT NULL, "gbif_nl" integer, "scope" text NOT NULL, "summary" text, "summary_nl" text, "facts" text[] DEFAULT '{}'::text[] NOT NULL, "mass_kg" real, "length_m" real, "lifespan_years" real, "named_year" smallint, "named_by" text, "sources" text[] DEFAULT '{}'::text[] NOT NULL, "photo" text, "photo_count" smallint DEFAULT 0 NOT NULL, "photos_checked_at" timestamp with time zone, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL ); --> statement-breakpoint CREATE UNIQUE INDEX "groups_slug_key" ON "wildlife"."groups" ("slug");--> statement-breakpoint CREATE UNIQUE INDEX "photos_species_position_key" ON "wildlife"."photos" ("species_id","position");--> statement-breakpoint CREATE UNIQUE INDEX "photos_species_photo_key" ON "wildlife"."photos" ("species_id","photo_id");--> statement-breakpoint CREATE UNIQUE INDEX "species_dex_key" ON "wildlife"."species" ("dex");--> statement-breakpoint CREATE UNIQUE INDEX "species_inat_key" ON "wildlife"."species" ("inat_id");--> statement-breakpoint CREATE UNIQUE INDEX "species_slug_key" ON "wildlife"."species" ("slug");--> statement-breakpoint CREATE INDEX "species_group_idx" ON "wildlife"."species" ("group_id");--> statement-breakpoint CREATE INDEX "species_nl_obs_idx" ON "wildlife"."species" ("nl_obs");--> statement-breakpoint CREATE INDEX "species_search_trgm_idx" ON "wildlife"."species" USING gin ("search" public.gin_trgm_ops);--> statement-breakpoint ALTER TABLE "wildlife"."photos" ADD CONSTRAINT "photos_species_id_species_id_fkey" FOREIGN KEY ("species_id") REFERENCES "wildlife"."species"("id") ON DELETE CASCADE;--> statement-breakpoint ALTER TABLE "wildlife"."species" ADD CONSTRAINT "species_group_id_groups_id_fkey" FOREIGN KEY ("group_id") REFERENCES "wildlife"."groups"("id") ON DELETE CASCADE;--> statement-breakpoint -- Hand-written: updated_at triggers, grants, GUI registration. Data comes from scripts/wildlife-import.ts and scripts/wildlife-photos.ts. DO $$ DECLARE t text; BEGIN FOREACH t IN ARRAY ARRAY['groups','species'] LOOP EXECUTE format('CREATE TRIGGER touch_updated_at BEFORE UPDATE ON wildlife.%I FOR EACH ROW EXECUTE FUNCTION meta.touch_updated_at()', t); END LOOP; END $$; --> statement-breakpoint DO $$ BEGIN IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'personal_app') THEN GRANT USAGE ON SCHEMA wildlife TO personal_app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA wildlife TO personal_app; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA wildlife TO personal_app; ALTER DEFAULT PRIVILEGES IN SCHEMA wildlife GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO personal_app; ALTER DEFAULT PRIVILEGES IN SCHEMA wildlife GRANT USAGE, SELECT ON SEQUENCES TO personal_app; END IF; IF EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'personal_ro') THEN GRANT USAGE ON SCHEMA wildlife TO personal_ro; GRANT SELECT ON ALL TABLES IN SCHEMA wildlife TO personal_ro; ALTER DEFAULT PRIVILEGES IN SCHEMA wildlife GRANT SELECT ON TABLES TO personal_ro; END IF; END $$; --> statement-breakpoint DO $$ BEGIN IF to_regclass('meta.tables') IS NOT NULL THEN INSERT INTO meta.tables (schema_name, table_name, slug, display_name, kind, editable, has_done, sort_order) VALUES ('wildlife','groups','wildlife-groups','Wildlife groups','core',false,false,1000), ('wildlife','species','wildlife-species','Wildlife species','core',false,false,1000), ('wildlife','photos','wildlife-photos','Wildlife photos','core',false,false,1000) ON CONFLICT DO NOTHING; IF has_function_privilege('meta.sync(integer)', 'EXECUTE') THEN PERFORM meta.sync(); END IF; END IF; END $$;