feat: improve AI resilience, add background enrichment loading states, and fix duplicate identifier in TagSelector
This commit is contained in:
@@ -36,12 +36,12 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
|
||||
|
||||
// 2. Auth & Credits
|
||||
supabase = await createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session || !session.user) {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
return { success: false, error: 'Nicht autorisiert oder Session abgelaufen.' };
|
||||
}
|
||||
|
||||
const userId = session.user.id;
|
||||
const userId = user.id;
|
||||
const creditCheck = await checkCreditBalance(userId, 'gemini_ai');
|
||||
if (!creditCheck.allowed) {
|
||||
return {
|
||||
@@ -85,98 +85,98 @@ export async function analyzeBottleMistral(input: any): Promise<AnalysisResponse
|
||||
|
||||
const prompt = getSystemPrompt(tags.length > 0 ? tags.join(', ') : 'Keine Tags verfügbar', locale);
|
||||
|
||||
const startApi = performance.now();
|
||||
const chatResponse = await client.chat.complete({
|
||||
model: 'mistral-large-latest',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{ type: 'image_url', imageUrl: dataUrl }
|
||||
]
|
||||
}
|
||||
],
|
||||
responseFormat: { type: 'json_object' },
|
||||
temperature: 0.1
|
||||
});
|
||||
const endApi = performance.now();
|
||||
|
||||
const startParse = performance.now();
|
||||
const rawContent = chatResponse.choices?.[0].message.content;
|
||||
if (!rawContent) throw new Error("Keine Antwort von Mistral");
|
||||
|
||||
let jsonData;
|
||||
try {
|
||||
jsonData = JSON.parse(rawContent as string);
|
||||
} catch (e) {
|
||||
const cleanedText = (rawContent as string).replace(/```json/g, '').replace(/```/g, '');
|
||||
jsonData = JSON.parse(cleanedText);
|
||||
const startApi = performance.now();
|
||||
const chatResponse = await client.chat.complete({
|
||||
model: 'mistral-large-latest',
|
||||
messages: [
|
||||
{
|
||||
role: 'user',
|
||||
content: [
|
||||
{ type: 'text', text: prompt },
|
||||
{ type: 'image_url', imageUrl: dataUrl }
|
||||
]
|
||||
}
|
||||
],
|
||||
responseFormat: { type: 'json_object' },
|
||||
temperature: 0.1
|
||||
});
|
||||
const endApi = performance.now();
|
||||
|
||||
const startParse = performance.now();
|
||||
const rawContent = chatResponse.choices?.[0].message.content;
|
||||
if (!rawContent) throw new Error("Keine Antwort von Mistral");
|
||||
|
||||
let jsonData;
|
||||
try {
|
||||
jsonData = JSON.parse(rawContent as string);
|
||||
} catch (e) {
|
||||
const cleanedText = (rawContent as string).replace(/```json/g, '').replace(/```/g, '');
|
||||
jsonData = JSON.parse(cleanedText);
|
||||
}
|
||||
|
||||
if (Array.isArray(jsonData)) jsonData = jsonData[0];
|
||||
console.log('[Mistral AI] JSON Response:', jsonData);
|
||||
|
||||
const searchString = jsonData.search_string;
|
||||
delete jsonData.search_string;
|
||||
|
||||
if (typeof jsonData.abv === 'string') {
|
||||
jsonData.abv = parseFloat(jsonData.abv.replace('%', '').trim());
|
||||
}
|
||||
|
||||
if (jsonData.age) jsonData.age = parseInt(jsonData.age);
|
||||
if (jsonData.vintage) jsonData.vintage = parseInt(jsonData.vintage);
|
||||
|
||||
const validatedData = BottleMetadataSchema.parse(jsonData);
|
||||
const endParse = performance.now();
|
||||
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'mistral/mistral-large',
|
||||
success: true
|
||||
});
|
||||
|
||||
await deductCredits(userId, 'gemini_ai', 'Mistral AI analysis');
|
||||
|
||||
await supabase
|
||||
.from('vision_cache')
|
||||
.insert({ hash: imageHash, result: validatedData });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: validatedData,
|
||||
search_string: searchString,
|
||||
perf: {
|
||||
apiDuration: endApi - startApi,
|
||||
parseDuration: endParse - startParse,
|
||||
uploadSize: uploadSize
|
||||
},
|
||||
raw: jsonData
|
||||
};
|
||||
|
||||
} catch (aiError: any) {
|
||||
console.warn('[MistralAnalysis] AI Analysis failed, providing fallback path:', aiError.message);
|
||||
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'mistral/mistral-large',
|
||||
success: false,
|
||||
errorMessage: aiError.message
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
isAiError: true,
|
||||
error: aiError.message,
|
||||
imageHash: imageHash
|
||||
} as any;
|
||||
}
|
||||
|
||||
if (Array.isArray(jsonData)) jsonData = jsonData[0];
|
||||
console.log('[Mistral AI] JSON Response:', jsonData);
|
||||
|
||||
// Extract search_string before validation
|
||||
const searchString = jsonData.search_string;
|
||||
delete jsonData.search_string;
|
||||
|
||||
// Ensure abv is a number if it came as a string
|
||||
if (typeof jsonData.abv === 'string') {
|
||||
jsonData.abv = parseFloat(jsonData.abv.replace('%', '').trim());
|
||||
}
|
||||
|
||||
// Ensure age/vintage are numbers
|
||||
if (jsonData.age) jsonData.age = parseInt(jsonData.age);
|
||||
if (jsonData.vintage) jsonData.vintage = parseInt(jsonData.vintage);
|
||||
|
||||
const validatedData = BottleMetadataSchema.parse(jsonData);
|
||||
const endParse = performance.now();
|
||||
|
||||
// Track usage
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'mistral/mistral-large',
|
||||
success: true
|
||||
});
|
||||
|
||||
// Deduct credits
|
||||
await deductCredits(userId, 'gemini_ai', 'Mistral AI analysis');
|
||||
|
||||
// Store in Cache
|
||||
await supabase
|
||||
.from('vision_cache')
|
||||
.insert({ hash: imageHash, result: validatedData });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: validatedData,
|
||||
search_string: searchString,
|
||||
perf: {
|
||||
apiDuration: endApi - startApi,
|
||||
parseDuration: endParse - startParse,
|
||||
uploadSize: uploadSize
|
||||
},
|
||||
raw: jsonData
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('Mistral Analysis Error:', error);
|
||||
|
||||
if (supabase) {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (session?.user) {
|
||||
await trackApiUsage({
|
||||
userId: session.user.id,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'mistral/mistral-large',
|
||||
success: false,
|
||||
errorMessage: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Mistral Analysis Global Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Mistral AI analysis failed.',
|
||||
|
||||
@@ -37,13 +37,13 @@ export async function analyzeBottle(input: any): Promise<AnalysisResponse> {
|
||||
|
||||
// 2. Auth & Credits (bleibt gleich)
|
||||
supabase = await createClient();
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
|
||||
if (!session || !session.user) {
|
||||
if (!user) {
|
||||
return { success: false, error: 'Nicht autorisiert oder Session abgelaufen.' };
|
||||
}
|
||||
|
||||
const userId = session.user.id;
|
||||
const userId = user.id;
|
||||
const creditCheck = await checkCreditBalance(userId, 'gemini_ai');
|
||||
|
||||
if (!creditCheck.allowed) {
|
||||
@@ -80,96 +80,96 @@ export async function analyzeBottle(input: any): Promise<AnalysisResponse> {
|
||||
}
|
||||
|
||||
// 5. Für Gemini vorbereiten
|
||||
// Wir müssen es hier zwar zu Base64 machen, aber Node.js (C++) macht das
|
||||
// extrem effizient. Das Problem vorher war der JSON Parser von Next.js.
|
||||
const base64Data = buffer.toString('base64');
|
||||
const mimeType = file.type || 'image/webp'; // Fallback
|
||||
const mimeType = file.type || 'image/webp';
|
||||
const uploadSize = buffer.length;
|
||||
|
||||
const instruction = getSystemPrompt(tags.length > 0 ? tags.join(', ') : 'No tags available', locale);
|
||||
|
||||
// API Call
|
||||
const startApi = performance.now();
|
||||
const result = await geminiModel.generateContent([
|
||||
{
|
||||
inlineData: {
|
||||
data: base64Data,
|
||||
mimeType: mimeType,
|
||||
},
|
||||
},
|
||||
{ text: instruction },
|
||||
]);
|
||||
const endApi = performance.now();
|
||||
|
||||
const startParse = performance.now();
|
||||
const responseText = result.response.text();
|
||||
|
||||
// JSON Parsing der ANTWORT (das ist klein, das schafft der N100 locker)
|
||||
let jsonData;
|
||||
try {
|
||||
jsonData = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
// Fallback falls Gemini Markdown ```json Blöcke schickt
|
||||
const cleanedText = responseText.replace(/```json/g, '').replace(/```/g, '');
|
||||
jsonData = JSON.parse(cleanedText);
|
||||
// API Call
|
||||
const startApi = performance.now();
|
||||
const result = await geminiModel.generateContent([
|
||||
{
|
||||
inlineData: {
|
||||
data: base64Data,
|
||||
mimeType: mimeType,
|
||||
},
|
||||
},
|
||||
{ text: instruction },
|
||||
]);
|
||||
const endApi = performance.now();
|
||||
|
||||
const startParse = performance.now();
|
||||
const responseText = result.response.text();
|
||||
|
||||
let jsonData;
|
||||
try {
|
||||
jsonData = JSON.parse(responseText);
|
||||
} catch (e) {
|
||||
const cleanedText = responseText.replace(/```json/g, '').replace(/```/g, '');
|
||||
jsonData = JSON.parse(cleanedText);
|
||||
}
|
||||
|
||||
if (Array.isArray(jsonData)) jsonData = jsonData[0];
|
||||
console.log('[Gemini AI] JSON Response:', jsonData);
|
||||
|
||||
const searchString = jsonData.search_string;
|
||||
delete jsonData.search_string;
|
||||
|
||||
if (!jsonData) throw new Error('Keine Daten in der KI-Antwort gefunden.');
|
||||
|
||||
const validatedData = BottleMetadataSchema.parse(jsonData);
|
||||
const endParse = performance.now();
|
||||
|
||||
// 6. Tracking & Credits
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'generateContent',
|
||||
success: true
|
||||
});
|
||||
|
||||
await deductCredits(userId, 'gemini_ai', 'Bottle analysis');
|
||||
|
||||
// Cache speichern
|
||||
await supabase
|
||||
.from('vision_cache')
|
||||
.insert({ hash: imageHash, result: validatedData });
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: validatedData,
|
||||
search_string: searchString,
|
||||
perf: {
|
||||
apiDuration: endApi - startApi,
|
||||
parseDuration: endParse - startParse,
|
||||
uploadSize: uploadSize
|
||||
},
|
||||
raw: jsonData
|
||||
} as any;
|
||||
|
||||
} catch (aiError: any) {
|
||||
console.warn('[AnalyzeBottle] AI Analysis failed, providing fallback path:', aiError.message);
|
||||
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'generateContent',
|
||||
success: false,
|
||||
errorMessage: aiError.message
|
||||
});
|
||||
|
||||
return {
|
||||
success: false,
|
||||
isAiError: true,
|
||||
error: aiError.message,
|
||||
imageHash: imageHash
|
||||
} as any;
|
||||
}
|
||||
|
||||
if (Array.isArray(jsonData)) jsonData = jsonData[0];
|
||||
console.log('[Gemini AI] JSON Response:', jsonData);
|
||||
|
||||
const searchString = jsonData.search_string;
|
||||
delete jsonData.search_string;
|
||||
|
||||
if (!jsonData) throw new Error('Keine Daten in der KI-Antwort gefunden.');
|
||||
|
||||
const validatedData = BottleMetadataSchema.parse(jsonData);
|
||||
const endParse = performance.now();
|
||||
|
||||
// 6. Tracking & Credits (bleibt gleich)
|
||||
await trackApiUsage({
|
||||
userId: userId,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'generateContent',
|
||||
success: true
|
||||
});
|
||||
|
||||
await deductCredits(userId, 'gemini_ai', 'Bottle analysis');
|
||||
|
||||
// Cache speichern
|
||||
const { error: storeError } = await supabase
|
||||
.from('vision_cache')
|
||||
.insert({ hash: imageHash, result: validatedData });
|
||||
|
||||
if (storeError) console.warn(`[AI Cache] Storage failed: ${storeError.message}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: validatedData,
|
||||
search_string: searchString,
|
||||
perf: {
|
||||
apiDuration: endApi - startApi,
|
||||
parseDuration: endParse - startParse,
|
||||
uploadSize: uploadSize
|
||||
},
|
||||
raw: jsonData
|
||||
} as any;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Gemini Analysis Error:', error);
|
||||
// Error Tracking Logic (bleibt gleich)
|
||||
if (supabase) {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (session?.user) {
|
||||
await trackApiUsage({
|
||||
userId: session.user.id,
|
||||
apiType: 'gemini_ai',
|
||||
endpoint: 'generateContent',
|
||||
success: false,
|
||||
errorMessage: error instanceof Error ? error.message : 'Unknown error'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.error('Gemini Analysis Global Error:', error);
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'An unknown error occurred during analysis.',
|
||||
|
||||
@@ -8,12 +8,12 @@ export async function addBuddy(rawData: BuddyData) {
|
||||
|
||||
try {
|
||||
const { name } = BuddySchema.parse(rawData);
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) throw new Error('Nicht autorisiert');
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('Nicht autorisiert');
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('buddies')
|
||||
.insert([{ name, user_id: session.user.id }])
|
||||
.insert([{ name, user_id: user.id }])
|
||||
.select()
|
||||
.single();
|
||||
|
||||
@@ -32,14 +32,14 @@ export async function deleteBuddy(id: string) {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) throw new Error('Nicht autorisiert');
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('Nicht autorisiert');
|
||||
|
||||
const { error } = await supabase
|
||||
.from('buddies')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
.eq('user_id', session.user.id);
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (error) throw error;
|
||||
return { success: true };
|
||||
|
||||
@@ -7,8 +7,8 @@ export async function deleteBottle(bottleId: string) {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
throw new Error('Nicht autorisiert.');
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ export async function deleteBottle(bottleId: string) {
|
||||
throw new Error('Flasche nicht gefunden.');
|
||||
}
|
||||
|
||||
if (bottle.user_id !== session.user.id) {
|
||||
if (bottle.user_id !== user.id) {
|
||||
throw new Error('Keine Berechtigung.');
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ export async function deleteSession(sessionId: string) {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
throw new Error('Nicht autorisiert.');
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function deleteSession(sessionId: string) {
|
||||
.from('tasting_sessions')
|
||||
.delete()
|
||||
.eq('id', sessionId)
|
||||
.eq('user_id', session.user.id);
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (deleteError) throw deleteError;
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ export async function deleteTasting(tastingId: string, bottleId: string) {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
throw new Error('Nicht autorisiert.');
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ export async function deleteTasting(tastingId: string, bottleId: string) {
|
||||
.from('tastings')
|
||||
.delete()
|
||||
.eq('id', tastingId)
|
||||
.eq('user_id', session.user.id);
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (deleteError) throw deleteError;
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@ export async function findMatchingBottle(metadata: BottleMetadata) {
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) return null;
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) return null;
|
||||
|
||||
const userId = session.user.id;
|
||||
const userId = user.id;
|
||||
|
||||
// 1. Try matching by Whiskybase ID (most reliable)
|
||||
if (metadata.whiskybaseId) {
|
||||
|
||||
@@ -14,12 +14,12 @@ export async function saveBottle(
|
||||
|
||||
try {
|
||||
const metadata = BottleMetadataSchema.parse(rawMetadata);
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
throw new Error('Nicht autorisiert oder Session abgelaufen.');
|
||||
}
|
||||
|
||||
const userId = session.user.id;
|
||||
const userId = user.id;
|
||||
let finalImageUrl = preUploadedUrl;
|
||||
|
||||
// 1. Upload Image to Storage if not already uploaded
|
||||
@@ -50,6 +50,26 @@ export async function saveBottle(
|
||||
throw new Error('Kein Bild zum Speichern vorhanden.');
|
||||
}
|
||||
|
||||
// 1.5 Deduplication Check
|
||||
// If a bottle with the same name/distillery was created by the same user in the last 5 minutes,
|
||||
// we treat it as a duplicate (likely from a race condition or double sync).
|
||||
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000).toISOString();
|
||||
const { data: existingBottle } = await supabase
|
||||
.from('bottles')
|
||||
.select('*')
|
||||
.eq('user_id', userId)
|
||||
.eq('name', metadata.name)
|
||||
.eq('distillery', metadata.distillery)
|
||||
.gte('created_at', fiveMinutesAgo)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(1)
|
||||
.maybeSingle();
|
||||
|
||||
if (existingBottle) {
|
||||
console.log('[saveBottle] Potential duplicate detected, returning existing bottle:', existingBottle.id);
|
||||
return { success: true, data: existingBottle };
|
||||
}
|
||||
|
||||
// 2. Save Metadata to Database
|
||||
const { data: bottleData, error: dbError } = await supabase
|
||||
.from('bottles')
|
||||
@@ -64,7 +84,7 @@ export async function saveBottle(
|
||||
image_url: finalImageUrl,
|
||||
status: 'sealed',
|
||||
is_whisky: metadata.is_whisky ?? true,
|
||||
confidence: metadata.confidence ?? 100,
|
||||
confidence: metadata.confidence ? Math.round(metadata.confidence * 100) : 100,
|
||||
distilled_at: metadata.distilled_at,
|
||||
bottled_at: metadata.bottled_at,
|
||||
batch_info: metadata.batch_info,
|
||||
|
||||
@@ -11,8 +11,8 @@ export async function saveTasting(rawData: TastingNoteData) {
|
||||
|
||||
try {
|
||||
const data = TastingNoteSchema.parse(rawData);
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) throw new Error('Nicht autorisiert');
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('Nicht autorisiert');
|
||||
|
||||
// Validate Session Age (12 hour limit)
|
||||
if (data.session_id) {
|
||||
@@ -26,7 +26,7 @@ export async function saveTasting(rawData: TastingNoteData) {
|
||||
.from('tastings')
|
||||
.insert({
|
||||
bottle_id: data.bottle_id,
|
||||
user_id: session.user.id,
|
||||
user_id: user.id,
|
||||
session_id: data.session_id,
|
||||
rating: data.rating,
|
||||
nose_notes: data.nose_notes,
|
||||
@@ -46,7 +46,7 @@ export async function saveTasting(rawData: TastingNoteData) {
|
||||
const buddies = data.buddy_ids.map(buddyId => ({
|
||||
tasting_id: tasting.id,
|
||||
buddy_id: buddyId,
|
||||
user_id: session.user.id
|
||||
user_id: user.id
|
||||
}));
|
||||
const { error: tagError } = await supabase
|
||||
.from('tasting_buddies')
|
||||
@@ -64,7 +64,7 @@ export async function saveTasting(rawData: TastingNoteData) {
|
||||
const aromaTags = data.tag_ids.map(tagId => ({
|
||||
tasting_id: tasting.id,
|
||||
tag_id: tagId,
|
||||
user_id: session.user.id
|
||||
user_id: user.id
|
||||
}));
|
||||
const { error: aromaTagError } = await supabase
|
||||
.from('tasting_tags')
|
||||
|
||||
@@ -74,8 +74,8 @@ export async function createCustomTag(rawName: string, rawCategory: TagCategory)
|
||||
|
||||
try {
|
||||
const { name, category } = TagSchema.parse({ name: rawName, category: rawCategory });
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) throw new Error('Nicht autorisiert');
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('Nicht autorisiert');
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('tags')
|
||||
@@ -83,7 +83,7 @@ export async function createCustomTag(rawName: string, rawCategory: TagCategory)
|
||||
name,
|
||||
category,
|
||||
is_system_default: false,
|
||||
created_by: session.user.id
|
||||
created_by: user.id
|
||||
})
|
||||
.select()
|
||||
.single();
|
||||
|
||||
@@ -7,8 +7,8 @@ export async function updateBottleStatus(bottleId: string, status: 'sealed' | 'o
|
||||
const supabase = await createClient();
|
||||
|
||||
try {
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) {
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) {
|
||||
throw new Error('Nicht autorisiert');
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function updateBottleStatus(bottleId: string, status: 'sealed' | 'o
|
||||
finished_at: status === 'empty' ? new Date().toISOString() : null
|
||||
})
|
||||
.eq('id', bottleId)
|
||||
.eq('user_id', session.user.id);
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (error) {
|
||||
throw error;
|
||||
|
||||
@@ -10,8 +10,8 @@ export async function updateBottle(bottleId: string, rawData: UpdateBottleData)
|
||||
|
||||
try {
|
||||
const data = UpdateBottleSchema.parse(rawData);
|
||||
const { data: { session } } = await supabase.auth.getSession();
|
||||
if (!session) throw new Error('Nicht autorisiert');
|
||||
const { data: { user } } = await supabase.auth.getUser();
|
||||
if (!user) throw new Error('Nicht autorisiert');
|
||||
|
||||
const { error } = await supabase
|
||||
.from('bottles')
|
||||
@@ -29,7 +29,7 @@ export async function updateBottle(bottleId: string, rawData: UpdateBottleData)
|
||||
updated_at: new Date().toISOString(),
|
||||
})
|
||||
.eq('id', bottleId)
|
||||
.eq('user_id', session.user.id);
|
||||
.eq('user_id', user.id);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user