Files
p2ns/plugin-sites/global.profile/routes/profiles.js
T
Raven Scott d57433d3e0 Modernize Holepunch stack for HyperDB 6 and current replication APIs
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.
2026-05-27 21:02:17 -04:00

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
};