From 57edf5b4a8aee835ee9234f206c01e27657c57d0 Mon Sep 17 00:00:00 2001 From: Roland Schneider Date: Fri, 12 Dec 2025 08:20:32 +0100 Subject: [PATCH] server: add user decorator, add ping module --- server/api.http | 5 ++++ server/src/app.module.ts | 2 ++ server/src/auth/user.decorator.ts | 23 +++++++++++++++++++ server/src/calendar/calendar.controller.ts | 4 ++++ server/src/calendar/dto/create-booking.dto.ts | 1 + server/src/main.ts | 17 +++++++++++++- server/src/ping/ping.controller.ts | 20 ++++++++++++++++ server/src/ping/ping.module.ts | 7 ++++++ server/src/types.ts | 14 +++++++++-- 9 files changed, 90 insertions(+), 3 deletions(-) create mode 100644 server/src/auth/user.decorator.ts create mode 100644 server/src/ping/ping.controller.ts create mode 100644 server/src/ping/ping.module.ts diff --git a/server/api.http b/server/api.http index 9e252c9..40b4fc1 100644 --- a/server/api.http +++ b/server/api.http @@ -9,6 +9,11 @@ Content-Type: application/json > {% client.global.set("auth_token", response.body.accessToken); %} +### GET request with parameter +GET {{apiBaseUrl}}/ping/auth +Accept: application/json +Authorization: Bearer {{auth_token}} + ### GET request with parameter GET {{apiBaseUrl}}/users Accept: application/json diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 05af4da..2e60505 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -24,6 +24,7 @@ import { EventExceptionsModule } from './event-exception/event-exceptions.module import { CalendarModule } from './calendar/calendar.module'; import { Booking } from './entity/booking.entity'; import { BookingsModule } from './booking/bookings.module'; +import { PingModule } from './ping/ping.module'; const moduleTypeOrm = TypeOrmModule.forRootAsync({ imports: [ConfigModule], @@ -69,6 +70,7 @@ const moduleTypeOrm = TypeOrmModule.forRootAsync({ EventExceptionsModule, CalendarModule, BookingsModule, + PingModule, ], controllers: [AppController], providers: [AppService], diff --git a/server/src/auth/user.decorator.ts b/server/src/auth/user.decorator.ts new file mode 100644 index 0000000..c156bde --- /dev/null +++ b/server/src/auth/user.decorator.ts @@ -0,0 +1,23 @@ +import { createParamDecorator, ExecutionContext } from '@nestjs/common'; +import express from 'express'; +import { AppUser, UserPayload } from '../types'; + +export const User = createParamDecorator( + (data: unknown, ctx: ExecutionContext) => { + const request: express.Request = ctx.switchToHttp().getRequest(); + const user: UserPayload = request.user as UserPayload; + let result: AppUser; + if (user) { + result = { + anonymous: false, + user: user, + }; + } else { + result = { + anonymous: true, + user: undefined, + }; + } + return result; + }, +); diff --git a/server/src/calendar/calendar.controller.ts b/server/src/calendar/calendar.controller.ts index 52b1569..5f036da 100644 --- a/server/src/calendar/calendar.controller.ts +++ b/server/src/calendar/calendar.controller.ts @@ -22,6 +22,8 @@ import { Role } from '../auth/role.enum'; import { CalendarCreateBookingDto } from './dto/create-booking.dto'; import { CancelBookingDto } from './dto/cancel-booking.dto'; import { ApiBody } from '@nestjs/swagger'; +import { User } from '../auth/user.decorator'; +import * as types from '../types'; @Controller('calendar') @UseGuards(JwtAuthGuard, RolesGuard) @@ -77,9 +79,11 @@ export class CalendarController { @Post('events/:id/bookings') @ApiBody({ type: CalendarCreateBookingDto }) createBooking( + @User() user: types.AppUser, @Param('id', ParseIntPipe) eventId: number, @Body(new ValidationPipe()) createBookingDto: CalendarCreateBookingDto, ) { + console.info('user user', user); return this.calendarService.createBooking(eventId, createBookingDto); } diff --git a/server/src/calendar/dto/create-booking.dto.ts b/server/src/calendar/dto/create-booking.dto.ts index 260b3f4..7927c0d 100644 --- a/server/src/calendar/dto/create-booking.dto.ts +++ b/server/src/calendar/dto/create-booking.dto.ts @@ -16,6 +16,7 @@ export class CalendarCreateBookingDto { @ApiProperty() occurrenceStartTime: Date; + @IsOptional() @IsNotEmpty() @IsInt() @ApiProperty() diff --git a/server/src/main.ts b/server/src/main.ts index 073fa58..d3150b2 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -15,9 +15,24 @@ async function bootstrap() { .setDescription('The DV Booking API description') .setVersion('1.0') .addTag('dvbooking') + .addBearerAuth( + { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + name: 'JWT', + description: 'Enter JWT token', + in: 'header', + }, + 'bearer', + ) .build(); const document = SwaggerModule.createDocument(app, config); - SwaggerModule.setup('api', app, document); + SwaggerModule.setup('api', app, document, { + swaggerOptions: { + security: [{ bearer: [] }], + }, + }); app.useGlobalPipes( new ValidationPipe({ diff --git a/server/src/ping/ping.controller.ts b/server/src/ping/ping.controller.ts new file mode 100644 index 0000000..08ecba7 --- /dev/null +++ b/server/src/ping/ping.controller.ts @@ -0,0 +1,20 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { JwtAuthGuard } from '../auth/jwt-auth.guard'; +import { RolesGuard } from '../auth/roles.guard'; +import { User } from '../auth/user.decorator'; +import * as types from '../types'; + +@Controller('ping') +export class PingController { + @Get() + ping() { + return 'pong'; + } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Get('auth') + pingAuth(@User() user: types.AppUser) { + console.info('user', user); + return 'pong'; + } +} diff --git a/server/src/ping/ping.module.ts b/server/src/ping/ping.module.ts new file mode 100644 index 0000000..355aff9 --- /dev/null +++ b/server/src/ping/ping.module.ts @@ -0,0 +1,7 @@ +import { Module } from '@nestjs/common'; +import { PingController } from './ping.controller'; + +@Module({ + controllers: [PingController], +}) +export class PingModule {} diff --git a/server/src/types.ts b/server/src/types.ts index 8e708e2..d25a9d8 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -1,5 +1,4 @@ - -export interface LoginRequest{ +export interface LoginRequest { username: string; password: string; } @@ -8,3 +7,14 @@ export interface LoginResponse { accessToken: string; refreshToken: string; } + +export interface AppUser { + anonymous: boolean; + user: UserPayload | undefined; +} + +export interface UserPayload { + userId: number; + username: string; + roles: string[]; +}