mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-22 12:06:42 +02:00
feat(manadeck): complete Supabase removal from frontend and backend
- Frontend: Updated deckStore to use fetch API calls to backend - Frontend: Removed supabase.ts utility and @supabase/supabase-js dependency - Backend: Removed SupabaseService and supabase.service.ts - Backend: Updated validation schema to use DATABASE_URL - Backend: Updated health controller to remove Supabase check - Backend: Marked AI generation endpoint as not implemented (was using Edge Functions) Phase 4 of PostgreSQL/Drizzle migration complete. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
1530efa936
commit
be9df4aa85
10 changed files with 87 additions and 553 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import type { Deck, CreateDeckInput, UpdateDeckInput } from '$lib/types/deck';
|
||||
import { getAuthenticatedSupabase } from '$lib/utils/supabase';
|
||||
import { PUBLIC_API_URL } from '$env/static/public';
|
||||
import { authService } from '$lib/auth';
|
||||
|
||||
// Svelte 5 runes-based deck store
|
||||
|
|
@ -8,6 +8,35 @@ let currentDeck = $state<Deck | null>(null);
|
|||
let loading = $state(false);
|
||||
let error = $state<string | null>(null);
|
||||
|
||||
/**
|
||||
* Helper to make authenticated API requests
|
||||
*/
|
||||
async function apiRequest<T>(
|
||||
endpoint: string,
|
||||
options: RequestInit = {}
|
||||
): Promise<T> {
|
||||
const appToken = await authService.getAppToken();
|
||||
if (!appToken) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const response = await fetch(`${PUBLIC_API_URL}${endpoint}`, {
|
||||
...options,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${appToken}`,
|
||||
...options.headers
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorData = await response.json().catch(() => ({}));
|
||||
throw new Error(errorData.message || `API error: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
export const deckStore = {
|
||||
get decks() {
|
||||
return decks;
|
||||
|
|
@ -30,31 +59,8 @@ export const deckStore = {
|
|||
error = null;
|
||||
|
||||
try {
|
||||
const appToken = await authService.getAppToken();
|
||||
if (!appToken) {
|
||||
throw new Error('Not authenticated');
|
||||
}
|
||||
|
||||
const user = await authService.getUserFromToken();
|
||||
if (!user) {
|
||||
throw new Error('No user found');
|
||||
}
|
||||
|
||||
const supabase = await getAuthenticatedSupabase(appToken);
|
||||
|
||||
const { data, error: fetchError } = await supabase
|
||||
.from('decks')
|
||||
.select('*, cards(count)')
|
||||
.or(`user_id.eq.${user.id},and(is_public.eq.true,user_id.eq.00000000-0000-0000-0000-000000000001)`)
|
||||
.order('updated_at', { ascending: false });
|
||||
|
||||
if (fetchError) throw fetchError;
|
||||
|
||||
// Map card count
|
||||
decks = (data || []).map((deck: any) => ({
|
||||
...deck,
|
||||
card_count: deck.cards?.[0]?.count || 0
|
||||
}));
|
||||
const response = await apiRequest<{ decks: Deck[]; count: number }>('/v1/api/decks');
|
||||
decks = response.decks || [];
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Failed to fetch decks';
|
||||
console.error('Fetch decks error:', err);
|
||||
|
|
@ -71,23 +77,15 @@ export const deckStore = {
|
|||
error = null;
|
||||
|
||||
try {
|
||||
const appToken = await authService.getAppToken();
|
||||
if (!appToken) throw new Error('Not authenticated');
|
||||
|
||||
const supabase = await getAuthenticatedSupabase(appToken);
|
||||
|
||||
const { data, error: fetchError } = await supabase
|
||||
.from('decks')
|
||||
.select('*, cards(count)')
|
||||
.eq('id', id)
|
||||
.single();
|
||||
|
||||
if (fetchError) throw fetchError;
|
||||
|
||||
currentDeck = {
|
||||
...data,
|
||||
card_count: data.cards?.[0]?.count || 0
|
||||
};
|
||||
// For now, find in local decks or fetch all
|
||||
// TODO: Add GET /v1/api/decks/:id endpoint to backend
|
||||
if (decks.length === 0) {
|
||||
await this.fetchDecks();
|
||||
}
|
||||
currentDeck = decks.find((d) => d.id === id) || null;
|
||||
if (!currentDeck) {
|
||||
throw new Error('Deck not found');
|
||||
}
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Failed to fetch deck';
|
||||
console.error('Fetch deck error:', err);
|
||||
|
|
@ -104,35 +102,23 @@ export const deckStore = {
|
|||
error = null;
|
||||
|
||||
try {
|
||||
const appToken = await authService.getAppToken();
|
||||
if (!appToken) throw new Error('Not authenticated');
|
||||
const response = await apiRequest<{ success: boolean; deck: Deck }>('/v1/api/decks', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
title: input.title,
|
||||
description: input.description || '',
|
||||
isPublic: input.is_public ?? false,
|
||||
tags: input.tags || [],
|
||||
settings: input.settings || {}
|
||||
})
|
||||
});
|
||||
|
||||
const user = await authService.getUserFromToken();
|
||||
if (!user) throw new Error('No user found');
|
||||
|
||||
const supabase = await getAuthenticatedSupabase(appToken);
|
||||
|
||||
const newDeck = {
|
||||
user_id: user.id,
|
||||
title: input.title,
|
||||
description: input.description || '',
|
||||
is_public: input.is_public ?? false,
|
||||
tags: input.tags || [],
|
||||
settings: input.settings || {},
|
||||
metadata: {}
|
||||
};
|
||||
|
||||
const { data, error: createError } = await supabase
|
||||
.from('decks')
|
||||
.insert(newDeck)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (createError) throw createError;
|
||||
|
||||
const deck = { ...data, card_count: 0 };
|
||||
decks = [deck, ...decks];
|
||||
return deck;
|
||||
if (response.deck) {
|
||||
const deck = { ...response.deck, card_count: 0 };
|
||||
decks = [deck, ...decks];
|
||||
return deck;
|
||||
}
|
||||
return null;
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Failed to create deck';
|
||||
console.error('Create deck error:', err);
|
||||
|
|
@ -150,26 +136,22 @@ export const deckStore = {
|
|||
error = null;
|
||||
|
||||
try {
|
||||
const appToken = await authService.getAppToken();
|
||||
if (!appToken) throw new Error('Not authenticated');
|
||||
const response = await apiRequest<{ success: boolean; deck: Deck }>(
|
||||
`/v1/api/decks/${id}`,
|
||||
{
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(updates)
|
||||
}
|
||||
);
|
||||
|
||||
const supabase = await getAuthenticatedSupabase(appToken);
|
||||
if (response.deck) {
|
||||
// Update in list
|
||||
decks = decks.map((d) => (d.id === id ? { ...d, ...response.deck } : d));
|
||||
|
||||
const { data, error: updateError } = await supabase
|
||||
.from('decks')
|
||||
.update(updates)
|
||||
.eq('id', id)
|
||||
.select()
|
||||
.single();
|
||||
|
||||
if (updateError) throw updateError;
|
||||
|
||||
// Update in list
|
||||
decks = decks.map((d) => (d.id === id ? { ...d, ...data } : d));
|
||||
|
||||
// Update current if it's the same
|
||||
if (currentDeck?.id === id) {
|
||||
currentDeck = { ...currentDeck, ...data };
|
||||
// Update current if it's the same
|
||||
if (currentDeck?.id === id) {
|
||||
currentDeck = { ...currentDeck, ...response.deck };
|
||||
}
|
||||
}
|
||||
} catch (err: any) {
|
||||
error = err.message || 'Failed to update deck';
|
||||
|
|
@ -187,14 +169,9 @@ export const deckStore = {
|
|||
error = null;
|
||||
|
||||
try {
|
||||
const appToken = await authService.getAppToken();
|
||||
if (!appToken) throw new Error('Not authenticated');
|
||||
|
||||
const supabase = await getAuthenticatedSupabase(appToken);
|
||||
|
||||
const { error: deleteError } = await supabase.from('decks').delete().eq('id', id);
|
||||
|
||||
if (deleteError) throw deleteError;
|
||||
await apiRequest<{ success: boolean }>(`/v1/api/decks/${id}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
|
||||
// Remove from list
|
||||
decks = decks.filter((d) => d.id !== id);
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
import { createClient } from '@supabase/supabase-js';
|
||||
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public';
|
||||
|
||||
// Create a singleton Supabase client
|
||||
export const supabase = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false,
|
||||
detectSessionInUrl: false
|
||||
}
|
||||
});
|
||||
|
||||
/**
|
||||
* Get authenticated Supabase client with Mana Core token
|
||||
* This injects the JWT token from Mana Core for RLS policies
|
||||
*/
|
||||
export async function getAuthenticatedSupabase(appToken?: string) {
|
||||
if (!appToken) {
|
||||
// Try to get token from sessionStorage
|
||||
if (typeof window !== 'undefined') {
|
||||
appToken = sessionStorage.getItem('appToken') || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
if (!appToken) {
|
||||
throw new Error('No auth token available');
|
||||
}
|
||||
|
||||
// Create a new client instance with the auth token
|
||||
const authenticatedClient = createClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${appToken}`
|
||||
}
|
||||
},
|
||||
auth: {
|
||||
autoRefreshToken: false,
|
||||
persistSession: false,
|
||||
detectSessionInUrl: false
|
||||
}
|
||||
});
|
||||
|
||||
return authenticatedClient;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue