feat: auth system — CSS page transitions, API helpers, users/tokens pages

This commit is contained in:
Zac Gaetano 2026-05-18 12:58:24 -04:00
parent d0f9848717
commit 2d21d4d44d

View file

@ -133,7 +133,6 @@ async function deleteProject(projectId) {
// ============================================================
// BIN API CALLS
// Bins are mounted at /api/v1/bins with project_id as query param
// ============================================================
async function getBins(projectId) {
@ -166,14 +165,8 @@ async function deleteBin(projectId, binId) {
// ============================================================
// CAPTURE API CALLS
// Routes: GET /capture/devices, GET /capture/status,
// POST /capture/start, POST /capture/stop
// ============================================================
/**
* Get list of available capture devices.
* Normalises capture service response ({index, name}) to {id, name, interface}.
*/
async function getCaptureDevices() {
const result = await captureApi('/devices');
if (result.success && result.data) {
@ -189,23 +182,14 @@ async function getCaptureDevices() {
return result;
}
/** Get overall capture service status */
async function getCaptureStatus() {
return captureApi('/status');
}
/** Get current recording state (alias for getCaptureStatus) */
async function getRecordingStatus() {
return captureApi('/status');
}
/**
* Start recording.
* @param {number} deviceIndex - Device index from getCaptureDevices
* @param {string} projectId
* @param {string|null} binId
* @param {string} clipName
*/
async function startRecording(deviceIndex, projectId, binId, clipName) {
const result = await captureApi('/start', {
method: 'POST',
@ -222,16 +206,10 @@ async function startRecording(deviceIndex, projectId, binId, clipName) {
return result;
}
/**
* Stop the active recording.
* Uses the session ID stored from the most recent startRecording call,
* falling back to the current status if no local state exists.
*/
async function stopRecording() {
let sessionId = _captureSessionId;
if (!sessionId) {
// Try to recover session_id from live status
const statusResult = await captureApi('/status');
if (statusResult.success && statusResult.data && statusResult.data.sessionId) {
sessionId = statusResult.data.sessionId;
@ -249,14 +227,10 @@ async function stopRecording() {
return result;
}
/**
* Get recent captures uses assets API ordered by created_at desc.
*/
async function getRecentCaptures(limit = 10) {
return api(`/assets?limit=${limit}`);
}
/** Not available in current capture service — returns empty */
async function getRecordingTimecode() {
return { success: true, data: { timecode: null } };
}
@ -318,13 +292,8 @@ function throttle(func, limit) {
// ============================================================
// UPLOAD API CALLS
// Routes expect camelCase field names
// ============================================================
/**
* Initialize a multipart upload.
* Returns { assetId, uploadId, key }
*/
async function initUpload(data) {
const body = {
filename: data.filename,
@ -337,10 +306,6 @@ async function initUpload(data) {
return api('/upload/init', { method: 'POST', body: JSON.stringify(body) });
}
/**
* Complete a multipart upload.
* @param {object} data - { uploadId, key, assetId, parts: [{partNumber, ETag}] }
*/
async function completeUpload(data) {
const body = {
uploadId: data.upload_id || data.uploadId,
@ -354,7 +319,6 @@ async function completeUpload(data) {
return api('/upload/complete', { method: 'POST', body: JSON.stringify(body) });
}
/** Abort an ongoing multipart upload */
async function abortUpload(data) {
const body = {
uploadId: data.upload_id || data.uploadId,
@ -364,7 +328,6 @@ async function abortUpload(data) {
return api('/upload/abort', { method: 'POST', body: JSON.stringify(body) });
}
/** Upload a file part (FormData, no JSON content-type) */
async function uploadPart(formData) {
try {
const response = await fetch('/api/v1/upload/part', {
@ -380,7 +343,6 @@ async function uploadPart(formData) {
}
}
/** Simple upload for small files (under 50MB) */
async function simpleUpload(formData) {
try {
const response = await fetch('/api/v1/upload/simple', {
@ -423,3 +385,74 @@ async function deleteRecorder(id) {
async function getRecorderStatus(id) {
return api(`/recorders/${id}/status`);
}
// ============================================================
// USERS API CALLS (admin)
// ============================================================
async function getUsers() {
return api('/users');
}
async function createUser(data) {
return api('/users', { method: 'POST', body: JSON.stringify(data) });
}
async function updateUser(id, data) {
return api(`/users/${id}`, { method: 'PATCH', body: JSON.stringify(data) });
}
async function deleteUser(id) {
return api(`/users/${id}`, { method: 'DELETE' });
}
// ============================================================
// GROUPS API CALLS (admin)
// ============================================================
async function getGroups() {
return api('/groups');
}
async function createGroup(data) {
return api('/groups', { method: 'POST', body: JSON.stringify(data) });
}
async function updateGroup(id, data) {
return api(`/groups/${id}`, { method: 'PATCH', body: JSON.stringify(data) });
}
async function deleteGroup(id) {
return api(`/groups/${id}`, { method: 'DELETE' });
}
async function getGroupMembers(id) {
return api(`/groups/${id}/members`);
}
async function addGroupMember(groupId, userId) {
return api(`/groups/${groupId}/members`, {
method: 'POST',
body: JSON.stringify({ user_id: userId }),
});
}
async function removeGroupMember(groupId, userId) {
return api(`/groups/${groupId}/members/${userId}`, { method: 'DELETE' });
}
// ============================================================
// TOKENS API CALLS
// ============================================================
async function getTokens() {
return api('/tokens');
}
async function createToken(data) {
return api('/tokens', { method: 'POST', body: JSON.stringify(data) });
}
async function revokeToken(id) {
return api(`/tokens/${id}`, { method: 'DELETE' });
}