"""Docker-Space entry point: pull the dataset, build the app, run uvicorn. Secrets/config (Space Secrets / env): STUDYHUB_DATASET private dataset (index + originals + app/ + uploads/) HF_TOKEN WRITE-scoped token (pulls the dataset AND persists users/history) GROQ_API_KEY (+ CEREBRAS_API_KEY) generation APP_USERS seed admin(s): "owner:admin:salt$hash" STUDYHUB_INVITE_CODE the class code classmates use to register STUDYHUB_EMBEDDER bge-m3 STUDYHUB_SESSION_SECRET random string for signing session cookies """ import os from pathlib import Path def _maybe_pull_dataset() -> None: repo = os.getenv("STUDYHUB_DATASET") if not repo: return from huggingface_hub import snapshot_download from ..accounts.uploads import merge_uploads_into_materials local = snapshot_download(repo_id=repo, repo_type="dataset", token=os.getenv("HF_TOKEN"), local_dir=os.getenv("STUDYHUB_DATA_DIR", "data"), allow_patterns=["index/**", "originals/**", "uploads/**"]) base = Path(local) merge_uploads_into_materials(base) # so /api/download can serve uploaded files too os.environ.setdefault("STUDYHUB_INDEX_DIR", str(base / "index")) os.environ.setdefault("STUDYHUB_MATERIALS_DIR", str(base / "originals")) def build_app(): _maybe_pull_dataset() from studyhub.accounts.decks import DeckStore from studyhub.accounts.history import HistoryStore from studyhub.accounts.invites import InviteStore from studyhub.accounts.notes import NotesStore from studyhub.accounts.store import HfDatasetStore, LocalStore from studyhub.accounts.users import UserStore from studyhub.app.auth import parse_users from studyhub.app.config import load_settings from studyhub.app.factory import build_service from studyhub.web.api import create_app settings = load_settings() service = build_service(settings) repo, token = os.getenv("STUDYHUB_DATASET"), os.getenv("HF_TOKEN") store = HfDatasetStore(repo, token) if repo else LocalStore(os.getenv("STUDYHUB_APPDATA", "appdata")) seed = {u: {"role": usr.role, "salt": usr.salt_hex, "hash": usr.hash_hex} for u, usr in parse_users(settings.app_users_raw).items()} users = UserStore(store, seed_admins=seed, invite_code=os.getenv("STUDYHUB_INVITE_CODE"), invites=InviteStore(store)) history = HistoryStore(store) from studyhub.web.ingest_worker import IngestWorker worker = IngestWorker(service, index_dir=os.environ.get("STUDYHUB_INDEX_DIR", "index"), materials_dir=os.environ.get("STUDYHUB_MATERIALS_DIR", "originals"), dataset_repo=repo or "", hf_token=token or "") worker.start() # Any upload that arrived while the Space was down gets ingested on boot. worker.boot_reconcile(store.list_files("uploads/")) secret = os.getenv("STUDYHUB_SESSION_SECRET") or token or "dev-secret-change-me" static = str(Path(__file__).parent / "static") return create_app(service, users, history, session_secret=secret, static_dir=static, dataset_name=repo or "", decks=DeckStore(store), notes=NotesStore(store), ingest=worker) def main() -> None: import uvicorn uvicorn.run(build_app(), host="0.0.0.0", port=int(os.getenv("PORT", "7860"))) if __name__ == "__main__": main()