117 lines
4.2 KiB
TypeScript
117 lines
4.2 KiB
TypeScript
|
|
import { serve } from "https://deno.land/std@0.168.0/http/server.ts";
|
|
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
|
|
|
|
const corsHeaders = {
|
|
'Access-Control-Allow-Origin': '*',
|
|
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
|
};
|
|
|
|
serve(async (req) => {
|
|
// Handle CORS preflight requests
|
|
if (req.method === 'OPTIONS') {
|
|
return new Response('ok', { headers: corsHeaders });
|
|
}
|
|
|
|
try {
|
|
// 1. Initialize Supabase Client with the incoming user's Auth context
|
|
// This allows us to use `auth.getUser()` securely based on the JWT sent by the frontend.
|
|
const supabaseClient = createClient(
|
|
Deno.env.get('SUPABASE_URL') ?? '',
|
|
Deno.env.get('SUPABASE_ANON_KEY') ?? '',
|
|
{
|
|
global: {
|
|
headers: { Authorization: req.headers.get('Authorization')! },
|
|
},
|
|
}
|
|
);
|
|
|
|
// 2. Get User from Token
|
|
const {
|
|
data: { user },
|
|
error: authError,
|
|
} = await supabaseClient.auth.getUser();
|
|
|
|
if (authError || !user) {
|
|
return new Response(
|
|
JSON.stringify({ allowed: false, error: 'Unauthorized', reason: 'auth_failed' }),
|
|
{ status: 401, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
);
|
|
}
|
|
|
|
// 3. Check Entitlements (Active Plan?)
|
|
// We look for the most recent entitlement that is active.
|
|
const { data: entitlement, error: entError } = await supabaseClient
|
|
.from('user_entitlements')
|
|
.select('is_active, valid_until, entitlement_code')
|
|
.eq('user_id', user.id)
|
|
.order('valid_until', { ascending: false })
|
|
.maybeSingle();
|
|
|
|
if (entError) {
|
|
console.error("Entitlement check error:", entError);
|
|
}
|
|
|
|
// A plan is active if is_active=true AND (valid_until is NULL (lifetime) OR valid_until > now)
|
|
const isActive = entitlement?.is_active && (!entitlement.valid_until || new Date(entitlement.valid_until) > new Date());
|
|
|
|
if (isActive) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
allowed: true,
|
|
plan: entitlement.entitlement_code,
|
|
reason: 'plan_active',
|
|
quota_remaining: -1 // Infinite/Plan
|
|
}),
|
|
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
);
|
|
}
|
|
|
|
// 4. Check Free Quota (Coach Analyses)
|
|
// Counts how many analyses already consumed the free quota.
|
|
const { count, error: countError } = await supabaseClient
|
|
.from('coach_analyses')
|
|
.select('*', { count: 'exact', head: true })
|
|
.eq('user_id', user.id)
|
|
.eq('used_free_quota', true);
|
|
|
|
if (countError) {
|
|
console.error("Quota check error:", countError);
|
|
throw new Error("Failed to check quota usage.");
|
|
}
|
|
|
|
const FREE_LIMIT = 3; // Defined limit for Coach
|
|
const used = count || 0;
|
|
const remaining = Math.max(0, FREE_LIMIT - used);
|
|
|
|
if (remaining > 0) {
|
|
return new Response(
|
|
JSON.stringify({
|
|
allowed: true,
|
|
plan: 'free',
|
|
reason: 'free_quota',
|
|
quota_remaining: remaining
|
|
}),
|
|
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
);
|
|
}
|
|
|
|
// 5. Quota Exceeded
|
|
return new Response(
|
|
JSON.stringify({
|
|
allowed: false,
|
|
plan: 'free',
|
|
reason: 'quota_exceeded',
|
|
quota_remaining: 0
|
|
}),
|
|
{ headers: { ...corsHeaders, 'Content-Type': 'application/json' } } // 200 OK because logic was successful, just access denied
|
|
);
|
|
|
|
} catch (error: any) {
|
|
console.error("Validate Access Error:", error);
|
|
return new Response(
|
|
JSON.stringify({ allowed: false, error: error.message }),
|
|
{ status: 400, headers: { ...corsHeaders, 'Content-Type': 'application/json' } }
|
|
);
|
|
}
|
|
});
|