| import { createClient } from '@supabase/supabase-js' |
| import { NextResponse } from 'next/server' |
|
|
| export const dynamic = 'force-dynamic' |
|
|
| |
| function getAdminClient() { |
| const url = process.env.NEXT_PUBLIC_SUPABASE_URL |
| const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY |
| if (!url || !serviceKey) { |
| throw new Error('Supabase credentials missing') |
| } |
| return createClient(url, serviceKey, { |
| auth: { autoRefreshToken: false, persistSession: false } |
| }) |
| } |
|
|
| |
| async function verifyAdmin(request: Request): Promise<{ isAdmin: boolean; userId: string | null; error?: string }> { |
| try { |
| const authHeader = request.headers.get('authorization') |
| if (!authHeader) { |
| return { isAdmin: false, userId: null, error: 'Authorization header eksik' } |
| } |
|
|
| const token = authHeader.replace('Bearer ', '') |
| const admin = getAdminClient() |
| const { data: { user }, error } = await admin.auth.getUser(token) |
|
|
| if (error || !user) { |
| return { isAdmin: false, userId: null, error: 'Geçersiz token' } |
| } |
|
|
| |
| const { data: profile, error: profileError } = await admin |
| .from('user_profiles') |
| .select('is_admin') |
| .eq('id', user.id) |
| .single() |
|
|
| if (!profile?.is_admin) { |
| return { isAdmin: false, userId: user.id, error: 'Admin yetkisi yok' } |
| } |
|
|
| return { isAdmin: true, userId: user.id } |
| } catch { |
| return { isAdmin: false, userId: null, error: 'Doğrulama hatası' } |
| } |
| } |
|
|
| |
| export async function GET(request: Request) { |
| const auth = await verifyAdmin(request) |
| if (!auth.isAdmin) { |
| return NextResponse.json({ error: auth.error }, { status: 403 }) |
| } |
|
|
| const { searchParams } = new URL(request.url) |
| const action = searchParams.get('action') || 'dashboard' |
| const admin = getAdminClient() |
|
|
| try { |
| if (action === 'dashboard') { |
| |
| const [usersRes, stocksRes, newsRes, predictionsRes, settingsRes, logsRes] = await Promise.all([ |
| admin.auth.admin.listUsers({ perPage: 1000 }), |
| admin.from('stocks').select('id', { count: 'exact', head: true }), |
| admin.from('news_articles').select('id', { count: 'exact', head: true }), |
| admin.from('ml_predictions').select('id', { count: 'exact', head: true }), |
| admin.from('app_settings').select('*'), |
| admin.from('admin_logs').select('*').order('created_at', { ascending: false }).limit(20), |
| ]) |
|
|
| const users = usersRes.data?.users || [] |
| const now = new Date() |
| const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1000) |
| const last7d = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) |
|
|
| const recentUsers = users.filter(u => new Date(u.created_at) > last7d) |
| const activeUsers = users.filter(u => u.last_sign_in_at && new Date(u.last_sign_in_at) > last24h) |
|
|
| return NextResponse.json({ |
| stats: { |
| totalUsers: users.length, |
| recentUsers: recentUsers.length, |
| activeUsersLast24h: activeUsers.length, |
| totalStocks: stocksRes.count || 0, |
| totalNews: newsRes.count || 0, |
| totalPredictions: predictionsRes.count || 0, |
| }, |
| settings: settingsRes.data || [], |
| logs: logsRes.data || [], |
| }) |
| } |
|
|
| if (action === 'users') { |
| const { data: { users }, error } = await admin.auth.admin.listUsers({ perPage: 1000 }) |
| if (error) throw error |
|
|
| |
| const { data: profiles } = await admin.from('user_profiles').select('id, is_admin, is_banned, display_name, last_login_at, login_count') |
|
|
| const profileMap = new Map((profiles || []).map(p => [p.id, p])) |
|
|
| const enrichedUsers = users.map(u => ({ |
| id: u.id, |
| email: u.email, |
| created_at: u.created_at, |
| last_sign_in_at: u.last_sign_in_at, |
| email_confirmed_at: u.email_confirmed_at, |
| is_admin: profileMap.get(u.id)?.is_admin || false, |
| is_banned: profileMap.get(u.id)?.is_banned || false, |
| display_name: profileMap.get(u.id)?.display_name || null, |
| login_count: profileMap.get(u.id)?.login_count || 0, |
| })) |
|
|
| return NextResponse.json({ users: enrichedUsers }) |
| } |
|
|
| if (action === 'settings') { |
| const { data, error } = await admin.from('app_settings').select('*') |
| if (error) throw error |
| return NextResponse.json({ settings: data }) |
| } |
|
|
| if (action === 'logs') { |
| const limit = parseInt(searchParams.get('limit') || '50', 10) |
| const { data, error } = await admin |
| .from('admin_logs') |
| .select('*') |
| .order('created_at', { ascending: false }) |
| .limit(limit) |
| if (error) throw error |
| return NextResponse.json({ logs: data }) |
| } |
|
|
| return NextResponse.json({ error: 'Geçersiz aksiyon' }, { status: 400 }) |
| } catch (e: unknown) { |
| return NextResponse.json({ error: e instanceof Error ? e.message : 'Sunucu hatası' }, { status: 500 }) |
| } |
| } |
|
|
| |
| export async function POST(request: Request) { |
| const auth = await verifyAdmin(request) |
| if (!auth.isAdmin) { |
| return NextResponse.json({ error: auth.error }, { status: 403 }) |
| } |
|
|
| const admin = getAdminClient() |
| const body = await request.json() |
| const { action } = body |
|
|
| try { |
| |
| const logAction = async (actionName: string, targetType: string, targetId: string, details: Record<string, unknown>) => { |
| await admin.from('admin_logs').insert({ |
| admin_id: auth.userId, |
| action: actionName, |
| target_type: targetType, |
| target_id: targetId, |
| details, |
| }) |
| } |
|
|
| |
| const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i |
|
|
| |
| if (action === 'ban_user') { |
| const { userId } = body |
| if (!userId || !UUID_REGEX.test(userId)) { |
| return NextResponse.json({ error: 'Geçersiz userId formatı (UUID bekleniyor)' }, { status: 400 }) |
| } |
| |
| await admin.from('user_profiles').upsert( |
| { id: userId, is_banned: true }, |
| { onConflict: 'id' } |
| ) |
| await logAction('ban_user', 'user', userId, { banned: true }) |
| return NextResponse.json({ success: true, message: 'Kullanıcı engellendi' }) |
| } |
|
|
| if (action === 'unban_user') { |
| const { userId } = body |
| if (!userId || !UUID_REGEX.test(userId)) { |
| return NextResponse.json({ error: 'Geçersiz userId formatı (UUID bekleniyor)' }, { status: 400 }) |
| } |
| await admin.from('user_profiles').upsert( |
| { id: userId, is_banned: false }, |
| { onConflict: 'id' } |
| ) |
| await logAction('unban_user', 'user', userId, { banned: false }) |
| return NextResponse.json({ success: true, message: 'Engel kaldırıldı' }) |
| } |
|
|
| if (action === 'make_admin') { |
| const { userId } = body |
| if (!userId || !UUID_REGEX.test(userId)) { |
| return NextResponse.json({ error: 'Geçersiz userId formatı (UUID bekleniyor)' }, { status: 400 }) |
| } |
| await admin.from('user_profiles').upsert( |
| { id: userId, is_admin: true }, |
| { onConflict: 'id' } |
| ) |
| await logAction('make_admin', 'user', userId, { admin: true }) |
| return NextResponse.json({ success: true, message: 'Admin yetkisi verildi' }) |
| } |
|
|
| if (action === 'remove_admin') { |
| const { userId } = body |
| if (!userId || !UUID_REGEX.test(userId)) { |
| return NextResponse.json({ error: 'Geçersiz userId formatı (UUID bekleniyor)' }, { status: 400 }) |
| } |
| if (userId === auth.userId) { |
| return NextResponse.json({ error: 'Kendi admin yetkinizi kaldıramazsınız' }, { status: 400 }) |
| } |
| await admin.from('user_profiles').upsert( |
| { id: userId, is_admin: false }, |
| { onConflict: 'id' } |
| ) |
| await logAction('remove_admin', 'user', userId, { admin: false }) |
| return NextResponse.json({ success: true, message: 'Admin yetkisi kaldırıldı' }) |
| } |
|
|
| if (action === 'delete_user') { |
| const { userId } = body |
| if (!userId || !UUID_REGEX.test(userId)) { |
| return NextResponse.json({ error: 'Geçersiz userId formatı (UUID bekleniyor)' }, { status: 400 }) |
| } |
| if (userId === auth.userId) { |
| return NextResponse.json({ error: 'Kendi hesabınızı silemezsiniz' }, { status: 400 }) |
| } |
| await admin.auth.admin.deleteUser(userId) |
| await logAction('delete_user', 'user', userId, {}) |
| return NextResponse.json({ success: true, message: 'Kullanıcı silindi' }) |
| } |
|
|
| if (action === 'reset_password') { |
| const { userId, email } = body |
| if (!userId || !UUID_REGEX.test(userId)) { |
| return NextResponse.json({ error: 'Geçersiz userId formatı (UUID bekleniyor)' }, { status: 400 }) |
| } |
| if (!email || typeof email !== 'string' || !email.includes('@')) { |
| return NextResponse.json({ error: 'Geçerli bir email adresi gerekli' }, { status: 400 }) |
| } |
| |
| const { data, error } = await admin.auth.admin.generateLink({ |
| type: 'recovery', |
| email, |
| }) |
| if (error) throw error |
| await logAction('reset_password', 'user', userId, { email }) |
| |
| |
| return NextResponse.json({ success: true, message: 'Şifre sıfırlama linki oluşturuldu' }) |
| } |
|
|
| |
| if (action === 'update_setting') { |
| const { key, value } = body |
| const { error } = await admin.from('app_settings').upsert( |
| { key, value: JSON.stringify(value), updated_by: auth.userId, updated_at: new Date().toISOString() }, |
| { onConflict: 'key' } |
| ) |
| if (error) throw error |
| await logAction('update_setting', 'config', key, { value }) |
| return NextResponse.json({ success: true, message: `${key} güncellendi` }) |
| } |
|
|
| |
| if (action === 'send_announcement') { |
| const { message } = body |
| await admin.from('app_settings').upsert( |
| { key: 'announcement', value: JSON.stringify(message), updated_by: auth.userId, updated_at: new Date().toISOString() }, |
| { onConflict: 'key' } |
| ) |
| await logAction('announcement', 'system', 'announcement', { message }) |
| return NextResponse.json({ success: true, message: 'Duyuru yayınlandı' }) |
| } |
|
|
| return NextResponse.json({ error: 'Geçersiz aksiyon' }, { status: 400 }) |
| } catch (e: unknown) { |
| return NextResponse.json({ error: e instanceof Error ? e.message : 'Sunucu hatası' }, { status: 500 }) |
| } |
| } |
|
|