From 88115fd00e391507b493021c80fb15ed9348f0c8 Mon Sep 17 00:00:00 2001 From: Renaud Laborbe <150821561+BluegReeno@users.noreply.github.com> Date: Fri, 24 Jul 2026 16:02:28 +0200 Subject: [PATCH 1/3] Fix: CI migration smoke-test against ephemeral prod-schema Postgres (#65) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The provision_workspace_membership migration (PR #60) passed CI + 5-agent review yet failed at prod apply with 42P17 (INSERT trigger WHEN cannot reference OLD). DDL-only errors are invisible to Deno type-check + offline pytest; only a real Postgres catches them. Add a CI net before prod. Changes: - .github/workflows/ci.yml: new migration-smoke job with a postgres:17 service container (major pinned to prod's verified 17.4). Fails loud with the exact `supabase db dump` command when supabase/schema.snapshot.sql is absent, loads the snapshot, then applies the newest migration under supabase/migrations/ with psql -v ON_ERROR_STOP=1; non-zero exit fails the job. - docs/operations.md §1: snapshot refresh procedure, auth prerequisite, the GoTrue limitation, the version-pin rationale, and the newest-only scope decision. - README.md: repo-layout pointer (marked NOT YET COMMITTED) + Develop & deploy note. schema.snapshot.sql is intentionally NOT created — it needs an interactive `supabase db dump` as renaud@bluegreen.ai against shared prod. The job is expected RED until the human commits it; a silent pass is the failure mode this issue removes. Fixes #65 Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 52 ++++++++++++++++++++++++++++++++++++++++ README.md | 6 ++++- docs/operations.md | 37 ++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index de25a26..0708d88 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,3 +37,55 @@ jobs: - name: Run offline test suite run: uv run pytest tests/ -q -m "not live" + + migration-smoke: + name: Migration smoke-test + runs-on: ubuntu-latest + services: + postgres: + # Prod (zgkvbjqlvebttbnkklpo) runs PostgreSQL 17.4, verified via `select version()` + # on 2026-07-24 (see docs/operations.md §1). Pin the major only; re-verify with the + # same query and bump this if prod is ever upgraded. + image: postgres:17 + env: + POSTGRES_PASSWORD: postgres + POSTGRES_DB: postgres + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 5s + --health-timeout 5s + --health-retries 10 + env: + PGHOST: localhost + PGPORT: 5432 + PGUSER: postgres + PGPASSWORD: postgres + PGDATABASE: postgres + steps: + - uses: actions/checkout@v4 + + - name: Install postgresql-client + run: sudo apt-get update && sudo apt-get install -y --no-install-recommends postgresql-client + + - name: Require schema snapshot + run: | + set -euo pipefail + if [ ! -f supabase/schema.snapshot.sql ]; then + echo "::error::supabase/schema.snapshot.sql is missing — this job cannot smoke-test migrations without a prod schema baseline." + echo "Generate and commit it with:" + echo " supabase db dump --schema auth,public -f supabase/schema.snapshot.sql" + echo "(requires the Supabase CLI authenticated as renaud@bluegreen.ai against project zgkvbjqlvebttbnkklpo — see docs/operations.md §1)." + exit 1 + fi + + - name: Load prod schema snapshot + run: psql -v ON_ERROR_STOP=1 -f supabase/schema.snapshot.sql + + - name: Apply newest migration + run: | + set -euo pipefail + newest=$(ls supabase/migrations/*.sql | sort | tail -1) + echo "Applying $newest" + psql -v ON_ERROR_STOP=1 -f "$newest" diff --git a/README.md b/README.md index 9b7d564..257f48d 100644 --- a/README.md +++ b/README.md @@ -245,7 +245,8 @@ hal/ │ ├── functions/ │ │ ├── hal-mcp/index.ts # THE runtime — 26 MCP tools │ │ └── edifice-map-generator/ # IGN 2D map generation (pg_net trigger) -│ └── migrations/ # halcrm_* migrations (source of truth) +│ ├── migrations/ # halcrm_* migrations (source of truth) +│ └── schema.snapshot.sql # prod schema baseline for CI (see docs/operations.md) — NOT YET COMMITTED │ ├── scripts/ │ ├── generate_devis.py # /hal devis — docxtpl DOCX renderer (live) @@ -281,6 +282,9 @@ make deploy # supabase functions deploy hal-mcp --n # dashboard SQL editor. The Supabase CLI push is NOT safe on this shared # partial-ledger DB; see docs/operations.md §1 for the runbook. +# CI migration smoke-test (migration-smoke job) replays the newest migration against +# a Postgres seeded with supabase/schema.snapshot.sql — see docs/operations.md §1. + # Edge Function tests (Deno) deno test supabase/functions/hal-mcp/ diff --git a/docs/operations.md b/docs/operations.md index 03f3ac7..42b5428 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -28,6 +28,43 @@ The DB is **shared** (edifice + hal). Each repo holds only its own migration fil any change to `provision_workspace_membership()`, `trg_provision_workspace_membership`, `workspace_members`, or `halcrm_workspaces.company_id`. +### CI migration smoke-test (`migration-smoke` job, issue #65) + +Every PR/push replays the newest `supabase/migrations/*.sql` file against an ephemeral +`postgres:17` service container seeded with a committed snapshot of prod's real schema +(`supabase/schema.snapshot.sql`). This is the only net that catches DDL-only failures — bad +FK/CHECK/type, missing column/table, index/constraint violations, and the `42P17` class from +PR #60 — *before* a human runs `apply_migration` against the shared prod project. Neither +`deno check` nor the offline `pytest` suite exercises a real Postgres. + +- **Refresh the snapshot** whenever a migration lands (slow-moving — a re-dump alongside each + DDL migration is enough): + ```bash + supabase db dump --schema auth,public -f supabase/schema.snapshot.sql + ``` + Requires the CLI authenticated as `renaud@bluegreen.ai` against `zgkvbjqlvebttbnkklpo` (same + auth prerequisite as §2 deploy below). +- **Honest limitation**: the snapshot captures `auth` + `public` tables, constraints, and + triggers as they exist at dump time, but **not** Supabase's internal GoTrue auth-service + migration history. Inconsequential for DDL on `auth.users` (this repo's only case so far); + would matter only for a future migration depending on GoTrue's own internal migration state. +- **Postgres version**: the service container pins major version `17` (prod verified `17.4` + via `select version()` on 2026-07-24) — bump if prod is upgraded, and re-verify with the + same query first. +- **Scope decision — newest migration only, not the full ledger**: the job applies just the + single newest file under `supabase/migrations/` (highest timestamp) on top of the snapshot, + not the whole directory. The snapshot already contains the cumulative DDL effect of every + migration applied to prod so far; replaying the full local ledger on top would re-run DDL + that already exists in the snapshot (`CREATE TABLE`/`ADD CONSTRAINT` etc., which would fail + `already exists`) for reasons unrelated to the migration actually under review. This holds + only as long as the snapshot is refreshed alongside each DDL migration — if that discipline + lapses, "newest" silently drifts from "not yet in the snapshot" and the job's coverage + narrows without any visible error. +- **Known gap as of 2026-07-24**: `supabase/schema.snapshot.sql` does not exist in this repo + yet — the `migration-smoke` job fails on every PR until it is dumped and committed (command + above). This is intentional (issue #65): a missing snapshot must fail loud, never skip or + pass silently. + --- ## 2. Edge-function deploy From 4c6977f313ce7268de70877dea8c963d9a6ea62c Mon Sep 17 00:00:00 2001 From: Renaud Laborbe <150821561+BluegReeno@users.noreply.github.com> Date: Fri, 24 Jul 2026 17:11:40 +0200 Subject: [PATCH 2/3] fix(ci): make migration-smoke actually runnable against prod's schema MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The job as first written could not have worked. Each fault below was found by running it locally against a real prod dump, not by review: - postgres:17 has neither the cluster roles the snapshot assigns ownership to (supabase_admin, supabase_auth_admin, anon, authenticated, service_role, dashboard_user) nor the extensions its types come from. It died on line 18 with `role "supabase_admin" does not exist`, and later on `type "public.geometry" does not exist`. Use public.ecr.aws/supabase/postgres at prod's exact version (17.4.1.052) — the same image `supabase db dump` runs — where postgis, vector and pg_net all resolve to prod's exact versions. - Connect as supabase_admin. In that image `postgres` is restricted and the snapshot's first statements fail with `permission denied for schema auth`. - Drop the image's own auth schema first. It ships a GoTrue-initialised `auth` older than prod's, and the snapshot uses CREATE TABLE IF NOT EXISTS — so it silently skipped auth.users, then failed on `column "is_sso_user" ... does not exist`. - Install prod's public-schema extensions before loading (tests/fixtures/ ci_extensions.sql). The dump carries the contents of auth/public but not the CREATE EXTENSION statements behind their types. - Apply what the branch ADDS, not "the newest migration". The snapshot is a dump of prod, so it already contains every migration prod has applied — replaying the newest one fails `relation ... already exists` on every single run. Diff against the base branch instead, which is why checkout needs fetch-depth: 0. - Dump auth,public,private, not auth,public: objects in public reference prod's private schema, and the load aborts on `schema "private" does not exist`. Verified locally on 2026-07-24 against a real dump: extensions + snapshot load clean (62 public tables, 23 auth tables, 8 halcrm tables, 23 triggers); a branch adding no migration passes; and a trigger re-introducing the pre-fix `WHEN (OLD...)` form fails with `INSERT trigger's WHEN condition cannot reference OLD values` — the exact error that reached prod through PR #60. The snapshot itself is still not committed, now for a new reason: a prod dump of `public` carries a hardcoded service_role key out of edifice's edifice_trigger_map_generation. Tracked separately in edifice; #65 stays open. Refs #65 Co-Authored-By: Claude Opus 4.8 --- .github/workflows/ci.yml | 63 +++++++++++++++++----- README.md | 2 +- docs/operations.md | 93 ++++++++++++++++++++------------ tests/fixtures/ci_extensions.sql | 27 ++++++++++ 4 files changed, 137 insertions(+), 48 deletions(-) create mode 100644 tests/fixtures/ci_extensions.sql diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0708d88..aded47c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,28 +43,41 @@ jobs: runs-on: ubuntu-latest services: postgres: - # Prod (zgkvbjqlvebttbnkklpo) runs PostgreSQL 17.4, verified via `select version()` - # on 2026-07-24 (see docs/operations.md §1). Pin the major only; re-verify with the - # same query and bump this if prod is ever upgraded. - image: postgres:17 + # NOT the vanilla postgres image. Prod is a Supabase-managed cluster, so a plain + # postgres:17 lacks both the cluster roles the snapshot assigns ownership to + # (supabase_admin, supabase_auth_admin, anon, authenticated, service_role, + # dashboard_user) and the extensions its types come from — loading the snapshot there + # dies on line 18 with `role "supabase_admin" does not exist`. + # + # This tag is prod's EXACT version (17.4.1.052), not just its major: verified via + # `select version()` on zgkvbjqlvebttbnkklpo on 2026-07-24, and it is the same image + # `supabase db dump` runs to produce the snapshot. postgis/vector/pg_net all resolve + # to prod's exact versions here too. Re-verify and bump if prod is upgraded. + image: public.ecr.aws/supabase/postgres:17.4.1.052 env: POSTGRES_PASSWORD: postgres - POSTGRES_DB: postgres ports: - 5432:5432 options: >- - --health-cmd pg_isready + --health-cmd "pg_isready -U postgres" --health-interval 5s --health-timeout 5s - --health-retries 10 + --health-retries 20 env: PGHOST: localhost PGPORT: 5432 - PGUSER: postgres + # supabase_admin, not postgres: in this image `postgres` is a restricted role and the + # snapshot's first statements (ALTER SCHEMA auth OWNER TO ...) fail with + # `permission denied for schema auth`. supabase_admin is the actual superuser. + PGUSER: supabase_admin PGPASSWORD: postgres PGDATABASE: postgres steps: - uses: actions/checkout@v4 + with: + # Full history: the apply step diffs against the base branch to find which + # migrations this PR adds. A shallow clone has no merge base. + fetch-depth: 0 - name: Install postgresql-client run: sudo apt-get update && sudo apt-get install -y --no-install-recommends postgresql-client @@ -75,17 +88,41 @@ jobs: if [ ! -f supabase/schema.snapshot.sql ]; then echo "::error::supabase/schema.snapshot.sql is missing — this job cannot smoke-test migrations without a prod schema baseline." echo "Generate and commit it with:" - echo " supabase db dump --schema auth,public -f supabase/schema.snapshot.sql" + echo " supabase db dump --schema auth,public,private -f supabase/schema.snapshot.sql" echo "(requires the Supabase CLI authenticated as renaud@bluegreen.ai against project zgkvbjqlvebttbnkklpo — see docs/operations.md §1)." exit 1 fi + - name: Drop the image's own auth schema + # The image ships a GoTrue-initialised `auth` schema older than prod's. The snapshot + # uses CREATE TABLE IF NOT EXISTS, so it would silently skip auth.users and then fail + # on `column "is_sso_user" ... does not exist`. Dropping it first makes the snapshot + # the sole source of the auth schema under test. + run: psql -v ON_ERROR_STOP=1 -c "DROP SCHEMA IF EXISTS auth CASCADE;" + + - name: Install prod extensions + run: psql -v ON_ERROR_STOP=1 -f tests/fixtures/ci_extensions.sql + - name: Load prod schema snapshot run: psql -v ON_ERROR_STOP=1 -f supabase/schema.snapshot.sql - - name: Apply newest migration + - name: Apply migrations added by this PR + # NOT "the newest migration". The snapshot is a dump of prod, so it already contains + # every migration prod has applied — including the newest one on main. Replaying that + # fails with `relation ... already exists` on every run, red for a reason unrelated to + # any defect. What is genuinely unapplied is what THIS branch adds on top of the base. run: | set -euo pipefail - newest=$(ls supabase/migrations/*.sql | sort | tail -1) - echo "Applying $newest" - psql -v ON_ERROR_STOP=1 -f "$newest" + base_ref="${{ github.base_ref || github.event.repository.default_branch }}" + git fetch -q origin "$base_ref" + new=$(git diff --name-only "origin/$base_ref...HEAD" -- 'supabase/migrations/*.sql') + if [ -z "$new" ]; then + echo "No migrations added on this branch — nothing to smoke-test." + exit 0 + fi + echo "Migrations to apply:"; echo "$new" + for f in $new; do + echo "::group::Applying $f" + psql -v ON_ERROR_STOP=1 -f "$f" + echo "::endgroup::" + done diff --git a/README.md b/README.md index 257f48d..563f43f 100644 --- a/README.md +++ b/README.md @@ -246,7 +246,7 @@ hal/ │ │ ├── hal-mcp/index.ts # THE runtime — 26 MCP tools │ │ └── edifice-map-generator/ # IGN 2D map generation (pg_net trigger) │ ├── migrations/ # halcrm_* migrations (source of truth) -│ └── schema.snapshot.sql # prod schema baseline for CI (see docs/operations.md) — NOT YET COMMITTED +│ └── schema.snapshot.sql # prod schema baseline for CI (see docs/operations.md) │ ├── scripts/ │ ├── generate_devis.py # /hal devis — docxtpl DOCX renderer (live) diff --git a/docs/operations.md b/docs/operations.md index 42b5428..f7f43f1 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -30,40 +30,65 @@ The DB is **shared** (edifice + hal). Each repo holds only its own migration fil ### CI migration smoke-test (`migration-smoke` job, issue #65) -Every PR/push replays the newest `supabase/migrations/*.sql` file against an ephemeral -`postgres:17` service container seeded with a committed snapshot of prod's real schema -(`supabase/schema.snapshot.sql`). This is the only net that catches DDL-only failures — bad -FK/CHECK/type, missing column/table, index/constraint violations, and the `42P17` class from -PR #60 — *before* a human runs `apply_migration` against the shared prod project. Neither -`deno check` nor the offline `pytest` suite exercises a real Postgres. - -- **Refresh the snapshot** whenever a migration lands (slow-moving — a re-dump alongside each - DDL migration is enough): - ```bash - supabase db dump --schema auth,public -f supabase/schema.snapshot.sql - ``` - Requires the CLI authenticated as `renaud@bluegreen.ai` against `zgkvbjqlvebttbnkklpo` (same - auth prerequisite as §2 deploy below). -- **Honest limitation**: the snapshot captures `auth` + `public` tables, constraints, and - triggers as they exist at dump time, but **not** Supabase's internal GoTrue auth-service - migration history. Inconsequential for DDL on `auth.users` (this repo's only case so far); - would matter only for a future migration depending on GoTrue's own internal migration state. -- **Postgres version**: the service container pins major version `17` (prod verified `17.4` - via `select version()` on 2026-07-24) — bump if prod is upgraded, and re-verify with the - same query first. -- **Scope decision — newest migration only, not the full ledger**: the job applies just the - single newest file under `supabase/migrations/` (highest timestamp) on top of the snapshot, - not the whole directory. The snapshot already contains the cumulative DDL effect of every - migration applied to prod so far; replaying the full local ledger on top would re-run DDL - that already exists in the snapshot (`CREATE TABLE`/`ADD CONSTRAINT` etc., which would fail - `already exists`) for reasons unrelated to the migration actually under review. This holds - only as long as the snapshot is refreshed alongside each DDL migration — if that discipline - lapses, "newest" silently drifts from "not yet in the snapshot" and the job's coverage - narrows without any visible error. -- **Known gap as of 2026-07-24**: `supabase/schema.snapshot.sql` does not exist in this repo - yet — the `migration-smoke` job fails on every PR until it is dumped and committed (command - above). This is intentional (issue #65): a missing snapshot must fail loud, never skip or - pass silently. +Every PR applies **the migrations that branch adds** against an ephemeral service container +seeded with a committed snapshot of prod's real schema (`supabase/schema.snapshot.sql`). This +is the only net that catches DDL-only failures — bad FK/CHECK/type, missing column/table, +index/constraint violations, and the `42P17` class from PR #60 — *before* a human runs +`apply_migration` against the shared prod project. Neither `deno check` nor the offline +`pytest` suite exercises a real Postgres. + +**Refresh the snapshot** whenever a migration lands (slow-moving — a re-dump alongside each +DDL migration is enough): + +```bash +supabase db dump --schema auth,public,private -f supabase/schema.snapshot.sql +``` + +Requires the CLI authenticated as `renaud@bluegreen.ai` against `zgkvbjqlvebttbnkklpo` (same +auth prerequisite as §2 deploy below) **and a running Docker daemon** — the CLI shells out to +a `pg_dump` inside the `supabase/postgres` image rather than using the local one, because +`pg_dump` refuses to dump a server newer than itself. + +Four things about this job are load-bearing and were each established by making the job fail +first. Change them only against evidence: + +- **`--schema auth,public,private`, not `auth,public`.** The issue prescribed two schemas; the + snapshot then aborts at line 4852 on `schema "private" does not exist`, because objects in + `public` reference prod's `private` schema. If a future migration reaches into yet another + schema, this list grows — the symptom is always a `schema "X" does not exist` mid-load. +- **The service container is `public.ecr.aws/supabase/postgres:`, not + `postgres:17`.** A vanilla image has neither the cluster roles the snapshot assigns + ownership to (`supabase_admin`, `supabase_auth_admin`, `anon`, `authenticated`, + `service_role`, `dashboard_user`) nor the extensions its types come from; it dies on line 18 + with `role "supabase_admin" does not exist`. The Supabase image carries prod's exact + postgis/vector/pg_net versions. Connect as **`supabase_admin`** — `postgres` is restricted + here and hits `permission denied for schema auth`. +- **The job drops the image's own `auth` schema first.** The image ships a GoTrue-initialised + `auth` older than prod's, and the snapshot uses `CREATE TABLE IF NOT EXISTS` — so it would + silently skip `auth.users` and fail later on `column "is_sso_user" ... does not exist`. + Dropping it makes the snapshot the only source of the schema under test. +- **It applies what the branch adds, never "the newest migration".** The snapshot is a dump of + prod, so it already contains every migration prod has applied — replaying the newest one + fails `relation ... already exists` on every single run. The job diffs against the base + branch (`git diff origin/...HEAD -- 'supabase/migrations/*.sql'`), which is why + `actions/checkout` needs `fetch-depth: 0`. A branch adding no migration has nothing to + smoke-test and passes. + +**Honest limitations:** + +- The snapshot captures schema objects at dump time but **not** Supabase's internal GoTrue + auth-service migration history. Inconsequential for DDL on `auth.users` (this repo's only + case so far); would matter only for a migration depending on GoTrue's own internal state. +- Coverage depends on the refresh discipline above. A stale snapshot means the job validates + new migrations against an old prod — still useful, but no longer the guarantee it looks like. +- On a push directly to the default branch the diff is empty, so the job passes without + applying anything. The net is a **pull-request** net by design. + +**Verified negative (issue #65 acceptance criterion), run 2026-07-24**: with the snapshot +loaded, a trigger re-introducing the pre-fix form — +`AFTER INSERT ... FOR EACH ROW WHEN (OLD.company_id IS DISTINCT FROM NEW.company_id)` — fails +with `INSERT trigger's WHEN condition cannot reference OLD values` and a non-zero exit. That is +the exact error that reached prod through PR #60. --- diff --git a/tests/fixtures/ci_extensions.sql b/tests/fixtures/ci_extensions.sql new file mode 100644 index 0000000..a36df95 --- /dev/null +++ b/tests/fixtures/ci_extensions.sql @@ -0,0 +1,27 @@ +-- Extensions the `migration-smoke` CI job must install before loading the schema snapshot +-- (issue #65). +-- +-- `supabase/schema.snapshot.sql` is produced by `supabase db dump --schema auth,public`, which +-- dumps the CONTENTS of those two schemas but not the `CREATE EXTENSION` statements behind the +-- types they use: extensions live in `extensions` / `topology` / `vault`, outside the dumped +-- scope. The snapshot therefore references `public.geometry` (PostGIS) with nothing that +-- creates it — it aborts at line 227 on `type "public.geometry" does not exist` unless the +-- extensions exist first. +-- +-- Only the extensions prod installs INTO `public` are listed here, plus postgis_topology which +-- PostGIS pulls into its own schema. The rest of prod's set (pgcrypto, uuid-ossp, +-- pg_stat_statements, supabase_vault, plpgsql) ships pre-installed in the +-- supabase/postgres image and must not be re-created. +-- +-- Verified against prod (zgkvbjqlvebttbnkklpo) on 2026-07-24 via +-- select e.extname, n.nspname, e.extversion from pg_extension e +-- join pg_namespace n on n.oid = e.extnamespace; +-- Every version below matches the image's default_version exactly (postgis 3.3.7, +-- vector 0.8.0, pg_net 0.14.0). If a future `CREATE EXTENSION` here resolves to a different +-- version than prod reports, the smoke-test is no longer testing prod's shape — re-check both +-- sides rather than pinning blindly. + +CREATE EXTENSION IF NOT EXISTS postgis WITH SCHEMA public; +CREATE EXTENSION IF NOT EXISTS postgis_topology; +CREATE EXTENSION IF NOT EXISTS vector WITH SCHEMA public; +CREATE EXTENSION IF NOT EXISTS pg_net WITH SCHEMA public; From 1e7fc0bb8196dcd4010152dd031444b7b842f37f Mon Sep 17 00:00:00 2001 From: Renaud Laborbe <150821561+BluegReeno@users.noreply.github.com> Date: Fri, 24 Jul 2026 18:21:09 +0200 Subject: [PATCH 3/3] =?UTF-8?q?feat(ci):=20commit=20the=20prod=20schema=20?= =?UTF-8?q?snapshot=20=E2=80=94=20migration-smoke=20can=20now=20run?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unblocks the last acceptance criterion of #65. The snapshot could not be committed until today: a dump of `public` carried a hardcoded service_role key out of edifice's edifice_trigger_map_generation(). That is now fixed on the edifice side (key moved to Vault, then replaced by a dedicated narrow-purpose secret, and both compromised keys rotated out), so a plain dump is finally clean. `supabase db dump --schema auth,public,private` against zgkvbjqlvebttbnkklpo, 2026-07-24. Audited before committing, not assumed: - no sb_secret_/sb_publishable_/eyJhbGci/SCRAM-SHA/PASSWORD literal, no private key block; the only long quoted strings are identifiers (`edifice_map_trigger_secret` is the NAME of a Vault entry, not its value); - edifice_trigger_map_generation() reads `vault.decrypted_secrets` — no credential in the body; - 0 COPY/INSERT statements: schema only, no data. Load verified against public.ecr.aws/supabase/postgres:17.4.1.052 — extensions + snapshot apply clean (62 public tables, 23 auth, 8 halcrm, 23 user triggers). Co-Authored-By: Claude Opus 4.8 --- supabase/schema.snapshot.sql | 6553 ++++++++++++++++++++++++++++++++++ 1 file changed, 6553 insertions(+) create mode 100644 supabase/schema.snapshot.sql diff --git a/supabase/schema.snapshot.sql b/supabase/schema.snapshot.sql new file mode 100644 index 0000000..4433cfc --- /dev/null +++ b/supabase/schema.snapshot.sql @@ -0,0 +1,6553 @@ + + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + + +CREATE SCHEMA IF NOT EXISTS "auth"; + + +ALTER SCHEMA "auth" OWNER TO "supabase_admin"; + + +CREATE SCHEMA IF NOT EXISTS "public"; + + +ALTER SCHEMA "public" OWNER TO "pg_database_owner"; + + +COMMENT ON SCHEMA "public" IS 'standard public schema'; + + + +CREATE SCHEMA IF NOT EXISTS "private"; + + +ALTER SCHEMA "private" OWNER TO "postgres"; + + +CREATE TYPE "auth"."aal_level" AS ENUM ( + 'aal1', + 'aal2', + 'aal3' +); + + +ALTER TYPE "auth"."aal_level" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "auth"."code_challenge_method" AS ENUM ( + 's256', + 'plain' +); + + +ALTER TYPE "auth"."code_challenge_method" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "auth"."factor_status" AS ENUM ( + 'unverified', + 'verified' +); + + +ALTER TYPE "auth"."factor_status" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "auth"."factor_type" AS ENUM ( + 'totp', + 'webauthn', + 'phone' +); + + +ALTER TYPE "auth"."factor_type" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "auth"."oauth_authorization_status" AS ENUM ( + 'pending', + 'approved', + 'denied', + 'expired' +); + + +ALTER TYPE "auth"."oauth_authorization_status" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "auth"."oauth_client_type" AS ENUM ( + 'public', + 'confidential' +); + + +ALTER TYPE "auth"."oauth_client_type" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "auth"."oauth_registration_type" AS ENUM ( + 'dynamic', + 'manual' +); + + +ALTER TYPE "auth"."oauth_registration_type" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "auth"."oauth_response_type" AS ENUM ( + 'code' +); + + +ALTER TYPE "auth"."oauth_response_type" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "auth"."one_time_token_type" AS ENUM ( + 'confirmation_token', + 'reauthentication_token', + 'recovery_token', + 'email_change_token_new', + 'email_change_token_current', + 'phone_change_token' +); + + +ALTER TYPE "auth"."one_time_token_type" OWNER TO "supabase_auth_admin"; + + +CREATE TYPE "public"."edifice_photo_category" AS ENUM ( + 'exterior', + 'context', + 'disorder', + 'plan', + 'aerial' +); + + +ALTER TYPE "public"."edifice_photo_category" OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "auth"."email"() RETURNS "text" + LANGUAGE "sql" STABLE + AS $$ + select + coalesce( + nullif(current_setting('request.jwt.claim.email', true), ''), + (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'email') + )::text +$$; + + +ALTER FUNCTION "auth"."email"() OWNER TO "supabase_auth_admin"; + + +COMMENT ON FUNCTION "auth"."email"() IS 'Deprecated. Use auth.jwt() -> ''email'' instead.'; + + + +CREATE OR REPLACE FUNCTION "auth"."jwt"() RETURNS "jsonb" + LANGUAGE "sql" STABLE + AS $$ + select + coalesce( + nullif(current_setting('request.jwt.claim', true), ''), + nullif(current_setting('request.jwt.claims', true), '') + )::jsonb +$$; + + +ALTER FUNCTION "auth"."jwt"() OWNER TO "supabase_auth_admin"; + + +CREATE OR REPLACE FUNCTION "auth"."role"() RETURNS "text" + LANGUAGE "sql" STABLE + AS $$ + select + coalesce( + nullif(current_setting('request.jwt.claim.role', true), ''), + (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'role') + )::text +$$; + + +ALTER FUNCTION "auth"."role"() OWNER TO "supabase_auth_admin"; + + +COMMENT ON FUNCTION "auth"."role"() IS 'Deprecated. Use auth.jwt() -> ''role'' instead.'; + + + +CREATE OR REPLACE FUNCTION "auth"."uid"() RETURNS "uuid" + LANGUAGE "sql" STABLE + AS $$ + select + coalesce( + nullif(current_setting('request.jwt.claim.sub', true), ''), + (nullif(current_setting('request.jwt.claims', true), '')::jsonb ->> 'sub') + )::uuid +$$; + + +ALTER FUNCTION "auth"."uid"() OWNER TO "supabase_auth_admin"; + + +COMMENT ON FUNCTION "auth"."uid"() IS 'Deprecated. Use auth.jwt() -> ''sub'' instead.'; + + + +CREATE OR REPLACE FUNCTION "private"."get_edifice_user_organization_id"() RETURNS "uuid" + LANGUAGE "sql" SECURITY DEFINER + SET "search_path" TO '' + AS $$ + SELECT organization_id FROM public.edifice_profiles WHERE id = (SELECT auth.uid()) +$$; + + +ALTER FUNCTION "private"."get_edifice_user_organization_id"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "private"."sync_photo_note_id"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + IF NEW.note_id IS NOT NULL THEN + UPDATE public.edifice_photos + SET note_id = NEW.note_id + WHERE id = NEW.photo_id + AND note_id IS NULL; + END IF; + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "private"."sync_photo_note_id"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "private"."update_updated_at_column"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + NEW.updated_at = NOW(); + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "private"."update_updated_at_column"() OWNER TO "postgres"; + +SET default_tablespace = ''; + +SET default_table_access_method = "heap"; + + +CREATE TABLE IF NOT EXISTS "public"."edifice_buildings" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "name" "text" NOT NULL, + "address" "text", + "latitude" numeric, + "longitude" numeric, + "building_type" "text", + "construction_year" integer, + "floor_count" integer, + "surface_area" numeric, + "ign_data" "jsonb", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"(), + "description" "text", + "building_3d_image_url" "text", + "x_lambert93" double precision, + "y_lambert93" double precision, + "building_geometry" "public"."geometry"(MultiPolygon,4326), + "rnb_id" character varying(12), + "bdnb_id" character varying(30), + "building_2d_map_url" "text", + "organization_ids" "uuid"[] NOT NULL, + CONSTRAINT "edifice_buildings_building_type_check" CHECK (("building_type" = ANY (ARRAY['residence'::"text", 'house'::"text", 'apartment_building'::"text", 'commercial'::"text", 'industrial'::"text", 'warehouse'::"text", 'mixed_use'::"text", 'other'::"text"]))), + CONSTRAINT "edifice_buildings_organization_ids_nonempty" CHECK (("cardinality"("organization_ids") > 0)) +); + + +ALTER TABLE "public"."edifice_buildings" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_buildings" IS 'Reusable building records across diagnostic projects'; + + + +COMMENT ON COLUMN "public"."edifice_buildings"."description" IS 'Structural and physical description of the building (immutable)'; + + + +COMMENT ON COLUMN "public"."edifice_buildings"."building_3d_image_url" IS 'URL to 3D building screenshot captured from iTowns viewer (stored in edifice-photos bucket)'; + + + +COMMENT ON COLUMN "public"."edifice_buildings"."x_lambert93" IS 'X coordinate in Lambert 93 (EPSG:2154), auto-computed from longitude via PostGIS'; + + + +COMMENT ON COLUMN "public"."edifice_buildings"."y_lambert93" IS 'Y coordinate in Lambert 93 (EPSG:2154), auto-computed from latitude via PostGIS'; + + + +COMMENT ON COLUMN "public"."edifice_buildings"."building_geometry" IS 'Building footprint polygon from RNB (Référentiel National des Bâtiments). Stored in WGS84 (EPSG:4326).'; + + + +COMMENT ON COLUMN "public"."edifice_buildings"."rnb_id" IS 'RNB unique building identifier (12 alphanumeric chars). Links to national building registry.'; + + + +COMMENT ON COLUMN "public"."edifice_buildings"."bdnb_id" IS 'BDNB building group identifier. Links to CSTB national building database.'; + + + +COMMENT ON COLUMN "public"."edifice_buildings"."building_2d_map_url" IS 'URL to 2D map screenshot captured from OpenLayers IGN viewer (stored in edifice-photos bucket)'; + + + +CREATE OR REPLACE FUNCTION "public"."add_org_to_building"("p_building_id" "uuid", "p_org_id" "uuid") RETURNS "public"."edifice_buildings" + LANGUAGE "plpgsql" SECURITY DEFINER + SET "search_path" TO 'public' + AS $$ +DECLARE + result public.edifice_buildings; +BEGIN + UPDATE public.edifice_buildings + SET organization_ids = ( + SELECT ARRAY(SELECT DISTINCT unnest(organization_ids || p_org_id)) + ) + WHERE id = p_building_id + RETURNING * INTO result; + + IF NOT FOUND THEN + RAISE EXCEPTION 'Building % not found', p_building_id; + END IF; + + RETURN result; +END; +$$; + + +ALTER FUNCTION "public"."add_org_to_building"("p_building_id" "uuid", "p_org_id" "uuid") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."add_org_to_building"("p_building_id" "uuid", "p_org_id" "uuid") IS 'Adds an org to building.organization_ids (idempotent, deduped).'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_projects" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "building_id" "uuid", + "created_by" "uuid", + "name" "text" NOT NULL, + "reference_number" "text", + "mission_context" "text", + "status" "text" DEFAULT 'active'::"text", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"(), + "type" "text" DEFAULT 'diagnostic'::"text", + "organization_ids" "uuid"[] NOT NULL, + "is_export_locked" boolean DEFAULT false NOT NULL, + CONSTRAINT "edifice_projects_organization_ids_nonempty" CHECK (("cardinality"("organization_ids") > 0)), + CONSTRAINT "edifice_projects_status_check" CHECK (("status" = ANY (ARRAY['active'::"text", 'completed'::"text", 'archived'::"text"]))), + CONSTRAINT "edifice_projects_type_check" CHECK (("type" = ANY (ARRAY['diagnostic'::"text", 'suivi_chantier'::"text", 'reception'::"text", 'devis'::"text", 'autre'::"text"]))) +); + + +ALTER TABLE "public"."edifice_projects" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_projects" IS 'Diagnostic missions linked to buildings'; + + + +COMMENT ON COLUMN "public"."edifice_projects"."is_export_locked" IS 'When true, pre-existing complex_case_enrichment JWTs can no longer write to this project.'; + + + +CREATE OR REPLACE FUNCTION "public"."add_org_to_project"("p_project_id" "uuid", "p_org_id" "uuid") RETURNS "public"."edifice_projects" + LANGUAGE "plpgsql" SECURITY DEFINER + SET "search_path" TO 'public' + AS $$ +DECLARE + result public.edifice_projects; +BEGIN + UPDATE public.edifice_projects + SET organization_ids = ( + SELECT ARRAY(SELECT DISTINCT unnest(organization_ids || p_org_id)) + ) + WHERE id = p_project_id + RETURNING * INTO result; + + IF NOT FOUND THEN + RAISE EXCEPTION 'Project % not found', p_project_id; + END IF; + + RETURN result; +END; +$$; + + +ALTER FUNCTION "public"."add_org_to_project"("p_project_id" "uuid", "p_org_id" "uuid") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."add_org_to_project"("p_project_id" "uuid", "p_org_id" "uuid") IS 'Adds an org to project.organization_ids (idempotent, deduped). B2 trigger auto-extends the parent building.'; + + + +CREATE OR REPLACE FUNCTION "public"."auto_generate_ref_id"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + IF NEW.ref_id IS NULL THEN + -- Get facade_id from intersections table via photo_id + SELECT i.facade_id INTO NEW.ref_id + FROM intersections i + WHERE i.photo_id = NEW.photo_id + LIMIT 1; + + -- Generate the ref_id using the facade_id + IF NEW.ref_id IS NOT NULL THEN + NEW.ref_id := generate_ref_id(NEW.ref_id, 'disorder'); + ELSE + -- Fallback if no intersection found + NEW.ref_id := generate_ref_id('unknown', 'disorder'); + END IF; + END IF; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."auto_generate_ref_id"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."auto_populate_report_notes"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + INSERT INTO public.edifice_report_notes (report_id, note_id, display_order) + SELECT + NEW.id, + d.id, + COALESCE(d.display_order, 0) + FROM public.edifice_notes d + LEFT JOIN public.edifice_annotations a ON a.note_id = d.id + WHERE d.project_id = NEW.project_id + OR a.photo_id IN ( + SELECT p.id FROM public.edifice_photos p WHERE p.project_id = NEW.project_id + ) + GROUP BY d.id + ON CONFLICT (report_id, note_id) DO NOTHING; + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."auto_populate_report_notes"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."betc_update_updated_at"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN NEW.updated_at = now(); RETURN NEW; END; +$$; + + +ALTER FUNCTION "public"."betc_update_updated_at"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."bwc_chunk_counts"("doc_ids" "uuid"[]) RETURNS TABLE("document_id" "uuid", "chunk_count" bigint) + LANGUAGE "sql" STABLE + AS $$ + SELECT c.document_id, COUNT(*) as chunk_count + FROM bwc_chunks c + WHERE c.document_id = ANY(doc_ids) + GROUP BY c.document_id; +$$; + + +ALTER FUNCTION "public"."bwc_chunk_counts"("doc_ids" "uuid"[]) OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."bwc_compare_metrics_across_sources"("p_metric_name" "text", "p_year" integer, "p_scenario" "text" DEFAULT NULL::"text") RETURNS TABLE("source_name" "text", "source_type" "text", "value" numeric, "unit" "text", "is_official_target" boolean, "confidence" "text", "deviation_from_official" numeric) + LANGUAGE "sql" STABLE + AS $$ +WITH official AS ( + SELECT value as official_value + FROM bwc_strategy_metrics + WHERE metric_name = p_metric_name + AND year = p_year + AND (p_scenario IS NULL OR scenario = p_scenario) + AND is_official_target = TRUE + LIMIT 1 +) +SELECT + m.source_name, + m.source_type, + m.value, + m.unit, + m.is_official_target, + m.confidence, + CASE + WHEN o.official_value IS NOT NULL AND o.official_value != 0 + THEN ROUND(((m.value - o.official_value) / o.official_value * 100)::numeric, 1) + ELSE NULL + END as deviation_from_official +FROM bwc_strategy_metrics m +LEFT JOIN official o ON TRUE +WHERE m.metric_name = p_metric_name + AND m.year = p_year + AND (p_scenario IS NULL OR m.scenario = p_scenario) +ORDER BY m.is_official_target DESC, m.source_type ASC; +$$; + + +ALTER FUNCTION "public"."bwc_compare_metrics_across_sources"("p_metric_name" "text", "p_year" integer, "p_scenario" "text") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."bwc_compare_metrics_across_sources"("p_metric_name" "text", "p_year" integer, "p_scenario" "text") IS 'Compare same metric across different sources to detect discrepancies'; + + + +CREATE OR REPLACE FUNCTION "public"."bwc_get_strategy_metrics"("p_metric_name" "text" DEFAULT NULL::"text", "p_year" integer DEFAULT NULL::integer, "p_scenario" "text" DEFAULT NULL::"text", "p_source_name" "text" DEFAULT NULL::"text", "p_category" "text" DEFAULT NULL::"text", "p_sector" "text" DEFAULT NULL::"text", "p_energy_source" "text" DEFAULT NULL::"text", "p_limit" integer DEFAULT 50) RETURNS TABLE("id" "uuid", "source_name" "text", "source_type" "text", "metric_category" "text", "metric_name" "text", "scenario" "text", "sector" "text", "energy_source" "text", "year" integer, "period" "text", "value" numeric, "unit" "text", "value_min" numeric, "value_max" numeric, "is_official_target" boolean, "confidence" "text", "source_text" "text") + LANGUAGE "sql" STABLE + AS $$ +SELECT + m.id, + m.source_name, + m.source_type, + m.metric_category, + m.metric_name, + m.scenario, + m.sector, + m.energy_source, + m.year, + m.period, + m.value, + m.unit, + m.value_min, + m.value_max, + m.is_official_target, + m.confidence, + m.source_text +FROM bwc_strategy_metrics m +WHERE + (p_metric_name IS NULL OR m.metric_name ILIKE '%' || p_metric_name || '%') + AND (p_year IS NULL OR m.year = p_year) + AND (p_scenario IS NULL OR m.scenario = p_scenario) + AND (p_source_name IS NULL OR m.source_name = p_source_name) + AND (p_category IS NULL OR m.metric_category = p_category) + AND (p_sector IS NULL OR m.sector = p_sector) + AND (p_energy_source IS NULL OR m.energy_source = p_energy_source) +ORDER BY + m.is_official_target DESC, -- Official targets first + m.year ASC, + m.source_name ASC +LIMIT p_limit; +$$; + + +ALTER FUNCTION "public"."bwc_get_strategy_metrics"("p_metric_name" "text", "p_year" integer, "p_scenario" "text", "p_source_name" "text", "p_category" "text", "p_sector" "text", "p_energy_source" "text", "p_limit" integer) OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."bwc_get_strategy_metrics"("p_metric_name" "text", "p_year" integer, "p_scenario" "text", "p_source_name" "text", "p_category" "text", "p_sector" "text", "p_energy_source" "text", "p_limit" integer) IS 'Query metrics with flexible filters'; + + + +CREATE OR REPLACE FUNCTION "public"."bwc_hybrid_search"("query_text" "text", "query_embedding" "public"."vector", "match_count" integer DEFAULT 10, "fulltext_weight" double precision DEFAULT 1.0, "semantic_weight" double precision DEFAULT 1.0, "rrf_k" integer DEFAULT 50, "language" "text" DEFAULT 'en'::"text", "filter_tender_id" "text" DEFAULT NULL::"text", "filter_country" "text" DEFAULT NULL::"text", "filter_document_type" "text" DEFAULT NULL::"text") RETURNS TABLE("chunk_id" "uuid", "document_id" "uuid", "content" "text", "document_title" "text", "document_source" "text", "document_language" "text", "document_country" "text", "document_tender_id" "text", "document_type" "text", "chunk_metadata" "jsonb", "score" double precision) + LANGUAGE "sql" STABLE + AS $$ +WITH full_text AS ( + SELECT + c.id, + ROW_NUMBER() OVER( + ORDER BY ts_rank_cd( + CASE WHEN language = 'fr' THEN c.fts_fr ELSE c.fts_en END, + websearch_to_tsquery( + (CASE WHEN language = 'fr' THEN 'french' ELSE 'english' END)::regconfig, + query_text + ) + ) DESC + ) AS rank_ix + FROM bwc_chunks c + JOIN bwc_documents d ON c.document_id = d.id + WHERE + CASE WHEN language = 'fr' + THEN c.fts_fr @@ websearch_to_tsquery('french'::regconfig, query_text) + ELSE c.fts_en @@ websearch_to_tsquery('english'::regconfig, query_text) + END + AND (filter_tender_id IS NULL OR d.tender_id = filter_tender_id) + AND (filter_country IS NULL OR d.country = filter_country) + AND (filter_document_type IS NULL OR d.document_type = filter_document_type) + LIMIT LEAST(match_count, 30) * 2 +), +semantic AS ( + SELECT + c.id, + ROW_NUMBER() OVER(ORDER BY c.embedding <=> query_embedding) AS rank_ix + FROM bwc_chunks c + JOIN bwc_documents d ON c.document_id = d.id + WHERE + (filter_tender_id IS NULL OR d.tender_id = filter_tender_id) + AND (filter_country IS NULL OR d.country = filter_country) + AND (filter_document_type IS NULL OR d.document_type = filter_document_type) + ORDER BY c.embedding <=> query_embedding + LIMIT LEAST(match_count, 30) * 2 +) +SELECT + c.id AS chunk_id, + c.document_id, + c.content, + d.title AS document_title, + d.source AS document_source, + d.language AS document_language, + d.country AS document_country, + d.tender_id AS document_tender_id, + d.document_type AS document_type, + c.metadata AS chunk_metadata, + ( + COALESCE(1.0 / (rrf_k + full_text.rank_ix), 0.0) * fulltext_weight + + COALESCE(1.0 / (rrf_k + semantic.rank_ix), 0.0) * semantic_weight + ) AS score +FROM full_text +FULL OUTER JOIN semantic ON full_text.id = semantic.id +JOIN bwc_chunks c ON COALESCE(full_text.id, semantic.id) = c.id +JOIN bwc_documents d ON c.document_id = d.id +ORDER BY score DESC +LIMIT match_count; +$$; + + +ALTER FUNCTION "public"."bwc_hybrid_search"("query_text" "text", "query_embedding" "public"."vector", "match_count" integer, "fulltext_weight" double precision, "semantic_weight" double precision, "rrf_k" integer, "language" "text", "filter_tender_id" "text", "filter_country" "text", "filter_document_type" "text") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."bwc_hybrid_search"("query_text" "text", "query_embedding" "public"."vector", "match_count" integer, "fulltext_weight" double precision, "semantic_weight" double precision, "rrf_k" integer, "language" "text", "filter_tender_id" "text", "filter_country" "text", "filter_document_type" "text") IS 'Hybrid search with RRF, supports tender/country/document_type filters'; + + + +CREATE OR REPLACE FUNCTION "public"."bwc_semantic_search"("query_embedding" "public"."vector", "match_count" integer DEFAULT 10) RETURNS TABLE("chunk_id" "uuid", "document_id" "uuid", "content" "text", "document_title" "text", "document_source" "text", "similarity" double precision) + LANGUAGE "sql" STABLE + AS $$ +SELECT + c.id AS chunk_id, + c.document_id, + c.content, + d.title AS document_title, + d.source AS document_source, + 1 - (c.embedding <=> query_embedding) AS similarity +FROM bwc_chunks c +JOIN bwc_documents d ON c.document_id = d.id +ORDER BY c.embedding <=> query_embedding +LIMIT match_count; +$$; + + +ALTER FUNCTION "public"."bwc_semantic_search"("query_embedding" "public"."vector", "match_count" integer) OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."bwc_usage_stats"("p_client_id" "uuid" DEFAULT NULL::"uuid", "p_days" integer DEFAULT 30) RETURNS TABLE("event_type" "text", "event_count" bigint, "unique_users" bigint) + LANGUAGE "plpgsql" + AS $$ +BEGIN + RETURN QUERY + SELECT + ul.event_type, + COUNT(*)::BIGINT AS event_count, + COUNT(DISTINCT ul.user_id)::BIGINT AS unique_users + FROM bwc_usage_logs ul + WHERE ul.created_at > NOW() - (p_days || ' days')::INTERVAL + AND (p_client_id IS NULL OR ul.client_id = p_client_id) + GROUP BY ul.event_type + ORDER BY event_count DESC; +END; +$$; + + +ALTER FUNCTION "public"."bwc_usage_stats"("p_client_id" "uuid", "p_days" integer) OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."compute_lambert93_coordinates"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + -- Only compute if both latitude and longitude are provided + IF NEW.latitude IS NOT NULL AND NEW.longitude IS NOT NULL THEN + -- Convert WGS84 (EPSG:4326) to Lambert 93 (EPSG:2154) + NEW.x_lambert93 := ST_X( + ST_Transform( + ST_SetSRID(ST_MakePoint(NEW.longitude, NEW.latitude), 4326), + 2154 + ) + ); + NEW.y_lambert93 := ST_Y( + ST_Transform( + ST_SetSRID(ST_MakePoint(NEW.longitude, NEW.latitude), 4326), + 2154 + ) + ); + ELSE + -- Clear Lambert 93 if WGS84 is cleared + NEW.x_lambert93 := NULL; + NEW.y_lambert93 := NULL; + END IF; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."compute_lambert93_coordinates"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."edifice_trigger_map_generation"() RETURNS "trigger" + LANGUAGE "plpgsql" SECURITY DEFINER + SET "search_path" TO 'public', 'pg_temp' + AS $$ +DECLARE + _trigger_secret TEXT; + _function_url TEXT := 'https://zgkvbjqlvebttbnkklpo.supabase.co/functions/v1/edifice-map-generator'; +BEGIN + IF NEW.latitude IS NULL OR NEW.longitude IS NULL THEN + RETURN NEW; + END IF; + IF NEW.building_2d_map_url IS NOT NULL THEN + RETURN NEW; + END IF; + IF TG_OP = 'UPDATE' AND + OLD.latitude IS NOT DISTINCT FROM NEW.latitude AND + OLD.longitude IS NOT DISTINCT FROM NEW.longitude THEN + RETURN NEW; + END IF; + + -- Read late: the early returns above are the common path, and this is the only + -- statement that touches Vault. + SELECT decrypted_secret + INTO _trigger_secret + FROM vault.decrypted_secrets + WHERE name = 'edifice_map_trigger_secret'; + + IF _trigger_secret IS NULL OR _trigger_secret = '' THEN + RAISE WARNING 'edifice_trigger_map_generation: vault secret "edifice_map_trigger_secret" is missing or empty — skipping map generation for building %', NEW.id; + RETURN NEW; + END IF; + + PERFORM net.http_post( + url := _function_url, + body := jsonb_build_object( + 'building_id', NEW.id, + 'latitude', NEW.latitude, + 'longitude', NEW.longitude + ), + headers := jsonb_build_object( + 'Content-Type', 'application/json', + 'Authorization', 'Bearer ' || _trigger_secret + ) + ); + + RETURN NEW; +EXCEPTION WHEN OTHERS THEN + RAISE WARNING 'edifice_trigger_map_generation failed: %', SQLERRM; + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."edifice_trigger_map_generation"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."fn_building_orgs_must_cover_projects"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +DECLARE + needed UUID[]; +BEGIN + SELECT ARRAY( + SELECT DISTINCT unnest(p.organization_ids) + FROM public.edifice_projects p + WHERE p.building_id = NEW.id + ) INTO needed; + + IF array_length(needed, 1) > 0 AND NOT (needed <@ NEW.organization_ids) THEN + RAISE EXCEPTION + 'Cannot remove organizations from building % — projects still reference them (required: %, new: %)', + NEW.id, needed, NEW.organization_ids; + END IF; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."fn_building_orgs_must_cover_projects"() OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."fn_building_orgs_must_cover_projects"() IS 'B3 — rejects shrinking building.organization_ids below the union still required by child projects.'; + + + +CREATE OR REPLACE FUNCTION "public"."fn_project_extend_building_orgs"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + IF NEW.building_id IS NULL THEN + RETURN NEW; + END IF; + + UPDATE public.edifice_buildings + SET organization_ids = ( + SELECT ARRAY(SELECT DISTINCT unnest(organization_ids || NEW.organization_ids)) + ) + WHERE id = NEW.building_id + AND NOT (NEW.organization_ids <@ organization_ids); + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."fn_project_extend_building_orgs"() OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."fn_project_extend_building_orgs"() IS 'B2 — keeps project.organization_ids subset of building.organization_ids by extending the parent building when a project introduces a new org.'; + + + +CREATE OR REPLACE FUNCTION "public"."generate_ref_id"("facade_id_param" character varying, "disorder_type" character varying) RETURNS character varying + LANGUAGE "plpgsql" + AS $$ +DECLARE + building_part VARCHAR; + facade_part VARCHAR; + timestamp_part VARCHAR; +BEGIN + -- Extraire building et facade de facade_id (ex: "jb_clement_A_f5" -> "A", "F5") + IF facade_id_param LIKE '%_%_%_f%' THEN + -- Format: residence_building_facade + building_part := UPPER(split_part(facade_id_param, '_', 3)); + facade_part := UPPER(replace(split_part(facade_id_param, '_', 4), 'f', 'F')); + ELSE + -- Format simple ou autre + building_part := 'X'; + facade_part := 'F0'; + END IF; + + -- Timestamp court (MMDD) + timestamp_part := to_char(NOW(), 'MMDD'); + + -- Format final: A_F5_0710 + RETURN building_part || '_' || facade_part || '_' || timestamp_part; +END; +$$; + + +ALTER FUNCTION "public"."generate_ref_id"("facade_id_param" character varying, "disorder_type" character varying) OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."get_buildings_with_geometry"("p_residence_uuid" "uuid") RETURNS TABLE("id" "uuid", "fid" integer, "cleabs" character varying, "building_id" character varying, "building_name" character varying, "usage_1" character varying, "nombre_de_logement" integer, "nombre_d_etages" integer, "hauteur" double precision, "geometry_wkt" "text") + LANGUAGE "sql" STABLE + AS $$ + SELECT + id, + fid, + cleabs, + building_id, + building_name, + usage_1, + nombre_de_logement, + nombre_d_etages, + hauteur, + ST_AsText(geometry) as geometry_wkt + FROM buildings + WHERE residence_id = p_residence_uuid; +$$; + + +ALTER FUNCTION "public"."get_buildings_with_geometry"("p_residence_uuid" "uuid") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."get_buildings_with_geometry"("p_residence_uuid" "uuid") IS 'Get buildings with PostGIS geometry converted to WKT for a residence'; + + + +CREATE OR REPLACE FUNCTION "public"."get_facades_with_geometry"("p_building_uuids" "uuid"[]) RETURNS TABLE("id" "uuid", "building_id" "uuid", "fid" integer, "facade_id" character varying, "building_name" character varying, "facade_name" character varying, "normal_x" double precision, "normal_y" double precision, "normal_z" double precision, "center_x" double precision, "center_y" double precision, "center_z" double precision, "geometry_wkt" "text") + LANGUAGE "sql" STABLE + AS $$ + SELECT + id, + building_id, + fid, + facade_id, + building_name, + facade_name, + normal_x, + normal_y, + normal_z, + center_x, + center_y, + center_z, + ST_AsText(geometry) as geometry_wkt + FROM facades + WHERE building_id = ANY(p_building_uuids); +$$; + + +ALTER FUNCTION "public"."get_facades_with_geometry"("p_building_uuids" "uuid"[]) OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."get_facades_with_geometry"("p_building_uuids" "uuid"[]) IS 'Get facades with PostGIS geometry converted to WKT for given building UUIDs'; + + + +CREATE OR REPLACE FUNCTION "public"."get_kind_stages"("p_workspace_slug" "text", "p_kind" "text" DEFAULT 'default'::"text") RETURNS "jsonb" + LANGUAGE "plpgsql" + AS $$ +DECLARE + v_kind_stages JSONB; + v_key TEXT; +BEGIN + SELECT kind_stages INTO v_kind_stages + FROM halcrm_workspaces + WHERE workspace_slug = p_workspace_slug; + + IF v_kind_stages IS NULL THEN + RETURN '{"active":[],"terminal":[]}'::JSONB; + END IF; + + v_key := CASE + WHEN p_kind IS NOT NULL AND v_kind_stages ? p_kind THEN p_kind + ELSE 'default' + END; + + RETURN COALESCE( + v_kind_stages -> v_key, + '{"active":[],"terminal":[]}'::JSONB + ); +END; +$$; + + +ALTER FUNCTION "public"."get_kind_stages"("p_workspace_slug" "text", "p_kind" "text") OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."get_project_by_id"("p_workspace_slug" "text", "p_project_id" "text") RETURNS json + LANGUAGE "plpgsql" + AS $$ +BEGIN + RETURN ( + SELECT row_to_json(r) + FROM ( + SELECT + p.id, + p.workspace_slug, + p.name, + p.stage, + p.kind, + p.amount_ht, + p.currency, + p.location, + p.description, + p.due_date, + p.project_ref, + p.edifice_building_id, + p.edifice_mission_id, + p.closed_at, + p.created_at, + p.updated_at, + jsonb_build_object('name', c.name, 'bg_id', c.bg_id) AS company, + jsonb_build_object('name', ct.name, 'email', ct.email) AS contact + FROM halcrm_projects p + LEFT JOIN halcrm_companies c ON c.id = p.company_id + LEFT JOIN halcrm_contacts ct ON ct.id = p.primary_contact_id + WHERE p.workspace_slug = p_workspace_slug + AND p.id = p_project_id + ) r + ); +END; +$$; + + +ALTER FUNCTION "public"."get_project_by_id"("p_workspace_slug" "text", "p_project_id" "text") OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text" DEFAULT NULL::"text", "p_stage" "text" DEFAULT NULL::"text", "p_limit" integer DEFAULT 50) RETURNS SETOF json + LANGUAGE "plpgsql" + AS $$ +BEGIN + RETURN QUERY + SELECT row_to_json(r) + FROM ( + SELECT + p.id, + p.workspace_slug, + p.name, + p.stage, + p.kind, + p.amount_ht, + p.currency, + p.location, + p.description, + p.due_date, + p.project_ref, + p.edifice_building_id, + p.edifice_mission_id, + p.closed_at, + p.created_at, + p.updated_at, + jsonb_build_object('name', c.name, 'bg_id', c.bg_id) AS company, + jsonb_build_object('name', ct.name, 'email', ct.email) AS contact + FROM halcrm_projects p + LEFT JOIN halcrm_companies c ON c.id = p.company_id + LEFT JOIN halcrm_contacts ct ON ct.id = p.primary_contact_id + WHERE p.workspace_slug = p_workspace_slug + AND (p_kind IS NULL OR p.kind = p_kind) + AND (p_stage IS NULL OR p.stage = p_stage) + ORDER BY p.created_at DESC + LIMIT p_limit + ) r; +END; +$$; + + +ALTER FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text", "p_stage" "text", "p_limit" integer) OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text" DEFAULT NULL::"text", "p_stage" "text" DEFAULT NULL::"text", "p_limit" integer DEFAULT 50, "p_tags" "text"[] DEFAULT NULL::"text"[]) RETURNS SETOF json + LANGUAGE "plpgsql" + AS $$ +BEGIN + RETURN QUERY + SELECT row_to_json(r) + FROM ( + SELECT + p.id, p.workspace_slug, p.name, p.stage, p.kind, p.tags, + p.amount_ht, p.currency, p.location, p.description, p.due_date, + p.project_ref, p.edifice_building_id, p.edifice_mission_id, + p.closed_at, p.created_at, p.updated_at, + jsonb_build_object('name', c.name, 'bg_id', c.bg_id) AS company, + jsonb_build_object('name', ct.name, 'email', ct.email) AS contact + FROM halcrm_projects p + LEFT JOIN halcrm_companies c ON c.id = p.company_id + LEFT JOIN halcrm_contacts ct ON ct.id = p.primary_contact_id + WHERE p.workspace_slug = p_workspace_slug + AND (p_kind IS NULL OR p.kind = p_kind) + AND (p_stage IS NULL OR p.stage = p_stage) + AND (p_tags IS NULL OR p.tags && p_tags) + ORDER BY p.created_at DESC + LIMIT p_limit + ) r; +END; +$$; + + +ALTER FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text", "p_stage" "text", "p_limit" integer, "p_tags" "text"[]) OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."get_project_stats"("project_id_param" "uuid") RETURNS TABLE("total_residences" integer, "total_visits" integer, "total_photos" integer, "total_disorders" integer, "last_visit_date" "date") + LANGUAGE "plpgsql" + AS $$ +BEGIN + RETURN QUERY + SELECT + COUNT(DISTINCT r.id)::INTEGER as total_residences, + COUNT(DISTINCT v.id)::INTEGER as total_visits, + COUNT(DISTINCT p.id)::INTEGER as total_photos, + COUNT(DISTINCT d.id)::INTEGER as total_disorders, + MAX(v.visit_date) as last_visit_date + FROM residences r + LEFT JOIN visits v ON r.id = v.residence_id + LEFT JOIN photos p ON v.id = p.visit_id + LEFT JOIN disorders d ON p.id = d.photo_id + WHERE r.project_id = project_id_param; +END; +$$; + + +ALTER FUNCTION "public"."get_project_stats"("project_id_param" "uuid") OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."get_tasks_with_assignee"("p_workspace_slug" "text", "p_project_id" "text" DEFAULT NULL::"text", "p_assignee_user_id" "uuid" DEFAULT NULL::"uuid", "p_status" "text" DEFAULT NULL::"text", "p_sprint_id" "text" DEFAULT NULL::"text", "p_tags" "text"[] DEFAULT NULL::"text"[]) RETURNS TABLE("id" "text", "workspace_slug" "text", "project_id" "text", "title" "text", "description" "text", "assignee_user_id" "uuid", "assignee_email" "text", "external_ref" "text", "tags" "text"[], "due_date" "date", "priority" "text", "status" "text", "sprint_id" "text", "created_at" timestamp with time zone, "updated_at" timestamp with time zone) + LANGUAGE "sql" STABLE SECURITY DEFINER + SET "search_path" TO '' + AS $$ + SELECT t.id, t.workspace_slug, t.project_id, t.title, t.description, + t.assignee_user_id, u.email::TEXT AS assignee_email, + t.external_ref, t.tags, t.due_date, t.priority, t.status, t.sprint_id, + t.created_at, t.updated_at + FROM public.halcrm_tasks t + LEFT JOIN auth.users u ON u.id = t.assignee_user_id + WHERE t.workspace_slug = p_workspace_slug + AND (p_project_id IS NULL OR t.project_id = p_project_id) + AND (p_assignee_user_id IS NULL OR t.assignee_user_id = p_assignee_user_id) + AND (p_status IS NULL OR t.status = p_status) + AND (p_sprint_id IS NULL OR t.sprint_id = p_sprint_id) + AND (p_tags IS NULL OR t.tags && p_tags) + ORDER BY t.created_at DESC + LIMIT 100; +$$; + + +ALTER FUNCTION "public"."get_tasks_with_assignee"("p_workspace_slug" "text", "p_project_id" "text", "p_assignee_user_id" "uuid", "p_status" "text", "p_sprint_id" "text", "p_tags" "text"[]) OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."handle_new_user"() RETURNS "trigger" + LANGUAGE "plpgsql" SECURITY DEFINER + AS $$ +BEGIN + INSERT INTO public.edifice_profiles (id, email, full_name, organization_id) + VALUES ( + NEW.id, + NEW.email, + COALESCE(NEW.raw_user_meta_data->>'full_name', ''), + NULLIF(NEW.raw_app_meta_data->>'company_id', '')::uuid + ); + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."handle_new_user"() OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."handle_new_user"() IS 'Automatically creates an edifice_profile when a new user signs up'; + + + +CREATE OR REPLACE FUNCTION "public"."provision_edifice_org"() RETURNS "trigger" + LANGUAGE "plpgsql" SECURITY DEFINER + SET "search_path" TO 'public' + AS $$ +DECLARE + v_company_id uuid := NULLIF(NEW.raw_app_meta_data->>'company_id', '')::uuid; +BEGIN + IF TG_OP = 'UPDATE' + AND OLD.raw_app_meta_data->>'company_id' + IS NOT DISTINCT FROM NEW.raw_app_meta_data->>'company_id' THEN + RETURN NEW; + END IF; + + UPDATE public.edifice_profiles + SET organization_id = v_company_id + WHERE id = NEW.id + AND organization_id IS NULL; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."provision_edifice_org"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."provision_workspace_membership"() RETURNS "trigger" + LANGUAGE "plpgsql" SECURITY DEFINER + SET "search_path" TO 'public' + AS $$ +DECLARE + v_company_id uuid := NULLIF(NEW.raw_app_meta_data->>'company_id','')::uuid; +BEGIN + -- Skip when company_id did not actually change on an UPDATE (routine updates that + -- also touch raw_app_meta_data must not re-provision). On INSERT there is no OLD, + -- so always proceed. This guard lives here, not in the trigger WHEN clause, because + -- an INSERT-OR-UPDATE trigger's WHEN cannot reference OLD (Postgres 42P17). + IF TG_OP = 'UPDATE' + AND OLD.raw_app_meta_data->>'company_id' + IS NOT DISTINCT FROM NEW.raw_app_meta_data->>'company_id' THEN + RETURN NEW; + END IF; + + INSERT INTO workspace_members (workspace_slug, user_id, role, is_default) + SELECT w.workspace_slug, + NEW.id, + 'member', + NOT EXISTS ( + SELECT 1 FROM workspace_members m + WHERE m.user_id = NEW.id AND m.is_default + ) + FROM halcrm_workspaces w + WHERE w.company_id = v_company_id + ON CONFLICT (workspace_slug, user_id) DO NOTHING; + + IF NOT EXISTS ( + SELECT 1 FROM halcrm_workspaces w WHERE w.company_id = v_company_id + ) THEN + RAISE WARNING 'provision_workspace_membership: no halcrm_workspaces row for company_id % (user %) — no membership created', + v_company_id, NEW.id; + END IF; + + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."provision_workspace_membership"() OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."remove_org_from_building"("p_building_id" "uuid", "p_org_id" "uuid") RETURNS "public"."edifice_buildings" + LANGUAGE "plpgsql" SECURITY DEFINER + SET "search_path" TO 'public' + AS $$ +DECLARE + result public.edifice_buildings; +BEGIN + UPDATE public.edifice_buildings + SET organization_ids = array_remove(organization_ids, p_org_id) + WHERE id = p_building_id + RETURNING * INTO result; + + IF NOT FOUND THEN + RAISE EXCEPTION 'Building % not found', p_building_id; + END IF; + + RETURN result; +END; +$$; + + +ALTER FUNCTION "public"."remove_org_from_building"("p_building_id" "uuid", "p_org_id" "uuid") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."remove_org_from_building"("p_building_id" "uuid", "p_org_id" "uuid") IS 'Removes an org from building.organization_ids (idempotent — no-op if org not present). B3 trigger rejects removal if a child project still references the org.'; + + + +CREATE OR REPLACE FUNCTION "public"."remove_org_from_project"("p_project_id" "uuid", "p_org_id" "uuid") RETURNS "public"."edifice_projects" + LANGUAGE "plpgsql" SECURITY DEFINER + SET "search_path" TO 'public' + AS $$ +DECLARE + result public.edifice_projects; +BEGIN + UPDATE public.edifice_projects + SET organization_ids = array_remove(organization_ids, p_org_id) + WHERE id = p_project_id + RETURNING * INTO result; + + IF NOT FOUND THEN + RAISE EXCEPTION 'Project % not found', p_project_id; + END IF; + + RETURN result; +END; +$$; + + +ALTER FUNCTION "public"."remove_org_from_project"("p_project_id" "uuid", "p_org_id" "uuid") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."remove_org_from_project"("p_project_id" "uuid", "p_org_id" "uuid") IS 'Removes an org from project.organization_ids (idempotent — no-op if org not present). Does not guard against removing the last org — callers must ensure at least one org remains.'; + + + +CREATE OR REPLACE FUNCTION "public"."resolve_workspace_member_by_email"("p_workspace_slug" "text", "p_email" "text") RETURNS "uuid" + LANGUAGE "sql" STABLE SECURITY DEFINER + SET "search_path" TO '' + AS $$ + SELECT wm.user_id + FROM public.workspace_members wm + JOIN auth.users u ON u.id = wm.user_id + WHERE wm.workspace_slug = p_workspace_slug AND u.email = p_email + LIMIT 1; +$$; + + +ALTER FUNCTION "public"."resolve_workspace_member_by_email"("p_workspace_slug" "text", "p_email" "text") OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."shared_check_app_access"("p_user_id" "uuid", "p_app_id" "text") RETURNS boolean + LANGUAGE "sql" STABLE SECURITY DEFINER + AS $$ + SELECT EXISTS ( + SELECT 1 FROM shared_app_access + WHERE user_id = p_user_id AND app_id = p_app_id + ); +$$; + + +ALTER FUNCTION "public"."shared_check_app_access"("p_user_id" "uuid", "p_app_id" "text") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."shared_check_app_access"("p_user_id" "uuid", "p_app_id" "text") IS 'Vérifie si un user a accès à une app. Appelé par les backends.'; + + + +CREATE OR REPLACE FUNCTION "public"."shared_grant_access"("p_email" "text", "p_app_id" "text") RETURNS "text" + LANGUAGE "plpgsql" SECURITY DEFINER + AS $$ +DECLARE + v_user_id UUID; + v_apps TEXT[]; +BEGIN + -- Trouver le user + SELECT id INTO v_user_id FROM auth.users WHERE email = LOWER(TRIM(p_email)); + + IF v_user_id IS NULL THEN + RETURN '❌ User not found: ' || p_email; + END IF; + + -- Vérifier que l'app_id est valide + IF p_app_id NOT IN ('bluewind', 'edifice') THEN + RETURN '❌ Invalid app_id: ' || p_app_id || '. Use: bluewind, edifice'; + END IF; + + -- Insérer l'accès + INSERT INTO shared_app_access (user_id, app_id) + VALUES (v_user_id, p_app_id) + ON CONFLICT (user_id, app_id) DO NOTHING; + + -- Récupérer la liste des apps pour confirmation + SELECT array_agg(app_id ORDER BY app_id) INTO v_apps + FROM shared_app_access WHERE user_id = v_user_id; + + RETURN '✅ ' || p_email || ' → ' || array_to_string(v_apps, ', '); +END; +$$; + + +ALTER FUNCTION "public"."shared_grant_access"("p_email" "text", "p_app_id" "text") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."shared_grant_access"("p_email" "text", "p_app_id" "text") IS 'Donne accès à une app. Usage: SELECT shared_grant_access(''email'', ''bluewind'')'; + + + +CREATE OR REPLACE FUNCTION "public"."shared_grant_all_access"("p_email" "text") RETURNS "text" + LANGUAGE "plpgsql" SECURITY DEFINER + AS $$ +DECLARE + v_result TEXT; +BEGIN + SELECT shared_grant_access(p_email, 'bluewind') INTO v_result; + IF v_result LIKE '❌%' THEN + RETURN v_result; + END IF; + + RETURN shared_grant_access(p_email, 'edifice'); +END; +$$; + + +ALTER FUNCTION "public"."shared_grant_all_access"("p_email" "text") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."shared_grant_all_access"("p_email" "text") IS 'Donne accès à toutes les apps. Usage: SELECT shared_grant_all_access(''email'')'; + + + +CREATE OR REPLACE FUNCTION "public"."shared_revoke_access"("p_email" "text", "p_app_id" "text") RETURNS "text" + LANGUAGE "plpgsql" SECURITY DEFINER + AS $$ +DECLARE + v_user_id UUID; + v_deleted INT; + v_apps TEXT[]; +BEGIN + -- Trouver le user + SELECT id INTO v_user_id FROM auth.users WHERE email = LOWER(TRIM(p_email)); + + IF v_user_id IS NULL THEN + RETURN '❌ User not found: ' || p_email; + END IF; + + -- Supprimer l'accès + DELETE FROM shared_app_access + WHERE user_id = v_user_id AND app_id = p_app_id; + + GET DIAGNOSTICS v_deleted = ROW_COUNT; + + IF v_deleted = 0 THEN + RETURN '⚠️ ' || p_email || ' did not have access to ' || p_app_id; + END IF; + + -- Récupérer la liste des apps restantes + SELECT array_agg(app_id ORDER BY app_id) INTO v_apps + FROM shared_app_access WHERE user_id = v_user_id; + + RETURN '🗑️ ' || p_email || ' ✕ ' || p_app_id || ' | Remaining: ' || COALESCE(array_to_string(v_apps, ', '), 'none'); +END; +$$; + + +ALTER FUNCTION "public"."shared_revoke_access"("p_email" "text", "p_app_id" "text") OWNER TO "postgres"; + + +COMMENT ON FUNCTION "public"."shared_revoke_access"("p_email" "text", "p_app_id" "text") IS 'Retire accès à une app. Usage: SELECT shared_revoke_access(''email'', ''bluewind'')'; + + + +CREATE OR REPLACE FUNCTION "public"."update_building_geometry_from_geojson"("p_building_id" "uuid", "p_geojson" "text") RETURNS "void" + LANGUAGE "plpgsql" SECURITY DEFINER + AS $$ +BEGIN + UPDATE edifice_buildings + SET building_geometry = ST_SetSRID(ST_GeomFromGeoJSON(p_geojson), 4326) + WHERE id = p_building_id; +END; +$$; + + +ALTER FUNCTION "public"."update_building_geometry_from_geojson"("p_building_id" "uuid", "p_geojson" "text") OWNER TO "postgres"; + + +CREATE OR REPLACE FUNCTION "public"."update_updated_at_column"() RETURNS "trigger" + LANGUAGE "plpgsql" + AS $$ +BEGIN + NEW.updated_at = now(); + RETURN NEW; +END; +$$; + + +ALTER FUNCTION "public"."update_updated_at_column"() OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "auth"."audit_log_entries" ( + "instance_id" "uuid", + "id" "uuid" NOT NULL, + "payload" json, + "created_at" timestamp with time zone, + "ip_address" character varying(64) DEFAULT ''::character varying NOT NULL +); + + +ALTER TABLE "auth"."audit_log_entries" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."audit_log_entries" IS 'Auth: Audit trail for user actions.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."custom_oauth_providers" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "provider_type" "text" NOT NULL, + "identifier" "text" NOT NULL, + "name" "text" NOT NULL, + "client_id" "text" NOT NULL, + "client_secret" "text" NOT NULL, + "acceptable_client_ids" "text"[] DEFAULT '{}'::"text"[] NOT NULL, + "scopes" "text"[] DEFAULT '{}'::"text"[] NOT NULL, + "pkce_enabled" boolean DEFAULT true NOT NULL, + "attribute_mapping" "jsonb" DEFAULT '{}'::"jsonb" NOT NULL, + "authorization_params" "jsonb" DEFAULT '{}'::"jsonb" NOT NULL, + "enabled" boolean DEFAULT true NOT NULL, + "email_optional" boolean DEFAULT false NOT NULL, + "issuer" "text", + "discovery_url" "text", + "skip_nonce_check" boolean DEFAULT false NOT NULL, + "cached_discovery" "jsonb", + "discovery_cached_at" timestamp with time zone, + "authorization_url" "text", + "token_url" "text", + "userinfo_url" "text", + "jwks_uri" "text", + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "custom_claims_allowlist" "text"[] DEFAULT '{}'::"text"[] NOT NULL, + CONSTRAINT "custom_oauth_providers_authorization_url_https" CHECK ((("authorization_url" IS NULL) OR ("authorization_url" ~~ 'https://%'::"text"))), + CONSTRAINT "custom_oauth_providers_authorization_url_length" CHECK ((("authorization_url" IS NULL) OR ("char_length"("authorization_url") <= 2048))), + CONSTRAINT "custom_oauth_providers_client_id_length" CHECK ((("char_length"("client_id") >= 1) AND ("char_length"("client_id") <= 512))), + CONSTRAINT "custom_oauth_providers_discovery_url_length" CHECK ((("discovery_url" IS NULL) OR ("char_length"("discovery_url") <= 2048))), + CONSTRAINT "custom_oauth_providers_identifier_format" CHECK (("identifier" ~ '^[a-z0-9][a-z0-9:-]{0,48}[a-z0-9]$'::"text")), + CONSTRAINT "custom_oauth_providers_issuer_length" CHECK ((("issuer" IS NULL) OR (("char_length"("issuer") >= 1) AND ("char_length"("issuer") <= 2048)))), + CONSTRAINT "custom_oauth_providers_jwks_uri_https" CHECK ((("jwks_uri" IS NULL) OR ("jwks_uri" ~~ 'https://%'::"text"))), + CONSTRAINT "custom_oauth_providers_jwks_uri_length" CHECK ((("jwks_uri" IS NULL) OR ("char_length"("jwks_uri") <= 2048))), + CONSTRAINT "custom_oauth_providers_name_length" CHECK ((("char_length"("name") >= 1) AND ("char_length"("name") <= 100))), + CONSTRAINT "custom_oauth_providers_oauth2_requires_endpoints" CHECK ((("provider_type" <> 'oauth2'::"text") OR (("authorization_url" IS NOT NULL) AND ("token_url" IS NOT NULL) AND ("userinfo_url" IS NOT NULL)))), + CONSTRAINT "custom_oauth_providers_oidc_discovery_url_https" CHECK ((("provider_type" <> 'oidc'::"text") OR ("discovery_url" IS NULL) OR ("discovery_url" ~~ 'https://%'::"text"))), + CONSTRAINT "custom_oauth_providers_oidc_issuer_https" CHECK ((("provider_type" <> 'oidc'::"text") OR ("issuer" IS NULL) OR ("issuer" ~~ 'https://%'::"text"))), + CONSTRAINT "custom_oauth_providers_oidc_requires_issuer" CHECK ((("provider_type" <> 'oidc'::"text") OR ("issuer" IS NOT NULL))), + CONSTRAINT "custom_oauth_providers_provider_type_check" CHECK (("provider_type" = ANY (ARRAY['oauth2'::"text", 'oidc'::"text"]))), + CONSTRAINT "custom_oauth_providers_token_url_https" CHECK ((("token_url" IS NULL) OR ("token_url" ~~ 'https://%'::"text"))), + CONSTRAINT "custom_oauth_providers_token_url_length" CHECK ((("token_url" IS NULL) OR ("char_length"("token_url") <= 2048))), + CONSTRAINT "custom_oauth_providers_userinfo_url_https" CHECK ((("userinfo_url" IS NULL) OR ("userinfo_url" ~~ 'https://%'::"text"))), + CONSTRAINT "custom_oauth_providers_userinfo_url_length" CHECK ((("userinfo_url" IS NULL) OR ("char_length"("userinfo_url") <= 2048))) +); + + +ALTER TABLE "auth"."custom_oauth_providers" OWNER TO "supabase_auth_admin"; + + +CREATE TABLE IF NOT EXISTS "auth"."flow_state" ( + "id" "uuid" NOT NULL, + "user_id" "uuid", + "auth_code" "text", + "code_challenge_method" "auth"."code_challenge_method", + "code_challenge" "text", + "provider_type" "text" NOT NULL, + "provider_access_token" "text", + "provider_refresh_token" "text", + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "authentication_method" "text" NOT NULL, + "auth_code_issued_at" timestamp with time zone, + "invite_token" "text", + "referrer" "text", + "oauth_client_state_id" "uuid", + "linking_target_id" "uuid", + "email_optional" boolean DEFAULT false NOT NULL +); + + +ALTER TABLE "auth"."flow_state" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."flow_state" IS 'Stores metadata for all OAuth/SSO login flows'; + + + +CREATE TABLE IF NOT EXISTS "auth"."identities" ( + "provider_id" "text" NOT NULL, + "user_id" "uuid" NOT NULL, + "identity_data" "jsonb" NOT NULL, + "provider" "text" NOT NULL, + "last_sign_in_at" timestamp with time zone, + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "email" "text" GENERATED ALWAYS AS ("lower"(("identity_data" ->> 'email'::"text"))) STORED, + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL +); + + +ALTER TABLE "auth"."identities" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."identities" IS 'Auth: Stores identities associated to a user.'; + + + +COMMENT ON COLUMN "auth"."identities"."email" IS 'Auth: Email is a generated column that references the optional email property in the identity_data'; + + + +CREATE TABLE IF NOT EXISTS "auth"."instances" ( + "id" "uuid" NOT NULL, + "uuid" "uuid", + "raw_base_config" "text", + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone +); + + +ALTER TABLE "auth"."instances" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."instances" IS 'Auth: Manages users across multiple sites.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."mfa_amr_claims" ( + "session_id" "uuid" NOT NULL, + "created_at" timestamp with time zone NOT NULL, + "updated_at" timestamp with time zone NOT NULL, + "authentication_method" "text" NOT NULL, + "id" "uuid" NOT NULL +); + + +ALTER TABLE "auth"."mfa_amr_claims" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."mfa_amr_claims" IS 'auth: stores authenticator method reference claims for multi factor authentication'; + + + +CREATE TABLE IF NOT EXISTS "auth"."mfa_challenges" ( + "id" "uuid" NOT NULL, + "factor_id" "uuid" NOT NULL, + "created_at" timestamp with time zone NOT NULL, + "verified_at" timestamp with time zone, + "ip_address" "inet" NOT NULL, + "otp_code" "text", + "web_authn_session_data" "jsonb" +); + + +ALTER TABLE "auth"."mfa_challenges" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."mfa_challenges" IS 'auth: stores metadata about challenge requests made'; + + + +CREATE TABLE IF NOT EXISTS "auth"."mfa_factors" ( + "id" "uuid" NOT NULL, + "user_id" "uuid" NOT NULL, + "friendly_name" "text", + "factor_type" "auth"."factor_type" NOT NULL, + "status" "auth"."factor_status" NOT NULL, + "created_at" timestamp with time zone NOT NULL, + "updated_at" timestamp with time zone NOT NULL, + "secret" "text", + "phone" "text", + "last_challenged_at" timestamp with time zone, + "web_authn_credential" "jsonb", + "web_authn_aaguid" "uuid", + "last_webauthn_challenge_data" "jsonb" +); + + +ALTER TABLE "auth"."mfa_factors" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."mfa_factors" IS 'auth: stores metadata about factors'; + + + +COMMENT ON COLUMN "auth"."mfa_factors"."last_webauthn_challenge_data" IS 'Stores the latest WebAuthn challenge data including attestation/assertion for customer verification'; + + + +CREATE TABLE IF NOT EXISTS "auth"."oauth_authorizations" ( + "id" "uuid" NOT NULL, + "authorization_id" "text" NOT NULL, + "client_id" "uuid" NOT NULL, + "user_id" "uuid", + "redirect_uri" "text" NOT NULL, + "scope" "text" NOT NULL, + "state" "text", + "resource" "text", + "code_challenge" "text", + "code_challenge_method" "auth"."code_challenge_method", + "response_type" "auth"."oauth_response_type" DEFAULT 'code'::"auth"."oauth_response_type" NOT NULL, + "status" "auth"."oauth_authorization_status" DEFAULT 'pending'::"auth"."oauth_authorization_status" NOT NULL, + "authorization_code" "text", + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "expires_at" timestamp with time zone DEFAULT ("now"() + '00:03:00'::interval) NOT NULL, + "approved_at" timestamp with time zone, + "nonce" "text", + CONSTRAINT "oauth_authorizations_authorization_code_length" CHECK (("char_length"("authorization_code") <= 255)), + CONSTRAINT "oauth_authorizations_code_challenge_length" CHECK (("char_length"("code_challenge") <= 128)), + CONSTRAINT "oauth_authorizations_expires_at_future" CHECK (("expires_at" > "created_at")), + CONSTRAINT "oauth_authorizations_nonce_length" CHECK (("char_length"("nonce") <= 255)), + CONSTRAINT "oauth_authorizations_redirect_uri_length" CHECK (("char_length"("redirect_uri") <= 2048)), + CONSTRAINT "oauth_authorizations_resource_length" CHECK (("char_length"("resource") <= 2048)), + CONSTRAINT "oauth_authorizations_scope_length" CHECK (("char_length"("scope") <= 4096)), + CONSTRAINT "oauth_authorizations_state_length" CHECK (("char_length"("state") <= 4096)) +); + + +ALTER TABLE "auth"."oauth_authorizations" OWNER TO "supabase_auth_admin"; + + +CREATE TABLE IF NOT EXISTS "auth"."oauth_client_states" ( + "id" "uuid" NOT NULL, + "provider_type" "text" NOT NULL, + "code_verifier" "text", + "created_at" timestamp with time zone NOT NULL +); + + +ALTER TABLE "auth"."oauth_client_states" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."oauth_client_states" IS 'Stores OAuth states for third-party provider authentication flows where Supabase acts as the OAuth client.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."oauth_clients" ( + "id" "uuid" NOT NULL, + "client_secret_hash" "text", + "registration_type" "auth"."oauth_registration_type" NOT NULL, + "redirect_uris" "text" NOT NULL, + "grant_types" "text" NOT NULL, + "client_name" "text", + "client_uri" "text", + "logo_uri" "text", + "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, + "client_type" "auth"."oauth_client_type" DEFAULT 'confidential'::"auth"."oauth_client_type" NOT NULL, + "token_endpoint_auth_method" "text" NOT NULL, + CONSTRAINT "oauth_clients_client_name_length" CHECK (("char_length"("client_name") <= 1024)), + CONSTRAINT "oauth_clients_client_uri_length" CHECK (("char_length"("client_uri") <= 2048)), + CONSTRAINT "oauth_clients_logo_uri_length" CHECK (("char_length"("logo_uri") <= 2048)), + CONSTRAINT "oauth_clients_token_endpoint_auth_method_check" CHECK (("token_endpoint_auth_method" = ANY (ARRAY['client_secret_basic'::"text", 'client_secret_post'::"text", 'none'::"text"]))) +); + + +ALTER TABLE "auth"."oauth_clients" OWNER TO "supabase_auth_admin"; + + +CREATE TABLE IF NOT EXISTS "auth"."oauth_consents" ( + "id" "uuid" NOT NULL, + "user_id" "uuid" NOT NULL, + "client_id" "uuid" NOT NULL, + "scopes" "text" NOT NULL, + "granted_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "revoked_at" timestamp with time zone, + CONSTRAINT "oauth_consents_revoked_after_granted" CHECK ((("revoked_at" IS NULL) OR ("revoked_at" >= "granted_at"))), + CONSTRAINT "oauth_consents_scopes_length" CHECK (("char_length"("scopes") <= 2048)), + CONSTRAINT "oauth_consents_scopes_not_empty" CHECK (("char_length"(TRIM(BOTH FROM "scopes")) > 0)) +); + + +ALTER TABLE "auth"."oauth_consents" OWNER TO "supabase_auth_admin"; + + +CREATE TABLE IF NOT EXISTS "auth"."one_time_tokens" ( + "id" "uuid" NOT NULL, + "user_id" "uuid" NOT NULL, + "token_type" "auth"."one_time_token_type" NOT NULL, + "token_hash" "text" NOT NULL, + "relates_to" "text" NOT NULL, + "created_at" timestamp without time zone DEFAULT "now"() NOT NULL, + "updated_at" timestamp without time zone DEFAULT "now"() NOT NULL, + CONSTRAINT "one_time_tokens_token_hash_check" CHECK (("char_length"("token_hash") > 0)) +); + + +ALTER TABLE "auth"."one_time_tokens" OWNER TO "supabase_auth_admin"; + + +CREATE TABLE IF NOT EXISTS "auth"."refresh_tokens" ( + "instance_id" "uuid", + "id" bigint NOT NULL, + "token" character varying(255), + "user_id" character varying(255), + "revoked" boolean, + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "parent" character varying(255), + "session_id" "uuid" +); + + +ALTER TABLE "auth"."refresh_tokens" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."refresh_tokens" IS 'Auth: Store of tokens used to refresh JWT tokens once they expire.'; + + + +CREATE SEQUENCE IF NOT EXISTS "auth"."refresh_tokens_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE "auth"."refresh_tokens_id_seq" OWNER TO "supabase_auth_admin"; + + +ALTER SEQUENCE "auth"."refresh_tokens_id_seq" OWNED BY "auth"."refresh_tokens"."id"; + + + +CREATE TABLE IF NOT EXISTS "auth"."saml_providers" ( + "id" "uuid" NOT NULL, + "sso_provider_id" "uuid" NOT NULL, + "entity_id" "text" NOT NULL, + "metadata_xml" "text" NOT NULL, + "metadata_url" "text", + "attribute_mapping" "jsonb", + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "name_id_format" "text", + CONSTRAINT "entity_id not empty" CHECK (("char_length"("entity_id") > 0)), + CONSTRAINT "metadata_url not empty" CHECK ((("metadata_url" = NULL::"text") OR ("char_length"("metadata_url") > 0))), + CONSTRAINT "metadata_xml not empty" CHECK (("char_length"("metadata_xml") > 0)) +); + + +ALTER TABLE "auth"."saml_providers" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."saml_providers" IS 'Auth: Manages SAML Identity Provider connections.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."saml_relay_states" ( + "id" "uuid" NOT NULL, + "sso_provider_id" "uuid" NOT NULL, + "request_id" "text" NOT NULL, + "for_email" "text", + "redirect_to" "text", + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "flow_state_id" "uuid", + CONSTRAINT "request_id not empty" CHECK (("char_length"("request_id") > 0)) +); + + +ALTER TABLE "auth"."saml_relay_states" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."saml_relay_states" IS 'Auth: Contains SAML Relay State information for each Service Provider initiated login.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."schema_migrations" ( + "version" character varying(255) NOT NULL +); + + +ALTER TABLE "auth"."schema_migrations" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."schema_migrations" IS 'Auth: Manages updates to the auth system.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."sessions" ( + "id" "uuid" NOT NULL, + "user_id" "uuid" NOT NULL, + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "factor_id" "uuid", + "aal" "auth"."aal_level", + "not_after" timestamp with time zone, + "refreshed_at" timestamp without time zone, + "user_agent" "text", + "ip" "inet", + "tag" "text", + "oauth_client_id" "uuid", + "refresh_token_hmac_key" "text", + "refresh_token_counter" bigint, + "scopes" "text", + CONSTRAINT "sessions_scopes_length" CHECK (("char_length"("scopes") <= 4096)) +); + + +ALTER TABLE "auth"."sessions" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."sessions" IS 'Auth: Stores session data associated to a user.'; + + + +COMMENT ON COLUMN "auth"."sessions"."not_after" IS 'Auth: Not after is a nullable column that contains a timestamp after which the session should be regarded as expired.'; + + + +COMMENT ON COLUMN "auth"."sessions"."refresh_token_hmac_key" IS 'Holds a HMAC-SHA256 key used to sign refresh tokens for this session.'; + + + +COMMENT ON COLUMN "auth"."sessions"."refresh_token_counter" IS 'Holds the ID (counter) of the last issued refresh token.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."sso_domains" ( + "id" "uuid" NOT NULL, + "sso_provider_id" "uuid" NOT NULL, + "domain" "text" NOT NULL, + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + CONSTRAINT "domain not empty" CHECK (("char_length"("domain") > 0)) +); + + +ALTER TABLE "auth"."sso_domains" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."sso_domains" IS 'Auth: Manages SSO email address domain mapping to an SSO Identity Provider.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."sso_providers" ( + "id" "uuid" NOT NULL, + "resource_id" "text", + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "disabled" boolean, + CONSTRAINT "resource_id not empty" CHECK ((("resource_id" = NULL::"text") OR ("char_length"("resource_id") > 0))) +); + + +ALTER TABLE "auth"."sso_providers" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."sso_providers" IS 'Auth: Manages SSO identity provider information; see saml_providers for SAML.'; + + + +COMMENT ON COLUMN "auth"."sso_providers"."resource_id" IS 'Auth: Uniquely identifies a SSO provider according to a user-chosen resource ID (case insensitive), useful in infrastructure as code.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."users" ( + "instance_id" "uuid", + "id" "uuid" NOT NULL, + "aud" character varying(255), + "role" character varying(255), + "email" character varying(255), + "encrypted_password" character varying(255), + "email_confirmed_at" timestamp with time zone, + "invited_at" timestamp with time zone, + "confirmation_token" character varying(255), + "confirmation_sent_at" timestamp with time zone, + "recovery_token" character varying(255), + "recovery_sent_at" timestamp with time zone, + "email_change_token_new" character varying(255), + "email_change" character varying(255), + "email_change_sent_at" timestamp with time zone, + "last_sign_in_at" timestamp with time zone, + "raw_app_meta_data" "jsonb", + "raw_user_meta_data" "jsonb", + "is_super_admin" boolean, + "created_at" timestamp with time zone, + "updated_at" timestamp with time zone, + "phone" "text" DEFAULT NULL::character varying, + "phone_confirmed_at" timestamp with time zone, + "phone_change" "text" DEFAULT ''::character varying, + "phone_change_token" character varying(255) DEFAULT ''::character varying, + "phone_change_sent_at" timestamp with time zone, + "confirmed_at" timestamp with time zone GENERATED ALWAYS AS (LEAST("email_confirmed_at", "phone_confirmed_at")) STORED, + "email_change_token_current" character varying(255) DEFAULT ''::character varying, + "email_change_confirm_status" smallint DEFAULT 0, + "banned_until" timestamp with time zone, + "reauthentication_token" character varying(255) DEFAULT ''::character varying, + "reauthentication_sent_at" timestamp with time zone, + "is_sso_user" boolean DEFAULT false NOT NULL, + "deleted_at" timestamp with time zone, + "is_anonymous" boolean DEFAULT false NOT NULL, + CONSTRAINT "users_email_change_confirm_status_check" CHECK ((("email_change_confirm_status" >= 0) AND ("email_change_confirm_status" <= 2))) +); + + +ALTER TABLE "auth"."users" OWNER TO "supabase_auth_admin"; + + +COMMENT ON TABLE "auth"."users" IS 'Auth: Stores user login data within a secure schema.'; + + + +COMMENT ON COLUMN "auth"."users"."is_sso_user" IS 'Auth: Set this column to true when the account comes from SSO. These accounts can have duplicate emails.'; + + + +CREATE TABLE IF NOT EXISTS "auth"."webauthn_challenges" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "user_id" "uuid", + "challenge_type" "text" NOT NULL, + "session_data" "jsonb" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "expires_at" timestamp with time zone NOT NULL, + CONSTRAINT "webauthn_challenges_challenge_type_check" CHECK (("challenge_type" = ANY (ARRAY['signup'::"text", 'registration'::"text", 'authentication'::"text"]))) +); + + +ALTER TABLE "auth"."webauthn_challenges" OWNER TO "supabase_auth_admin"; + + +CREATE TABLE IF NOT EXISTS "auth"."webauthn_credentials" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "user_id" "uuid" NOT NULL, + "credential_id" "bytea" NOT NULL, + "public_key" "bytea" NOT NULL, + "attestation_type" "text" DEFAULT ''::"text" NOT NULL, + "aaguid" "uuid", + "sign_count" bigint DEFAULT 0 NOT NULL, + "transports" "jsonb" DEFAULT '[]'::"jsonb" NOT NULL, + "backup_eligible" boolean DEFAULT false NOT NULL, + "backed_up" boolean DEFAULT false NOT NULL, + "friendly_name" "text" DEFAULT ''::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "last_used_at" timestamp with time zone +); + + +ALTER TABLE "auth"."webauthn_credentials" OWNER TO "supabase_auth_admin"; + + +CREATE TABLE IF NOT EXISTS "public"."bgign_bdtopo_buildings" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "department" character varying(3) NOT NULL, + "rnb_id" character varying(100), + "cleabs" character varying(100), + "geom" "public"."geometry"(Geometry,2154) NOT NULL, + "centroid" "public"."geometry"(Point,2154), + "hauteur" numeric(5,2), + "nature" character varying(100), + "usage1" character varying(100), + "usage2" character varying(100), + "construction_legere" boolean, + "etat" character varying(50), + "numero" character varying(20), + "rep" character varying(10), + "nom_voie" character varying(255), + "code_postal" character varying(5), + "nom_commune" character varying(100), + "code_insee" character varying(5), + "estimated_floors" integer, + "import_date" timestamp with time zone DEFAULT "now"(), + "source_file" character varying(500), + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bgign_bdtopo_buildings" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."bgign_bdtopo_buildings" IS 'BD TOPO building data with PostGIS geometry support. Stores building footprints, height, and characteristics in Lambert 93 (EPSG:2154).'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."id" IS 'Unique building identifier (UUID)'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."department" IS 'French department code (2-3 digits) for query partitioning'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."rnb_id" IS 'RNB (Référentiel National des Bâtiments) identifier - stable unique ID across updates'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."cleabs" IS 'BD TOPO CLEABS identifier for data traceability'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."geom" IS 'Building footprint (Polygon or MultiPolygon) in Lambert 93 (EPSG:2154)'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."centroid" IS 'Building centroid point in Lambert 93 (EPSG:2154) for distance calculations'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."hauteur" IS 'Building height in meters from BD TOPO HAUTEUR field'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."nature" IS 'Building nature/type from BD TOPO NATURE field (e.g., "Bâtiment quelconque", "Bâtiment industriel")'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."usage1" IS 'Primary building usage from BD TOPO USAGE1 field (e.g., "Résidentiel", "Commercial")'; + + + +COMMENT ON COLUMN "public"."bgign_bdtopo_buildings"."estimated_floors" IS 'Estimated floor count calculated as (height / 3.0)'; + + + +CREATE TABLE IF NOT EXISTS "public"."bgign_datasets" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "code" character varying(50) NOT NULL, + "name" character varying(255) NOT NULL, + "description" "text", + "provider" character varying(100) DEFAULT 'IGN'::character varying, + "version" character varying(50), + "release_date" "date", + "active" boolean DEFAULT true, + "download_url_pattern" "text", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bgign_datasets" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."bgign_datasets" IS 'Initial datasets: BD TOPO, BDNVF, LiDAR, BAN-Plus, RNB'; + + + +COMMENT ON COLUMN "public"."bgign_datasets"."code" IS 'Unique dataset code used in table names and API endpoints'; + + + +COMMENT ON COLUMN "public"."bgign_datasets"."download_url_pattern" IS 'URL template for automatic download: {department}, {layer}, {version}'; + + + +CREATE TABLE IF NOT EXISTS "public"."bgign_department_coverage" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "dataset_code" character varying(50) NOT NULL, + "department" character varying(3) NOT NULL, + "layer" character varying(100) NOT NULL, + "import_status" character varying(20) DEFAULT 'pending'::character varying, + "import_date" timestamp with time zone, + "import_duration_seconds" integer, + "source_file" character varying(500), + "record_count" integer, + "error_message" "text", + "bbox" "public"."geometry"(Polygon,2154), + "storage_bucket" character varying(100), + "storage_path" character varying(500), + "tiles_generated" boolean DEFAULT false, + "tiles_url" "text", + "tiles_generation_date" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bgign_department_coverage" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."bgign_department_coverage" IS 'Tracks which departments have which data layers available'; + + + +COMMENT ON COLUMN "public"."bgign_department_coverage"."import_status" IS 'Current import status for this layer'; + + + +COMMENT ON COLUMN "public"."bgign_department_coverage"."bbox" IS 'Bounding box of the imported data in Lambert 93'; + + + +CREATE TABLE IF NOT EXISTS "public"."bgign_download_queue" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "dataset_code" character varying(50) NOT NULL, + "department" character varying(3) NOT NULL, + "layer" character varying(100) NOT NULL, + "status" character varying(20) DEFAULT 'queued'::character varying, + "priority" integer DEFAULT 5, + "started_at" timestamp with time zone, + "completed_at" timestamp with time zone, + "error_message" "text", + "retry_count" integer DEFAULT 0, + "max_retries" integer DEFAULT 3, + "requested_by" character varying(100), + "trigger_source" character varying(50), + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bgign_download_queue" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."bgign_download_queue" IS 'Queue for automatic download and import jobs'; + + + +COMMENT ON COLUMN "public"."bgign_download_queue"."priority" IS 'Job priority: 1 (high) to 10 (low)'; + + + +COMMENT ON COLUMN "public"."bgign_download_queue"."trigger_source" IS 'What triggered this download (api_request, admin, scheduled)'; + + + +CREATE TABLE IF NOT EXISTS "public"."buildings" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "residence_id" "uuid", + "fid" integer, + "cleabs" character varying, + "building_id" character varying, + "building_name" character varying DEFAULT 'Bâtiment'::character varying, + "usage_1" character varying, + "nombre_de_logement" integer, + "nombre_d_etages" integer, + "hauteur" double precision, + "geometry" "public"."geometry"(MultiPolygonZ,2154) NOT NULL, + "created_at" timestamp without time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."buildings" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."bwc_chunks" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "document_id" "uuid" NOT NULL, + "content" "text" NOT NULL, + "embedding" "public"."vector"(1536) NOT NULL, + "fts_en" "tsvector" GENERATED ALWAYS AS ("to_tsvector"('"english"'::"regconfig", "content")) STORED, + "fts_fr" "tsvector" GENERATED ALWAYS AS ("to_tsvector"('"french"'::"regconfig", "content")) STORED, + "chunk_index" integer NOT NULL, + "token_count" integer, + "metadata" "jsonb" DEFAULT '{}'::"jsonb", + "created_at" timestamp with time zone DEFAULT "now"(), + "project_zones" "text"[] +); + + +ALTER TABLE "public"."bwc_chunks" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."bwc_client_accounts" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "name" "text" NOT NULL, + "slug" "text" NOT NULL, + "logo_url" "text", + "primary_color" "text" DEFAULT '#1a365d'::"text", + "default_language" "text" DEFAULT 'en'::"text", + "metadata" "jsonb" DEFAULT '{}'::"jsonb", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bwc_client_accounts" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."bwc_client_users" ( + "user_id" "uuid" NOT NULL, + "client_id" "uuid" NOT NULL, + "role" "text" DEFAULT 'member'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bwc_client_users" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."bwc_definitions" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "tender_id" "text" NOT NULL, + "term" "text" NOT NULL, + "definition" "text" NOT NULL, + "language" "text" NOT NULL, + "source_document" "text", + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bwc_definitions" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."bwc_definitions" IS 'Legal definitions extracted from tender documents for on-demand lookup'; + + + +COMMENT ON COLUMN "public"."bwc_definitions"."tender_id" IS 'Tender identifier (AO6, AO9, AR8, etc.)'; + + + +COMMENT ON COLUMN "public"."bwc_definitions"."term" IS 'The defined term (e.g., Installation, Generator)'; + + + +COMMENT ON COLUMN "public"."bwc_definitions"."definition" IS 'The full legal definition text'; + + + +COMMENT ON COLUMN "public"."bwc_definitions"."language" IS 'Language of the definition (fr or en)'; + + + +COMMENT ON COLUMN "public"."bwc_definitions"."source_document" IS 'Source document name for reference'; + + + +CREATE TABLE IF NOT EXISTS "public"."bwc_documents" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "title" "text" NOT NULL, + "source" "text" NOT NULL, + "content" "text", + "language" "text" DEFAULT 'en'::"text", + "country" "text", + "tender_id" "text", + "metadata" "jsonb" DEFAULT '{}'::"jsonb", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"(), + "document_type" "text" DEFAULT 'tender'::"text", + "project_zone" "text" +); + + +ALTER TABLE "public"."bwc_documents" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."bwc_jurisdiction_attributes" ( + "id" bigint NOT NULL, + "jurisdiction" "text" NOT NULL, + "tender_id" "text", + "domain" "text" NOT NULL, + "category" "text" NOT NULL, + "attribute_key" "text" NOT NULL, + "attribute_value" "text" NOT NULL, + "source_document" "text", + "confidence" "text" DEFAULT 'verified'::"text", + "notes" "text", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bwc_jurisdiction_attributes" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."bwc_jurisdiction_attributes" IS 'Structured regulatory attributes per jurisdiction/tender for comparative analysis. Supports the analytical methodology framework for offshore wind development.'; + + + +CREATE SEQUENCE IF NOT EXISTS "public"."bwc_jurisdiction_attributes_id_seq" + START WITH 1 + INCREMENT BY 1 + NO MINVALUE + NO MAXVALUE + CACHE 1; + + +ALTER SEQUENCE "public"."bwc_jurisdiction_attributes_id_seq" OWNER TO "postgres"; + + +ALTER SEQUENCE "public"."bwc_jurisdiction_attributes_id_seq" OWNED BY "public"."bwc_jurisdiction_attributes"."id"; + + + +CREATE TABLE IF NOT EXISTS "public"."bwc_projects" ( + "project_id" "text" NOT NULL, + "tender_id" "text" NOT NULL, + "country" "text" NOT NULL, + "lot_code" "text" NOT NULL, + "name" "text" NOT NULL, + "area" "text", + "technology" "text", + "lot_group" "text", + "facade" "text", + "capacity_mw" integer, + "status" "text" DEFAULT 'active'::"text", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"(), + "authorisation_type" "text", + "raccordement_type" "text", + CONSTRAINT "bwc_projects_authorisation_type_check" CHECK (("authorisation_type" = ANY (ARRAY['ZEE'::"text", 'DPM'::"text"]))), + CONSTRAINT "bwc_projects_raccordement_type_check" CHECK (("raccordement_type" = ANY (ARRAY['AC'::"text", 'HVDC'::"text", 'mixed'::"text"]))) +); + + +ALTER TABLE "public"."bwc_projects" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."bwc_reports" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "client_id" "uuid", + "created_by" "uuid", + "report_type" "text" DEFAULT 'strategic_market'::"text" NOT NULL, + "market" "text" DEFAULT 'France'::"text" NOT NULL, + "language" "text" DEFAULT 'en'::"text" NOT NULL, + "status" "text" DEFAULT 'completed'::"text" NOT NULL, + "sections_count" integer DEFAULT 0, + "file_size_bytes" integer, + "download_token" "text", + "token_expires_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT "now"(), + "completed_at" timestamp with time zone, + "file_path" "text", + "title" "text" DEFAULT 'Strategic Market Report'::"text" +); + + +ALTER TABLE "public"."bwc_reports" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."bwc_strategy_metrics" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "document_id" "uuid", + "source_name" "text" NOT NULL, + "source_type" "text" NOT NULL, + "source_date" "date", + "source_page" integer, + "source_section" "text", + "source_text" "text", + "metric_category" "text" NOT NULL, + "metric_name" "text" NOT NULL, + "scenario" "text", + "sector" "text", + "energy_source" "text", + "country" "text" DEFAULT 'FR'::"text", + "year" integer, + "period" "text", + "value" numeric NOT NULL, + "unit" "text" NOT NULL, + "value_min" numeric, + "value_max" numeric, + "is_official_target" boolean DEFAULT false, + "confidence" "text" DEFAULT 'extracted'::"text", + "extraction_method" "text", + "needs_validation" boolean DEFAULT true, + "created_at" timestamp with time zone DEFAULT "now"(), + "validated_at" timestamp with time zone, + "validated_by" "text" +); + + +ALTER TABLE "public"."bwc_strategy_metrics" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."bwc_strategy_metrics" IS 'Structured metrics extracted from energy strategy documents (PPE, SNBC, RTE)'; + + + +CREATE TABLE IF NOT EXISTS "public"."bwc_tender_registry" ( + "tender_id" "text" NOT NULL, + "country" "text" NOT NULL, + "label" "text" NOT NULL, + "status" "text" DEFAULT 'open'::"text" NOT NULL, + "capacity_gw" numeric, + "technology" "text", + "launched_at" "text", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bwc_tender_registry" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."bwc_tender_registry" IS 'One row per offshore wind tender. Populated by ingestion pipeline Stage 2 via _tender.yaml files. Drives the dynamic tender dropdown in the frontend.'; + + + +CREATE TABLE IF NOT EXISTS "public"."bwc_usage_logs" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "user_id" "uuid" NOT NULL, + "client_id" "uuid", + "event_type" "text" NOT NULL, + "agent_type" "text", + "metadata" "jsonb" DEFAULT '{}'::"jsonb", + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."bwc_usage_logs" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."companies" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "name" "text" NOT NULL, + "slug" "text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."companies" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."disorder_catalog" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "disorder_id" character varying NOT NULL, + "name" character varying NOT NULL, + "description" "text", + "severity_levels" "text"[] DEFAULT ARRAY['Faible'::"text", 'Moyen'::"text", 'Élevé'::"text"], + "color" character varying DEFAULT '#FF0000'::character varying, + "created_at" timestamp without time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."disorder_catalog" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."disorders" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "photo_id" "uuid", + "disorder_catalog_id" "uuid", + "ref_id" character varying, + "x_pixel" integer, + "y_pixel" integer, + "x_coord" double precision, + "y_coord" double precision, + "z_coord" double precision, + "geometry" "public"."geometry"(Point,2154), + "severity" character varying NOT NULL, + "description" "text", + "confidence" double precision, + "status" character varying DEFAULT 'identified'::character varying, + "timestamp" timestamp without time zone DEFAULT "now"(), + "created_at" timestamp without time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."disorders" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."facades" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "building_id" "uuid", + "fid" integer, + "facade_id" character varying NOT NULL, + "building_name" character varying, + "facade_name" character varying, + "normal_x" double precision, + "normal_y" double precision, + "normal_z" double precision, + "center_x" double precision, + "center_y" double precision, + "center_z" double precision, + "geometry" "public"."geometry"(LineStringZ,2154) NOT NULL, + "created_at" timestamp without time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."facades" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."intersections" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "photo_id" "uuid" NOT NULL, + "facade_id" "uuid" NOT NULL, + "building_id" "uuid" NOT NULL, + "distance" double precision NOT NULL, + "intersection_x" double precision NOT NULL, + "intersection_y" double precision NOT NULL, + "intersection_z" double precision NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"(), + "geometry" "public"."geometry"(PointZ,2154), + "surface_type" character varying(10), + CONSTRAINT "intersections_surface_type_check" CHECK ((("surface_type")::"text" = ANY ((ARRAY['facade'::character varying, 'toit'::character varying])::"text"[]))) +); + + +ALTER TABLE "public"."intersections" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."intersections" IS 'Intersection points calculated during photo processing using Python methodology. Linked directly to photos instead of separate drone positions.'; + + + +COMMENT ON COLUMN "public"."intersections"."photo_id" IS 'Reference to the photo that generated this intersection'; + + + +COMMENT ON COLUMN "public"."intersections"."facade_id" IS 'Reference to the facade that was intersected'; + + + +COMMENT ON COLUMN "public"."intersections"."building_id" IS 'Reference to the building containing the facade'; + + + +COMMENT ON COLUMN "public"."intersections"."distance" IS 'Distance from drone position to intersection point (meters)'; + + + +COMMENT ON COLUMN "public"."intersections"."intersection_x" IS 'X coordinate of intersection point (Lambert93)'; + + + +COMMENT ON COLUMN "public"."intersections"."intersection_y" IS 'Y coordinate of intersection point (Lambert93)'; + + + +COMMENT ON COLUMN "public"."intersections"."intersection_z" IS 'Z coordinate of intersection point (altitude)'; + + + +COMMENT ON COLUMN "public"."intersections"."geometry" IS 'Intersection point geometry in Lambert93 3D (POINTZ) for QGIS visualization'; + + + +COMMENT ON COLUMN "public"."intersections"."surface_type" IS 'Automatic surface classification: facade (pitch < 10°), toit (pitch > 75°), NULL (manual classification required)'; + + + +CREATE TABLE IF NOT EXISTS "public"."photos" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "visit_id" "uuid", + "filename" character varying NOT NULL, + "storage_path" character varying NOT NULL, + "file_size" bigint, + "capture_time" timestamp without time zone, + "xmp_metadata" "jsonb", + "processing_status" character varying DEFAULT 'uploaded'::character varying, + "created_at" timestamp without time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."photos" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."photos" IS 'Photos from drone visits. Photos are linked to facades through the intersections table, not directly. facade_id column removed in migration 011 as it was obsolete.'; + + + +CREATE TABLE IF NOT EXISTS "public"."projects" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "name" character varying NOT NULL, + "description" "text", + "user_id" "uuid", + "created_at" timestamp without time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."projects" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."residences" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "project_id" "uuid", + "name" character varying NOT NULL, + "address" "text", + "created_at" timestamp without time zone DEFAULT "now"(), + "departement" character(3) DEFAULT '093'::"bpchar", + "commune" "text", + CONSTRAINT "check_departement_format" CHECK (("departement" ~ '^[0-9]{3}$'::"text")) +); + + +ALTER TABLE "public"."residences" OWNER TO "postgres"; + + +COMMENT ON COLUMN "public"."residences"."departement" IS 'Code département sur 3 chiffres (ex: 093 pour Seine-Saint-Denis)'; + + + +COMMENT ON COLUMN "public"."residences"."commune" IS 'Nom de la commune (texte libre)'; + + + +CREATE TABLE IF NOT EXISTS "public"."visits" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "residence_id" "uuid", + "visit_date" "date", + "visit_time" time without time zone, + "status" character varying DEFAULT 'in_progress'::character varying, + "intersections_computed" boolean DEFAULT false, + "metadata" "jsonb", + "created_at" timestamp without time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."visits" OWNER TO "postgres"; + + +CREATE OR REPLACE VIEW "public"."disorders_full" AS + SELECT "d"."id", + "d"."ref_id", + "d"."x_pixel", + "d"."y_pixel", + "d"."x_coord", + "d"."y_coord", + "d"."z_coord", + "d"."severity", + "d"."description", + "d"."status", + "d"."timestamp", + "dc"."name" AS "disorder_name", + "dc"."color" AS "disorder_color", + "p"."filename" AS "photo_filename", + "p"."storage_path", + "f"."facade_id", + "f"."building_name", + "f"."facade_name", + "r"."name" AS "residence_name", + "pr"."name" AS "project_name" + FROM ((((((("public"."disorders" "d" + JOIN "public"."disorder_catalog" "dc" ON (("d"."disorder_catalog_id" = "dc"."id"))) + JOIN "public"."photos" "p" ON (("d"."photo_id" = "p"."id"))) + JOIN "public"."visits" "v" ON (("p"."visit_id" = "v"."id"))) + JOIN "public"."residences" "r" ON (("v"."residence_id" = "r"."id"))) + JOIN "public"."projects" "pr" ON (("r"."project_id" = "pr"."id"))) + LEFT JOIN "public"."intersections" "i" ON (("i"."photo_id" = "p"."id"))) + LEFT JOIN "public"."facades" "f" ON (("i"."facade_id" = "f"."id"))); + + +ALTER VIEW "public"."disorders_full" OWNER TO "postgres"; + + +COMMENT ON VIEW "public"."disorders_full" IS 'Complete disorder information with photo and facade details. Updated in migration 011 to use intersections table instead of obsolete photos.facade_id column.'; + + + +CREATE TABLE IF NOT EXISTS "public"."drone_positions" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "visit_id" "uuid", + "fid" integer, + "filename" character varying NOT NULL, + "x_coord" double precision, + "y_coord" double precision, + "z_coord" double precision, + "gimbal_yaw" double precision, + "gimbal_pitch" double precision, + "gimbal_roll" double precision, + "created_at" timestamp without time zone DEFAULT "now"(), + "geometry" "public"."geometry"(PointZ,2154), + "relative_altitude" double precision +); + + +ALTER TABLE "public"."drone_positions" OWNER TO "postgres"; + + +COMMENT ON COLUMN "public"."drone_positions"."z_coord" IS 'Z coordinate for 3D positioning in Lambert93 coordinate system'; + + + +COMMENT ON COLUMN "public"."drone_positions"."geometry" IS '3D point geometry (X, Y, Z) in Lambert93 coordinate system'; + + + +COMMENT ON COLUMN "public"."drone_positions"."relative_altitude" IS 'Relative altitude from drone XMP metadata (meters above takeoff point)'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_annotations" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "photo_id" "uuid" NOT NULL, + "note_id" "uuid", + "x_center" numeric NOT NULL, + "y_center" numeric NOT NULL, + "width" numeric NOT NULL, + "height" numeric NOT NULL, + "segmentation_polygon" "jsonb", + "source" "text" DEFAULT 'manual'::"text", + "confidence" numeric, + "validated" boolean DEFAULT false, + "roboflow_annotation_id" "text", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"(), + "photo_description" "text", + CONSTRAINT "edifice_annotations_confidence_check" CHECK ((("confidence" >= (0)::numeric) AND ("confidence" <= (1)::numeric))), + CONSTRAINT "edifice_annotations_height_check" CHECK ((("height" > (0)::numeric) AND ("height" <= (1)::numeric))), + CONSTRAINT "edifice_annotations_source_check" CHECK (("source" = ANY (ARRAY['manual'::"text", 'weweb'::"text", 'roboflow'::"text", 'sam'::"text", 'yolo'::"text"]))), + CONSTRAINT "edifice_annotations_width_check" CHECK ((("width" > (0)::numeric) AND ("width" <= (1)::numeric))), + CONSTRAINT "edifice_annotations_x_center_check" CHECK ((("x_center" >= (0)::numeric) AND ("x_center" <= (1)::numeric))), + CONSTRAINT "edifice_annotations_y_center_check" CHECK ((("y_center" >= (0)::numeric) AND ("y_center" <= (1)::numeric))) +); + + +ALTER TABLE "public"."edifice_annotations" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_annotations" IS 'Photo annotations linking photos to disorders. Each annotation represents a bounding box on a photo that identifies a disorder. The annotation stores the visual location (YOLO coordinates) and optionally a description of what is visible on this specific photo.'; + + + +COMMENT ON COLUMN "public"."edifice_annotations"."x_center" IS 'Center X coordinate (0-1, YOLO format)'; + + + +COMMENT ON COLUMN "public"."edifice_annotations"."y_center" IS 'Center Y coordinate (0-1, YOLO format)'; + + + +COMMENT ON COLUMN "public"."edifice_annotations"."photo_description" IS 'Specific description of what the technician observes on this photo. This is different from the disorder''s general description - it describes the visual evidence on this particular photo.'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_component_types" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "code" "text" NOT NULL, + "label_fr" "text" NOT NULL, + "label_en" "text", + "yolo_class_id" integer, + "parent_id" "uuid", + "created_at" timestamp with time zone DEFAULT "now"(), + "category" "text", + "subtypes" "jsonb", + "display_order" integer DEFAULT 0 +); + + +ALTER TABLE "public"."edifice_component_types" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_component_types" IS 'Controlled vocabulary for building components (CV-ready)'; + + + +COMMENT ON COLUMN "public"."edifice_component_types"."yolo_class_id" IS 'YOLO class ID for ML training'; + + + +COMMENT ON COLUMN "public"."edifice_component_types"."category" IS 'Category: structure, enveloppe, equipement'; + + + +COMMENT ON COLUMN "public"."edifice_component_types"."subtypes" IS 'Array of subtype codes (e.g., escalier_bois, escalier_beton)'; + + + +COMMENT ON COLUMN "public"."edifice_component_types"."display_order" IS 'Display order in catalogue UI'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_disorder_types" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "code" "text" NOT NULL, + "label_fr" "text" NOT NULL, + "label_en" "text", + "yolo_class_id" integer, + "created_at" timestamp with time zone DEFAULT "now"(), + "category" "text", + "description" "text", + "applicable_component_codes" "text"[], + "display_order" integer DEFAULT 0 +); + + +ALTER TABLE "public"."edifice_disorder_types" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_disorder_types" IS 'Controlled vocabulary for disorder types (CV-ready)'; + + + +COMMENT ON COLUMN "public"."edifice_disorder_types"."yolo_class_id" IS 'YOLO class ID for ML training'; + + + +COMMENT ON COLUMN "public"."edifice_disorder_types"."category" IS 'Category: fissuration, degradation_beton, etc.'; + + + +COMMENT ON COLUMN "public"."edifice_disorder_types"."description" IS 'Technical description of the disorder'; + + + +COMMENT ON COLUMN "public"."edifice_disorder_types"."applicable_component_codes" IS 'Array of component codes this disorder applies to'; + + + +COMMENT ON COLUMN "public"."edifice_disorder_types"."display_order" IS 'Display order in catalogue UI'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_enum_labels" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "enum_type" "text" NOT NULL, + "enum_value" "text" NOT NULL, + "label_fr" "text" NOT NULL, + "label_en" "text", + "display_order" integer DEFAULT 0, + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."edifice_enum_labels" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_enum_labels" IS 'Translation lookup for enum values (i18n support)'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_messages" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "project_id" "uuid" NOT NULL, + "user_id" "uuid", + "role" "text" NOT NULL, + "content" "text" NOT NULL, + "metadata" "jsonb", + "created_at" timestamp with time zone DEFAULT "now"(), + CONSTRAINT "edifice_messages_role_check" CHECK (("role" = ANY (ARRAY['user'::"text", 'assistant'::"text", 'system'::"text"]))) +); + + +ALTER TABLE "public"."edifice_messages" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."edifice_notes" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "project_id" "uuid" NOT NULL, + "created_by" "uuid", + "component_type_id" "uuid", + "disorder_type_id" "uuid", + "name" "text" NOT NULL, + "location" "text", + "description" "text", + "cause" "text", + "recommendations" "text", + "display_order" integer, + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"(), + "type" "text" DEFAULT 'disorder'::"text", + "ref" "text", + "element" "text", + "metadata" "jsonb", + "zone" "text", + "assessment" "text", + CONSTRAINT "edifice_notes_type_check" CHECK (("type" = ANY (ARRAY['disorder'::"text", 'note'::"text", 'context'::"text", 'reservation'::"text"]))) +); + + +ALTER TABLE "public"."edifice_notes" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_notes" IS 'Project-level disorder characterization for reports'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_photos" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "project_id" "uuid" NOT NULL, + "uploaded_by" "uuid", + "storage_path" "text" NOT NULL, + "original_filename" "text", + "file_size" integer, + "mime_type" "text", + "width" integer NOT NULL, + "height" integer NOT NULL, + "rotation" integer DEFAULT 0, + "crop_region" "jsonb", + "roboflow_image_id" "text", + "roboflow_sync_status" "text" DEFAULT 'pending'::"text", + "exif_data" "jsonb", + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"(), + "category" "public"."edifice_photo_category" DEFAULT 'disorder'::"public"."edifice_photo_category", + "caption" "text", + "message_id" "uuid", + "note_id" "uuid", + CONSTRAINT "edifice_photos_roboflow_sync_status_check" CHECK (("roboflow_sync_status" = ANY (ARRAY['pending'::"text", 'synced'::"text", 'error'::"text"]))), + CONSTRAINT "edifice_photos_rotation_check" CHECK (("rotation" = ANY (ARRAY[0, 90, 180, 270]))) +); + + +ALTER TABLE "public"."edifice_photos" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_photos" IS 'Original photos with non-destructive editing metadata'; + + + +COMMENT ON COLUMN "public"."edifice_photos"."rotation" IS 'Rotation in degrees: 0, 90, 180, 270'; + + + +COMMENT ON COLUMN "public"."edifice_photos"."crop_region" IS 'Normalized crop {x, y, width, height} relative to original'; + + + +COMMENT ON COLUMN "public"."edifice_photos"."category" IS 'Photo usage category: exterior/context for building presentation, disorder for specific issues'; + + + +COMMENT ON COLUMN "public"."edifice_photos"."caption" IS 'Optional caption for photo (used in reports)'; + + + +COMMENT ON COLUMN "public"."edifice_photos"."note_id" IS 'Direct note link — set by PWA sync or when photo is associated with a note. Single source of truth for photo↔note relation.'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_profiles" ( + "id" "uuid" NOT NULL, + "organization_id" "uuid", + "full_name" "text" NOT NULL, + "email" "text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."edifice_profiles" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_profiles" IS 'User profiles extending Supabase auth.users'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_project_documents" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "project_id" "uuid" NOT NULL, + "uploaded_by" "uuid", + "storage_path" "text" NOT NULL, + "file_name" "text" NOT NULL, + "file_type" "text", + "file_size" integer, + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."edifice_project_documents" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_project_documents" IS 'Project document attachments stored in edifice-documents bucket'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_report_notes" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "report_id" "uuid" NOT NULL, + "note_id" "uuid" NOT NULL, + "display_order" integer, + "photo_ids" "uuid"[], + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."edifice_report_notes" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_report_notes" IS 'Many-to-many link between reports and disorders'; + + + +COMMENT ON COLUMN "public"."edifice_report_notes"."photo_ids" IS 'Specific photos for this disorder in this report (overrides disorder default)'; + + + +CREATE TABLE IF NOT EXISTS "public"."edifice_reports" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "project_id" "uuid" NOT NULL, + "report_type" "text" DEFAULT 'diagnostic'::"text", + "visit_date" "date", + "generated_by" "uuid", + "contexte" "text", + "detail_visite" "text", + "investigation_methods" "text", + "description_batiment" "text", + "observations" "text", + "conclusions" "text", + "recommandations" "text", + "status" "text" DEFAULT 'draft'::"text", + "docx_storage_path" "text", + "docx_file_name" "text", + "docx_generated_at" timestamp with time zone, + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"(), + "docx_filename" "text", + "generated_at" timestamp with time zone, + "enriched_at" timestamp with time zone, + "enriched_by" "text", + CONSTRAINT "edifice_reports_report_type_check" CHECK (("report_type" = ANY (ARRAY['diagnostic'::"text", 'follow_up'::"text", 'final'::"text", 'suivi_chantier'::"text", 'devis'::"text", 'estimation_pluriannuelle'::"text", 'cr_visite'::"text", 'reception'::"text"]))), + CONSTRAINT "edifice_reports_status_check" CHECK (("status" = ANY (ARRAY['draft'::"text", 'ready'::"text", 'generated'::"text", 'archived'::"text"]))) +); + + +ALTER TABLE "public"."edifice_reports" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."edifice_reports" IS 'Diagnostic reports with AI-generated sections and workflow states'; + + + +COMMENT ON COLUMN "public"."edifice_reports"."description_batiment" IS 'Copy of building description (editable per report, initially copied from edifice_buildings.description)'; + + + +COMMENT ON COLUMN "public"."edifice_reports"."status" IS 'Workflow: draft → ready → generated → archived'; + + + +COMMENT ON COLUMN "public"."edifice_reports"."generated_at" IS 'Timestamp when the DOCX report was last generated'; + + + +COMMENT ON COLUMN "public"."edifice_reports"."enriched_at" IS 'Last time the report content was updated from a Cowork enrichment session.'; + + + +COMMENT ON COLUMN "public"."edifice_reports"."enriched_by" IS 'Identifier of the Cowork session that wrote the update. Free text, for audit.'; + + + +COMMENT ON CONSTRAINT "edifice_reports_report_type_check" ON "public"."edifice_reports" IS 'Unified deliverable taxonomy per docs/prd-v2.md. Extend this list when a new mission output type ships; keep values in snake_case French where possible.'; + + + +CREATE OR REPLACE VIEW "public"."facade_stats" AS + SELECT "f"."facade_id", + "f"."building_name", + "f"."facade_name", + "count"(DISTINCT "p"."id") AS "total_photos", + "count"(DISTINCT "d"."id") AS "total_disorders", + "count"(DISTINCT + CASE + WHEN (("d"."severity")::"text" = 'Élevé'::"text") THEN "d"."id" + ELSE NULL::"uuid" + END) AS "high_severity_disorders", + "count"(DISTINCT + CASE + WHEN (("d"."severity")::"text" = 'Moyen'::"text") THEN "d"."id" + ELSE NULL::"uuid" + END) AS "medium_severity_disorders", + "count"(DISTINCT + CASE + WHEN (("d"."severity")::"text" = 'Faible'::"text") THEN "d"."id" + ELSE NULL::"uuid" + END) AS "low_severity_disorders" + FROM ((("public"."facades" "f" + LEFT JOIN "public"."intersections" "i" ON (("f"."id" = "i"."facade_id"))) + LEFT JOIN "public"."photos" "p" ON (("i"."photo_id" = "p"."id"))) + LEFT JOIN "public"."disorders" "d" ON (("p"."id" = "d"."photo_id"))) + GROUP BY "f"."facade_id", "f"."building_name", "f"."facade_name"; + + +ALTER VIEW "public"."facade_stats" OWNER TO "postgres"; + + +COMMENT ON VIEW "public"."facade_stats" IS 'Statistics summary for each facade including photo and disorder counts. Updated in migration 011 to use intersections table instead of obsolete photos.facade_id column.'; + + + +CREATE TABLE IF NOT EXISTS "public"."facade_vectors" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "residence_id" "uuid", + "facade_id" "text" NOT NULL, + "building_name" "text" NOT NULL, + "facade_name" "text" NOT NULL, + "normal_x" double precision NOT NULL, + "normal_y" double precision NOT NULL, + "normal_z" double precision DEFAULT 0.0, + "geometry" "public"."geometry"(PointZ,2154) NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"(), + "updated_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."facade_vectors" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."facade_vectors" IS 'Points géométriques au centre des façades avec vecteurs normaux pour visualisation QGIS'; + + + +COMMENT ON COLUMN "public"."facade_vectors"."facade_id" IS 'Identifiant unique de la façade (ex: jb_clement_A_f1)'; + + + +COMMENT ON COLUMN "public"."facade_vectors"."building_name" IS 'Nom du bâtiment (ex: A, B, C)'; + + + +COMMENT ON COLUMN "public"."facade_vectors"."facade_name" IS 'Nom de la façade avec orientation (ex: North Facade)'; + + + +COMMENT ON COLUMN "public"."facade_vectors"."normal_x" IS 'Composante X du vecteur normal unitaire'; + + + +COMMENT ON COLUMN "public"."facade_vectors"."normal_y" IS 'Composante Y du vecteur normal unitaire'; + + + +COMMENT ON COLUMN "public"."facade_vectors"."normal_z" IS 'Composante Z du vecteur normal (toujours 0 pour façades 2D)'; + + + +COMMENT ON COLUMN "public"."facade_vectors"."geometry" IS 'Point géométrique au centre de la façade (EPSG:2154 - Lambert 93)'; + + + +CREATE TABLE IF NOT EXISTS "public"."halcrm_companies" ( + "id" "text" NOT NULL, + "workspace_slug" "text" NOT NULL, + "name" "text" NOT NULL, + "bg_id" integer, + "type" "text", + "ecosystem" "text", + "activity_type" "text", + "address" "text", + "website" "text", + "notes" "text", + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL +); + + +ALTER TABLE "public"."halcrm_companies" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."halcrm_contacts" ( + "id" "text" NOT NULL, + "workspace_slug" "text" NOT NULL, + "company_id" "text", + "name" "text" NOT NULL, + "role" "text", + "email" "text", + "phone" "text", + "linkedin" "text", + "tone" "text", + "tags" "text"[] DEFAULT '{}'::"text"[], + "notes" "text", + "last_contact_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 +); + + +ALTER TABLE "public"."halcrm_contacts" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."halcrm_documents" ( + "id" "uuid" DEFAULT "gen_random_uuid"() NOT NULL, + "workspace_slug" "text" NOT NULL, + "slug" "text" NOT NULL, + "domain" "text" NOT NULL, + "kind" "text" NOT NULL, + "title" "text" NOT NULL, + "person_name" "text", + "content_md" "text", + "facts" "jsonb" DEFAULT '{}'::"jsonb" NOT NULL, + "storage_path" "text", + "mime_type" "text", + "issued_date" "date", + "valid_until" "date", + "sensitive" 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 "halcrm_documents_content_check" CHECK ((("content_md" IS NOT NULL) OR ("storage_path" IS NOT NULL))), + CONSTRAINT "halcrm_documents_mime_check" CHECK ((("storage_path" IS NULL) OR ("mime_type" IS NOT NULL))) +); + + +ALTER TABLE "public"."halcrm_documents" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."halcrm_interactions" ( + "id" "text" NOT NULL, + "workspace_slug" "text" NOT NULL, + "project_id" "text", + "contact_id" "text", + "channel" "text" NOT NULL, + "occurred_at" timestamp with time zone NOT NULL, + "summary" "text" NOT NULL, + "created_by" "text", + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "transcript" "text", + "tags" "text"[] +); + + +ALTER TABLE "public"."halcrm_interactions" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."halcrm_projects" ( + "id" "text" NOT NULL, + "workspace_slug" "text" NOT NULL, + "name" "text" NOT NULL, + "company_id" "text", + "primary_contact_id" "text", + "stage" "text" NOT NULL, + "kind" "text", + "location" "text", + "description" "text", + "amount_ht" numeric, + "currency" "text" DEFAULT 'EUR'::"text", + "edifice_building_id" "text", + "edifice_mission_id" "text", + "won_at" timestamp with time zone, + "closed_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, + "due_date" "date", + "project_ref" "text", + "tags" "text"[] DEFAULT '{}'::"text"[] NOT NULL +); + + +ALTER TABLE "public"."halcrm_projects" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."halcrm_sprints" ( + "id" "text" NOT NULL, + "workspace_slug" "text" NOT NULL, + "name" "text" NOT NULL, + "sprint_number" integer NOT NULL, + "status" "text" DEFAULT 'a_venir'::"text" NOT NULL, + "starts_at" "date", + "ends_at" "date", + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL +); + + +ALTER TABLE "public"."halcrm_sprints" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."halcrm_tasks" ( + "id" "text" NOT NULL, + "workspace_slug" "text" NOT NULL, + "project_id" "text", + "title" "text" NOT NULL, + "description" "text", + "external_ref" "text", + "due_date" "date", + "priority" "text", + "status" "text" DEFAULT 'todo'::"text" NOT NULL, + "sprint_id" "text", + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "assignee_user_id" "uuid", + "tags" "text"[] DEFAULT '{}'::"text"[] NOT NULL, + CONSTRAINT "halcrm_tasks_status_check" CHECK (("status" = ANY (ARRAY['todo'::"text", 'in_progress'::"text", 'done'::"text", 'blocked'::"text"]))) +); + + +ALTER TABLE "public"."halcrm_tasks" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."halcrm_workspaces" ( + "workspace_slug" "text" NOT NULL, + "name" "text" NOT NULL, + "edifice_enabled" boolean DEFAULT false NOT NULL, + "sprints_enabled" boolean DEFAULT false NOT NULL, + "sprints_cadence" "text" DEFAULT 'weekly'::"text" NOT NULL, + "members" "jsonb" DEFAULT '[]'::"jsonb" NOT NULL, + "model" "text" DEFAULT 'claude-haiku-4-5'::"text" NOT NULL, + "agents_md" "text", + "soul_md" "text", + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "updated_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "company_id" "uuid", + "kind_stages" "jsonb" NOT NULL, + "allowed_tags" "text"[] DEFAULT '{}'::"text"[] NOT NULL +); + + +ALTER TABLE "public"."halcrm_workspaces" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."qgis_projects" ( + "name" "text" NOT NULL, + "metadata" "jsonb", + "content" "bytea" +); + + +ALTER TABLE "public"."qgis_projects" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."shared_app_access" ( + "user_id" "uuid" NOT NULL, + "app_id" "text" NOT NULL, + "role" "text" DEFAULT 'user'::"text", + "created_at" timestamp with time zone DEFAULT "now"() +); + + +ALTER TABLE "public"."shared_app_access" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."shared_app_access" IS 'Multi-app access control. Each row grants a user access to a specific app (bluewind, edifice, etc.)'; + + + +COMMENT ON COLUMN "public"."shared_app_access"."app_id" IS 'App identifier: bluewind, edifice'; + + + +COMMENT ON COLUMN "public"."shared_app_access"."role" IS 'User role within the app (user, admin) - for future use'; + + + +CREATE OR REPLACE VIEW "public"."shared_users_access_view" AS + SELECT "u"."id" AS "user_id", + "u"."email", + "u"."created_at" AS "user_created", + ("u"."email_confirmed_at" IS NOT NULL) AS "is_confirmed", + COALESCE("array_agg"("a"."app_id" ORDER BY "a"."app_id") FILTER (WHERE ("a"."app_id" IS NOT NULL)), ARRAY[]::"text"[]) AS "apps" + FROM ("auth"."users" "u" + LEFT JOIN "public"."shared_app_access" "a" ON (("u"."id" = "a"."user_id"))) + GROUP BY "u"."id", "u"."email", "u"."created_at", "u"."email_confirmed_at" + ORDER BY "u"."created_at" DESC; + + +ALTER VIEW "public"."shared_users_access_view" OWNER TO "postgres"; + + +COMMENT ON VIEW "public"."shared_users_access_view" IS 'Vue des utilisateurs avec leurs accès apps. Apps disponibles: bluewind, edifice'; + + + +CREATE OR REPLACE VIEW "public"."visit_summary" AS + SELECT "v"."id" AS "visit_id", + "v"."visit_date", + "v"."visit_time", + "v"."status" AS "visit_status", + "r"."id" AS "residence_id", + "r"."name" AS "residence_name", + "r"."address" AS "residence_address", + "b"."id" AS "building_id", + "b"."building_name", + "b"."building_id" AS "building_string_id", + "f"."id" AS "facade_id", + "f"."facade_name", + "f"."facade_id" AS "facade_string_id", + "count"(DISTINCT "i"."photo_id") AS "photo_count", + "min"(("p"."filename")::"text") AS "first_photo_filename", + "max"(("p"."filename")::"text") AS "last_photo_filename", + "count"(DISTINCT "d"."id") AS "disorder_count", + "string_agg"(DISTINCT ("i"."surface_type")::"text", ', '::"text") AS "surface_types", + "min"("i"."distance") AS "min_distance", + "max"("i"."distance") AS "max_distance", + "avg"("i"."distance") AS "avg_distance", + "concat"("b"."building_name", ' - ', "f"."facade_name") AS "facade_display_name", + "concat"("v"."visit_date", ' ', COALESCE(("v"."visit_time")::"text", ''::"text")) AS "visit_display_name" + FROM (((((("public"."visits" "v" + JOIN "public"."residences" "r" ON (("v"."residence_id" = "r"."id"))) + JOIN "public"."buildings" "b" ON (("b"."residence_id" = "r"."id"))) + JOIN "public"."facades" "f" ON (("f"."building_id" = "b"."id"))) + LEFT JOIN "public"."intersections" "i" ON ((("i"."facade_id" = "f"."id") AND ("i"."building_id" = "b"."id")))) + LEFT JOIN "public"."photos" "p" ON ((("p"."id" = "i"."photo_id") AND ("p"."visit_id" = "v"."id")))) + LEFT JOIN "public"."disorders" "d" ON (("d"."photo_id" = "p"."id"))) + GROUP BY "v"."id", "v"."visit_date", "v"."visit_time", "v"."status", "r"."id", "r"."name", "r"."address", "b"."id", "b"."building_name", "b"."building_id", "f"."id", "f"."facade_name", "f"."facade_id" + ORDER BY "r"."name", "b"."building_name", "f"."facade_name"; + + +ALTER VIEW "public"."visit_summary" OWNER TO "postgres"; + + +COMMENT ON VIEW "public"."visit_summary" IS 'Consolidated view for buildingWatchApi providing all visit-related data in a single query. Includes residence, building, facade, photo counts (via intersections), and disorder counts with friendly display names. facade_display_name shows "Building - Facade" format without residence prefix for cleaner UI. Note: photos are linked to facades through the intersections table, not directly.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_commodities" ( + "timestamp" timestamp with time zone NOT NULL, + "commodity" "text" NOT NULL, + "price" double precision NOT NULL, + "currency" "text" DEFAULT 'EUR'::"text" NOT NULL, + "source" "text" DEFAULT 'manual'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL +); + + +ALTER TABLE "public"."wcast_commodities" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_commodities" IS 'TTF natural gas and CO2 EUA prices used as model features.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_cross_border_flows" ( + "timestamp" timestamp with time zone NOT NULL, + "border" "text" NOT NULL, + "flow_mw" double precision NOT NULL, + "source" "text" DEFAULT 'entsoe'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL +); + + +ALTER TABLE "public"."wcast_cross_border_flows" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_cross_border_flows" IS 'Physical cross-border electricity flows between France and neighbors. Source: ENTSO-E Transparency Platform.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_forecasts" ( + "generated_at" timestamp with time zone NOT NULL, + "delivery_timestamp" timestamp with time zone NOT NULL, + "horizon_days" smallint NOT NULL, + "quantile" double precision NOT NULL, + "price_eur_mwh" double precision NOT NULL, + "model_version" "text" DEFAULT 'v1'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + CONSTRAINT "wcast_forecasts_horizon_range" CHECK ((("horizon_days" >= 1) AND ("horizon_days" <= 7))), + CONSTRAINT "wcast_forecasts_quantile_range" CHECK ((("quantile" >= (0)::double precision) AND ("quantile" <= (1)::double precision))) +); + + +ALTER TABLE "public"."wcast_forecasts" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_forecasts" IS 'Probabilistic (quantile) Day-Ahead electricity price forecasts. 7 horizons x 5 quantiles per generation run.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_generation" ( + "timestamp" timestamp with time zone NOT NULL, + "production_type" "text" NOT NULL, + "actual_mw" double precision, + "forecast_mw" double precision, + "source" "text" DEFAULT 'entsoe'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + CONSTRAINT "wcast_generation_not_both_null" CHECK ((("actual_mw" IS NOT NULL) OR ("forecast_mw" IS NOT NULL))) +); + + +ALTER TABLE "public"."wcast_generation" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_generation" IS 'Actual and forecast electricity generation by production type for France. Sources: ENTSO-E, RTE.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_load" ( + "timestamp" timestamp with time zone NOT NULL, + "load_mw" double precision, + "forecast_mw" double precision, + "source" "text" DEFAULT 'entsoe'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + CONSTRAINT "wcast_load_not_both_null" CHECK ((("load_mw" IS NOT NULL) OR ("forecast_mw" IS NOT NULL))) +); + + +ALTER TABLE "public"."wcast_load" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_load" IS 'Actual and D+1 forecast electricity load for France. Sources: ENTSO-E, RTE.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_neighbor_prices" ( + "timestamp" timestamp with time zone NOT NULL, + "country" "text" NOT NULL, + "price_eur_mwh" double precision NOT NULL, + "source" "text" DEFAULT 'entsoe'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL +); + + +ALTER TABLE "public"."wcast_neighbor_prices" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_neighbor_prices" IS 'Day-ahead spot prices for France neighbor countries. Source: ENTSO-E Transparency Platform.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_nuclear_availability" ( + "timestamp" timestamp with time zone NOT NULL, + "unavailable_mw" double precision NOT NULL, + "available_mw" double precision NOT NULL, + "installed_mw" double precision NOT NULL, + "source" "text" DEFAULT 'rte_unavailability'::"text" NOT NULL +); + + +ALTER TABLE "public"."wcast_nuclear_availability" OWNER TO "postgres"; + + +CREATE TABLE IF NOT EXISTS "public"."wcast_spot_prices" ( + "timestamp" timestamp with time zone NOT NULL, + "price_eur_mwh" double precision NOT NULL, + "source" "text" DEFAULT 'entsoe'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL +); + + +ALTER TABLE "public"."wcast_spot_prices" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_spot_prices" IS 'EPEX SPOT Day-Ahead hourly prices for France. Source: ENTSO-E Transparency Platform.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_weather" ( + "timestamp" timestamp with time zone NOT NULL, + "location" "text" NOT NULL, + "wind_speed_100m" double precision, + "wind_direction_100m" double precision, + "temperature_2m" double precision, + "shortwave_radiation" double precision, + "direct_radiation" double precision, + "diffuse_radiation" double precision, + "source" "text" DEFAULT 'open_meteo'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL +); + + +ALTER TABLE "public"."wcast_weather" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_weather" IS 'Hourly weather data (observations and NWP forecasts) at French grid locations. Source: Open-Meteo.'; + + + +CREATE TABLE IF NOT EXISTS "public"."wcast_weather_forecast" ( + "timestamp" timestamp with time zone NOT NULL, + "location" "text" NOT NULL, + "wind_speed_100m" double precision, + "wind_direction_100m" double precision, + "temperature_2m" double precision, + "shortwave_radiation" double precision, + "direct_radiation" double precision, + "diffuse_radiation" double precision, + "source" "text" DEFAULT 'open_meteo_forecast'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL +); + + +ALTER TABLE "public"."wcast_weather_forecast" OWNER TO "postgres"; + + +COMMENT ON TABLE "public"."wcast_weather_forecast" IS 'Hourly NWP forecast model output at French grid locations. Source: Open-Meteo Historical Forecast API (2022+). Used as future covariates for delivery-day weather.'; + + + +CREATE TABLE IF NOT EXISTS "public"."workspace_members" ( + "workspace_slug" "text" NOT NULL, + "user_id" "uuid" NOT NULL, + "role" "text" DEFAULT 'member'::"text" NOT NULL, + "created_at" timestamp with time zone DEFAULT "now"() NOT NULL, + "is_default" boolean DEFAULT false NOT NULL +); + + +ALTER TABLE "public"."workspace_members" OWNER TO "postgres"; + + +ALTER TABLE ONLY "auth"."refresh_tokens" ALTER COLUMN "id" SET DEFAULT "nextval"('"auth"."refresh_tokens_id_seq"'::"regclass"); + + + +ALTER TABLE ONLY "public"."bwc_jurisdiction_attributes" ALTER COLUMN "id" SET DEFAULT "nextval"('"public"."bwc_jurisdiction_attributes_id_seq"'::"regclass"); + + + +ALTER TABLE ONLY "auth"."mfa_amr_claims" + ADD CONSTRAINT "amr_id_pk" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."audit_log_entries" + ADD CONSTRAINT "audit_log_entries_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."custom_oauth_providers" + ADD CONSTRAINT "custom_oauth_providers_identifier_key" UNIQUE ("identifier"); + + + +ALTER TABLE ONLY "auth"."custom_oauth_providers" + ADD CONSTRAINT "custom_oauth_providers_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."flow_state" + ADD CONSTRAINT "flow_state_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."identities" + ADD CONSTRAINT "identities_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."identities" + ADD CONSTRAINT "identities_provider_id_provider_unique" UNIQUE ("provider_id", "provider"); + + + +ALTER TABLE ONLY "auth"."instances" + ADD CONSTRAINT "instances_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."mfa_amr_claims" + ADD CONSTRAINT "mfa_amr_claims_session_id_authentication_method_pkey" UNIQUE ("session_id", "authentication_method"); + + + +ALTER TABLE ONLY "auth"."mfa_challenges" + ADD CONSTRAINT "mfa_challenges_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."mfa_factors" + ADD CONSTRAINT "mfa_factors_last_challenged_at_key" UNIQUE ("last_challenged_at"); + + + +ALTER TABLE ONLY "auth"."mfa_factors" + ADD CONSTRAINT "mfa_factors_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."oauth_authorizations" + ADD CONSTRAINT "oauth_authorizations_authorization_code_key" UNIQUE ("authorization_code"); + + + +ALTER TABLE ONLY "auth"."oauth_authorizations" + ADD CONSTRAINT "oauth_authorizations_authorization_id_key" UNIQUE ("authorization_id"); + + + +ALTER TABLE ONLY "auth"."oauth_authorizations" + ADD CONSTRAINT "oauth_authorizations_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."oauth_client_states" + ADD CONSTRAINT "oauth_client_states_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."oauth_clients" + ADD CONSTRAINT "oauth_clients_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."oauth_consents" + ADD CONSTRAINT "oauth_consents_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."oauth_consents" + ADD CONSTRAINT "oauth_consents_user_client_unique" UNIQUE ("user_id", "client_id"); + + + +ALTER TABLE ONLY "auth"."one_time_tokens" + ADD CONSTRAINT "one_time_tokens_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."refresh_tokens" + ADD CONSTRAINT "refresh_tokens_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."refresh_tokens" + ADD CONSTRAINT "refresh_tokens_token_unique" UNIQUE ("token"); + + + +ALTER TABLE ONLY "auth"."saml_providers" + ADD CONSTRAINT "saml_providers_entity_id_key" UNIQUE ("entity_id"); + + + +ALTER TABLE ONLY "auth"."saml_providers" + ADD CONSTRAINT "saml_providers_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."saml_relay_states" + ADD CONSTRAINT "saml_relay_states_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."schema_migrations" + ADD CONSTRAINT "schema_migrations_pkey" PRIMARY KEY ("version"); + + + +ALTER TABLE ONLY "auth"."sessions" + ADD CONSTRAINT "sessions_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."sso_domains" + ADD CONSTRAINT "sso_domains_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."sso_providers" + ADD CONSTRAINT "sso_providers_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."users" + ADD CONSTRAINT "users_phone_key" UNIQUE ("phone"); + + + +ALTER TABLE ONLY "auth"."users" + ADD CONSTRAINT "users_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."webauthn_challenges" + ADD CONSTRAINT "webauthn_challenges_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "auth"."webauthn_credentials" + ADD CONSTRAINT "webauthn_credentials_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bgign_bdtopo_buildings" + ADD CONSTRAINT "bgign_bdtopo_buildings_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bgign_datasets" + ADD CONSTRAINT "bgign_datasets_code_key" UNIQUE ("code"); + + + +ALTER TABLE ONLY "public"."bgign_datasets" + ADD CONSTRAINT "bgign_datasets_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bgign_department_coverage" + ADD CONSTRAINT "bgign_department_coverage_dataset_code_department_layer_key" UNIQUE ("dataset_code", "department", "layer"); + + + +ALTER TABLE ONLY "public"."bgign_department_coverage" + ADD CONSTRAINT "bgign_department_coverage_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bgign_download_queue" + ADD CONSTRAINT "bgign_download_queue_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."buildings" + ADD CONSTRAINT "buildings_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bwc_chunks" + ADD CONSTRAINT "bwc_chunks_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bwc_client_accounts" + ADD CONSTRAINT "bwc_client_accounts_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bwc_client_accounts" + ADD CONSTRAINT "bwc_client_accounts_slug_key" UNIQUE ("slug"); + + + +ALTER TABLE ONLY "public"."bwc_client_users" + ADD CONSTRAINT "bwc_client_users_pkey" PRIMARY KEY ("user_id", "client_id"); + + + +ALTER TABLE ONLY "public"."bwc_definitions" + ADD CONSTRAINT "bwc_definitions_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bwc_definitions" + ADD CONSTRAINT "bwc_definitions_tender_id_term_key" UNIQUE ("tender_id", "term"); + + + +ALTER TABLE ONLY "public"."bwc_documents" + ADD CONSTRAINT "bwc_documents_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bwc_jurisdiction_attributes" + ADD CONSTRAINT "bwc_jurisdiction_attributes_jurisdiction_tender_id_domain_c_key" UNIQUE ("jurisdiction", "tender_id", "domain", "category", "attribute_key"); + + + +ALTER TABLE ONLY "public"."bwc_jurisdiction_attributes" + ADD CONSTRAINT "bwc_jurisdiction_attributes_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bwc_projects" + ADD CONSTRAINT "bwc_projects_pkey" PRIMARY KEY ("project_id"); + + + +ALTER TABLE ONLY "public"."bwc_reports" + ADD CONSTRAINT "bwc_reports_download_token_key" UNIQUE ("download_token"); + + + +ALTER TABLE ONLY "public"."bwc_reports" + ADD CONSTRAINT "bwc_reports_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bwc_strategy_metrics" + ADD CONSTRAINT "bwc_strategy_metrics_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."bwc_tender_registry" + ADD CONSTRAINT "bwc_tender_registry_pkey" PRIMARY KEY ("tender_id"); + + + +ALTER TABLE ONLY "public"."bwc_usage_logs" + ADD CONSTRAINT "bwc_usage_logs_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."companies" + ADD CONSTRAINT "companies_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."companies" + ADD CONSTRAINT "companies_slug_key" UNIQUE ("slug"); + + + +ALTER TABLE ONLY "public"."disorder_catalog" + ADD CONSTRAINT "disorder_catalog_disorder_id_key" UNIQUE ("disorder_id"); + + + +ALTER TABLE ONLY "public"."disorder_catalog" + ADD CONSTRAINT "disorder_catalog_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."disorders" + ADD CONSTRAINT "disorders_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."drone_positions" + ADD CONSTRAINT "drone_positions_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_annotations" + ADD CONSTRAINT "edifice_annotations_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_buildings" + ADD CONSTRAINT "edifice_buildings_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_component_types" + ADD CONSTRAINT "edifice_component_types_code_key" UNIQUE ("code"); + + + +ALTER TABLE ONLY "public"."edifice_component_types" + ADD CONSTRAINT "edifice_component_types_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_component_types" + ADD CONSTRAINT "edifice_component_types_yolo_class_id_key" UNIQUE ("yolo_class_id"); + + + +ALTER TABLE ONLY "public"."edifice_disorder_types" + ADD CONSTRAINT "edifice_disorder_types_code_key" UNIQUE ("code"); + + + +ALTER TABLE ONLY "public"."edifice_disorder_types" + ADD CONSTRAINT "edifice_disorder_types_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_disorder_types" + ADD CONSTRAINT "edifice_disorder_types_yolo_class_id_key" UNIQUE ("yolo_class_id"); + + + +ALTER TABLE ONLY "public"."edifice_notes" + ADD CONSTRAINT "edifice_disorders_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_enum_labels" + ADD CONSTRAINT "edifice_enum_labels_enum_type_enum_value_key" UNIQUE ("enum_type", "enum_value"); + + + +ALTER TABLE ONLY "public"."edifice_enum_labels" + ADD CONSTRAINT "edifice_enum_labels_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_messages" + ADD CONSTRAINT "edifice_messages_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_photos" + ADD CONSTRAINT "edifice_photos_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_profiles" + ADD CONSTRAINT "edifice_profiles_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_project_documents" + ADD CONSTRAINT "edifice_project_documents_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_projects" + ADD CONSTRAINT "edifice_projects_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_report_notes" + ADD CONSTRAINT "edifice_report_disorders_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."edifice_reports" + ADD CONSTRAINT "edifice_reports_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."facade_vectors" + ADD CONSTRAINT "facade_vectors_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."facades" + ADD CONSTRAINT "facades_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."halcrm_companies" + ADD CONSTRAINT "halcrm_companies_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."halcrm_companies" + ADD CONSTRAINT "halcrm_companies_workspace_name_key" UNIQUE ("workspace_slug", "name"); + + + +ALTER TABLE ONLY "public"."halcrm_contacts" + ADD CONSTRAINT "halcrm_contacts_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."halcrm_documents" + ADD CONSTRAINT "halcrm_documents_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."halcrm_documents" + ADD CONSTRAINT "halcrm_documents_workspace_slug_slug_key" UNIQUE ("workspace_slug", "slug"); + + + +ALTER TABLE ONLY "public"."halcrm_interactions" + ADD CONSTRAINT "halcrm_interactions_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."halcrm_projects" + ADD CONSTRAINT "halcrm_missions_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."halcrm_projects" + ADD CONSTRAINT "halcrm_missions_workspace_name_key" UNIQUE ("workspace_slug", "name"); + + + +ALTER TABLE ONLY "public"."halcrm_sprints" + ADD CONSTRAINT "halcrm_sprints_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."halcrm_tasks" + ADD CONSTRAINT "halcrm_tasks_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."halcrm_workspaces" + ADD CONSTRAINT "halcrm_workspaces_company_id_key" UNIQUE ("company_id"); + + + +ALTER TABLE ONLY "public"."halcrm_workspaces" + ADD CONSTRAINT "halcrm_workspaces_pkey" PRIMARY KEY ("workspace_slug"); + + + +ALTER TABLE ONLY "public"."intersections" + ADD CONSTRAINT "intersections_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."photos" + ADD CONSTRAINT "photos_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."projects" + ADD CONSTRAINT "projects_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."qgis_projects" + ADD CONSTRAINT "qgis_projects_pkey" PRIMARY KEY ("name"); + + + +ALTER TABLE ONLY "public"."residences" + ADD CONSTRAINT "residences_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."shared_app_access" + ADD CONSTRAINT "shared_app_access_pkey" PRIMARY KEY ("user_id", "app_id"); + + + +ALTER TABLE ONLY "public"."bwc_strategy_metrics" + ADD CONSTRAINT "unique_metric" UNIQUE ("source_name", "metric_name", "scenario", "sector", "energy_source", "year", "period"); + + + +ALTER TABLE ONLY "public"."visits" + ADD CONSTRAINT "visits_pkey" PRIMARY KEY ("id"); + + + +ALTER TABLE ONLY "public"."wcast_commodities" + ADD CONSTRAINT "wcast_commodities_pkey" PRIMARY KEY ("timestamp", "commodity", "source"); + + + +ALTER TABLE ONLY "public"."wcast_cross_border_flows" + ADD CONSTRAINT "wcast_cross_border_flows_pkey" PRIMARY KEY ("timestamp", "border", "source"); + + + +ALTER TABLE ONLY "public"."wcast_forecasts" + ADD CONSTRAINT "wcast_forecasts_pkey" PRIMARY KEY ("generated_at", "delivery_timestamp", "horizon_days", "quantile", "model_version"); + + + +ALTER TABLE ONLY "public"."wcast_generation" + ADD CONSTRAINT "wcast_generation_pkey" PRIMARY KEY ("timestamp", "production_type", "source"); + + + +ALTER TABLE ONLY "public"."wcast_load" + ADD CONSTRAINT "wcast_load_pkey" PRIMARY KEY ("timestamp", "source"); + + + +ALTER TABLE ONLY "public"."wcast_neighbor_prices" + ADD CONSTRAINT "wcast_neighbor_prices_pkey" PRIMARY KEY ("timestamp", "country", "source"); + + + +ALTER TABLE ONLY "public"."wcast_nuclear_availability" + ADD CONSTRAINT "wcast_nuclear_availability_timestamp_source_key" UNIQUE ("timestamp", "source"); + + + +ALTER TABLE ONLY "public"."wcast_spot_prices" + ADD CONSTRAINT "wcast_spot_prices_pkey" PRIMARY KEY ("timestamp", "source"); + + + +ALTER TABLE ONLY "public"."wcast_weather_forecast" + ADD CONSTRAINT "wcast_weather_forecast_pkey" PRIMARY KEY ("timestamp", "location", "source"); + + + +ALTER TABLE ONLY "public"."wcast_weather" + ADD CONSTRAINT "wcast_weather_pkey" PRIMARY KEY ("timestamp", "location", "source"); + + + +ALTER TABLE ONLY "public"."workspace_members" + ADD CONSTRAINT "workspace_members_pkey" PRIMARY KEY ("workspace_slug", "user_id"); + + + +CREATE INDEX "audit_logs_instance_id_idx" ON "auth"."audit_log_entries" USING "btree" ("instance_id"); + + + +CREATE UNIQUE INDEX "confirmation_token_idx" ON "auth"."users" USING "btree" ("confirmation_token") WHERE (("confirmation_token")::"text" !~ '^[0-9 ]*$'::"text"); + + + +CREATE INDEX "custom_oauth_providers_created_at_idx" ON "auth"."custom_oauth_providers" USING "btree" ("created_at"); + + + +CREATE INDEX "custom_oauth_providers_enabled_idx" ON "auth"."custom_oauth_providers" USING "btree" ("enabled"); + + + +CREATE INDEX "custom_oauth_providers_identifier_idx" ON "auth"."custom_oauth_providers" USING "btree" ("identifier"); + + + +CREATE INDEX "custom_oauth_providers_provider_type_idx" ON "auth"."custom_oauth_providers" USING "btree" ("provider_type"); + + + +CREATE UNIQUE INDEX "email_change_token_current_idx" ON "auth"."users" USING "btree" ("email_change_token_current") WHERE (("email_change_token_current")::"text" !~ '^[0-9 ]*$'::"text"); + + + +CREATE UNIQUE INDEX "email_change_token_new_idx" ON "auth"."users" USING "btree" ("email_change_token_new") WHERE (("email_change_token_new")::"text" !~ '^[0-9 ]*$'::"text"); + + + +CREATE INDEX "factor_id_created_at_idx" ON "auth"."mfa_factors" USING "btree" ("user_id", "created_at"); + + + +CREATE INDEX "flow_state_created_at_idx" ON "auth"."flow_state" USING "btree" ("created_at" DESC); + + + +CREATE INDEX "identities_email_idx" ON "auth"."identities" USING "btree" ("email" "text_pattern_ops"); + + + +COMMENT ON INDEX "auth"."identities_email_idx" IS 'Auth: Ensures indexed queries on the email column'; + + + +CREATE INDEX "identities_user_id_idx" ON "auth"."identities" USING "btree" ("user_id"); + + + +CREATE INDEX "idx_auth_code" ON "auth"."flow_state" USING "btree" ("auth_code"); + + + +CREATE INDEX "idx_oauth_client_states_created_at" ON "auth"."oauth_client_states" USING "btree" ("created_at"); + + + +CREATE INDEX "idx_user_id_auth_method" ON "auth"."flow_state" USING "btree" ("user_id", "authentication_method"); + + + +CREATE INDEX "mfa_challenge_created_at_idx" ON "auth"."mfa_challenges" USING "btree" ("created_at" DESC); + + + +CREATE UNIQUE INDEX "mfa_factors_user_friendly_name_unique" ON "auth"."mfa_factors" USING "btree" ("friendly_name", "user_id") WHERE (TRIM(BOTH FROM "friendly_name") <> ''::"text"); + + + +CREATE INDEX "mfa_factors_user_id_idx" ON "auth"."mfa_factors" USING "btree" ("user_id"); + + + +CREATE INDEX "oauth_auth_pending_exp_idx" ON "auth"."oauth_authorizations" USING "btree" ("expires_at") WHERE ("status" = 'pending'::"auth"."oauth_authorization_status"); + + + +CREATE INDEX "oauth_clients_deleted_at_idx" ON "auth"."oauth_clients" USING "btree" ("deleted_at"); + + + +CREATE INDEX "oauth_consents_active_client_idx" ON "auth"."oauth_consents" USING "btree" ("client_id") WHERE ("revoked_at" IS NULL); + + + +CREATE INDEX "oauth_consents_active_user_client_idx" ON "auth"."oauth_consents" USING "btree" ("user_id", "client_id") WHERE ("revoked_at" IS NULL); + + + +CREATE INDEX "oauth_consents_user_order_idx" ON "auth"."oauth_consents" USING "btree" ("user_id", "granted_at" DESC); + + + +CREATE INDEX "one_time_tokens_relates_to_hash_idx" ON "auth"."one_time_tokens" USING "hash" ("relates_to"); + + + +CREATE INDEX "one_time_tokens_token_hash_hash_idx" ON "auth"."one_time_tokens" USING "hash" ("token_hash"); + + + +CREATE UNIQUE INDEX "one_time_tokens_user_id_token_type_key" ON "auth"."one_time_tokens" USING "btree" ("user_id", "token_type"); + + + +CREATE UNIQUE INDEX "reauthentication_token_idx" ON "auth"."users" USING "btree" ("reauthentication_token") WHERE (("reauthentication_token")::"text" !~ '^[0-9 ]*$'::"text"); + + + +CREATE UNIQUE INDEX "recovery_token_idx" ON "auth"."users" USING "btree" ("recovery_token") WHERE (("recovery_token")::"text" !~ '^[0-9 ]*$'::"text"); + + + +CREATE INDEX "refresh_tokens_instance_id_idx" ON "auth"."refresh_tokens" USING "btree" ("instance_id"); + + + +CREATE INDEX "refresh_tokens_instance_id_user_id_idx" ON "auth"."refresh_tokens" USING "btree" ("instance_id", "user_id"); + + + +CREATE INDEX "refresh_tokens_parent_idx" ON "auth"."refresh_tokens" USING "btree" ("parent"); + + + +CREATE INDEX "refresh_tokens_session_id_revoked_idx" ON "auth"."refresh_tokens" USING "btree" ("session_id", "revoked"); + + + +CREATE INDEX "refresh_tokens_updated_at_idx" ON "auth"."refresh_tokens" USING "btree" ("updated_at" DESC); + + + +CREATE INDEX "saml_providers_sso_provider_id_idx" ON "auth"."saml_providers" USING "btree" ("sso_provider_id"); + + + +CREATE INDEX "saml_relay_states_created_at_idx" ON "auth"."saml_relay_states" USING "btree" ("created_at" DESC); + + + +CREATE INDEX "saml_relay_states_for_email_idx" ON "auth"."saml_relay_states" USING "btree" ("for_email"); + + + +CREATE INDEX "saml_relay_states_sso_provider_id_idx" ON "auth"."saml_relay_states" USING "btree" ("sso_provider_id"); + + + +CREATE INDEX "sessions_not_after_idx" ON "auth"."sessions" USING "btree" ("not_after" DESC); + + + +CREATE INDEX "sessions_oauth_client_id_idx" ON "auth"."sessions" USING "btree" ("oauth_client_id"); + + + +CREATE INDEX "sessions_user_id_idx" ON "auth"."sessions" USING "btree" ("user_id"); + + + +CREATE UNIQUE INDEX "sso_domains_domain_idx" ON "auth"."sso_domains" USING "btree" ("lower"("domain")); + + + +CREATE INDEX "sso_domains_sso_provider_id_idx" ON "auth"."sso_domains" USING "btree" ("sso_provider_id"); + + + +CREATE UNIQUE INDEX "sso_providers_resource_id_idx" ON "auth"."sso_providers" USING "btree" ("lower"("resource_id")); + + + +CREATE INDEX "sso_providers_resource_id_pattern_idx" ON "auth"."sso_providers" USING "btree" ("resource_id" "text_pattern_ops"); + + + +CREATE UNIQUE INDEX "unique_phone_factor_per_user" ON "auth"."mfa_factors" USING "btree" ("user_id", "phone"); + + + +CREATE INDEX "user_id_created_at_idx" ON "auth"."sessions" USING "btree" ("user_id", "created_at"); + + + +CREATE UNIQUE INDEX "users_email_partial_key" ON "auth"."users" USING "btree" ("email") WHERE ("is_sso_user" = false); + + + +COMMENT ON INDEX "auth"."users_email_partial_key" IS 'Auth: A partial unique index that applies only when is_sso_user is false'; + + + +CREATE INDEX "users_instance_id_email_idx" ON "auth"."users" USING "btree" ("instance_id", "lower"(("email")::"text")); + + + +CREATE INDEX "users_instance_id_idx" ON "auth"."users" USING "btree" ("instance_id"); + + + +CREATE INDEX "users_is_anonymous_idx" ON "auth"."users" USING "btree" ("is_anonymous"); + + + +CREATE INDEX "webauthn_challenges_expires_at_idx" ON "auth"."webauthn_challenges" USING "btree" ("expires_at"); + + + +CREATE INDEX "webauthn_challenges_user_id_idx" ON "auth"."webauthn_challenges" USING "btree" ("user_id"); + + + +CREATE UNIQUE INDEX "webauthn_credentials_credential_id_key" ON "auth"."webauthn_credentials" USING "btree" ("credential_id"); + + + +CREATE INDEX "webauthn_credentials_user_id_idx" ON "auth"."webauthn_credentials" USING "btree" ("user_id"); + + + +CREATE INDEX "bwc_chunks_document_id_idx" ON "public"."bwc_chunks" USING "btree" ("document_id"); + + + +CREATE INDEX "bwc_chunks_embedding_idx" ON "public"."bwc_chunks" USING "hnsw" ("embedding" "public"."vector_cosine_ops") WITH ("m"='16', "ef_construction"='64'); + + + +CREATE INDEX "bwc_chunks_fts_en_idx" ON "public"."bwc_chunks" USING "gin" ("fts_en"); + + + +CREATE INDEX "bwc_chunks_fts_fr_idx" ON "public"."bwc_chunks" USING "gin" ("fts_fr"); + + + +CREATE INDEX "bwc_documents_country_idx" ON "public"."bwc_documents" USING "btree" ("country"); + + + +CREATE INDEX "bwc_documents_document_type_idx" ON "public"."bwc_documents" USING "btree" ("document_type"); + + + +CREATE INDEX "bwc_documents_language_idx" ON "public"."bwc_documents" USING "btree" ("language"); + + + +CREATE INDEX "bwc_documents_tender_id_idx" ON "public"."bwc_documents" USING "btree" ("tender_id"); + + + +CREATE INDEX "facade_vectors_facade_id_idx" ON "public"."facade_vectors" USING "btree" ("facade_id"); + + + +CREATE INDEX "facade_vectors_geometry_idx" ON "public"."facade_vectors" USING "gist" ("geometry"); + + + +CREATE INDEX "facade_vectors_residence_id_idx" ON "public"."facade_vectors" USING "btree" ("residence_id"); + + + +CREATE UNIQUE INDEX "halcrm_contacts_workspace_email_idx" ON "public"."halcrm_contacts" USING "btree" ("workspace_slug", "email") WHERE ("email" IS NOT NULL); + + + +CREATE INDEX "halcrm_documents_valid_until_idx" ON "public"."halcrm_documents" USING "btree" ("valid_until") WHERE ("valid_until" IS NOT NULL); + + + +CREATE INDEX "halcrm_documents_ws_domain_idx" ON "public"."halcrm_documents" USING "btree" ("workspace_slug", "domain"); + + + +CREATE INDEX "halcrm_tasks_assignee_user_id_idx" ON "public"."halcrm_tasks" USING "btree" ("assignee_user_id"); + + + +CREATE INDEX "idx_bdtopo_buildings_centroid" ON "public"."bgign_bdtopo_buildings" USING "gist" ("centroid"); + + + +CREATE INDEX "idx_bdtopo_buildings_cleabs" ON "public"."bgign_bdtopo_buildings" USING "btree" ("cleabs") WHERE ("cleabs" IS NOT NULL); + + + +COMMENT ON INDEX "public"."idx_bdtopo_buildings_cleabs" IS 'B-tree index on BD TOPO CLEABS identifier'; + + + +CREATE INDEX "idx_bdtopo_buildings_department" ON "public"."bgign_bdtopo_buildings" USING "btree" ("department"); + + + +COMMENT ON INDEX "public"."idx_bdtopo_buildings_department" IS 'B-tree index on department code for query partitioning'; + + + +CREATE INDEX "idx_bdtopo_buildings_geom" ON "public"."bgign_bdtopo_buildings" USING "gist" ("geom"); + + + +CREATE INDEX "idx_bdtopo_buildings_postal" ON "public"."bgign_bdtopo_buildings" USING "btree" ("code_postal") WHERE ("code_postal" IS NOT NULL); + + + +CREATE INDEX "idx_bdtopo_buildings_rnb" ON "public"."bgign_bdtopo_buildings" USING "btree" ("rnb_id") WHERE ("rnb_id" IS NOT NULL); + + + +COMMENT ON INDEX "public"."idx_bdtopo_buildings_rnb" IS 'B-tree index on RNB identifier for linkage with other datasets'; + + + +CREATE INDEX "idx_buildings_geometry" ON "public"."buildings" USING "gist" ("geometry"); + + + +CREATE INDEX "idx_bwc_chunks_project_zones" ON "public"."bwc_chunks" USING "gin" ("project_zones"); + + + +CREATE INDEX "idx_bwc_client_users_client" ON "public"."bwc_client_users" USING "btree" ("client_id"); + + + +CREATE INDEX "idx_bwc_client_users_user" ON "public"."bwc_client_users" USING "btree" ("user_id"); + + + +CREATE INDEX "idx_bwc_definitions_tender" ON "public"."bwc_definitions" USING "btree" ("tender_id"); + + + +CREATE INDEX "idx_bwc_definitions_term" ON "public"."bwc_definitions" USING "btree" ("term"); + + + +CREATE INDEX "idx_bwc_definitions_term_lower" ON "public"."bwc_definitions" USING "btree" ("lower"("term")); + + + +CREATE INDEX "idx_bwc_jurisdiction_attrs_compare" ON "public"."bwc_jurisdiction_attributes" USING "btree" ("domain", "category", "attribute_key"); + + + +CREATE INDEX "idx_bwc_jurisdiction_attrs_lookup" ON "public"."bwc_jurisdiction_attributes" USING "btree" ("jurisdiction", "domain", "category"); + + + +CREATE INDEX "idx_bwc_reports_client" ON "public"."bwc_reports" USING "btree" ("client_id"); + + + +CREATE INDEX "idx_bwc_reports_created_by" ON "public"."bwc_reports" USING "btree" ("created_by"); + + + +CREATE INDEX "idx_bwc_reports_status" ON "public"."bwc_reports" USING "btree" ("status"); + + + +CREATE INDEX "idx_bwc_reports_token" ON "public"."bwc_reports" USING "btree" ("download_token") WHERE ("download_token" IS NOT NULL); + + + +CREATE INDEX "idx_bwc_tender_registry_country" ON "public"."bwc_tender_registry" USING "btree" ("country"); + + + +CREATE INDEX "idx_bwc_usage_logs_client" ON "public"."bwc_usage_logs" USING "btree" ("client_id"); + + + +CREATE INDEX "idx_bwc_usage_logs_created" ON "public"."bwc_usage_logs" USING "btree" ("created_at"); + + + +CREATE INDEX "idx_bwc_usage_logs_type" ON "public"."bwc_usage_logs" USING "btree" ("event_type"); + + + +CREATE INDEX "idx_bwc_usage_logs_user" ON "public"."bwc_usage_logs" USING "btree" ("user_id"); + + + +CREATE INDEX "idx_coverage_dataset" ON "public"."bgign_department_coverage" USING "btree" ("dataset_code"); + + + +CREATE INDEX "idx_coverage_dept" ON "public"."bgign_department_coverage" USING "btree" ("department"); + + + +CREATE INDEX "idx_coverage_lookup" ON "public"."bgign_department_coverage" USING "btree" ("dataset_code", "department", "layer"); + + + +CREATE INDEX "idx_coverage_status" ON "public"."bgign_department_coverage" USING "btree" ("import_status"); + + + +CREATE INDEX "idx_disorder_catalog_disorder_id" ON "public"."disorder_catalog" USING "btree" ("disorder_id"); + + + +CREATE INDEX "idx_disorders_geometry" ON "public"."disorders" USING "gist" ("geometry"); + + + +CREATE INDEX "idx_disorders_photo" ON "public"."disorders" USING "btree" ("photo_id"); + + + +CREATE INDEX "idx_disorders_photo_id" ON "public"."disorders" USING "btree" ("photo_id"); + + + +CREATE INDEX "idx_drone_positions_geometry_3d" ON "public"."drone_positions" USING "gist" ("geometry"); + + + +CREATE INDEX "idx_drone_positions_visit_id" ON "public"."drone_positions" USING "btree" ("visit_id"); + + + +CREATE INDEX "idx_edifice_annotations_note" ON "public"."edifice_annotations" USING "btree" ("note_id"); + + + +CREATE INDEX "idx_edifice_annotations_photo" ON "public"."edifice_annotations" USING "btree" ("photo_id"); + + + +CREATE INDEX "idx_edifice_annotations_source" ON "public"."edifice_annotations" USING "btree" ("source"); + + + +CREATE INDEX "idx_edifice_buildings_geometry" ON "public"."edifice_buildings" USING "gist" ("building_geometry"); + + + +CREATE INDEX "idx_edifice_buildings_organization_ids" ON "public"."edifice_buildings" USING "gin" ("organization_ids"); + + + +CREATE INDEX "idx_edifice_buildings_rnb_id" ON "public"."edifice_buildings" USING "btree" ("rnb_id") WHERE ("rnb_id" IS NOT NULL); + + + +CREATE INDEX "idx_edifice_enum_labels_type" ON "public"."edifice_enum_labels" USING "btree" ("enum_type"); + + + +CREATE INDEX "idx_edifice_messages_project" ON "public"."edifice_messages" USING "btree" ("project_id", "created_at"); + + + +CREATE INDEX "idx_edifice_notes_assessment" ON "public"."edifice_notes" USING "btree" ("project_id", "assessment") WHERE ("assessment" IS NOT NULL); + + + +CREATE INDEX "idx_edifice_notes_project" ON "public"."edifice_notes" USING "btree" ("project_id"); + + + +CREATE INDEX "idx_edifice_photos_note_id" ON "public"."edifice_photos" USING "btree" ("note_id") WHERE ("note_id" IS NOT NULL); + + + +CREATE INDEX "idx_edifice_photos_project" ON "public"."edifice_photos" USING "btree" ("project_id"); + + + +CREATE INDEX "idx_edifice_profiles_organization" ON "public"."edifice_profiles" USING "btree" ("organization_id"); + + + +CREATE INDEX "idx_edifice_project_documents_project_id" ON "public"."edifice_project_documents" USING "btree" ("project_id"); + + + +CREATE INDEX "idx_edifice_projects_building" ON "public"."edifice_projects" USING "btree" ("building_id"); + + + +CREATE INDEX "idx_edifice_projects_organization_ids" ON "public"."edifice_projects" USING "gin" ("organization_ids"); + + + +CREATE INDEX "idx_facades_building_id" ON "public"."facades" USING "btree" ("building_id"); + + + +CREATE INDEX "idx_facades_facade_id" ON "public"."facades" USING "btree" ("facade_id"); + + + +CREATE INDEX "idx_facades_geometry" ON "public"."facades" USING "gist" ("geometry"); + + + +CREATE INDEX "idx_halcrm_companies_bg_id" ON "public"."halcrm_companies" USING "btree" ("bg_id"); + + + +CREATE INDEX "idx_halcrm_companies_slug" ON "public"."halcrm_companies" USING "btree" ("workspace_slug"); + + + +CREATE INDEX "idx_halcrm_contacts_company" ON "public"."halcrm_contacts" USING "btree" ("company_id"); + + + +CREATE INDEX "idx_halcrm_contacts_slug" ON "public"."halcrm_contacts" USING "btree" ("workspace_slug"); + + + +CREATE INDEX "idx_halcrm_interactions_mission" ON "public"."halcrm_interactions" USING "btree" ("project_id"); + + + +CREATE INDEX "idx_halcrm_interactions_slug" ON "public"."halcrm_interactions" USING "btree" ("workspace_slug"); + + + +CREATE INDEX "idx_halcrm_interactions_tags" ON "public"."halcrm_interactions" USING "gin" ("tags"); + + + +CREATE INDEX "idx_halcrm_missions_company" ON "public"."halcrm_projects" USING "btree" ("company_id"); + + + +CREATE INDEX "idx_halcrm_missions_edifice" ON "public"."halcrm_projects" USING "btree" ("edifice_mission_id"); + + + +CREATE INDEX "idx_halcrm_missions_slug" ON "public"."halcrm_projects" USING "btree" ("workspace_slug"); + + + +CREATE INDEX "idx_halcrm_missions_stage" ON "public"."halcrm_projects" USING "btree" ("stage"); + + + +CREATE INDEX "idx_halcrm_sprints_slug" ON "public"."halcrm_sprints" USING "btree" ("workspace_slug"); + + + +CREATE INDEX "idx_halcrm_sprints_status" ON "public"."halcrm_sprints" USING "btree" ("status"); + + + +CREATE INDEX "idx_halcrm_tasks_mission" ON "public"."halcrm_tasks" USING "btree" ("project_id"); + + + +CREATE INDEX "idx_halcrm_tasks_slug" ON "public"."halcrm_tasks" USING "btree" ("workspace_slug"); + + + +CREATE INDEX "idx_halcrm_tasks_sprint" ON "public"."halcrm_tasks" USING "btree" ("sprint_id"); + + + +CREATE INDEX "idx_halcrm_tasks_status" ON "public"."halcrm_tasks" USING "btree" ("status"); + + + +CREATE INDEX "idx_intersections_building_id" ON "public"."intersections" USING "btree" ("building_id"); + + + +CREATE INDEX "idx_intersections_distance" ON "public"."intersections" USING "btree" ("distance"); + + + +CREATE INDEX "idx_intersections_facade_building" ON "public"."intersections" USING "btree" ("facade_id", "building_id"); + + + +CREATE INDEX "idx_intersections_facade_id" ON "public"."intersections" USING "btree" ("facade_id"); + + + +CREATE INDEX "idx_intersections_geometry" ON "public"."intersections" USING "gist" ("geometry"); + + + +CREATE INDEX "idx_intersections_photo" ON "public"."intersections" USING "btree" ("photo_id"); + + + +CREATE INDEX "idx_intersections_photo_id" ON "public"."intersections" USING "btree" ("photo_id"); + + + +CREATE INDEX "idx_intersections_surface_type" ON "public"."intersections" USING "btree" ("surface_type"); + + + +CREATE INDEX "idx_metrics_category" ON "public"."bwc_strategy_metrics" USING "btree" ("metric_category"); + + + +CREATE INDEX "idx_metrics_comparison" ON "public"."bwc_strategy_metrics" USING "btree" ("metric_name", "year", "scenario"); + + + +CREATE INDEX "idx_metrics_document" ON "public"."bwc_strategy_metrics" USING "btree" ("document_id"); + + + +CREATE INDEX "idx_metrics_source" ON "public"."bwc_strategy_metrics" USING "btree" ("source_name", "source_type"); + + + +CREATE INDEX "idx_metrics_validation" ON "public"."bwc_strategy_metrics" USING "btree" ("needs_validation") WHERE ("needs_validation" = true); + + + +CREATE INDEX "idx_photos_filename" ON "public"."photos" USING "btree" ("filename"); + + + +CREATE INDEX "idx_photos_visit" ON "public"."photos" USING "btree" ("visit_id"); + + + +CREATE INDEX "idx_photos_visit_id" ON "public"."photos" USING "btree" ("visit_id"); + + + +CREATE INDEX "idx_queue_lookup" ON "public"."bgign_download_queue" USING "btree" ("dataset_code", "department", "layer"); + + + +CREATE INDEX "idx_queue_priority" ON "public"."bgign_download_queue" USING "btree" ("priority", "created_at"); + + + +CREATE INDEX "idx_queue_status" ON "public"."bgign_download_queue" USING "btree" ("status"); + + + +CREATE INDEX "idx_report_notes_note_id" ON "public"."edifice_report_notes" USING "btree" ("note_id"); + + + +CREATE INDEX "idx_report_notes_report_id" ON "public"."edifice_report_notes" USING "btree" ("report_id"); + + + +CREATE UNIQUE INDEX "idx_report_notes_unique" ON "public"."edifice_report_notes" USING "btree" ("report_id", "note_id"); + + + +CREATE INDEX "idx_reports_project_id" ON "public"."edifice_reports" USING "btree" ("project_id"); + + + +CREATE INDEX "idx_reports_status" ON "public"."edifice_reports" USING "btree" ("status"); + + + +CREATE INDEX "idx_shared_app_access_app" ON "public"."shared_app_access" USING "btree" ("app_id"); + + + +CREATE INDEX "idx_wcast_nuclear_availability_ts" ON "public"."wcast_nuclear_availability" USING "btree" ("timestamp"); + + + +CREATE INDEX "idx_workspace_members_user" ON "public"."workspace_members" USING "btree" ("user_id"); + + + +CREATE INDEX "residences_commune_idx" ON "public"."residences" USING "btree" ("commune"); + + + +CREATE INDEX "residences_departement_idx" ON "public"."residences" USING "btree" ("departement"); + + + +CREATE INDEX "wcast_commodities_commodity_idx" ON "public"."wcast_commodities" USING "btree" ("commodity"); + + + +CREATE INDEX "wcast_commodities_timestamp_idx" ON "public"."wcast_commodities" USING "btree" ("timestamp"); + + + +CREATE INDEX "wcast_cross_border_flows_timestamp_idx" ON "public"."wcast_cross_border_flows" USING "btree" ("timestamp"); + + + +CREATE INDEX "wcast_forecasts_delivery_timestamp_idx" ON "public"."wcast_forecasts" USING "btree" ("delivery_timestamp"); + + + +CREATE INDEX "wcast_forecasts_generated_at_idx" ON "public"."wcast_forecasts" USING "btree" ("generated_at"); + + + +CREATE INDEX "wcast_generation_timestamp_idx" ON "public"."wcast_generation" USING "btree" ("timestamp"); + + + +CREATE INDEX "wcast_generation_type_idx" ON "public"."wcast_generation" USING "btree" ("production_type"); + + + +CREATE INDEX "wcast_load_timestamp_idx" ON "public"."wcast_load" USING "btree" ("timestamp"); + + + +CREATE INDEX "wcast_neighbor_prices_timestamp_idx" ON "public"."wcast_neighbor_prices" USING "btree" ("timestamp"); + + + +CREATE INDEX "wcast_spot_prices_timestamp_idx" ON "public"."wcast_spot_prices" USING "btree" ("timestamp"); + + + +CREATE INDEX "wcast_weather_forecast_location_idx" ON "public"."wcast_weather_forecast" USING "btree" ("location"); + + + +CREATE INDEX "wcast_weather_forecast_timestamp_idx" ON "public"."wcast_weather_forecast" USING "btree" ("timestamp"); + + + +CREATE INDEX "wcast_weather_location_idx" ON "public"."wcast_weather" USING "btree" ("location"); + + + +CREATE INDEX "wcast_weather_timestamp_idx" ON "public"."wcast_weather" USING "btree" ("timestamp"); + + + +CREATE UNIQUE INDEX "workspace_members_one_default_per_user" ON "public"."workspace_members" USING "btree" ("user_id") WHERE "is_default"; + + + +CREATE OR REPLACE TRIGGER "on_auth_user_created" AFTER INSERT ON "auth"."users" FOR EACH ROW EXECUTE FUNCTION "public"."handle_new_user"(); + + + +CREATE OR REPLACE TRIGGER "trg_provision_edifice_org" AFTER INSERT OR UPDATE OF "raw_app_meta_data" ON "auth"."users" FOR EACH ROW WHEN (((NULLIF(("new"."raw_app_meta_data" ->> 'company_id'::"text"), ''::"text") IS NOT NULL) AND "jsonb_exists"(("new"."raw_app_meta_data" -> 'allowed_apps'::"text"), 'edifice'::"text"))) EXECUTE FUNCTION "public"."provision_edifice_org"(); + + + +CREATE OR REPLACE TRIGGER "trg_provision_workspace_membership" AFTER INSERT OR UPDATE OF "raw_app_meta_data" ON "auth"."users" FOR EACH ROW WHEN (((NULLIF(("new"."raw_app_meta_data" ->> 'company_id'::"text"), ''::"text") IS NOT NULL) AND (("new"."raw_app_meta_data" ->> 'company_id'::"text") ~* '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$'::"text"))) EXECUTE FUNCTION "public"."provision_workspace_membership"(); + + + +CREATE OR REPLACE TRIGGER "edifice_building_map_on_coords" AFTER INSERT OR UPDATE OF "latitude", "longitude" ON "public"."edifice_buildings" FOR EACH ROW EXECUTE FUNCTION "public"."edifice_trigger_map_generation"(); + + + +CREATE OR REPLACE TRIGGER "populate_report_notes_on_create" AFTER INSERT ON "public"."edifice_reports" FOR EACH ROW EXECUTE FUNCTION "public"."auto_populate_report_notes"(); + + + +CREATE OR REPLACE TRIGGER "sync_photo_note_id_on_annotation" AFTER INSERT OR UPDATE OF "note_id" ON "public"."edifice_annotations" FOR EACH ROW EXECUTE FUNCTION "private"."sync_photo_note_id"(); + + + +CREATE OR REPLACE TRIGGER "trg_building_orgs_must_cover_projects" BEFORE UPDATE OF "organization_ids" ON "public"."edifice_buildings" FOR EACH ROW EXECUTE FUNCTION "public"."fn_building_orgs_must_cover_projects"(); + + + +CREATE OR REPLACE TRIGGER "trg_project_extend_building_orgs" BEFORE INSERT OR UPDATE OF "organization_ids", "building_id" ON "public"."edifice_projects" FOR EACH ROW EXECUTE FUNCTION "public"."fn_project_extend_building_orgs"(); + + + +CREATE OR REPLACE TRIGGER "trigger_auto_generate_ref_id" BEFORE INSERT ON "public"."disorders" FOR EACH ROW EXECUTE FUNCTION "public"."auto_generate_ref_id"(); + + + +CREATE OR REPLACE TRIGGER "trigger_compute_lambert93" BEFORE INSERT OR UPDATE OF "latitude", "longitude" ON "public"."edifice_buildings" FOR EACH ROW EXECUTE FUNCTION "public"."compute_lambert93_coordinates"(); + + + +CREATE OR REPLACE TRIGGER "update_bdtopo_buildings_updated_at" BEFORE UPDATE ON "public"."bgign_bdtopo_buildings" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"(); + + + +COMMENT ON TRIGGER "update_bdtopo_buildings_updated_at" ON "public"."bgign_bdtopo_buildings" IS 'Auto-update updated_at timestamp on row modification'; + + + +CREATE OR REPLACE TRIGGER "update_bwc_documents_updated_at" BEFORE UPDATE ON "public"."bwc_documents" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_coverage_updated_at" BEFORE UPDATE ON "public"."bgign_department_coverage" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_datasets_updated_at" BEFORE UPDATE ON "public"."bgign_datasets" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_edifice_annotations_updated_at" BEFORE UPDATE ON "public"."edifice_annotations" FOR EACH ROW EXECUTE FUNCTION "private"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_edifice_buildings_updated_at" BEFORE UPDATE ON "public"."edifice_buildings" FOR EACH ROW EXECUTE FUNCTION "private"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_edifice_notes_updated_at" BEFORE UPDATE ON "public"."edifice_notes" FOR EACH ROW EXECUTE FUNCTION "private"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_edifice_photos_updated_at" BEFORE UPDATE ON "public"."edifice_photos" FOR EACH ROW EXECUTE FUNCTION "private"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_edifice_projects_updated_at" BEFORE UPDATE ON "public"."edifice_projects" FOR EACH ROW EXECUTE FUNCTION "private"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_edifice_reports_updated_at" BEFORE UPDATE ON "public"."edifice_reports" FOR EACH ROW EXECUTE FUNCTION "private"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_facade_vectors_updated_at" BEFORE UPDATE ON "public"."facade_vectors" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"(); + + + +CREATE OR REPLACE TRIGGER "update_queue_updated_at" BEFORE UPDATE ON "public"."bgign_download_queue" FOR EACH ROW EXECUTE FUNCTION "public"."update_updated_at_column"(); + + + +ALTER TABLE ONLY "auth"."identities" + ADD CONSTRAINT "identities_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."mfa_amr_claims" + ADD CONSTRAINT "mfa_amr_claims_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "auth"."sessions"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."mfa_challenges" + ADD CONSTRAINT "mfa_challenges_auth_factor_id_fkey" FOREIGN KEY ("factor_id") REFERENCES "auth"."mfa_factors"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."mfa_factors" + ADD CONSTRAINT "mfa_factors_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."oauth_authorizations" + ADD CONSTRAINT "oauth_authorizations_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "auth"."oauth_clients"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."oauth_authorizations" + ADD CONSTRAINT "oauth_authorizations_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."oauth_consents" + ADD CONSTRAINT "oauth_consents_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "auth"."oauth_clients"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."oauth_consents" + ADD CONSTRAINT "oauth_consents_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."one_time_tokens" + ADD CONSTRAINT "one_time_tokens_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."refresh_tokens" + ADD CONSTRAINT "refresh_tokens_session_id_fkey" FOREIGN KEY ("session_id") REFERENCES "auth"."sessions"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."saml_providers" + ADD CONSTRAINT "saml_providers_sso_provider_id_fkey" FOREIGN KEY ("sso_provider_id") REFERENCES "auth"."sso_providers"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."saml_relay_states" + ADD CONSTRAINT "saml_relay_states_flow_state_id_fkey" FOREIGN KEY ("flow_state_id") REFERENCES "auth"."flow_state"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."saml_relay_states" + ADD CONSTRAINT "saml_relay_states_sso_provider_id_fkey" FOREIGN KEY ("sso_provider_id") REFERENCES "auth"."sso_providers"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."sessions" + ADD CONSTRAINT "sessions_oauth_client_id_fkey" FOREIGN KEY ("oauth_client_id") REFERENCES "auth"."oauth_clients"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."sessions" + ADD CONSTRAINT "sessions_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."sso_domains" + ADD CONSTRAINT "sso_domains_sso_provider_id_fkey" FOREIGN KEY ("sso_provider_id") REFERENCES "auth"."sso_providers"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."webauthn_challenges" + ADD CONSTRAINT "webauthn_challenges_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "auth"."webauthn_credentials" + ADD CONSTRAINT "webauthn_credentials_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."bgign_department_coverage" + ADD CONSTRAINT "bgign_department_coverage_dataset_code_fkey" FOREIGN KEY ("dataset_code") REFERENCES "public"."bgign_datasets"("code") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."bgign_download_queue" + ADD CONSTRAINT "bgign_download_queue_dataset_code_fkey" FOREIGN KEY ("dataset_code") REFERENCES "public"."bgign_datasets"("code"); + + + +ALTER TABLE ONLY "public"."buildings" + ADD CONSTRAINT "buildings_residence_id_fkey" FOREIGN KEY ("residence_id") REFERENCES "public"."residences"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."bwc_chunks" + ADD CONSTRAINT "bwc_chunks_document_id_fkey" FOREIGN KEY ("document_id") REFERENCES "public"."bwc_documents"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."bwc_client_users" + ADD CONSTRAINT "bwc_client_users_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "public"."bwc_client_accounts"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."bwc_client_users" + ADD CONSTRAINT "bwc_client_users_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."bwc_reports" + ADD CONSTRAINT "bwc_reports_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "public"."bwc_client_accounts"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."bwc_reports" + ADD CONSTRAINT "bwc_reports_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "auth"."users"("id"); + + + +ALTER TABLE ONLY "public"."bwc_strategy_metrics" + ADD CONSTRAINT "bwc_strategy_metrics_document_id_fkey" FOREIGN KEY ("document_id") REFERENCES "public"."bwc_documents"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."bwc_usage_logs" + ADD CONSTRAINT "bwc_usage_logs_client_id_fkey" FOREIGN KEY ("client_id") REFERENCES "public"."bwc_client_accounts"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."bwc_usage_logs" + ADD CONSTRAINT "bwc_usage_logs_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id"); + + + +ALTER TABLE ONLY "public"."disorders" + ADD CONSTRAINT "disorders_disorder_catalog_id_fkey" FOREIGN KEY ("disorder_catalog_id") REFERENCES "public"."disorder_catalog"("id"); + + + +ALTER TABLE ONLY "public"."disorders" + ADD CONSTRAINT "disorders_photo_id_fkey" FOREIGN KEY ("photo_id") REFERENCES "public"."photos"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."drone_positions" + ADD CONSTRAINT "drone_positions_visit_id_fkey" FOREIGN KEY ("visit_id") REFERENCES "public"."visits"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_annotations" + ADD CONSTRAINT "edifice_annotations_disorder_id_fkey" FOREIGN KEY ("note_id") REFERENCES "public"."edifice_notes"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_annotations" + ADD CONSTRAINT "edifice_annotations_photo_id_fkey" FOREIGN KEY ("photo_id") REFERENCES "public"."edifice_photos"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_component_types" + ADD CONSTRAINT "edifice_component_types_parent_id_fkey" FOREIGN KEY ("parent_id") REFERENCES "public"."edifice_component_types"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_notes" + ADD CONSTRAINT "edifice_disorders_component_type_id_fkey" FOREIGN KEY ("component_type_id") REFERENCES "public"."edifice_component_types"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_notes" + ADD CONSTRAINT "edifice_disorders_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."edifice_profiles"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_notes" + ADD CONSTRAINT "edifice_disorders_disorder_type_id_fkey" FOREIGN KEY ("disorder_type_id") REFERENCES "public"."edifice_disorder_types"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_notes" + ADD CONSTRAINT "edifice_disorders_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."edifice_projects"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_messages" + ADD CONSTRAINT "edifice_messages_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."edifice_projects"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_messages" + ADD CONSTRAINT "edifice_messages_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_photos" + ADD CONSTRAINT "edifice_photos_message_id_fkey" FOREIGN KEY ("message_id") REFERENCES "public"."edifice_messages"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_photos" + ADD CONSTRAINT "edifice_photos_note_id_fkey" FOREIGN KEY ("note_id") REFERENCES "public"."edifice_notes"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_photos" + ADD CONSTRAINT "edifice_photos_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."edifice_projects"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_photos" + ADD CONSTRAINT "edifice_photos_uploaded_by_fkey" FOREIGN KEY ("uploaded_by") REFERENCES "public"."edifice_profiles"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_profiles" + ADD CONSTRAINT "edifice_profiles_id_fkey" FOREIGN KEY ("id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_project_documents" + ADD CONSTRAINT "edifice_project_documents_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."edifice_projects"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_project_documents" + ADD CONSTRAINT "edifice_project_documents_uploaded_by_fkey" FOREIGN KEY ("uploaded_by") REFERENCES "public"."edifice_profiles"("id"); + + + +ALTER TABLE ONLY "public"."edifice_projects" + ADD CONSTRAINT "edifice_projects_building_id_fkey" FOREIGN KEY ("building_id") REFERENCES "public"."edifice_buildings"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_projects" + ADD CONSTRAINT "edifice_projects_created_by_fkey" FOREIGN KEY ("created_by") REFERENCES "public"."edifice_profiles"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_report_notes" + ADD CONSTRAINT "edifice_report_disorders_disorder_id_fkey" FOREIGN KEY ("note_id") REFERENCES "public"."edifice_notes"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_report_notes" + ADD CONSTRAINT "edifice_report_disorders_report_id_fkey" FOREIGN KEY ("report_id") REFERENCES "public"."edifice_reports"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."edifice_reports" + ADD CONSTRAINT "edifice_reports_generated_by_fkey" FOREIGN KEY ("generated_by") REFERENCES "public"."edifice_profiles"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."edifice_reports" + ADD CONSTRAINT "edifice_reports_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."edifice_projects"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."facade_vectors" + ADD CONSTRAINT "facade_vectors_residence_id_fkey" FOREIGN KEY ("residence_id") REFERENCES "public"."residences"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."facades" + ADD CONSTRAINT "facades_building_id_fkey" FOREIGN KEY ("building_id") REFERENCES "public"."buildings"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."halcrm_companies" + ADD CONSTRAINT "halcrm_companies_workspace_slug_fkey" FOREIGN KEY ("workspace_slug") REFERENCES "public"."halcrm_workspaces"("workspace_slug"); + + + +ALTER TABLE ONLY "public"."halcrm_contacts" + ADD CONSTRAINT "halcrm_contacts_company_id_fkey" FOREIGN KEY ("company_id") REFERENCES "public"."halcrm_companies"("id"); + + + +ALTER TABLE ONLY "public"."halcrm_contacts" + ADD CONSTRAINT "halcrm_contacts_workspace_slug_fkey" FOREIGN KEY ("workspace_slug") REFERENCES "public"."halcrm_workspaces"("workspace_slug"); + + + +ALTER TABLE ONLY "public"."halcrm_documents" + ADD CONSTRAINT "halcrm_documents_workspace_slug_fkey" FOREIGN KEY ("workspace_slug") REFERENCES "public"."halcrm_workspaces"("workspace_slug"); + + + +ALTER TABLE ONLY "public"."halcrm_interactions" + ADD CONSTRAINT "halcrm_interactions_contact_id_fkey" FOREIGN KEY ("contact_id") REFERENCES "public"."halcrm_contacts"("id"); + + + +ALTER TABLE ONLY "public"."halcrm_interactions" + ADD CONSTRAINT "halcrm_interactions_mission_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."halcrm_projects"("id"); + + + +ALTER TABLE ONLY "public"."halcrm_interactions" + ADD CONSTRAINT "halcrm_interactions_workspace_slug_fkey" FOREIGN KEY ("workspace_slug") REFERENCES "public"."halcrm_workspaces"("workspace_slug"); + + + +ALTER TABLE ONLY "public"."halcrm_projects" + ADD CONSTRAINT "halcrm_missions_company_id_fkey" FOREIGN KEY ("company_id") REFERENCES "public"."halcrm_companies"("id"); + + + +ALTER TABLE ONLY "public"."halcrm_projects" + ADD CONSTRAINT "halcrm_missions_primary_contact_id_fkey" FOREIGN KEY ("primary_contact_id") REFERENCES "public"."halcrm_contacts"("id"); + + + +ALTER TABLE ONLY "public"."halcrm_projects" + ADD CONSTRAINT "halcrm_missions_workspace_slug_fkey" FOREIGN KEY ("workspace_slug") REFERENCES "public"."halcrm_workspaces"("workspace_slug"); + + + +ALTER TABLE ONLY "public"."halcrm_sprints" + ADD CONSTRAINT "halcrm_sprints_workspace_slug_fkey" FOREIGN KEY ("workspace_slug") REFERENCES "public"."halcrm_workspaces"("workspace_slug"); + + + +ALTER TABLE ONLY "public"."halcrm_tasks" + ADD CONSTRAINT "halcrm_tasks_assignee_user_id_fkey" FOREIGN KEY ("assignee_user_id") REFERENCES "auth"."users"("id"); + + + +ALTER TABLE ONLY "public"."halcrm_tasks" + ADD CONSTRAINT "halcrm_tasks_mission_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."halcrm_projects"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."halcrm_tasks" + ADD CONSTRAINT "halcrm_tasks_sprint_id_fkey" FOREIGN KEY ("sprint_id") REFERENCES "public"."halcrm_sprints"("id"); + + + +ALTER TABLE ONLY "public"."halcrm_tasks" + ADD CONSTRAINT "halcrm_tasks_workspace_slug_fkey" FOREIGN KEY ("workspace_slug") REFERENCES "public"."halcrm_workspaces"("workspace_slug"); + + + +ALTER TABLE ONLY "public"."halcrm_workspaces" + ADD CONSTRAINT "halcrm_workspaces_company_id_fkey" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE SET NULL; + + + +ALTER TABLE ONLY "public"."intersections" + ADD CONSTRAINT "intersections_building_id_fkey" FOREIGN KEY ("building_id") REFERENCES "public"."buildings"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."intersections" + ADD CONSTRAINT "intersections_facade_id_fkey" FOREIGN KEY ("facade_id") REFERENCES "public"."facades"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."intersections" + ADD CONSTRAINT "intersections_photo_id_fkey" FOREIGN KEY ("photo_id") REFERENCES "public"."photos"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."photos" + ADD CONSTRAINT "photos_visit_id_fkey" FOREIGN KEY ("visit_id") REFERENCES "public"."visits"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."residences" + ADD CONSTRAINT "residences_project_id_fkey" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."shared_app_access" + ADD CONSTRAINT "shared_app_access_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."visits" + ADD CONSTRAINT "visits_residence_id_fkey" FOREIGN KEY ("residence_id") REFERENCES "public"."residences"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."workspace_members" + ADD CONSTRAINT "workspace_members_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "auth"."users"("id") ON DELETE CASCADE; + + + +ALTER TABLE ONLY "public"."workspace_members" + ADD CONSTRAINT "workspace_members_workspace_slug_fkey" FOREIGN KEY ("workspace_slug") REFERENCES "public"."halcrm_workspaces"("workspace_slug") ON DELETE CASCADE; + + + +ALTER TABLE "auth"."audit_log_entries" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."flow_state" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."identities" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."instances" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."mfa_amr_claims" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."mfa_challenges" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."mfa_factors" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."one_time_tokens" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."refresh_tokens" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."saml_providers" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."saml_relay_states" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."schema_migrations" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."sessions" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."sso_domains" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."sso_providers" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "auth"."users" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "Allow authenticated users to access intersections" ON "public"."intersections" USING (("auth"."role"() = 'authenticated'::"text")); + + + +CREATE POLICY "Enable delete access for all users" ON "public"."facade_vectors" FOR DELETE USING (true); + + + +CREATE POLICY "Enable insert access for all users" ON "public"."facade_vectors" FOR INSERT WITH CHECK (true); + + + +CREATE POLICY "Enable read access for all users" ON "public"."facade_vectors" FOR SELECT USING (true); + + + +CREATE POLICY "Enable update access for all users" ON "public"."facade_vectors" FOR UPDATE USING (true); + + + +CREATE POLICY "Users can delete project documents" ON "public"."edifice_project_documents" FOR DELETE USING (("project_id" IN ( SELECT "p"."id" + FROM "public"."edifice_projects" "p" + WHERE (( SELECT "edifice_profiles"."organization_id" + FROM "public"."edifice_profiles" + WHERE ("edifice_profiles"."id" = "auth"."uid"())) = ANY ("p"."organization_ids"))))); + + + +CREATE POLICY "Users can insert project documents" ON "public"."edifice_project_documents" FOR INSERT WITH CHECK (("project_id" IN ( SELECT "p"."id" + FROM "public"."edifice_projects" "p" + WHERE (( SELECT "edifice_profiles"."organization_id" + FROM "public"."edifice_profiles" + WHERE ("edifice_profiles"."id" = "auth"."uid"())) = ANY ("p"."organization_ids"))))); + + + +CREATE POLICY "Users can read own access" ON "public"."shared_app_access" FOR SELECT TO "authenticated" USING (("auth"."uid"() = "user_id")); + + + +CREATE POLICY "Users can view project documents" ON "public"."edifice_project_documents" FOR SELECT USING (("project_id" IN ( SELECT "p"."id" + FROM "public"."edifice_projects" "p" + WHERE (( SELECT "edifice_profiles"."organization_id" + FROM "public"."edifice_profiles" + WHERE ("edifice_profiles"."id" = "auth"."uid"())) = ANY ("p"."organization_ids"))))); + + + +ALTER TABLE "public"."companies" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "companies_readable_by_authenticated" ON "public"."companies" FOR SELECT TO "authenticated" USING (true); + + + +CREATE POLICY "complex_case_insert_reports" ON "public"."edifice_reports" FOR INSERT WITH CHECK (((("auth"."jwt"() ->> 'scope'::"text") = 'complex_case_enrichment'::"text") AND ("project_id" = (("auth"."jwt"() ->> 'project_id'::"text"))::"uuid") AND (EXISTS ( SELECT 1 + FROM "public"."edifice_projects" "p" + WHERE (("p"."id" = "edifice_reports"."project_id") AND (NOT "p"."is_export_locked")))))); + + + +CREATE POLICY "complex_case_update_annotations" ON "public"."edifice_annotations" FOR UPDATE USING (((("auth"."jwt"() ->> 'scope'::"text") = 'complex_case_enrichment'::"text") AND (EXISTS ( SELECT 1 + FROM "public"."edifice_notes" "n" + WHERE (("n"."id" = "edifice_annotations"."note_id") AND ("n"."project_id" = (("auth"."jwt"() ->> 'project_id'::"text"))::"uuid")))) AND (EXISTS ( SELECT 1 + FROM "public"."edifice_projects" "p" + WHERE (("p"."id" = (("auth"."jwt"() ->> 'project_id'::"text"))::"uuid") AND (NOT "p"."is_export_locked")))))); + + + +CREATE POLICY "complex_case_update_notes" ON "public"."edifice_notes" FOR UPDATE USING (((("auth"."jwt"() ->> 'scope'::"text") = 'complex_case_enrichment'::"text") AND ("project_id" = (("auth"."jwt"() ->> 'project_id'::"text"))::"uuid") AND (EXISTS ( SELECT 1 + FROM "public"."edifice_projects" "p" + WHERE (("p"."id" = "edifice_notes"."project_id") AND (NOT "p"."is_export_locked")))))); + + + +CREATE POLICY "complex_case_update_reports" ON "public"."edifice_reports" FOR UPDATE USING (((("auth"."jwt"() ->> 'scope'::"text") = 'complex_case_enrichment'::"text") AND ("project_id" = (("auth"."jwt"() ->> 'project_id'::"text"))::"uuid") AND (EXISTS ( SELECT 1 + FROM "public"."edifice_projects" "p" + WHERE (("p"."id" = "edifice_reports"."project_id") AND (NOT "p"."is_export_locked")))))); + + + +ALTER TABLE "public"."edifice_annotations" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_annotations_all" ON "public"."edifice_annotations" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_buildings" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_buildings_all" ON "public"."edifice_buildings" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_component_types" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_component_types_all" ON "public"."edifice_component_types" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_disorder_types" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_disorder_types_all" ON "public"."edifice_disorder_types" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_enum_labels" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_enum_labels_all" ON "public"."edifice_enum_labels" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_messages" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_messages_all" ON "public"."edifice_messages" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_notes" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_notes_all" ON "public"."edifice_notes" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_photos" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_photos_all" ON "public"."edifice_photos" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_profiles" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_profiles_all" ON "public"."edifice_profiles" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_project_documents" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."edifice_projects" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "edifice_projects_all" ON "public"."edifice_projects" TO "authenticated" USING (true) WITH CHECK (true); + + + +ALTER TABLE "public"."edifice_report_notes" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."edifice_reports" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."facade_vectors" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."halcrm_companies" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."halcrm_contacts" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."halcrm_documents" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."halcrm_interactions" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."halcrm_projects" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."halcrm_sprints" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."halcrm_tasks" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."halcrm_workspaces" ENABLE ROW LEVEL SECURITY; + + +ALTER TABLE "public"."intersections" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "org_report_notes_all" ON "public"."edifice_report_notes" USING (("report_id" IN ( SELECT "r"."id" + FROM ("public"."edifice_reports" "r" + JOIN "public"."edifice_projects" "p" ON (("r"."project_id" = "p"."id"))) + WHERE (( SELECT "edifice_profiles"."organization_id" + FROM "public"."edifice_profiles" + WHERE ("edifice_profiles"."id" = "auth"."uid"())) = ANY ("p"."organization_ids"))))); + + + +COMMENT ON POLICY "org_report_notes_all" ON "public"."edifice_report_notes" IS 'Users can access report-note links when their org appears in the project organization_ids array'; + + + +CREATE POLICY "org_reports_all" ON "public"."edifice_reports" USING (("project_id" IN ( SELECT "edifice_projects"."id" + FROM "public"."edifice_projects" + WHERE (( SELECT "edifice_profiles"."organization_id" + FROM "public"."edifice_profiles" + WHERE ("edifice_profiles"."id" = "auth"."uid"())) = ANY ("edifice_projects"."organization_ids"))))); + + + +COMMENT ON POLICY "org_reports_all" ON "public"."edifice_reports" IS 'Users can access reports when their org appears in the parent project organization_ids array'; + + + +ALTER TABLE "public"."shared_app_access" ENABLE ROW LEVEL SECURITY; + + +CREATE POLICY "user sees own memberships" ON "public"."workspace_members" FOR SELECT TO "authenticated" USING (("user_id" = ( SELECT "auth"."uid"() AS "uid"))); + + + +CREATE POLICY "workspace member access" ON "public"."halcrm_companies" TO "authenticated" USING (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))) WITH CHECK (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))); + + + +CREATE POLICY "workspace member access" ON "public"."halcrm_contacts" TO "authenticated" USING (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))) WITH CHECK (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))); + + + +CREATE POLICY "workspace member access" ON "public"."halcrm_documents" TO "authenticated" USING (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))) WITH CHECK (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))); + + + +CREATE POLICY "workspace member access" ON "public"."halcrm_interactions" TO "authenticated" USING (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))) WITH CHECK (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))); + + + +CREATE POLICY "workspace member access" ON "public"."halcrm_projects" TO "authenticated" USING (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))) WITH CHECK (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))); + + + +CREATE POLICY "workspace member access" ON "public"."halcrm_sprints" TO "authenticated" USING (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))) WITH CHECK (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))); + + + +CREATE POLICY "workspace member access" ON "public"."halcrm_tasks" TO "authenticated" USING (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))) WITH CHECK (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))); + + + +CREATE POLICY "workspace member access" ON "public"."halcrm_workspaces" TO "authenticated" USING (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))) WITH CHECK (("workspace_slug" IN ( SELECT "workspace_members"."workspace_slug" + FROM "public"."workspace_members" + WHERE ("workspace_members"."user_id" = ( SELECT "auth"."uid"() AS "uid"))))); + + + +ALTER TABLE "public"."workspace_members" ENABLE ROW LEVEL SECURITY; + + +GRANT USAGE ON SCHEMA "auth" TO "anon"; +GRANT USAGE ON SCHEMA "auth" TO "authenticated"; +GRANT USAGE ON SCHEMA "auth" TO "service_role"; +GRANT ALL ON SCHEMA "auth" TO "supabase_auth_admin"; +GRANT ALL ON SCHEMA "auth" TO "dashboard_user"; +GRANT USAGE ON SCHEMA "auth" TO "postgres"; + + + +GRANT USAGE ON SCHEMA "public" TO "postgres"; +GRANT USAGE ON SCHEMA "public" TO "anon"; +GRANT USAGE ON SCHEMA "public" TO "authenticated"; +GRANT USAGE ON SCHEMA "public" TO "service_role"; + + + +GRANT ALL ON FUNCTION "auth"."email"() TO "dashboard_user"; + + + +GRANT ALL ON FUNCTION "auth"."jwt"() TO "postgres"; +GRANT ALL ON FUNCTION "auth"."jwt"() TO "dashboard_user"; + + + +GRANT ALL ON FUNCTION "auth"."role"() TO "dashboard_user"; + + + +GRANT ALL ON FUNCTION "auth"."uid"() TO "dashboard_user"; + + + +GRANT ALL ON TABLE "public"."edifice_buildings" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_buildings" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_buildings" TO "service_role"; + + + +REVOKE ALL ON FUNCTION "public"."add_org_to_building"("p_building_id" "uuid", "p_org_id" "uuid") FROM PUBLIC; +GRANT ALL ON FUNCTION "public"."add_org_to_building"("p_building_id" "uuid", "p_org_id" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."add_org_to_building"("p_building_id" "uuid", "p_org_id" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."add_org_to_building"("p_building_id" "uuid", "p_org_id" "uuid") TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_projects" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_projects" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_projects" TO "service_role"; + + + +REVOKE ALL ON FUNCTION "public"."add_org_to_project"("p_project_id" "uuid", "p_org_id" "uuid") FROM PUBLIC; +GRANT ALL ON FUNCTION "public"."add_org_to_project"("p_project_id" "uuid", "p_org_id" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."add_org_to_project"("p_project_id" "uuid", "p_org_id" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."add_org_to_project"("p_project_id" "uuid", "p_org_id" "uuid") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."auto_generate_ref_id"() TO "anon"; +GRANT ALL ON FUNCTION "public"."auto_generate_ref_id"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."auto_generate_ref_id"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."auto_populate_report_notes"() TO "anon"; +GRANT ALL ON FUNCTION "public"."auto_populate_report_notes"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."auto_populate_report_notes"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."betc_update_updated_at"() TO "anon"; +GRANT ALL ON FUNCTION "public"."betc_update_updated_at"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."betc_update_updated_at"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."bwc_chunk_counts"("doc_ids" "uuid"[]) TO "anon"; +GRANT ALL ON FUNCTION "public"."bwc_chunk_counts"("doc_ids" "uuid"[]) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."bwc_chunk_counts"("doc_ids" "uuid"[]) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."bwc_compare_metrics_across_sources"("p_metric_name" "text", "p_year" integer, "p_scenario" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."bwc_compare_metrics_across_sources"("p_metric_name" "text", "p_year" integer, "p_scenario" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."bwc_compare_metrics_across_sources"("p_metric_name" "text", "p_year" integer, "p_scenario" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."bwc_get_strategy_metrics"("p_metric_name" "text", "p_year" integer, "p_scenario" "text", "p_source_name" "text", "p_category" "text", "p_sector" "text", "p_energy_source" "text", "p_limit" integer) TO "anon"; +GRANT ALL ON FUNCTION "public"."bwc_get_strategy_metrics"("p_metric_name" "text", "p_year" integer, "p_scenario" "text", "p_source_name" "text", "p_category" "text", "p_sector" "text", "p_energy_source" "text", "p_limit" integer) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."bwc_get_strategy_metrics"("p_metric_name" "text", "p_year" integer, "p_scenario" "text", "p_source_name" "text", "p_category" "text", "p_sector" "text", "p_energy_source" "text", "p_limit" integer) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."bwc_hybrid_search"("query_text" "text", "query_embedding" "public"."vector", "match_count" integer, "fulltext_weight" double precision, "semantic_weight" double precision, "rrf_k" integer, "language" "text", "filter_tender_id" "text", "filter_country" "text", "filter_document_type" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."bwc_hybrid_search"("query_text" "text", "query_embedding" "public"."vector", "match_count" integer, "fulltext_weight" double precision, "semantic_weight" double precision, "rrf_k" integer, "language" "text", "filter_tender_id" "text", "filter_country" "text", "filter_document_type" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."bwc_hybrid_search"("query_text" "text", "query_embedding" "public"."vector", "match_count" integer, "fulltext_weight" double precision, "semantic_weight" double precision, "rrf_k" integer, "language" "text", "filter_tender_id" "text", "filter_country" "text", "filter_document_type" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."bwc_semantic_search"("query_embedding" "public"."vector", "match_count" integer) TO "anon"; +GRANT ALL ON FUNCTION "public"."bwc_semantic_search"("query_embedding" "public"."vector", "match_count" integer) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."bwc_semantic_search"("query_embedding" "public"."vector", "match_count" integer) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."bwc_usage_stats"("p_client_id" "uuid", "p_days" integer) TO "anon"; +GRANT ALL ON FUNCTION "public"."bwc_usage_stats"("p_client_id" "uuid", "p_days" integer) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."bwc_usage_stats"("p_client_id" "uuid", "p_days" integer) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."compute_lambert93_coordinates"() TO "anon"; +GRANT ALL ON FUNCTION "public"."compute_lambert93_coordinates"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."compute_lambert93_coordinates"() TO "service_role"; + + + +REVOKE ALL ON FUNCTION "public"."edifice_trigger_map_generation"() FROM PUBLIC; +GRANT ALL ON FUNCTION "public"."edifice_trigger_map_generation"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."fn_building_orgs_must_cover_projects"() TO "anon"; +GRANT ALL ON FUNCTION "public"."fn_building_orgs_must_cover_projects"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."fn_building_orgs_must_cover_projects"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."fn_project_extend_building_orgs"() TO "anon"; +GRANT ALL ON FUNCTION "public"."fn_project_extend_building_orgs"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."fn_project_extend_building_orgs"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."generate_ref_id"("facade_id_param" character varying, "disorder_type" character varying) TO "anon"; +GRANT ALL ON FUNCTION "public"."generate_ref_id"("facade_id_param" character varying, "disorder_type" character varying) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."generate_ref_id"("facade_id_param" character varying, "disorder_type" character varying) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."get_buildings_with_geometry"("p_residence_uuid" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."get_buildings_with_geometry"("p_residence_uuid" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_buildings_with_geometry"("p_residence_uuid" "uuid") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."get_facades_with_geometry"("p_building_uuids" "uuid"[]) TO "anon"; +GRANT ALL ON FUNCTION "public"."get_facades_with_geometry"("p_building_uuids" "uuid"[]) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_facades_with_geometry"("p_building_uuids" "uuid"[]) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."get_kind_stages"("p_workspace_slug" "text", "p_kind" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."get_kind_stages"("p_workspace_slug" "text", "p_kind" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_kind_stages"("p_workspace_slug" "text", "p_kind" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."get_project_by_id"("p_workspace_slug" "text", "p_project_id" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."get_project_by_id"("p_workspace_slug" "text", "p_project_id" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_project_by_id"("p_workspace_slug" "text", "p_project_id" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text", "p_stage" "text", "p_limit" integer) TO "anon"; +GRANT ALL ON FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text", "p_stage" "text", "p_limit" integer) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text", "p_stage" "text", "p_limit" integer) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text", "p_stage" "text", "p_limit" integer, "p_tags" "text"[]) TO "anon"; +GRANT ALL ON FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text", "p_stage" "text", "p_limit" integer, "p_tags" "text"[]) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_project_list"("p_workspace_slug" "text", "p_kind" "text", "p_stage" "text", "p_limit" integer, "p_tags" "text"[]) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."get_project_stats"("project_id_param" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."get_project_stats"("project_id_param" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_project_stats"("project_id_param" "uuid") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."get_tasks_with_assignee"("p_workspace_slug" "text", "p_project_id" "text", "p_assignee_user_id" "uuid", "p_status" "text", "p_sprint_id" "text", "p_tags" "text"[]) TO "anon"; +GRANT ALL ON FUNCTION "public"."get_tasks_with_assignee"("p_workspace_slug" "text", "p_project_id" "text", "p_assignee_user_id" "uuid", "p_status" "text", "p_sprint_id" "text", "p_tags" "text"[]) TO "authenticated"; +GRANT ALL ON FUNCTION "public"."get_tasks_with_assignee"("p_workspace_slug" "text", "p_project_id" "text", "p_assignee_user_id" "uuid", "p_status" "text", "p_sprint_id" "text", "p_tags" "text"[]) TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "anon"; +GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."handle_new_user"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."provision_edifice_org"() TO "anon"; +GRANT ALL ON FUNCTION "public"."provision_edifice_org"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."provision_edifice_org"() TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."provision_workspace_membership"() TO "anon"; +GRANT ALL ON FUNCTION "public"."provision_workspace_membership"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."provision_workspace_membership"() TO "service_role"; + + + +REVOKE ALL ON FUNCTION "public"."remove_org_from_building"("p_building_id" "uuid", "p_org_id" "uuid") FROM PUBLIC; +GRANT ALL ON FUNCTION "public"."remove_org_from_building"("p_building_id" "uuid", "p_org_id" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."remove_org_from_building"("p_building_id" "uuid", "p_org_id" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."remove_org_from_building"("p_building_id" "uuid", "p_org_id" "uuid") TO "service_role"; + + + +REVOKE ALL ON FUNCTION "public"."remove_org_from_project"("p_project_id" "uuid", "p_org_id" "uuid") FROM PUBLIC; +GRANT ALL ON FUNCTION "public"."remove_org_from_project"("p_project_id" "uuid", "p_org_id" "uuid") TO "anon"; +GRANT ALL ON FUNCTION "public"."remove_org_from_project"("p_project_id" "uuid", "p_org_id" "uuid") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."remove_org_from_project"("p_project_id" "uuid", "p_org_id" "uuid") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."resolve_workspace_member_by_email"("p_workspace_slug" "text", "p_email" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."resolve_workspace_member_by_email"("p_workspace_slug" "text", "p_email" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."resolve_workspace_member_by_email"("p_workspace_slug" "text", "p_email" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."shared_check_app_access"("p_user_id" "uuid", "p_app_id" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."shared_check_app_access"("p_user_id" "uuid", "p_app_id" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."shared_check_app_access"("p_user_id" "uuid", "p_app_id" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."shared_grant_access"("p_email" "text", "p_app_id" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."shared_grant_access"("p_email" "text", "p_app_id" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."shared_grant_access"("p_email" "text", "p_app_id" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."shared_grant_all_access"("p_email" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."shared_grant_all_access"("p_email" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."shared_grant_all_access"("p_email" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."shared_revoke_access"("p_email" "text", "p_app_id" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."shared_revoke_access"("p_email" "text", "p_app_id" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."shared_revoke_access"("p_email" "text", "p_app_id" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."update_building_geometry_from_geojson"("p_building_id" "uuid", "p_geojson" "text") TO "anon"; +GRANT ALL ON FUNCTION "public"."update_building_geometry_from_geojson"("p_building_id" "uuid", "p_geojson" "text") TO "authenticated"; +GRANT ALL ON FUNCTION "public"."update_building_geometry_from_geojson"("p_building_id" "uuid", "p_geojson" "text") TO "service_role"; + + + +GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "anon"; +GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "authenticated"; +GRANT ALL ON FUNCTION "public"."update_updated_at_column"() TO "service_role"; + + + +GRANT ALL ON TABLE "auth"."audit_log_entries" TO "dashboard_user"; +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."audit_log_entries" TO "postgres"; +GRANT SELECT ON TABLE "auth"."audit_log_entries" TO "postgres" WITH GRANT OPTION; + + + +GRANT ALL ON TABLE "auth"."custom_oauth_providers" TO "postgres"; +GRANT ALL ON TABLE "auth"."custom_oauth_providers" TO "dashboard_user"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."flow_state" TO "postgres"; +GRANT SELECT ON TABLE "auth"."flow_state" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."flow_state" TO "dashboard_user"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."identities" TO "postgres"; +GRANT SELECT ON TABLE "auth"."identities" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."identities" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "auth"."instances" TO "dashboard_user"; +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."instances" TO "postgres"; +GRANT SELECT ON TABLE "auth"."instances" TO "postgres" WITH GRANT OPTION; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."mfa_amr_claims" TO "postgres"; +GRANT SELECT ON TABLE "auth"."mfa_amr_claims" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."mfa_amr_claims" TO "dashboard_user"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."mfa_challenges" TO "postgres"; +GRANT SELECT ON TABLE "auth"."mfa_challenges" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."mfa_challenges" TO "dashboard_user"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."mfa_factors" TO "postgres"; +GRANT SELECT ON TABLE "auth"."mfa_factors" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."mfa_factors" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "auth"."oauth_authorizations" TO "postgres"; +GRANT ALL ON TABLE "auth"."oauth_authorizations" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "auth"."oauth_client_states" TO "postgres"; +GRANT ALL ON TABLE "auth"."oauth_client_states" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "auth"."oauth_clients" TO "postgres"; +GRANT ALL ON TABLE "auth"."oauth_clients" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "auth"."oauth_consents" TO "postgres"; +GRANT ALL ON TABLE "auth"."oauth_consents" TO "dashboard_user"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."one_time_tokens" TO "postgres"; +GRANT SELECT ON TABLE "auth"."one_time_tokens" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."one_time_tokens" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "auth"."refresh_tokens" TO "dashboard_user"; +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."refresh_tokens" TO "postgres"; +GRANT SELECT ON TABLE "auth"."refresh_tokens" TO "postgres" WITH GRANT OPTION; + + + +GRANT ALL ON SEQUENCE "auth"."refresh_tokens_id_seq" TO "dashboard_user"; +GRANT ALL ON SEQUENCE "auth"."refresh_tokens_id_seq" TO "postgres"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."saml_providers" TO "postgres"; +GRANT SELECT ON TABLE "auth"."saml_providers" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."saml_providers" TO "dashboard_user"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."saml_relay_states" TO "postgres"; +GRANT SELECT ON TABLE "auth"."saml_relay_states" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."saml_relay_states" TO "dashboard_user"; + + + +GRANT SELECT ON TABLE "auth"."schema_migrations" TO "postgres" WITH GRANT OPTION; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."sessions" TO "postgres"; +GRANT SELECT ON TABLE "auth"."sessions" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."sessions" TO "dashboard_user"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."sso_domains" TO "postgres"; +GRANT SELECT ON TABLE "auth"."sso_domains" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."sso_domains" TO "dashboard_user"; + + + +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."sso_providers" TO "postgres"; +GRANT SELECT ON TABLE "auth"."sso_providers" TO "postgres" WITH GRANT OPTION; +GRANT ALL ON TABLE "auth"."sso_providers" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "auth"."users" TO "dashboard_user"; +GRANT INSERT,REFERENCES,DELETE,TRIGGER,TRUNCATE,MAINTAIN,UPDATE ON TABLE "auth"."users" TO "postgres"; +GRANT SELECT ON TABLE "auth"."users" TO "postgres" WITH GRANT OPTION; + + + +GRANT ALL ON TABLE "auth"."webauthn_challenges" TO "postgres"; +GRANT ALL ON TABLE "auth"."webauthn_challenges" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "auth"."webauthn_credentials" TO "postgres"; +GRANT ALL ON TABLE "auth"."webauthn_credentials" TO "dashboard_user"; + + + +GRANT ALL ON TABLE "public"."bgign_bdtopo_buildings" TO "anon"; +GRANT ALL ON TABLE "public"."bgign_bdtopo_buildings" TO "authenticated"; +GRANT ALL ON TABLE "public"."bgign_bdtopo_buildings" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bgign_datasets" TO "anon"; +GRANT ALL ON TABLE "public"."bgign_datasets" TO "authenticated"; +GRANT ALL ON TABLE "public"."bgign_datasets" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bgign_department_coverage" TO "anon"; +GRANT ALL ON TABLE "public"."bgign_department_coverage" TO "authenticated"; +GRANT ALL ON TABLE "public"."bgign_department_coverage" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bgign_download_queue" TO "anon"; +GRANT ALL ON TABLE "public"."bgign_download_queue" TO "authenticated"; +GRANT ALL ON TABLE "public"."bgign_download_queue" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."buildings" TO "anon"; +GRANT ALL ON TABLE "public"."buildings" TO "authenticated"; +GRANT ALL ON TABLE "public"."buildings" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_chunks" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_chunks" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_chunks" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_client_accounts" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_client_accounts" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_client_accounts" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_client_users" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_client_users" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_client_users" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_definitions" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_definitions" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_definitions" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_documents" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_documents" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_documents" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_jurisdiction_attributes" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_jurisdiction_attributes" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_jurisdiction_attributes" TO "service_role"; + + + +GRANT ALL ON SEQUENCE "public"."bwc_jurisdiction_attributes_id_seq" TO "anon"; +GRANT ALL ON SEQUENCE "public"."bwc_jurisdiction_attributes_id_seq" TO "authenticated"; +GRANT ALL ON SEQUENCE "public"."bwc_jurisdiction_attributes_id_seq" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_projects" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_projects" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_projects" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_reports" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_reports" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_reports" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_strategy_metrics" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_strategy_metrics" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_strategy_metrics" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_tender_registry" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_tender_registry" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_tender_registry" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."bwc_usage_logs" TO "anon"; +GRANT ALL ON TABLE "public"."bwc_usage_logs" TO "authenticated"; +GRANT ALL ON TABLE "public"."bwc_usage_logs" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."companies" TO "anon"; +GRANT ALL ON TABLE "public"."companies" TO "authenticated"; +GRANT ALL ON TABLE "public"."companies" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."disorder_catalog" TO "anon"; +GRANT ALL ON TABLE "public"."disorder_catalog" TO "authenticated"; +GRANT ALL ON TABLE "public"."disorder_catalog" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."disorders" TO "anon"; +GRANT ALL ON TABLE "public"."disorders" TO "authenticated"; +GRANT ALL ON TABLE "public"."disorders" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."facades" TO "anon"; +GRANT ALL ON TABLE "public"."facades" TO "authenticated"; +GRANT ALL ON TABLE "public"."facades" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."intersections" TO "anon"; +GRANT ALL ON TABLE "public"."intersections" TO "authenticated"; +GRANT ALL ON TABLE "public"."intersections" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."photos" TO "anon"; +GRANT ALL ON TABLE "public"."photos" TO "authenticated"; +GRANT ALL ON TABLE "public"."photos" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."projects" TO "anon"; +GRANT ALL ON TABLE "public"."projects" TO "authenticated"; +GRANT ALL ON TABLE "public"."projects" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."residences" TO "anon"; +GRANT ALL ON TABLE "public"."residences" TO "authenticated"; +GRANT ALL ON TABLE "public"."residences" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."visits" TO "anon"; +GRANT ALL ON TABLE "public"."visits" TO "authenticated"; +GRANT ALL ON TABLE "public"."visits" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."disorders_full" TO "anon"; +GRANT ALL ON TABLE "public"."disorders_full" TO "authenticated"; +GRANT ALL ON TABLE "public"."disorders_full" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."drone_positions" TO "anon"; +GRANT ALL ON TABLE "public"."drone_positions" TO "authenticated"; +GRANT ALL ON TABLE "public"."drone_positions" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_annotations" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_annotations" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_annotations" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_component_types" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_component_types" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_component_types" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_disorder_types" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_disorder_types" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_disorder_types" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_enum_labels" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_enum_labels" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_enum_labels" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_messages" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_messages" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_messages" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_notes" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_notes" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_notes" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_photos" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_photos" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_photos" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_profiles" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_profiles" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_profiles" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_project_documents" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_project_documents" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_project_documents" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_report_notes" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_report_notes" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_report_notes" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."edifice_reports" TO "anon"; +GRANT ALL ON TABLE "public"."edifice_reports" TO "authenticated"; +GRANT ALL ON TABLE "public"."edifice_reports" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."facade_stats" TO "anon"; +GRANT ALL ON TABLE "public"."facade_stats" TO "authenticated"; +GRANT ALL ON TABLE "public"."facade_stats" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."facade_vectors" TO "anon"; +GRANT ALL ON TABLE "public"."facade_vectors" TO "authenticated"; +GRANT ALL ON TABLE "public"."facade_vectors" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."halcrm_companies" TO "anon"; +GRANT ALL ON TABLE "public"."halcrm_companies" TO "authenticated"; +GRANT ALL ON TABLE "public"."halcrm_companies" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."halcrm_contacts" TO "anon"; +GRANT ALL ON TABLE "public"."halcrm_contacts" TO "authenticated"; +GRANT ALL ON TABLE "public"."halcrm_contacts" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."halcrm_documents" TO "anon"; +GRANT ALL ON TABLE "public"."halcrm_documents" TO "authenticated"; +GRANT ALL ON TABLE "public"."halcrm_documents" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."halcrm_interactions" TO "anon"; +GRANT ALL ON TABLE "public"."halcrm_interactions" TO "authenticated"; +GRANT ALL ON TABLE "public"."halcrm_interactions" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."halcrm_projects" TO "anon"; +GRANT ALL ON TABLE "public"."halcrm_projects" TO "authenticated"; +GRANT ALL ON TABLE "public"."halcrm_projects" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."halcrm_sprints" TO "anon"; +GRANT ALL ON TABLE "public"."halcrm_sprints" TO "authenticated"; +GRANT ALL ON TABLE "public"."halcrm_sprints" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."halcrm_tasks" TO "anon"; +GRANT ALL ON TABLE "public"."halcrm_tasks" TO "authenticated"; +GRANT ALL ON TABLE "public"."halcrm_tasks" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."halcrm_workspaces" TO "anon"; +GRANT ALL ON TABLE "public"."halcrm_workspaces" TO "authenticated"; +GRANT ALL ON TABLE "public"."halcrm_workspaces" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."qgis_projects" TO "anon"; +GRANT ALL ON TABLE "public"."qgis_projects" TO "authenticated"; +GRANT ALL ON TABLE "public"."qgis_projects" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."shared_app_access" TO "anon"; +GRANT ALL ON TABLE "public"."shared_app_access" TO "authenticated"; +GRANT ALL ON TABLE "public"."shared_app_access" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."shared_users_access_view" TO "anon"; +GRANT ALL ON TABLE "public"."shared_users_access_view" TO "authenticated"; +GRANT ALL ON TABLE "public"."shared_users_access_view" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."visit_summary" TO "anon"; +GRANT ALL ON TABLE "public"."visit_summary" TO "authenticated"; +GRANT ALL ON TABLE "public"."visit_summary" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_commodities" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_commodities" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_commodities" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_cross_border_flows" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_cross_border_flows" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_cross_border_flows" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_forecasts" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_forecasts" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_forecasts" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_generation" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_generation" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_generation" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_load" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_load" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_load" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_neighbor_prices" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_neighbor_prices" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_neighbor_prices" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_nuclear_availability" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_nuclear_availability" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_nuclear_availability" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_spot_prices" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_spot_prices" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_spot_prices" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_weather" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_weather" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_weather" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."wcast_weather_forecast" TO "anon"; +GRANT ALL ON TABLE "public"."wcast_weather_forecast" TO "authenticated"; +GRANT ALL ON TABLE "public"."wcast_weather_forecast" TO "service_role"; + + + +GRANT ALL ON TABLE "public"."workspace_members" TO "anon"; +GRANT ALL ON TABLE "public"."workspace_members" TO "authenticated"; +GRANT ALL ON TABLE "public"."workspace_members" TO "service_role"; + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_auth_admin" IN SCHEMA "auth" GRANT ALL ON SEQUENCES TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_auth_admin" IN SCHEMA "auth" GRANT ALL ON SEQUENCES TO "dashboard_user"; + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_auth_admin" IN SCHEMA "auth" GRANT ALL ON FUNCTIONS TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_auth_admin" IN SCHEMA "auth" GRANT ALL ON FUNCTIONS TO "dashboard_user"; + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_auth_admin" IN SCHEMA "auth" GRANT ALL ON TABLES TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "supabase_auth_admin" IN SCHEMA "auth" GRANT ALL ON TABLES TO "dashboard_user"; + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON SEQUENCES TO "service_role"; + + + + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON FUNCTIONS TO "service_role"; + + + + + + +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "postgres"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "anon"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "authenticated"; +ALTER DEFAULT PRIVILEGES FOR ROLE "postgres" IN SCHEMA "public" GRANT ALL ON TABLES TO "service_role"; + + + + + +