diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 906791d..49d44fa 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -36,3 +36,45 @@ jobs:
- name: Lint
run: npm run lint
+
+ # Les migrations s'appliquent-elles sur une base vierge ? C'est la seule
+ # panne qui transforme une mise à jour automatique en panne totale : une
+ # migration invalide part sur toutes les instances, l'entrypoint réessaie
+ # trente fois, abandonne, et le conteneur tourne en boucle de redémarrage.
+ # Les éprouver ici coûte une minute et referme ce chemin.
+ migrations:
+ runs-on: ubuntu-latest
+ services:
+ postgres:
+ image: postgres:16-alpine
+ env:
+ POSTGRES_DB: apel_manager
+ POSTGRES_USER: apel
+ POSTGRES_PASSWORD: apel-ci
+ options: >-
+ --health-cmd "pg_isready -U apel -d apel_manager"
+ --health-interval 5s
+ --health-timeout 5s
+ --health-retries 20
+ ports:
+ - 5432:5432
+ env:
+ DATABASE_URL: postgresql://apel:apel-ci@localhost:5432/apel_manager
+ steps:
+ - name: Récupérer les sources
+ uses: actions/checkout@v4
+
+ - name: Préparer Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Installer les dépendances
+ run: npm ci
+
+ - name: Appliquer les migrations sur une base vierge
+ run: node scripts/migrate.mjs
+
+ - name: Les rejouer, elles doivent rester sans effet
+ run: node scripts/migrate.mjs
diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml
index 817d944..0fd68eb 100644
--- a/.github/workflows/docker-publish.yml
+++ b/.github/workflows/docker-publish.yml
@@ -25,8 +25,53 @@ env:
IMAGE_NAME: ${{ github.repository }}
jobs:
+ # Les migrations s'appliquent-elles sur une base vierge ? C'est la seule
+ # panne qui transforme une mise à jour automatique en panne totale : une
+ # migration invalide part sur toutes les instances, l'entrypoint réessaie
+ # trente fois, abandonne, et le conteneur tourne en boucle de redémarrage.
+ # Les éprouver ici coûte une minute et referme ce chemin.
+ migrations:
+ runs-on: ubuntu-latest
+ services:
+ postgres:
+ image: postgres:16-alpine
+ env:
+ POSTGRES_DB: apel_manager
+ POSTGRES_USER: apel
+ POSTGRES_PASSWORD: apel-ci
+ options: >-
+ --health-cmd "pg_isready -U apel -d apel_manager"
+ --health-interval 5s
+ --health-timeout 5s
+ --health-retries 20
+ ports:
+ - 5432:5432
+ env:
+ DATABASE_URL: postgresql://apel:apel-ci@localhost:5432/apel_manager
+ steps:
+ - name: Récupérer les sources
+ uses: actions/checkout@v4
+
+ - name: Préparer Node.js
+ uses: actions/setup-node@v4
+ with:
+ node-version: 20
+ cache: npm
+
+ - name: Installer les dépendances
+ run: npm ci
+
+ - name: Appliquer les migrations sur une base vierge
+ run: node scripts/migrate.mjs
+
+ - name: Les rejouer, elles doivent rester sans effet
+ run: node scripts/migrate.mjs
+
publish:
runs-on: ubuntu-latest
+ # Rien ne part tant que les migrations n'ont pas été éprouvées : une image
+ # publiée est installée toute seule sur les instances dans l'heure.
+ needs: migrations
permissions:
contents: read
packages: write
diff --git a/compose.yaml b/compose.yaml
index 33fd298..893cc86 100644
--- a/compose.yaml
+++ b/compose.yaml
@@ -34,6 +34,13 @@ x-app-environment: &app-environment
UPDATE_REPOSITORY: ${UPDATE_REPOSITORY:-flocom/APEL-manager}
UPDATE_CHANNEL: ${UPDATE_CHANNEL:-main}
UPDATE_CHECK_ENABLED: ${UPDATE_CHECK_ENABLED:-true}
+ # L'image réellement déployée, celle que l'`updater` surveille. C'est elle que
+ # l'application interroge dans le registre pour savoir s'il y a quelque chose
+ # à installer : comparer au dernier commit du dépôt reviendrait à annoncer une
+ # mise à jour pendant les dix minutes de construction, et indéfiniment si la
+ # publication échoue. Même valeur par défaut que la clé `image` des services,
+ # pour qu'on ne puisse pas surveiller une autre image que celle qui tourne.
+ APEL_IMAGE: ${APEL_IMAGE:-ghcr.io/flocom/apel-manager:latest}
services:
db:
diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh
index f1562cc..bddb76d 100644
--- a/docker/entrypoint.sh
+++ b/docker/entrypoint.sh
@@ -91,6 +91,14 @@ load_or_create_secret CRON_SECRET cron-secret 32
# aux secrets ci-dessus, le remplacer n'invalide rien — une valeur imposée dans
# le `.env` prend simplement la place de celle qui a été générée.
updater_token_path="$CONFIG_DIR/updater-token"
+# Un fichier présent mais vide — interruption au premier démarrage, volume
+# restauré à moitié — n'était jamais réécrit : `ln` échouait sur la cible
+# existante et l'application repartait avec un jeton vide, donc sans bouton
+# d'installation immédiate, et le conseil affiché (« relancez la pile ») ne
+# corrigeait rien puisque le fichier restait là.
+if [ -e "$updater_token_path" ] && [ ! -s "$updater_token_path" ]; then
+ rm -f "$updater_token_path"
+fi
if [ -n "${WATCHTOWER_HTTP_API_TOKEN:-}" ]; then
if [ "$(sed -n '1p' "$updater_token_path" 2>/dev/null || true)" != "$WATCHTOWER_HTTP_API_TOKEN" ]; then
mv "$(write_temp_file "$updater_token_path" "$WATCHTOWER_HTTP_API_TOKEN")" \
@@ -121,9 +129,36 @@ if [ "${SKIP_MIGRATIONS:-0}" != "1" ]; then
attempt=1
maximum_attempts="${MIGRATION_MAX_ATTEMPTS:-30}"
- while ! node scripts/migrate.mjs; do
+ while true; do
+ # `if cmd; then …; fi` rend 0 quand la condition échoue sans branche
+ # `else` : relire $? après le bloc donnerait donc toujours 0 et le code
+ # fatal ne serait jamais vu. `|| status=$?` capture la vraie valeur, et
+ # protège la commande de `set -e`.
+ status=0
+ node scripts/migrate.mjs || status=$?
+ if [ "$status" -eq 0 ]; then
+ break
+ fi
+
+ # Code 2 : PostgreSQL a répondu et a refusé la migration. La rejouer
+ # trente fois donnerait trente fois le même refus, en annonçant une base
+ # indisponible qui se porte très bien — et en envoyant chercher la panne
+ # là où elle n'est pas. Mieux vaut s'arrêter en disant quoi faire.
+ if [ "$status" -eq 2 ]; then
+ echo "Cette version ne peut pas démarrer : sa migration est refusée par la base." >&2
+ echo "Le schéma n'a pas été modifié — la migration est appliquée d'un bloc, donc annulée d'un bloc." >&2
+ if [ -s "$CONFIG_DIR/last-good-revision" ]; then
+ echo "Pour revenir à la version qui tournait avant, sur le serveur :" >&2
+ echo " APEL_IMAGE=${UPDATE_IMAGE_REPOSITORY:-ghcr.io/flocom/apel-manager}:sha-$(sed -n '1p' "$CONFIG_DIR/last-good-revision" | cut -c1-7) docker compose up -d" >&2
+ else
+ echo "Revenez à la version précédente en fixant APEL_IMAGE sur son étiquette sha-xxxxxxx." >&2
+ fi
+ echo "Un « docker compose pull && docker compose up -d » réinstallerait la même version." >&2
+ exit 2
+ fi
+
if [ "$attempt" -ge "$maximum_attempts" ]; then
- echo "Échec des migrations après $attempt tentatives." >&2
+ echo "PostgreSQL est resté injoignable après $attempt tentatives." >&2
exit 1
fi
@@ -134,4 +169,13 @@ if [ "${SKIP_MIGRATIONS:-0}" != "1" ]; then
done
fi
+# Repère du retour en arrière. Écrit après les migrations, donc seulement par
+# une version qui a pu démarrer : quand la suivante échouera, l'étiquette à
+# réinstaller sera là, sur le disque, lisible sans l'application.
+if [ -n "${APP_REVISION:-}" ] && [ "${SKIP_MIGRATIONS:-0}" != "1" ]; then
+ printf '%s\n' "$APP_REVISION" > "$CONFIG_DIR/last-good-revision.tmp" 2>/dev/null &&
+ mv "$CONFIG_DIR/last-good-revision.tmp" "$CONFIG_DIR/last-good-revision" 2>/dev/null ||
+ true
+fi
+
exec "$@"
diff --git a/docs/DOCKER.md b/docs/DOCKER.md
index 8bf0f5c..0dae0dd 100644
--- a/docs/DOCKER.md
+++ b/docs/DOCKER.md
@@ -323,9 +323,56 @@ curl -s https://apel.example.org/api/health
{"status":"ok","database":"up","latencyMs":3,"version":"main-42","revision":"a1b2c3d", ...}
```
-Ces indicateurs reposent sur un appel à l'API publique de GitHub, mis en cache
-30 minutes. `UPDATE_CHECK_ENABLED="false"` supprime tout appel sortant : la
-mise à jour automatique continue de fonctionner, seul l'indicateur disparaît.
+Ces indicateurs reposent sur la lecture de l'image publiée dans le registre —
+celle que l'`updater` installerait — et non sur le dernier commit du dépôt :
+entre une fusion et la fin de la construction il s'écoule une dizaine de
+minutes, pendant lesquelles il n'y a rien à installer. Le résultat est mis en
+cache dix minutes. Le dépôt GitHub n'est consulté qu'en appoint, pour signaler
+une version fusionnée dont l'image n'est pas encore publiée ; cette
+consultation échoue sans conséquence. `UPDATE_CHECK_ENABLED="false"` supprime
+tout appel sortant : la mise à jour automatique continue de fonctionner, seul
+l'indicateur disparaît.
+
+L'écran indique aussi si le service `updater` répond réellement. Une mise à
+jour automatique annoncée « activée » alors que le conteneur est arrêté
+n'installerait jamais rien : la distinction est faite par une sonde, pas
+déduite de la configuration.
+
+### Revenir à la version précédente
+
+Chaque publication pousse, à côté de `latest`, une étiquette immuable
+`sha-xxxxxxx` reprenant les sept premiers caractères de la révision. C'est la
+porte de sortie quand une version ne démarre pas.
+
+L'application écrit dans le volume `app_config` la révision du dernier
+démarrage réussi. Pour la lire, même application arrêtée :
+
+```bash
+docker run --rm -v apel-manager_app_config:/config alpine \
+ cat /config/last-good-revision
+```
+
+Puis, dans `.env` :
+
+```env
+APEL_IMAGE="ghcr.io/flocom/apel-manager:sha-2ab04da"
+```
+
+```bash
+docker compose up -d
+```
+
+Deux points à connaître. D'abord, `docker compose pull && docker compose up -d`
+ne répare rien : `latest` désigne justement la version fautive, et la commande
+la réinstalle. Ensuite, tant qu'`APEL_IMAGE` désigne une étiquette `sha-`, la
+mise à jour automatique est figée — l'`updater` surveille une étiquette qui ne
+bouge plus. Remettez `APEL_IMAGE` à `latest` une fois le correctif publié.
+
+Une migration refusée par PostgreSQL n'abîme pas la base : elle est appliquée
+d'un seul bloc, donc annulée d'un seul bloc, et le schéma reste celui de la
+version précédente. Le conteneur s'arrête alors immédiatement en écrivant la
+commande de retour dans ses journaux (`docker compose logs app`), au lieu de
+réessayer en boucle.
### Précautions
diff --git a/drizzle/0021_association_headquarters.sql b/drizzle/0021_association_headquarters.sql
new file mode 100644
index 0000000..0e07544
--- /dev/null
+++ b/drizzle/0021_association_headquarters.sql
@@ -0,0 +1 @@
+ALTER TABLE "association_settings" ADD COLUMN "headquarters" text DEFAULT '' NOT NULL;
\ No newline at end of file
diff --git a/drizzle/meta/0021_snapshot.json b/drizzle/meta/0021_snapshot.json
new file mode 100644
index 0000000..a907233
--- /dev/null
+++ b/drizzle/meta/0021_snapshot.json
@@ -0,0 +1,3587 @@
+{
+ "id": "35531e8b-0540-44f3-99b4-438ae842e6ea",
+ "prevId": "d3c1d65e-8a29-43b1-938d-7cd6a77d722b",
+ "version": "7",
+ "dialect": "postgresql",
+ "tables": {
+ "public.accounting_categories": {
+ "name": "accounting_categories",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "accounting_category_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounting_categories_type_active_idx": {
+ "name": "accounting_categories_type_active_idx",
+ "columns": [
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "is_active",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.accounting_entries": {
+ "name": "accounting_entries",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "type": {
+ "name": "type",
+ "type": "accounting_entry_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "accounting_entry_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'draft'"
+ },
+ "account_id": {
+ "name": "account_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "category_id": {
+ "name": "category_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "amount_cents": {
+ "name": "amount_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "occurred_at": {
+ "name": "occurred_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "counterparty": {
+ "name": "counterparty",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "payment_method": {
+ "name": "payment_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reference": {
+ "name": "reference",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "attachment_url": {
+ "name": "attachment_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "accounting_entries_occurred_at_idx": {
+ "name": "accounting_entries_occurred_at_idx",
+ "columns": [
+ {
+ "expression": "occurred_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "accounting_entries_status_occurred_at_idx": {
+ "name": "accounting_entries_status_occurred_at_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "occurred_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "accounting_entries_account_idx": {
+ "name": "accounting_entries_account_idx",
+ "columns": [
+ {
+ "expression": "account_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "accounting_entries_category_idx": {
+ "name": "accounting_entries_category_idx",
+ "columns": [
+ {
+ "expression": "category_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "accounting_entries_event_idx": {
+ "name": "accounting_entries_event_idx",
+ "columns": [
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "accounting_entries_account_id_financial_accounts_id_fk": {
+ "name": "accounting_entries_account_id_financial_accounts_id_fk",
+ "tableFrom": "accounting_entries",
+ "tableTo": "financial_accounts",
+ "columnsFrom": [
+ "account_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "accounting_entries_category_id_accounting_categories_id_fk": {
+ "name": "accounting_entries_category_id_accounting_categories_id_fk",
+ "tableFrom": "accounting_entries",
+ "tableTo": "accounting_categories",
+ "columnsFrom": [
+ "category_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "accounting_entries_event_id_events_id_fk": {
+ "name": "accounting_entries_event_id_events_id_fk",
+ "tableFrom": "accounting_entries",
+ "tableTo": "events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "accounting_entries_created_by_users_id_fk": {
+ "name": "accounting_entries_created_by_users_id_fk",
+ "tableFrom": "accounting_entries",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "accounting_entries_amount_cents_check": {
+ "name": "accounting_entries_amount_cents_check",
+ "value": "\"accounting_entries\".\"amount_cents\" > 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.association_documents": {
+ "name": "association_documents",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "type": {
+ "name": "type",
+ "type": "association_document_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "association_document_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'draft'"
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "document_date": {
+ "name": "document_date",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "content": {
+ "name": "content",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "member_id": {
+ "name": "member_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "file_url": {
+ "name": "file_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "association_documents_type_status_date_idx": {
+ "name": "association_documents_type_status_date_idx",
+ "columns": [
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "document_date",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "association_documents_member_idx": {
+ "name": "association_documents_member_idx",
+ "columns": [
+ {
+ "expression": "member_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "association_documents_member_id_association_members_id_fk": {
+ "name": "association_documents_member_id_association_members_id_fk",
+ "tableFrom": "association_documents",
+ "tableTo": "association_members",
+ "columnsFrom": [
+ "member_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "association_documents_created_by_users_id_fk": {
+ "name": "association_documents_created_by_users_id_fk",
+ "tableFrom": "association_documents",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.association_members": {
+ "name": "association_members",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "first_name": {
+ "name": "first_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "last_name": {
+ "name": "last_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "address_line_1": {
+ "name": "address_line_1",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "address_line_2": {
+ "name": "address_line_2",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "postal_code": {
+ "name": "postal_code",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "city": {
+ "name": "city",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "country": {
+ "name": "country",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'France'"
+ },
+ "status": {
+ "name": "status",
+ "type": "association_member_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'pending'"
+ },
+ "school_year": {
+ "name": "school_year",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "membership_fee_cents": {
+ "name": "membership_fee_cents",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "fee_paid_at": {
+ "name": "fee_paid_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "joined_at": {
+ "name": "joined_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "notes": {
+ "name": "notes",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "association_members_user_idx": {
+ "name": "association_members_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"association_members\".\"user_id\" is not null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "association_members_status_school_year_idx": {
+ "name": "association_members_status_school_year_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "school_year",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "association_members_name_idx": {
+ "name": "association_members_name_idx",
+ "columns": [
+ {
+ "expression": "last_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "first_name",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "association_members_user_id_users_id_fk": {
+ "name": "association_members_user_id_users_id_fk",
+ "tableFrom": "association_members",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "association_members_membership_fee_cents_check": {
+ "name": "association_members_membership_fee_cents_check",
+ "value": "\"association_members\".\"membership_fee_cents\" >= 0"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.association_settings": {
+ "name": "association_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "'default'"
+ },
+ "association_name": {
+ "name": "association_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'APEL Manager'"
+ },
+ "school_name": {
+ "name": "school_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'Votre établissement'"
+ },
+ "contact_email": {
+ "name": "contact_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "rna": {
+ "name": "rna",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "headquarters": {
+ "name": "headquarters",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "''"
+ },
+ "logo_url": {
+ "name": "logo_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "task_reminder_window_days": {
+ "name": "task_reminder_window_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 3
+ },
+ "volunteer_reminder_window_days": {
+ "name": "volunteer_reminder_window_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 2
+ },
+ "telegram_enabled": {
+ "name": "telegram_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "encrypted_telegram_bot_token": {
+ "name": "encrypted_telegram_bot_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegram_token_last_four": {
+ "name": "telegram_token_last_four",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegram_bot_username": {
+ "name": "telegram_bot_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "telegram_token_verified_at": {
+ "name": "telegram_token_verified_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "recaptcha_enabled": {
+ "name": "recaptcha_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "recaptcha_site_key": {
+ "name": "recaptcha_site_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_recaptcha_secret": {
+ "name": "encrypted_recaptcha_secret",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "recaptcha_min_score": {
+ "name": "recaptcha_min_score",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 50
+ },
+ "whatsapp_group_url": {
+ "name": "whatsapp_group_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "push_vapid_public_key": {
+ "name": "push_vapid_public_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_push_vapid_private_key": {
+ "name": "encrypted_push_vapid_private_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "association_settings_updated_by_users_id_fk": {
+ "name": "association_settings_updated_by_users_id_fk",
+ "tableFrom": "association_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "association_settings_singleton_check": {
+ "name": "association_settings_singleton_check",
+ "value": "\"association_settings\".\"id\" = 'default'"
+ },
+ "association_settings_task_reminder_window_check": {
+ "name": "association_settings_task_reminder_window_check",
+ "value": "\"association_settings\".\"task_reminder_window_days\" >= 0 and \"association_settings\".\"task_reminder_window_days\" <= 30"
+ },
+ "association_settings_recaptcha_min_score_check": {
+ "name": "association_settings_recaptcha_min_score_check",
+ "value": "\"association_settings\".\"recaptcha_min_score\" >= 0 and \"association_settings\".\"recaptcha_min_score\" <= 100"
+ },
+ "association_settings_volunteer_reminder_window_check": {
+ "name": "association_settings_volunteer_reminder_window_check",
+ "value": "\"association_settings\".\"volunteer_reminder_window_days\" >= 0 and \"association_settings\".\"volunteer_reminder_window_days\" <= 30"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.audit_logs": {
+ "name": "audit_logs",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "actor_user_id": {
+ "name": "actor_user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "oauth_client_id": {
+ "name": "oauth_client_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "action": {
+ "name": "action",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_type": {
+ "name": "entity_type",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "entity_id": {
+ "name": "entity_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "source": {
+ "name": "source",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'web'"
+ },
+ "ip_address": {
+ "name": "ip_address",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "details": {
+ "name": "details",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'{}'::jsonb"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "audit_logs_entity_idx": {
+ "name": "audit_logs_entity_idx",
+ "columns": [
+ {
+ "expression": "entity_type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "entity_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_logs_actor_created_idx": {
+ "name": "audit_logs_actor_created_idx",
+ "columns": [
+ {
+ "expression": "actor_user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_logs_client_created_idx": {
+ "name": "audit_logs_client_created_idx",
+ "columns": [
+ {
+ "expression": "oauth_client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "audit_logs_created_at_idx": {
+ "name": "audit_logs_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "audit_logs_actor_user_id_users_id_fk": {
+ "name": "audit_logs_actor_user_id_users_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "users",
+ "columnsFrom": [
+ "actor_user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ },
+ "audit_logs_oauth_client_id_oauth_clients_id_fk": {
+ "name": "audit_logs_oauth_client_id_oauth_clients_id_fk",
+ "tableFrom": "audit_logs",
+ "tableTo": "oauth_clients",
+ "columnsFrom": [
+ "oauth_client_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.checklist_templates": {
+ "name": "checklist_templates",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "tasks": {
+ "name": "tasks",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.event_attachments": {
+ "name": "event_attachments",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "label": {
+ "name": "label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "file_url": {
+ "name": "file_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "uploaded_by": {
+ "name": "uploaded_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "event_attachments_event_idx": {
+ "name": "event_attachments_event_idx",
+ "columns": [
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "event_attachments_event_id_events_id_fk": {
+ "name": "event_attachments_event_id_events_id_fk",
+ "tableFrom": "event_attachments",
+ "tableTo": "events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "event_attachments_uploaded_by_users_id_fk": {
+ "name": "event_attachments_uploaded_by_users_id_fk",
+ "tableFrom": "event_attachments",
+ "tableTo": "users",
+ "columnsFrom": [
+ "uploaded_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.events": {
+ "name": "events",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "kind": {
+ "name": "kind",
+ "type": "event_kind",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'event'"
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "public_description": {
+ "name": "public_description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "ticketing_url": {
+ "name": "ticketing_url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "location": {
+ "name": "location",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start_at": {
+ "name": "start_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "end_at": {
+ "name": "end_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "event_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'draft'"
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "share_token": {
+ "name": "share_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "events_status_start_idx": {
+ "name": "events_status_start_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "start_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "events_created_by_users_id_fk": {
+ "name": "events_created_by_users_id_fk",
+ "tableFrom": "events",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "events_share_token_unique": {
+ "name": "events_share_token_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "share_token"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.financial_accounts": {
+ "name": "financial_accounts",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "financial_account_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "is_active": {
+ "name": "is_active",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "financial_accounts_type_active_idx": {
+ "name": "financial_accounts_type_active_idx",
+ "columns": [
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "is_active",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.meeting_attendance": {
+ "name": "meeting_attendance",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "meeting_attendance_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cancel_token": {
+ "name": "cancel_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "note": {
+ "name": "note",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "meeting_attendance_event_user_unique": {
+ "name": "meeting_attendance_event_user_unique",
+ "columns": [
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "meeting_attendance_event_email_unique": {
+ "name": "meeting_attendance_event_email_unique",
+ "columns": [
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "lower(\"email\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"meeting_attendance\".\"user_id\" is null and \"meeting_attendance\".\"email\" is not null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "meeting_attendance_cancel_token_idx": {
+ "name": "meeting_attendance_cancel_token_idx",
+ "columns": [
+ {
+ "expression": "cancel_token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "meeting_attendance_event_idx": {
+ "name": "meeting_attendance_event_idx",
+ "columns": [
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "meeting_attendance_event_id_events_id_fk": {
+ "name": "meeting_attendance_event_id_events_id_fk",
+ "tableFrom": "meeting_attendance",
+ "tableTo": "events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "meeting_attendance_user_id_users_id_fk": {
+ "name": "meeting_attendance_user_id_users_id_fk",
+ "tableFrom": "meeting_attendance",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "meeting_attendance_identite": {
+ "name": "meeting_attendance_identite",
+ "value": "\"meeting_attendance\".\"user_id\" is not null or \"meeting_attendance\".\"name\" is not null"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.notifications_log": {
+ "name": "notifications_log",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "task_id": {
+ "name": "task_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "kind": {
+ "name": "kind",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "sent_at": {
+ "name": "sent_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "notifications_log_task_user_kind_idx": {
+ "name": "notifications_log_task_user_kind_idx",
+ "columns": [
+ {
+ "expression": "task_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "kind",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "notifications_log_task_id_tasks_id_fk": {
+ "name": "notifications_log_task_id_tasks_id_fk",
+ "tableFrom": "notifications_log",
+ "tableTo": "tasks",
+ "columnsFrom": [
+ "task_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "notifications_log_user_id_users_id_fk": {
+ "name": "notifications_log_user_id_users_id_fk",
+ "tableFrom": "notifications_log",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_authorization_codes": {
+ "name": "oauth_authorization_codes",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "code_hash": {
+ "name": "code_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oauth_client_id": {
+ "name": "oauth_client_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "redirect_uri": {
+ "name": "redirect_uri",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "code_challenge": {
+ "name": "code_challenge",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "code_challenge_method": {
+ "name": "code_challenge_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'S256'"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "used_at": {
+ "name": "used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "oauth_authorization_codes_code_hash_idx": {
+ "name": "oauth_authorization_codes_code_hash_idx",
+ "columns": [
+ {
+ "expression": "code_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauth_authorization_codes_client_expires_idx": {
+ "name": "oauth_authorization_codes_client_expires_idx",
+ "columns": [
+ {
+ "expression": "oauth_client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauth_authorization_codes_user_idx": {
+ "name": "oauth_authorization_codes_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_authorization_codes_oauth_client_id_oauth_clients_id_fk": {
+ "name": "oauth_authorization_codes_oauth_client_id_oauth_clients_id_fk",
+ "tableFrom": "oauth_authorization_codes",
+ "tableTo": "oauth_clients",
+ "columnsFrom": [
+ "oauth_client_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_authorization_codes_user_id_users_id_fk": {
+ "name": "oauth_authorization_codes_user_id_users_id_fk",
+ "tableFrom": "oauth_authorization_codes",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_clients": {
+ "name": "oauth_clients",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "client_id": {
+ "name": "client_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "client_secret_hash": {
+ "name": "client_secret_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "redirect_uris": {
+ "name": "redirect_uris",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "token_endpoint_auth_method": {
+ "name": "token_endpoint_auth_method",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'none'"
+ },
+ "grant_types": {
+ "name": "grant_types",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[\"authorization_code\",\"refresh_token\"]'::jsonb"
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_by": {
+ "name": "created_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "oauth_clients_client_id_idx": {
+ "name": "oauth_clients_client_id_idx",
+ "columns": [
+ {
+ "expression": "client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauth_clients_enabled_idx": {
+ "name": "oauth_clients_enabled_idx",
+ "columns": [
+ {
+ "expression": "enabled",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_clients_created_by_users_id_fk": {
+ "name": "oauth_clients_created_by_users_id_fk",
+ "tableFrom": "oauth_clients",
+ "tableTo": "users",
+ "columnsFrom": [
+ "created_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.oauth_tokens": {
+ "name": "oauth_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "type": {
+ "name": "type",
+ "type": "oauth_token_type",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "oauth_client_id": {
+ "name": "oauth_client_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "authorization_code_id": {
+ "name": "authorization_code_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "scopes": {
+ "name": "scopes",
+ "type": "jsonb",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'[]'::jsonb"
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "revoked_at": {
+ "name": "revoked_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "oauth_tokens_token_hash_idx": {
+ "name": "oauth_tokens_token_hash_idx",
+ "columns": [
+ {
+ "expression": "token_hash",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauth_tokens_client_type_expires_idx": {
+ "name": "oauth_tokens_client_type_expires_idx",
+ "columns": [
+ {
+ "expression": "oauth_client_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "type",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "expires_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauth_tokens_user_idx": {
+ "name": "oauth_tokens_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "oauth_tokens_authorization_code_idx": {
+ "name": "oauth_tokens_authorization_code_idx",
+ "columns": [
+ {
+ "expression": "authorization_code_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "oauth_tokens_oauth_client_id_oauth_clients_id_fk": {
+ "name": "oauth_tokens_oauth_client_id_oauth_clients_id_fk",
+ "tableFrom": "oauth_tokens",
+ "tableTo": "oauth_clients",
+ "columnsFrom": [
+ "oauth_client_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_tokens_user_id_users_id_fk": {
+ "name": "oauth_tokens_user_id_users_id_fk",
+ "tableFrom": "oauth_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "oauth_tokens_authorization_code_id_oauth_authorization_codes_id_fk": {
+ "name": "oauth_tokens_authorization_code_id_oauth_authorization_codes_id_fk",
+ "tableFrom": "oauth_tokens",
+ "tableTo": "oauth_authorization_codes",
+ "columnsFrom": [
+ "authorization_code_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.outbound_mail_settings": {
+ "name": "outbound_mail_settings",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "text",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "'default'"
+ },
+ "provider": {
+ "name": "provider",
+ "type": "outbound_mail_provider",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'resend'"
+ },
+ "enabled": {
+ "name": "enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "from_name": {
+ "name": "from_name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "from_email": {
+ "name": "from_email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reply_to": {
+ "name": "reply_to",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "domain": {
+ "name": "domain",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_api_key": {
+ "name": "encrypted_api_key",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "key_last_four": {
+ "name": "key_last_four",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "smtp_host": {
+ "name": "smtp_host",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "smtp_port": {
+ "name": "smtp_port",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "smtp_secure": {
+ "name": "smtp_secure",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": false
+ },
+ "smtp_username": {
+ "name": "smtp_username",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "encrypted_smtp_password": {
+ "name": "encrypted_smtp_password",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_tested_at": {
+ "name": "last_tested_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_test_status": {
+ "name": "last_test_status",
+ "type": "outbound_mail_test_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'untested'"
+ },
+ "updated_by": {
+ "name": "updated_by",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "updated_at": {
+ "name": "updated_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "outbound_mail_settings_updated_by_users_id_fk": {
+ "name": "outbound_mail_settings_updated_by_users_id_fk",
+ "tableFrom": "outbound_mail_settings",
+ "tableTo": "users",
+ "columnsFrom": [
+ "updated_by"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "outbound_mail_settings_singleton_check": {
+ "name": "outbound_mail_settings_singleton_check",
+ "value": "\"outbound_mail_settings\".\"id\" = 'default'"
+ },
+ "outbound_mail_settings_smtp_port_check": {
+ "name": "outbound_mail_settings_smtp_port_check",
+ "value": "\"outbound_mail_settings\".\"smtp_port\" is null or (\"outbound_mail_settings\".\"smtp_port\" >= 1 and \"outbound_mail_settings\".\"smtp_port\" <= 65535)"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.password_reset_tokens": {
+ "name": "password_reset_tokens",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "token_hash": {
+ "name": "token_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "expires_at": {
+ "name": "expires_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "used_at": {
+ "name": "used_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {
+ "password_reset_tokens_user_id_users_id_fk": {
+ "name": "password_reset_tokens_user_id_users_id_fk",
+ "tableFrom": "password_reset_tokens",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_deliveries": {
+ "name": "push_deliveries",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "notification_id": {
+ "name": "notification_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "subscription_id": {
+ "name": "subscription_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "status": {
+ "name": "status",
+ "type": "push_delivery_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'queued'"
+ },
+ "ack_token": {
+ "name": "ack_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "error": {
+ "name": "error",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "sent_at": {
+ "name": "sent_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "received_at": {
+ "name": "received_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "opened_at": {
+ "name": "opened_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ }
+ },
+ "indexes": {
+ "push_deliveries_notification_idx": {
+ "name": "push_deliveries_notification_idx",
+ "columns": [
+ {
+ "expression": "notification_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "push_deliveries_ack_token_idx": {
+ "name": "push_deliveries_ack_token_idx",
+ "columns": [
+ {
+ "expression": "ack_token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "push_deliveries_user_idx": {
+ "name": "push_deliveries_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_deliveries_notification_id_push_notifications_id_fk": {
+ "name": "push_deliveries_notification_id_push_notifications_id_fk",
+ "tableFrom": "push_deliveries",
+ "tableTo": "push_notifications",
+ "columnsFrom": [
+ "notification_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "push_deliveries_user_id_users_id_fk": {
+ "name": "push_deliveries_user_id_users_id_fk",
+ "tableFrom": "push_deliveries",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "push_deliveries_subscription_id_push_subscriptions_id_fk": {
+ "name": "push_deliveries_subscription_id_push_subscriptions_id_fk",
+ "tableFrom": "push_deliveries",
+ "tableTo": "push_subscriptions",
+ "columnsFrom": [
+ "subscription_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_notifications": {
+ "name": "push_notifications",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "sender_id": {
+ "name": "sender_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "body": {
+ "name": "body",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "url": {
+ "name": "url",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_notifications_created_at_idx": {
+ "name": "push_notifications_created_at_idx",
+ "columns": [
+ {
+ "expression": "created_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_notifications_sender_id_users_id_fk": {
+ "name": "push_notifications_sender_id_users_id_fk",
+ "tableFrom": "push_notifications",
+ "tableTo": "users",
+ "columnsFrom": [
+ "sender_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.push_subscriptions": {
+ "name": "push_subscriptions",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "endpoint": {
+ "name": "endpoint",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "p256dh": {
+ "name": "p256dh",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "auth": {
+ "name": "auth",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "device_label": {
+ "name": "device_label",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "last_success_at": {
+ "name": "last_success_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "push_subscriptions_endpoint_idx": {
+ "name": "push_subscriptions_endpoint_idx",
+ "columns": [
+ {
+ "expression": "endpoint",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "push_subscriptions_user_idx": {
+ "name": "push_subscriptions_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "push_subscriptions_user_id_users_id_fk": {
+ "name": "push_subscriptions_user_id_users_id_fk",
+ "tableFrom": "push_subscriptions",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.task_assignees": {
+ "name": "task_assignees",
+ "schema": "",
+ "columns": {
+ "task_id": {
+ "name": "task_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ }
+ },
+ "indexes": {
+ "task_assignees_user_idx": {
+ "name": "task_assignees_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "task_assignees_task_id_tasks_id_fk": {
+ "name": "task_assignees_task_id_tasks_id_fk",
+ "tableFrom": "task_assignees",
+ "tableTo": "tasks",
+ "columnsFrom": [
+ "task_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "task_assignees_user_id_users_id_fk": {
+ "name": "task_assignees_user_id_users_id_fk",
+ "tableFrom": "task_assignees",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {
+ "task_assignees_task_id_user_id_pk": {
+ "name": "task_assignees_task_id_user_id_pk",
+ "columns": [
+ "task_id",
+ "user_id"
+ ]
+ }
+ },
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.tasks": {
+ "name": "tasks",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "lead_time_days": {
+ "name": "lead_time_days",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 7
+ },
+ "lead_time_value": {
+ "name": "lead_time_value",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 7
+ },
+ "lead_time_unit": {
+ "name": "lead_time_unit",
+ "type": "task_lead_time_unit",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'days'"
+ },
+ "due_at": {
+ "name": "due_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "status": {
+ "name": "status",
+ "type": "task_status",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'todo'"
+ },
+ "position": {
+ "name": "position",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "version": {
+ "name": "version",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "completed_at": {
+ "name": "completed_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "tasks_event_idx": {
+ "name": "tasks_event_idx",
+ "columns": [
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "tasks_status_due_idx": {
+ "name": "tasks_status_due_idx",
+ "columns": [
+ {
+ "expression": "status",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "due_at",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "tasks_event_id_events_id_fk": {
+ "name": "tasks_event_id_events_id_fk",
+ "tableFrom": "tasks",
+ "tableTo": "events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {
+ "tasks_lead_time_value_check": {
+ "name": "tasks_lead_time_value_check",
+ "value": "\"tasks\".\"lead_time_value\" >= 0"
+ },
+ "tasks_lead_time_consistency_check": {
+ "name": "tasks_lead_time_consistency_check",
+ "value": "\"tasks\".\"lead_time_days\" = \"tasks\".\"lead_time_value\" * case \"tasks\".\"lead_time_unit\" when 'days' then 1 when 'weeks' then 7 when 'months' then 30 end"
+ }
+ },
+ "isRLSEnabled": false
+ },
+ "public.users": {
+ "name": "users",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "password_hash": {
+ "name": "password_hash",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "role": {
+ "name": "role",
+ "type": "role",
+ "typeSchema": "public",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "'member'"
+ },
+ "telegram_chat_id": {
+ "name": "telegram_chat_id",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "session_epoch": {
+ "name": "session_epoch",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 0
+ },
+ "onboarding_seen_at": {
+ "name": "onboarding_seen_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "push_enabled": {
+ "name": "push_enabled",
+ "type": "boolean",
+ "primaryKey": false,
+ "notNull": true,
+ "default": true
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {},
+ "foreignKeys": {},
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {
+ "users_email_unique": {
+ "name": "users_email_unique",
+ "nullsNotDistinct": false,
+ "columns": [
+ "email"
+ ]
+ }
+ },
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.volunteer_signups": {
+ "name": "volunteer_signups",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "slot_id": {
+ "name": "slot_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "user_id": {
+ "name": "user_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "name": {
+ "name": "name",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "email": {
+ "name": "email",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "phone": {
+ "name": "phone",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "cancel_token": {
+ "name": "cancel_token",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "reminded_at": {
+ "name": "reminded_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "volunteer_signups_slot_idx": {
+ "name": "volunteer_signups_slot_idx",
+ "columns": [
+ {
+ "expression": "slot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "volunteer_signups_user_idx": {
+ "name": "volunteer_signups_user_idx",
+ "columns": [
+ {
+ "expression": "user_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "volunteer_signups_cancel_token_idx": {
+ "name": "volunteer_signups_cancel_token_idx",
+ "columns": [
+ {
+ "expression": "cancel_token",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ },
+ "volunteer_signups_slot_email_idx": {
+ "name": "volunteer_signups_slot_email_idx",
+ "columns": [
+ {
+ "expression": "slot_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ },
+ {
+ "expression": "lower(\"email\")",
+ "asc": true,
+ "isExpression": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": true,
+ "where": "\"volunteer_signups\".\"email\" is not null",
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "volunteer_signups_slot_id_volunteer_slots_id_fk": {
+ "name": "volunteer_signups_slot_id_volunteer_slots_id_fk",
+ "tableFrom": "volunteer_signups",
+ "tableTo": "volunteer_slots",
+ "columnsFrom": [
+ "slot_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ },
+ "volunteer_signups_user_id_users_id_fk": {
+ "name": "volunteer_signups_user_id_users_id_fk",
+ "tableFrom": "volunteer_signups",
+ "tableTo": "users",
+ "columnsFrom": [
+ "user_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "set null",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ },
+ "public.volunteer_slots": {
+ "name": "volunteer_slots",
+ "schema": "",
+ "columns": {
+ "id": {
+ "name": "id",
+ "type": "uuid",
+ "primaryKey": true,
+ "notNull": true,
+ "default": "gen_random_uuid()"
+ },
+ "event_id": {
+ "name": "event_id",
+ "type": "uuid",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "title": {
+ "name": "title",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": true
+ },
+ "description": {
+ "name": "description",
+ "type": "text",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "start_at": {
+ "name": "start_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "end_at": {
+ "name": "end_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": false
+ },
+ "capacity": {
+ "name": "capacity",
+ "type": "integer",
+ "primaryKey": false,
+ "notNull": true,
+ "default": 1
+ },
+ "created_at": {
+ "name": "created_at",
+ "type": "timestamp with time zone",
+ "primaryKey": false,
+ "notNull": true,
+ "default": "now()"
+ }
+ },
+ "indexes": {
+ "volunteer_slots_event_idx": {
+ "name": "volunteer_slots_event_idx",
+ "columns": [
+ {
+ "expression": "event_id",
+ "isExpression": false,
+ "asc": true,
+ "nulls": "last"
+ }
+ ],
+ "isUnique": false,
+ "concurrently": false,
+ "method": "btree",
+ "with": {}
+ }
+ },
+ "foreignKeys": {
+ "volunteer_slots_event_id_events_id_fk": {
+ "name": "volunteer_slots_event_id_events_id_fk",
+ "tableFrom": "volunteer_slots",
+ "tableTo": "events",
+ "columnsFrom": [
+ "event_id"
+ ],
+ "columnsTo": [
+ "id"
+ ],
+ "onDelete": "cascade",
+ "onUpdate": "no action"
+ }
+ },
+ "compositePrimaryKeys": {},
+ "uniqueConstraints": {},
+ "policies": {},
+ "checkConstraints": {},
+ "isRLSEnabled": false
+ }
+ },
+ "enums": {
+ "public.accounting_category_type": {
+ "name": "accounting_category_type",
+ "schema": "public",
+ "values": [
+ "income",
+ "expense"
+ ]
+ },
+ "public.accounting_entry_status": {
+ "name": "accounting_entry_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "posted"
+ ]
+ },
+ "public.accounting_entry_type": {
+ "name": "accounting_entry_type",
+ "schema": "public",
+ "values": [
+ "income",
+ "expense"
+ ]
+ },
+ "public.association_document_status": {
+ "name": "association_document_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "final",
+ "archived"
+ ]
+ },
+ "public.association_document_type": {
+ "name": "association_document_type",
+ "schema": "public",
+ "values": [
+ "ag_minutes",
+ "attestation",
+ "statutes",
+ "internal_rules",
+ "insurance",
+ "agreement",
+ "other"
+ ]
+ },
+ "public.association_member_status": {
+ "name": "association_member_status",
+ "schema": "public",
+ "values": [
+ "active",
+ "pending",
+ "inactive"
+ ]
+ },
+ "public.event_kind": {
+ "name": "event_kind",
+ "schema": "public",
+ "values": [
+ "event",
+ "meeting"
+ ]
+ },
+ "public.event_status": {
+ "name": "event_status",
+ "schema": "public",
+ "values": [
+ "draft",
+ "published",
+ "archived"
+ ]
+ },
+ "public.financial_account_type": {
+ "name": "financial_account_type",
+ "schema": "public",
+ "values": [
+ "bank",
+ "cash"
+ ]
+ },
+ "public.meeting_attendance_status": {
+ "name": "meeting_attendance_status",
+ "schema": "public",
+ "values": [
+ "yes",
+ "maybe",
+ "no"
+ ]
+ },
+ "public.oauth_token_type": {
+ "name": "oauth_token_type",
+ "schema": "public",
+ "values": [
+ "access",
+ "refresh"
+ ]
+ },
+ "public.outbound_mail_provider": {
+ "name": "outbound_mail_provider",
+ "schema": "public",
+ "values": [
+ "resend",
+ "smtp"
+ ]
+ },
+ "public.outbound_mail_test_status": {
+ "name": "outbound_mail_test_status",
+ "schema": "public",
+ "values": [
+ "untested",
+ "success",
+ "failed"
+ ]
+ },
+ "public.push_delivery_status": {
+ "name": "push_delivery_status",
+ "schema": "public",
+ "values": [
+ "queued",
+ "sent",
+ "failed",
+ "received",
+ "opened"
+ ]
+ },
+ "public.role": {
+ "name": "role",
+ "schema": "public",
+ "values": [
+ "admin",
+ "manager",
+ "member"
+ ]
+ },
+ "public.task_lead_time_unit": {
+ "name": "task_lead_time_unit",
+ "schema": "public",
+ "values": [
+ "days",
+ "weeks",
+ "months"
+ ]
+ },
+ "public.task_status": {
+ "name": "task_status",
+ "schema": "public",
+ "values": [
+ "todo",
+ "in_progress",
+ "done"
+ ]
+ }
+ },
+ "schemas": {},
+ "sequences": {},
+ "roles": {},
+ "policies": {},
+ "views": {},
+ "_meta": {
+ "columns": {},
+ "schemas": {},
+ "tables": {}
+ }
+}
\ No newline at end of file
diff --git a/drizzle/meta/_journal.json b/drizzle/meta/_journal.json
index 2ed11fd..73d81a2 100644
--- a/drizzle/meta/_journal.json
+++ b/drizzle/meta/_journal.json
@@ -148,6 +148,13 @@
"when": 1789051948309,
"tag": "0020_public_meeting_attendance",
"breakpoints": true
+ },
+ {
+ "idx": 21,
+ "version": "7",
+ "when": 1789629977428,
+ "tag": "0021_association_headquarters",
+ "breakpoints": true
}
]
}
\ No newline at end of file
diff --git a/scripts/migrate.mjs b/scripts/migrate.mjs
index eecde7c..18264ee 100644
--- a/scripts/migrate.mjs
+++ b/scripts/migrate.mjs
@@ -30,6 +30,44 @@ const client = postgres(databaseUrl, {
idle_timeout: 5,
});
+/**
+ * Codes qui signifient « la base n'est pas encore joignable ». Eux seuls
+ * justifient de réessayer : au démarrage d'une pile, PostgreSQL met quelques
+ * secondes à accepter les connexions.
+ *
+ * Tout le reste — une erreur SQL, un index unique refusé par des doublons
+ * réels, une contrainte violée par les données en place — se reproduira à
+ * l'identique trente fois de suite. Les confondre faisait afficher
+ * « PostgreSQL indisponible » pendant quatre minutes pour une migration
+ * fautive, et envoyait chercher une panne de base qui n'existait pas.
+ */
+const CODES_CONNEXION = new Set([
+ "ECONNREFUSED",
+ "ENOTFOUND",
+ "ETIMEDOUT",
+ "EHOSTUNREACH",
+ "ENETUNREACH",
+ "ECONNRESET",
+ "EPIPE",
+ "CONNECT_TIMEOUT",
+ "CONNECTION_CLOSED",
+ "CONNECTION_ENDED",
+ "CONNECTION_DESTROYED",
+ // PostgreSQL démarre, accepte la connexion et la refuse aussitôt.
+ "57P03",
+]);
+
+function estPanneDeConnexion(error) {
+ for (let cause = error; cause; cause = cause.cause) {
+ if (cause.code && CODES_CONNEXION.has(String(cause.code))) return true;
+ }
+ return false;
+}
+
+/** Code 2 : inutile de réessayer. Code 1 : la base n'est pas encore là. */
+const SORTIE_FATALE = 2;
+const SORTIE_REESSAYABLE = 1;
+
let migrationFailed = false;
try {
@@ -38,14 +76,20 @@ try {
console.log("Migrations PostgreSQL appliquées.");
} catch (error) {
migrationFailed = true;
- process.exitCode = 1;
- console.error("Échec des migrations PostgreSQL.");
+ const reessayable = estPanneDeConnexion(error);
+ process.exitCode = reessayable ? SORTIE_REESSAYABLE : SORTIE_FATALE;
+ console.error(
+ reessayable
+ ? "PostgreSQL n'est pas joignable pour appliquer les migrations."
+ : "Migration refusée par PostgreSQL : cette version ne peut pas démarrer.",
+ );
console.error(error instanceof Error ? error.message : error);
} finally {
try {
await client.end({ timeout: 5 });
} catch (error) {
- process.exitCode = 1;
+ // Ne pas écraser un diagnostic fatal par un simple défaut de fermeture.
+ process.exitCode = process.exitCode || SORTIE_REESSAYABLE;
console.error("Impossible de fermer proprement la connexion PostgreSQL.");
if (!migrationFailed) {
console.error(error instanceof Error ? error.message : error);
diff --git a/src/app/dashboard/settings/page.tsx b/src/app/dashboard/settings/page.tsx
index 8898329..00ddaf5 100644
--- a/src/app/dashboard/settings/page.tsx
+++ b/src/app/dashboard/settings/page.tsx
@@ -29,6 +29,7 @@ export default async function SettingsPage() {
schoolName: associationStatus.schoolName,
contactEmail: associationStatus.contactEmail,
rna: associationStatus.rna,
+ headquarters: associationStatus.headquarters,
logoUrl: associationStatus.logoUrl,
taskReminderWindowDays: associationStatus.taskReminderWindowDays,
volunteerReminderWindowDays:
diff --git a/src/components/association-settings-form.tsx b/src/components/association-settings-form.tsx
index b383bad..e08b15d 100644
--- a/src/components/association-settings-form.tsx
+++ b/src/components/association-settings-form.tsx
@@ -24,6 +24,7 @@ export interface AssociationSettingsView {
schoolName: string;
contactEmail: string | null;
rna: string;
+ headquarters: string;
logoUrl: string | null;
taskReminderWindowDays: number;
volunteerReminderWindowDays: number;
@@ -89,6 +90,7 @@ export function AssociationSettingsForm({
schoolName: form.get("schoolName"),
contactEmail: form.get("contactEmail") || null,
rna: form.get("rna"),
+ headquarters: form.get("headquarters"),
logoUrl: form.get("logoUrl") || null,
taskReminderWindowDays: Number(
form.get("taskReminderWindowDays"),
@@ -219,6 +221,20 @@ export function AssociationSettingsForm({
required
/>
+ {titre}
- Exécution hors image publiée -
-- Aucune version n'est estampillée : la comparaison avec la - version publiée n'est pas possible. C'est le cas en - développement local ou après une construction manuelle. -
-{suivi.message}
+ {suivi.etat === "installee" && ( + + )} +
+ docker compose ps updater
+ {" "}
+ puis{" "}
+
+ docker compose up -d updater
+
+ .
+ - Une version plus récente est publiée -
-- {current.autoUpdate.enabled - ? `Elle sera installée automatiquement lors du prochain contrôle (${formatInterval( - current.autoUpdate.pollIntervalSeconds, - )}), sans intervention.` - : "La mise à jour automatique est désactivée : lancez « docker compose pull && docker compose up -d » sur le serveur."} -
- {current.autoUpdate.canTriggerNow && ( - - )} - {current.autoUpdate.enabled && - !current.autoUpdate.canTriggerNow && ( -
- L’installation immédiate n’est pas disponible : le jeton
- qui autorise cette page à demander un redémarrage au
- service de mise à jour n’a pas encore été généré. Il l’est
- tout seul au démarrage ; relancez la pile sur le serveur
- avec{" "}
-
- docker compose pull && docker compose up -d
-
- .
-
- {current.error} -
+{current.error}
)}
+ L’installation immédiate n’est pas disponible : le jeton qui
+ autorise cette page à demander un redémarrage au service de mise
+ à jour n’a pas encore été généré. Il l’est tout seul au
+ démarrage ; relancez la pile sur le serveur avec{" "}
+
+ docker compose pull && docker compose up -d
+
+ .
+