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
I4 — Forms publics + space etudiant : - baserow/seed/seed_forms.py : cree forms publics sur attribution + intervention (saisie heures via lien sans compte). Form cree OK, field-options endpoint retourne 404 sur Baserow 1.30 — a investiguer (URL different selon version). - docmost/setup/create-space-etudiant.py : cree space prive + invite etudiant + page Welcome template. Slug strict lettres+chiffres only (fix Docmost). I5 — Ops : - scripts/healthcheck.sh : check UI + API health (Docmost+Baserow), affiche status containers Docker. 4/4 OK en local. - scripts/cron-install.sh : installe cron quotidien backup + healthcheck */5min. Makefile : targets seed-baserow-forms, create-space-etudiant. Tous les targets utilisent maintenant .venv/ local pour eviter pip systeme. .gitignore : exclut .venv/ + __pycache__/.
182 lines
6.4 KiB
Python
182 lines
6.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Cree un space etudiant prive dans Docmost — pattern story S-08.
|
|
|
|
Usage :
|
|
DOCMOST_URL=http://localhost:3000 \\
|
|
DOCMOST_ADMIN_EMAIL=corentin@acadenice.fr \\
|
|
DOCMOST_ADMIN_PASSWORD=... \\
|
|
python docmost/setup/create-space-etudiant.py \\
|
|
--nom "Dupont" --prenom "Marie" --email "marie.dupont@acadenice.fr"
|
|
|
|
Cree :
|
|
- Un space "Etudiant - Marie Dupont" en visibility=private
|
|
- Y invite l'etudiant en role 'writer'
|
|
- Cree une page template "Bienvenue" avec instructions
|
|
|
|
Idempotent : skip si space existe deja.
|
|
"""
|
|
|
|
import argparse
|
|
import os
|
|
import sys
|
|
from typing import Any
|
|
|
|
import requests
|
|
|
|
|
|
class DocmostSpaceCreator:
|
|
def __init__(self, base_url: str) -> None:
|
|
self.base_url = base_url.rstrip("/")
|
|
self.session = requests.Session()
|
|
self.session.headers.update({"Content-Type": "application/json"})
|
|
|
|
def _post(self, path: str, body: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
r = self.session.post(f"{self.base_url}{path}", json=body or {})
|
|
if r.status_code >= 300:
|
|
print(f" [ERROR] POST {path} → {r.status_code} resp={r.text[:300]}")
|
|
r.raise_for_status()
|
|
if not r.text:
|
|
return {}
|
|
payload = r.json()
|
|
if isinstance(payload, dict) and "data" in payload and isinstance(payload["data"], (dict, list)):
|
|
return payload["data"]
|
|
return payload
|
|
|
|
def login(self, email: str, password: str) -> None:
|
|
self._post("/api/auth/login", {"email": email, "password": password})
|
|
print(f" [auth] Logged in as {email}")
|
|
|
|
def list_spaces(self) -> list[dict[str, Any]]:
|
|
try:
|
|
data = self._post("/api/spaces/", {"page": 1, "limit": 100})
|
|
if isinstance(data, dict):
|
|
return data.get("items") or []
|
|
if isinstance(data, list):
|
|
return data
|
|
except requests.HTTPError:
|
|
pass
|
|
return []
|
|
|
|
def create_space(self, name: str, description: str, slug: str, visibility: str = "private") -> str:
|
|
result = self._post(
|
|
"/api/spaces/create",
|
|
{"name": name, "description": description, "slug": slug, "visibility": visibility},
|
|
)
|
|
return result.get("id")
|
|
|
|
def add_member_by_email(self, space_id: str, email: str, role: str = "writer") -> dict[str, Any] | None:
|
|
try:
|
|
return self._post(
|
|
"/api/spaces/members/add",
|
|
{"spaceId": space_id, "userEmails": [email], "role": role},
|
|
)
|
|
except requests.HTTPError as e:
|
|
print(f" [member] Echec ajout {email} : {e}")
|
|
print(f" (l'etudiant doit deja exister comme user Docmost. Sinon, l'inviter d'abord via UI ou API user/create.)")
|
|
return None
|
|
|
|
def create_page(self, space_id: str, title: str, content_md: str) -> str:
|
|
result = self._post(
|
|
"/api/pages/create",
|
|
{"spaceId": space_id, "title": title, "format": "markdown", "content": content_md},
|
|
)
|
|
return result.get("id")
|
|
|
|
|
|
def welcome_template(prenom: str, nom: str) -> str:
|
|
return f"""# Bienvenue {prenom} !
|
|
|
|
Voici ton **espace personnel** sur le wiki Acadenice. Il est **prive** : seul toi et l'admin peuvent le voir.
|
|
|
|
## Tu peux faire ce que tu veux ici
|
|
|
|
- Prendre des notes pendant les cours
|
|
- Garder tes ressources perso (liens, references, brouillons)
|
|
- Tester les fonctionnalites du wiki :
|
|
- `/mermaid` pour faire un diagramme
|
|
- `/excalidraw` pour dessiner
|
|
- `/drawio` pour des schemas techniques
|
|
- Liste, tableaux, code blocks, embeds video, etc.
|
|
|
|
## Quelques idees pour commencer
|
|
|
|
- Cree une page "Mes objectifs formation"
|
|
- Une page par module avec tes notes
|
|
- Un journal d'apprentissage hebdomadaire
|
|
|
|
## Acces aux supports formation
|
|
|
|
Les supports officiels sont dans le space **CFA** (lecture seule pour toi).
|
|
|
|
Bonne formation !
|
|
|
|
— L'equipe Acadenice
|
|
"""
|
|
|
|
|
|
def slugify(s: str) -> str:
|
|
"""Docmost exige slug = lettres + chiffres uniquement."""
|
|
import re
|
|
return re.sub(r"[^a-z0-9]", "", s.lower())
|
|
|
|
|
|
def main() -> int:
|
|
parser = argparse.ArgumentParser(description="Cree un space etudiant Docmost")
|
|
parser.add_argument("--nom", required=True)
|
|
parser.add_argument("--prenom", required=True)
|
|
parser.add_argument("--email", required=True, help="Email de l'etudiant (doit exister comme user Docmost)")
|
|
parser.add_argument("--no-template", action="store_true", help="Ne pas creer la page Bienvenue")
|
|
args = parser.parse_args()
|
|
|
|
base_url = os.environ.get("DOCMOST_URL", "http://localhost:3000")
|
|
admin_email = os.environ.get("DOCMOST_ADMIN_EMAIL")
|
|
admin_password = os.environ.get("DOCMOST_ADMIN_PASSWORD")
|
|
if not admin_email or not admin_password:
|
|
print("ERROR: set DOCMOST_ADMIN_EMAIL and DOCMOST_ADMIN_PASSWORD", file=sys.stderr)
|
|
return 1
|
|
|
|
creator = DocmostSpaceCreator(base_url)
|
|
space_name = f"Etudiant - {args.prenom} {args.nom}"
|
|
slug = slugify(f"etudiant-{args.prenom}-{args.nom}")
|
|
|
|
try:
|
|
print(f"\n[1/4] Login admin")
|
|
creator.login(admin_email, admin_password)
|
|
|
|
print(f"\n[2/4] Check existing space '{space_name}'")
|
|
existing = next((s for s in creator.list_spaces() if s.get("slug") == slug or s.get("name") == space_name), None)
|
|
if existing:
|
|
sid = existing["id"]
|
|
print(f" [space] Reuse id={sid}")
|
|
else:
|
|
sid = creator.create_space(
|
|
name=space_name,
|
|
description=f"Space personnel de {args.prenom} {args.nom}. Libre usage.",
|
|
slug=slug,
|
|
visibility="private",
|
|
)
|
|
print(f" [space] Created id={sid}")
|
|
|
|
print(f"\n[3/4] Add etudiant {args.email} as writer")
|
|
creator.add_member_by_email(sid, args.email, role="writer")
|
|
|
|
if not args.no_template:
|
|
print(f"\n[4/4] Welcome page template")
|
|
try:
|
|
creator.create_page(sid, "Bienvenue", welcome_template(args.prenom, args.nom))
|
|
print(f" [page] Created Bienvenue")
|
|
except requests.HTTPError as e:
|
|
print(f" [page] Skip (peut-etre deja existante) : {e}")
|
|
|
|
print(f"\n=== Space etudiant OK ===")
|
|
print(f" Name : {space_name}")
|
|
print(f" Slug : {slug}")
|
|
print(f" URL : {base_url} (login {args.email})")
|
|
except requests.HTTPError as e:
|
|
print(f"\nHTTP error: {e}", file=sys.stderr)
|
|
return 2
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|