251124:1700 Ready to Phase 7
This commit is contained in:
@@ -1,10 +1,16 @@
|
||||
// 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 { CreateUserDto } from './dto/create-user.dto';
|
||||
@@ -14,26 +20,23 @@ import { UpdateUserDto } from './dto/update-user.dto';
|
||||
export class UserService {
|
||||
constructor(
|
||||
@InjectRepository(User)
|
||||
private usersRepository: Repository<User>, // ✅ ชื่อตัวแปรจริงคือ usersRepository
|
||||
private usersRepository: Repository<User>,
|
||||
@Inject(CACHE_MANAGER) private cacheManager: Cache,
|
||||
) {}
|
||||
|
||||
// 1. สร้างผู้ใช้ (Hash Password ก่อนบันทึก)
|
||||
async create(createUserDto: CreateUserDto): Promise<User> {
|
||||
// สร้าง Salt และ Hash Password
|
||||
const salt = await bcrypt.genSalt();
|
||||
const hashedPassword = await bcrypt.hash(createUserDto.password, salt);
|
||||
|
||||
// เตรียมข้อมูล (เปลี่ยน password ธรรมดา เป็น password_hash)
|
||||
const newUser = this.usersRepository.create({
|
||||
...createUserDto,
|
||||
password: hashedPassword,
|
||||
});
|
||||
|
||||
try {
|
||||
// บันทึกลง DB
|
||||
return await this.usersRepository.save(newUser);
|
||||
} catch (error: any) {
|
||||
// เช็ค Error กรณี Username/Email ซ้ำ (MySQL Error Code 1062)
|
||||
if (error.code === 'ER_DUP_ENTRY') {
|
||||
throw new ConflictException('Username or Email already exists');
|
||||
}
|
||||
@@ -44,7 +47,6 @@ export class UserService {
|
||||
// 2. ดึงข้อมูลทั้งหมด
|
||||
async findAll(): Promise<User[]> {
|
||||
return this.usersRepository.find({
|
||||
// ไม่ส่ง password กลับไปเพื่อความปลอดภัย
|
||||
select: [
|
||||
'user_id',
|
||||
'username',
|
||||
@@ -61,7 +63,7 @@ export class UserService {
|
||||
// 3. ดึงข้อมูลรายคน
|
||||
async findOne(id: number): Promise<User> {
|
||||
const user = await this.usersRepository.findOne({
|
||||
where: { user_id: id }, // ใช้ user_id ตาม Entity
|
||||
where: { user_id: id },
|
||||
});
|
||||
|
||||
if (!user) {
|
||||
@@ -71,26 +73,26 @@ export class UserService {
|
||||
return user;
|
||||
}
|
||||
|
||||
// ฟังก์ชันแถม: สำหรับ AuthService ใช้ (ต้องเห็น Password เพื่อเอาไปเทียบ)
|
||||
async findOneByUsername(username: string): Promise<User | null> {
|
||||
return this.usersRepository.findOne({ where: { username } });
|
||||
}
|
||||
|
||||
// 4. แก้ไขข้อมูล
|
||||
async update(id: number, updateUserDto: UpdateUserDto): Promise<User> {
|
||||
// เช็คก่อนว่ามี User นี้ไหม
|
||||
const user = await this.findOne(id);
|
||||
|
||||
// ถ้ามีการแก้รหัสผ่าน ต้อง Hash ใหม่ด้วย
|
||||
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);
|
||||
|
||||
return this.usersRepository.save(updatedUser);
|
||||
// ⚠️ สำคัญ: เมื่อมีการแก้ไขข้อมูล User ต้องเคลียร์ Cache สิทธิ์เสมอ
|
||||
await this.clearUserCache(id);
|
||||
|
||||
return savedUser;
|
||||
}
|
||||
|
||||
// 5. ลบผู้ใช้ (Soft Delete)
|
||||
@@ -100,31 +102,48 @@ export class UserService {
|
||||
if (result.affected === 0) {
|
||||
throw new NotFoundException(`User with ID ${id} not found`);
|
||||
}
|
||||
// เคลียร์ Cache เมื่อลบ
|
||||
await this.clearUserCache(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* หา User ID ของคนที่เป็น Document Control (หรือตัวแทน) ในองค์กร
|
||||
* เพื่อส่ง Notification
|
||||
*/
|
||||
async findDocControlIdByOrg(organizationId: number): Promise<number | null> {
|
||||
// ✅ FIX: ใช้ usersRepository ให้ตรงกับ Constructor
|
||||
const user = await this.usersRepository.findOne({
|
||||
where: { primaryOrganizationId: organizationId },
|
||||
// order: { roleId: 'ASC' } // (Optional) Logic การเลือกคน
|
||||
});
|
||||
|
||||
return user ? user.user_id : null;
|
||||
}
|
||||
|
||||
// ฟังก์ชันดึงสิทธิ์ (Permission)
|
||||
/**
|
||||
* ✅ ดึงสิทธิ์ (Permission) โดยใช้ Caching Strategy
|
||||
* TTL: 30 นาที (ตาม Requirement 6.5.2)
|
||||
*/
|
||||
async getUserPermissions(userId: number): Promise<string[]> {
|
||||
// Query ข้อมูลจาก View: v_user_all_permissions
|
||||
const cacheKey = `permissions:user:${userId}`;
|
||||
|
||||
// 1. ลองดึงจาก Cache ก่อน
|
||||
const cachedPermissions = await this.cacheManager.get<string[]>(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],
|
||||
);
|
||||
|
||||
// แปลงผลลัพธ์เป็น Array ของ string ['user.create', 'project.view', ...]
|
||||
return permissions.map((row: any) => row.permission_name);
|
||||
const permissionList = permissions.map((row: any) => row.permission_name);
|
||||
|
||||
// 3. บันทึกลง Cache (TTL 1800 วินาที = 30 นาที)
|
||||
await this.cacheManager.set(cacheKey, permissionList, 1800 * 1000);
|
||||
|
||||
return permissionList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper สำหรับล้าง Cache เมื่อมีการเปลี่ยนแปลงสิทธิ์หรือบทบาท
|
||||
*/
|
||||
async clearUserCache(userId: number): Promise<void> {
|
||||
await this.cacheManager.del(`permissions:user:${userId}`);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user