"""FlowDeck — Dashboard : pages_api. Découpe A28 de l'ancien app/routers/dashboard.py (2 735 lignes, 63 routes) — un module par concern, contrat inchangé. """ from __future__ import annotations import logging from fastapi import APIRouter, Body, Request from fastapi.responses import JSONResponse from app.auth.session import SessionManager from app.db import get_conn logger = logging.getLogger(__name__) router = APIRouter(tags=["dashboard"]) # ═══════════ Library page actions API ═══════════ @router.get("/api/pages/{page_id:int}/content") def api_page_content(page_id: int): """Get page content for side peek preview.""" with get_conn() as conn: row = conn.execute( "SELECT title, content, content_format FROM pages WHERE id=? AND deleted_at IS NULL", (page_id,), ).fetchone() if not row: return JSONResponse({"error": "Not found"}, status_code=404) # v6.5.0: resolve synced blocks server-side (fresh content on read). from app.services.synced_blocks import resolve_content_json return { "title": row["title"], "content": resolve_content_json(row["content"], row["content_format"]), "format": row["content_format"] or "blocks", } @router.put("/api/pages/{page_id:int}/rename") def api_rename_page(page_id: int, request: Request, body: dict = Body(default={})): """Inline rename a page title.""" title = (body.get("title") or "").strip() if not title: return JSONResponse({"error": "Title required"}, status_code=400) with get_conn() as conn: conn.execute( "UPDATE pages SET title=?, updated_at=CURRENT_TIMESTAMP WHERE id=? AND deleted_at IS NULL", (title, page_id), ) # v6.5.0: renaming a database row's content page updates the row. from app.services.row_pages import sync_page_title_to_row sync_page_title_to_row(conn, page_id) conn.commit() return {"status": "ok", "title": title} @router.post("/api/pages/{page_id:int}/trash") def api_trash_page(page_id: int): """Soft-delete a page (move to trash).""" with get_conn() as conn: conn.execute( "UPDATE pages SET parent_section='Trash', deleted_at=CURRENT_TIMESTAMP WHERE id=?", (page_id,), ) conn.commit() return {"status": "ok"} @router.post("/api/pages/{page_id:int}/convert-to-database") def api_convert_to_database(page_id: int, request: Request, body: dict = Body(default={})): """Convert a page into a full-page database (Notion-style). Creates a collection linked to this page, adds the default 'Name' property, and sets the page's content_format to 'collection'. """ import json as _json db_name = (body.get("name") or "").strip() with get_conn() as conn: page = conn.execute( "SELECT id, title, workspace_id FROM pages WHERE id=? AND deleted_at IS NULL", (page_id,), ).fetchone() if not page: return JSONResponse({"error": "Page not found"}, status_code=404) if not db_name: db_name = page["title"] or "New Database" # Create the collection cur = conn.execute( """INSERT INTO collections (name, description, icon, schema_json, is_inline, parent_page_id, workspace_id) VALUES (?, '', '📋', '[]', 0, ?, ?)""", (db_name, page_id, page["workspace_id"]), ) collection_id = cur.lastrowid # Create default "Name" property (text, position 0) conn.execute( """INSERT INTO collection_properties (collection_id, name, prop_type, position, required, visible_in_views) VALUES (?, 'Name', 'title', 0, 1, 1)""", (collection_id,), ) # Create default "Table" view conn.execute( """INSERT INTO collection_views (collection_id, name, view_type, config_json, position) VALUES (?, 'Table', 'table', ?, 0)""", (collection_id, _json.dumps({"visible_properties": ["Name"]})), ) # Update the page to be a database page conn.execute( "UPDATE pages SET content_format='collection', collection_id=?, updated_at=CURRENT_TIMESTAMP WHERE id=?", (collection_id, page_id), ) conn.commit() return { "status": "converted", "collection_id": collection_id, "name": db_name, "view_url": f"/pages/{page_id}", } @router.get("/api/collections/{collection_id:int}/table-data") def api_collection_table_data(collection_id: int): """Get collection properties + pages for rendering the table view.""" with get_conn() as conn: coll = conn.execute( "SELECT * FROM collections WHERE id=?", (collection_id,) ).fetchone() if not coll: return JSONResponse({"error": "Collection not found"}, status_code=404) properties = [ dict(r) for r in conn.execute( "SELECT * FROM collection_properties WHERE collection_id=? ORDER BY position", (collection_id,), ).fetchall() ] pages = [ dict(r) for r in conn.execute( "SELECT * FROM collection_pages WHERE collection_id=? ORDER BY position", (collection_id,), ).fetchall() ] views = [ dict(r) for r in conn.execute( "SELECT * FROM collection_views WHERE collection_id=? ORDER BY position", (collection_id,), ).fetchall() ] return { "collection": dict(coll), "properties": properties, "pages": pages, "views": views, } @router.post("/api/collections/{collection_id:int}/pages") def api_create_collection_page(collection_id: int, request: Request, body: dict = Body(default={})): """Create a new page (row) in a collection.""" import json as _json title = body.get("title", "New page").strip() or "New page" icon = body.get("icon", "file") with get_conn() as conn: coll = conn.execute( "SELECT id FROM collections WHERE id=?", (collection_id,) ).fetchone() if not coll: return JSONResponse({"error": "Collection not found"}, status_code=404) # Get next position max_pos = conn.execute( "SELECT COALESCE(MAX(position), -1) + 1 FROM collection_pages WHERE collection_id=?", (collection_id,), ).fetchone()[0] # Load default property values from collection properties props = [ dict(r) for r in conn.execute( "SELECT id, name, prop_type FROM collection_properties WHERE collection_id=? ORDER BY position", (collection_id,), ).fetchall() ] default_values = {} for p in props: if p["prop_type"] == "title": default_values[str(p["id"])] = title # Caller-provided values (e.g. board "add card in column X") win. incoming = body.get("properties") or {} if isinstance(incoming, dict): default_values.update(incoming) from app.services.property_types import apply_auto_properties user = SessionManager.decode_session(request.cookies.get("flowdeck_session", "")) or {"login": "admin", "id": 1} apply_auto_properties(props, default_values, user, is_create=True) cur = conn.execute( """INSERT INTO collection_pages (collection_id, title, icon, cover_url, position, property_values_json) VALUES (?, ?, ?, ?, ?, ?)""", (collection_id, title, icon, body.get("cover_url", ""), max_pos, _json.dumps(default_values)), ) page_id = cur.lastrowid conn.commit() return { "id": page_id, "title": title, "icon": icon, "position": max_pos, "property_values_json": default_values, "status": "created", }