diff --git a/actions/admin.ts b/actions/admin.ts index 4db6309..e7f3842 100644 --- a/actions/admin.ts +++ b/actions/admin.ts @@ -1,8 +1,7 @@ 'use server'; -import { createServerClient } from '@supabase/ssr'; import { createClient } from '@supabase/supabase-js'; -import { cookies } from 'next/headers'; +import { requireAdmin } from '@/lib/auth'; async function getAdminSupabase() { const url = process.env.NEXT_PUBLIC_SUPABASE_URL!; @@ -14,42 +13,15 @@ async function getAdminSupabase() { } async function assertAdmin() { - const cookieStore = await cookies(); - const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { get: (name) => cookieStore.get(name)?.value }, - } - ); - - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) throw new Error('Unauthorized'); - - const { data: profile } = await supabase - .from('users') - .select('user_role') - .eq('id', session.user.id) - .maybeSingle(); - - const role = profile?.user_role; - const isAdmin = - role === 'admin' || - role === 'superadmin' || - session.user.email === 'deepaksharma834@gmail.com'; - - if (!isAdmin) throw new Error('Forbidden'); + await requireAdmin(); } // Table mapping based on experience type -const TABLE_MAP: Record = { +const TABLE_MAP = { user: 'new_interview', scraped: 'scraped_experiences', legacy: 'experiences', -}; +} as const satisfies Record; export async function deleteExperience( rawId: string, diff --git a/app/admin/page.tsx b/app/admin/page.tsx index cf8cc3f..f6e058e 100644 --- a/app/admin/page.tsx +++ b/app/admin/page.tsx @@ -1,8 +1,7 @@ import AdminDashboard from '@/components/admin-dashboard'; -import { createServerClient } from '@supabase/ssr'; -import { cookies } from 'next/headers'; import type { Metadata } from 'next'; import { redirect } from 'next/navigation'; +import { getAuthState } from '@/lib/auth'; export const metadata: Metadata = { title: 'Admin Panel | Frontend Junction', @@ -11,37 +10,9 @@ export const metadata: Metadata = { }; export default async function AdminPage() { - const cookieStore = await cookies(); - const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - get(name: string) { - return cookieStore.get(name)?.value; - }, - }, - } - ); + const { isAdmin } = await getAuthState(); - const { - data: { session }, - } = await supabase.auth.getSession(); - - if (!session) { - redirect('/'); - } - - const { data: userProfile } = await supabase - .from('users') - .select('user_role') - .eq('id', session.user.id) - .maybeSingle(); - - if ( - userProfile?.user_role !== 'admin' && - userProfile?.user_role !== 'superadmin' - ) { + if (!isAdmin) { redirect('/'); } diff --git a/app/api/admin/stats/route.ts b/app/api/admin/stats/route.ts index d494026..b6a69e1 100644 --- a/app/api/admin/stats/route.ts +++ b/app/api/admin/stats/route.ts @@ -1,35 +1,10 @@ -import { createServerClient } from '@supabase/ssr'; -import { cookies } from 'next/headers'; import { NextResponse } from 'next/server'; +import { requireAdmin, AuthError } from '@/lib/auth'; export async function GET() { try { - const cookieStore = await cookies(); - const supabase = createServerClient( - process.env.NEXT_PUBLIC_SUPABASE_URL!, - process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!, - { - cookies: { - get(name: string) { - return cookieStore.get(name)?.value; - }, - }, - } - ); - - // 1. Check if user is logged in - const { - data: { session }, - } = await supabase.auth.getSession(); - if (!session) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); - } - - // 2. Check for Admin role (Hardcoded for current MVP, same as session-provider) - const isAdmin = session.user.email === 'deepaksharma834@gmail.com'; - if (!isAdmin) { - return NextResponse.json({ error: 'Forbidden' }, { status: 403 }); - } + // Consistent admin gate (dual rule, verified user) + await requireAdmin(); const url = process.env.NEXT_PUBLIC_SUPABASE_URL; const key = process.env.SUPABASE_SERVICE_ROLE_KEY; @@ -102,6 +77,9 @@ export async function GET() { successRate, }); } catch (error: any) { + if (error instanceof AuthError) { + return NextResponse.json({ error: error.message }, { status: error.status }); + } console.error('[AdminStatsAPI] Error:', error); return NextResponse.json({ error: error.message }, { status: 500 }); } diff --git a/app/api/auth/me/route.ts b/app/api/auth/me/route.ts new file mode 100644 index 0000000..cbdb80b --- /dev/null +++ b/app/api/auth/me/route.ts @@ -0,0 +1,14 @@ +import { NextResponse } from 'next/server'; +import { getAuthState } from '@/lib/auth'; + +// Never cache — this reflects per-request session state. +export const dynamic = 'force-dynamic'; +export const revalidate = 0; + +export async function GET() { + const { isAdmin, role } = await getAuthState(); + return NextResponse.json( + { isAdmin, role: role ?? null }, + { headers: { 'Cache-Control': 'no-store, max-age=0' } } + ); +} diff --git a/app/layout.tsx b/app/layout.tsx index 7d1983e..d6d2832 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -46,6 +46,9 @@ export const metadata: Metadata = { publisher: 'Frontend Junction', alternates: { canonical: '/', + types: { + 'application/rss+xml': '/feed.xml', + }, }, formatDetection: { email: false, @@ -122,14 +125,14 @@ export default function RootLayout({ {process.env.NEXT_GOOGLE_ANALYTICS && (