fix(docmost-seed): handle Docmost data envelope + add format field
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

Docmost API responses wrap : {data, success, status} (3 keys top-level).
- _post auto-unwrap si payload contient `data` dict|list
- create_page : ajout du field 'format' (markdown|json|html) requis
- welcome_page_content() : retourne markdown plain (Docmost convertit auto en Tiptap)

Iteration 3 BUILD validee en local :
  Workspace : Acadenice (id 019e034d-...)
  3 spaces  : CFA, Agence, Interne
  Page test : Welcome formation-hub dans CFA
  Share URL : http://localhost:3000/share/019e0352-7fc4-7639...

Endpoints reverse-engineered confirmes fonctionnels :
  /api/auth/setup, /api/auth/login, /api/workspace/info,
  /api/spaces/, /api/spaces/create, /api/pages/create, /api/shares/create
This commit is contained in:
Corentin JOGUET 2026-05-07 18:43:39 +02:00
parent 8a676d27c8
commit d5558caf9a

View file

@ -35,12 +35,19 @@ class DocmostSeed:
self.workspace_id: str | None = None self.workspace_id: str | None = None
def _post(self, path: str, body: dict[str, Any] | None = None) -> dict[str, Any]: def _post(self, path: str, body: dict[str, Any] | None = None) -> dict[str, Any]:
"""POST + auto-unwrap `data` envelope (Docmost API wrap convention)."""
url = f"{self.base_url}{path}" url = f"{self.base_url}{path}"
r = self.session.post(url, json=body or {}) r = self.session.post(url, json=body or {})
if r.status_code >= 300: if r.status_code >= 300:
print(f" [ERROR] POST {path}{r.status_code}\n body={body}\n resp={r.text[:500]}") print(f" [ERROR] POST {path}{r.status_code}\n body={body}\n resp={r.text[:500]}")
r.raise_for_status() r.raise_for_status()
return r.json() if r.text else {} if not r.text:
return {}
payload = r.json()
# Docmost wraps responses : {data: ..., success: bool, status: int}
if isinstance(payload, dict) and "data" in payload and isinstance(payload["data"], (dict, list)):
return payload["data"]
return payload
def setup_admin_and_workspace(self, name: str, email: str, password: str, workspace_name: str) -> bool: def setup_admin_and_workspace(self, name: str, email: str, password: str, workspace_name: str) -> bool:
"""POST /api/auth/setup — bootstrap workspace + admin (1ere fois seulement). """POST /api/auth/setup — bootstrap workspace + admin (1ere fois seulement).
@ -72,7 +79,12 @@ class DocmostSeed:
def list_spaces(self) -> list[dict[str, Any]]: def list_spaces(self) -> list[dict[str, Any]]:
try: try:
data = self._post("/api/spaces/", {"page": 1, "limit": 100}) data = self._post("/api/spaces/", {"page": 1, "limit": 100})
return data.get("items") or data.get("data") or [] # Apres unwrap de data, on a { items: [...], meta: {...} }
if isinstance(data, dict):
return data.get("items") or []
if isinstance(data, list):
return data
return []
except requests.HTTPError: except requests.HTTPError:
return [] return []
@ -97,8 +109,8 @@ class DocmostSeed:
print(f" [space] Created '{name}' id={sid}") print(f" [space] Created '{name}' id={sid}")
return sid return sid
def create_page(self, space_id: str, title: str, content: dict[str, Any] | None = None) -> str: def create_page(self, space_id: str, title: str, content: str | dict[str, Any] | None = None, fmt: str = "markdown") -> str:
body: dict[str, Any] = {"spaceId": space_id, "title": title} body: dict[str, Any] = {"spaceId": space_id, "title": title, "format": fmt}
if content is not None: if content is not None:
body["content"] = content body["content"] = content
result = self._post("/api/pages/create", body) result = self._post("/api/pages/create", body)
@ -118,34 +130,31 @@ class DocmostSeed:
return result return result
def welcome_page_content() -> dict[str, Any]: def welcome_page_content() -> str:
"""Tiptap doc JSON minimal pour la page Welcome.""" """Page Welcome en markdown — Docmost le converti en Tiptap auto."""
return { return """# Welcome formation-hub
"type": "doc",
"content": [ Wiki Docmost interne Acadenice voir `docs/` du repo wiki pour la conception complete.
{"type": "heading", "attrs": {"level": 1}, "content": [
{"type": "text", "text": "Welcome formation-hub"}]}, ## Stack
{"type": "paragraph", "content": [
{"type": "text", "text": "Wiki Docmost interne Acadenice — voir docs/ du repo wiki pour la conception complete."}]}, - **Docmost** : ce wiki (AGPL self-host)
{"type": "heading", "attrs": {"level": 2}, "content": [ - **Baserow** : DBs structurees (MIT self-host) http://localhost:8080
{"type": "text", "text": "Stack"}]}, - **Bridge service** : Phase 2
{"type": "bulletList", "content": [
{"type": "listItem", "content": [ ## Diagrammes natifs
{"type": "paragraph", "content": [
{"type": "text", "marks": [{"type": "bold"}], "text": "Docmost"}, Ajouter via le slash menu :
{"type": "text", "text": " : ce wiki (AGPL self-host)"}]}]}, - `/mermaid` pour les diagrammes Mermaid
{"type": "listItem", "content": [ - `/drawio` pour les diagrammes Draw.io
{"type": "paragraph", "content": [ - `/excalidraw` pour les whiteboards Excalidraw
{"type": "text", "marks": [{"type": "bold"}], "text": "Baserow"},
{"type": "text", "text": " : DBs structurees (MIT self-host)"}]}]}, ## Liens utiles
{"type": "listItem", "content": [
{"type": "paragraph", "content": [ - Repo Forgejo : https://git.acadenice.com/AcadeNice/Wiki
{"type": "text", "marks": [{"type": "bold"}], "text": "Bridge service"}, - Stack locale : Docmost (3000) + Baserow (8080)
{"type": "text", "text": " : Phase 2"}]}]}]}, - Documentation projet : `docs/00-readme.md` du repo
{"type": "paragraph", "content": [ """
{"type": "text", "text": "Note : ajouter les blocks Mermaid / Drawio / Excalidraw via le slash menu une fois la page ouverte (ils ne sont pas pre-inserees par le seed)."}]},
],
}
def main() -> int: def main() -> int: