251210:1709 Frontend: reeactor organization and run build
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-10 17:09:11 +07:00
parent aa96cd90e3
commit c8a0f281ef
140 changed files with 3780 additions and 1473 deletions
@@ -0,0 +1,38 @@
import { IsOptional, IsString, IsInt, Min } from 'class-validator';
import { Type } from 'class-transformer';
import { ApiPropertyOptional } from '@nestjs/swagger';
export class SearchUserDto {
@ApiPropertyOptional({
description: 'Search term (username, email, or name)',
})
@IsOptional()
@IsString()
search?: string;
@ApiPropertyOptional({ description: 'Filter by Role ID' })
@IsOptional()
@IsInt()
@Type(() => Number)
roleId?: number;
@ApiPropertyOptional({ description: 'Filter by Organization ID' })
@IsOptional()
@IsInt()
@Type(() => Number)
primaryOrganizationId?: number;
@ApiPropertyOptional({ description: 'Page number', default: 1 })
@IsOptional()
@IsInt()
@Min(1)
@Type(() => Number)
page?: number = 1;
@ApiPropertyOptional({ description: 'Items per page', default: 10 })
@IsOptional()
@IsInt()
@Min(1)
@Type(() => Number)
limit?: number = 10;
}
@@ -40,6 +40,15 @@ export class User {
@Column({ name: 'is_active', default: true })
isActive!: boolean;
@Column({ name: 'failed_attempts', default: 0 })
failedAttempts!: number;
@Column({ name: 'locked_until', type: 'datetime', nullable: true })
lockedUntil?: Date;
@Column({ name: 'last_login_at', type: 'timestamp', nullable: true })
lastLoginAt?: Date;
@Column({ name: 'line_id', nullable: true, length: 100 })
lineId?: string;
+4 -2
View File
@@ -6,6 +6,7 @@ import {
Patch,
Param,
Delete,
Query,
UseGuards,
ParseIntPipe,
} from '@nestjs/common';
@@ -24,6 +25,7 @@ import { UserPreferenceService } from './user-preference.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { AssignRoleDto } from './dto/assign-role.dto';
import { SearchUserDto } from './dto/search-user.dto';
import { UpdatePreferenceDto } from './dto/update-preference.dto';
import { JwtAuthGuard } from '../../common/guards/jwt-auth.guard';
@@ -106,8 +108,8 @@ export class UserController {
@ApiOperation({ summary: 'List all users' })
@ApiResponse({ status: 200, description: 'List of users' })
@RequirePermission('user.view')
findAll() {
return this.userService.findAll();
findAll(@Query() query: SearchUserDto) {
return this.userService.findAll(query);
}
@Get(':id')
+62 -14
View File
@@ -50,20 +50,68 @@ export class UserService {
}
}
// 2. ดึงข้อมูลทั้งหมด
async findAll(): Promise<User[]> {
return this.usersRepository.find({
select: [
'user_id',
'username',
'email',
'firstName',
'lastName',
'isActive',
'createdAt',
'updatedAt',
],
});
// 2. ดึงข้อมูลทั้งหมด (Search & Pagination)
async findAll(params?: any): Promise<any> {
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. ดึงข้อมูลรายคน