Commit Graph

5 Commits

Author SHA1 Message Date
David Montero 810d8e4b51 feat(espidf): per-compile library-scope materialization hook (P2.1e read repoint)
A scoped ESP32 compile can now resolve libraries from a per-compile directory
provided by an overlay (the manifest's libs symlinked from a content-addressed
cache, with a legacy-dir fallback) instead of the single shared global volume.

- core/hooks.py: register_materialize_library_scope / materialize_library_scope
  (no-op default -> None, so the OSS image keeps its single scan-all dir).
- espidf_compiler: _attempt(allowed) calls the hook, folds the returned content
  token into the build-variant eff_hash (a content change gets a clean build
  dir), passes libraries_dir to _compile_in_dir (arduino_libs = libraries_dir or
  _find_arduino_libraries_dir()), and removes the throwaway dir after. The
  graceful scan-all fallback (allowed=None) keeps using the default dir, so the
  worst case of any materializer failure is fall-back-to-legacy (no break).
2026-06-07 16:37:37 +02:00
David Montero b7954fa8c5 feat(esp32): scope ESP-IDF resolution to the project's SAVED library manifest
Adds the get_project_libraries hook: the compile route reads a saved project's
declared library manifest (by project_id) and uses it as the ESP-IDF resolution
scope, preferring it over the client-sent manifest. So a saved project always
compiles against only its own declared libraries — never another user's, or
another project's, stray install in the shared dir — authoritatively from the
server, independent of frontend wiring. Client-sent manifest still used for
unsaved examples; None/empty → legacy scan-all. Overlay fills the hook in
register_pro; OSS default is no-op (None).
2026-06-07 02:01:08 +02:00
David Montero 594291c830 feat(hooks): iot_gateway_gate extension point + content-negotiated 402
Adds a generic gating hook so a private overlay can restrict the IoT
gateway proxy to paid plans without the OSS image carrying any plan
logic.  register_iot_gateway_gate() installs an async callback that
returns None to allow or a detail dict to block; the OSS default (no
overlay) allows everyone, and a failing gate fails OPEN so the gateway
can never be taken down by a buggy overlay.

gateway_proxy() calls the gate first.  When blocked it content-
negotiates the 402: browsers (Accept: text/html — the frontend opens
the gateway via window.open) get a small styled upgrade page with a
link to /pricing; programmatic fetch/XHR callers get the JSON detail.

No behaviour change for the open-source image — the gate is a no-op
there.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-29 19:44:30 +02:00
davidmonterocrespo24 1fb7518226 fix(hooks): annotate get_current_user_id(request: Request)
Without the type annotation, FastAPI treats `request` as a Query
parameter and bubbles it up to every endpoint that uses
`Depends(get_current_user_id)`. Result: POST /api/compile/start
and POST /api/compile/ both returned 422
{"loc":["query","request"],"msg":"Field required"} on every call
the frontend made — compile was fully broken in production.

The frontend then caught the 422 axios error and surfaced
response.data as a CompileResult, which had no success/stdout/
stderr/error fields, so the editor's CompilationConsole rendered
only the fallback "✕ Compilation failed" line with no detail.

Annotating `request: Request` is the standard FastAPI pattern;
the framework injects the raw HTTPRequest and no longer treats
it as a query parameter.
2026-05-14 20:44:57 +02:00
David Montero Crespo 12b6e94e4d refactor(oss-split): introduce extension hooks for auth, DB, metrics, auto-save
First phase of the OSS / pro split. Goal: open the seams so the auth/DB/admin
stack can move into the private overlay (Phase 2-3) without the routes that
stay in OSS (compile, libraries, simulation, iot_gateway) having to know.

Backend
-------
* New app/core/hooks.py — registry for record_compile, get_current_user_id,
  and lifespan startup tasks. Each hook is a no-op by default; overlays
  call register_* in register_pro(app) to plug in a real implementation.
* compile.py now imports only from app.core.hooks. Drops the direct deps on
  app.core.dependencies, app.database.session, app.models.user, and
  app.services.metrics. Route signatures use `Depends(get_current_user_id)`
  instead of `Depends(get_current_user)`; the metric helper passes user_id
  through rather than a User instance.
* compile_chip.py drops the unused _current_user Depends entirely.
* main.py wraps the auth/DB stack import in try/except. When it succeeds
  (today's behavior on velxio.dev), an adapter bridges record_compile and
  get_current_user_id to the existing app.services.metrics + dependencies,
  and the create_all + ALTER TABLE migration block runs via a registered
  lifespan_startup hook. When it fails (the post-Phase-2 OSS image), main
  logs "running stateless" and skips registering anything — the routes
  still load and behave as no-ops for metrics + always-anonymous for auth.

Frontend
--------
* useAutoSaveProject becomes a skeleton: one useState + one useEffect that
  delegates to an installed AutoSaveImpl. installAutoSaveImpl() replaces
  the impl without changing hook count, so React's rules-of-hooks stay
  satisfied even after the impl moves out of OSS.
* New hooks/autoSaveImpl.ts holds the original logic (debouncing, dirty
  detection, owner eligibility, fetch keepalive on unload), refactored to
  emit() instead of useState. It self-registers at module load; main.tsx
  imports it for the side effect.
* AppHeader wraps the entire user-vs-login UI in a data-velxio-slot
  ="header-auth" boundary. Today the OSS UI still renders inside the slot
  — the overlay can portal-inject additional items now, and in Phase 3
  the slot becomes the sole owner of header auth UX.

Behavior is identical on velxio.dev (pro overlay imports everything
successfully, every adapter wires up). The change is purely structural:
deleting the auth/DB modules tomorrow no longer crashes OSS at import.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-14 13:24:51 -03:00