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
+89
View File
@@ -0,0 +1,89 @@
'use server'
import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { auth } from '@/lib/auth'
import { supabaseAdmin, deleteAuthUser } from '@/lib/supabaseAdmin'
async function requireAdmin() {
const session = await auth()
if (!session || (session.user as any)?.role !== 'ADMIN') {
throw new Error('Yetkisiz erişim')
}
}
function toIsoOrNull(dateInput: FormDataEntryValue | null): string | null {
const value = String(dateInput || '').trim()
if (!value) return null
const d = new Date(`${value}T23:59:59.999Z`)
if (isNaN(d.getTime())) return null
return d.toISOString()
}
// profiles.license_status masaüstü uygulamasının (laawos/Electron) TEK gerçek
// erişim kapısı (bkz. LicenseGate.tsx::fetchActiveStatus) ve laawos-backend'in
// API middleware'i de aynı alana bakıyor — bu yüzden bunu yazan HER yol (Kullanıcılar
// sayfası, Lisanslar sayfası) bu tek fonksiyondan geçmeli, yoksa iki ekran
// birbirinden habersiz farklı değerler yazıp "veriler eşleşmiyor" durumuna düşer.
export async function setProfileAccess(userId: string, status: string, expiresAt: string | null) {
await supabaseAdmin
.from('profiles')
.update({ license_status: status, license_expires_at: expiresAt })
.eq('id', userId)
}
export async function updateUserAction(formData: FormData) {
await requireAdmin()
const id = String(formData.get('id') || '')
if (!id) throw new Error('Kullanıcı id eksik')
const fullName = String(formData.get('full_name') || '').trim()
const barName = String(formData.get('bar_name') || '').trim()
const barNo = String(formData.get('bar_association_no') || '').trim()
const phone = String(formData.get('phone') || '').trim()
const licenseStatus = String(formData.get('license_status') || '').trim()
const licenseExpiresAt = toIsoOrNull(formData.get('license_expires_at'))
const trialEndDate = toIsoOrNull(formData.get('trial_end_date'))
const update: Record<string, unknown> = {
full_name: fullName || null,
bar_name: barName || null,
bar_association_no: barNo || null,
phone: phone || null,
trial_end_date: trialEndDate,
}
if (licenseStatus) {
// license_status + license_expires_at ikilisi setProfileAccess üzerinden yazılıyor,
// ki Lisanslar sayfasıyla aynı tek yoldan geçsin.
await setProfileAccess(id, licenseStatus, licenseExpiresAt)
} else {
update.license_expires_at = licenseExpiresAt
}
const { error } = await supabaseAdmin.from('profiles').update(update).eq('id', id)
if (error) {
redirect(`/admin/users/${id}?error=${encodeURIComponent(error.message)}`)
}
revalidatePath('/admin/users')
revalidatePath(`/admin/users/${id}`)
redirect(`/admin/users/${id}?success=1`)
}
export async function deleteUserAction(formData: FormData) {
await requireAdmin()
const id = String(formData.get('id') || '')
if (!id) throw new Error('Kullanıcı id eksik')
const { error: authError } = await deleteAuthUser(id)
if (authError) {
redirect(`/admin/users/${id}?error=${encodeURIComponent(authError)}`)
}
await supabaseAdmin.from('profiles').delete().eq('id', id)
revalidatePath('/admin/users')
redirect('/admin/users?deleted=1')
}