157 lines
4.6 KiB
TypeScript
157 lines
4.6 KiB
TypeScript
import type { Credentials, DlqItem, NotificationGroup, NotificationPrefs, Task } from '../types'
|
|
import { clearSession, session, setSessionToken } from '../stores/session'
|
|
|
|
const API_BASE = 'http://localhost:8080/api/v1'
|
|
|
|
function headers(extra?: Record<string, string>) {
|
|
const h: Record<string, string> = { 'Content-Type': 'application/json', ...(extra ?? {}) }
|
|
if (session.token) h.Authorization = `Bearer ${session.token}`
|
|
return h
|
|
}
|
|
|
|
async function handle<T>(response: Response): Promise<T> {
|
|
if (!response.ok) {
|
|
const body = await response.text()
|
|
throw new Error(body || `Request failed: ${response.status}`)
|
|
}
|
|
if (response.status === 204) return undefined as T
|
|
return (await response.json()) as T
|
|
}
|
|
|
|
async function refreshAccessToken(): Promise<boolean> {
|
|
try {
|
|
const response = await fetch(`${API_BASE}/auth/refresh`, {
|
|
method: 'POST',
|
|
credentials: 'include',
|
|
headers: headers(),
|
|
})
|
|
if (!response.ok) return false
|
|
const data = (await response.json()) as { access_token: string }
|
|
if (!data?.access_token) return false
|
|
setSessionToken(data.access_token)
|
|
return true
|
|
} catch {
|
|
return false
|
|
}
|
|
}
|
|
|
|
async function request<T>(path: string, init: RequestInit = {}, allowRefresh = true): Promise<T> {
|
|
const merged: RequestInit = {
|
|
credentials: 'include',
|
|
...init,
|
|
headers: headers((init.headers as Record<string, string> | undefined) ?? {}),
|
|
}
|
|
const response = await fetch(`${API_BASE}${path}`, merged)
|
|
if (response.status === 401 && allowRefresh && !path.startsWith('/auth/')) {
|
|
const refreshed = await refreshAccessToken()
|
|
if (refreshed) {
|
|
return request<T>(path, init, false)
|
|
}
|
|
clearSession()
|
|
}
|
|
return handle<T>(response)
|
|
}
|
|
|
|
export async function register(credentials: Credentials, autoLogin = true) {
|
|
return request<{ email: string; access_token?: string; expires_in?: number; session_id?: string }>('/auth/register', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ ...credentials, auto_login: autoLogin }),
|
|
})
|
|
}
|
|
|
|
export async function login(credentials: Credentials) {
|
|
return request<{ access_token: string; expires_in: number; session_id: string }>('/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify(credentials),
|
|
})
|
|
}
|
|
|
|
export async function refresh() {
|
|
return request<{ access_token: string; expires_in: number; session_id: string }>('/auth/refresh', {
|
|
method: 'POST',
|
|
}, false)
|
|
}
|
|
|
|
export async function logout() {
|
|
return request<void>('/auth/logout', {
|
|
method: 'POST',
|
|
}, false)
|
|
}
|
|
|
|
export async function logoutAll() {
|
|
return request<void>('/auth/logout-all', {
|
|
method: 'POST',
|
|
}, false)
|
|
}
|
|
|
|
export async function listSessions() {
|
|
return request<Array<{ id: string; device_info: string; ip: string; user_agent: string; created_at: string; expires_at: string; revoked_at?: string }>>('/auth/sessions')
|
|
}
|
|
|
|
export async function revokeSession(id: string) {
|
|
return request<void>(`/auth/sessions/${id}`, { method: 'DELETE' }, false)
|
|
}
|
|
|
|
export async function listTasks() {
|
|
return request<Task[]>('/tasks')
|
|
}
|
|
|
|
export async function createTask(payload: Partial<Task>) {
|
|
return request<Task>('/tasks', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export async function updateTask(id: number, payload: Partial<Task>) {
|
|
return request<Task>(`/tasks/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export async function deleteTask(id: number) {
|
|
return request<void>(`/tasks/${id}`, {
|
|
method: 'DELETE',
|
|
})
|
|
}
|
|
|
|
export async function getNotificationPrefs() {
|
|
return request<NotificationPrefs>('/notifications/prefs')
|
|
}
|
|
|
|
export async function updateNotificationPrefs(payload: NotificationPrefs) {
|
|
return request<NotificationPrefs>('/notifications/prefs', {
|
|
method: 'PUT',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export async function listNotificationGroups() {
|
|
return request<NotificationGroup[]>('/notifications/groups')
|
|
}
|
|
|
|
export async function createNotificationGroup(payload: Omit<NotificationGroup, 'id'>) {
|
|
return request<NotificationGroup>('/notifications/groups', {
|
|
method: 'POST',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export async function updateNotificationGroup(id: number, payload: Omit<NotificationGroup, 'id'>) {
|
|
return request<NotificationGroup>(`/notifications/groups/${id}`, {
|
|
method: 'PUT',
|
|
body: JSON.stringify(payload),
|
|
})
|
|
}
|
|
|
|
export async function deleteNotificationGroup(id: number) {
|
|
return request<void>(`/notifications/groups/${id}`, {
|
|
method: 'DELETE',
|
|
})
|
|
}
|
|
|
|
export async function listNotificationDlq(page = 1, pageSize = 20) {
|
|
return request<DlqItem[]>(`/notifications/dlq?page=${page}&page_size=${pageSize}`)
|
|
}
|