Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
DATABASE_URL=mongodb://localhost:30000,localhost:30001,localhost:30002/starter?replicaSet=rs0
NGROK_DOMAIN=
NGROK_AUTHTOKEN=
NGROK_ENABLE=false
NODE_ENV=development
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ lerna-debug.log*
!.vscode/extensions.json

.adminjs

# env
.env
13,573 changes: 11,762 additions & 1,811 deletions package-lock.json

Large diffs are not rendered by default.

10 changes: 9 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@
"@nestjs/common": "10.0.2",
"@nestjs/config": "3.0.0",
"@nestjs/core": "10.0.2",
"@nestjs/jwt": "10.1.0",
"@nestjs/jwt": "^10.1.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "10.0.2",
"@nestjs/swagger": "7.0.2",
"@nestjs/terminus": "10.0.1",
"@nestjs/throttler": "4.1.0",
"@ngrok/ngrok": "^1.2.0",
"@nodeteam/nestjs-pipes": "1.2.5",
"@nodeteam/nestjs-prisma-pagination": "1.0.6",
"@prisma/client": "4.15.0",
Expand All @@ -60,6 +62,10 @@
"express-basic-auth": "1.2.1",
"jest-mock-extended": "3.0.5",
"module-alias": "2.2.3",
"passport": "^0.7.0",
"passport-google-oauth2": "^0.2.0",
"passport-jwt": "^4.0.1",
"passport-local": "^1.0.0",
"reflect-metadata": "0.1.13",
"rxjs": "7.8.1",
"sqs-consumer": "7.2.0",
Expand All @@ -77,6 +83,8 @@
"@types/express": "4.17.17",
"@types/jest": "29.5.2",
"@types/node": "20.3.1",
"@types/passport-google-oauth2": "^0.1.8",
"@types/passport-jwt": "^4.0.1",
"@types/supertest": "2.0.12",
"@typescript-eslint/eslint-plugin": "5.60.0",
"@typescript-eslint/parser": "5.60.0",
Expand Down
52 changes: 36 additions & 16 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,46 @@ datasource db {
}

model User {
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
phone String?
firstName String?
lastName String?
password String
roles Roles[] @default([customer])
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
id String @id @default(auto()) @map("_id") @db.ObjectId
email String @unique
phone String?
firstName String?
lastName String?
password String
roles Roles[] @default([customer])
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
socialAccount SocialAccount[]
}

model SocialAccount {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String @db.ObjectId
user User @relation(fields: [userId], references: [id])
provider String
email String?
name String?
picture String?
providerId String
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
}

model TokenWhiteList {
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
accessToken String?
refreshToken String?
id String @id @default(auto()) @map("_id") @db.ObjectId
userId String
accessToken String?
refreshToken String?
refreshTokenId String?
expiredAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
expiredAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @default(now())
}

enum SocialProviders {
google
facebook
twitter
}

enum Roles {
Expand Down
33 changes: 23 additions & 10 deletions src/config/app.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { registerAs } from '@nestjs/config';
import * as path from 'path';
import { NgrokConfig } from '@config/ngrok.config';
import * as process from 'node:process';

function parseLogLevel(level: string | undefined): string[] {
if (!level) {
Expand All @@ -13,13 +15,24 @@ function parseLogLevel(level: string | undefined): string[] {
return level.split(',');
}

export default registerAs('app', () => ({
port: process.env.APP_PORT || 3000,
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
loggerLevel: parseLogLevel(
process.env.APP_LOGGER_LEVEL || 'log,error,warn,debug,verbose',
),
env: process.env.NODE_ENV || 'dev',
// eslint-disable-next-line global-require,@typescript-eslint/no-var-requires
version: require(path.join(process.cwd(), 'package.json')).version,
}));
export type AppConfig = {
readonly port: number;
readonly baseUrl: string;
readonly loggerLevel: string[];
readonly env: string;
readonly version: string;
};

export default registerAs(
'app',
(): AppConfig => ({
port: Number(process.env.APP_PORT) || 3000,
baseUrl: process.env.BASE_URL || 'http://localhost:3000',
loggerLevel: parseLogLevel(
process.env.APP_LOGGER_LEVEL || 'log,error,warn,debug,verbose',
),
env: process.env.NODE_ENV || 'dev',
// eslint-disable-next-line global-require,@typescript-eslint/no-var-requires
version: require(path.join(process.cwd(), 'package.json')).version,
}),
);
16 changes: 16 additions & 0 deletions src/config/ngrok.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { registerAs } from '@nestjs/config';

export type NgrokConfig = {
readonly domain: string;
readonly authToken: string;
readonly isEnable: string | undefined | boolean;
};

export default registerAs(
'ngrok',
(): NgrokConfig => ({
domain: process.env.NGROK_DOMAIN!,
authToken: process.env.NGROK_AUTHTOKEN!,
isEnable: process.env.NGROK_ENABLE,
}),
);
19 changes: 19 additions & 0 deletions src/config/social.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { registerAs } from '@nestjs/config';
import * as process from 'node:process';

export type SocialConfig = {
readonly google: {
readonly clientId: string;
readonly secret: string;
};
};

export default registerAs(
'social',
(): SocialConfig => ({
google: {
clientId: process.env.GOOGLE_CLIENT_ID! || '...',
secret: process.env.GOOGLE_CLIENT_SECRET! || '...',
},
}),
);
3 changes: 3 additions & 0 deletions src/constants/errors.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export const PHOTO_NOT_FOUND = '404029: Photo not found';
export const REGION_NOT_FOUND = '404030: Region not found';
export const PERMISSIONS_NOT_FOUND = '404031: Permissions not found';
export const INVITE_IS_INVALID = '404032: Invite is invalid';
export const SOCIAL_ACCOUNT_NOT_FOUND = '404033: Social account not found';

export const UNAUTHORIZED_RESOURCE = '401000: Unauthorized resource';
export const INVALID_CREDENTIALS = '401001: Invalid credentials';
Expand Down Expand Up @@ -125,3 +126,5 @@ export const RATE_LIMIT_EXCEEDED = '429000: Rate limit exceeded';
export const VALIDATION_ERROR = '422000: Validation error';

export const INTERNAL_SERVER_ERROR = '500000: Internal server error';
export const OAUTH_INVALID_RESPONSE = '500001: OAuth invalid response';
export const OAUTH_INVALID_STATE = '500002: OAuth invalid state';
13 changes: 13 additions & 0 deletions src/filters/oauth2.exception.filter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ExceptionFilter, Catch, ArgumentsHost } from '@nestjs/common';
import { Response } from 'express';
import { Oauth2Exception } from '@filters/oauth2.exception';

@Catch(Oauth2Exception)
export class Oauth2ExceptionFilter implements ExceptionFilter {
catch(exception: Oauth2Exception, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();

response.send(exception.message);
}
}
8 changes: 8 additions & 0 deletions src/filters/oauth2.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export class Oauth2Exception extends Error {
message;

constructor(message: any) {
super();
this.message = message;
}
}
53 changes: 48 additions & 5 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,14 @@ import { ThrottlerExceptionsFilter } from '@filters/throttler-exception.filter';
import { TransformInterceptor } from '@interceptors/transform.interceptor';
import { AccessExceptionFilter } from '@filters/access-exception.filter';
import { NotFoundExceptionFilter } from '@filters/not-found-exception.filter';

async function bootstrap(): Promise<{ port: number }> {
import { NgrokConfig } from '@config/ngrok.config';
import { Listener } from '@ngrok/ngrok';
import { AppConfig } from '@config/app.config';

async function bootstrap(): Promise<{
appConfig: AppConfig;
ngrokConfig: NgrokConfig;
}> {
/**
* Create NestJS application
*/
Expand All @@ -43,6 +49,23 @@ async function bootstrap(): Promise<{ port: number }> {
app.useLogger(options);
}

{
/**
* Enable CORS
*/

// TODO: we need to change it on stag and prod
const options = {
origin: '*',
methods: 'GET,HEAD,PUT,PATCH,POST,DELETE',
preflightContinue: false,
optionsSuccessStatus: 204,
credentials: true,
};

app.enableCors(options);
}

{
/**
* ValidationPipe options
Expand Down Expand Up @@ -141,9 +164,29 @@ async function bootstrap(): Promise<{ port: number }> {

await app.listen(appConfig.port);

return appConfig;
return {
appConfig,
ngrokConfig: configService.get<NgrokConfig>('ngrok') as NgrokConfig,
};
}
bootstrap().then(async ({ appConfig, ngrokConfig }): Promise<void> => {
if (
appConfig.env === 'development' &&
ngrokConfig.domain &&
ngrokConfig.isEnable === 'true'
) {
const ngrok = await import('@ngrok/ngrok');

const listener: Listener = await ngrok.forward({
port: appConfig.port,
domain: ngrokConfig.domain,
authtoken: ngrokConfig.authToken,
});

bootstrap().then((appConfig) => {
Logger.log(`Running in http://localhost:${appConfig.port}`, 'Bootstrap');
Logger.log(`Ngrok ingress established at: ${listener.url()}`, 'Ngrok');
Logger.log(`Docs at: ${listener.url()}/docs`, 'Swagger');
} else {
Logger.log(`Running at ${appConfig.baseUrl}`, 'Bootstrap');
Logger.log(`Docs at ${appConfig.baseUrl}/docs`, 'Swagger');
}
});
20 changes: 18 additions & 2 deletions src/modules/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,31 @@ import jwtConfig from '@config/jwt.config';
import { CaslModule } from '@modules/casl';
import { Roles } from '@modules/app/app.roles';
import { APP_GUARD } from '@nestjs/core';
import { AuthGuard } from '@modules/auth/auth.guard';
import { AuthGuard } from '@modules/auth/guards/auth.guard';
import { JwtModule, JwtService } from '@nestjs/jwt';
import s3Config from '@config/s3.config';
import sqsConfig from '@config/sqs.config';
import { TokenService } from '@modules/auth/token.service';
import { TokenRepository } from '@modules/auth/token.repository';
import ngrokConfig from '@config/ngrok.config';
import { GoogleStrategy } from '@modules/auth/strategies/google.strategy';
import socialConfig from '@config/social.config';
import { GoogleGuard } from '@modules/auth/guards/google.guard';

@Module({
controllers: [],
imports: [
ConfigModule.forRoot({
isGlobal: true,
load: [appConfig, swaggerConfig, jwtConfig, s3Config, sqsConfig],
load: [
appConfig,
swaggerConfig,
jwtConfig,
s3Config,
sqsConfig,
ngrokConfig,
socialConfig,
],
}),
PrismaModule.forRoot({
isGlobal: true,
Expand All @@ -50,6 +62,10 @@ import { TokenRepository } from '@modules/auth/token.repository';
provide: APP_GUARD,
useClass: AuthGuard,
},
{
provide: 'GoogleGuard',
useClass: GoogleGuard,
},
],
})
export class AppModule {}
Loading