225 lines
6.7 KiB
TypeScript
225 lines
6.7 KiB
TypeScript
'use server'
|
||
|
||
import { revalidatePath } from 'next/cache'
|
||
import { redirect } from 'next/navigation'
|
||
import { getAccessToken, refreshAccessToken } from '@/app/auth/actions'
|
||
|
||
const API_BASE = process.env.API_BASE_URL ?? 'http://localhost:8080'
|
||
|
||
async function getToken(): Promise<string> {
|
||
const token = await getAccessToken()
|
||
if (token) return token
|
||
const fresh = await refreshAccessToken()
|
||
if (fresh) return fresh
|
||
redirect('/auth/login?from=/admin/users')
|
||
}
|
||
|
||
/** API'nin döndürdüğü hata yanıtından okunabilir bir mesaj çıkarır. */
|
||
function extractError(data: Record<string, unknown>, fallback: string): string {
|
||
if (typeof data?.error === 'string' && data.error) return data.error
|
||
if (typeof data?.message === 'string' && data.message) return data.message
|
||
if (typeof data?.detail === 'string' && data.detail) return data.detail
|
||
if (data?.errors && typeof data.errors === 'object') {
|
||
const msgs = Object.values(data.errors as Record<string, string>).filter(Boolean)
|
||
if (msgs.length > 0) return msgs.join(', ')
|
||
}
|
||
console.error('[API Error]', JSON.stringify(data))
|
||
return fallback
|
||
}
|
||
|
||
export type User = {
|
||
id: number
|
||
username: string
|
||
email: string
|
||
email_verified: boolean
|
||
is_active: boolean
|
||
is_admin: boolean
|
||
created_at: string
|
||
updated_at: string
|
||
}
|
||
|
||
export type UsersResponse = {
|
||
items: User[]
|
||
meta: { page: number; limit: number; total: number }
|
||
}
|
||
|
||
export type UserFormState = {
|
||
error?: string
|
||
success?: boolean
|
||
user?: User
|
||
deletedId?: number
|
||
}
|
||
|
||
export async function getUsers(page = 1, limit = 10): Promise<UsersResponse> {
|
||
let token = await getToken()
|
||
const url = `${API_BASE}/api/v1/admin/users?page=${page}&limit=${limit}`
|
||
|
||
const doFetch = () =>
|
||
fetch(url, { headers: { Authorization: `Bearer ${token}` }, cache: 'no-store' })
|
||
|
||
let res: Response
|
||
try {
|
||
res = await doFetch()
|
||
} catch (error) {
|
||
console.error('[getUsers] fetch failed', error)
|
||
throw new Error('API sunucusuna bağlanılamadı. Backend çalışıyor mu kontrol edin.')
|
||
}
|
||
|
||
// Token süresi dolmuşsa yenile ve bir kez daha dene
|
||
if (res.status === 401) {
|
||
const fresh = await refreshAccessToken()
|
||
if (!fresh) throw new Error('Oturum süresi doldu')
|
||
token = fresh
|
||
try {
|
||
res = await doFetch()
|
||
} catch (error) {
|
||
console.error('[getUsers] fetch failed after refresh', error)
|
||
throw new Error('API sunucusuna bağlanılamadı. Backend çalışıyor mu kontrol edin.')
|
||
}
|
||
}
|
||
|
||
// Rate limit aşıldıysa kısa aralıkla sınırlı sayıda tekrar dene
|
||
if (res.status === 429) {
|
||
for (const waitMs of [500, 1000, 1500]) {
|
||
await new Promise((r) => setTimeout(r, waitMs))
|
||
res = await doFetch()
|
||
if (res.ok) break
|
||
if (res.status !== 429) break
|
||
}
|
||
}
|
||
|
||
if (!res.ok) {
|
||
console.error(`[getUsers] API error: ${res.status} ${res.statusText}`)
|
||
if (res.status === 429) {
|
||
throw new Error('Çok fazla istek gönderildi, lütfen birkaç saniye sonra tekrar deneyin (429)')
|
||
}
|
||
throw new Error(`Kullanıcılar alınamadı (${res.status})`)
|
||
}
|
||
return res.json()
|
||
}
|
||
|
||
export async function createUser(
|
||
_prev: UserFormState,
|
||
formData: FormData
|
||
): Promise<UserFormState> {
|
||
'use server'
|
||
try {
|
||
const token = await getToken()
|
||
const body = {
|
||
username: formData.get('username') as string,
|
||
email: formData.get('email') as string,
|
||
password: formData.get('password') as string,
|
||
confirm_password: formData.get('confirm_password') as string,
|
||
is_active: formData.get('is_active') === 'true',
|
||
is_admin: formData.get('is_admin') === 'true',
|
||
}
|
||
const res = await fetch(`${API_BASE}/api/v1/admin/users`, {
|
||
method: 'POST',
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(body),
|
||
})
|
||
if (!res.ok) {
|
||
const data = await res.json().catch(() => ({}))
|
||
return { error: extractError(data, 'Kullanıcı oluşturulamadı') }
|
||
}
|
||
revalidatePath('/admin/users')
|
||
const created = (await res.json()) as User
|
||
return { success: true, user: created }
|
||
} catch (e) {
|
||
console.error('[createUser]', e)
|
||
return { error: 'Sunucu hatası' }
|
||
}
|
||
}
|
||
|
||
export async function updateUser(
|
||
id: number,
|
||
_prev: UserFormState,
|
||
formData: FormData
|
||
): Promise<UserFormState> {
|
||
'use server'
|
||
try {
|
||
const token = await getToken()
|
||
const body = {
|
||
username: formData.get('username') as string,
|
||
email: formData.get('email') as string,
|
||
password: formData.get('password') as string,
|
||
confirm_password: formData.get('confirm_password') as string,
|
||
is_active: formData.get('is_active') === 'true',
|
||
is_admin: formData.get('is_admin') === 'true',
|
||
}
|
||
const res = await fetch(`${API_BASE}/api/v1/admin/users/${id}`, {
|
||
method: 'PUT',
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(body),
|
||
})
|
||
if (!res.ok) {
|
||
const data = await res.json().catch(() => ({}))
|
||
return { error: extractError(data, 'Kullanıcı güncellenemedi') }
|
||
}
|
||
revalidatePath('/admin/users')
|
||
const updated = (await res.json()) as User
|
||
return { success: true, user: updated }
|
||
} catch (e) {
|
||
console.error('[updateUser]', e)
|
||
return { error: 'Sunucu hatası' }
|
||
}
|
||
}
|
||
|
||
export async function deleteUser(
|
||
_prev: UserFormState,
|
||
formData: FormData
|
||
): Promise<UserFormState> {
|
||
'use server'
|
||
try {
|
||
const token = await getToken()
|
||
const id = formData.get('id') as string
|
||
const res = await fetch(`${API_BASE}/api/v1/admin/users/${id}`, {
|
||
method: 'DELETE',
|
||
headers: { Authorization: `Bearer ${token}` },
|
||
})
|
||
if (!res.ok) {
|
||
const data = await res.json().catch(() => ({}))
|
||
return { error: extractError(data, 'Kullanıcı silinemedi') }
|
||
}
|
||
revalidatePath('/admin/users')
|
||
return { success: true, deletedId: Number(id) }
|
||
} catch (e) {
|
||
console.error('[deleteUser]', e)
|
||
return { error: 'Sunucu hatası' }
|
||
}
|
||
}
|
||
|
||
export async function changeUserStatus(
|
||
id: number,
|
||
isActive: boolean
|
||
): Promise<UserFormState> {
|
||
'use server'
|
||
try {
|
||
const token = await getToken()
|
||
const res = await fetch(`${API_BASE}/api/v1/admin/users/${id}/status`, {
|
||
method: 'PATCH',
|
||
headers: {
|
||
Authorization: `Bearer ${token}`,
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ is_active: isActive }),
|
||
})
|
||
if (!res.ok) {
|
||
const data = await res.json().catch(() => ({}))
|
||
return { error: extractError(data, 'Durum güncellenemedi') }
|
||
}
|
||
revalidatePath('/admin/users')
|
||
const updated = (await res.json()) as User
|
||
return { success: true, user: updated }
|
||
} catch (e) {
|
||
console.error('[changeUserStatus]', e)
|
||
return { error: 'Sunucu hatası' }
|
||
}
|
||
}
|