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
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>
324 lines
18 KiB
JavaScript
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),
|
|
};
|
|
},
|
|
};
|
|
}
|