/**
* @fileoverview Users entity for GitHubServer
* @file users.js
* @license Apache-2.0
* @version 3.0.0
*
* @author Michael Hay <michael.hay@mediumroast.io>
* @copyright 2025 Mediumroast, Inc. All rights reserved.
*/
import { BaseObjects } from '../baseObjects.js';
import { logger } from '../logger.js';
export class Users extends BaseObjects {
/**
* @constructor
* @param {string} token - GitHub API token
* @param {string} org - GitHub organization name
* @param {string} processName - Process name for locking
*/
constructor(token, org, processName) {
super(token, org, processName, 'Users');
// Add users-specific cache keys
this._cacheKeys.allUsers = 'all_users';
this._cacheKeys.authUser = 'auth_user';
this._cacheKeys.byLogin = `${this.objType}_byLogin`;
this._cacheKeys.byRole = `${this.objType}_byRole`;
this._cacheKeys.userActivity = `${this.objType}_activity`;
this._cacheKeys.orgActivity = 'org_activity';
// Set specific cache timeouts
this.cacheTimeouts.userDetails = 600000; // 10 minutes for user details
this.cacheTimeouts.authUser = 900000; // 15 minutes for auth user
this.cacheTimeouts.activity = 300000; // 5 minutes for activity data
}
/**
* Get all users with enhanced caching
* @returns {Promise<Array>} List of users
*/
async getAll() {
// Track this operation
const tracking = logger.trackOperation ?
logger.trackOperation(this.objType, 'getAll') :
{ end: () => {} };
try {
return await this.cache.getOrFetch(
this._cacheKeys.allUsers,
async () => this.serverCtl.getAllUsers(),
this.cacheTimeouts.userDetails || 600000,
[] // No dependencies
);
} catch (error) {
return this._createError(
`Failed to retrieve users: ${error.message}`,
error,
500
);
} finally {
tracking.end();
}
}
/**
* Get the authenticated user
* @returns {Promise<Array>} User information
*/
async getAuthenticatedUser() {
// Track this operation
const tracking = logger.trackOperation ?
logger.trackOperation(this.objType, 'getAuthenticatedUser') :
{ end: () => {} };
try {
return await this.cache.getOrFetch(
this._cacheKeys.authUser,
async () => this.serverCtl.getUser(),
this.cacheTimeouts.authUser || 900000,
[] // No dependencies
);
} catch (error) {
return this._createError(
`Failed to retrieve authenticated user: ${error.message}`,
error,
500
);
} finally {
tracking.end();
}
}
/**
* Find user by username/login
* @param {string} login - GitHub username
* @returns {Promise<Array>} User information
*/
async findByLogin(login) {
// Track this operation
const tracking = logger.trackOperation ?
logger.trackOperation(this.objType, 'findByLogin') :
{ end: () => {} };
try {
// Validate parameter
const validationError = this._validateParams(
{ login },
{ login: 'string' }
);
if (validationError) return validationError;
// Use cache with dependency on all users
const loginCacheKey = `${this._cacheKeys.byLogin}_${login}`;
return await this.cache.getOrFetch(
loginCacheKey,
async () => {
// Get all users
const allUsersResp = await this.getAll();
if (!allUsersResp[0]) {
return allUsersResp;
}
// Find user with matching login
const user = allUsersResp[2].find(u => u.login === login);
if (!user) {
return this._createError(
`User with login [${login}] not found`,
null,
404
);
}
return this._createSuccess(
`Found user with login [${login}]`,
user
);
},
this.cacheTimeouts.userDetails || 600000,
[this._cacheKeys.allUsers] // Depends on all users
);
} catch (error) {
return this._createError(
`Error finding user: ${error.message}`,
error,
500
);
} finally {
tracking.end();
}
}
/**
* Find user by role
* @param {string} role - Role to search for
* @returns {Promise<Array>} User information
*/
async findByRole(role) {
// Track this operation
const tracking = logger.trackOperation ?
logger.trackOperation(this.objType, 'findByRole') :
{ end: () => {} };
try {
// Validate parameter
const validationError = this._validateParams(
{ role },
{ role: 'string' }
);
if (validationError) return validationError;
// Use cache with dependency on all users
const roleCacheKey = `${this._cacheKeys.byRole}_${role}`;
return await this.cache.getOrFetch(
roleCacheKey,
async () => {
// Get all users
const allUsersResp = await this.getAll();
if (!allUsersResp[0]) {
return allUsersResp;
}
// Find users with matching role
const users = allUsersResp[2].filter(u => u.role === role);
if (users.length === 0) {
return this._createError(
`No users found with role [${role}]`,
null,
404
);
}
return this._createSuccess(
`Found ${users.length} users with role [${role}]`,
users
);
},
this.cacheTimeouts.userDetails || 600000,
[this._cacheKeys.allUsers] // Depends on all users
);
} catch (error) {
return this._createError(
`Error finding users by role: ${error.message}`,
error,
500
);
} finally {
tracking.end();
}
}
/**
* Update user role
* @param {string} login - GitHub username
* @param {string} newRole - New role to assign
* @returns {Promise<Array>} Operation result
*/
async updateUserRole(login, newRole) {
// Track this operation
const tracking = logger.trackOperation ?
logger.trackOperation(this.objType, 'updateUserRole') :
{ end: () => {} };
try {
// Validate parameters
const validationError = this._validateParams(
{ login, newRole },
{ login: 'string', newRole: 'string' }
);
if (validationError) return validationError;
// Additional validation for role values
const validRoles = ['admin', 'member', 'billing_manager'];
if (!validRoles.includes(newRole)) {
return this._createError(
`Invalid role: [${newRole}]. Must be one of: ${validRoles.join(', ')}`,
null,
400
);
}
// Update the user's role
const result = await this.serverCtl.updateOrgMembership(login, newRole);
if (result[0]) {
// Invalidate user caches
this.cache.invalidate(this._cacheKeys.allUsers);
this.cache.invalidate(`${this._cacheKeys.byLogin}_${login}`);
// Invalidate all role caches as they may have changed
validRoles.forEach(role => {
this.cache.invalidate(`${this._cacheKeys.byRole}_${role}`);
});
}
return result;
} catch (error) {
return this._createError(
`Failed to update user role: ${error.message}`,
error,
500
);
} finally {
tracking.end();
}
}
/**
* Get user activity metrics
* @param {string} login - GitHub username (optional)
* @returns {Promise<Array>} User activity metrics
*/
async getUserActivity(login = null) {
// Track this operation
const tracking = logger.trackOperation ?
logger.trackOperation(this.objType, 'getUserActivity') :
{ end: () => {} };
try {
// If login provided, get metrics for specific user
if (login) {
// Validate parameter
const validationError = this._validateParams(
{ login },
{ login: 'string' }
);
if (validationError) return validationError;
// Use cache with specific user activity key
const userActivityKey = `${this._cacheKeys.userActivity}_${login}`;
return await this.cache.getOrFetch(
userActivityKey,
async () => {
// Find the user first
const userResp = await this.findByLogin(login);
if (!userResp[0]) {
return userResp;
}
const activityResp = await this.serverCtl.getUserActivity(login);
if (!activityResp[0]) {
return activityResp;
}
return this._createSuccess(
`Retrieved activity for user [${login}]`,
activityResp[2]
);
},
this.cacheTimeouts.activity || 300000,
[`${this._cacheKeys.byLogin}_${login}`] // Depends on user data
);
} else {
// Get metrics for all users
return await this.cache.getOrFetch(
this._cacheKeys.orgActivity,
async () => {
const allUsersResp = await this.getAll();
if (!allUsersResp[0]) {
return allUsersResp;
}
const orgActivityResp = await this.serverCtl.getOrgActivity();
if (!orgActivityResp[0]) {
return orgActivityResp;
}
return this._createSuccess(
'Retrieved organization activity metrics',
orgActivityResp[2]
);
},
this.cacheTimeouts.activity || 300000,
[this._cacheKeys.allUsers] // Depends on all users
);
}
} catch (error) {
return this._createError(
`Error retrieving activity metrics: ${error.message}`,
error,
500
);
} finally {
tracking.end();
}
}
/**
* Invite a new user to the organization
* @param {string} email - User email
* @param {string} role - Role to assign (admin, member, billing_manager)
* @returns {Promise<Array>} Operation result
*/
async inviteUser(email, role = 'member') {
// Track this operation
const tracking = logger.trackOperation ?
logger.trackOperation(this.objType, 'inviteUser') :
{ end: () => {} };
try {
// Validate parameters
const validationError = this._validateParams(
{ email, role },
{ email: 'string', role: 'string' }
);
if (validationError) return validationError;
// Additional validation
if (!email.includes('@')) {
return this._createError('Invalid email format', null, 400);
}
// Check role validity
const validRoles = ['admin', 'member', 'billing_manager'];
if (!validRoles.includes(role)) {
return this._createError(
`Invalid role: [${role}]. Must be one of: ${validRoles.join(', ')}`,
null,
400
);
}
const result = await this.serverCtl.inviteOrgMember(email, role);
if (result[0]) {
// Invalidate user caches on success
this.cache.invalidate(this._cacheKeys.allUsers);
this.cache.invalidate(`${this._cacheKeys.byRole}_${role}`);
}
return result;
} catch (error) {
return this._createError(
`Failed to invite user: ${error.message}`,
error,
500
);
} finally {
tracking.end();
}
}
/**
* Remove a user from the organization
* @param {string} login - GitHub username
* @returns {Promise<Array>} Operation result
*/
async removeUser(login) {
// Track this operation
const tracking = logger.trackOperation ?
logger.trackOperation(this.objType, 'removeUser') :
{ end: () => {} };
try {
// Validate parameter
const validationError = this._validateParams(
{ login },
{ login: 'string' }
);
if (validationError) return validationError;
// Find user to get their current role before removal
const userResp = await this.findByLogin(login);
let userRole = null;
if (userResp[0] && userResp[2] && userResp[2].role) {
userRole = userResp[2].role;
}
const result = await this.serverCtl.removeOrgMember(login);
if (result[0]) {
// Invalidate user caches on success
this.cache.invalidate(this._cacheKeys.allUsers);
this.cache.invalidate(`${this._cacheKeys.byLogin}_${login}`);
// Also invalidate role cache if we knew the user's role
if (userRole) {
this.cache.invalidate(`${this._cacheKeys.byRole}_${userRole}`);
} else {
// If we don't know the role, invalidate all role caches
this.cache.invalidate(`${this._cacheKeys.byRole}_admin`);
this.cache.invalidate(`${this._cacheKeys.byRole}_member`);
this.cache.invalidate(`${this._cacheKeys.byRole}_billing_manager`);
}
}
return result;
} catch (error) {
return this._createError(
`Failed to remove user: ${error.message}`,
error,
500
);
} finally {
tracking.end();
}
}
}