feat(P2.2): thread project owner_id into compile scope materialization
So a scoped compile can resolve the project OWNER's per-user custom libraries (not the requester's) — a shared/embed/anon compile of someone else's project still finds that owner's uploaded libs. - core/hooks.py: new get_project_owner hook; materialize_library_scope gains an opaque owner_id param (no-op default unchanged). - espidf_compiler.compile/_attempt: thread owner_id to the materializer. - compile.py: resolve owner via get_project_owner(project_id), pass to compile. Additive: the OSS image (no overlay) ignores owner_id; index libs still resolve from the cache. Foundation for per-user custom-lib storage (P2.2a write side).
This commit is contained in:
parent
810d8e4b51
commit
6d5f6b01a4
|
|
@ -8,7 +8,12 @@ from typing import Any
|
|||
from fastapi import APIRouter, Depends, HTTPException, Request
|
||||
from pydantic import BaseModel
|
||||
|
||||
from app.core.hooks import get_current_user_id, get_project_libraries, record_compile
|
||||
from app.core.hooks import (
|
||||
get_current_user_id,
|
||||
get_project_libraries,
|
||||
get_project_owner,
|
||||
record_compile,
|
||||
)
|
||||
from app.services.arduino_cli import ArduinoCLIService
|
||||
from app.services.espidf_compiler import espidf_compiler
|
||||
|
||||
|
|
@ -229,12 +234,17 @@ async def _run_compile(
|
|||
project_libs = await get_project_libraries(request.project_id)
|
||||
if project_libs:
|
||||
allowed_libraries = set(project_libs)
|
||||
# P2.2 — the OWNER of the project (not the requester) owns any per-user
|
||||
# custom libraries the manifest references, so resolve the owner so the
|
||||
# scope materializer can find them. None for unsaved/anon compiles.
|
||||
owner_id = await get_project_owner(request.project_id)
|
||||
result = await espidf_compiler.compile(
|
||||
files, request.board_fqbn,
|
||||
progress_callback=progress_callback,
|
||||
board_options=request.board_options,
|
||||
spiffs_files=spiffs_dicts,
|
||||
allowed_libraries=allowed_libraries,
|
||||
owner_id=owner_id,
|
||||
)
|
||||
return CompileResponse(
|
||||
success=result["success"],
|
||||
|
|
|
|||
|
|
@ -145,7 +145,11 @@ async def get_project_libraries(project_id: Optional[str]) -> Optional[list[str]
|
|||
# empty -> the compiler uses its single default libraries dir (OSS self-host
|
||||
# parity / scan-all). SYNC — pure filesystem (symlink creation).
|
||||
|
||||
MaterializeLibraryScopeHook = Callable[[set], Optional[tuple]]
|
||||
# `owner_id` is the project OWNER's id (NOT the requester's): a shared / embed /
|
||||
# anonymous compile of someone else's project must resolve THAT user's custom
|
||||
# (per-user-store) libraries. The overlay treats it as an opaque key; the OSS
|
||||
# compiler only threads it through. None for unsaved/anon-no-project compiles.
|
||||
MaterializeLibraryScopeHook = Callable[[set, Optional[str]], Optional[tuple]]
|
||||
|
||||
_materialize_library_scope_hook: Optional[MaterializeLibraryScopeHook] = None
|
||||
|
||||
|
|
@ -156,19 +160,48 @@ def register_materialize_library_scope(hook: MaterializeLibraryScopeHook) -> Non
|
|||
_materialize_library_scope_hook = hook
|
||||
|
||||
|
||||
def materialize_library_scope(allowed_libraries: Optional[set]) -> Optional[tuple]:
|
||||
def materialize_library_scope(
|
||||
allowed_libraries: Optional[set], owner_id: Optional[str] = None
|
||||
) -> Optional[tuple]:
|
||||
"""Return (libraries_dir, content_token) for the manifest, or None to use the
|
||||
compiler's default single libraries dir. Never raises (a failing materializer
|
||||
degrades to the default dir)."""
|
||||
if _materialize_library_scope_hook is None or not allowed_libraries:
|
||||
return None
|
||||
try:
|
||||
return _materialize_library_scope_hook(allowed_libraries)
|
||||
return _materialize_library_scope_hook(allowed_libraries, owner_id)
|
||||
except Exception:
|
||||
logger.exception("materialize_library_scope hook failed (using default libraries dir)")
|
||||
return None
|
||||
|
||||
|
||||
# ── get_project_owner ─────────────────────────────────────────────────────────
|
||||
# Resolve a project's OWNER user-id from its id (the value stored as user_id on
|
||||
# the project record). Used so a compile resolves the OWNER's per-user custom
|
||||
# libraries, not the requester's. Returns None for an unknown project or when no
|
||||
# overlay is loaded.
|
||||
|
||||
GetProjectOwnerHook = Callable[[str], Awaitable[Optional[str]]]
|
||||
|
||||
_get_project_owner_hook: Optional[GetProjectOwnerHook] = None
|
||||
|
||||
|
||||
def register_get_project_owner(hook: GetProjectOwnerHook) -> None:
|
||||
"""Install the project-owner resolver. Called by overlays in register_pro."""
|
||||
global _get_project_owner_hook
|
||||
_get_project_owner_hook = hook
|
||||
|
||||
|
||||
async def get_project_owner(project_id: Optional[str]) -> Optional[str]:
|
||||
if _get_project_owner_hook is None or not project_id:
|
||||
return None
|
||||
try:
|
||||
return await _get_project_owner_hook(project_id)
|
||||
except Exception:
|
||||
logger.exception("get_project_owner hook failed (treating as no owner)")
|
||||
return None
|
||||
|
||||
|
||||
# ── lifespan startup ──────────────────────────────────────────────────────────
|
||||
# Overlays that need to run async setup during FastAPI lifespan (DB init,
|
||||
# table creation, legacy column migrations, etc.) register a coroutine here.
|
||||
|
|
|
|||
|
|
@ -1718,6 +1718,7 @@ class ESPIDFCompiler:
|
|||
board_options: dict | None = None,
|
||||
spiffs_files: list[dict] | None = None,
|
||||
allowed_libraries: set[str] | None = None,
|
||||
owner_id: str | None = None,
|
||||
) -> dict:
|
||||
"""
|
||||
Compile Arduino sketch using ESP-IDF.
|
||||
|
|
@ -1804,7 +1805,7 @@ class ESPIDFCompiler:
|
|||
# cache update) gets its own clean build dir — and the throwaway
|
||||
# scope dir is removed after the attempt (its files were already
|
||||
# copied into the build's user_libs_all by _compile_in_dir).
|
||||
scope = materialize_library_scope(allowed)
|
||||
scope = materialize_library_scope(allowed, owner_id)
|
||||
scope_dir = scope[0] if scope else None
|
||||
scope_token = scope[1] if scope else ''
|
||||
# Fold the effective library set + resolved content into the build-dir
|
||||
|
|
|
|||
Loading…
Reference in New Issue