feat(tutorials): add video training academy management with YouTube parser and live preview
This commit is contained in:
+13
-8
@@ -10,19 +10,24 @@ export const { handlers, auth, signIn, signOut } = NextAuth({
|
||||
password: { label: "Password", type: "password" }
|
||||
},
|
||||
async authorize(credentials) {
|
||||
// Boilerplate mock logic
|
||||
// TODO: In production, lookup user in Prisma and verify password using bcrypt
|
||||
// const user = await db.user.findUnique({ where: { email: credentials.email } })
|
||||
|
||||
if (credentials?.email === "admin@ayris.tech" && credentials?.password === "admin") {
|
||||
// Gerçek AyrisLegal kullanıcı sistemine (Supabase Auth) bağlı DEĞİL —
|
||||
// admin paneli girişi bilerek ayrı, basit bir env-tabanlı kontrol.
|
||||
// ADMIN_EMAIL / ADMIN_PASSWORD .env dosyasında tanımlı, sadece
|
||||
// server-side (bu fonksiyon) okunuyor, hiçbir zaman client'a gitmiyor.
|
||||
const adminEmail = process.env.ADMIN_EMAIL
|
||||
const adminPassword = process.env.ADMIN_PASSWORD
|
||||
|
||||
if (!adminEmail || !adminPassword) return null
|
||||
|
||||
if (credentials?.email === adminEmail && credentials?.password === adminPassword) {
|
||||
return {
|
||||
id: "1",
|
||||
name: "Admin User",
|
||||
email: "admin@ayris.tech",
|
||||
name: "Admin",
|
||||
email: adminEmail,
|
||||
role: "ADMIN"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return null
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
import { PrismaClient } from '@prisma/client'
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma: PrismaClient | undefined
|
||||
}
|
||||
|
||||
export const db = globalForPrisma.prisma ?? new PrismaClient()
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = db
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user