All checks were successful
Deploy production / deploy (push) Successful in 2m48s
139 lines
6.7 KiB
SQL
139 lines
6.7 KiB
SQL
-- Trigram search for group/item names (trusted extension; the database owner may create it).
|
|
CREATE EXTENSION IF NOT EXISTS pg_trgm WITH SCHEMA public;
|
|
--> statement-breakpoint
|
|
CREATE TABLE "groceries"."groups" (
|
|
"id" serial PRIMARY KEY,
|
|
"key" text,
|
|
"name" text NOT NULL,
|
|
"brand" text,
|
|
"category" text,
|
|
"unit" text,
|
|
"family_key" text,
|
|
"favorite" boolean DEFAULT false NOT NULL,
|
|
"manual" boolean DEFAULT false NOT NULL,
|
|
"merged_into_id" integer,
|
|
"notes" text,
|
|
"item_count" integer DEFAULT 0 NOT NULL,
|
|
"store_count" integer DEFAULT 0 NOT NULL,
|
|
"store_ids" integer[] DEFAULT '{}'::integer[] NOT NULL,
|
|
"min_unit_price_cents" integer,
|
|
"min_price_cents" integer,
|
|
"promo_count" integer DEFAULT 0 NOT NULL,
|
|
"image_url" text,
|
|
"search_text" text DEFAULT '' NOT NULL,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT "groups_name_check" CHECK (length(btrim("name")) > 0),
|
|
CONSTRAINT "groups_unit_check" CHECK ("unit" IS NULL OR "unit" IN ('piece','kg','l')),
|
|
CONSTRAINT "groups_not_self_merged" CHECK ("merged_into_id" IS NULL OR "merged_into_id" <> "id")
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "groceries"."import_runs" (
|
|
"id" serial PRIMARY KEY,
|
|
"store_id" integer NOT NULL,
|
|
"started_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"finished_at" timestamp with time zone,
|
|
"status" text DEFAULT 'running' NOT NULL,
|
|
"requests" integer DEFAULT 0 NOT NULL,
|
|
"fetched" integer DEFAULT 0 NOT NULL,
|
|
"inserted" integer DEFAULT 0 NOT NULL,
|
|
"price_changes" integer DEFAULT 0 NOT NULL,
|
|
"error" text
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "groceries"."item_prices" (
|
|
"id" serial PRIMARY KEY,
|
|
"item_id" integer NOT NULL,
|
|
"observed_on" date NOT NULL,
|
|
"price_cents" integer,
|
|
"promo_price_cents" integer,
|
|
"promo_label" text,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE TABLE "groceries"."items" (
|
|
"id" serial PRIMARY KEY,
|
|
"store_id" integer NOT NULL,
|
|
"store_product_id" text NOT NULL,
|
|
"name" text NOT NULL,
|
|
"brand" text,
|
|
"size_text" text,
|
|
"unit" text,
|
|
"unit_amount" numeric(12,4),
|
|
"unit_source" text,
|
|
"price_cents" integer,
|
|
"promo_price_cents" integer,
|
|
"promo_label" text,
|
|
"unit_price_cents" integer,
|
|
"promo_unit_price_cents" integer,
|
|
"category" text,
|
|
"category_path" text,
|
|
"gtin" text,
|
|
"gtin_checked_at" timestamp with time zone,
|
|
"image_url" text,
|
|
"url" text,
|
|
"available" boolean DEFAULT true NOT NULL,
|
|
"first_seen_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"last_seen_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"group_id" integer,
|
|
"match_key" text,
|
|
"match_method" text,
|
|
"match_confidence" real,
|
|
"match_locked" boolean DEFAULT false NOT NULL,
|
|
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
"updated_at" timestamp with time zone DEFAULT now() NOT NULL,
|
|
CONSTRAINT "items_unit_check" CHECK ("unit" IS NULL OR "unit" IN ('piece','kg','l')),
|
|
CONSTRAINT "items_price_check" CHECK ("price_cents" IS NULL OR "price_cents" >= 0)
|
|
);
|
|
--> statement-breakpoint
|
|
CREATE UNIQUE INDEX "groups_key_key" ON "groceries"."groups" ("key");--> statement-breakpoint
|
|
CREATE INDEX "groups_family_idx" ON "groceries"."groups" ("family_key");--> statement-breakpoint
|
|
CREATE INDEX "groups_category_idx" ON "groceries"."groups" ("category");--> statement-breakpoint
|
|
CREATE INDEX "groups_store_ids_idx" ON "groceries"."groups" USING gin ("store_ids");--> statement-breakpoint
|
|
CREATE INDEX "groups_search_trgm_idx" ON "groceries"."groups" USING gin ("search_text" gin_trgm_ops);--> statement-breakpoint
|
|
CREATE INDEX "groups_list_idx" ON "groceries"."groups" ("store_count","name");--> statement-breakpoint
|
|
CREATE INDEX "import_runs_store_idx" ON "groceries"."import_runs" ("store_id","started_at");--> statement-breakpoint
|
|
CREATE UNIQUE INDEX "item_prices_item_day_key" ON "groceries"."item_prices" ("item_id","observed_on");--> statement-breakpoint
|
|
CREATE UNIQUE INDEX "items_store_product_key" ON "groceries"."items" ("store_id","store_product_id");--> statement-breakpoint
|
|
CREATE INDEX "items_group_idx" ON "groceries"."items" ("group_id");--> statement-breakpoint
|
|
CREATE INDEX "items_gtin_idx" ON "groceries"."items" ("gtin");--> statement-breakpoint
|
|
CREATE INDEX "items_name_trgm_idx" ON "groceries"."items" USING gin ("name" gin_trgm_ops);--> statement-breakpoint
|
|
ALTER TABLE "groceries"."groups" ADD CONSTRAINT "groups_merged_into_id_groups_id_fkey" FOREIGN KEY ("merged_into_id") REFERENCES "groceries"."groups"("id") ON DELETE SET NULL;--> statement-breakpoint
|
|
ALTER TABLE "groceries"."import_runs" ADD CONSTRAINT "import_runs_store_id_stores_id_fkey" FOREIGN KEY ("store_id") REFERENCES "groceries"."stores"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
ALTER TABLE "groceries"."item_prices" ADD CONSTRAINT "item_prices_item_id_items_id_fkey" FOREIGN KEY ("item_id") REFERENCES "groceries"."items"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
ALTER TABLE "groceries"."items" ADD CONSTRAINT "items_store_id_stores_id_fkey" FOREIGN KEY ("store_id") REFERENCES "groceries"."stores"("id") ON DELETE CASCADE;--> statement-breakpoint
|
|
ALTER TABLE "groceries"."items" ADD CONSTRAINT "items_group_id_groups_id_fkey" FOREIGN KEY ("group_id") REFERENCES "groceries"."groups"("id") ON DELETE SET NULL;--> statement-breakpoint
|
|
DO $$
|
|
DECLARE t text;
|
|
BEGIN
|
|
IF to_regprocedure('meta.touch_updated_at()') IS NOT NULL THEN
|
|
FOREACH t IN ARRAY ARRAY['groups','items'] LOOP
|
|
EXECUTE format('CREATE TRIGGER touch_updated_at BEFORE UPDATE ON groceries.%I FOR EACH ROW EXECUTE FUNCTION meta.touch_updated_at()', t);
|
|
END LOOP;
|
|
END IF;
|
|
END $$;
|
|
--> statement-breakpoint
|
|
-- Private: app role only (default privileges from the first groceries migration also apply).
|
|
DO $$
|
|
BEGIN
|
|
IF current_user = 'personal' AND EXISTS (SELECT 1 FROM pg_roles WHERE rolname = 'personal_app') THEN
|
|
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA groceries TO personal_app;
|
|
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA groceries TO personal_app;
|
|
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 ('groceries','groups','groceries-groups','Grocery groups','core',true,false,1100),
|
|
('groceries','items','groceries-items','Grocery store items','core',true,false,1100),
|
|
('groceries','item_prices','groceries-item-prices','Grocery price history','core',false,false,1100),
|
|
('groceries','import_runs','groceries-import-runs','Grocery import runs','core',false,false,1100)
|
|
ON CONFLICT DO NOTHING;
|
|
IF has_function_privilege('meta.sync(integer)', 'EXECUTE') THEN
|
|
PERFORM meta.sync();
|
|
END IF;
|
|
END IF;
|
|
END $$;
|