chore: initial project setup with documentation and design assets
Add project foundation: CLAUDE.md, requirements tracking system, technical architecture docs, Firestore setup guide, device testing guide, and Stitch design mockups for Precision Vitality app. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
349
docs/FIRESTORE_SETUP.md
Normal file
349
docs/FIRESTORE_SETUP.md
Normal file
@@ -0,0 +1,349 @@
|
||||
# Guida Configurazione Firebase & Firestore
|
||||
|
||||
> Guida passo-passo per configurare Firebase, Firestore e Cloud Functions per CalorieTracker.
|
||||
> Ultimo aggiornamento: 16/04/2026
|
||||
|
||||
---
|
||||
|
||||
## 1. Creazione Progetto Firebase
|
||||
|
||||
### Passo 1 — Console Firebase
|
||||
1. Andare su [console.firebase.google.com](https://console.firebase.google.com)
|
||||
2. Cliccare "Aggiungi progetto"
|
||||
3. Nome progetto: `calorie-tracker` (o simile disponibile)
|
||||
4. **Disabilitare** Google Analytics (non necessario per ora)
|
||||
5. Cliccare "Crea progetto"
|
||||
|
||||
### Passo 2 — Aggiungere App Web
|
||||
1. Nella dashboard progetto, cliccare l'icona **Web** (`</>`)
|
||||
2. Nome app: `CalorieTracker Web`
|
||||
3. **Non** abilitare Firebase Hosting (non necessario)
|
||||
4. Copiare la configurazione Firebase:
|
||||
```javascript
|
||||
const firebaseConfig = {
|
||||
apiKey: "AIza...",
|
||||
authDomain: "calorie-tracker-XXXXX.firebaseapp.com",
|
||||
projectId: "calorie-tracker-XXXXX",
|
||||
storageBucket: "calorie-tracker-XXXXX.appspot.com",
|
||||
messagingSenderId: "123456789",
|
||||
appId: "1:123456789:web:abcdef"
|
||||
};
|
||||
```
|
||||
5. Salvare questi valori in `environment.ts` e `environment.prod.ts`
|
||||
|
||||
---
|
||||
|
||||
## 2. Abilitare Firestore
|
||||
|
||||
### Passo 1 — Creare Database
|
||||
1. Nel menu laterale Firebase: **Build → Firestore Database**
|
||||
2. Cliccare "Crea database"
|
||||
3. Selezionare località: **europe-west1 (Belgio)** — più vicino all'Italia
|
||||
4. Selezionare "Inizia in modalità produzione"
|
||||
5. Cliccare "Abilita"
|
||||
|
||||
### Passo 2 — Regole di Sicurezza
|
||||
Nella tab **Regole** di Firestore, incollare:
|
||||
|
||||
```
|
||||
rules_version = '2';
|
||||
|
||||
service cloud.firestore {
|
||||
match /databases/{database}/documents {
|
||||
|
||||
// Nessun accesso di default
|
||||
match /{document=**} {
|
||||
allow read, write: if false;
|
||||
}
|
||||
|
||||
// Accesso pasti: solo utente proprietario
|
||||
match /users/{userId}/meals/{mealId} {
|
||||
allow read, write: if request.auth != null
|
||||
&& request.auth.uid == userId;
|
||||
|
||||
// Validazione scrittura
|
||||
allow create: if request.auth != null
|
||||
&& request.auth.uid == userId
|
||||
&& request.resource.data.calories is number
|
||||
&& request.resource.data.calories > 0
|
||||
&& request.resource.data.calories <= 9999
|
||||
&& request.resource.data.inputType in ['manual', 'llm']
|
||||
&& request.resource.data.timestamp is timestamp
|
||||
&& request.resource.data.createdAt is timestamp;
|
||||
|
||||
allow update: if request.auth != null
|
||||
&& request.auth.uid == userId;
|
||||
|
||||
allow delete: if request.auth != null
|
||||
&& request.auth.uid == userId;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Passo 3 — Indici
|
||||
Nella tab **Indici** di Firestore, creare indice composto:
|
||||
|
||||
| Collection | Campi | Query Scope |
|
||||
|------------|-------|-------------|
|
||||
| `users/{userId}/meals` | `timestamp` DESC | Collection |
|
||||
|
||||
> **Nota**: Firestore creerà automaticamente gli indici necessari quando l'app esegue le prime query. In alternativa, il file `firestore.indexes.json` nel progetto definisce gli indici programmaticamente.
|
||||
|
||||
---
|
||||
|
||||
## 3. Abilitare Authentication
|
||||
|
||||
### Passo 1 — Anonymous Auth
|
||||
1. Nel menu laterale: **Build → Authentication**
|
||||
2. Cliccare "Inizia"
|
||||
3. Tab **Metodo di accesso**
|
||||
4. Abilitare **Anonimo** → switch ON → Salva
|
||||
|
||||
### Passo 2 — (Futuro) Google Sign-In
|
||||
> Per upgrade futuro da Anonymous a Google Sign-In:
|
||||
> 1. Abilitare provider "Google" in Authentication
|
||||
> 2. Configurare OAuth consent screen in Google Cloud Console
|
||||
> 3. Implementare linking account (da anonimo a Google)
|
||||
|
||||
---
|
||||
|
||||
## 4. Configurazione App Android
|
||||
|
||||
### Passo 1 — Aggiungere App Android
|
||||
1. Dashboard Firebase → "Aggiungi app" → icona Android
|
||||
2. Package name: `com.precisionvitality.app` (deve corrispondere a `capacitor.config.ts`)
|
||||
3. App nickname: `CalorieTracker Android`
|
||||
4. SHA-1: (opzionale per ora, necessario per Google Sign-In)
|
||||
|
||||
### Passo 2 — Scaricare google-services.json
|
||||
1. Scaricare `google-services.json`
|
||||
2. Copiare in `android/app/google-services.json` (dopo `npx cap add android`)
|
||||
|
||||
---
|
||||
|
||||
## 5. Configurazione App iOS
|
||||
|
||||
### Passo 1 — Aggiungere App iOS
|
||||
1. Dashboard Firebase → "Aggiungi app" → icona iOS
|
||||
2. Bundle ID: `com.precisionvitality.app`
|
||||
3. App nickname: `CalorieTracker iOS`
|
||||
|
||||
### Passo 2 — Scaricare GoogleService-Info.plist
|
||||
1. Scaricare `GoogleService-Info.plist`
|
||||
2. Copiare in `ios/App/App/GoogleService-Info.plist` (dopo `npx cap add ios`)
|
||||
|
||||
---
|
||||
|
||||
## 6. Setup Firebase Cloud Functions (Proxy AI)
|
||||
|
||||
### Passo 1 — Upgrade a Blaze Plan (Pay-as-you-go)
|
||||
> **ATTENZIONE**: Le Cloud Functions richiedono il piano Blaze (pay-as-you-go).
|
||||
> Il piano Blaze include comunque il tier gratuito di Firestore e Auth.
|
||||
> Non ci saranno costi se il traffico resta nel free tier.
|
||||
|
||||
1. Console Firebase → icona ingranaggio → Utilizzo e fatturazione
|
||||
2. Upgrade a Blaze
|
||||
3. Impostare budget alert: $5/mese
|
||||
|
||||
### Alternativa gratuita: Se non si vuole il piano Blaze
|
||||
- Usare un backend NestJS su VPS esistente come proxy
|
||||
- Oppure usare Cloudflare Workers (free tier: 100k req/giorno)
|
||||
|
||||
### Passo 2 — Inizializzare Cloud Functions
|
||||
```bash
|
||||
# Nella root del progetto
|
||||
npm install -g firebase-tools
|
||||
firebase login
|
||||
firebase init functions
|
||||
|
||||
# Selezionare:
|
||||
# - Linguaggio: TypeScript
|
||||
# - ESLint: Sì
|
||||
# - Installare dipendenze: Sì
|
||||
```
|
||||
|
||||
### Passo 3 — Configurare Secret per API Key
|
||||
```bash
|
||||
# Salvare API key come secret (non in codice!)
|
||||
firebase functions:secrets:set ANTHROPIC_API_KEY
|
||||
# Inserire: sk-ant-...
|
||||
```
|
||||
|
||||
### Passo 4 — Implementare la Function
|
||||
File `functions/src/index.ts`:
|
||||
|
||||
```typescript
|
||||
import { onRequest } from 'firebase-functions/v2/https';
|
||||
import { defineSecret } from 'firebase-functions/params';
|
||||
import Anthropic from '@anthropic-ai/sdk';
|
||||
|
||||
const anthropicKey = defineSecret('ANTHROPIC_API_KEY');
|
||||
|
||||
export const estimateCalories = onRequest(
|
||||
{
|
||||
cors: true,
|
||||
region: 'europe-west1',
|
||||
secrets: [anthropicKey],
|
||||
timeoutSeconds: 15,
|
||||
memory: '256MiB',
|
||||
},
|
||||
async (req, res) => {
|
||||
// Verificare metodo POST
|
||||
if (req.method !== 'POST') {
|
||||
res.status(405).send('Method Not Allowed');
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificare auth token
|
||||
const authHeader = req.headers.authorization;
|
||||
if (!authHeader?.startsWith('Bearer ')) {
|
||||
res.status(401).send('Unauthorized');
|
||||
return;
|
||||
}
|
||||
|
||||
// Estrarre e validare input
|
||||
const { description } = req.body;
|
||||
if (!description || typeof description !== 'string' || description.length > 500) {
|
||||
res.status(400).send('Invalid description');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const client = new Anthropic({ apiKey: anthropicKey.value() });
|
||||
|
||||
const message = await client.messages.create({
|
||||
model: 'claude-haiku-4-5-20251001',
|
||||
max_tokens: 256,
|
||||
system: `Sei un nutrizionista. Stima le calorie del pasto descritto.
|
||||
Rispondi SOLO con JSON: {"calories": number, "confidence": "high"|"medium"|"low", "explanation": "string"}`,
|
||||
messages: [{ role: 'user', content: description }],
|
||||
});
|
||||
|
||||
// Parsare risposta
|
||||
const text = message.content[0].type === 'text' ? message.content[0].text : '';
|
||||
const jsonStr = text.replace(/```json?\n?/g, '').replace(/```/g, '').trim();
|
||||
const result = JSON.parse(jsonStr);
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Estimation error:', error);
|
||||
res.status(500).json({ error: 'Estimation failed' });
|
||||
}
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
### Passo 5 — Deploy
|
||||
```bash
|
||||
cd functions
|
||||
npm install @anthropic-ai/sdk
|
||||
firebase deploy --only functions
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Integrazione Angular
|
||||
|
||||
### Installare dipendenze
|
||||
```bash
|
||||
npm install @angular/fire firebase
|
||||
```
|
||||
|
||||
### Configurare in app.config.ts (Angular 17+ standalone)
|
||||
```typescript
|
||||
import { provideFirebaseApp, initializeApp } from '@angular/fire/app';
|
||||
import { provideFirestore, getFirestore, enableIndexedDbPersistence } from '@angular/fire/firestore';
|
||||
import { provideAuth, getAuth } from '@angular/fire/auth';
|
||||
import { environment } from '../environments/environment';
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [
|
||||
provideFirebaseApp(() => initializeApp(environment.firebase)),
|
||||
provideFirestore(() => {
|
||||
const firestore = getFirestore();
|
||||
enableIndexedDbPersistence(firestore);
|
||||
return firestore;
|
||||
}),
|
||||
provideAuth(() => getAuth()),
|
||||
// ... altri providers
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. File di Configurazione Locale
|
||||
|
||||
### `firestore.rules` (nella root del progetto)
|
||||
Copiare le regole dal Passo 2 della Sezione 2.
|
||||
|
||||
### `firestore.indexes.json`
|
||||
```json
|
||||
{
|
||||
"indexes": [
|
||||
{
|
||||
"collectionGroup": "meals",
|
||||
"queryScope": "COLLECTION",
|
||||
"fields": [
|
||||
{ "fieldPath": "timestamp", "order": "DESCENDING" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"fieldOverrides": []
|
||||
}
|
||||
```
|
||||
|
||||
### `.firebaserc`
|
||||
```json
|
||||
{
|
||||
"projects": {
|
||||
"default": "calorie-tracker-XXXXX"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### `firebase.json`
|
||||
```json
|
||||
{
|
||||
"firestore": {
|
||||
"rules": "firestore.rules",
|
||||
"indexes": "firestore.indexes.json"
|
||||
},
|
||||
"functions": {
|
||||
"source": "functions",
|
||||
"runtime": "nodejs18"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. Limiti Free Tier Firebase (Spark / Blaze)
|
||||
|
||||
| Risorsa | Limite Gratuito | Note |
|
||||
|---------|----------------|------|
|
||||
| Firestore letture | 50.000/giorno | ~1.600 utenti × 30 letture/giorno |
|
||||
| Firestore scritture | 20.000/giorno | ~2.000 utenti × 10 pasti/giorno |
|
||||
| Firestore storage | 1 GiB | Sufficiente per ~5M documenti pasto |
|
||||
| Auth utenti | Illimitati | Anonymous + email + social |
|
||||
| Cloud Functions invocazioni | 2M/mese | ~66.000/giorno |
|
||||
| Cloud Functions compute | 400.000 GB-secondi/mese | Sufficiente per proxy AI |
|
||||
|
||||
---
|
||||
|
||||
## 10. Checklist Configurazione
|
||||
|
||||
- [ ] Progetto Firebase creato
|
||||
- [ ] Firestore abilitato (europe-west1)
|
||||
- [ ] Regole sicurezza configurate
|
||||
- [ ] Anonymous Auth abilitato
|
||||
- [ ] App Web registrata + config copiata
|
||||
- [ ] App Android registrata + google-services.json scaricato
|
||||
- [ ] (Opzionale) App iOS registrata + GoogleService-Info.plist scaricato
|
||||
- [ ] Cloud Functions inizializzate
|
||||
- [ ] ANTHROPIC_API_KEY salvata come secret
|
||||
- [ ] Cloud Function deployata
|
||||
- [ ] @angular/fire installato e configurato
|
||||
- [ ] Test lettura/scrittura funzionante
|
||||
- [ ] Offline persistence verificata
|
||||
Reference in New Issue
Block a user