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:
2025-12-18 13:56:21 +01:00
parent 334bece471
commit dd27cfe0e7
4 changed files with 536 additions and 0 deletions

View File

@@ -229,3 +229,65 @@ CREATE POLICY "Allow authenticated users to view cache"
ON vision_cache FOR SELECT
TO authenticated
USING (true);
-- ============================================
-- API Usage Tracking & Credits System
-- ============================================
-- API Usage tracking table
CREATE TABLE IF NOT EXISTS api_usage (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE,
api_type TEXT NOT NULL CHECK (api_type IN ('google_search', 'gemini_ai')),
endpoint TEXT,
success BOOLEAN DEFAULT true,
error_message TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('Europe/Berlin'::text, now())
);
CREATE INDEX idx_api_usage_user_id ON api_usage(user_id);
CREATE INDEX idx_api_usage_api_type ON api_usage(api_type);
CREATE INDEX idx_api_usage_created_at ON api_usage(created_at);
CREATE INDEX idx_api_usage_user_date ON api_usage(user_id, DATE(created_at));
-- User credits table (for future credits system)
CREATE TABLE IF NOT EXISTS user_credits (
user_id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
balance INTEGER DEFAULT 0,
total_purchased INTEGER DEFAULT 0,
total_used INTEGER DEFAULT 0,
updated_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('Europe/Berlin'::text, now())
);
-- Admin users table
CREATE TABLE IF NOT EXISTS admin_users (
user_id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
role TEXT DEFAULT 'admin' CHECK (role IN ('admin', 'super_admin')),
created_at TIMESTAMP WITH TIME ZONE DEFAULT timezone('Europe/Berlin'::text, now())
);
-- Enable RLS for API tracking tables
ALTER TABLE api_usage ENABLE ROW LEVEL SECURITY;
ALTER TABLE user_credits ENABLE ROW LEVEL SECURITY;
ALTER TABLE admin_users ENABLE ROW LEVEL SECURITY;
-- Policies for api_usage (users can view their own, admins can view all)
CREATE POLICY "Users can view their own API usage" ON api_usage FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Admins can view all API usage" ON api_usage FOR SELECT USING (
EXISTS (SELECT 1 FROM admin_users WHERE user_id = auth.uid())
);
CREATE POLICY "System can insert API usage" ON api_usage FOR INSERT WITH CHECK (true);
-- Policies for user_credits
CREATE POLICY "Users can view their own credits" ON user_credits FOR SELECT USING (auth.uid() = user_id);
CREATE POLICY "Admins can view all credits" ON user_credits FOR SELECT USING (
EXISTS (SELECT 1 FROM admin_users WHERE user_id = auth.uid())
);
-- Policies for admin_users (only admins can view)
CREATE POLICY "Admins can view admin users" ON admin_users FOR SELECT USING (
EXISTS (SELECT 1 FROM admin_users WHERE user_id = auth.uid())
);
-- Note: To add robin as admin, run this after getting the user_id:
-- INSERT INTO admin_users (user_id, role) VALUES ('<robin_user_id>', 'super_admin');