towards multi-user

This commit is contained in:
Davide Scaini
2026-04-08 19:37:10 +02:00
parent 36a91362d9
commit f76cc0ce7e
18 changed files with 1248 additions and 30 deletions
+80 -10
View File
@@ -70,20 +70,81 @@ def _ensure_npm(site: Path) -> None:
subprocess.run(["npm", "install"], cwd=site, check=True)
def _merge_edits(data: Path) -> None:
"""Run the sidecar merge step, producing data/_merged/."""
def _is_multiuser(data: Path) -> bool:
return (data / "instance.db").exists()
def _user_dirs(data: Path) -> list[Path]:
"""Return all per-user subdirectories (contain an activities/ dir)."""
return sorted(
p for p in data.iterdir()
if p.is_dir() and (p / "activities").exists()
)
def _merge_edits(data: Path, handle: str | None = None) -> None:
"""Run the sidecar merge step for one user or all users."""
from bincio.render.merge import merge_all
n = merge_all(data)
if n:
console.print(f"Merged [cyan]{n}[/cyan] sidecar edit(s) into _merged/")
if _is_multiuser(data):
targets = [data / handle] if handle else _user_dirs(data)
total = 0
for user_dir in targets:
n = merge_all(user_dir)
total += n
console.print(f" [cyan]{user_dir.name}[/cyan]: {n} sidecar(s) merged")
if not total:
console.print("No sidecars found — _merged/ dirs mirror extracted data.")
else:
console.print("No sidecars found — _merged/ mirrors extracted data.")
n = merge_all(data)
if n:
console.print(f"Merged [cyan]{n}[/cyan] sidecar edit(s) into _merged/")
else:
console.print("No sidecars found — _merged/ mirrors extracted data.")
def _write_root_manifest(data: Path) -> None:
"""Rewrite the root index.json shard manifest from current user dirs."""
import json
from datetime import datetime, timezone
users = _user_dirs(data)
# Read existing manifest to preserve instance metadata
root = data / "index.json"
existing: dict = {}
if root.exists():
try:
existing = json.loads(root.read_text())
except Exception:
pass
manifest = {
"bas_version": "1.0",
"instance": existing.get("instance", {"name": "BincioActivity", "private": True}),
"generated_at": datetime.now(timezone.utc).isoformat(),
"shards": [
{
"handle": u.name,
"url": f"{u.name}/_merged/index.json"
if (u / "_merged" / "index.json").exists()
else f"{u.name}/index.json",
}
for u in users
],
"activities": [],
}
root.write_text(json.dumps(manifest, indent=2))
console.print(f"Root manifest updated: [cyan]{len(users)}[/cyan] user shard(s)")
def _link_data(site: Path, data: Path) -> None:
"""Symlink site/public/data → data/_merged/ (the post-merge output)."""
merged = data / "_merged"
target = merged if merged.exists() else data
"""Symlink site/public/data → data (multi-user) or data/_merged/ (single-user)."""
if _is_multiuser(data):
# Multi-user: link to data root directly (each user has their own _merged/)
target = data
else:
merged = data / "_merged"
target = merged if merged.exists() else data
public_data = site / "public" / "data"
public_data.parent.mkdir(parents=True, exist_ok=True)
if public_data.is_symlink():
@@ -113,6 +174,8 @@ def _link_data(site: Path, data: Path) -> None:
help="Start dev server with hot reload instead of building.")
@click.option("--deploy", default=None, metavar="TARGET",
help="Deploy after build. Currently supports: github.")
@click.option("--handle", default=None,
help="(Multi-user) Incrementally re-merge one user's shard only.")
def render(
config_path: Optional[str],
data_dir: Optional[str],
@@ -120,6 +183,7 @@ def render(
out_dir: Optional[str],
serve: bool,
deploy: Optional[str],
handle: Optional[str],
) -> None:
"""Build (or serve) the BincioActivity static site from a BAS data store."""
@@ -129,8 +193,14 @@ def render(
console.print(f"Site: [cyan]{site}[/cyan]")
console.print(f"Data: [cyan]{data}[/cyan]")
multiuser = _is_multiuser(data)
if multiuser:
console.print("[cyan]Multi-user mode[/cyan]")
_ensure_npm(site)
_merge_edits(data)
_merge_edits(data, handle=handle)
if multiuser:
_write_root_manifest(data)
_link_data(site, data)
env = {**os.environ, "BINCIO_DATA_DIR": str(data)}