Skip to content

Commit

Permalink
feat: add mongo error handling and a new field for project
Browse files Browse the repository at this point in the history
  • Loading branch information
manekenpix committed Mar 15, 2024
1 parent d0a91e9 commit 300b0be
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 70 deletions.
47 changes: 47 additions & 0 deletions api/src/database/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ArgumentsHost, Catch, HttpStatus, Logger } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
import { Response } from 'express';
import { MongoError } from 'mongodb';

@Catch(MongoError)
export class MongoExceptionFilter extends BaseExceptionFilter {
private readonly logger = new Logger(MongoExceptionFilter.name);

catch(exception: MongoError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
this.logger.error(exception.message);

let status;
switch (exception.name) {
case 'DocumentNotFoundError': {
status = HttpStatus.NOT_FOUND;
response.status(status).json({
statusCode: HttpStatus.NOT_FOUND,
message: 'Not Found',
});

break;
}
case 'MongooseError':
case 'CastError':
case 'DisconnectedError':
case 'DivergentArrayError':
case 'MissingSchemaError':
case 'ValidatorError':
case 'ValidationError':
case 'ObjectExpectedError':
case 'ObjectParameterError':
case 'OverwriteModelError':
case 'ParallelSaveError':
case 'StrictModeError':
case 'VersionError':
case 'MongooseError':
default: {
super.catch(exception, host);

break;
}
}
}
}
34 changes: 25 additions & 9 deletions api/src/flags/flags.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { Body, Controller, Get, Post, Patch, UseGuards } from '@nestjs/common';
import {
Body,
Controller,
Get,
Post,
Patch,
UseGuards,
Param,
Delete,
} from '@nestjs/common';
import FlagsService from './flags.service';
import FlagDto from './flag.dts';
import FlagGuard from './flag.guard';
Expand All @@ -8,23 +17,30 @@ import EnableGuard from './enable.guard';
class FlagsController {
constructor(private readonly flagsService: FlagsService) {}

@Get('getFlags')
@Get('/')
async getFlags(): Promise<FlagDto[]> {
return this.flagsService.findAll();
return this.flagsService.get();
}

@UseGuards(FlagGuard)
@Post('saveFlag')
@Post('/')
async saveFlag(@Body() flagRequest: FlagDto): Promise<FlagDto> {
return this.flagsService.saveFlag(flagRequest);
return this.flagsService.create(flagRequest);
}

@UseGuards(EnableGuard)
@Patch('setEnabled')
async setEnabled(
@Body() { id, isEnabled }: { id: string; isEnabled: boolean },
@Patch('/:id')
async toggleEnabled(
@Param('id') id: string,
@Body() { isEnabled }: { isEnabled: boolean },
): Promise<FlagDto | null> {
return this.flagsService.setEnabled({ id, isEnabled });
return this.flagsService.toggleEnabled(id, isEnabled);
}

@UseGuards(EnableGuard)
@Delete('/:id')
delete(@Param('id') id: string): void {
this.flagsService.delete(id);
}
}

Expand Down
3 changes: 2 additions & 1 deletion api/src/flags/flags.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ import { MongooseModule } from '@nestjs/mongoose';
import FlagsController from './flags.controller';
import FlagsService from './flags.service';
import { Flag, FlagSchema } from './schemas/flag.schema';
import FlagsRepository from './flags.repository';

@Module({
imports: [
MongooseModule.forFeature([{ name: Flag.name, schema: FlagSchema }]),
],
controllers: [FlagsController],
providers: [FlagsService],
providers: [FlagsService, FlagsRepository],
})
export default class FlagsModule {}
61 changes: 61 additions & 0 deletions api/src/flags/flags.repository.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Flag, FlagDocument } from './schemas/flag.schema';
import { Model, Types } from 'mongoose';
import FlagDto from './flag.dts';

@Injectable()
class FlagsRepository {
private readonly logger = new Logger(FlagsRepository.name);

constructor(
@InjectModel(Flag.name)
private db: Model<FlagDocument>,
) {}

private formatFlags(flags: FlagDocument[]): FlagDto[] {
return flags.map(
({ _id, name, type, value, environment, project, isEnabled }) => ({
id: _id as Types.ObjectId,
name,
type,
value,
environment,
project,
isEnabled,
}),
);
}

async create(flag: FlagDto): Promise<FlagDto> {
const newFlag = new this.db(flag);
const f = await newFlag.save();

return this.formatFlags([f])[0];
}

async get(): Promise<FlagDto[]> {
const fs = await this.db.find();

return this.formatFlags(fs);
}

async toggleEnabled(id: string, isEnabled: boolean): Promise<FlagDto> {
this.db.findById(id);
const f = await this.db
.findByIdAndUpdate(
{ _id: new Types.ObjectId(id) },
{ isEnabled },
{ new: true },
)
.orFail();

return this.formatFlags([f])[0];
}

delete(id: string): void {
this.db.deleteOne({ _id: new Types.ObjectId(id) });
}
}

export default FlagsRepository;
75 changes: 16 additions & 59 deletions api/src/flags/flags.service.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,33 @@
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { Model, Types } from 'mongoose';
import { Injectable, Logger } from '@nestjs/common';
import FlagDto from './flag.dts';
import { Flag, FlagDocument } from './schemas/flag.schema';
import FlagsRepository from './flags.repository';

@Injectable()
class FlagsService {
private readonly logger = new Logger(FlagsService.name);

constructor(
@InjectModel(Flag.name)
private FlagModel: Model<FlagDocument>,
) {}
constructor(private repository: FlagsRepository) {}

static formatFlags(flags: FlagDocument[]): FlagDto[] {
return flags.map(({ _id, name, type, value, environment, isEnabled }) => ({
id: _id as Types.ObjectId,
name,
type,
value,
environment,
isEnabled,
}));
}

async findAll(): Promise<FlagDto[]> {
const flags = await this.FlagModel.find();
const formattedFlags = FlagsService.formatFlags(flags);
this.logger.log({ formattedFlags });
async get(): Promise<FlagDto[]> {
const flags = await this.repository.get();
this.logger.log({ flags });

return formattedFlags;
return flags;
}

async saveFlag(flag: FlagDto): Promise<FlagDto | never> {
try {
const newFlag = new this.FlagModel(flag);
const storedFlag = await newFlag.save();
const trimmedFlag = FlagsService.formatFlags([storedFlag]);
async create(flag: FlagDto): Promise<FlagDto | never> {
const f = await this.repository.create(flag);
this.logger.log({ flag: f });

this.logger.log({ trimmedFlag });
return trimmedFlag[0];
} catch (error) {
this.logger.error({ error });

throw new HttpException(
'Service Unavailable',
HttpStatus.SERVICE_UNAVAILABLE,
);
}
return f;
}

async setEnabled({
id,
isEnabled,
}: {
id: string;
isEnabled: boolean;
}): Promise<FlagDto | null> {
try {
return await this.FlagModel.findOneAndUpdate(
{ _id: new Types.ObjectId(id) },
{ isEnabled },
{ new: true },
);
} catch (error) {
this.logger.error({ error });
toggleEnabled(id: string, isEnabled: boolean): Promise<FlagDto> {
return this.repository.toggleEnabled(id, isEnabled);
}

throw new HttpException(
'Service Unavailable',
HttpStatus.SERVICE_UNAVAILABLE,
);
}
delete(id: string): void {
this.repository.delete(id);
}
}

Expand Down
3 changes: 3 additions & 0 deletions api/src/flags/schemas/flag.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export class Flag {
@Prop({ type: String })
environment!: FlagEnv;

@Prop({ type: String })
project!: string;

@Prop({ type: Boolean, default: true })
isEnabled: boolean;
}
Expand Down
6 changes: 5 additions & 1 deletion api/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { NestFactory } from '@nestjs/core';
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import AppModule from './app.module';
import { MongoExceptionFilter } from './database/errorHandler';

async function bootstrap() {
const logger = new Logger('main.ts');
Expand All @@ -11,6 +12,9 @@ async function bootstrap() {
const port = configService.get('PORT');

await app.listen(port);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new MongoExceptionFilter(httpAdapter));

logger.log(`App started on port ${port}`);
}
bootstrap();

0 comments on commit 300b0be

Please sign in to comment.