150 lines
5.3 KiB
TypeScript
150 lines
5.3 KiB
TypeScript
'use server';
|
|
|
|
import { trackApiUsage, checkDailyLimit } from './track-api-usage';
|
|
import { checkCreditBalance, deductCredits } from './credit-service';
|
|
import { createClient } from '@/lib/supabase/server';
|
|
|
|
import { DiscoveryDataSchema, DiscoveryData } from '@/types/whisky';
|
|
|
|
/**
|
|
* Service to discover a Whiskybase ID for a given bottle.
|
|
* Uses Google Custom Search JSON API to search Google and extracts the ID from the first result.
|
|
*/
|
|
export async function discoverWhiskybaseId(rawBottle: DiscoveryData) {
|
|
// Validate input
|
|
const bottle = DiscoveryDataSchema.parse(rawBottle);
|
|
// Both Gemini and Custom Search often use the same API key if created via AI Studio
|
|
const apiKey = process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY;
|
|
const cx = '37e905eb03fd14e0f'; // Provided by user
|
|
|
|
if (!apiKey) {
|
|
return {
|
|
success: false,
|
|
error: 'GEMINI_API_KEY oder GOOGLE_API_KEY ist nicht konfiguriert.'
|
|
};
|
|
}
|
|
|
|
// Get current user for tracking
|
|
const supabase = await createClient();
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
|
|
if (!user) {
|
|
return {
|
|
success: false,
|
|
error: 'Benutzer nicht authentifiziert.'
|
|
};
|
|
}
|
|
|
|
// Check daily limit before making API call
|
|
const limitCheck = await checkDailyLimit('google_search');
|
|
if (!limitCheck.allowed) {
|
|
return {
|
|
success: false,
|
|
error: `Tageslimit für Whiskybase-Suchen erreicht (${limitCheck.limit} pro Tag). Versuche es morgen erneut.`,
|
|
limitReached: true
|
|
};
|
|
}
|
|
|
|
// Check credit balance before making API call
|
|
const creditCheck = await checkCreditBalance(user.id, 'google_search');
|
|
if (!creditCheck.allowed) {
|
|
return {
|
|
success: false,
|
|
error: `Nicht genügend Credits. Du benötigst ${creditCheck.cost} Credits, hast aber nur ${creditCheck.balance}.`,
|
|
insufficientCredits: true
|
|
};
|
|
}
|
|
|
|
try {
|
|
// Construct targeted search query
|
|
const queryParts = [
|
|
bottle.distillery ? `${bottle.distillery}` : '', // Removed quotes for more fuzzy matching
|
|
bottle.name ? `${bottle.name}` : '',
|
|
bottle.abv ? `${bottle.abv}%` : '',
|
|
bottle.age ? `${bottle.age} year old` : '',
|
|
bottle.batch_info ? `"${bottle.batch_info}"` : '',
|
|
bottle.distilled_at ? `distilled ${bottle.distilled_at}` : '',
|
|
bottle.bottled_at ? `bottled ${bottle.bottled_at}` : ''
|
|
].filter(Boolean);
|
|
|
|
const q = queryParts.join(' ');
|
|
console.log('Whiskybase Search Query:', q);
|
|
|
|
const url = `https://www.googleapis.com/customsearch/v1?key=${apiKey}&cx=${cx}&q=${encodeURIComponent(q)}`;
|
|
|
|
const response = await fetch(url);
|
|
const data = await response.json();
|
|
|
|
if (data.error) {
|
|
console.error('Google API Error Response:', data.error);
|
|
// Track failed API call
|
|
await trackApiUsage({
|
|
userId: user.id,
|
|
apiType: 'google_search',
|
|
endpoint: 'customsearch/v1',
|
|
success: false,
|
|
errorMessage: data.error.message
|
|
});
|
|
throw new Error(data.error.message || 'Google API Error');
|
|
}
|
|
|
|
// Track successful API call
|
|
await trackApiUsage({
|
|
userId: user.id,
|
|
apiType: 'google_search',
|
|
endpoint: 'customsearch/v1',
|
|
success: true
|
|
});
|
|
|
|
// Deduct credits after successful API call
|
|
const creditDeduction = await deductCredits(user.id, 'google_search', 'Whiskybase search');
|
|
if (!creditDeduction.success) {
|
|
console.error('Failed to deduct credits:', creditDeduction.error);
|
|
// Don't fail the search if credit deduction fails
|
|
}
|
|
|
|
if (!data.items || data.items.length === 0) {
|
|
return {
|
|
success: false,
|
|
error: `Keine Treffer auf Whiskybase gefunden. (Query: ${q})`
|
|
};
|
|
}
|
|
|
|
// Try to find the first result that looks like a valid product page
|
|
// Pattern matches: https://www.whiskybase.com/whiskies/whisky/12345/name
|
|
const wbRegex = /\/whisky\/(\d+)\//;
|
|
|
|
for (const item of data.items) {
|
|
const link = item.link;
|
|
const match = link.match(wbRegex);
|
|
|
|
if (match && match[1]) {
|
|
return {
|
|
success: true,
|
|
id: match[1],
|
|
url: link,
|
|
title: item.title
|
|
};
|
|
}
|
|
}
|
|
|
|
return { success: false, error: 'Konnte keine gültige Whiskybase-ID im Suchergebnis finden.' };
|
|
} catch (error) {
|
|
console.error('Whiskybase Discovery Error:', error);
|
|
// Track failed attempt (if not already tracked)
|
|
if (user) {
|
|
await trackApiUsage({
|
|
userId: user.id,
|
|
apiType: 'google_search',
|
|
endpoint: 'customsearch/v1',
|
|
success: false,
|
|
errorMessage: error instanceof Error ? error.message : 'Unknown error'
|
|
});
|
|
}
|
|
return {
|
|
success: false,
|
|
error: error instanceof Error ? error.message : 'Fehler bei der Suche auf Whiskybase.'
|
|
};
|
|
}
|
|
}
|