mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:01:09 +02:00
Final cleanup of references missed in previous rename commits: - Dockerfiles: PUBLIC_MANA_CORE_AUTH_URL → PUBLIC_MANA_AUTH_URL - Go modules: github.com/manacore/* → github.com/mana/* (7 go.mod files) - launchd plists: com.manacore.* → com.mana.* (14 files renamed + content) - Image assets: *_Manacore_AI_Credits* → *_Mana_AI_Credits* (11 files) - .env.example files: ManaCore brand strings → Mana - .prettierignore: stale apps/manacore/* paths → apps/mana/* - Markdown docs (CLAUDE.md, /docs/*): mana-core-auth → mana-auth, etc. Excluded from rename: .claude/, devlog/, manascore/ (historical content), client testimonials, blueprints, npm package refs (@mana-core/*). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
300 lines
8.6 KiB
Markdown
300 lines
8.6 KiB
Markdown
# Picture App: Supabase zu Backend API Migration
|
|
|
|
## Ziel
|
|
Komplette Entfernung der direkten Supabase-Nutzung in der Mobile App. Alle Datenbankzugriffe sollen über die Backend API erfolgen (wie in der Chat App).
|
|
|
|
---
|
|
|
|
## Aktuelle Situation
|
|
|
|
### Backend (bereits implementiert)
|
|
| Endpoint | Status |
|
|
|----------|--------|
|
|
| `/api/images/*` | Vorhanden - CRUD, Archive, Batch-Operationen |
|
|
| `/api/generate/*` | Vorhanden - Bildgenerierung mit Replicate |
|
|
| `/api/tags/*` | Vorhanden - Tag-Management |
|
|
| `/api/boards/*` | Vorhanden - Board-Management |
|
|
| `/api/board-items/*` | Vorhanden - Board-Items |
|
|
| `/api/models/*` | Vorhanden - AI Modelle |
|
|
| `/api/explore/*` | Vorhanden - Öffentliche Galerie |
|
|
| `/api/upload/*` | Vorhanden - Datei-Upload |
|
|
| `/api/profiles/*` | **FEHLT** - User Profile |
|
|
| `/api/likes/*` | **FEHLT** - Image Likes |
|
|
|
|
### Mobile App (direkte Supabase-Nutzung)
|
|
Dateien die `supabase.from()` oder Supabase-Client nutzen:
|
|
|
|
1. `app/(tabs)/profile.tsx` - Profile-Daten laden/aktualisieren
|
|
2. `app/image/[id].tsx` - Einzelbild-Details
|
|
3. `hooks/useImageFetching.ts` - Bilder laden
|
|
4. `hooks/useImageLikes.ts` - Like-Funktionalität
|
|
5. `hooks/useImagePrefetch.ts` - Prefetching
|
|
6. `hooks/useExploreFetching.ts` - Explore-Daten
|
|
7. `hooks/useExplorePrefetch.ts` - Explore-Prefetching
|
|
8. `hooks/useArchiveFetching.ts` - Archiv-Daten
|
|
9. `store/tagStore.ts` - Tag-Daten
|
|
10. `store/batchStore.ts` - Batch-Generierung (nutzt Edge Functions)
|
|
11. `components/RateLimitIndicator.tsx` - Rate Limit Check
|
|
|
|
---
|
|
|
|
## Migrationsplan
|
|
|
|
### Phase 1: Backend erweitern
|
|
|
|
#### 1.1 Profile Endpoints hinzufügen
|
|
```
|
|
GET /api/profiles/me - Eigenes Profil laden
|
|
PATCH /api/profiles/me - Profil aktualisieren
|
|
GET /api/profiles/stats - User Stats (Bilder, Favoriten)
|
|
```
|
|
|
|
**Schema erweitern** (falls nicht vorhanden):
|
|
```typescript
|
|
// profiles table
|
|
{
|
|
id: uuid (PK, same as auth user id)
|
|
username: text
|
|
email: text
|
|
avatarUrl: text (optional)
|
|
createdAt: timestamp
|
|
updatedAt: timestamp
|
|
}
|
|
```
|
|
|
|
#### 1.2 Like Endpoints hinzufügen
|
|
```
|
|
POST /api/images/:id/like - Bild liken
|
|
DELETE /api/images/:id/like - Like entfernen
|
|
GET /api/images/:id/likes - Like-Status & Anzahl
|
|
```
|
|
|
|
**Schema erweitern** (falls nicht vorhanden):
|
|
```typescript
|
|
// image_likes table
|
|
{
|
|
id: uuid (PK)
|
|
imageId: uuid (FK to images)
|
|
userId: uuid
|
|
createdAt: timestamp
|
|
}
|
|
```
|
|
|
|
#### 1.3 Rate Limit Endpoint
|
|
```
|
|
GET /api/rate-limit - Aktueller Rate Limit Status
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 2: Mobile API Client erstellen
|
|
|
|
#### 2.1 Zentraler API Client
|
|
Datei: `services/api/client.ts`
|
|
|
|
```typescript
|
|
import * as SecureStore from 'expo-secure-store';
|
|
|
|
const APP_TOKEN_KEY = '@mana/app_token';
|
|
const BACKEND_URL = process.env.EXPO_PUBLIC_PICTURE_BACKEND_URL || 'http://localhost:3003';
|
|
|
|
async function getAuthToken(): Promise<string | null> {
|
|
try {
|
|
return await SecureStore.getItemAsync(APP_TOKEN_KEY);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
export async function apiRequest<T>(
|
|
endpoint: string,
|
|
options: RequestInit = {}
|
|
): Promise<{ data: T | null; error: string | null }> {
|
|
const token = await getAuthToken();
|
|
|
|
const headers: HeadersInit = {
|
|
'Content-Type': 'application/json',
|
|
...(options.headers || {}),
|
|
};
|
|
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`${BACKEND_URL}/api${endpoint}`, {
|
|
...options,
|
|
headers,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const error = await response.json().catch(() => ({ message: 'Request failed' }));
|
|
return { data: null, error: error.message || `HTTP ${response.status}` };
|
|
}
|
|
|
|
const data = await response.json();
|
|
return { data, error: null };
|
|
} catch (error) {
|
|
return { data: null, error: error instanceof Error ? error.message : 'Network error' };
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 2.2 Domain-spezifische API Module
|
|
|
|
**`services/api/profiles.ts`**
|
|
```typescript
|
|
export const profileApi = {
|
|
getMyProfile: () => apiRequest<Profile>('/profiles/me'),
|
|
updateProfile: (data: UpdateProfileDto) => apiRequest<Profile>('/profiles/me', {
|
|
method: 'PATCH',
|
|
body: JSON.stringify(data),
|
|
}),
|
|
getStats: () => apiRequest<UserStats>('/profiles/stats'),
|
|
};
|
|
```
|
|
|
|
**`services/api/images.ts`** (erweitern)
|
|
```typescript
|
|
export const imageApi = {
|
|
// Bestehende Funktionen...
|
|
getImages: (params) => apiRequest<Image[]>(`/images?${new URLSearchParams(params)}`),
|
|
getImage: (id) => apiRequest<Image>(`/images/${id}`),
|
|
likeImage: (id) => apiRequest(`/images/${id}/like`, { method: 'POST' }),
|
|
unlikeImage: (id) => apiRequest(`/images/${id}/like`, { method: 'DELETE' }),
|
|
// etc.
|
|
};
|
|
```
|
|
|
|
**`services/api/explore.ts`**
|
|
```typescript
|
|
export const exploreApi = {
|
|
getPublicImages: (params) => apiRequest<Image[]>(`/explore?${new URLSearchParams(params)}`),
|
|
search: (term, params) => apiRequest<Image[]>(`/explore/search?searchTerm=${term}&${new URLSearchParams(params)}`),
|
|
};
|
|
```
|
|
|
|
**`services/api/tags.ts`**
|
|
```typescript
|
|
export const tagApi = {
|
|
getTags: () => apiRequest<Tag[]>('/tags'),
|
|
createTag: (data) => apiRequest<Tag>('/tags', { method: 'POST', body: JSON.stringify(data) }),
|
|
updateTag: (id, data) => apiRequest<Tag>(`/tags/${id}`, { method: 'PATCH', body: JSON.stringify(data) }),
|
|
deleteTag: (id) => apiRequest(`/tags/${id}`, { method: 'DELETE' }),
|
|
getImageTags: (imageId) => apiRequest<Tag[]>(`/tags/image/${imageId}`),
|
|
addTagToImage: (imageId, tagId) => apiRequest(`/tags/image/${imageId}/${tagId}`, { method: 'POST' }),
|
|
removeTagFromImage: (imageId, tagId) => apiRequest(`/tags/image/${imageId}/${tagId}`, { method: 'DELETE' }),
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 3: Hooks migrieren
|
|
|
|
#### 3.1 `useImageFetching.ts`
|
|
- Ersetze `supabase.from('images')` durch `imageApi.getImages()`
|
|
- Pagination über API Query-Parameter
|
|
|
|
#### 3.2 `useImageLikes.ts`
|
|
- Ersetze `supabase.from('image_likes')` durch `imageApi.likeImage()` / `unlikeImage()`
|
|
|
|
#### 3.3 `useExploreFetching.ts`
|
|
- Ersetze `supabase.from('images').eq('is_public', true)` durch `exploreApi.getPublicImages()`
|
|
|
|
#### 3.4 `useArchiveFetching.ts`
|
|
- Ersetze `supabase.from('images').not('archived_at', 'is', null)` durch `imageApi.getImages({ archived: true })`
|
|
|
|
---
|
|
|
|
### Phase 4: Stores migrieren
|
|
|
|
#### 4.1 `tagStore.ts`
|
|
- Ersetze alle `supabase.from('tags')` und `supabase.from('image_tags')` durch `tagApi.*`
|
|
|
|
#### 4.2 `batchStore.ts`
|
|
- Ersetze `supabase.functions.invoke()` durch Backend API Calls
|
|
- Ersetze `supabase.channel()` Realtime durch Polling oder WebSocket zum Backend
|
|
- **ODER**: Batch-Generierung komplett über Backend API abwickeln
|
|
|
|
---
|
|
|
|
### Phase 5: Screens migrieren
|
|
|
|
#### 5.1 `profile.tsx`
|
|
```typescript
|
|
// Vorher:
|
|
const { data } = await supabase.from('profiles').select('*').eq('id', user.id).single();
|
|
|
|
// Nachher:
|
|
const { data } = await profileApi.getMyProfile();
|
|
```
|
|
|
|
#### 5.2 `image/[id].tsx`
|
|
```typescript
|
|
// Vorher:
|
|
const { data } = await supabase.from('images').select('*').eq('id', id).single();
|
|
|
|
// Nachher:
|
|
const { data } = await imageApi.getImage(id);
|
|
```
|
|
|
|
---
|
|
|
|
### Phase 6: Aufräumen
|
|
|
|
#### 6.1 Dateien entfernen
|
|
- `utils/supabase.ts` - Supabase Client
|
|
- Alle Supabase-Typen die nicht mehr gebraucht werden
|
|
|
|
#### 6.2 Dependencies entfernen
|
|
```json
|
|
// package.json - Diese entfernen:
|
|
"@supabase/supabase-js": "^2.38.4",
|
|
```
|
|
|
|
#### 6.3 Environment Variables aufräumen
|
|
```
|
|
# Nicht mehr benötigt:
|
|
EXPO_PUBLIC_SUPABASE_URL
|
|
EXPO_PUBLIC_SUPABASE_ANON_KEY
|
|
|
|
# Weiterhin benötigt:
|
|
EXPO_PUBLIC_PICTURE_BACKEND_URL
|
|
EXPO_PUBLIC_MANA_AUTH_URL
|
|
```
|
|
|
|
---
|
|
|
|
## Implementierungsreihenfolge
|
|
|
|
1. **Backend erweitern** (Profile, Likes, Rate Limit Endpoints)
|
|
2. **API Client erstellen** (`services/api/client.ts`)
|
|
3. **Domain APIs erstellen** (profiles, images erweitern, explore, tags)
|
|
4. **Hooks einzeln migrieren** (mit Tests nach jeder Migration)
|
|
5. **Stores migrieren** (tagStore, batchStore)
|
|
6. **Screens migrieren** (profile.tsx, image/[id].tsx)
|
|
7. **Supabase entfernen** (Dependencies, Environment Variables)
|
|
8. **Testen** (Alle Flows durchgehen)
|
|
|
|
---
|
|
|
|
## Geschätzter Aufwand
|
|
|
|
| Phase | Aufwand |
|
|
|-------|---------|
|
|
| Phase 1: Backend erweitern | ~2-3 Stunden |
|
|
| Phase 2: API Client | ~1 Stunde |
|
|
| Phase 3: Hooks migrieren | ~2-3 Stunden |
|
|
| Phase 4: Stores migrieren | ~1-2 Stunden |
|
|
| Phase 5: Screens migrieren | ~1 Stunde |
|
|
| Phase 6: Aufräumen & Testen | ~1 Stunde |
|
|
| **Gesamt** | **~8-11 Stunden** |
|
|
|
|
---
|
|
|
|
## Risiken & Hinweise
|
|
|
|
1. **Realtime-Subscriptions**: Supabase Realtime wird durch Polling ersetzt (bereits bei Bildgenerierung umgesetzt)
|
|
2. **Batch-Generierung**: Der `batchStore` nutzt Edge Functions - diese müssen ins Backend migriert werden
|
|
3. **Storage**: Bilder werden weiterhin irgendwo gespeichert - prüfen ob S3/R2 oder weiterhin Supabase Storage
|
|
4. **RLS Policies**: Backend übernimmt die Autorisierung - alle Queries müssen `userId` filtern
|