Skip to content

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

  1. Regular User (default)
  2. Can create services
  3. Can place orders
  4. Can chat
  5. Can leave comments

  6. Staff (isStaff = true)

  7. All regular user permissions
  8. Can access admin endpoints (if granted permissions)
  9. Requires specific permissions for admin actions

  10. Superuser (isSuperuser = true)

  11. All permissions granted automatically
  12. Can manage users
  13. Can manage services
  14. Can resolve disputes
  15. 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

GET /users/me
Authorization: Bearer <access_token>

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

GET /users/:id

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)

GET /users?page=1&limit=20&search=john&isActive=true
Authorization: Bearer <access_token>

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.