godcrm/backend/routes/v3/user-settings.js
GOD CRM Release f89e074dd1
Some checks failed
CI / Lint / Typecheck / Test / Build (push) Has been cancelled
CI / PostgreSQL Integration Tests (push) Has been cancelled
GOD CRM — public scrubbed snapshot
Governed substrate for autonomous agents: scoped identity (passports),
audited actions, MCP workspace. Infra IPs and secrets redacted for public release.
2026-08-10 04:01:45 +03:00

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;