- Backend DELETE /api/groups/<gid> (admin, own-org enforced via _get_owned_group); cascades removal of the group's sessions. GroupStore.delete added. - Training.vue: each group card gets a trash (Trash2 line icon) delete button shown only to admins (auth.isAdmin); confirm dialog; @click.stop so it doesn't navigate. Card restructured so the delete button is not inside the clickable router-link. - test_user_journey: trainee cannot delete (403), admin deletes, group gone (404). All suites pass. Rebuilt dist.
68 lines
2.9 KiB
JavaScript
68 lines
2.9 KiB
JavaScript
// API client with JWT auth injection.
|
|
const TOKEN_KEY = 'st_token'
|
|
|
|
export function getToken() {
|
|
return localStorage.getItem(TOKEN_KEY)
|
|
}
|
|
export function setToken(t) {
|
|
if (t) localStorage.setItem(TOKEN_KEY, t)
|
|
else localStorage.removeItem(TOKEN_KEY)
|
|
}
|
|
|
|
async function request(method, url, body, isForm = false) {
|
|
const headers = {}
|
|
const token = getToken()
|
|
if (token) headers['Authorization'] = `Bearer ${token}`
|
|
let payload = body
|
|
if (!isForm && body !== undefined && body !== null) {
|
|
headers['Content-Type'] = 'application/json'
|
|
payload = JSON.stringify(body)
|
|
}
|
|
const res = await fetch(url, { method, headers, body: payload })
|
|
let data = null
|
|
try {
|
|
data = await res.json()
|
|
} catch (e) {
|
|
/* ignore json parse errors */
|
|
}
|
|
if (!res.ok) {
|
|
const msg = (data && (data.error || data.message)) || `HTTP ${res.status}`
|
|
throw new Error(msg)
|
|
}
|
|
return data
|
|
}
|
|
|
|
export const api = {
|
|
login: (username, password) => request('POST', '/api/auth/login', { username, password }),
|
|
me: () => request('GET', '/api/auth/me'),
|
|
setup: (b) => request('POST', '/api/auth/setup', b),
|
|
updateProfile: (b) => request('PATCH', '/api/auth/profile', b),
|
|
adminCreateUser: (b) => request('POST', '/api/admin/users', b),
|
|
adminListUsers: () => request('GET', '/api/admin/users'),
|
|
adminUpdateUser: (username, b) => request('PUT', `/api/admin/users/${username}`, b),
|
|
createGroup: (formData) => request('POST', '/api/groups', formData, true),
|
|
listGroups: () => request('GET', '/api/groups'),
|
|
getGroup: (id) => request('GET', `/api/groups/${id}`),
|
|
deleteGroup: (id) => request('DELETE', `/api/groups/${id}`),
|
|
analyzeGroup: (id, opts = {}) => request('POST', `/api/groups/${id}/analyze${opts.append ? '?append=true' : ''}`),
|
|
listPersonas: (gid) => request('GET', `/api/groups/${gid}/personas`),
|
|
getPersona: (gid, pid) => request('GET', `/api/groups/${gid}/personas/${pid}`),
|
|
updatePersona: (gid, pid, b) => request('PUT', `/api/groups/${gid}/personas/${pid}`, b),
|
|
chatStart: (gid, pid, scenario, locale) => request('POST', `/api/chat/${gid}/personas/${pid}/chat/start`, { scenario, locale }),
|
|
chatResume: (gid, pid) => request('GET', `/api/chat/${gid}/personas/${pid}/chat/resume`),
|
|
chatSend: (gid, pid, text) => request('POST', `/api/chat/${gid}/personas/${pid}/chat/send`, { text }),
|
|
chatFinish: (gid, pid) => request('POST', `/api/chat/${gid}/personas/${pid}/chat/finish`),
|
|
mySessions: () => request('GET', '/api/chat/sessions'),
|
|
myBoard: () => request('GET', '/api/me/board'),
|
|
weakAreas: () => request('GET', '/api/me/weak-areas'),
|
|
myPersonas: () => request('GET', '/api/me/personas'),
|
|
generatePersona: (b) => request('POST', '/api/me/personas/generate', b),
|
|
analytics: (params = {}) => {
|
|
const qs = new URLSearchParams()
|
|
if (params.from) qs.set('from', params.from)
|
|
if (params.to) qs.set('to', params.to)
|
|
const q = qs.toString()
|
|
return request('GET', `/api/analytics${q ? `?${q}` : ''}`)
|
|
},
|
|
}
|