96 lines
5.1 KiB
SQL
96 lines
5.1 KiB
SQL
CREATE SCHEMA "worldtodo";
|
|
--> statement-breakpoint
|
|
CREATE TYPE "worldtodo"."req_kind" AS ENUM('add', 'edit', 'delete');--> statement-breakpoint
|
|
CREATE TYPE "worldtodo"."req_status" AS ENUM('pending', 'approved', 'rejected');--> statement-breakpoint
|
|
CREATE TYPE "worldtodo"."scale" AS ENUM('planet', 'region', 'country', 'city', 'street');--> statement-breakpoint
|
|
CREATE TYPE "worldtodo"."status" AS ENUM('open', 'in_progress', 'done');--> statement-breakpoint
|
|
CREATE TABLE "worldtodo"."categories" (
|
|
"slug" text PRIMARY KEY,
|
|
"label" text NOT NULL,
|
|
"icon" text NOT NULL,
|
|
"colour" text NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "worldtodo"."change_requests" (
|
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "worldtodo"."change_requests_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
|
|
"kind" "worldtodo"."req_kind" NOT NULL,
|
|
"task_id" bigint,
|
|
"payload" jsonb DEFAULT '{}' NOT NULL,
|
|
"applied" jsonb,
|
|
"task_updated_at" timestamp with time zone,
|
|
"reason" text NOT NULL,
|
|
"display_name" text,
|
|
"status" "worldtodo"."req_status" DEFAULT 'pending'::"worldtodo"."req_status" NOT NULL,
|
|
"owner_note" text,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"decided_at" timestamp with time zone,
|
|
CONSTRAINT "change_requests_reason_check" CHECK (char_length("reason") BETWEEN 10 AND 1000),
|
|
CONSTRAINT "change_requests_display_name_check" CHECK (char_length("display_name") <= 40),
|
|
CONSTRAINT "change_requests_task_check" CHECK ((("kind" = 'add') = ("task_id" IS NULL)))
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "worldtodo"."tasks" (
|
|
"id" bigint PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY (sequence name "worldtodo"."tasks_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1),
|
|
"slug" text NOT NULL,
|
|
"title" text NOT NULL,
|
|
"description" text NOT NULL,
|
|
"scale" "worldtodo"."scale" NOT NULL,
|
|
"category" text NOT NULL,
|
|
"location_name" text NOT NULL,
|
|
"country_code" text,
|
|
"lat" double precision,
|
|
"lon" double precision,
|
|
"status" "worldtodo"."status" DEFAULT 'open'::"worldtodo"."status" NOT NULL,
|
|
"source_urls" text[] NOT NULL,
|
|
"source_date" text,
|
|
"confidence" text DEFAULT 'high' NOT NULL,
|
|
"verification_note" text,
|
|
"as_of" date NOT NULL,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"deleted_at" timestamp with time zone,
|
|
"search" tsvector GENERATED ALWAYS AS (to_tsvector('simple', title || ' ' || description || ' ' || location_name)) STORED,
|
|
CONSTRAINT "tasks_title_check" CHECK (char_length("title") BETWEEN 5 AND 140),
|
|
CONSTRAINT "tasks_description_check" CHECK (char_length("description") <= 2000),
|
|
CONSTRAINT "tasks_lat_check" CHECK ("lat" BETWEEN -90 AND 90),
|
|
CONSTRAINT "tasks_lon_check" CHECK ("lon" BETWEEN -180 AND 180),
|
|
CONSTRAINT "tasks_sources_check" CHECK (cardinality("source_urls") >= 1),
|
|
CONSTRAINT "tasks_confidence_check" CHECK ("confidence" IN ('high','medium'))
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE INDEX "change_requests_status_idx" ON "worldtodo"."change_requests" ("status","created_at");--> statement-breakpoint
|
|
CREATE INDEX "change_requests_task_idx" ON "worldtodo"."change_requests" ("task_id","status");--> statement-breakpoint
|
|
CREATE UNIQUE INDEX "tasks_slug_key" ON "worldtodo"."tasks" ("slug");--> statement-breakpoint
|
|
CREATE INDEX "tasks_search_idx" ON "worldtodo"."tasks" USING gin ("search");--> statement-breakpoint
|
|
CREATE INDEX "tasks_scale_status_idx" ON "worldtodo"."tasks" ("scale","status") WHERE deleted_at IS NULL;--> statement-breakpoint
|
|
ALTER TABLE "worldtodo"."change_requests" ADD CONSTRAINT "change_requests_task_id_tasks_id_fkey" FOREIGN KEY ("task_id") REFERENCES "worldtodo"."tasks"("id");--> statement-breakpoint
|
|
ALTER TABLE "worldtodo"."tasks" ADD CONSTRAINT "tasks_category_categories_slug_fkey" FOREIGN KEY ("category") REFERENCES "worldtodo"."categories"("slug");
|
|
--> statement-breakpoint
|
|
-- Hand-written below this line (not generated by drizzle-kit).
|
|
-- updated_at trigger on tasks, same as the other core tables.
|
|
DO $$
|
|
BEGIN
|
|
IF to_regprocedure('meta.touch_updated_at()') IS NOT NULL THEN
|
|
CREATE TRIGGER touch_updated_at BEFORE UPDATE ON worldtodo.tasks
|
|
FOR EACH ROW EXECUTE FUNCTION meta.touch_updated_at();
|
|
END IF;
|
|
END $$;
|
|
--> statement-breakpoint
|
|
-- Grants: app role read/write only (like ai and movies; no personal_ro: change requests may
|
|
-- carry a display name). Prod only, where migrations run as `personal`. Rows are seeded by
|
|
-- scripts/seed-worldtodo.ts on every deploy.
|
|
REVOKE ALL ON SCHEMA worldtodo FROM PUBLIC;
|
|
--> statement-breakpoint
|
|
DO $$
|
|
BEGIN
|
|
IF current_user = 'personal' AND EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'personal_app') THEN
|
|
GRANT USAGE ON SCHEMA worldtodo TO personal_app;
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA worldtodo TO personal_app;
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA worldtodo TO personal_app;
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA worldtodo
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO personal_app;
|
|
ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA worldtodo
|
|
GRANT USAGE, SELECT ON SEQUENCES TO personal_app;
|
|
END IF;
|
|
END $$;
|