@hsuite/users - User Management Module

👥 Comprehensive NestJS module for user management and profile operations

Advanced user management module providing complete user lifecycle management, profile operations, and authentication integration for HSuite applications.


Table of Contents


Quick Start

Installation

npm install @hsuite/users

Basic Setup

import { UsersModule } from '@hsuite/users';

@Module({
  imports: [
    UsersModule.forRootAsync({
      useFactory: () => ({
        enableProfilePictures: true,
        enableEmailVerification: true,
        passwordPolicy: {
          minLength: 8,
          requireUppercase: true,
          requireNumbers: true
        }
      })
    })
  ]
})
export class AppModule {}

Basic Usage

@Injectable()
export class UserService {
  constructor(private usersService: UsersService) {}

  async createUser(userData: CreateUserDto) {
    return await this.usersService.create(userData);
  }
}

Architecture

Core Components

👤 User Management

  • UsersService - Core user operations and business logic

  • UsersController - REST API endpoints for user operations

  • User Entity - Database entity with full user profile support

📧 Profile Management

  • Profile Updates - Real-time profile modification

  • Avatar Management - Profile picture upload and management

  • Preference Settings - User preferences and configuration

🔐 Authentication Integration

  • Password Management - Secure password hashing and validation

  • Email Verification - Email confirmation workflows

  • Account Status - User activation, suspension, and deletion

Module Structure

src/
├── entities/              # User database entities
├── dto/                   # Data transfer objects
├── interfaces/            # TypeScript interfaces
├── users.service.ts       # Core user service
├── users.controller.ts    # HTTP endpoints
├── users.module.ts        # Module configuration
└── index.ts              # Public API exports

API Reference

UsersModule

Static Methods

forRootAsync(options: UsersModuleAsyncOptions): DynamicModule

Configures the users module with async dependency injection.

interface UsersModuleAsyncOptions {
  imports?: any[];
  useFactory?: (...args: any[]) => Promise<IUsers.IOptions>;
  inject?: any[];
  useClass?: Type<any>;
}

UsersService

Core Methods

create(userData: CreateUserDto): Promise<User>

  • Creates a new user with validation

  • Parameters: userData - User registration information

  • Returns: Created user entity with assigned ID

findById(id: string): Promise<User>

  • Retrieves user by ID

  • Parameters: id - User unique identifier

  • Returns: User entity with full profile details

findByEmail(email: string): Promise<User>

  • Finds user by email address

  • Parameters: email - User email address

  • Returns: User entity or null if not found

update(id: string, updateData: UpdateUserDto): Promise<User>

  • Updates user profile information

  • Parameters: id, updateData - User ID and update data

  • Returns: Updated user entity

delete(id: string): Promise<void>

  • Soft deletes user account

  • Parameters: id - User ID to delete

  • Throws: Error if user has active dependencies

UsersController

Endpoints

POST /users - Create new userGET /users/:id - Get user by IDPUT /users/:id - Update user profileDELETE /users/:id - Delete user accountGET /users/:id/profile - Get user profilePUT /users/:id/avatar - Update profile picture


Guides

User Registration Guide

Learn how to implement user registration with validation and verification. Set up email confirmation, password policies, and user onboarding flows with comprehensive error handling.

Profile Management Guide

Manage user profiles, avatars, and preferences. Implement profile updates, image uploads, social links management, and privacy controls for user data.

Authentication Integration Guide

Integrate user management with authentication systems. Connect with JWT authentication, session management, and role-based access control systems.


Examples

Complete Module Configuration

import { UsersModule } from '@hsuite/users';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    UsersModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        enableProfilePictures: true,
        enableEmailVerification: true,
        passwordPolicy: {
          minLength: configService.get('PASSWORD_MIN_LENGTH', 8),
          requireUppercase: true,
          requireLowercase: true,
          requireNumbers: true,
          requireSpecialChars: false,
          maxLength: 128
        },
        profileSettings: {
          allowPublicProfiles: configService.get('ALLOW_PUBLIC_PROFILES', true),
          enableBio: true,
          enableSocialLinks: true,
          maxBioLength: 500
        },
        fileUpload: {
          maxAvatarSize: 5 * 1024 * 1024, // 5MB
          allowedImageTypes: ['jpg', 'jpeg', 'png', 'gif'],
          uploadPath: configService.get('UPLOAD_PATH', './uploads/avatars')
        }
      }),
      inject: [ConfigService]
    })
  ]
})
export class UserManagementModule {}

User Registration Service

import { Injectable, ConflictException, BadRequestException } from '@nestjs/common';
import { UsersService } from '@hsuite/users';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UserRegistrationService {
  constructor(private usersService: UsersService) {}

  async registerUser(registrationData: {
    email: string;
    password: string;
    firstName: string;
    lastName: string;
    organizationName?: string;
    acceptedTerms: boolean;
  }) {
    // Validate registration data
    await this.validateRegistrationData(registrationData);

    try {
      // Create user with pending email verification
      const user = await this.usersService.create({
        email: registrationData.email,
        password: registrationData.password,
        firstName: registrationData.firstName,
        lastName: registrationData.lastName,
        organizationName: registrationData.organizationName,
        status: 'pending_verification',
        emailVerified: false,
        termsAcceptedAt: new Date(),
        createdAt: new Date()
      });

      // Send verification email
      await this.sendVerificationEmail(user);

      return {
        success: true,
        user: {
          id: user.id,
          email: user.email,
          firstName: user.firstName,
          lastName: user.lastName,
          status: user.status
        },
        message: 'Registration successful. Please check your email for verification.'
      };
    } catch (error) {
      if (error.code === '23505') { // Unique constraint violation
        throw new ConflictException('Email address already registered');
      }
      throw error;
    }
  }

  async verifyEmail(verificationToken: string) {
    const user = await this.usersService.findByVerificationToken(verificationToken);
    
    if (!user) {
      throw new BadRequestException('Invalid verification token');
    }

    if (user.emailVerified) {
      return {
        success: true,
        message: 'Email already verified'
      };
    }

    // Update user status
    const updatedUser = await this.usersService.update(user.id, {
      emailVerified: true,
      status: 'active',
      verificationToken: null,
      verifiedAt: new Date()
    });

    return {
      success: true,
      user: {
        id: updatedUser.id,
        email: updatedUser.email,
        status: updatedUser.status,
        emailVerified: updatedUser.emailVerified
      },
      message: 'Email verified successfully'
    };
  }

  private async validateRegistrationData(data: any) {
    // Check if email is already registered
    const existingUser = await this.usersService.findByEmail(data.email);
    if (existingUser) {
      throw new ConflictException('Email address already registered');
    }

    // Validate terms acceptance
    if (!data.acceptedTerms) {
      throw new BadRequestException('Terms and conditions must be accepted');
    }

    // Additional validation logic...
  }

  private async sendVerificationEmail(user: any) {
    // Implementation for sending verification email
    console.log(`Verification email sent to ${user.email}`);
  }
}

Profile Management Service

import { Injectable, NotFoundException, BadRequestException } from '@nestjs/common';
import { UsersService } from '@hsuite/users';
import * as path from 'path';
import * as fs from 'fs/promises';

@Injectable()
export class ProfileManagementService {
  constructor(private usersService: UsersService) {}

  async updateProfile(userId: string, profileData: {
    firstName?: string;
    lastName?: string;
    bio?: string;
    phoneNumber?: string;
    dateOfBirth?: Date;
    socialLinks?: Record<string, string>;
    preferences?: Record<string, any>;
  }) {
    const user = await this.usersService.findById(userId);
    if (!user) {
      throw new NotFoundException('User not found');
    }

    // Validate profile data
    this.validateProfileData(profileData);

    const updatedUser = await this.usersService.update(userId, {
      ...profileData,
      updatedAt: new Date()
    });

    return {
      success: true,
      user: {
        id: updatedUser.id,
        firstName: updatedUser.firstName,
        lastName: updatedUser.lastName,
        bio: updatedUser.bio,
        phoneNumber: updatedUser.phoneNumber,
        socialLinks: updatedUser.socialLinks,
        updatedAt: updatedUser.updatedAt
      },
      message: 'Profile updated successfully'
    };
  }

  async uploadAvatar(userId: string, file: Express.Multer.File) {
    const user = await this.usersService.findById(userId);
    if (!user) {
      throw new NotFoundException('User not found');
    }

    // Validate file
    this.validateAvatarFile(file);

    try {
      // Save file
      const fileName = `${userId}_${Date.now()}${path.extname(file.originalname)}`;
      const uploadPath = path.join('./uploads/avatars', fileName);
      
      await fs.writeFile(uploadPath, file.buffer);

      // Delete old avatar if exists
      if (user.avatarUrl) {
        await this.deleteOldAvatar(user.avatarUrl);
      }

      // Update user with new avatar URL
      const updatedUser = await this.usersService.update(userId, {
        avatarUrl: `/uploads/avatars/${fileName}`,
        updatedAt: new Date()
      });

      return {
        success: true,
        avatarUrl: updatedUser.avatarUrl,
        message: 'Avatar updated successfully'
      };
    } catch (error) {
      throw new BadRequestException('Failed to upload avatar');
    }
  }

  async getUserProfile(userId: string, requestingUserId?: string) {
    const user = await this.usersService.findById(userId);
    if (!user) {
      throw new NotFoundException('User not found');
    }

    // Check if profile is public or user is viewing own profile
    const isOwnProfile = requestingUserId === userId;
    const isPublicProfile = user.profileVisibility === 'public';

    if (!isOwnProfile && !isPublicProfile) {
      throw new BadRequestException('Profile is private');
    }

    return {
      id: user.id,
      firstName: user.firstName,
      lastName: user.lastName,
      email: isOwnProfile ? user.email : null, // Only show email to owner
      bio: user.bio,
      avatarUrl: user.avatarUrl,
      socialLinks: user.socialLinks,
      joinedAt: user.createdAt,
      isVerified: user.emailVerified,
      profileVisibility: user.profileVisibility
    };
  }

  async updatePreferences(userId: string, preferences: {
    theme?: 'light' | 'dark';
    language?: string;
    notifications?: {
      email: boolean;
      push: boolean;
      sms: boolean;
    };
    privacy?: {
      profileVisibility: 'public' | 'private';
      showEmail: boolean;
      showPhoneNumber: boolean;
    };
  }) {
    const user = await this.usersService.findById(userId);
    if (!user) {
      throw new NotFoundException('User not found');
    }

    const updatedUser = await this.usersService.update(userId, {
      preferences: {
        ...user.preferences,
        ...preferences
      },
      updatedAt: new Date()
    });

    return {
      success: true,
      preferences: updatedUser.preferences,
      message: 'Preferences updated successfully'
    };
  }

  private validateProfileData(data: any) {
    if (data.bio && data.bio.length > 500) {
      throw new BadRequestException('Bio must be 500 characters or less');
    }

    if (data.phoneNumber && !/^\+?[\d\s\-\(\)]+$/.test(data.phoneNumber)) {
      throw new BadRequestException('Invalid phone number format');
    }

    // Additional validation...
  }

  private validateAvatarFile(file: Express.Multer.File) {
    const allowedTypes = ['image/jpeg', 'image/png', 'image/gif'];
    if (!allowedTypes.includes(file.mimetype)) {
      throw new BadRequestException('Invalid file type. Only JPEG, PNG, and GIF allowed');
    }

    const maxSize = 5 * 1024 * 1024; // 5MB
    if (file.size > maxSize) {
      throw new BadRequestException('File size too large. Maximum 5MB allowed');
    }
  }

  private async deleteOldAvatar(avatarUrl: string) {
    try {
      const filePath = path.join('.', avatarUrl);
      await fs.unlink(filePath);
    } catch (error) {
      console.error('Failed to delete old avatar:', error);
    }
  }
}

User Administration Service

import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common';
import { UsersService } from '@hsuite/users';

@Injectable()
export class UserAdministrationService {
  constructor(private usersService: UsersService) {}

  async getAllUsers(adminUserId: string, filters?: {
    status?: string;
    role?: string;
    search?: string;
    page?: number;
    limit?: number;
  }) {
    // Verify admin permissions
    await this.verifyAdminPermissions(adminUserId);

    const users = await this.usersService.findAll({
      status: filters?.status,
      role: filters?.role,
      search: filters?.search,
      page: filters?.page || 1,
      limit: filters?.limit || 50
    });

    return {
      users: users.data.map(user => ({
        id: user.id,
        email: user.email,
        firstName: user.firstName,
        lastName: user.lastName,
        status: user.status,
        role: user.role,
        emailVerified: user.emailVerified,
        createdAt: user.createdAt,
        lastLoginAt: user.lastLoginAt
      })),
      pagination: {
        total: users.total,
        page: users.page,
        limit: users.limit,
        totalPages: Math.ceil(users.total / users.limit)
      }
    };
  }

  async suspendUser(adminUserId: string, userId: string, reason: string) {
    await this.verifyAdminPermissions(adminUserId);

    const user = await this.usersService.findById(userId);
    if (!user) {
      throw new NotFoundException('User not found');
    }

    const updatedUser = await this.usersService.update(userId, {
      status: 'suspended',
      suspendedAt: new Date(),
      suspensionReason: reason,
      suspendedBy: adminUserId
    });

    // Log admin action
    await this.logAdminAction(adminUserId, 'suspend_user', userId, reason);

    return {
      success: true,
      user: {
        id: updatedUser.id,
        status: updatedUser.status,
        suspendedAt: updatedUser.suspendedAt
      },
      message: 'User suspended successfully'
    };
  }

  async reactivateUser(adminUserId: string, userId: string) {
    await this.verifyAdminPermissions(adminUserId);

    const user = await this.usersService.findById(userId);
    if (!user) {
      throw new NotFoundException('User not found');
    }

    const updatedUser = await this.usersService.update(userId, {
      status: 'active',
      suspendedAt: null,
      suspensionReason: null,
      suspendedBy: null,
      reactivatedAt: new Date(),
      reactivatedBy: adminUserId
    });

    await this.logAdminAction(adminUserId, 'reactivate_user', userId);

    return {
      success: true,
      user: {
        id: updatedUser.id,
        status: updatedUser.status,
        reactivatedAt: updatedUser.reactivatedAt
      },
      message: 'User reactivated successfully'
    };
  }

  async getUserStatistics() {
    const stats = await this.usersService.getStatistics();

    return {
      totalUsers: stats.total,
      activeUsers: stats.active,
      suspendedUsers: stats.suspended,
      pendingVerification: stats.pendingVerification,
      registrationsThisMonth: stats.registrationsThisMonth,
      lastLoginActivity: stats.lastLoginActivity
    };
  }

  private async verifyAdminPermissions(userId: string) {
    const user = await this.usersService.findById(userId);
    if (!user || user.role !== 'admin') {
      throw new ForbiddenException('Insufficient permissions');
    }
  }

  private async logAdminAction(
    adminId: string,
    action: string,
    targetUserId: string,
    reason?: string
  ) {
    // Implementation for logging admin actions
    console.log(`Admin ${adminId} performed ${action} on user ${targetUserId}`);
  }
}

Integration

Required Dependencies

{
  "@hsuite/users-types": "^2.0.9",
  "@hsuite/auth": "^2.1.2",
  "@hsuite/shared-types": "^2.0.0",
  "@nestjs/typeorm": "^10.0.0",
  "typeorm": "^0.3.0"
}

Environment Variables

# User Management
PASSWORD_MIN_LENGTH=8
ALLOW_PUBLIC_PROFILES=true
UPLOAD_PATH=./uploads/avatars

# Email Verification
EMAIL_VERIFICATION_REQUIRED=true
VERIFICATION_TOKEN_EXPIRY=24h

# File Upload
MAX_AVATAR_SIZE=5242880
ALLOWED_IMAGE_TYPES=jpg,jpeg,png,gif

Database Schema

-- Users table
CREATE TABLE users (
  id VARCHAR PRIMARY KEY,
  email VARCHAR UNIQUE NOT NULL,
  password_hash VARCHAR NOT NULL,
  first_name VARCHAR NOT NULL,
  last_name VARCHAR NOT NULL,
  bio TEXT,
  phone_number VARCHAR,
  date_of_birth DATE,
  avatar_url VARCHAR,
  social_links JSONB,
  preferences JSONB,
  status VARCHAR DEFAULT 'pending_verification',
  role VARCHAR DEFAULT 'user',
  email_verified BOOLEAN DEFAULT FALSE,
  profile_visibility VARCHAR DEFAULT 'public',
  terms_accepted_at TIMESTAMP,
  verified_at TIMESTAMP,
  suspended_at TIMESTAMP,
  suspension_reason TEXT,
  suspended_by VARCHAR,
  created_at TIMESTAMP DEFAULT NOW(),
  updated_at TIMESTAMP DEFAULT NOW()
);

-- User sessions
CREATE TABLE user_sessions (
  id VARCHAR PRIMARY KEY,
  user_id VARCHAR REFERENCES users(id),
  token_hash VARCHAR NOT NULL,
  expires_at TIMESTAMP NOT NULL,
  created_at TIMESTAMP DEFAULT NOW()
);

👥 User-Centric: Complete user lifecycle management from registration to administration.

🔐 Security First: Built-in password policies, email verification, and secure session management.

Built with ❤️ by the HbarSuite Team Copyright © 2024 HbarSuite. All rights reserved.

Last updated