feat(baserow): add seed script + Fast-App iteration 1 artifacts
Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
Some checks are pending
CI / Lint bridge (Biome) (push) Waiting to run
CI / Type-check bridge (push) Blocked by required conditions
CI / Tests unit bridge (push) Blocked by required conditions
CI / Tests integration bridge (push) Blocked by required conditions
CI / Security scan (push) Waiting to run
CI / Docker build + healthcheck (push) Blocked by required conditions
- baserow/seed/schema.json : 9 tables declaratif (personne + CFA + Agence) - baserow/seed/seed.py : Python script idempotent (login + workspace + db + tables + fields + links) - baserow/seed/requirements.txt : requests - baserow/seed/README.md : quickstart 4 etapes - Makefile target seed-baserow Fast-App workflow local : - _byan-output/fast-app/formation-hub/ : 6 artifacts (pitch, backlog, cdcf-stories, plan, dispatch, build-state) - Phase 0 mappee : phases 1-6 done depuis docs Merise/UML existants - Iteration 1 BUILD = setup tables Baserow vanilla (S-02 + S-03 + S-04) Stack locale up et healthy. Pret pour seed apres creation compte admin Baserow.
This commit is contained in:
parent
ecb7a44c3c
commit
6724be6c85
11 changed files with 813 additions and 0 deletions
8
Makefile
8
Makefile
|
|
@ -74,6 +74,14 @@ backup-baserow:
|
|||
docker compose exec -T baserow tar czf - /baserow/data > $(BACKUP_DIR)/baserow-$(DATE).tar.gz
|
||||
@echo " -> $(BACKUP_DIR)/baserow-$(DATE).tar.gz"
|
||||
|
||||
seed-baserow:
|
||||
@command -v python3 >/dev/null || (echo "ERREUR: python3 requis" && exit 1)
|
||||
@test -n "$$BASEROW_EMAIL" -a -n "$$BASEROW_PASSWORD" || \
|
||||
(echo "ERREUR: exporter BASEROW_EMAIL et BASEROW_PASSWORD" && exit 1)
|
||||
@cd baserow/seed && pip install -q -r requirements.txt
|
||||
BASEROW_URL=$${BASEROW_URL:-http://localhost:8080} \
|
||||
python3 baserow/seed/seed.py
|
||||
|
||||
clean:
|
||||
@echo "ATTENTION: cette commande supprime TOUS les volumes (donnees perdues)."
|
||||
@read -p "Tapez 'oui' pour confirmer: " confirm; [ "$$confirm" = "oui" ] || exit 1
|
||||
|
|
|
|||
29
_byan-output/fast-app/formation-hub/backlog.json
Normal file
29
_byan-output/fast-app/formation-hub/backlog.json
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"scope_mode": "full",
|
||||
"features": [
|
||||
{"id": "F-01", "title": "Wiki Docmost avec spaces multi-tenant", "priority": "MUST", "justification": "Coeur metier — centralisation doc"},
|
||||
{"id": "F-02", "title": "Diagrammes natifs Mermaid + Drawio + Excalidraw", "priority": "MUST", "justification": "Inclus Docmost v0.3+, zero dev"},
|
||||
{"id": "F-03", "title": "Permissions hierarchiques (workspace/space/page)", "priority": "MUST", "justification": "RGPD + workflow team"},
|
||||
{"id": "F-04", "title": "Share links externes (clients guests)", "priority": "MUST", "justification": "Acces partenaires/financeurs"},
|
||||
{"id": "F-05", "title": "9 tables Baserow (PERSONNE pivot + CFA + Agence)", "priority": "MUST", "justification": "Modele de donnees scope B approved"},
|
||||
{"id": "F-06", "title": "Rollups + formulas heures restantes", "priority": "MUST", "justification": "Calcul auto capacite formateurs/devs"},
|
||||
{"id": "F-07", "title": "Vues kanban/calendar/timeline par DB", "priority": "MUST", "justification": "UX metier admin"},
|
||||
{"id": "F-08", "title": "Spaces personnels etudiants Docmost", "priority": "MUST", "justification": "Promesse Vision Acadenice"},
|
||||
{"id": "F-09", "title": "Forms publics saisie heures (formateurs/devs)", "priority": "SHOULD", "justification": "UX mobile-friendly + permissions simples"},
|
||||
{"id": "F-10", "title": "Bridge service Tiptap node-views custom", "priority": "SHOULD", "justification": "UX unifie Phase 2"},
|
||||
{"id": "F-11", "title": "Sync bidirectionnel Docmost ↔ Baserow", "priority": "SHOULD", "justification": "Auto-creation pages depuis projets, etc."},
|
||||
{"id": "F-12", "title": "MCP server pour Claude/agents IA", "priority": "COULD", "justification": "Productivity boost pour admin Yan/Corentin"},
|
||||
{"id": "F-13", "title": "Bidirec backlinks Docmost (custom)", "priority": "COULD", "justification": "Nice-to-have, le bridge couvre 80% du besoin via DB relations"},
|
||||
{"id": "F-14", "title": "Dual-mode editor (WYSIWYG + raw markdown)", "priority": "COULD", "justification": "Power-users only"},
|
||||
{"id": "F-15", "title": "Rapports PDF (formation/personne/projet)", "priority": "COULD", "justification": "Export pour comptabilite, Phase 3"}
|
||||
],
|
||||
"wont": [
|
||||
{"id": "W-01", "title": "Modeliser les etudiants en table Baserow", "reason": "Decision Corentin 2026-05-07 : etudiants restent users Docmost libres, pas de modelisation structuree (inscriptions/notes). Si besoin Phase 4."},
|
||||
{"id": "W-02", "title": "Generer factures clients automatiquement", "reason": "Hors scope outil de connaissance. Comptabilite via outil dedie."},
|
||||
{"id": "W-03", "title": "ATS / recrutement", "reason": "Pas le metier, hors scope."},
|
||||
{"id": "W-04", "title": "Application mobile native", "reason": "Responsive web suffit. Mobile-native couterait 6 mois. Ockham."},
|
||||
{"id": "W-05", "title": "Multi-tenant (plusieurs centres de formation sur instance)", "reason": "Acadenice mono-instance pour l'instant. A reevaluer si scale."}
|
||||
],
|
||||
"validated_at": "2026-05-07",
|
||||
"validated_by": "Corentin JOGUET"
|
||||
}
|
||||
15
_byan-output/fast-app/formation-hub/build-state.json
Normal file
15
_byan-output/fast-app/formation-hub/build-state.json
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"current_iteration": 1,
|
||||
"completed": [],
|
||||
"pending": [1, 2, 3, 4, 5, 6, 7],
|
||||
"next_iteration": 1,
|
||||
"stack_local_status": {
|
||||
"docmost": "up + healthy on http://localhost:3000",
|
||||
"baserow": "up + healthy on http://localhost:8080",
|
||||
"docmost-db": "up + healthy",
|
||||
"docmost-redis": "up",
|
||||
"verified_at": "2026-05-07T14:48:00Z"
|
||||
},
|
||||
"ready_for_iteration_1": true,
|
||||
"iteration_1_first_step": "Creer compte admin Baserow via http://localhost:8080 (1ere page invite a creer un compte). Apres : creer database 'formation-hub', puis table 'personne'."
|
||||
}
|
||||
93
_byan-output/fast-app/formation-hub/cdcf-stories.json
Normal file
93
_byan-output/fast-app/formation-hub/cdcf-stories.json
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"scope": "Phase 1 vanilla setup metier — focalise BUILD iteration 1-3",
|
||||
"stories": [
|
||||
{
|
||||
"id": "S-01",
|
||||
"feature_id": "F-01",
|
||||
"connextra": "En tant qu'Admin Acadenice, je veux creer un workspace Docmost et 3 spaces (CFA, Agence, Interne), afin de centraliser la doc avec une structure miroir des collections Outline existantes.",
|
||||
"ac": [
|
||||
{"given": "compte admin Docmost cree", "when": "je cree workspace 'Acadenice formation-hub'", "then": "workspace existe avec moi en owner"},
|
||||
{"given": "workspace cree", "when": "je cree spaces CFA/Agence/Interne avec permissions par defaut 'workspace members'", "then": "3 spaces visibles sidebar"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-02",
|
||||
"feature_id": "F-05",
|
||||
"connextra": "En tant qu'Admin, je veux creer la table PERSONNE dans Baserow avec tous ses champs et formulas selon doc 15 MPD section 2, afin d'avoir le pivot multi-roles operationnel.",
|
||||
"ac": [
|
||||
{"given": "database 'formation-hub' creee", "when": "je cree la table PERSONNE avec 16 fields (incluant capacity_annuelle, split_pcts, roles multi-select, formulas heures_restantes)", "then": "la table est listee dans la database et les fields ont les bons types"},
|
||||
{"given": "table PERSONNE creee", "when": "je cree une row test (Yan, role formateur+developpeur, capacity 1500)", "then": "les formulas heures_restantes affichent 750/750/1500"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-03",
|
||||
"feature_id": "F-05",
|
||||
"connextra": "En tant qu'Admin, je veux creer les 4 tables CFA (FORMATION → BLOC → MODULE → ATTRIBUTION) avec leurs liens FK et rollups, afin que le suivi des heures formation soit operationnel.",
|
||||
"ac": [
|
||||
{"given": "table PERSONNE existe", "when": "je cree FORMATION, BLOC, MODULE, ATTRIBUTION dans l'ordre avec liens vers PERSONNE pour ATTRIBUTION", "then": "les 4 tables existent avec les liens visibles bidirectionnellement"},
|
||||
{"given": "tables CFA crees", "when": "je cree formation test avec 1 bloc + 1 module + 1 attribution a Yan", "then": "les rollups formation_heures_attribuees, bloc_heures_attribuees, module_heures_attribuees sont calcules"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-04",
|
||||
"feature_id": "F-05",
|
||||
"connextra": "En tant qu'Admin, je veux creer les 4 tables Agence (CLIENT → PROJET → TACHE → INTERVENTION) avec liens FK et rollups, afin de tracer les projets clients.",
|
||||
"ac": [
|
||||
{"given": "table PERSONNE existe", "when": "je cree CLIENT, PROJET, TACHE, INTERVENTION avec liens", "then": "tables existent + lien optionnel PROJET ↔ FORMATION pour projet pedagogique"},
|
||||
{"given": "tables creees", "when": "j'ajoute client Centralis Europe + projet test + tache + intervention", "then": "les rollups projet_heures_realisees + tache_heures_realisees calculent"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-05",
|
||||
"feature_id": "F-07",
|
||||
"connextra": "En tant qu'Admin, je veux creer les vues recommandees doc 15 par table (table, kanban, calendar) afin d'avoir l'UX metier prete pour onboarding.",
|
||||
"ac": [
|
||||
{"given": "tables existent", "when": "je cree vue 'A attribuer' kanban sur MODULE group by module_statut", "then": "vue affiche kanban fonctionnel"},
|
||||
{"given": "vues creees", "when": "je verifie les vues principales par table (Tous, Kanban, Calendar selon doc 15)", "then": "minimum 9 vues fonctionnelles (~1 par table)"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-06",
|
||||
"feature_id": "F-09",
|
||||
"connextra": "En tant que Formateur, je veux saisir mes heures realisees via un form public Baserow sans compte, afin de logger rapidement depuis mobile.",
|
||||
"ac": [
|
||||
{"given": "table ATTRIBUTION existe", "when": "Admin cree form view publique 'Saisir heures realisees' sur ATTRIBUTION (champs limites)", "then": "formateur peut acceder par lien et soumettre"},
|
||||
{"given": "form public actif", "when": "formateur saisit attribution + heures + date", "then": "row creee, rollups recalcules"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-07",
|
||||
"feature_id": "F-04",
|
||||
"connextra": "En tant qu'Admin, je veux generer un share link Docmost pour une page support de formation, afin qu'un client puisse consulter sans creer de compte.",
|
||||
"ac": [
|
||||
{"given": "page Docmost existante", "when": "je clique 'Share' et configure expiration 7j + password", "then": "lien genere fonctionne en navigation privee sans login"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-08",
|
||||
"feature_id": "F-08",
|
||||
"connextra": "En tant qu'Admin, je veux pouvoir creer rapidement un space personnel pour un nouvel etudiant avec template, afin de l'onboarder en moins de 2 min.",
|
||||
"ac": [
|
||||
{"given": "Docmost workspace 'Acadenice formation-hub'", "when": "je cree space 'Etudiant - Marie Dupont' visibility prive (Marie + admins)", "then": "space cree, Marie peut creer/editer ses pages, autres etudiants ne le voient pas"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-09",
|
||||
"feature_id": "F-05",
|
||||
"connextra": "En tant qu'Admin, je veux generer un API token Baserow scope read+write pour le bridge service Phase 2, afin que le code custom puisse interroger les tables.",
|
||||
"ac": [
|
||||
{"given": "database formation-hub ok", "when": "je cree API token via Settings → API tokens", "then": "token genere fonctionne sur curl GET /api/database/rows/table/X/"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "S-10",
|
||||
"feature_id": "F-01",
|
||||
"connextra": "En tant qu'Admin, je veux backup quotidien automatique de Postgres docmost + data Baserow + uploads, afin d'avoir RPO 24h conforme CDC.",
|
||||
"ac": [
|
||||
{"given": "stack up", "when": "le cron quotidien 03:00 execute scripts/backup.sh", "then": "fichiers .sql.gz et .tar.gz crees dans backups/local/, retention 30 jours"}
|
||||
]
|
||||
}
|
||||
],
|
||||
"validated_at": null,
|
||||
"next_step": "PLAN d'iterations BUILD"
|
||||
}
|
||||
55
_byan-output/fast-app/formation-hub/dispatch.json
Normal file
55
_byan-output/fast-app/formation-hub/dispatch.json
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
{
|
||||
"assignments": [
|
||||
{
|
||||
"iteration_idx": 1,
|
||||
"specialist": "Claude Code (Sonnet 4.6) + Corentin",
|
||||
"model_tier": "haiku",
|
||||
"rationale": "Setup Baserow tables = action UI repetitive. Claude guide etape par etape, Corentin clique. Pas besoin de gros raisonnement.",
|
||||
"automation_possible": "Partiel — l'API Baserow permet de creer tables/fields programmatiquement, mais l'UI Baserow est plus rapide pour le 1er setup"
|
||||
},
|
||||
{
|
||||
"iteration_idx": 2,
|
||||
"specialist": "Claude Code + Corentin",
|
||||
"model_tier": "sonnet",
|
||||
"rationale": "Formulas Baserow ont une syntaxe specifique (field('x'), lookup, count, sum). Sonnet pour bien syntaxer les formulas du doc 15.",
|
||||
"automation_possible": "Partiel"
|
||||
},
|
||||
{
|
||||
"iteration_idx": 3,
|
||||
"specialist": "Corentin solo",
|
||||
"model_tier": "haiku",
|
||||
"rationale": "Setup Docmost UI = action de config standard. Pas besoin Claude.",
|
||||
"automation_possible": "Faible — Docmost API est limitee pour creation workspace/spaces"
|
||||
},
|
||||
{
|
||||
"iteration_idx": 4,
|
||||
"specialist": "Corentin + (eventuellement Claude pour script create-space-etudiant)",
|
||||
"model_tier": "haiku",
|
||||
"rationale": "Pattern repetitif → automatisable via script bash + Docmost API",
|
||||
"automation_possible": "Oui via script"
|
||||
},
|
||||
{
|
||||
"iteration_idx": 5,
|
||||
"specialist": "Corentin (DevOps son metier)",
|
||||
"model_tier": "haiku",
|
||||
"rationale": "API token + cron + smoke = du DevOps pur. Corentin maitrise.",
|
||||
"automation_possible": "100% script"
|
||||
},
|
||||
{
|
||||
"iteration_idx": 6,
|
||||
"specialist": "Corentin + Yan + Sophie",
|
||||
"model_tier": "n/a (humain)",
|
||||
"rationale": "Migration data necessite knowledge metier (qui est qui dans les RH, quels clients, etc.). Pas Claude — humains internes.",
|
||||
"automation_possible": "Partiel : Claude peut transformer CSV → format Baserow API"
|
||||
},
|
||||
{
|
||||
"iteration_idx": 7,
|
||||
"specialist": "Equipe Acadenice (Yan, Ludo, Corentin) + 5-10 testeurs",
|
||||
"model_tier": "n/a (humain)",
|
||||
"rationale": "Test reel necessite humains. Claude peut compiler les retours en backlog priorise apres.",
|
||||
"automation_possible": "Non"
|
||||
}
|
||||
],
|
||||
"halt": null,
|
||||
"validated_at": null
|
||||
}
|
||||
10
_byan-output/fast-app/formation-hub/pitch.json
Normal file
10
_byan-output/fast-app/formation-hub/pitch.json
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"name_app": "formation-hub",
|
||||
"one_liner": "Notion-like self-host pour Acadenice (CFA + Agence dev) avec suivi heures formateurs/devs unifie",
|
||||
"who": "Equipe Acadenice (~20 employes : direction Ludo, resp tech Yan, AdminSys/DevOps Corentin, formateurs, devs) + ~70 etudiants (spaces personnels libres) + clients guests (acces lien partage). Cible totale : 90-100 users, ~30 simultanes peak.",
|
||||
"what": "Plateforme self-host composite : (1) Wiki collaboratif (Docmost AGPL) avec diagrammes natifs Mermaid/Drawio/Excalidraw + share links + spaces multi-tenant. (2) Bases de donnees structurees (Baserow MIT) pour le suivi heures formation/agence avec entite PERSONNE pivot multi-roles. (3) Bridge custom Node TS (Phase 2) qui synchronise Docmost et Baserow bidirectionnel et expose des Tiptap nodes custom pour UX unifie. (4) MCP server (Phase 3) pour interaction Claude/agents IA.",
|
||||
"why": "Centraliser la doc + suivre les heures formation/agence dans un outil unifie self-host **illimite users**. Alternatives ecartees : Notion paye au seat, AFFiNE limite a 10 seats free, AppFlowy limite a 1 user free, Outline pas de bidirec backlinks. Acadenice a une double casquette CFA + Agence dev (formateurs = devs sur projets clients) = capacite annuelle splittee entre les deux activites. Aucun outil existant ne modelise ca correctement.",
|
||||
"context": "Phase 0 conception complete (19 docs Merise Agile + UML + GitOps). Repo : github.com/AcadeNice/wiki + git.acadenice.com/AcadeNice/Wiki (selfhost source of truth). Stack Docker compose locale up et healthy au 2026-05-07.",
|
||||
"validated_at": "2026-05-07",
|
||||
"validated_by": "Corentin JOGUET"
|
||||
}
|
||||
65
_byan-output/fast-app/formation-hub/plan.json
Normal file
65
_byan-output/fast-app/formation-hub/plan.json
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"iterations": [
|
||||
{
|
||||
"idx": 1,
|
||||
"name": "I1 — Setup Baserow vanilla (tables + liens)",
|
||||
"stories": ["S-02", "S-03", "S-04"],
|
||||
"expected_loops": 2,
|
||||
"definition_of_done": "9 tables creees dans Baserow database 'formation-hub' avec tous les liens FK fonctionnels (testes manuellement avec rows-temoin). Pas encore de formulas/rollups complexes — juste structure.",
|
||||
"deliverable": "Schema Baserow exporte JSON dans baserow/schemas/*.json + screenshots de chaque table"
|
||||
},
|
||||
{
|
||||
"idx": 2,
|
||||
"name": "I2 — Formulas, rollups, vues",
|
||||
"stories": ["S-02 (formulas part)", "S-05"],
|
||||
"expected_loops": 2,
|
||||
"definition_of_done": "Toutes les formulas du doc 15 sont actives + 9+ vues recommandees (kanban, calendar, table) crees. Rows test confirment les calculs.",
|
||||
"deliverable": "Vues exportees + screenshots dashboards"
|
||||
},
|
||||
{
|
||||
"idx": 3,
|
||||
"name": "I3 — Setup Docmost workspace + permissions + share",
|
||||
"stories": ["S-01", "S-07"],
|
||||
"expected_loops": 2,
|
||||
"definition_of_done": "Workspace + 3 spaces + permissions par defaut + 1 page test partagee par lien public expire 7j",
|
||||
"deliverable": "Captures workspace + URL share test"
|
||||
},
|
||||
{
|
||||
"idx": 4,
|
||||
"name": "I4 — Spaces etudiants + form public saisie heures",
|
||||
"stories": ["S-08", "S-06"],
|
||||
"expected_loops": 2,
|
||||
"definition_of_done": "Pattern create-space-etudiant valide (script ou checklist 2-min) + form public Baserow ATTRIBUTION accessible mobile",
|
||||
"deliverable": "Doc onboarding etudiant + URL form public"
|
||||
},
|
||||
{
|
||||
"idx": 5,
|
||||
"name": "I5 — API token + backup automatise + smoke test E2E",
|
||||
"stories": ["S-09", "S-10"],
|
||||
"expected_loops": 1,
|
||||
"definition_of_done": "Token Baserow fonctionnel + cron backup setup + scripts/healthcheck.sh + scripts/smoke-test.sh passent",
|
||||
"deliverable": "Token stocke (vault/.env), 1ere execution backup reussie, logs"
|
||||
},
|
||||
{
|
||||
"idx": 6,
|
||||
"name": "I6 — Migration data initiale (formations + clients existants)",
|
||||
"stories": ["data migration"],
|
||||
"expected_loops": 3,
|
||||
"definition_of_done": "Donnees reelles Acadenice importees depuis sources actuelles (Excel/Trello/autre) dans Baserow, integrite verifiee (rollups coherents avec realite metier)",
|
||||
"deliverable": "Rapport migration : nb rows attendus vs imported, cas speciaux"
|
||||
},
|
||||
{
|
||||
"idx": 7,
|
||||
"name": "I7 — Onboarding 5-10 power users + retours UX",
|
||||
"stories": ["onboarding"],
|
||||
"expected_loops": 2,
|
||||
"definition_of_done": "5-10 personnes Acadenice (Yan, Ludo, Sophie, 2-3 formateurs, 2 devs) ont utilise pendant 1 semaine + retours collectes",
|
||||
"deliverable": "Backlog UX issues priorise"
|
||||
}
|
||||
],
|
||||
"phases_apres_plan": [
|
||||
"Iterations Phase 2 (bridge custom) seront planifiees apres I7 selon douleurs reelles identifiees",
|
||||
"MPD Baserow concret est dans doc 15-baserow-mpd.md"
|
||||
],
|
||||
"validated_at": null
|
||||
}
|
||||
86
baserow/seed/README.md
Normal file
86
baserow/seed/README.md
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
# Baserow seed
|
||||
|
||||
Cree les 9 tables `formation-hub` dans Baserow via l'API. Idempotent — skip ce qui existe.
|
||||
|
||||
## Quickstart (4 etapes, ~5 min)
|
||||
|
||||
### 1. Creer le compte admin Baserow (premier boot — manuel UI)
|
||||
|
||||
Aller sur **http://localhost:8080**. La premiere page propose de creer un compte. Remplir :
|
||||
- Workspace name : `Acadenice`
|
||||
- Email : `admin@acadenice.fr` (ou autre admin)
|
||||
- Password : password robuste (a sauvegarder dans pass/vault)
|
||||
|
||||
### 2. Pre-requis Python
|
||||
|
||||
```bash
|
||||
cd baserow/seed
|
||||
pip install -r requirements.txt
|
||||
# OU si tu prefere : python -m venv .venv && source .venv/bin/activate && pip install -r requirements.txt
|
||||
```
|
||||
|
||||
### 3. Run le seed
|
||||
|
||||
```bash
|
||||
BASEROW_URL=http://localhost:8080 \
|
||||
BASEROW_EMAIL=admin@acadenice.fr \
|
||||
BASEROW_PASSWORD='ton-password' \
|
||||
python baserow/seed/seed.py
|
||||
```
|
||||
|
||||
Output attendu :
|
||||
```
|
||||
[1/5] Login http://localhost:8080
|
||||
[auth] Logged in as admin@acadenice.fr
|
||||
|
||||
[2/5] Workspace 'Acadenice'
|
||||
[workspace] Reuse 'Acadenice' id=1
|
||||
|
||||
[3/5] Database 'formation-hub'
|
||||
[db] Created 'formation-hub' id=1
|
||||
|
||||
[4/5] Tables + primitive fields
|
||||
[table] Created 'personne' id=1
|
||||
[field] Created 'personne_prenom' (text)
|
||||
...
|
||||
|
||||
[5/5] Link fields (2nd pass)
|
||||
[link] Created bloc.bloc_formation -> formation
|
||||
...
|
||||
|
||||
=== Seed OK ===
|
||||
Workspace: Acadenice
|
||||
Database : formation-hub
|
||||
Tables : 9
|
||||
Links : 10
|
||||
```
|
||||
|
||||
### 4. Verifier dans l'UI
|
||||
|
||||
Ouvrir http://localhost:8080, naviguer dans le workspace **Acadenice** → database **formation-hub** → 9 tables doivent etre presentes avec leurs champs et liens.
|
||||
|
||||
## Re-runnable
|
||||
|
||||
Le script est **idempotent**. Tu peux le relancer apres modifications du schema — il ne recree pas ce qui existe deja.
|
||||
|
||||
Pour reset complet : drop la database via l'UI Baserow, puis relancer.
|
||||
|
||||
## Schema
|
||||
|
||||
`schema.json` decrit les 9 tables + 10 liens FK. Format JSON declaratif, modifiable.
|
||||
|
||||
Les **formulas** (rollups, heures_restantes, etc.) ne sont **pas** crees par ce seed (Phase 1 = structure seule). Elles seront ajoutees en iteration 2 du plan Fast-App via un seed-formulas.py separe.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptome | Cause probable | Fix |
|
||||
|----------|----------------|-----|
|
||||
| `401 Unauthorized` | Mauvais email/password | Verifier BASEROW_EMAIL et BASEROW_PASSWORD |
|
||||
| `400 Bad Request` sur field | Type non supporte version Baserow | Voir logs detail, ajuster `_field_to_payload` |
|
||||
| Field deja existant | Idempotent OK | Aucune action |
|
||||
| Tables creees mais vides | Normal — pas de seed data Phase 1 | Sera ajoute iteration 6 (migration data) |
|
||||
|
||||
## References
|
||||
|
||||
- Baserow API doc : https://baserow.io/api-docs
|
||||
- Doc 15 MPD (mapping fields → Baserow types) : `docs/15-baserow-mpd.md`
|
||||
1
baserow/seed/requirements.txt
Normal file
1
baserow/seed/requirements.txt
Normal file
|
|
@ -0,0 +1 @@
|
|||
requests>=2.32.0
|
||||
202
baserow/seed/schema.json
Normal file
202
baserow/seed/schema.json
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
{
|
||||
"$schema": "Baserow schema declaratif — formation-hub Phase 1",
|
||||
"version": "1.0.0",
|
||||
"workspace_name": "Acadenice",
|
||||
"database_name": "formation-hub",
|
||||
"tables": [
|
||||
{
|
||||
"name": "personne",
|
||||
"primary_field": "personne_nom",
|
||||
"fields": [
|
||||
{"name": "personne_nom", "type": "text", "primary": true},
|
||||
{"name": "personne_prenom", "type": "text"},
|
||||
{"name": "personne_email", "type": "email"},
|
||||
{"name": "personne_telephone", "type": "phone_number"},
|
||||
{"name": "personne_capacite_annuelle", "type": "number", "number_decimal_places": 2},
|
||||
{"name": "personne_split_formation_pct", "type": "number", "number_decimal_places": 1, "number_default": 50.0},
|
||||
{"name": "personne_split_agence_pct", "type": "number", "number_decimal_places": 1, "number_default": 50.0},
|
||||
{"name": "personne_roles", "type": "multiple_select", "select_options": [
|
||||
{"value": "formateur", "color": "blue"},
|
||||
{"value": "developpeur", "color": "green"},
|
||||
{"value": "admin", "color": "red"},
|
||||
{"value": "direction", "color": "purple"},
|
||||
{"value": "support", "color": "gray"}
|
||||
]},
|
||||
{"name": "personne_statut", "type": "single_select", "select_options": [
|
||||
{"value": "actif", "color": "green"},
|
||||
{"value": "inactif", "color": "gray"}
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "formation",
|
||||
"primary_field": "formation_nom",
|
||||
"fields": [
|
||||
{"name": "formation_nom", "type": "text", "primary": true},
|
||||
{"name": "formation_description", "type": "long_text"},
|
||||
{"name": "formation_filiere", "type": "single_select", "select_options": [
|
||||
{"value": "dev", "color": "blue"},
|
||||
{"value": "graphisme", "color": "pink"},
|
||||
{"value": "marketing", "color": "yellow"},
|
||||
{"value": "iot", "color": "orange"},
|
||||
{"value": "cybersec", "color": "red"}
|
||||
]},
|
||||
{"name": "formation_heures_totales", "type": "number", "number_decimal_places": 2},
|
||||
{"name": "formation_statut", "type": "single_select", "select_options": [
|
||||
{"value": "draft", "color": "gray"},
|
||||
{"value": "actif", "color": "green"},
|
||||
{"value": "termine", "color": "blue"},
|
||||
{"value": "archive", "color": "dark-gray"}
|
||||
]},
|
||||
{"name": "formation_date_debut", "type": "date"},
|
||||
{"name": "formation_date_fin", "type": "date"},
|
||||
{"name": "formation_created_at", "type": "created_on"},
|
||||
{"name": "formation_updated_at", "type": "last_modified"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "bloc",
|
||||
"primary_field": "bloc_nom",
|
||||
"fields": [
|
||||
{"name": "bloc_nom", "type": "text", "primary": true},
|
||||
{"name": "bloc_description", "type": "long_text"},
|
||||
{"name": "bloc_heures_prevues", "type": "number", "number_decimal_places": 2},
|
||||
{"name": "bloc_ordre", "type": "number", "number_decimal_places": 0}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "module",
|
||||
"primary_field": "module_nom",
|
||||
"fields": [
|
||||
{"name": "module_nom", "type": "text", "primary": true},
|
||||
{"name": "module_description", "type": "long_text"},
|
||||
{"name": "module_heures_prevues", "type": "number", "number_decimal_places": 2},
|
||||
{"name": "module_statut", "type": "single_select", "select_options": [
|
||||
{"value": "a_attribuer", "color": "gray"},
|
||||
{"value": "attribue", "color": "blue"},
|
||||
{"value": "en_cours", "color": "yellow"},
|
||||
{"value": "realise", "color": "green"},
|
||||
{"value": "annule", "color": "red"}
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "attribution",
|
||||
"primary_field": "attribution_titre",
|
||||
"fields": [
|
||||
{"name": "attribution_titre", "type": "text", "primary": true, "comment": "Sera remplace par formula apres link"},
|
||||
{"name": "attribution_heures_attribuees", "type": "number", "number_decimal_places": 2},
|
||||
{"name": "attribution_heures_realisees", "type": "number", "number_decimal_places": 2, "number_default": 0},
|
||||
{"name": "attribution_date_debut", "type": "date"},
|
||||
{"name": "attribution_date_fin", "type": "date"},
|
||||
{"name": "attribution_statut", "type": "single_select", "select_options": [
|
||||
{"value": "planifie", "color": "gray"},
|
||||
{"value": "en_cours", "color": "yellow"},
|
||||
{"value": "realise", "color": "green"},
|
||||
{"value": "annule", "color": "red"}
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "client",
|
||||
"primary_field": "client_nom",
|
||||
"fields": [
|
||||
{"name": "client_nom", "type": "text", "primary": true},
|
||||
{"name": "client_contact_principal", "type": "text"},
|
||||
{"name": "client_contact_email", "type": "email"},
|
||||
{"name": "client_contact_telephone", "type": "phone_number"},
|
||||
{"name": "client_secteur", "type": "text"},
|
||||
{"name": "client_notes", "type": "long_text"},
|
||||
{"name": "client_statut", "type": "single_select", "select_options": [
|
||||
{"value": "prospect", "color": "yellow"},
|
||||
{"value": "actif", "color": "green"},
|
||||
{"value": "inactif", "color": "gray"},
|
||||
{"value": "archive", "color": "dark-gray"}
|
||||
]},
|
||||
{"name": "client_created_at", "type": "created_on"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "projet",
|
||||
"primary_field": "projet_nom",
|
||||
"fields": [
|
||||
{"name": "projet_nom", "type": "text", "primary": true},
|
||||
{"name": "projet_description", "type": "long_text"},
|
||||
{"name": "projet_type", "type": "single_select", "select_options": [
|
||||
{"value": "site_web", "color": "blue"},
|
||||
{"value": "app_mobile", "color": "green"},
|
||||
{"value": "api", "color": "yellow"},
|
||||
{"value": "infra", "color": "orange"},
|
||||
{"value": "audit", "color": "red"},
|
||||
{"value": "support", "color": "purple"},
|
||||
{"value": "autre", "color": "gray"}
|
||||
]},
|
||||
{"name": "projet_charge_heures", "type": "number", "number_decimal_places": 2},
|
||||
{"name": "projet_date_debut", "type": "date"},
|
||||
{"name": "projet_date_fin_prevue", "type": "date"},
|
||||
{"name": "projet_date_livraison", "type": "date"},
|
||||
{"name": "projet_statut", "type": "single_select", "select_options": [
|
||||
{"value": "devis", "color": "yellow"},
|
||||
{"value": "en_cours", "color": "blue"},
|
||||
{"value": "livre", "color": "green"},
|
||||
{"value": "cloture", "color": "dark-green"},
|
||||
{"value": "abandonne", "color": "red"}
|
||||
]},
|
||||
{"name": "projet_url", "type": "url"},
|
||||
{"name": "projet_repository", "type": "url"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "tache",
|
||||
"primary_field": "tache_titre",
|
||||
"fields": [
|
||||
{"name": "tache_titre", "type": "text", "primary": true},
|
||||
{"name": "tache_description", "type": "long_text"},
|
||||
{"name": "tache_charge_heures", "type": "number", "number_decimal_places": 2},
|
||||
{"name": "tache_priorite", "type": "single_select", "select_options": [
|
||||
{"value": "faible", "color": "gray"},
|
||||
{"value": "normale", "color": "blue"},
|
||||
{"value": "haute", "color": "orange"},
|
||||
{"value": "critique", "color": "red"}
|
||||
]},
|
||||
{"name": "tache_statut", "type": "single_select", "select_options": [
|
||||
{"value": "todo", "color": "gray"},
|
||||
{"value": "in_progress", "color": "blue"},
|
||||
{"value": "review", "color": "yellow"},
|
||||
{"value": "done", "color": "green"},
|
||||
{"value": "abandoned", "color": "red"}
|
||||
]},
|
||||
{"name": "tache_date_debut", "type": "date"},
|
||||
{"name": "tache_date_fin_prevue", "type": "date"}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "intervention",
|
||||
"primary_field": "intervention_titre",
|
||||
"fields": [
|
||||
{"name": "intervention_titre", "type": "text", "primary": true, "comment": "Sera remplace par formula apres link"},
|
||||
{"name": "intervention_heures", "type": "number", "number_decimal_places": 2},
|
||||
{"name": "intervention_date", "type": "date"},
|
||||
{"name": "intervention_notes", "type": "long_text"},
|
||||
{"name": "intervention_statut", "type": "single_select", "select_options": [
|
||||
{"value": "planifie", "color": "gray"},
|
||||
{"value": "realise", "color": "green"},
|
||||
{"value": "annule", "color": "red"}
|
||||
]}
|
||||
]
|
||||
}
|
||||
],
|
||||
"links": [
|
||||
{"from_table": "bloc", "from_field": "bloc_formation", "to_table": "formation"},
|
||||
{"from_table": "module", "from_field": "module_bloc", "to_table": "bloc"},
|
||||
{"from_table": "attribution", "from_field": "attribution_module", "to_table": "module"},
|
||||
{"from_table": "attribution", "from_field": "attribution_personne", "to_table": "personne"},
|
||||
{"from_table": "projet", "from_field": "projet_client", "to_table": "client"},
|
||||
{"from_table": "projet", "from_field": "projet_formation_pedagogique", "to_table": "formation"},
|
||||
{"from_table": "tache", "from_field": "tache_projet", "to_table": "projet"},
|
||||
{"from_table": "tache", "from_field": "tache_assignee", "to_table": "personne"},
|
||||
{"from_table": "intervention", "from_field": "intervention_tache", "to_table": "tache"},
|
||||
{"from_table": "intervention", "from_field": "intervention_personne", "to_table": "personne"}
|
||||
],
|
||||
"_note_phase_2": "Les formulas (rollups, heures_restantes) seront ajoutees en iteration 2 du plan Fast-App. Phase 1 = structure + liens seuls."
|
||||
}
|
||||
249
baserow/seed/seed.py
Normal file
249
baserow/seed/seed.py
Normal file
|
|
@ -0,0 +1,249 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Baserow seed script — cree les 9 tables formation-hub via API.
|
||||
|
||||
Usage :
|
||||
BASEROW_URL=http://localhost:8080 \
|
||||
BASEROW_EMAIL=admin@acadenice.fr \
|
||||
BASEROW_PASSWORD=... \
|
||||
python baserow/seed/seed.py [--schema baserow/seed/schema.json]
|
||||
|
||||
Process :
|
||||
1. Login (recupere JWT)
|
||||
2. Create or reuse workspace
|
||||
3. Create or reuse database
|
||||
4. Create tables (1 pass)
|
||||
5. Create primitive fields per table
|
||||
6. Create link_row fields (2nd pass — apres que les tables existent)
|
||||
|
||||
Idempotent : skip ce qui existe deja.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
import requests
|
||||
|
||||
|
||||
class BaserowSeed:
|
||||
def __init__(self, base_url: str, email: str, password: str) -> None:
|
||||
self.base_url = base_url.rstrip("/")
|
||||
self.email = email
|
||||
self.password = password
|
||||
self.token: str | None = None
|
||||
self.session = requests.Session()
|
||||
|
||||
def _headers(self) -> dict[str, str]:
|
||||
if self.token is None:
|
||||
raise RuntimeError("Not logged in")
|
||||
return {"Authorization": f"JWT {self.token}", "Content-Type": "application/json"}
|
||||
|
||||
def login(self) -> None:
|
||||
url = f"{self.base_url}/api/user/token-auth/"
|
||||
r = self.session.post(url, json={"email": self.email, "password": self.password})
|
||||
r.raise_for_status()
|
||||
self.token = r.json()["token"]
|
||||
print(f" [auth] Logged in as {self.email}")
|
||||
|
||||
def get_or_create_workspace(self, name: str) -> int:
|
||||
r = self.session.get(f"{self.base_url}/api/workspaces/", headers=self._headers())
|
||||
r.raise_for_status()
|
||||
for w in r.json():
|
||||
if w["name"] == name:
|
||||
print(f" [workspace] Reuse '{name}' id={w['id']}")
|
||||
return w["id"]
|
||||
r = self.session.post(
|
||||
f"{self.base_url}/api/workspaces/", headers=self._headers(), json={"name": name}
|
||||
)
|
||||
r.raise_for_status()
|
||||
wid = r.json()["id"]
|
||||
print(f" [workspace] Created '{name}' id={wid}")
|
||||
return wid
|
||||
|
||||
def get_or_create_database(self, workspace_id: int, name: str) -> int:
|
||||
r = self.session.get(
|
||||
f"{self.base_url}/api/applications/workspace/{workspace_id}/", headers=self._headers()
|
||||
)
|
||||
r.raise_for_status()
|
||||
for app in r.json():
|
||||
if app["name"] == name and app["type"] == "database":
|
||||
print(f" [db] Reuse '{name}' id={app['id']}")
|
||||
return app["id"]
|
||||
r = self.session.post(
|
||||
f"{self.base_url}/api/applications/workspace/{workspace_id}/",
|
||||
headers=self._headers(),
|
||||
json={"name": name, "type": "database"},
|
||||
)
|
||||
r.raise_for_status()
|
||||
did = r.json()["id"]
|
||||
print(f" [db] Created '{name}' id={did}")
|
||||
return did
|
||||
|
||||
def list_tables(self, database_id: int) -> dict[str, int]:
|
||||
r = self.session.get(
|
||||
f"{self.base_url}/api/database/tables/database/{database_id}/", headers=self._headers()
|
||||
)
|
||||
r.raise_for_status()
|
||||
return {t["name"]: t["id"] for t in r.json()}
|
||||
|
||||
def create_table_with_minimal(self, database_id: int, name: str) -> int:
|
||||
r = self.session.post(
|
||||
f"{self.base_url}/api/database/tables/database/{database_id}/",
|
||||
headers=self._headers(),
|
||||
json={"name": name},
|
||||
)
|
||||
r.raise_for_status()
|
||||
tid = r.json()["id"]
|
||||
print(f" [table] Created '{name}' id={tid}")
|
||||
return tid
|
||||
|
||||
def list_fields(self, table_id: int) -> list[dict[str, Any]]:
|
||||
r = self.session.get(
|
||||
f"{self.base_url}/api/database/fields/table/{table_id}/", headers=self._headers()
|
||||
)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
|
||||
def create_field(self, table_id: int, field_def: dict[str, Any]) -> int:
|
||||
payload = self._field_to_payload(field_def)
|
||||
r = self.session.post(
|
||||
f"{self.base_url}/api/database/fields/table/{table_id}/",
|
||||
headers=self._headers(),
|
||||
json=payload,
|
||||
)
|
||||
if r.status_code >= 300:
|
||||
print(f" [ERROR] field {field_def['name']} payload={payload} resp={r.text}")
|
||||
r.raise_for_status()
|
||||
return r.json()["id"]
|
||||
|
||||
def update_field(self, field_id: int, field_def: dict[str, Any]) -> None:
|
||||
payload = self._field_to_payload(field_def)
|
||||
r = self.session.patch(
|
||||
f"{self.base_url}/api/database/fields/{field_id}/",
|
||||
headers=self._headers(),
|
||||
json=payload,
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
def delete_field(self, field_id: int) -> None:
|
||||
r = self.session.delete(
|
||||
f"{self.base_url}/api/database/fields/{field_id}/", headers=self._headers()
|
||||
)
|
||||
r.raise_for_status()
|
||||
|
||||
@staticmethod
|
||||
def _field_to_payload(field_def: dict[str, Any]) -> dict[str, Any]:
|
||||
ftype = field_def["type"]
|
||||
payload: dict[str, Any] = {"name": field_def["name"], "type": ftype}
|
||||
if ftype == "number":
|
||||
payload["number_decimal_places"] = field_def.get("number_decimal_places", 0)
|
||||
payload["number_negative"] = True
|
||||
elif ftype == "single_select" or ftype == "multiple_select":
|
||||
payload["select_options"] = field_def.get("select_options", [])
|
||||
elif ftype == "date":
|
||||
payload["date_format"] = "ISO"
|
||||
elif ftype == "long_text":
|
||||
payload["long_text_enable_rich_text"] = True
|
||||
return payload
|
||||
|
||||
def create_link_field(self, from_table_id: int, name: str, to_table_id: int) -> int:
|
||||
r = self.session.post(
|
||||
f"{self.base_url}/api/database/fields/table/{from_table_id}/",
|
||||
headers=self._headers(),
|
||||
json={"name": name, "type": "link_row", "link_row_table_id": to_table_id},
|
||||
)
|
||||
if r.status_code >= 300:
|
||||
print(f" [ERROR] link {name} resp={r.text}")
|
||||
r.raise_for_status()
|
||||
return r.json()["id"]
|
||||
|
||||
def seed(self, schema: dict[str, Any]) -> None:
|
||||
print(f"\n[1/5] Login {self.base_url}")
|
||||
self.login()
|
||||
|
||||
print(f"\n[2/5] Workspace '{schema['workspace_name']}'")
|
||||
ws_id = self.get_or_create_workspace(schema["workspace_name"])
|
||||
|
||||
print(f"\n[3/5] Database '{schema['database_name']}'")
|
||||
db_id = self.get_or_create_database(ws_id, schema["database_name"])
|
||||
|
||||
print(f"\n[4/5] Tables + primitive fields")
|
||||
existing = self.list_tables(db_id)
|
||||
table_ids: dict[str, int] = dict(existing)
|
||||
for table in schema["tables"]:
|
||||
tname = table["name"]
|
||||
if tname in existing:
|
||||
tid = existing[tname]
|
||||
print(f" [table] Reuse '{tname}' id={tid}")
|
||||
else:
|
||||
tid = self.create_table_with_minimal(db_id, tname)
|
||||
table_ids[tname] = tid
|
||||
self._sync_fields(tid, table)
|
||||
|
||||
print(f"\n[5/5] Link fields (2nd pass)")
|
||||
for link in schema["links"]:
|
||||
from_id = table_ids[link["from_table"]]
|
||||
to_id = table_ids[link["to_table"]]
|
||||
existing_fields = {f["name"]: f for f in self.list_fields(from_id)}
|
||||
if link["from_field"] in existing_fields:
|
||||
print(f" [link] Reuse {link['from_table']}.{link['from_field']} -> {link['to_table']}")
|
||||
continue
|
||||
self.create_link_field(from_id, link["from_field"], to_id)
|
||||
print(f" [link] Created {link['from_table']}.{link['from_field']} -> {link['to_table']}")
|
||||
|
||||
print("\n=== Seed OK ===")
|
||||
print(f" Workspace: {schema['workspace_name']}")
|
||||
print(f" Database : {schema['database_name']}")
|
||||
print(f" Tables : {len(table_ids)}")
|
||||
print(f" Links : {len(schema['links'])}")
|
||||
|
||||
def _sync_fields(self, table_id: int, table_def: dict[str, Any]) -> None:
|
||||
existing = {f["name"]: f for f in self.list_fields(table_id)}
|
||||
# Default Baserow table comes with one primary field "Name". Rename it to our primary if needed.
|
||||
primary_target = table_def["primary_field"]
|
||||
if primary_target not in existing:
|
||||
primary_existing = next((f for f in existing.values() if f.get("primary")), None)
|
||||
if primary_existing is not None:
|
||||
# Find the primary field def
|
||||
primary_def = next(f for f in table_def["fields"] if f.get("primary"))
|
||||
self.update_field(primary_existing["id"], primary_def)
|
||||
print(f" [field] Renamed primary -> '{primary_target}'")
|
||||
existing[primary_target] = {**primary_existing, "name": primary_target}
|
||||
if primary_existing["name"] != primary_target:
|
||||
del existing[primary_existing["name"]]
|
||||
|
||||
for field_def in table_def["fields"]:
|
||||
fname = field_def["name"]
|
||||
if fname in existing:
|
||||
continue
|
||||
self.create_field(table_id, field_def)
|
||||
print(f" [field] Created '{fname}' ({field_def['type']})")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description="Seed Baserow formation-hub")
|
||||
parser.add_argument("--schema", default=str(Path(__file__).parent / "schema.json"))
|
||||
args = parser.parse_args()
|
||||
|
||||
base_url = os.environ.get("BASEROW_URL", "http://localhost:8080")
|
||||
email = os.environ.get("BASEROW_EMAIL")
|
||||
password = os.environ.get("BASEROW_PASSWORD")
|
||||
if not email or not password:
|
||||
print("ERROR: set BASEROW_EMAIL et BASEROW_PASSWORD env vars", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
schema = json.loads(Path(args.schema).read_text())
|
||||
seeder = BaserowSeed(base_url, email, password)
|
||||
try:
|
||||
seeder.seed(schema)
|
||||
except requests.HTTPError as e:
|
||||
print(f"\nHTTP error: {e}\nResponse: {e.response.text if e.response else ''}", file=sys.stderr)
|
||||
return 2
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Loading…
Add table
Reference in a new issue