diff --git a/adiuvAI b/adiuvAI index 27bc9d9..5d112c8 160000 --- a/adiuvAI +++ b/adiuvAI @@ -1 +1 @@ -Subproject commit 27bc9d90afd349c73a69c70feb590e8d961935af +Subproject commit 5d112c8dfdde46f2c9241ca57e2de1b5bac0126b diff --git a/api b/api index ce139bb..c510cba 160000 --- a/api +++ b/api @@ -1 +1 @@ -Subproject commit ce139bbac317fe04a98ff54f28da91a46534ee91 +Subproject commit c510cbaae5a421ea91f53e92d69c3a1144722455 diff --git a/docs/plan-google-auth.md b/docs/plan-google-auth.md index a8c48c5..943499d 100644 --- a/docs/plan-google-auth.md +++ b/docs/plan-google-auth.md @@ -91,7 +91,7 @@ il backup encryption locale (`_cachedPassword`) dalla password utente. ## Step 3: Electron — Deep Link + Auth Manager -**Status:** [ ] Da fare +**Status:** [x] Completato **Cosa fare:** - Registrare protocollo custom `adiuvai://` in `adiuvAI/forge.config.ts` (packagerConfig.protocols) @@ -113,13 +113,19 @@ il backup encryption locale (`_cachedPassword`) dalla password utente. - `adiuvAI/src/main/router/index.ts` **Lessons learned:** -- _(da compilare a step completato)_ +- **`adiuvai://` non è accettato da Google Console come redirect URI**: Google accetta solo `http://localhost` e `https://`. Soluzione: il backend espone `GET /auth/oauth/{provider}/web-callback` che riceve il redirect da Google e rimanda subito a `adiuvai://`. Il redirect_uri registrato su Google punta al backend, non direttamente all'Electron app. +- **`OAUTH_REDIRECT_URI` punta al backend, non al dominio website**: `adiuvai.com` è il sito statico. L'API starà su `api.adiuvai.com` (o simile). Il default in `settings.py` è `http://localhost:8000/api/v1/auth/oauth/google/web-callback` per sviluppo locale — sovrascrivere con la var d'ambiente in prod. +- **`app.requestSingleInstanceLock()` è necessario per `second-instance`**: senza il lock, l'evento non viene mai emesso su Windows/Linux. Se `requestSingleInstanceLock()` restituisce `false`, bisogna uscire subito (`app.quit()`). +- **`process.defaultApp` in dev**: in dev mode, Electron è lanciato come `electron .` — il nome del processo non corrisponde all'app. Occorre passare `[path.resolve(process.argv[1])]` come terzo argomento a `setAsDefaultProtocolClient` per includere lo script nella registrazione OS del protocollo. +- **`post()` in auth-manager serializza snake_case**: il backend si aspetta `{ code, state }` in snake_case. La conversione avviene automaticamente via `toSnakeCase()` dentro `post()`, quindi la chiamata da `handleOAuthCallback` è safe. +- **`loginWithOAuth` usa `fetch()` diretto** (non `this.get()`): la route authorize è pubblica (no JWT richiesto), ma `get()` lancia se non autenticati. Usare `fetch()` diretto evita la dipendenza dalla sessione attiva. +- **`avatarUrl` in `UserProfileSchema`**: il backend restituisce `avatar_url` (snake_case) che viene camelCased in `toCamelCase()` prima del parse Zod. Il campo deve stare nello schema Zod come `avatarUrl` (camelCase). --- ## Step 4: Electron — UI Login + Avatar -**Status:** [ ] Da fare +**Status:** [x] Completato **Cosa fare:** - In `adiuvAI/src/renderer/components/auth/LoginForm.tsx`: @@ -139,7 +145,12 @@ il backup encryption locale (`_cachedPassword`) dalla password utente. - Componenti profilo utente (da identificare) **Lessons learned:** -- _(da compilare a step completato)_ +- **`oauthMutation.isPending` dura fino a 5 min**: la mutation rimane in `isPending` mentre Electron attende il deep-link. Il bottone mostra "Waiting for browser…" e tutti gli input vengono disabilitati tramite `isBusy = loginMutation.isPending || oauthMutation.isPending` per evitare azioni concorrenti. +- **Google icon inline SVG**: il progetto usa solo `lucide-react` per le icone. Il logo Google 'G' è stato inserito come SVG inline direttamente nel componente — non introdurre librerie di icone esterne. +- **`profile.avatarUrl` è snake_case sul backend ma camelCase dopo `toCamelCase()`**: il campo sul tipo `UserProfile` (Electron) si chiama `avatarUrl`. La proprietà è `nullable().optional()` nello schema Zod — verificare sempre il null prima di passarla a `AvatarImage`. +- **`AvatarImage` come fallback graceful**: Radix UI `AvatarImage` mostra `AvatarFallback` automaticamente se l'immagine non carica (CORS, URL scaduto, etc). Non serve gestire l'errore manualmente. +- **`AccountSection` usa IIFE per evitare variabili di blocco**: il profilo era condizionale — l'IIFE `(() => { ... })()` dentro JSX permette di dichiarare variabili locali (`displayName`, `initials`) senza inquinare il componente con useState o helper esterni. +- **`AppSidebarProps` type non usa `UserProfile` importato**: il tipo è definito inline per evitare dipendenze circolari tra renderer e shared. Se `UserProfile` dovesse cambiare, va aggiornato anche il tipo inline. ---