CREATE SCHEMA "home"; --> statement-breakpoint CREATE TABLE "home"."links" ( "id" serial PRIMARY KEY, "title" text NOT NULL, "url" text NOT NULL, "section" text DEFAULT 'links' NOT NULL, "sort_order" integer DEFAULT 0 NOT NULL, "created_at" timestamp with time zone DEFAULT now() NOT NULL, "updated_at" timestamp with time zone DEFAULT now() NOT NULL, CONSTRAINT "links_title_check" CHECK (length(btrim("title")) between 1 and 80), CONSTRAINT "links_url_check" CHECK ("url" ~ '^https?://' and length("url") <= 2000) ); --> statement-breakpoint CREATE TABLE "home"."snapshots" ( "key" text PRIMARY KEY, "data" jsonb NOT NULL, "collected_at" timestamp with time zone DEFAULT now() NOT NULL ); --> statement-breakpoint CREATE INDEX "links_sort_idx" ON "home"."links" ("section","sort_order");--> statement-breakpoint -- Privacy: like tasks and health, the start page is the app role only (no personal_ro). -- Grants only run on prod, where migrations run as `personal`. REVOKE ALL ON SCHEMA home 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 home TO personal_app; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA home TO personal_app; GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA home TO personal_app; ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA home GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO personal_app; ALTER DEFAULT PRIVILEGES FOR ROLE personal IN SCHEMA home GRANT USAGE, SELECT ON SEQUENCES TO personal_app; END IF; -- The host-side collector (deploy/home-collector) may only upsert snapshots. The role is -- created out of band (it has a password); see deploy/home-collector/README.md. IF current_user = 'personal' AND EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'home_collector') THEN GRANT USAGE ON SCHEMA home TO home_collector; GRANT SELECT, INSERT, UPDATE ON home.snapshots TO home_collector; END IF; END $$; --> statement-breakpoint -- Starter links; edited inline on /start. INSERT INTO "home"."links" ("title", "url", "section", "sort_order") VALUES ('Gmail', 'https://mail.google.com/mail/u/0/', 'daily', 10), ('Calendar', 'https://calendar.google.com/', 'daily', 20), ('WhatsApp', 'https://web.whatsapp.com/', 'daily', 30), ('Discord', 'https://discord.com/app', 'daily', 40), ('X', 'https://x.com/home', 'daily', 50), ('YouTube', 'https://www.youtube.com/', 'daily', 60), ('Spotify', 'https://open.spotify.com/', 'daily', 70), ('Drive', 'https://drive.google.com/', 'daily', 80), ('Moneybird', 'https://moneybird.com/390525278507501515', 'money', 110), ('Revolut', 'https://app.revolut.com/', 'money', 120), ('Knab', 'https://www.knab.nl/', 'money', 130), ('Tikkie', 'https://www.tikkie.me/', 'money', 140), ('Gitea', 'https://git.stanrunge.dev/', 'dev', 210), ('GitHub', 'https://github.com/', 'dev', 220), ('Cloudflare', 'https://dash.cloudflare.com/', 'dev', 230), ('Tailscale', 'https://login.tailscale.com/admin/machines', 'dev', 240), ('Backblaze B2', 'https://secure.backblaze.com/b2_buckets.htm', 'dev', 250), ('1Password', 'https://my.1password.com/', 'dev', 260), ('osu!', 'https://osu.ppy.sh/', 'fun', 310), ('Twitch', 'https://www.twitch.tv/', 'fun', 320);