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.
120 lines
3.5 KiB
JavaScript
120 lines
3.5 KiB
JavaScript
/**
|
|
* Global Profile Plugin - Profiles Routes
|
|
*
|
|
* Handles profiles listing and search API endpoints
|
|
*/
|
|
|
|
const sdk = require('../../../includes/plugins/sdk');
|
|
const { getAllProfilesFromDB } = require('../database');
|
|
const { dbErrorResponse } = require('../db-errors');
|
|
|
|
/**
|
|
* Handle profiles-related routes
|
|
*/
|
|
async function handleProfilesRoutes(req, res, path, query, method) {
|
|
// GET /api/profiles - List all profiles with search/filter, pagination, and sorting
|
|
if (path === 'api/profiles' && method === 'GET') {
|
|
try {
|
|
const search = query.search || '';
|
|
const tagFilter = query.tag || '';
|
|
const limit = parseInt(query.limit || '100', 10);
|
|
const offset = parseInt(query.offset || '0', 10);
|
|
const sort = query.sort || 'lastUpdated'; // lastUpdated, displayName
|
|
|
|
// Validate pagination params
|
|
if (limit < 1 || limit > 1000) {
|
|
return sdk.router.error(res, 'Limit must be between 1 and 1000', 400);
|
|
}
|
|
if (offset < 0) {
|
|
return sdk.router.error(res, 'Offset must be >= 0', 400);
|
|
}
|
|
if (sort !== 'lastUpdated' && sort !== 'displayName') {
|
|
return sdk.router.error(res, 'Sort must be either "lastUpdated" or "displayName"', 400);
|
|
}
|
|
|
|
const allProfiles = await getAllProfilesFromDB();
|
|
let filtered = allProfiles;
|
|
|
|
// Apply search filter
|
|
if (search) {
|
|
const searchLower = search.toLowerCase();
|
|
filtered = filtered.filter(p =>
|
|
(p.displayName && p.displayName.toLowerCase().includes(searchLower)) ||
|
|
(p.bio && p.bio.toLowerCase().includes(searchLower))
|
|
);
|
|
}
|
|
|
|
// Apply tag filter
|
|
if (tagFilter) {
|
|
filtered = filtered.filter(p => {
|
|
try {
|
|
const tags = p.tags ? JSON.parse(p.tags) : [];
|
|
return tags.some(tag => tag.toLowerCase() === tagFilter.toLowerCase());
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
});
|
|
}
|
|
|
|
// Sort
|
|
if (sort === 'lastUpdated') {
|
|
filtered.sort((a, b) => {
|
|
const aTime = a.lastUpdated || 0;
|
|
const bTime = b.lastUpdated || 0;
|
|
return bTime - aTime; // Descending (newest first)
|
|
});
|
|
} else if (sort === 'displayName') {
|
|
filtered.sort((a, b) => {
|
|
const aName = (a.displayName || '').toLowerCase();
|
|
const bName = (b.displayName || '').toLowerCase();
|
|
return aName.localeCompare(bName); // Ascending (A-Z)
|
|
});
|
|
}
|
|
|
|
const total = filtered.length;
|
|
|
|
// Paginate
|
|
const paginated = filtered.slice(offset, offset + limit);
|
|
|
|
// Parse JSON fields and add online status
|
|
const profiles = paginated.map(p => {
|
|
try {
|
|
p.customFields = p.customFields ? JSON.parse(p.customFields) : {};
|
|
p.tags = p.tags ? JSON.parse(p.tags) : [];
|
|
} catch (err) {
|
|
p.customFields = {};
|
|
p.tags = [];
|
|
}
|
|
|
|
// Add online status
|
|
p.online = sdk.state.peerIds.includes(p.peerId);
|
|
|
|
return p;
|
|
});
|
|
|
|
return sdk.router.json(res, {
|
|
profiles,
|
|
pagination: {
|
|
total,
|
|
limit,
|
|
offset,
|
|
hasMore: offset + limit < total
|
|
}
|
|
});
|
|
} catch (err) {
|
|
sdk.log.error('global.profile', `Error getting profiles: ${err.message}`);
|
|
const { status, body } = dbErrorResponse(err);
|
|
return sdk.router.json(res, body, status, req);
|
|
}
|
|
}
|
|
|
|
return false; // Not handled by this module
|
|
}
|
|
|
|
module.exports = {
|
|
handleProfilesRoutes
|
|
};
|
|
|
|
|
|
|