Align P2NS with current Autopass, Hypercore, Hyperdrive, and Protomux behavior: add plugin DB recovery for incompatible persisted HyperDB data, stop unconditional spec rebuilds via fingerprinting, update SDK replication paths, and align proxy invite channels with p2ns.core-* protocols without replicating Autopass over the p2ns topic.
108 lines
3.4 KiB
JavaScript
108 lines
3.4 KiB
JavaScript
/**
|
|
* Global Profile Plugin - Fields Routes
|
|
*
|
|
* Handles custom fields management API endpoints
|
|
*/
|
|
|
|
const sdk = require('../../../includes/plugins/sdk');
|
|
const { dbErrorResponse } = require('../db-errors');
|
|
|
|
/**
|
|
* Handle fields-related routes
|
|
*/
|
|
async function handleFieldsRoutes(req, res, path, query, method) {
|
|
// GET /api/fields - Get all registered fields
|
|
if (path === 'api/fields' && method === 'GET') {
|
|
try {
|
|
await sdk.db.ready();
|
|
const fields = await sdk.db.find('@profile/fields', {});
|
|
return sdk.router.json(res, { fields: fields || [] });
|
|
} catch (err) {
|
|
sdk.log.error('global.profile', `Error getting fields: ${err.message}`);
|
|
const { status, body } = dbErrorResponse(err);
|
|
return sdk.router.json(res, { fields: [], ...body }, status);
|
|
}
|
|
}
|
|
|
|
// POST /api/fields - Register a new custom field
|
|
if (path === 'api/fields' && method === 'POST') {
|
|
// Require authentication for write operations
|
|
const peerId = await sdk.auth.requireLocalPeer(req, res);
|
|
if (!peerId) return true; // 401 already sent
|
|
|
|
let body = '';
|
|
req.on('data', chunk => { body += chunk.toString(); });
|
|
req.on('end', async () => {
|
|
try {
|
|
if (!body) {
|
|
return sdk.router.error(res, 'Request body is required', 400);
|
|
}
|
|
|
|
let fieldData;
|
|
try {
|
|
fieldData = JSON.parse(body);
|
|
} catch (parseErr) {
|
|
return sdk.router.error(res, 'Invalid JSON in request body', 400);
|
|
}
|
|
|
|
const pluginDomain = process.env.PLUGIN_DOMAIN || 'unknown';
|
|
|
|
if (!fieldData.fieldKey || !fieldData.label || !fieldData.type) {
|
|
return sdk.router.error(res, 'Missing required fields: fieldKey, label, type', 400);
|
|
}
|
|
|
|
// Validate fieldKey format
|
|
if (typeof fieldData.fieldKey !== 'string' || fieldData.fieldKey.trim() === '') {
|
|
return sdk.router.error(res, 'fieldKey must be a non-empty string', 400);
|
|
}
|
|
|
|
// Validate type
|
|
const validTypes = ['string', 'number', 'boolean', 'array', 'object'];
|
|
if (!validTypes.includes(fieldData.type)) {
|
|
return sdk.router.error(res, `type must be one of: ${validTypes.join(', ')}`, 400);
|
|
}
|
|
|
|
await sdk.db.ready();
|
|
|
|
// Check if field already exists
|
|
const existing = await sdk.db.get('@profile/fields', { fieldKey: fieldData.fieldKey });
|
|
if (existing && existing.registeredBy !== pluginDomain) {
|
|
return sdk.router.error(res, 'Field already registered by another plugin', 409);
|
|
}
|
|
|
|
const fieldDef = {
|
|
fieldKey: fieldData.fieldKey,
|
|
registeredBy: pluginDomain,
|
|
label: fieldData.label,
|
|
type: fieldData.type,
|
|
description: fieldData.description || '',
|
|
defaultValue: fieldData.defaultValue ? JSON.stringify(fieldData.defaultValue) : ''
|
|
};
|
|
|
|
await sdk.db.insert('@profile/fields', fieldDef);
|
|
await sdk.db.flush();
|
|
|
|
return sdk.router.json(res, { success: true, field: fieldDef });
|
|
} catch (err) {
|
|
sdk.log.error('global.profile', `Error registering field: ${err.message}`);
|
|
|
|
if (err.message && err.message.includes('Unexpected token')) {
|
|
return sdk.router.error(res, 'Invalid JSON in request body', 400);
|
|
}
|
|
|
|
return sdk.router.error(res, err.message, 500);
|
|
}
|
|
});
|
|
return true;
|
|
}
|
|
|
|
return false; // Not handled by this module
|
|
}
|
|
|
|
module.exports = {
|
|
handleFieldsRoutes
|
|
};
|
|
|
|
|
|
|