251211:1314 Frontend: reeactor Admin panel
Spec Validation / validate-markdown (push) Has been cancelled
Spec Validation / validate-diagrams (push) Has been cancelled
Spec Validation / check-todos (push) Has been cancelled

This commit is contained in:
admin
2025-12-11 13:14:15 +07:00
parent c8a0f281ef
commit 3fa28bd14f
79 changed files with 6571 additions and 206 deletions
@@ -11,9 +11,9 @@ import {
} from 'typeorm';
import { User } from './user.entity';
import { Role } from './role.entity';
import { Organization } from '../../project/entities/organization.entity'; // ปรับ Path ให้ตรงกับ ProjectModule
import { Organization } from '../../organization/entities/organization.entity'; // ปรับ Path ให้ตรงกับ ProjectModule
import { Project } from '../../project/entities/project.entity'; // ปรับ Path ให้ตรงกับ ProjectModule
import { Contract } from '../../project/entities/contract.entity'; // ปรับ Path ให้ตรงกับ ProjectModule
import { Contract } from '../../contract/entities/contract.entity'; // ปรับ Path ให้ตรงกับ ProjectModule
@Entity('user_assignments')
export class UserAssignment {
@@ -13,7 +13,7 @@ import {
OneToOne,
JoinColumn,
} from 'typeorm';
import { Organization } from '../../project/entities/organization.entity'; // Adjust path as needed
import { Organization } from '../../organization/entities/organization.entity'; // Adjust path as needed
import { UserAssignment } from './user-assignment.entity';
import { UserPreference } from './user-preference.entity';
@@ -93,6 +93,16 @@ export class UserController {
return this.userService.findAllPermissions();
}
@Patch('roles/:id/permissions')
@RequirePermission('permission.assign')
@ApiOperation({ summary: 'Update role permissions' })
async updateRolePermissions(
@Param('id', ParseIntPipe) id: number,
@Body('permissionIds') permissionIds: number[]
) {
return this.userService.updateRolePermissions(id, permissionIds);
}
// --- User CRUD (Admin) ---
@Post()
+34 -4
View File
@@ -4,6 +4,8 @@ import { getRepositoryToken } from '@nestjs/typeorm';
import { CACHE_MANAGER } from '@nestjs/cache-manager';
import { UserService } from './user.service';
import { User } from './entities/user.entity';
import { Role } from './entities/role.entity';
import { Permission } from './entities/permission.entity';
// Mock Repository
const mockUserRepository = {
@@ -14,6 +16,14 @@ const mockUserRepository = {
merge: jest.fn(),
softDelete: jest.fn(),
query: jest.fn(),
createQueryBuilder: jest.fn(() => ({
leftJoinAndSelect: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([[], 0]),
})),
};
// Mock Cache Manager
@@ -38,6 +48,14 @@ describe('UserService', () => {
provide: CACHE_MANAGER,
useValue: mockCacheManager,
},
{
provide: getRepositoryToken(Role),
useValue: mockUserRepository, // Reuse generic mock
},
{
provide: getRepositoryToken(Permission),
useValue: mockUserRepository, // Reuse generic mock
},
],
}).compile();
@@ -53,14 +71,26 @@ describe('UserService', () => {
});
describe('findAll', () => {
it('should return array of users', async () => {
it('should return paginated users', async () => {
const mockUsers = [{ user_id: 1, username: 'test' }];
mockUserRepository.find.mockResolvedValue(mockUsers);
const mockTotal = 1;
const mockQB = {
leftJoinAndSelect: jest.fn().mockReturnThis(),
select: jest.fn().mockReturnThis(),
andWhere: jest.fn().mockReturnThis(),
skip: jest.fn().mockReturnThis(),
take: jest.fn().mockReturnThis(),
getManyAndCount: jest.fn().mockResolvedValue([mockUsers, mockTotal]),
};
mockUserRepository.createQueryBuilder.mockReturnValue(mockQB);
const result = await service.findAll();
expect(result).toEqual(mockUsers);
expect(mockUserRepository.find).toHaveBeenCalled();
expect(result.data).toEqual(mockUsers);
expect(result.total).toEqual(mockTotal);
expect(mockUserRepository.createQueryBuilder).toHaveBeenCalled();
});
});
+27 -1
View File
@@ -203,13 +203,39 @@ export class UserService {
// --- Roles & Permissions (Helper for Admin/UI) ---
async findAllRoles(): Promise<Role[]> {
return this.roleRepository.find();
return this.roleRepository.find({ relations: ['permissions'] });
}
async findAllPermissions(): Promise<Permission[]> {
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 เมื่อมีการเปลี่ยนแปลงสิทธิ์หรือบทบาท
*/