242 lines
8.1 KiB
TypeScript
242 lines
8.1 KiB
TypeScript
'use server';
|
|
|
|
import { createClient } from '@/lib/supabase/server';
|
|
import { checkIsAdmin } from './track-api-usage';
|
|
import { addCredits, getUserCredits } from './credit-service';
|
|
import { AdminCreditUpdateSchema, AdminSettingsSchema } from '@/types/whisky';
|
|
|
|
interface UserWithCredits {
|
|
id: string;
|
|
email: string;
|
|
username: string;
|
|
balance: number;
|
|
total_purchased: number;
|
|
total_used: number;
|
|
daily_limit: number | null;
|
|
google_search_cost: number;
|
|
gemini_ai_cost: number;
|
|
last_active?: string;
|
|
}
|
|
|
|
/**
|
|
* Get all users with their credit information (admin only)
|
|
*/
|
|
export async function getAllUsersWithCredits(): Promise<UserWithCredits[]> {
|
|
try {
|
|
const supabase = await createClient();
|
|
|
|
// Check if current user is admin
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) {
|
|
console.log('[getAllUsersWithCredits] No user found');
|
|
return [];
|
|
}
|
|
|
|
const isAdmin = await checkIsAdmin(user.id);
|
|
if (!isAdmin) {
|
|
console.log('[getAllUsersWithCredits] User is not admin');
|
|
return [];
|
|
}
|
|
|
|
console.log('[getAllUsersWithCredits] Fetching profiles...');
|
|
// Get all users with their profiles
|
|
const { data: profiles, error: profilesError } = await supabase
|
|
.from('profiles')
|
|
.select('id, username');
|
|
|
|
if (profilesError) {
|
|
console.error('Error fetching profiles:', profilesError);
|
|
return [];
|
|
}
|
|
|
|
console.log('[getAllUsersWithCredits] Found profiles:', profiles?.length);
|
|
|
|
// Get all user credits
|
|
const { data: credits, error: creditsError } = await supabase
|
|
.from('user_credits')
|
|
.select('*');
|
|
|
|
if (creditsError) {
|
|
console.error('Error fetching credits:', creditsError);
|
|
}
|
|
|
|
console.log('[getAllUsersWithCredits] Found credits:', credits?.length);
|
|
|
|
// Combine data - we'll use profile id as email fallback
|
|
const usersWithCredits: UserWithCredits[] = profiles?.map(profile => {
|
|
const userCredits = credits?.find(c => c.user_id === profile.id);
|
|
|
|
return {
|
|
id: profile.id,
|
|
email: profile.id.substring(0, 8) + '...', // Show partial ID as placeholder
|
|
username: profile.username || 'Unknown',
|
|
balance: userCredits?.balance || 0,
|
|
total_purchased: userCredits?.total_purchased || 0,
|
|
total_used: userCredits?.total_used || 0,
|
|
daily_limit: userCredits?.daily_limit || null,
|
|
google_search_cost: userCredits?.google_search_cost || 1,
|
|
gemini_ai_cost: userCredits?.gemini_ai_cost || 1,
|
|
last_active: undefined
|
|
};
|
|
}) || [];
|
|
|
|
console.log('[getAllUsersWithCredits] Returning users:', usersWithCredits.length);
|
|
return usersWithCredits;
|
|
} catch (err) {
|
|
console.error('Error in getAllUsersWithCredits:', err);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update user's credit balance (admin only)
|
|
*/
|
|
export async function updateUserCredits(
|
|
userId: string,
|
|
newBalance: number,
|
|
reason: string
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
const validated = AdminCreditUpdateSchema.parse({ userId, newBalance, reason });
|
|
const supabase = await createClient();
|
|
|
|
// Check if current user is admin
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) return { success: false, error: 'Not authenticated' };
|
|
|
|
const isAdmin = await checkIsAdmin(user.id);
|
|
if (!isAdmin) return { success: false, error: 'Not authorized' };
|
|
|
|
// Get current credits
|
|
const currentCredits = await getUserCredits(validated.userId);
|
|
if (!currentCredits) {
|
|
return { success: false, error: 'User credits not found' };
|
|
}
|
|
|
|
const difference = validated.newBalance - currentCredits.balance;
|
|
|
|
// Use addCredits which handles the transaction logging
|
|
const result = await addCredits(validated.userId, difference, validated.reason, user.id);
|
|
|
|
return result;
|
|
} catch (err) {
|
|
console.error('Error in updateUserCredits:', err);
|
|
return { success: false, error: err instanceof Error ? err.message : 'Failed to update credits' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set user's daily limit (admin only)
|
|
*/
|
|
export async function setUserDailyLimit(
|
|
userId: string,
|
|
dailyLimit: number | null
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
const validated = AdminSettingsSchema.parse({ userId, dailyLimit });
|
|
const supabase = await createClient();
|
|
|
|
// Check if current user is admin
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) return { success: false, error: 'Not authenticated' };
|
|
|
|
const isAdmin = await checkIsAdmin(user.id);
|
|
if (!isAdmin) return { success: false, error: 'Not authorized' };
|
|
|
|
const { error } = await supabase
|
|
.from('user_credits')
|
|
.update({ daily_limit: validated.dailyLimit })
|
|
.eq('user_id', validated.userId);
|
|
|
|
if (error) {
|
|
console.error('Error setting daily limit:', error);
|
|
return { success: false, error: 'Failed to set daily limit' };
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (err) {
|
|
console.error('Error in setUserDailyLimit:', err);
|
|
return { success: false, error: err instanceof Error ? err.message : 'Failed to set daily limit' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set user's API costs (admin only)
|
|
*/
|
|
export async function setUserApiCosts(
|
|
userId: string,
|
|
googleSearchCost: number,
|
|
geminiAiCost: number
|
|
): Promise<{ success: boolean; error?: string }> {
|
|
try {
|
|
const validated = AdminSettingsSchema.parse({ userId, googleSearchCost, geminiAiCost });
|
|
const supabase = await createClient();
|
|
|
|
// Check if current user is admin
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) return { success: false, error: 'Not authenticated' };
|
|
|
|
const isAdmin = await checkIsAdmin(user.id);
|
|
if (!isAdmin) return { success: false, error: 'Not authorized' };
|
|
|
|
const { error } = await supabase
|
|
.from('user_credits')
|
|
.update({
|
|
google_search_cost: validated.googleSearchCost,
|
|
gemini_ai_cost: validated.geminiAiCost
|
|
})
|
|
.eq('user_id', validated.userId);
|
|
|
|
if (error) {
|
|
console.error('Error setting API costs:', error);
|
|
return { success: false, error: 'Failed to set API costs' };
|
|
}
|
|
|
|
return { success: true };
|
|
} catch (err) {
|
|
console.error('Error in setUserApiCosts:', err);
|
|
return { success: false, error: err instanceof Error ? err.message : 'Failed to set API costs' };
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bulk add credits to multiple users (admin only)
|
|
*/
|
|
export async function bulkAddCredits(
|
|
userIds: string[],
|
|
amount: number,
|
|
reason: string
|
|
): Promise<{ success: boolean; processed: number; failed: number; error?: string }> {
|
|
try {
|
|
const supabase = await createClient();
|
|
|
|
// Check if current user is admin
|
|
const { data: { user } } = await supabase.auth.getUser();
|
|
if (!user) return { success: false, processed: 0, failed: 0, error: 'Not authenticated' };
|
|
|
|
const isAdmin = await checkIsAdmin(user.id);
|
|
if (!isAdmin) return { success: false, processed: 0, failed: 0, error: 'Not authorized' };
|
|
|
|
let processed = 0;
|
|
let failed = 0;
|
|
|
|
for (const userId of userIds) {
|
|
const result = await addCredits(userId, amount, reason, user.id);
|
|
if (result.success) {
|
|
processed++;
|
|
} else {
|
|
failed++;
|
|
}
|
|
}
|
|
|
|
return {
|
|
success: true,
|
|
processed,
|
|
failed
|
|
};
|
|
} catch (err) {
|
|
console.error('Error in bulkAddCredits:', err);
|
|
return { success: false, processed: 0, failed: 0, error: 'Failed to bulk add credits' };
|
|
}
|
|
}
|