// File: src/modules/user/user.service.ts // บันทึกการแก้ไข: แก้ไข Error TS1272 โดยใช้ 'import type' สำหรับ Cache interface (T1.3) import { Injectable, NotFoundException, ConflictException, Inject, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { CACHE_MANAGER } from '@nestjs/cache-manager'; import type { Cache } from 'cache-manager'; // ✅ FIX: เพิ่ม 'type' ตรงนี้ import * as bcrypt from 'bcrypt'; import { User } from './entities/user.entity'; import { Role } from './entities/role.entity'; import { Permission } from './entities/permission.entity'; import { CreateUserDto } from './dto/create-user.dto'; import { UpdateUserDto } from './dto/update-user.dto'; @Injectable() export class UserService { constructor( @InjectRepository(User) private usersRepository: Repository, @InjectRepository(Role) private roleRepository: Repository, @InjectRepository(Permission) private permissionRepository: Repository, @Inject(CACHE_MANAGER) private cacheManager: Cache ) {} // 1. สร้างผู้ใช้ (Hash Password ก่อนบันทึก) async create(createUserDto: CreateUserDto): Promise { const salt = await bcrypt.genSalt(); const hashedPassword = await bcrypt.hash(createUserDto.password, salt); const newUser = this.usersRepository.create({ ...createUserDto, password: hashedPassword, }); try { return await this.usersRepository.save(newUser); } catch (error: any) { if (error.code === 'ER_DUP_ENTRY') { throw new ConflictException('Username or Email already exists'); } throw error; } } // 2. ดึงข้อมูลทั้งหมด (Search & Pagination) async findAll(params?: any): Promise { const { search, roleId, primaryOrganizationId, page = 1, limit = 100, } = params || {}; // Create query builder const query = this.usersRepository .createQueryBuilder('user') .leftJoinAndSelect('user.preference', 'preference') // Optional .leftJoinAndSelect('user.assignments', 'assignments') .leftJoinAndSelect('assignments.role', 'role') .select([ 'user.user_id', 'user.username', 'user.email', 'user.firstName', 'user.lastName', 'user.lineId', 'user.primaryOrganizationId', 'user.isActive', 'user.createdAt', 'user.updatedAt', 'assignments.id', 'role.roleId', 'role.roleName', ]); // Apply Filters if (search) { query.andWhere( '(user.username LIKE :search OR user.email LIKE :search OR user.firstName LIKE :search OR user.lastName LIKE :search)', { search: `%${search}%` } ); } if (primaryOrganizationId) { query.andWhere('user.primaryOrganizationId = :orgId', { orgId: primaryOrganizationId, }); } if (roleId) { query.andWhere('role.roleId = :roleId', { roleId }); } // Pagination query.skip((page - 1) * limit).take(limit); const [data, total] = await query.getManyAndCount(); return { data, total, page, limit, totalPages: Math.ceil(total / limit), }; } // 3. ดึงข้อมูลรายคน async findOne(id: number): Promise { const user = await this.usersRepository.findOne({ where: { user_id: id }, relations: [ 'preference', 'assignments', 'assignments.role', 'assignments.role.permissions', // [FIX] Required for RBAC AbilityFactory ], }); if (!user) { throw new NotFoundException(`User with ID ${id} not found`); } return user; } async findOneByUsername(username: string): Promise { return this.usersRepository.findOne({ where: { username } }); } // 4. แก้ไขข้อมูล async update(id: number, updateUserDto: UpdateUserDto): Promise { const user = await this.findOne(id); if (updateUserDto.password) { const salt = await bcrypt.genSalt(); updateUserDto.password = await bcrypt.hash(updateUserDto.password, salt); } const updatedUser = this.usersRepository.merge(user, updateUserDto); const savedUser = await this.usersRepository.save(updatedUser); // ⚠️ สำคัญ: เมื่อมีการแก้ไขข้อมูล User ต้องเคลียร์ Cache สิทธิ์เสมอ await this.clearUserCache(id); return savedUser; } // 5. ลบผู้ใช้ (Soft Delete) async remove(id: number): Promise { const result = await this.usersRepository.softDelete(id); if (result.affected === 0) { throw new NotFoundException(`User with ID ${id} not found`); } // เคลียร์ Cache เมื่อลบ await this.clearUserCache(id); } async findDocControlIdByOrg(organizationId: number): Promise { const user = await this.usersRepository.findOne({ where: { primaryOrganizationId: organizationId }, }); return user ? user.user_id : null; } /** * ✅ ดึงสิทธิ์ (Permission) โดยใช้ Caching Strategy * TTL: 30 นาที (ตาม Requirement 6.5.2) */ async getUserPermissions(userId: number): Promise { const cacheKey = `permissions:user:${userId}`; // 1. ลองดึงจาก Cache ก่อน const cachedPermissions = await this.cacheManager.get(cacheKey); if (cachedPermissions) { return cachedPermissions; } // 2. ถ้าไม่มีใน Cache ให้ Query จาก DB (View: v_user_all_permissions) const permissions = await this.usersRepository.query( `SELECT permission_name FROM v_user_all_permissions WHERE user_id = ?`, [userId] ); const permissionList = permissions.map((row: any) => row.permission_name); // 3. บันทึกลง Cache (TTL 1800 วินาที = 30 นาที) await this.cacheManager.set(cacheKey, permissionList, 1800 * 1000); return permissionList; } // --- Roles & Permissions (Helper for Admin/UI) --- async findAllRoles(): Promise { return this.roleRepository.find({ relations: ['permissions'] }); } async findAllPermissions(): Promise { return this.permissionRepository.find(); } async updateRolePermissions(roleId: number, permissionIds: number[]) { const role = await this.roleRepository.findOne({ where: { roleId }, relations: ['permissions'], }); if (!role) { throw new NotFoundException(`Role ID ${roleId} not found`); } // Load permissions entities const permissions = []; if (permissionIds.length > 0) { // Note: findByIds is deprecated in newer TypeORM, uses In() instead // but if current version supports it or using a simplified query: const perms = await this.permissionRepository .createQueryBuilder('p') .where('p.permissionId IN (:...ids)', { ids: permissionIds }) .getMany(); permissions.push(...perms); } role.permissions = permissions; return this.roleRepository.save(role); } /** * Helper สำหรับล้าง Cache เมื่อมีการเปลี่ยนแปลงสิทธิ์หรือบทบาท */ async clearUserCache(userId: number): Promise { await this.cacheManager.del(`permissions:user:${userId}`); } }