Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions autopilot-docs/app/api/leaderboard/sync/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UserStats } from '@/lib/leaderboard';
export async function POST(request: Request) {
try {
const body = await request.json();

// Basic validation
if (!body.id || !body.username) {
return NextResponse.json({ error: 'Missing required fields' }, { status: 400 });
Expand Down Expand Up @@ -34,22 +34,31 @@ export async function POST(request: Request) {
const { error: upsertError } = await supabase
.from('leaderboard')
.upsert(row, { onConflict: 'id' });

if (upsertError) {
return NextResponse.json({ error: 'Failed to sync leaderboard' }, { status: 500 });
console.error('Supabase upsert error:', upsertError);
return NextResponse.json({
error: 'Failed to sync leaderboard',
details: upsertError.message,
hint: 'Check if RLS policies allow upsert or if SUPABASE_SERVICE_ROLE_KEY is missing.'
}, { status: 500 });
}

const { data: all, error: fetchError } = await supabase
.from('leaderboard')
.select('id,score')
.order('score', { ascending: false })
.limit(100);

if (fetchError || !all) {
return NextResponse.json({ success: true });
console.warn('Leaderboard fetch error or empty:', fetchError);
return NextResponse.json({ success: true, rank: null });
}

const rank = (all.findIndex(u => u.id === stats.id) + 1) || null;
return NextResponse.json({ success: true, rank });
} catch (error) {
console.error('Failed to sync leaderboard:', error);
return NextResponse.json({ error: 'Internal server error' }, { status: 500 });
console.error('Failed to sync leaderboard (Catch):', error);
return NextResponse.json({ error: 'Internal server error', details: error instanceof Error ? error.message : String(error) }, { status: 500 });
}
}
6 changes: 6 additions & 0 deletions autopilot-docs/lib/supabase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,15 @@ export function getAnonClient(): SupabaseClient {

export function getServerClient(): SupabaseClient {
if (cachedServiceClient) return cachedServiceClient;

const url = process.env.NEXT_PUBLIC_SUPABASE_URL || '';
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
const anonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || '';

if (!serviceKey) {
console.warn('⚠️ SUPABASE_SERVICE_ROLE_KEY is missing. Falling back to ANON_KEY. Some operations may fail due to RLS.');
}

const key = serviceKey || anonKey;
cachedServiceClient = createClient(url, key);
return cachedServiceClient;
Expand Down
44 changes: 17 additions & 27 deletions autopilot-docs/supabase/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -7,26 +7,22 @@ create table if not exists public.leaderboard (
streak integer not null default 0,
last_active timestamptz not null default now()
);

create index if not exists idx_leaderboard_score on public.leaderboard (score desc);
create index if not exists idx_leaderboard_last_active on public.leaderboard (last_active desc);

alter table public.leaderboard enable row level security;
create policy "leaderboard_select_public" on public.leaderboard
for select
to anon, authenticated
using (true);
create policy "leaderboard_insert_service" on public.leaderboard
for insert
to service_role
with check (true);
create policy "leaderboard_update_service" on public.leaderboard
for update
to service_role
using (true)
with check (true);
grant select on public.leaderboard to anon, authenticated;

create policy "leaderboard_select_public" on public.leaderboard for
select to anon,
authenticated using (true);
create policy "leaderboard_insert_public" on public.leaderboard for
insert to anon,
authenticated,
service_role with check (true);
create policy "leaderboard_update_public" on public.leaderboard for
update to anon,
authenticated,
service_role using (true) with check (true);
grant select on public.leaderboard to anon,
authenticated;
create table if not exists public.events (
id bigserial primary key,
type text not null,
Expand All @@ -38,17 +34,11 @@ create table if not exists public.events (
retry_count integer,
received_at timestamptz not null default now()
);

create index if not exists idx_events_type on public.events (type);
create index if not exists idx_events_user on public.events (user_id);
create index if not exists idx_events_received_at on public.events (received_at desc);

alter table public.events enable row level security;
create policy "events_insert_service" on public.events
for insert
to service_role
with check (true);
create policy "events_select_service" on public.events
for select
to service_role
using (true);
create policy "events_insert_service" on public.events for
insert to service_role with check (true);
create policy "events_select_service" on public.events for
select to service_role using (true);
9 changes: 8 additions & 1 deletion src/commands/leaderboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,14 @@ async function syncLeaderboard(apiUrl, options) {
});

if (!response.ok) {
throw new Error(`Server responded with ${response.status}`);
let errorDetail = '';
try {
const errJson = await response.json();
errorDetail = errJson.details || errJson.error || '';
} catch (e) {
// Not a JSON error
}
throw new Error(`Server responded with ${response.status}${errorDetail ? ': ' + errorDetail : ''}`);
}

const data = await response.json();
Expand Down
Loading