feat(tutorials): add video training academy management with YouTube parser and live preview

This commit is contained in:
mstfyldz
2026-08-22 13:49:00 +03:00
parent 2d149f1178
commit 7f690cc6d3
29 changed files with 2476 additions and 1034 deletions
+79
View File
@@ -0,0 +1,79 @@
import 'server-only';
import { createClient } from '@supabase/supabase-js';
const supabaseUrl = process.env.SUPABASE_URL;
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
if (!supabaseUrl || !serviceRoleKey) {
throw new Error('SUPABASE_URL / SUPABASE_SERVICE_ROLE_KEY tanımlı değil (.env dosyasına bakın).');
}
// service_role ile — RLS bypass edilir, admin panelinin ihtiyacı budur (licenses,
// crash_reports, access_logs gibi tablolarda bilerek hiç RLS policy yok, sadece
// service_role erişebiliyor). SADECE server component / route handler içinde
// import edilmeli — 'server-only' paketi bunu build-time'da garanti ediyor.
export const supabaseAdmin = createClient(supabaseUrl, serviceRoleKey, {
auth: { autoRefreshToken: false, persistSession: false },
});
export interface AuthUser {
id: string;
email?: string;
created_at: string;
last_sign_in_at?: string | null;
}
// profiles tablosunda e-posta yok (auth.users'a referans veriyor) — e-postayı
// Supabase Auth Admin API'den çekmemiz gerekiyor.
export async function listAuthUsers(): Promise<Map<string, AuthUser>> {
const res = await fetch(`${supabaseUrl}/auth/v1/admin/users?per_page=1000`, {
headers: { apikey: serviceRoleKey!, Authorization: `Bearer ${serviceRoleKey}` },
cache: 'no-store',
});
if (!res.ok) return new Map();
const data = await res.json();
const users: AuthUser[] = data.users || [];
return new Map(users.map((u) => [u.id, u]));
}
// Kullanıcı silme: Supabase Auth Admin API üzerinden. profiles satırını silmek
// admin'in sorumluluğunda ayrıca çağrılmalı — auth.users -> profiles cascade'i
// bu self-hosted kurulumda garanti değil.
export async function deleteAuthUser(id: string): Promise<{ error?: string }> {
const res = await fetch(`${supabaseUrl}/auth/v1/admin/users/${id}`, {
method: 'DELETE',
headers: { apikey: serviceRoleKey!, Authorization: `Bearer ${serviceRoleKey}` },
});
if (!res.ok) {
const text = await res.text().catch(() => '');
return { error: `Auth kullanıcı silinemedi (${res.status}): ${text}` };
}
return {};
}
export interface CustomerOption {
id: string;
name: string | null;
email: string;
}
// Lisans düzenleme/oluşturma formlarındaki "Müşteri" seçimi Kullanıcılar sayfasıyla
// AYNI kaynaktan (profiles + Supabase Auth e-postası) besleniyor — serbest metin
// yerine, gerçekten var olan bir kullanıcı seçtirmek için.
export async function listCustomerOptions(): Promise<CustomerOption[]> {
const [{ data: profiles }, authUsers] = await Promise.all([
supabaseAdmin.from('profiles').select('id, full_name'),
listAuthUsers(),
]);
const options: CustomerOption[] = (profiles || [])
.map((p: { id: string; full_name: string | null }) => ({
id: p.id,
name: p.full_name,
email: authUsers.get(p.id)?.email || '',
}))
.filter((c) => c.email);
options.sort((a, b) => (a.name || a.email).localeCompare(b.name || b.email, 'tr'));
return options;
}