Skip to content

Commit

Permalink
Merge pull request #17 from kir-dev/feat/4-application-period-crud
Browse files Browse the repository at this point in the history
added basic crud for application periods
  • Loading branch information
FearsomeRover authored Aug 20, 2024
2 parents 358fd2a + 3d67a0e commit 652bd0a
Show file tree
Hide file tree
Showing 11 changed files with 312 additions and 9 deletions.
1 change: 1 addition & 0 deletions apps/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.3.8",
"@prisma/client": "^5.13.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"nestjs-prisma": "^0.23.0",
"passport": "^0.7.0",
Expand Down
3 changes: 2 additions & 1 deletion apps/backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { PrismaModule } from 'nestjs-prisma';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ApplicationPeriodModule } from './application-period/application-period.module';
import { AuthModule } from './auth/auth.module';
import { PostsModule } from './posts/posts.module';
import { UserModule } from './user/user.module';

@Module({
imports: [PrismaModule.forRoot({ isGlobal: true }), UserModule, AuthModule, PostsModule],
imports: [PrismaModule.forRoot({ isGlobal: true }), UserModule, AuthModule, ApplicationPeriodModule, PostsModule],
controllers: [AppController],
providers: [AppService],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { CurrentUser } from '@kir-dev/passport-authsch';
import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseGuards } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';
import { ApiBearerAuth } from '@nestjs/swagger';
import { ApplicationPeriod, Role, User } from '@prisma/client';
import { Roles } from 'src/auth/decorators/Roles.decorator';
import { RolesGuard } from 'src/auth/roles.guard';

import { ApplicationPeriodService } from './application-period.service';
import { CreateApplicationPeriodDto } from './dto/create-application-period.dto';
import { GetApplicationPeriodsDto } from './dto/get-application-periods.dto';
import { SimpleApplicationPeriodDto } from './dto/simple-application-period.dto';
import { UpdateApplicationPeriodDto } from './dto/update-application-period.dto';

@Controller('application-periods')
export class ApplicationPeriodController {
constructor(private readonly applicationPeriodService: ApplicationPeriodService) {}

@Get()
async findAll(@Query() getApplicationPeriodsDto: GetApplicationPeriodsDto): Promise<SimpleApplicationPeriodDto[]> {
return this.applicationPeriodService.findAll(getApplicationPeriodsDto);
}

@Get('current')
async getCurrentPeriod(): Promise<ApplicationPeriod> {
return this.applicationPeriodService.getCurrentPeriod();
}

@Get(':id')
async findOne(@Param('id') id: string): Promise<ApplicationPeriod> {
return this.applicationPeriodService.findOne(Number(id));
}

@UseGuards(AuthGuard('jwt'), RolesGuard)
@ApiBearerAuth()
@Roles(Role.BODY_ADMIN, Role.BODY_MEMBER)
@Post()
async create(
@Body() createApplicationPeriodDto: CreateApplicationPeriodDto,
@CurrentUser() user: User
): Promise<ApplicationPeriod> {
return this.applicationPeriodService.create(createApplicationPeriodDto, user);
}

@UseGuards(AuthGuard('jwt'), RolesGuard)
@ApiBearerAuth()
@Roles(Role.BODY_ADMIN, Role.BODY_MEMBER)
@Delete(':id')
async delete(@Param('id') id: string): Promise<ApplicationPeriod> {
return this.applicationPeriodService.delete(Number(id));
}

@UseGuards(AuthGuard('jwt'), RolesGuard)
@ApiBearerAuth()
@Roles(Role.BODY_ADMIN, Role.BODY_MEMBER)
@Patch(':id')
async update(
@Body() updateApplicationPeriodDto: UpdateApplicationPeriodDto,
@Param() id: string
): Promise<ApplicationPeriod> {
return this.applicationPeriodService.update(updateApplicationPeriodDto, Number(id));
}
}
10 changes: 10 additions & 0 deletions apps/backend/src/application-period/application-period.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Module } from '@nestjs/common';

import { ApplicationPeriodController } from './application-period.controller';
import { ApplicationPeriodService } from './application-period.service';

@Module({
controllers: [ApplicationPeriodController],
providers: [ApplicationPeriodService],
})
export class ApplicationPeriodModule {}
144 changes: 144 additions & 0 deletions apps/backend/src/application-period/application-period.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { ApplicationPeriod, Prisma } from '@prisma/client';
import { PrismaService } from 'nestjs-prisma';

import { CreateApplicationPeriodDto } from './dto/create-application-period.dto';
import { GetApplicationPeriodsDto } from './dto/get-application-periods.dto';
import { UpdateApplicationPeriodDto } from './dto/update-application-period.dto';

@Injectable()
export class ApplicationPeriodService {
constructor(private readonly prisma: PrismaService) {}
findAll(getApplicationPeriodsDto: GetApplicationPeriodsDto) {
const skip = getApplicationPeriodsDto.page * getApplicationPeriodsDto.page_size;
return this.prisma.applicationPeriod.findMany({
skip,
take: Number(getApplicationPeriodsDto.page_size),
});
}
async getCurrentPeriod(): Promise<ApplicationPeriod> {
const period = await this.prisma.applicationPeriod.findFirst({
where: {
applicationPeriodStartAt: {
lte: new Date(),
},
applicationPeriodEndAt: {
gte: new Date(),
},
},
});
if (!period) {
throw new NotFoundException('No current application period found');
}
return period;
}

async findOne(id: number): Promise<ApplicationPeriod> {
const period = await this.prisma.applicationPeriod.findUnique({
where: {
id,
},
});
if (!period) {
throw new NotFoundException('Application period not found');
}
return period;
}

async create(createApplicationPeriodDto: CreateApplicationPeriodDto, user): Promise<ApplicationPeriod> {
try {
const conflictingPeriod = await this.prisma.applicationPeriod.findFirst({
where: {
AND: [
{
applicationPeriodStartAt: {
lte: createApplicationPeriodDto.applicationPeriodEndAt,
},
},
{
applicationPeriodEndAt: {
gte: createApplicationPeriodDto.applicationPeriodStartAt,
},
},
],
},
});
if (conflictingPeriod) {
throw new BadRequestException('Application period overlaps with another period');
}
} catch (e: any) {
if (e instanceof Prisma.PrismaClientValidationError) {
throw new BadRequestException('Invalid date format');
}
}
return this.prisma.applicationPeriod.create({
data: {
...createApplicationPeriodDto,
author: {
connect: {
authSchId: user.authSchId,
},
},
},
});
}

async delete(id: number): Promise<ApplicationPeriod> {
try {
return await this.prisma.applicationPeriod.delete({
where: {
id,
},
});
} catch (e) {
throw new NotFoundException('Application period not found');
}
}

async update(updateApplicationPeriodDto: UpdateApplicationPeriodDto, id: number): Promise<ApplicationPeriod> {
if (
Boolean(updateApplicationPeriodDto.applicationPeriodStartAt) ||
Boolean(updateApplicationPeriodDto.applicationPeriodEndAt)
) {
try {
const conflictingPeriod = await this.prisma.applicationPeriod.findFirst({
where: {
AND: [
{
applicationPeriodStartAt: {
lte: updateApplicationPeriodDto.applicationPeriodEndAt,
},
},
{
applicationPeriodEndAt: {
gte: updateApplicationPeriodDto.applicationPeriodStartAt,
},
},
],
},
});
if (conflictingPeriod) {
throw new BadRequestException('Application period overlaps with another period');
}
} catch (e: any) {
if (e instanceof Prisma.PrismaClientValidationError) {
throw new BadRequestException('Invalid date format');
}
}
}

try {
return this.prisma.applicationPeriod.update({
where: {
id,
},
data: updateApplicationPeriodDto,
});
} catch (e) {
if (e instanceof Prisma.PrismaClientKnownRequestError) {
throw new BadRequestException('Invalid date format');
}
throw new BadRequestException('Application period not found');
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { IsBoolean, IsDateString, IsISO8601, IsOptional } from 'class-validator';

export class CreateApplicationPeriodDto {
name: string;
@IsDateString()
@IsISO8601()
applicationPeriodStartAt: Date;
@IsDateString()
@IsISO8601()
applicationPeriodEndAt: Date;
@IsOptional()
@IsBoolean()
ticketsAreValid?: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { IsInt, IsOptional, Min } from 'class-validator';
export class GetApplicationPeriodsDto {
@IsOptional()
@IsInt()
@Min(0)
page: number;
@IsOptional()
@IsInt()
@Min(1)
page_size: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export class SimpleApplicationPeriodDto {
id: number;
name: string;
applicationPeriodStartAt: Date;
applicationPeriodEndAt: Date;
createdAt: Date;
updatedAt: Date;
ticketsAreValid: boolean;
authorId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { PartialType } from '@nestjs/swagger';

import { CreateApplicationPeriodDto } from './create-application-period.dto';

export class UpdateApplicationPeriodDto extends PartialType(CreateApplicationPeriodDto) {}
2 changes: 2 additions & 0 deletions apps/backend/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';

Expand All @@ -10,6 +11,7 @@ async function bootstrap() {
methods: ['GET', 'POST', 'PATCH', 'DELETE', 'PUT'],
credentials: true,
});
app.useGlobalPipes(new ValidationPipe());
const config = new DocumentBuilder().setTitle('SCHBody WEB').setVersion('1.0').addBearerAuth().build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
Expand Down
Loading

0 comments on commit 652bd0a

Please sign in to comment.