feat: implement API usage tracking and admin dashboard
- Added database schema for API tracking system: * api_usage table - tracks all Google Search and Gemini AI calls * user_credits table - prepared for future credits system * admin_users table - controls admin dashboard access - Created comprehensive tracking service (track-api-usage.ts): * trackApiUsage() - records API calls with success/failure * checkDailyLimit() - enforces 80 Google Search calls/day limit * getUserApiStats() - per-user statistics * getGlobalApiStats() - app-wide statistics (admin only) * checkIsAdmin() - server-side authorization - Integrated tracking into discover-whiskybase.ts: * Pre-call limit checking with friendly error messages * Post-call usage tracking for success and failures * User authentication verification - Built admin dashboard at /admin: * Global statistics cards (total, today, by API type) * Top 10 users by API usage * Recent activity log with 50 latest calls * Color-coded status indicators * Secure access with RLS policies - Features: * Daily limit resets at midnight Europe/Berlin timezone * Graceful error handling (allows on tracking failure) * Comprehensive indexes for fast queries * Ready for future credits/monetization system
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
'use server';
|
||||
|
||||
import { trackApiUsage, checkDailyLimit } from './track-api-usage';
|
||||
import { createServerComponentClient } from '@supabase/auth-helpers-nextjs';
|
||||
import { cookies } from 'next/headers';
|
||||
|
||||
/**
|
||||
* 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.
|
||||
@@ -24,6 +28,27 @@ export async function discoverWhiskybaseId(bottle: {
|
||||
};
|
||||
}
|
||||
|
||||
// Get current user for tracking
|
||||
const supabase = createServerComponentClient({ cookies });
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Construct targeted search query
|
||||
const queryParts = [
|
||||
@@ -46,9 +71,25 @@ export async function discoverWhiskybaseId(bottle: {
|
||||
|
||||
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
|
||||
});
|
||||
|
||||
if (!data.items || data.items.length === 0) {
|
||||
return {
|
||||
success: false,
|
||||
@@ -77,6 +118,16 @@ export async function discoverWhiskybaseId(bottle: {
|
||||
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.'
|
||||
|
||||
Reference in New Issue
Block a user