Governed substrate for autonomous agents: scoped identity (passports), audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
220 lines
6 KiB
JavaScript
220 lines
6 KiB
JavaScript
// API v3: User Settings Routes
|
|
// Handles user preferences like spaces order
|
|
/**
|
|
* @swagger
|
|
* components:
|
|
* schemas:
|
|
* UserSettings:
|
|
* type: object
|
|
* properties:
|
|
* spacesOrder:
|
|
* type: object
|
|
* additionalProperties:
|
|
* type: integer
|
|
*/
|
|
import express from 'express';
|
|
import { dbGet, dbRun } from '../../database/connection.js';
|
|
import { apiLogger } from '../../utils/logger.js';
|
|
import { getUserDashboard, createDashboard } from '../../services/DashboardService.js';
|
|
import { success, badRequest, error } from '../../utils/response.js';
|
|
|
|
const router = express.Router();
|
|
|
|
/**
|
|
* GET /api/v3/user-settings/spaces-order
|
|
* Get spaces order for authenticated user
|
|
* Returns: { spacesOrder: { [spaceId]: number } }
|
|
* @swagger
|
|
* /api/v3/user-settings/spaces-order:
|
|
* get:
|
|
* summary: Get spaces order for user
|
|
* tags: [User Settings]
|
|
* security:
|
|
* - bearerAuth: []
|
|
* responses:
|
|
* 200:
|
|
* description: User spaces order
|
|
*/
|
|
router.get('/spaces-order', async (req, res) => {
|
|
try {
|
|
const userId = req.user.id;
|
|
|
|
const setting = await dbGet(
|
|
`SELECT setting_value_encrypted as value FROM user_settings
|
|
WHERE user_id = ? AND setting_key = 'spaces_order'`,
|
|
[userId]
|
|
);
|
|
|
|
// Return the order object or empty object
|
|
let spacesOrder = {};
|
|
if (setting?.value) {
|
|
try {
|
|
spacesOrder = JSON.parse(setting.value);
|
|
} catch (e) {
|
|
apiLogger.error('Failed to parse spaces_order:', e);
|
|
spacesOrder = {};
|
|
}
|
|
}
|
|
|
|
success(res, { spacesOrder });
|
|
} catch (err) {
|
|
apiLogger.error('Error fetching spaces order:', err);
|
|
error(res, 'FETCH_ERROR', err.message, 500);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* PUT /api/v3/user-settings/spaces-order
|
|
* Update spaces order for authenticated user
|
|
* Body: { spacesOrder: { [spaceId]: number } }
|
|
* @swagger
|
|
* /api/v3/user-settings/spaces-order:
|
|
* put:
|
|
* summary: Update spaces order for user
|
|
* tags: [User Settings]
|
|
* security:
|
|
* - bearerAuth: []
|
|
* requestBody:
|
|
* required: true
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* $ref: '#/components/schemas/UserSettings'
|
|
* responses:
|
|
* 200:
|
|
* description: Order updated
|
|
*/
|
|
router.put('/spaces-order', async (req, res) => {
|
|
try {
|
|
const userId = req.user.id;
|
|
const { spacesOrder } = req.body;
|
|
|
|
if (typeof spacesOrder !== 'object' || spacesOrder === null || Array.isArray(spacesOrder)) {
|
|
return badRequest(res, 'spacesOrder must be an object with spaceId keys and number values');
|
|
}
|
|
|
|
const valueJson = JSON.stringify(spacesOrder);
|
|
|
|
// Upsert the setting
|
|
await dbRun(
|
|
`INSERT INTO user_settings (user_id, setting_key, setting_value_encrypted, setting_type)
|
|
VALUES (?, 'spaces_order', ?, 'preference')
|
|
ON CONFLICT(user_id, setting_key)
|
|
DO UPDATE SET setting_value_encrypted = ?, last_used_at = CURRENT_TIMESTAMP`,
|
|
[userId, valueJson, valueJson]
|
|
);
|
|
|
|
success(res, { spacesOrder });
|
|
} catch (err) {
|
|
apiLogger.error('Error updating spaces order:', err);
|
|
error(res, 'UPDATE_ERROR', err.message, 500);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* PATCH /api/v3/user-settings/spaces-order/:spaceId
|
|
* Update order for a single space
|
|
* Body: { order: number }
|
|
*/
|
|
router.patch('/spaces-order/:spaceId', async (req, res) => {
|
|
try {
|
|
const userId = req.user.id;
|
|
const spaceId = parseInt(req.params.spaceId, 10);
|
|
const { order } = req.body;
|
|
|
|
if (isNaN(spaceId)) {
|
|
return badRequest(res, 'Invalid spaceId');
|
|
}
|
|
|
|
if (typeof order !== 'number') {
|
|
return badRequest(res, 'order must be a number');
|
|
}
|
|
|
|
// Get current settings
|
|
const setting = await dbGet(
|
|
`SELECT setting_value_encrypted as value FROM user_settings
|
|
WHERE user_id = ? AND setting_key = 'spaces_order'`,
|
|
[userId]
|
|
);
|
|
|
|
let spacesOrder = {};
|
|
if (setting?.value) {
|
|
try {
|
|
spacesOrder = JSON.parse(setting.value);
|
|
} catch (e) {
|
|
spacesOrder = {};
|
|
}
|
|
}
|
|
|
|
// Update order for this space
|
|
spacesOrder[spaceId] = order;
|
|
|
|
const valueJson = JSON.stringify(spacesOrder);
|
|
|
|
// Upsert
|
|
await dbRun(
|
|
`INSERT INTO user_settings (user_id, setting_key, setting_value_encrypted, setting_type)
|
|
VALUES (?, 'spaces_order', ?, 'preference')
|
|
ON CONFLICT(user_id, setting_key)
|
|
DO UPDATE SET setting_value_encrypted = ?, last_used_at = CURRENT_TIMESTAMP`,
|
|
[userId, valueJson, valueJson]
|
|
);
|
|
|
|
success(res, { spacesOrder });
|
|
} catch (err) {
|
|
apiLogger.error('Error updating space order:', err);
|
|
error(res, 'UPDATE_ERROR', err.message, 500);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* DELETE /api/v3/user-settings/spaces-order
|
|
* Reset spaces order to default
|
|
*/
|
|
router.delete('/spaces-order', async (req, res) => {
|
|
try {
|
|
const userId = req.user.id;
|
|
|
|
await dbRun(
|
|
`DELETE FROM user_settings WHERE user_id = ? AND setting_key = 'spaces_order'`,
|
|
[userId]
|
|
);
|
|
|
|
success(res, { spacesOrder: {} });
|
|
} catch (err) {
|
|
apiLogger.error('Error resetting spaces order:', err);
|
|
error(res, 'DELETE_ERROR', err.message, 500);
|
|
}
|
|
});
|
|
|
|
/**
|
|
* GET /api/v3/user-settings/home-dashboard
|
|
* Get or create home dashboard for authenticated user
|
|
* Returns: { id, name, icon, ... }
|
|
*/
|
|
router.get('/home-dashboard', async (req, res) => {
|
|
try {
|
|
const userId = req.user.id;
|
|
|
|
// Try to get existing home dashboard
|
|
let dashboard = await getUserDashboard(userId);
|
|
|
|
// Create if doesn't exist
|
|
if (!dashboard) {
|
|
dashboard = await createDashboard({
|
|
user_id: userId,
|
|
name: 'Home',
|
|
icon: '🏠',
|
|
is_default: true
|
|
});
|
|
apiLogger.info(`Created home dashboard for user ${userId}`);
|
|
}
|
|
|
|
success(res, dashboard);
|
|
} catch (err) {
|
|
apiLogger.error('Error fetching home dashboard:', err);
|
|
error(res, 'FETCH_ERROR', err.message, 500);
|
|
}
|
|
});
|
|
|
|
export default router;
|