Skip to content

Commit

Permalink
docs: Add CHANGELOG.md
Browse files Browse the repository at this point in the history
  • Loading branch information
rajdip-b committed Dec 25, 2023
1 parent 9d5c521 commit 184220e
Show file tree
Hide file tree
Showing 33 changed files with 972 additions and 105 deletions.
5 changes: 4 additions & 1 deletion .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
{
"files": ["*.ts", "*.tsx"],
"extends": ["plugin:@nx/typescript"],
"rules": {}
"rules": {
"@typescript-eslint/explicit-function-return-type": "off",
"@typescript-eslint/explicit-module-boundary-types": "off"
}
},
{
"files": ["*.js", "*.jsx"],
Expand Down
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

- Initial release
24 changes: 24 additions & 0 deletions apps/api/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,30 @@
"options": {
"jestConfig": "apps/api/jest.config.ts"
}
},
"prisma:generate": {
"command": "prisma generate",
"options": {
"cwd": "apps/api/src/prisma"
}
},
"prisma:format": {
"command": "prisma format",
"options": {
"cwd": "apps/api/src/prisma"
}
},
"prisma:validate": {
"command": "prisma validate",
"options": {
"cwd": "apps/api/src/prisma"
}
},
"prisma:reset": {
"command": "prisma migrate reset --force",
"options": {
"cwd": "apps/api/src/prisma"
}
}
},
"tags": []
Expand Down
7 changes: 3 additions & 4 deletions apps/api/src/app/app.controller.spec.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
import { Test, TestingModule } from '@nestjs/testing';

import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
let app: TestingModule;

beforeAll(async () => {
app = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
providers: [],
}).compile();
});

describe('getData', () => {
describe('healthCheck', () => {
it('should return "Hello API"', () => {
const appController = app.get<AppController>(AppController);
expect(appController.getData()).toEqual({ message: 'Hello API' });
expect(appController.health()).toEqual('UP');
});
});
});
13 changes: 4 additions & 9 deletions apps/api/src/app/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { SupabaseService } from '../supabase/supabase.service';

@Controller()
export class AppController {
constructor(
private readonly appService: AppService,
private readonly supabaseService: SupabaseService,
) {}
constructor() {}

@Get()
getHello(): string {
return this.appService.getData().message;
@Get('health')
health(): string {
return 'UP';
}
}
14 changes: 10 additions & 4 deletions apps/api/src/app/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { SupabaseModule } from '../supabase/supabase.module';
import { ConfigModule } from '@nestjs/config';
import { PassportModule } from '@nestjs/passport';
import { AuthModule } from '../auth/auth.module';
import { PrismaModule } from '../prisma/prisma.module';
import { CommonModule } from '../common/common.module';
import { ResendModule } from '../resend/resend.module';

@Module({
controllers: [AppController],
Expand All @@ -13,9 +16,12 @@ import { PassportModule } from '@nestjs/passport';
}),
PassportModule,
SupabaseModule,
AuthModule,
PrismaModule,
CommonModule,
ResendModule,
SupabaseModule
],
providers: [
AppService,
],
providers: [],
})
export class AppModule {}
21 changes: 0 additions & 21 deletions apps/api/src/app/app.service.spec.ts

This file was deleted.

8 changes: 0 additions & 8 deletions apps/api/src/app/app.service.ts

This file was deleted.

23 changes: 23 additions & 0 deletions apps/api/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Controller, Param, Post, Query } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UserAuthenticatedResponse } from './auth.types';

@Controller('auth')
export class AuthController {
constructor(
private authService: AuthService
) {}

@Post('send-otp/:email')
async sendOtp(@Param('email') email: string): Promise<void> {
await this.authService.sendOtp(email);
}

@Post('validate-otp')
async validateOtp(
@Query('email') email: string,
@Query('otp') otp: string)
: Promise<UserAuthenticatedResponse> {
return await this.authService.validateOtp(email, otp);
}
}
21 changes: 21 additions & 0 deletions apps/api/src/auth/auth.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtModule } from '@nestjs/jwt';

@Module({
imports: [
JwtModule.register({
global: true,
secret: process.env.JWT_SECRET,
signOptions: {
expiresIn: '1d' ,
issuer: 'keyshade.xyz',
algorithm: 'HS256',
}
})
],
providers: [AuthService],
controllers: [AuthController]
})
export class AuthModule {}
29 changes: 29 additions & 0 deletions apps/api/src/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AuthService } from './auth.service';
import { PrimsaRepository } from '../prisma/prisma.repository';
import { TestResend } from '../resend/services/test.resend';
import { RESEND_SERVICE } from '../resend/services/resend.service.interface';
import { JwtService } from '@nestjs/jwt';
import { PrismaService } from '../prisma/prisma.service';

describe('AuthService', () => {
let service: AuthService;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [
AuthService,
PrimsaRepository,
{ provide: RESEND_SERVICE, useClass: TestResend },
JwtService,
PrismaService
],
}).compile();

service = module.get<AuthService>(AuthService);
});

it('should be defined', () => {
expect(service).toBeDefined();
});
});
56 changes: 56 additions & 0 deletions apps/api/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common';
import { PrimsaRepository } from '../prisma/prisma.repository';
import { randomUUID } from 'crypto';
import { JwtService } from '@nestjs/jwt';
import { UserAuthenticatedResponse } from './auth.types';
import { IResendService, RESEND_SERVICE } from '../resend/services/resend.service.interface';

@Injectable()
export class AuthService {
private readonly OTP_EXPIRY = 5 * 60 * 1000; // 5 minutes
constructor(
private repository: PrimsaRepository,
@Inject(RESEND_SERVICE) private resend: IResendService,
private jwt: JwtService
) {}

async sendOtp(email: string): Promise<void> {
if (!email || !email.includes('@')) {
console.error(`Invalid email address: ${email}`);
throw new HttpException('Please enter a valid email address', HttpStatus.BAD_REQUEST);
}

// We need to create the user if it doesn't exist yet
if (!await this.repository.findUserByEmail(email)) {
await this.repository.createUser(email);
}

const otp = await this.repository.createOtp(
email,
randomUUID().slice(0, 6).toUpperCase(),
this.OTP_EXPIRY);

await this.resend.sendOtp(email, otp.code);
console.info(`Login code sent to ${email}: ${otp.code}`);
}

async validateOtp(email: string, otp: string): Promise<UserAuthenticatedResponse> {
const user = await this.repository.findUserByEmail(email);
if (!user) {
console.error(`User not found: ${email}`);
throw new HttpException('User not found', HttpStatus.NOT_FOUND);
}

if (!await this.repository.isOtpValid(email, otp)) {
console.error(`Invalid login code for ${email}: ${otp}`);
throw new HttpException('Invalid login code', HttpStatus.UNAUTHORIZED);
}

await this.repository.deleteOtp(email, otp);

return {
...user,
token: await this.jwt.signAsync({ id: user.id, email: user.email })
};
}
}
5 changes: 5 additions & 0 deletions apps/api/src/auth/auth.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { User } from "@prisma/client";

export type UserAuthenticatedResponse = User & {
token: string;
}
8 changes: 8 additions & 0 deletions apps/api/src/common/common.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { Global, Module } from '@nestjs/common';

@Global()
@Module({
providers: [],
exports: []
})
export class CommonModule {}
29 changes: 26 additions & 3 deletions apps/api/src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,41 @@
* This is only a minimal backend to get started.
*/

import { Logger } from '@nestjs/common';
import { LoggerService } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';

import { AppModule } from './app/app.module';
import chalk from 'chalk';
import moment from 'moment';

class CustomLogger implements LoggerService {
log(message: string) {
this.info(message);
}

info(message: string) {
console.info(`${chalk.green('[INFO]')} ${chalk.green(moment().format('YYYY-MM-DD HH:mm:ss'))} - ${message}`);
}

error(message: string) {
console.error(`${chalk.red('[ERROR]')} ${chalk.red(moment().format('YYYY-MM-DD HH:mm:ss'))} - ${message}`);
}

warn(message: string) {
console.warn(`${chalk.yellow('[WARN]')} ${chalk.yellow(moment().format('YYYY-MM-DD HH:mm:ss'))} - ${message}`);
}
}

async function bootstrap() {
const app = await NestFactory.create(AppModule);
const logger = new CustomLogger();
const app = await NestFactory.create(AppModule, {
logger
});
const globalPrefix = 'api';
app.setGlobalPrefix(globalPrefix);
const port = 4200;
await app.listen(port);
Logger.log(
logger.log(
`🚀 Application is running on: http://localhost:${port}/${globalPrefix}`
);
}
Expand Down
Loading

0 comments on commit 184220e

Please sign in to comment.