Skip to content

Commit

Permalink
feat: add logger (#25)
Browse files Browse the repository at this point in the history
* feat(logger): add logger module

* feat(logger): add logger to projects module

* fix(projects): add adminonly route for all projects

* fix(logger): display the full error

* feat(logger): use logger on users and ssh keys

* feat(logger): add logger to mail

* lint: lint files
  • Loading branch information
BioTheWolff authored Jul 12, 2023
1 parent 177be32 commit bc36574
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 30 deletions.
9 changes: 9 additions & 0 deletions src/logger/custom-logger.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { ConsoleLogger, Injectable, Scope } from '@nestjs/common';

@Injectable({ scope: Scope.TRANSIENT })
export class CustomLoggerService extends ConsoleLogger {
cError(method: string, message: string, err: unknown): void {
super.error(`{${method}} : ${message}`);
super.error(err); // display the full error after so we dont lose data
}
}
9 changes: 9 additions & 0 deletions src/logger/logger.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { CustomLoggerService } from './custom-logger.service';

@Global()
@Module({
providers: [CustomLoggerService],
exports: [CustomLoggerService],
})
export class LoggerModule {}
3 changes: 2 additions & 1 deletion src/mail/mail.module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Global, Module } from '@nestjs/common';
import { MailService } from './mail.service';
import { _InternalMailerService } from './mailer.internal.service';
import { CustomLoggerService } from 'src/logger/custom-logger.service';

@Global()
@Module({
providers: [MailService, _InternalMailerService],
providers: [MailService, _InternalMailerService, CustomLoggerService],
exports: [MailService],
})
export class MailModule {}
47 changes: 27 additions & 20 deletions src/mail/mail.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Injectable } from '@nestjs/common';
import { _InternalMailerService as MailerService } from './mailer.internal.service';
import { compile } from 'handlebars';
import { ConfigService } from '@nestjs/config';
import Mail from 'nodemailer/lib/mailer';
import { CustomLoggerService } from 'src/logger/custom-logger.service';

const EMAIL_CONFIRMATION_TEMPLATE = `<h2>Welcome to PaasTech!</h2>
<p>Please click the link below to confirm your email:</p>
Expand Down Expand Up @@ -29,41 +31,46 @@ const PASSWORD_RESET_TEMPLATE = `<h2>Password reset</h2>
export class MailService {
private hostname: string;

constructor(private readonly mailerService: MailerService, private readonly configService: ConfigService) {
constructor(private readonly mailerService: MailerService, private readonly configService: ConfigService, private readonly logger: CustomLoggerService) {
this.hostname = this.configService.getOrThrow('FRONTEND_ORIGIN');
}

private async sendMail(options: Mail.Options): Promise<boolean> {
return await this.mailerService
.sendMail(options)
.then(() => {
return true;
})
.catch((e) => {
this.logger.cError(this.sendMail.name, 'Failed to send mail', e);
return false;
});
}

async sendUserConfirmation(email: string, token: string): Promise<boolean> {
const url = `${this.hostname}/#/email-verification/${token}`;
const template = compile(EMAIL_CONFIRMATION_TEMPLATE);

return this.mailerService
.sendMail({
from: `${this.configService.getOrThrow('MAILER_FROM')}`,
to: email,
subject: 'Welcome to PaaSTech!',
html: template({ url }),
return this.sendMail({
from: `${this.configService.getOrThrow('MAILER_FROM')}`,
to: email,
subject: 'Welcome to PaaSTech!',
html: template({ url }),

// TODO: use @nestjs-modules/mailer once bumped to 10.0.0
// TODO: use @nestjs-modules/mailer once bumped to 10.0.0

//template: './confirmation',
//context: {
// url,
//},
})
.then(() => {
return true;
})
.catch(() => {
return false;
});
//template: './confirmation',
//context: {
// url,
//},
});
}

async sendPasswordReset(email: string, token: string) {
const url = `${this.hostname}/#/password-reset/${token}`;
const template = compile(PASSWORD_RESET_TEMPLATE);

await this.mailerService.sendMail({
await this.sendMail({
from: `${this.configService.getOrThrow('MAILER_FROM')}`,
to: email,
subject: '[PaaSTech] Password reset',
Expand Down
11 changes: 9 additions & 2 deletions src/projects/projects.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { DeployDto } from './dto/deploy.dto';
import { ApiBearerAuth, ApiCookieAuth, ApiCreatedResponse, ApiOkResponse, ApiResponse, ApiTags } from '@nestjs/swagger';
import { ApiStandardResponse } from 'src/interfaces/standard-response.inteface';
import { CompliantContentResponse } from 'src/types/standard-response.type';
import { AdminOnly } from 'src/decorators/adminonly.decorator';

@ApiCookieAuth()
@ApiBearerAuth()
Expand All @@ -20,10 +21,16 @@ export class ProjectsController {

// GET /projects
// This action returns all of the authenticated user's projects
@AdminOnly()
@ApiOkResponse({ type: ApiStandardResponse })
@Get()
async findAll(@GetUser() user: RequestUser): Promise<CompliantContentResponse<SanitizedProject[]>> {
return this.projectsService.findAll(user.id);
async findAll(): Promise<CompliantContentResponse<SanitizedProject[]>> {
return this.projectsService.findAll();
}

@Get('my')
async findAllForUser(@GetUser() user: RequestUser): Promise<CompliantContentResponse<SanitizedProject[]>> {
return this.projectsService.findAllForUser(user.id);
}

// GET /projects/:uuid
Expand Down
3 changes: 2 additions & 1 deletion src/projects/projects.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import { ProjectsService } from './projects.service';
import { PrismaService } from 'src/prisma.service';
import { GitRepoManagerService } from './git-repo-manager.service';
import { PomegranateService } from './pomegranate.service';
import { CustomLoggerService } from 'src/logger/custom-logger.service';
@Module({
controllers: [ProjectsController],
providers: [ProjectsService, PrismaService, GitRepoManagerService, PomegranateService],
providers: [ProjectsService, PrismaService, GitRepoManagerService, PomegranateService, CustomLoggerService],
})
export class ProjectsModule {}
26 changes: 23 additions & 3 deletions src/projects/projects.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,18 @@ import {
StopDeployRequest,
} from 'paastech-proto/types/proto/pomegranate';
import { PomegranateService } from './pomegranate.service';
import { CustomLoggerService } from 'src/logger/custom-logger.service';

@Injectable()
export class ProjectsService {
constructor(private prisma: PrismaService, private gitRepoManagerService: GitRepoManagerService, private pomegranateService: PomegranateService) {}
constructor(
private prisma: PrismaService,
private gitRepoManagerService: GitRepoManagerService,
private pomegranateService: PomegranateService,
private readonly logger: CustomLoggerService,
) {
this.logger.setContext(ProjectsService.name);
}

private sanitizeOutput(project: Project): SanitizedProject {
return exclude(project, ['userId']);
Expand All @@ -48,6 +56,12 @@ export class ProjectsService {
try {
await this.gitRepoManagerService.create(repositoryRequest);
} catch (e) {
if (e.status === 409) {
throw new ConflictException(`Project with name ${name} already exists`);
}

this.logger.cError(this.create.name, 'Failed to create repository', e);

throw new InternalServerErrorException(`Failed to create repository for project ${createdProject.id}`);
}

Expand Down Expand Up @@ -121,7 +135,8 @@ export class ProjectsService {

return await this.prisma.$transaction(async (tx) => {
// delete the project
await tx.project.delete({ where: { id: projectId } }).catch(() => {
await tx.project.delete({ where: { id: projectId } }).catch((e) => {
this.logger.cError(this.delete.name, 'Failed to delete project', e);
throw new InternalServerErrorException(`Failed to delete project ${projectId}`);
});

Expand All @@ -135,6 +150,7 @@ export class ProjectsService {
} catch (e) {
// If the image does not exist, we do not throw an error
if (e.status !== 404) {
this.logger.cError(this.delete.name, 'Failed to delete pomegranate image', e);
throw new InternalServerErrorException(`Failed to delete the image for project ${projectId}: ${e}`);
}
}
Expand All @@ -145,7 +161,11 @@ export class ProjectsService {
});
}

async findAll(userId: string): Promise<SanitizedProject[]> {
async findAll(): Promise<SanitizedProject[]> {
return (await this.prisma.project.findMany()).map((project) => this.sanitizeOutput(project));
}

async findAllForUser(userId: string): Promise<SanitizedProject[]> {
// check if user is admin
const user = await this.prisma.user
.findFirstOrThrow({
Expand Down
3 changes: 2 additions & 1 deletion src/sshkeys/sshkeys.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { PrismaService } from 'src/prisma.service';
import { UsersService } from '../users/users.service';
import { SshKeysController } from './sshkeys.controller';
import { SshKeysService } from './sshkeys.service';
import { CustomLoggerService } from 'src/logger/custom-logger.service';

@Module({
controllers: [SshKeysController],
providers: [SshKeysService, PrismaService, UsersService],
providers: [SshKeysService, PrismaService, UsersService, CustomLoggerService],
exports: [SshKeysService],
})
export class SshKeysModule {}
3 changes: 2 additions & 1 deletion src/users/users.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { PrismaService } from 'src/prisma.service';
import { CustomLoggerService } from 'src/logger/custom-logger.service';

@Module({
controllers: [UsersController],
providers: [UsersService, PrismaService],
providers: [UsersService, PrismaService, CustomLoggerService],
exports: [UsersService],
})
export class UsersModule {}
6 changes: 5 additions & 1 deletion src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ import { v4 as uuidv4 } from 'uuid';
import * as bcrypt from 'bcrypt';
import { MailService } from 'src/mail/mail.service';
import { StandardResponseOutput } from 'src/types/standard-response.type';
import { CustomLoggerService } from 'src/logger/custom-logger.service';

@Injectable()
export class UsersService {
constructor(private prisma: PrismaService, private readonly mailService: MailService) {}
constructor(private prisma: PrismaService, private readonly mailService: MailService, private readonly logger: CustomLoggerService) {}

public sanitizeOutput(user: User): SanitizedUser {
return exclude(user, ['id', 'emailNonce', 'passwordNonce', 'password', 'createdAt', 'updatedAt']);
Expand Down Expand Up @@ -63,6 +64,7 @@ export class UsersService {
});
return true;
} catch (err) {
this.logger.cError(this.validateEmailNonce.name, `Failed to validate email with nonce '${email_nonce}'`, err);
return false;
}
}
Expand All @@ -80,6 +82,7 @@ export class UsersService {
});
return true;
} catch (err) {
this.logger.cError(this.updatePassword.name, `Failed to update password for user '${id}'`, err);
return false;
}
}
Expand All @@ -97,6 +100,7 @@ export class UsersService {
});
return passwordNonce;
} catch (err) {
this.logger.cError(this.regeneratePasswordNonce.name, `Failed to regenerate password nonce for user '${id}'`, err);
return null;
}
}
Expand Down

0 comments on commit bc36574

Please sign in to comment.