refactor: restructure

monorepo with apps/ and services/
  directories
This commit is contained in:
Wuesteon 2025-11-26 03:03:24 +01:00
parent 25824ed0ac
commit ff80aeec1f
4062 changed files with 2592 additions and 1278 deletions

View file

@ -0,0 +1,3 @@
# Disable automatic JWT verification by Supabase Edge Gateway
# We'll manually verify the Mana Core JWT inside the function
verify_jwt = false

View file

@ -0,0 +1,3 @@
# Disable automatic JWT verification by Supabase Edge Gateway
# We'll manually verify the Mana Core JWT inside the function
verify_jwt = false

View file

@ -0,0 +1,273 @@
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2.39.3';
import * as jose from "https://deno.land/x/jose@v5.9.6/index.ts";
const corsHeaders = {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type'
};
// System prompt for consistent card generation
const SYSTEM_PROMPT = `Du bist ein Experte für die Erstellung von Lernkarten. Erstelle strukturierte Lernkarten basierend auf dem gegebenen Thema.
Regeln für die Kartenerstellung:
1. Erstelle abwechslungsreiche Karten (Flashcards und Quiz-Karten)
2. Formuliere klare, präzise Fragen und Antworten
3. Verwende die deutsche Sprache
4. Stelle sicher, dass die Karten aufeinander aufbauen
5. Füge hilfreiche Hinweise und Erklärungen hinzu
Du musst die Karten als JSON-Array zurückgeben mit folgendem Format:
[
{
"card_type": "flashcard" | "quiz",
"content": {
// Für flashcard:
"front": "Frage oder Begriff",
"back": "Antwort oder Definition",
"hint": "Optionaler Hinweis"
// Für quiz:
"question": "Die Frage",
"options": ["Option 1", "Option 2", "Option 3", "Option 4"],
"correct_answer": 0, // Index der richtigen Antwort (0-3)
"explanation": "Erklärung zur richtigen Antwort"
},
"position": 1, // Position in der Reihenfolge
"title": "Kurzer Titel für die Karte"
}
]
Erstelle GENAU die angeforderte Anzahl von Karten.`;
Deno.serve(async (req) => {
// Handle CORS preflight requests
if (req.method === 'OPTIONS') {
return new Response('ok', { headers: corsHeaders });
}
try {
// Get the authorization header
const authHeader = req.headers.get('Authorization');
if (!authHeader) {
throw new Error('No authorization header');
}
// Extract the Mana app token
const appToken = authHeader.replace('Bearer ', '');
// Get Mana Core JWKS URL from environment variable
const manaJwksUrl = Deno.env.get('JWKS_URL');
if (!manaJwksUrl) {
throw new Error('JWKS_URL not configured');
}
// Verify the Mana token using JWKS
const JWKS = jose.createRemoteJWKSet(new URL(manaJwksUrl));
const { payload } = await jose.jwtVerify(appToken, JWKS);
const userId = payload.sub as string;
if (!userId) {
throw new Error('Invalid token: no user ID');
}
console.log(`Authenticated user: ${userId}`);
// Initialize Supabase client with service role
const supabaseUrl = Deno.env.get('SUPABASE_URL')!;
const supabaseServiceKey = Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!;
const supabase = createClient(supabaseUrl, supabaseServiceKey);
// Use the userId from the Mana token
const user = { id: userId };
// Parse request body
const requestData = await req.json();
const {
prompt: userPrompt,
deckTitle,
deckDescription = '',
cardCount = 10,
cardTypes = ['flashcard', 'quiz'],
difficulty = 'intermediate',
tags = []
} = requestData;
// Validate input
if (!userPrompt || !deckTitle) {
throw new Error('userPrompt and deckTitle are required');
}
if (cardCount < 1 || cardCount > 50) {
throw new Error('cardCount must be between 1 and 50');
}
// Get OpenAI API key from environment
const openAIApiKey = Deno.env.get('OPENAI_API_KEY');
if (!openAIApiKey) {
throw new Error('OpenAI API key not configured');
}
// Prepare the user message with specific instructions
const userMessage = `
Thema: ${userPrompt}
Anzahl Karten: ${cardCount}
Kartentypen: ${cardTypes.join(', ')}
Schwierigkeit: ${difficulty}
Tags: ${tags.length > 0 ? tags.join(', ') : 'keine spezifischen Tags'}
Erstelle ${cardCount} Lernkarten zum obigen Thema. Mische die Kartentypen ab und stelle sicher, dass die Karten progressiv aufeinander aufbauen.`;
console.log('Generating cards with OpenAI...');
// Call OpenAI API
const openAIResponse = await fetch('https://api.openai.com/v1/chat/completions', {
method: 'POST',
headers: {
'Authorization': `Bearer ${openAIApiKey}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: 'gpt-4o-mini',
messages: [
{ role: 'system', content: SYSTEM_PROMPT },
{ role: 'user', content: userMessage }
],
temperature: 0.7,
max_tokens: 4000,
response_format: { type: "json_object" }
})
});
if (!openAIResponse.ok) {
const error = await openAIResponse.text();
console.error('OpenAI API error:', error);
throw new Error(`OpenAI API error: ${error}`);
}
const aiData = await openAIResponse.json();
const content = aiData.choices[0]?.message?.content;
if (!content) {
throw new Error('No content received from OpenAI');
}
// Parse the JSON response
let generatedCards;
try {
const parsed = JSON.parse(content);
generatedCards = Array.isArray(parsed) ? parsed : parsed.cards || parsed.karten || [];
} catch (parseError) {
console.error('Failed to parse OpenAI response:', content);
throw new Error('Invalid response format from AI');
}
if (!Array.isArray(generatedCards) || generatedCards.length === 0) {
throw new Error('No cards generated');
}
console.log(`Generated ${generatedCards.length} cards`);
// Create the deck in the database
const { data: deck, error: deckError } = await supabase
.from('decks')
.insert({
user_id: user.id,
title: deckTitle,
description: deckDescription || `KI-generiertes Deck zum Thema: ${userPrompt}`,
is_public: false,
tags: tags,
metadata: {
ai_generated: true,
generation_prompt: userPrompt,
generation_date: new Date().toISOString(),
model: 'gpt-4o-mini'
}
})
.select()
.single();
if (deckError || !deck) {
console.error('Failed to create deck:', deckError);
throw new Error('Failed to create deck');
}
// Prepare cards for insertion
const cardsToInsert = generatedCards.map((card, index) => ({
deck_id: deck.id,
card_type: card.card_type,
content: card.content,
position: card.position || index + 1,
title: card.title || `Karte ${index + 1}`,
ai_model: 'gpt-4o-mini',
ai_prompt: userPrompt,
version: 1,
is_favorite: false
}));
// Insert all cards
const { data: cards, error: cardsError } = await supabase
.from('cards')
.insert(cardsToInsert)
.select();
if (cardsError) {
console.error('Failed to create cards:', cardsError);
await supabase.from('decks').delete().eq('id', deck.id);
throw new Error('Failed to create cards: ' + JSON.stringify(cardsError));
}
console.log(`Successfully created deck with ${cards?.length} cards`);
// Track the generation (optional)
try {
await supabase.from('ai_generations').insert({
user_id: user.id,
deck_id: deck.id,
function_name: 'generate-deck',
prompt: userPrompt,
model: 'gpt-4o-mini',
status: 'completed',
metadata: {
card_count: cards?.length,
card_types: cardTypes,
difficulty: difficulty
},
completed_at: new Date().toISOString()
});
} catch (trackingError) {
console.log('Could not track generation:', trackingError);
}
// Return success response
return new Response(JSON.stringify({
success: true,
deck: {
id: deck.id,
title: deck.title,
description: deck.description,
card_count: cards?.length || 0
},
cards: cards,
message: `Deck "${deckTitle}" mit ${cards?.length} Karten erfolgreich erstellt!`
}), {
headers: {
...corsHeaders,
'Content-Type': 'application/json'
},
status: 200
});
} catch (error) {
console.error('Error in generate-deck function:', error);
return new Response(JSON.stringify({
success: false,
error: error.message || 'Ein unerwarteter Fehler ist aufgetreten'
}), {
headers: {
...corsHeaders,
'Content-Type': 'application/json'
},
status: error.message?.includes('authorization') ? 401 : 400
});
}
});

View file

@ -0,0 +1,10 @@
-- Remove foreign key constraint on study_sessions.user_id
-- This allows storing Mana Core user IDs without requiring a local users table
ALTER TABLE study_sessions DROP CONSTRAINT IF EXISTS study_sessions_user_id_fkey;
-- Remove foreign key constraint on card_progress.user_id
ALTER TABLE card_progress DROP CONSTRAINT IF EXISTS card_progress_user_id_fkey;
-- Add comments to document that user_id references Mana Core users
COMMENT ON COLUMN study_sessions.user_id IS 'User ID from Mana Core authentication system';
COMMENT ON COLUMN card_progress.user_id IS 'User ID from Mana Core authentication system';

View file

@ -0,0 +1,6 @@
-- Remove foreign key constraint on decks.user_id
-- This allows storing Mana Core user IDs without requiring a local users table
ALTER TABLE decks DROP CONSTRAINT IF EXISTS decks_user_id_fkey;
-- Optional: Add a comment to document that user_id references Mana Core users
COMMENT ON COLUMN decks.user_id IS 'User ID from Mana Core authentication system';