Admin Dashboard Backend (Phases 1-4) (#5)
Some checks failed
Deploy Apartment API / deploy (push) Failing after 5m1s

Co-authored-by: Stephen Minakian <stephenminakian@gmail.com>
Co-committed-by: Stephen Minakian <stephenminakian@gmail.com>
This commit is contained in:
2026-01-22 14:11:43 -07:00
committed by stephen
parent d6e56a6e40
commit 81e5682ab1
16 changed files with 11215 additions and 31 deletions

View File

@ -73,15 +73,37 @@ async function findById(db, id) {
* @param {Db} db - MongoDB database instance
* @param {string|ObjectId} id - User's MongoDB _id
* @param {boolean} isActive - New active status
* @param {string|ObjectId|null} adminId - Admin performing the action (required when disabling)
* @returns {Promise<Object>} Update result
*/
async function setUserActive(db, id, isActive) {
async function setUserActive(db, id, isActive, adminId = null) {
// Convert string to ObjectId if needed
const objectId = typeof id === 'string' ? new ObjectId(id) : id;
let updateDoc;
if (isActive) {
// Enabling: clear disabledAt and disabledBy
updateDoc = {
$set: { isActive: true, disabledAt: null, disabledBy: null }
};
} else {
// Disabling: set disabledAt and disabledBy
// Convert string to ObjectId if needed, keep ObjectId as-is
const adminObjectId = adminId && typeof adminId === 'string'
? new ObjectId(adminId)
: adminId;
updateDoc = {
$set: {
isActive: false,
disabledAt: new Date(),
disabledBy: adminObjectId
}
};
}
const result = await db.collection(USER_COLLECTION).findOneAndUpdate(
{ _id: objectId },
{ $set: { isActive } },
updateDoc,
{ returnDocument: 'after' }
);
@ -115,14 +137,117 @@ async function createIndexes(db) {
{ isActive: 1, lastLoginAt: -1 }
);
// Index on role for admin queries
await collection.createIndex({ role: 1 });
// Compound index on isActive and role for filtered admin queries
await collection.createIndex({ isActive: 1, role: 1 });
// Index on createdAt for recent registrations
await collection.createIndex({ createdAt: -1 });
// Index on lastLoginAt for recently active users
await collection.createIndex({ lastLoginAt: -1 });
console.log('User collection indexes created successfully');
}
/**
* Get paginated list of users with optional filtering and sorting
*
* @param {Db} db - MongoDB database instance
* @param {Object} options - Query options
* @param {number} options.page - Page number (1-indexed)
* @param {number} options.limit - Items per page (max 100)
* @param {string} options.search - Search term for name/email
* @param {string} options.status - Filter by status: 'all', 'active', 'disabled'
* @param {string} options.sort - Sort field: 'createdAt', 'lastLoginAt', 'loginCount'
* @param {string} options.order - Sort order: 'asc', 'desc'
* @returns {Promise<{users: Array, pagination: Object}>}
*/
async function getPaginatedUsers(db, options = {}) {
const page = Math.max(1, parseInt(options.page) || 1);
const limit = Math.min(100, Math.max(1, parseInt(options.limit) || 20));
const skip = (page - 1) * limit;
// Build filter
const filter = {};
if (options.search) {
const searchRegex = new RegExp(options.search, 'i');
filter.$or = [{ name: searchRegex }, { email: searchRegex }];
}
if (options.status === 'active') {
filter.isActive = true;
} else if (options.status === 'disabled') {
filter.isActive = false;
}
// Build sort
const sortField = ['createdAt', 'lastLoginAt', 'loginCount'].includes(options.sort)
? options.sort
: 'createdAt';
const sortOrder = options.order === 'asc' ? 1 : -1;
const sort = { [sortField]: sortOrder };
const collection = db.collection(USER_COLLECTION);
// Execute queries in parallel
const [users, total] = await Promise.all([
collection.find(filter).sort(sort).skip(skip).limit(limit).toArray(),
collection.countDocuments(filter)
]);
return {
users,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit)
}
};
}
/**
* Update a user's role
*
* @param {Db} db - MongoDB database instance
* @param {string|ObjectId} id - User's MongoDB _id
* @param {string} role - New role ('user' or 'admin')
* @returns {Promise<Object|null>} Updated user or null
*/
async function updateUserRole(db, id, role) {
const objectId = typeof id === 'string' ? new ObjectId(id) : id;
const result = await db.collection(USER_COLLECTION).findOneAndUpdate(
{ _id: objectId },
{ $set: { role } },
{ returnDocument: 'after' }
);
return result;
}
/**
* Check if a string is a valid MongoDB ObjectId
*
* @param {string} id - String to validate
* @returns {boolean} True if valid ObjectId format
*/
function isValidObjectId(id) {
if (typeof id !== 'string') return false;
if (!id || !id.trim()) return false;
return /^[0-9a-fA-F]{24}$/.test(id);
}
module.exports = {
USER_COLLECTION,
findOrCreateUser,
findByGoogleId,
findById,
setUserActive,
createIndexes
createIndexes,
getPaginatedUsers,
updateUserRole,
isValidObjectId
};