This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/src/js/sw/sdk/api-domains.js
Jeffrey Smith 7bdc6df6b4
Some checks failed
CI/CD / detect-changes (pull_request) Successful in 20s
CI/CD / test-frontend (pull_request) Successful in 6s
CI/CD / test-sqlite (pull_request) Has been cancelled
CI/CD / build-and-deploy (pull_request) Has been cancelled
CI/CD / test-go-pg (pull_request) Has been cancelled
Feat v0.6.1-v0.6.2: backup/restore, docs surface, dynamic OpenAPI
v0.6.1 — Backup tooling (.swb ZIP export/import), admin backup section,
docs surface with markdown rendering, 5 reference docs, 6 handler tests.

v0.6.2 — Dark mode contrast fixes, topbar nav, 📖 docs icon, dynamic
OpenAPI spec with extension route merging, docs outline sidebar,
scroll/routing fixes; 7 new tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:53:24 +00:00

324 lines
18 KiB
JavaScript

// ==========================================
// Switchboard Core — SDK: API Domain Namespaces
// ==========================================
// Namespaced domain clients wrapping restClient.
// Each domain is a plain object with typed methods.
//
// Factory: createDomains(restClient)
// ==========================================
/**
* Build query string from an options object. Skips null/undefined values.
* @param {object} [opts]
* @returns {string} — '' or '?key=val&...'
*/
function _qs(opts) {
if (!opts) return '';
const p = new URLSearchParams();
for (const [k, v] of Object.entries(opts)) {
if (v != null) p.set(k, String(v));
}
const s = p.toString();
return s ? '?' + s : '';
}
/**
* Standard CRUD factory.
* @param {object} rc — restClient
* @param {string} base — e.g. '/api/v1/channels'
* @returns {object} — { list, get, create, update, del }
*/
function crud(rc, base) {
return {
list: (opts) => rc.get(base + _qs(opts)),
get: (id) => rc.get(`${base}/${id}`),
create: (data) => rc.post(base, data),
update: (id, data) => rc.put(`${base}/${id}`, data),
del: (id) => rc.del(`${base}/${id}`),
};
}
/**
* Create all 18 domain namespaces.
*
* @param {object} restClient
* @returns {object} api — the full sw.api object
*/
export function createDomains(restClient) {
const rc = restClient;
return {
// ── Escape hatches (raw restClient) ─────
get: rc.get.bind(rc),
post: rc.post.bind(rc),
put: rc.put.bind(rc),
patch: rc.patch.bind(rc),
del: rc.del.bind(rc),
stream: rc.stream.bind(rc),
upload: rc.upload.bind(rc),
// ── 1. Auth ─────────────────────────────
auth: {
login: (login, password) => rc.post('/api/v1/auth/login', { login, password }),
register: (username, email, password) => rc.post('/api/v1/auth/register', { username, email, password }),
refresh: (refreshToken) => rc.post('/api/v1/auth/refresh', { refresh_token: refreshToken }),
logout: (refreshToken) => rc.post('/api/v1/auth/logout', { refresh_token: refreshToken }),
},
// ── 2. Connections ────────────────────
connections: {
...crud(rc, '/api/v1/connections'),
resolve: (type, name) => rc.get('/api/v1/connections/resolve' + _qs({ type, name })),
},
// ── 3. Connection Types ────────────────
connectionTypes: {
list: () => rc.get('/api/v1/connection-types'),
},
// ── 11. Notifications ──────────────────
notifications: {
list: (opts) => rc.get('/api/v1/notifications' + _qs(opts)),
unreadCount: () => rc.get('/api/v1/notifications/unread-count'),
markRead: (id) => rc.patch(`/api/v1/notifications/${id}/read`, {}),
markAllRead: () => rc.post('/api/v1/notifications/mark-all-read', {}),
prefs: () => rc.get('/api/v1/notifications/preferences'),
setPref: (type, data) => rc.put(`/api/v1/notifications/preferences/${encodeURIComponent(type)}`, data),
delPref: (type) => rc.del(`/api/v1/notifications/preferences/${encodeURIComponent(type)}`),
del: (id) => rc.del(`/api/v1/notifications/${id}`),
},
// ── 12. Extensions ─────────────────────
extensions: {
list: (opts) => rc.get('/api/v1/extensions' + _qs(opts)),
get: (id) => rc.get(`/api/v1/extensions/${id}`),
settings: (id) => rc.get(`/api/v1/extensions/${id}/settings`),
},
// ── 13. Profile ────────────────────────
profile: {
get: () => rc.get('/api/v1/profile'),
update: (data) => rc.put('/api/v1/profile', data),
avatar: (base64) => rc.post('/api/v1/profile/avatar', { image: base64 }),
deleteAvatar:() => rc.del('/api/v1/profile/avatar'),
password: (current, pw) => rc.post('/api/v1/profile/password', { current_password: current, new_password: pw }),
settings: () => rc.get('/api/v1/settings'),
updateSettings: (data) => rc.put('/api/v1/settings', data),
permissions: () => rc.get('/api/v1/profile/permissions'),
},
// ── 14. Teams ──────────────────────────
teams: {
mine: () => rc.get('/api/v1/teams/mine'),
get: (id) => rc.get(`/api/v1/teams/${id}`),
update: (id, data) => rc.put(`/api/v1/teams/${id}`, data),
members: (id) => rc.get(`/api/v1/teams/${id}/members`),
addMember: (id, userId, role) => rc.post(`/api/v1/teams/${id}/members`, { user_id: userId, role }),
updateMember:(id, memberId, role) => rc.put(`/api/v1/teams/${id}/members/${memberId}`, { role }),
removeMember:(id, memberId) => rc.del(`/api/v1/teams/${id}/members/${memberId}`),
groups: (id) => rc.get(`/api/v1/teams/${id}/groups`),
// Team connections
connections: (id) => rc.get(`/api/v1/teams/${id}/connections`),
createConnection: (id, data) => rc.post(`/api/v1/teams/${id}/connections`, data),
updateConnection: (id, cid, data) => rc.put(`/api/v1/teams/${id}/connections/${cid}`, data),
deleteConnection: (id, cid) => rc.del(`/api/v1/teams/${id}/connections/${cid}`),
audit: (id, opts) => rc.get(`/api/v1/teams/${id}/audit` + _qs(opts)),
auditActions:(id) => rc.get(`/api/v1/teams/${id}/audit/actions`),
// Team workflows
workflows: (id, opts) => rc.get(`/api/v1/teams/${id}/workflows` + _qs(opts)),
createWorkflow: (id, data) => rc.post(`/api/v1/teams/${id}/workflows`, data),
getWorkflow: (id, wfId) => rc.get(`/api/v1/teams/${id}/workflows/${wfId}`),
updateWorkflow: (id, wfId, data) => rc.put(`/api/v1/teams/${id}/workflows/${wfId}`, data),
deleteWorkflow: (id, wfId) => rc.del(`/api/v1/teams/${id}/workflows/${wfId}`),
publishWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/publish`, {}),
// Team workflow stages (v0.37.15 — FE wiring for existing BE routes)
workflowStages: (id, wfId) => rc.get(`/api/v1/teams/${id}/workflows/${wfId}/stages`),
createWorkflowStage: (id, wfId, data) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/stages`, data),
updateWorkflowStage: (id, wfId, sid, data) => rc.put(`/api/v1/teams/${id}/workflows/${wfId}/stages/${sid}`, data),
deleteWorkflowStage: (id, wfId, sid) => rc.del(`/api/v1/teams/${id}/workflows/${wfId}/stages/${sid}`),
reorderWorkflowStages: (id, wfId, ids) => rc.patch(`/api/v1/teams/${id}/workflows/${wfId}/stages/reorder`, { ordered_ids: ids }),
// Adopt global workflows (v0.3.6)
availableWorkflows: (id) => rc.get(`/api/v1/teams/${id}/workflows/available`),
adoptWorkflow: (id, wfId) => rc.post(`/api/v1/teams/${id}/workflows/${wfId}/adopt`, {}),
},
// ── 9. Workflows ───────────────────────
workflows: {
...crud(rc, '/api/v1/workflows'),
update: (id, data) => rc.patch(`/api/v1/workflows/${id}`, data),
stages: (id) => rc.get(`/api/v1/workflows/${id}/stages`),
instances: (id, opts) => rc.get(`/api/v1/workflows/${id}/instances` + _qs(opts)),
advance: (id, data) => rc.post(`/api/v1/workflows/${id}/advance`, data),
reject: (id, data) => rc.post(`/api/v1/workflows/${id}/reject`, data),
cancel: (channelId) => rc.post(`/api/v1/channels/${channelId}/workflow/cancel`, {}),
},
// ── 10. Surfaces ──────────────────────
surfaces: {
list: (opts) => rc.get('/api/v1/surfaces' + _qs(opts)),
get: (id) => rc.get(`/api/v1/surfaces/${id}`),
install: (id) => rc.post(`/api/v1/surfaces/${id}/install`, {}),
},
// ── 18. Admin ──────────────────────────
admin: {
stats: () => rc.get('/api/v1/admin/stats'),
users: {
list: (opts) => rc.get('/api/v1/admin/users' + _qs(opts)),
create: (data) => rc.post('/api/v1/admin/users', data),
del: (id) => rc.del(`/api/v1/admin/users/${id}`),
resetPassword: (id, pw) => rc.post(`/api/v1/admin/users/${id}/reset-password`, { password: pw }),
updateRole: (id, isAdmin) => rc.put(`/api/v1/admin/users/${id}/role`, { is_admin: isAdmin }),
toggleActive: (id, active, opts) => rc.put(`/api/v1/admin/users/${id}/active`, { is_active: active, ...opts }),
permissions: (id) => rc.get(`/api/v1/admin/users/${id}/permissions`),
resetVault: (id) => rc.post(`/api/v1/admin/users/${id}/vault/reset`, {}),
},
settings: {
get: () => rc.get('/api/v1/admin/settings'),
update: (key, val) => rc.put(`/api/v1/admin/settings/${key}`, val),
public: () => rc.get('/api/v1/settings/public'),
},
teams: {
list: () => rc.get('/api/v1/admin/teams'),
get: (id) => rc.get(`/api/v1/admin/teams/${id}`),
create: (data) => rc.post('/api/v1/admin/teams', data),
update: (id, data) => rc.put(`/api/v1/admin/teams/${id}`, data),
del: (id) => rc.del(`/api/v1/admin/teams/${id}`),
members: (id) => rc.get(`/api/v1/admin/teams/${id}/members`),
addMember: (id, userId, role) => rc.post(`/api/v1/admin/teams/${id}/members`, { user_id: userId, role }),
updateMember: (id, mid, role) => rc.put(`/api/v1/admin/teams/${id}/members/${mid}`, { role }),
removeMember: (id, mid) => rc.del(`/api/v1/admin/teams/${id}/members/${mid}`),
},
groups: {
list: () => rc.get('/api/v1/admin/groups'),
get: (id) => rc.get(`/api/v1/admin/groups/${id}`),
create: (data) => rc.post('/api/v1/admin/groups', data),
update: (id, data) => rc.put(`/api/v1/admin/groups/${id}`, data),
del: (id) => rc.del(`/api/v1/admin/groups/${id}`),
members: (id) => rc.get(`/api/v1/admin/groups/${id}/members`),
addMember: (id, userId) => rc.post(`/api/v1/admin/groups/${id}/members`, { user_id: userId }),
removeMember: (id, userId) => rc.del(`/api/v1/admin/groups/${id}/members/${userId}`),
},
grants: {
get: (type, id) => rc.get(`/api/v1/admin/grants/${type}/${id}`),
set: (type, id, scope, groups) => rc.put(`/api/v1/admin/grants/${type}/${id}`, { grant_scope: scope, granted_groups: groups || [] }),
del: (type, id) => rc.del(`/api/v1/admin/grants/${type}/${id}`),
},
permissions: {
list: () => rc.get('/api/v1/admin/permissions'),
},
audit: {
list: (opts) => rc.get('/api/v1/admin/audit' + _qs(opts)),
actions: () => rc.get('/api/v1/admin/audit/actions'),
},
storage: {
status: () => rc.get('/api/v1/admin/storage/status'),
},
vault: {
status: () => rc.get('/api/v1/admin/vault/status'),
},
notifications: {
testEmail: () => rc.post('/api/v1/admin/notifications/test-email', {}),
broadcast: (data) => rc.post('/api/v1/admin/notifications/broadcast', data),
},
extensions: {
list: () => rc.get('/api/v1/admin/extensions'),
create: (data) => rc.post('/api/v1/admin/extensions', data),
update: (id, data) => rc.put(`/api/v1/admin/extensions/${id}`, data),
del: (id) => rc.del(`/api/v1/admin/extensions/${id}`),
},
// v0.38.1: Global connections
connections: crud(rc, '/api/v1/admin/connections'),
packages: {
list: (opts) => rc.get('/api/v1/admin/packages' + _qs(opts)),
get: (id) => rc.get(`/api/v1/admin/packages/${id}`),
install: (file) => rc.upload('/api/v1/admin/packages/install', file),
enable: (id) => rc.put(`/api/v1/admin/packages/${id}/enable`, {}),
disable: (id) => rc.put(`/api/v1/admin/packages/${id}/disable`, {}),
del: (id) => rc.del(`/api/v1/admin/packages/${id}`),
settings: (id) => rc.get(`/api/v1/admin/packages/${id}/settings`),
updateSettings: (id, data) => rc.put(`/api/v1/admin/packages/${id}/settings`, data),
dependencies: (id) => rc.get(`/api/v1/admin/packages/${id}/dependencies`), // v0.38.2
consumers: (id) => rc.get(`/api/v1/admin/packages/${id}/consumers`), // v0.38.2
update: (id, file) => rc.upload(`/api/v1/admin/packages/${id}/update`, file), // v0.5.4
exportPkg: (id) => `/api/v1/admin/packages/${id}/export`, // v0.5.4 (URL for window.open)
registry: () => rc.get('/api/v1/admin/packages/registry'),
registryInstall: (url) => rc.post('/api/v1/admin/packages/registry/install', { url }),
// v0.5.0: Extension permissions
permissions: (id) => rc.get(`/api/v1/admin/extensions/${id}/permissions`),
grantPerm: (id, perm) => rc.post(`/api/v1/admin/extensions/${id}/permissions/${perm}/grant`, {}),
revokePerm: (id, perm) => rc.post(`/api/v1/admin/extensions/${id}/permissions/${perm}/revoke`, {}),
grantAllPerms: (id) => rc.post(`/api/v1/admin/extensions/${id}/permissions/grant-all`, {}),
},
// v0.38.2: Full dependency graph
dependencies: {
list: () => rc.get('/api/v1/admin/dependencies'),
},
surfaces: {
list: () => rc.get('/api/v1/admin/surfaces'),
get: (id) => rc.get(`/api/v1/admin/surfaces/${id}`),
install: (file) => rc.upload('/api/v1/admin/surfaces/install', file),
enable: (id) => rc.put(`/api/v1/admin/surfaces/${id}/enable`, {}),
disable: (id) => rc.put(`/api/v1/admin/surfaces/${id}/disable`, {}),
del: (id) => rc.del(`/api/v1/admin/surfaces/${id}`),
},
// v0.6.1: Backup/Restore
backup: {
create: (opts) => rc.post('/api/v1/admin/backup' + _qs(opts)),
list: () => rc.get('/api/v1/admin/backups'),
download: (name) => `/api/v1/admin/backups/${name}`,
del: (name) => rc.del(`/api/v1/admin/backups/${name}`),
restore: (file) => rc.upload('/api/v1/admin/restore', file),
},
},
// ── Users ──────────────────────────────
users: {
search: (q) => rc.get('/api/v1/users/search' + _qs({ q })),
},
presence: {
heartbeat: () => rc.post('/api/v1/presence/heartbeat', {}),
},
groups: {
mine: () => rc.get('/api/v1/groups/mine'),
},
health: () => rc.get('/api/v1/health'),
// ── Extension-scoped client ──────────
/**
* Scoped API client for an extension's surface routes.
* @param {string} packageId — package slug (e.g. 'tasks')
* @returns {object} — { get, post, put, del, upload }
*/
ext(packageId) {
const base = '/s/' + packageId + '/api';
return {
get: (path, opts) => rc.get(base + path + _qs(opts)),
post: (path, data) => rc.post(base + path, data),
put: (path, data) => rc.put(base + path, data),
del: (path) => rc.del(base + path),
upload: (path, file) => rc.upload(base + path, file),
};
},
};
}