import crypto from 'crypto';
import { supabase } from './supabase-client';
/**
* Returns the user_id for a given API key, or null if not found.
*/
export async function getUserIdFromApiKey(apiKey: string): Promise<string | null> {
const tokenHash = crypto.createHash('sha256').update(apiKey).digest('hex');
const { data, error } = await supabase
.from('api_tokens')
.select('user_id')
.eq('token_hash', tokenHash)
.maybeSingle();
if (error) {
console.error('Supabase error during user_id lookup:', error);
throw new Error('Internal error validating API key.');
}
return data?.user_id || null;
}
/**
* Returns the user's tool usage and allotment.
*/
export async function getUserToolAllotment(userId: string): Promise<number> {
const { data, error } = await supabase
.from('user_tool_usage')
.select('allotment')
.eq('user_id', userId)
.maybeSingle();
if (error) {
console.error('Supabase error during usage lookup:', error);
throw new Error('Internal error fetching usage.');
}
if (!data) {
// Default to 0 allotment if not found
return 0;
}
return data.allotment;
}
/**
* Atomically increments the user's tool usage by 1.
*/
export async function decrementUserToolAllotment(userId: string): Promise<void> {
const { error } = await supabase.rpc('increment_user_tool_usage', { user_id_input: userId });
if (error) {
console.error('Supabase error during allotment decrement:', error);
throw new Error('Internal error decrementing allotment.');
}
}
/**
* Checks if the user has remaining allotment and increments usage if allowed.
* Throws an error if the allotment is exhausted.
*/
export async function checkUserToolAllotment(apiKey: string): Promise<void> {
const userId = await getUserIdFromApiKey(apiKey);
if (!userId) throw new Error('Invalid API key.');
const allotment = await getUserToolAllotment(userId);
if (allotment <= 0) {
throw new Error('Tool call allotment exhausted. Please purchase more or contact support.');
}
}
/**
* Hashes the provided API key using SHA-256 and checks if the hash exists in the Supabase database.
* @param apiKey The raw API key provided by the user.
* @returns Promise<boolean> True if the hash exists, false otherwise.
*/
export async function isApiKeyValid(apiKey: string): Promise<boolean> {
const tokenHash = crypto.createHash('sha256').update(apiKey).digest('hex');
// Select id and revoked status
const { data, error } = await supabase
.from('api_tokens')
.select('id, revoked')
.eq('token_hash', tokenHash)
.maybeSingle();
if (error) {
// Log error for debugging, but do not leak details to the user
console.error('Supabase error during API key validation:', error);
throw new Error('Internal error validating API key.');
}
if (!data) {
return false;
}
if (data.revoked) {
throw new Error('This API key has been revoked. Please contact support if you believe this is a mistake.');
}
return true;
}