mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
- Migrate image generation from Supabase Edge Functions to NestJS - Add profiles and image-likes schemas - Refactor mobile auth context for backend API - Update all mobile hooks and services for API integration - Add Docker configuration for deployment - Remove Supabase functions and migrations - Add migration plan documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
8.6 KiB
8.6 KiB
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:
app/(tabs)/profile.tsx- Profile-Daten laden/aktualisierenapp/image/[id].tsx- Einzelbild-Detailshooks/useImageFetching.ts- Bilder ladenhooks/useImageLikes.ts- Like-Funktionalitäthooks/useImagePrefetch.ts- Prefetchinghooks/useExploreFetching.ts- Explore-Datenhooks/useExplorePrefetch.ts- Explore-Prefetchinghooks/useArchiveFetching.ts- Archiv-Datenstore/tagStore.ts- Tag-Datenstore/batchStore.ts- Batch-Generierung (nutzt Edge Functions)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):
// 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):
// 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
import * as SecureStore from 'expo-secure-store';
const APP_TOKEN_KEY = '@manacore/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
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)
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
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
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')durchimageApi.getImages() - Pagination über API Query-Parameter
3.2 useImageLikes.ts
- Ersetze
supabase.from('image_likes')durchimageApi.likeImage()/unlikeImage()
3.3 useExploreFetching.ts
- Ersetze
supabase.from('images').eq('is_public', true)durchexploreApi.getPublicImages()
3.4 useArchiveFetching.ts
- Ersetze
supabase.from('images').not('archived_at', 'is', null)durchimageApi.getImages({ archived: true })
Phase 4: Stores migrieren
4.1 tagStore.ts
- Ersetze alle
supabase.from('tags')undsupabase.from('image_tags')durchtagApi.*
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
// Vorher:
const { data } = await supabase.from('profiles').select('*').eq('id', user.id).single();
// Nachher:
const { data } = await profileApi.getMyProfile();
5.2 image/[id].tsx
// 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
// 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_CORE_AUTH_URL
Implementierungsreihenfolge
- Backend erweitern (Profile, Likes, Rate Limit Endpoints)
- API Client erstellen (
services/api/client.ts) - Domain APIs erstellen (profiles, images erweitern, explore, tags)
- Hooks einzeln migrieren (mit Tests nach jeder Migration)
- Stores migrieren (tagStore, batchStore)
- Screens migrieren (profile.tsx, image/[id].tsx)
- Supabase entfernen (Dependencies, Environment Variables)
- 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
- Realtime-Subscriptions: Supabase Realtime wird durch Polling ersetzt (bereits bei Bildgenerierung umgesetzt)
- Batch-Generierung: Der
batchStorenutzt Edge Functions - diese müssen ins Backend migriert werden - Storage: Bilder werden weiterhin irgendwo gespeichert - prüfen ob S3/R2 oder weiterhin Supabase Storage
- RLS Policies: Backend übernimmt die Autorisierung - alle Queries müssen
userIdfiltern