User Management
User management encompasses user profiles, roles, permissions, portfolio management (skills, projects, experience, education), and account settings. It provides comprehensive user lifecycle management.
Overview
The user management system provides:
- User profiles – Personal information, title, description
- Role-based access control – Staff, superuser, custom roles
- Permission system – Granular permissions (VIEW_USERS, MANAGE_SERVICES, etc.)
- Portfolio management – Skills, projects, experience, education
- Account settings – Avatar, password change, profile updates
- User search and filtering – Admin user management
User Entity
export class User {
id: string;
fullName: string;
email: string;
username: string;
passwordHash: string;
isActive: boolean;
isStaff: boolean;
isSuperuser: boolean;
isKYCVerified: boolean;
title: string; // Professional title (e.g., "Full Stack Developer")
description: string; // Bio/about text
notes: string; // Admin notes
lastLogin: Date | null;
createdAt: Date;
updatedAt: Date;
private permissions?: Permission[];
hasPermission(permission: Permission): boolean {
if (this.isSuperuser) {
return true; // Superusers have all permissions
}
return this.permissions?.includes(permission) ?? false;
}
}
User Roles
Built-in Roles
- Regular User (default)
- Can create services
- Can place orders
- Can chat
-
Can leave comments
-
Staff (
isStaff = true) - All regular user permissions
- Can access admin endpoints (if granted permissions)
-
Requires specific permissions for admin actions
-
Superuser (
isSuperuser = true) - All permissions granted automatically
- Can manage users
- Can manage services
- Can resolve disputes
- Full system access
Custom Roles
export class Role {
id: string;
name: string;
permissions: Permission[];
createdAt: Date;
updatedAt: Date;
}
Example roles: - Moderator – APPROVE_SERVICES, RESOLVE_DISPUTES - Support – VIEW_USERS, VIEW_ORDERS, MANAGE_KYC - Finance – VIEW_ACCOUNTS, APPROVE_WITHDRAWALS
Permissions
export enum Permission {
// User permissions
VIEW_USERS = 'view_users',
MANAGE_USERS = 'manage_users',
// Service permissions
VIEW_SERVICES = 'view_services',
APPROVE_SERVICES = 'approve_services',
// Order permissions
VIEW_ORDERS = 'view_orders',
MANAGE_ORDERS = 'manage_orders',
RESOLVE_DISPUTES = 'resolve_disputes',
// KYC permissions
VIEW_KYC = 'view_kyc',
MANAGE_KYC = 'manage_kyc',
// Account permissions
VIEW_ACCOUNTS = 'view_accounts',
APPROVE_WITHDRAWALS = 'approve_withdrawals',
}
Use Cases
Create User
async function create(
data: any,
userRepository: UserRepository,
notifier: Notifier
) {
const validator = new UserCreateValidator(userRepository);
const validatedData = await validator.validate(data);
const passwordHash = await hasher.hash(validatedData.password);
const user = User.create(
userRepository.nextId(),
validatedData.fullName,
validatedData.username || generateUsername(validatedData.fullName),
validatedData.email,
passwordHash
);
await userRepository.add(user);
const activationToken = getActivationToken(user.id);
notifier.sendWelcomeMessage(activationToken, user);
return toUserDto(user);
}
Update User Profile
async function updateMe(
data: any,
userRepository: UserRepository,
storage: StorageService,
user: User
) {
const validatedData = await new UserUpdateValidator().validate(data);
const { avatar, ...updateData } = validatedData;
// Handle avatar upload
if (avatar) {
await storage.attachOrReplace(
avatar,
UploadObjectType.USER,
user.id,
UploadTag.AVATAR
);
}
const updatedUser = await userRepository.update(user.id, updateData);
return toUserDto(updatedUser);
}
List Users (Admin)
async function list(
userRepository: UserRepository,
serviceRepository: ServiceRepository,
filters: Record<string, string>,
user: User
) {
// Check permission
if (!(user.isStaff && user.hasPermission(Permission.VIEW_USERS))) {
throw new ForbiddenError();
}
const validatedFilters = await new ListUsersQueryValidator().validate(filters);
const users = await userRepository.all(validatedFilters);
// Enrich with service counts
const serviceCounts = await serviceRepository.countByUserIds(
users.results.map(u => u.id)
);
return {
...users,
results: users.results.map(user => ({
...toUserDto(user),
servicesCount: serviceCounts[user.id] || 0,
})),
};
}
Retrieve User Profile
async function retrieveUser(
id: string,
userRepository: UserRepository,
orderRepository: OrderRepository,
serviceRepository: ServiceRepository,
competencyRepository: CompetencyRepository,
commentRepository: CommentRepository
) {
const user = await userRepository.get(id);
// Get portfolio data
const skills = await competencyRepository.getSkillsByUserId(id);
const projects = await competencyRepository.getProjectsByUserId(id);
const experiences = await competencyRepository.getExperiencesByUserId(id);
const education = await competencyRepository.getEducationByUserId(id);
// Get statistics
const services = await serviceRepository.findAll({ ownerId: id });
const completedOrders = await orderRepository.findAll({
sellerId: id,
state: 'completed',
});
const comments = await commentRepository.getByServiceIds(
services.results.map(s => s.id)
);
return {
...toUserDto(user),
portfolio: {
skills: skills.map(toSkillDto),
projects: projects.map(toProjectDto),
experiences: experiences.map(toExperienceDto),
education: education.map(toEducationDto),
},
stats: {
servicesCount: services.count,
completedOrdersCount: completedOrders.count,
averageRating: calculateAverageRating(comments),
reviewsCount: comments.length,
},
};
}
Activate User Account
async function activate(
data: any,
userRepository: UserRepository
) {
const { token } = await new UserActivateValidator().validate(data);
// Decode user ID from token
const payload = jwt.verify(token, process.env.ACTIVATION_SECRET_KEY!);
const userId = payload.sub;
const user = await userRepository.get(userId);
user.activate();
await userRepository.update(userId, { isActive: true });
return toUserDto(user);
}
Deactivate User (Admin)
async function deactivate(
userId: string,
user: User,
userRepository: UserRepository
) {
// Check permission
if (!(user.isStaff && user.hasPermission(Permission.MANAGE_USERS))) {
throw new ForbiddenError();
}
const targetUser = await userRepository.get(userId);
targetUser.deactivate();
await userRepository.update(userId, { isActive: false });
return toUserDto(targetUser);
}
Create Staff User (Superuser only)
async function createStaffUser(
data: any,
userRepository: UserRepository,
userRoleRepository: UserRoleRepository,
user: User
) {
// Only superusers can create staff
if (!user.isSuperuser) {
throw new ForbiddenError();
}
const validatedData = await new CreateStaffUserValidator().validate(data);
const passwordHash = await hasher.hash(validatedData.password);
const staffUser = User.create(
userRepository.nextId(),
validatedData.fullName,
validatedData.username,
validatedData.email,
passwordHash,
null, // lastLogin
true, // isActive
true // isStaff
);
await userRepository.add(staffUser);
// Assign role if provided
if (validatedData.roleId) {
await userRoleRepository.assignRole(staffUser.id, validatedData.roleId);
}
return toUserDto(staffUser);
}
Portfolio Management
Skills
export class Skill {
id: string;
userId: string;
name: string;
level: 'beginner' | 'intermediate' | 'advanced' | 'expert';
yearsOfExperience: number;
createdAt: Date;
}
// Add skill
async function addSkill(
data: any,
competencyRepository: CompetencyRepository,
user: User
) {
const validatedData = await new SkillCreateValidator().validate(data);
const skill = Skill.create(
competencyRepository.nextId(),
user.id,
validatedData.name,
validatedData.level,
validatedData.yearsOfExperience
);
await competencyRepository.addSkill(skill);
return toSkillDto(skill);
}
Projects
export class Project {
id: string;
userId: string;
title: string;
description: string;
url: string | null;
startDate: Date;
endDate: Date | null;
technologies: string[];
createdAt: Date;
}
// Add project
async function addProject(
data: any,
competencyRepository: CompetencyRepository,
user: User
) {
const validatedData = await new ProjectCreateValidator().validate(data);
const project = Project.create(
competencyRepository.nextId(),
user.id,
validatedData.title,
validatedData.description,
validatedData.url,
validatedData.startDate,
validatedData.endDate,
validatedData.technologies
);
await competencyRepository.addProject(project);
return toProjectDto(project);
}
Experience
export class Experience {
id: string;
userId: string;
company: string;
position: string;
description: string;
startDate: Date;
endDate: Date | null; // null = currently working
location: string;
createdAt: Date;
}
Education
export class Education {
id: string;
userId: string;
institution: string;
degree: string;
fieldOfStudy: string;
startDate: Date;
endDate: Date | null;
grade: string | null;
description: string;
createdAt: Date;
}
User Statistics
function getUserStats(
user: User,
services: Service[],
orders: Order[],
comments: Comment[]
) {
return {
// Service metrics
totalServices: services.length,
activeServices: services.filter(s => s.status === 'active').length,
averageServicePrice: calculateAverage(services.map(s => s.price)),
// Order metrics (as seller)
totalOrders: orders.length,
completedOrders: orders.filter(o => o.state === 'completed').length,
completionRate: orders.length > 0
? (orders.filter(o => o.state === 'completed').length / orders.length) * 100
: 0,
// Review metrics
totalReviews: comments.length,
averageRating: calculateAverageRating(comments),
ratingDistribution: {
5: comments.filter(c => c.rating === 5).length,
4: comments.filter(c => c.rating === 4).length,
3: comments.filter(c => c.rating === 3).length,
2: comments.filter(c => c.rating === 2).length,
1: comments.filter(c => c.rating === 1).length,
},
// Activity metrics
memberSince: user.createdAt,
lastLogin: user.lastLogin,
isKYCVerified: user.isKYCVerified,
};
}
Validation Rules
User Creation
export class UserCreateValidator extends BaseValidator {
schema = z.object({
email: z
.string()
.email('Invalid email format')
.transform(normalizeEmail),
password: z
.string()
.min(8, 'Password must be at least 8 characters')
.regex(/[A-Z]/, 'Password must contain uppercase letter')
.regex(/[a-z]/, 'Password must contain lowercase letter')
.regex(/[0-9]/, 'Password must contain number'),
fullName: z
.string()
.min(2, 'Full name too short')
.max(100, 'Full name too long'),
username: z
.string()
.min(3, 'Username too short')
.max(20, 'Username too long')
.regex(/^[a-z0-9_]+$/, 'Username can only contain lowercase letters, numbers, and underscores')
.optional(),
});
async customValidation(data: any) {
// Check email uniqueness
const emailExists = await this.userRepository.exists({ email: data.email });
if (emailExists) {
this.errors.email = ['Email already registered'];
}
// Check username uniqueness if provided
if (data.username) {
const usernameExists = await this.userRepository.exists({ username: data.username });
if (usernameExists) {
this.errors.username = ['Username already taken'];
}
}
}
}
User Update
export class UserUpdateValidator extends BaseValidator {
schema = z.object({
fullName: z.string().min(2).max(100).optional(),
username: z
.string()
.min(3)
.max(20)
.regex(/^[a-z0-9_]+$/)
.optional(),
title: z.string().max(100).optional(),
description: z.string().max(1000).optional(),
avatar: z.string().optional(), // Upload ID
});
}
API Endpoints
Get Current User
Response:
{
"id": "user-123",
"email": "user@example.com",
"fullName": "John Doe",
"username": "johndoe",
"title": "Full Stack Developer",
"description": "Passionate about building great software",
"isActive": true,
"isKYCVerified": true,
"createdAt": "2024-01-01T00:00:00Z"
}
Update Current User
PATCH /users/me
Authorization: Bearer <access_token>
Content-Type: application/json
{
"fullName": "John Smith",
"title": "Senior Full Stack Developer",
"description": "10+ years of experience"
}
Get User Profile
Response:
{
"id": "user-123",
"fullName": "John Doe",
"username": "johndoe",
"title": "Full Stack Developer",
"description": "Passionate developer",
"portfolio": {
"skills": [...],
"projects": [...],
"experiences": [...],
"education": [...]
},
"stats": {
"servicesCount": 5,
"completedOrdersCount": 23,
"averageRating": 4.8,
"reviewsCount": 20
}
}
List Users (Admin)
Best Practices
✅ Do
- Validate email uniqueness – Check before registration
- Generate usernames – Auto-generate if not provided
- Normalize emails – Lowercase and trim
- Check permissions – Use hasPermission() before actions
- Enrich user profiles – Include stats and portfolio
- Log user activities – Track important actions
❌ Don't
- Don't expose password hashes – Never return in API
- Don't skip permission checks – Always verify authorization
- Don't allow username changes – Keep usernames stable
- Don't delete users – Deactivate instead
- Don't expose admin notes – Keep internal notes private
Summary
The user management system provides:
- Complete user lifecycle management
- Role-based access control with granular permissions
- Portfolio management for professionals
- User statistics and analytics
- Comprehensive validation rules
- Admin tools for user management
This system enables flexible user management while maintaining security and data integrity.