add user frontend components

This commit is contained in:
Roland Schneider
2025-11-20 15:27:22 +01:00
parent 635975207f
commit e050bf5def
19 changed files with 750 additions and 7 deletions

View File

@@ -5,7 +5,6 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { UserModule } from './user/user.module';
import { AuthModule } from './auth/auth.module';
import { User } from './entity/user';
import { UserGroup } from './entity/user-group';
import { UserRole } from './entity/user-role';
import { LoggerModule } from './logger/logger.module';
@@ -13,8 +12,9 @@ import { EventType } from './entity/event-type.entity';
import { EventTypesModule } from './event-type/event-type.module';
import { Product } from './entity/product.entity';
import { ProductsModule } from './product/products.module';
import { Event } from "./entity/event.entity";
import { EventsModule } from "./event/events.module";
import { Event } from './entity/event.entity';
import { EventsModule } from './event/events.module';
import { User } from './entity/user';
const moduleTypeOrm = TypeOrmModule.forRootAsync({
imports: [ConfigModule],
@@ -43,8 +43,8 @@ const moduleTypeOrm = TypeOrmModule.forRootAsync({
LoggerModule,
EventTypesModule,
ProductsModule,
EventsModule
],
EventsModule,
],
controllers: [AppController],
providers: [AppService],
})

View File

@@ -0,0 +1,14 @@
import {
IsOptional,
IsString,
IsNumber,
IsIn,
} from 'class-validator';
import { Type } from 'class-transformer';
export class QueryUserDto {
@IsOptional() @Type(() => Number) @IsNumber() page?: number;
@IsOptional() @Type(() => Number) @IsNumber() limit?: number;
@IsOptional() @IsString() sortBy?: string;
@IsOptional() @IsIn(['ASC', 'DESC']) order?: 'ASC' | 'DESC';
}

View File

@@ -8,6 +8,9 @@ import {
Delete,
UseGuards,
ValidationPipe,
Query,
DefaultValuePipe,
ParseIntPipe,
} from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';
@@ -18,7 +21,7 @@ import { Roles } from '../auth/roles.decorator';
import { Role } from '../auth/role.enum';
import { RolesGuard } from '../auth/roles.guard';
@Controller('users')
@Controller('user')
@UseGuards(JwtAuthGuard, RolesGuard)
@Roles(Role.Admin)
export class UserController {
@@ -36,6 +39,15 @@ export class UserController {
return this.userService.findAll();
}
@Get('search')
search(
@Query('q') term: string,
@Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number,
@Query('limit', new DefaultValuePipe(10), ParseIntPipe) limit: number,
) {
return this.userService.search(term, { page, limit });
}
@Get(':id')
findOne(@Param('id') id: string): Promise<User | null> {
return this.userService.findOne(+id);

View File

@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { ILike, Repository } from 'typeorm';
import { User } from '../entity/user';
import * as bcrypt from 'bcrypt';
import { FindOptionsRelations } from 'typeorm/find-options/FindOptionsRelations';
@@ -8,6 +8,8 @@ import { DvbookingLoggerService } from '../logger/dvbooking-logger.service';
@Injectable()
export class UserService {
private readonly searchableFields: (keyof User)[] = ['username', 'email'];
constructor(
@InjectRepository(User)
private usersRepository: Repository<User>,
@@ -55,6 +57,40 @@ export class UserService {
await this.usersRepository.delete(id);
}
async search(term: string, options: { page: number; limit: number }) {
if (this.searchableFields.length === 0) {
console.warn('Search is not configured for this entity.');
return {
data: [],
meta: {
totalItems: 0,
itemCount: 0,
itemsPerPage: options.limit,
totalPages: 0,
currentPage: options.page,
},
};
}
const whereConditions = this.searchableFields.map((field) => ({
[field]: ILike(`%${term}%`),
}));
const [data, totalItems] = await this.usersRepository.findAndCount({
where: whereConditions,
skip: (options.page - 1) * options.limit,
take: options.limit,
});
return {
data,
meta: {
totalItems,
itemCount: data.length,
itemsPerPage: options.limit,
totalPages: Math.ceil(totalItems / options.limit),
currentPage: options.page,
},
};
}
async setRefreshToken(
id: number,
refreshToken: string | null,