diff --git a/.env.sample b/.env.sample index 6f006fe..2703e70 100644 --- a/.env.sample +++ b/.env.sample @@ -9,6 +9,9 @@ USERS_PORT=15001 TASKS_HOST=localhost TASKS_PORT=15002 +AUTH_HOST=localhost +AUTH_PORT=15003 + DATABASE_HOST=localhost DATABASE_PORT=27017 DATABASE_NAME=product_a_main diff --git a/apps/auth-e2e/eslint.config.js b/apps/auth-e2e/eslint.config.js new file mode 100644 index 0000000..df7cfc2 --- /dev/null +++ b/apps/auth-e2e/eslint.config.js @@ -0,0 +1,3 @@ +const baseConfig = require('../../eslint.config.js'); + +module.exports = [...baseConfig]; diff --git a/apps/auth-e2e/jest.config.ts b/apps/auth-e2e/jest.config.ts new file mode 100644 index 0000000..b27f299 --- /dev/null +++ b/apps/auth-e2e/jest.config.ts @@ -0,0 +1,18 @@ +export default { + displayName: 'auth-e2e', + preset: '../../jest.preset.js', + globalSetup: '/src/support/global-setup.ts', + globalTeardown: '/src/support/global-teardown.ts', + setupFiles: ['/src/support/test-setup.ts'], + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.spec.json', + }, + ], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/auth-e2e', +}; diff --git a/apps/auth-e2e/project.json b/apps/auth-e2e/project.json new file mode 100644 index 0000000..5acd3ef --- /dev/null +++ b/apps/auth-e2e/project.json @@ -0,0 +1,17 @@ +{ + "name": "auth-e2e", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "projectType": "application", + "implicitDependencies": ["auth"], + "targets": { + "e2e": { + "executor": "@nx/jest:jest", + "outputs": ["{workspaceRoot}/coverage/{e2eProjectRoot}"], + "options": { + "jestConfig": "apps/auth-e2e/jest.config.ts", + "passWithNoTests": true + }, + "dependsOn": ["auth:build"] + } + } +} diff --git a/apps/auth-e2e/src/auth/auth.spec.ts b/apps/auth-e2e/src/auth/auth.spec.ts new file mode 100644 index 0000000..1ec8fe1 --- /dev/null +++ b/apps/auth-e2e/src/auth/auth.spec.ts @@ -0,0 +1,10 @@ +import axios from 'axios'; + +describe('GET /', () => { + it('should return a message', async () => { + const res = await axios.get(`/`); + + expect(res.status).toBe(200); + expect(res.data).toEqual({ message: 'Hello Auth Module' }); + }); +}); diff --git a/apps/auth-e2e/src/support/global-setup.ts b/apps/auth-e2e/src/support/global-setup.ts new file mode 100644 index 0000000..c1f5144 --- /dev/null +++ b/apps/auth-e2e/src/support/global-setup.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ +var __TEARDOWN_MESSAGE__: string; + +module.exports = async function () { + // Start services that that the app needs to run (e.g. database, docker-compose, etc.). + console.log('\nSetting up...\n'); + + // Hint: Use `globalThis` to pass variables to global teardown. + globalThis.__TEARDOWN_MESSAGE__ = '\nTearing down...\n'; +}; diff --git a/apps/auth-e2e/src/support/global-teardown.ts b/apps/auth-e2e/src/support/global-teardown.ts new file mode 100644 index 0000000..32ea345 --- /dev/null +++ b/apps/auth-e2e/src/support/global-teardown.ts @@ -0,0 +1,7 @@ +/* eslint-disable */ + +module.exports = async function () { + // Put clean up logic here (e.g. stopping services, docker-compose, etc.). + // Hint: `globalThis` is shared between setup and teardown. + console.log(globalThis.__TEARDOWN_MESSAGE__); +}; diff --git a/apps/auth-e2e/src/support/test-setup.ts b/apps/auth-e2e/src/support/test-setup.ts new file mode 100644 index 0000000..4356c82 --- /dev/null +++ b/apps/auth-e2e/src/support/test-setup.ts @@ -0,0 +1,10 @@ +/* eslint-disable */ + +import axios from 'axios'; + +module.exports = async function () { + // Configure axios for tests to use. + const host = process.env.HOST ?? 'localhost'; + const port = process.env.PORT ?? '15003'; + axios.defaults.baseURL = `http://${host}:${port}`; +}; diff --git a/apps/auth-e2e/tsconfig.json b/apps/auth-e2e/tsconfig.json new file mode 100644 index 0000000..ed633e1 --- /dev/null +++ b/apps/auth-e2e/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/apps/auth-e2e/tsconfig.spec.json b/apps/auth-e2e/tsconfig.spec.json new file mode 100644 index 0000000..d7f9cf2 --- /dev/null +++ b/apps/auth-e2e/tsconfig.spec.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": ["jest.config.ts", "src/**/*.ts"] +} diff --git a/apps/auth/eslint.config.js b/apps/auth/eslint.config.js new file mode 100644 index 0000000..df7cfc2 --- /dev/null +++ b/apps/auth/eslint.config.js @@ -0,0 +1,3 @@ +const baseConfig = require('../../eslint.config.js'); + +module.exports = [...baseConfig]; diff --git a/apps/auth/jest.config.ts b/apps/auth/jest.config.ts new file mode 100644 index 0000000..a0cb1b0 --- /dev/null +++ b/apps/auth/jest.config.ts @@ -0,0 +1,10 @@ +export default { + displayName: 'auth', + preset: '../../jest.preset.js', + testEnvironment: 'node', + transform: { + '^.+\\.[tj]s$': ['ts-jest', { tsconfig: '/tsconfig.spec.json' }], + }, + moduleFileExtensions: ['ts', 'js', 'html'], + coverageDirectory: '../../coverage/apps/auth', +}; diff --git a/apps/auth/project.json b/apps/auth/project.json new file mode 100644 index 0000000..80deb1e --- /dev/null +++ b/apps/auth/project.json @@ -0,0 +1,26 @@ +{ + "name": "auth", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "apps/auth/src", + "projectType": "application", + "tags": [], + "targets": { + "serve": { + "executor": "@nx/js:node", + "defaultConfiguration": "development", + "dependsOn": ["build"], + "options": { + "buildTarget": "auth:build", + "runBuildTargetDependencies": false + }, + "configurations": { + "development": { + "buildTarget": "auth:build:development" + }, + "production": { + "buildTarget": "auth:build:production" + } + } + } + } +} diff --git a/apps/auth/src/app/app.controller.spec.ts b/apps/auth/src/app/app.controller.spec.ts new file mode 100644 index 0000000..336af8c --- /dev/null +++ b/apps/auth/src/app/app.controller.spec.ts @@ -0,0 +1,24 @@ +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], + }).compile(); + }); + + describe('getData', () => { + it('should return "Hello Auth Module"', () => { + const appController = app.get(AppController); + expect(appController.getData()).toEqual({ + message: 'Hello Auth Module', + }); + }); + }); +}); diff --git a/apps/auth/src/app/app.controller.ts b/apps/auth/src/app/app.controller.ts new file mode 100644 index 0000000..dff210a --- /dev/null +++ b/apps/auth/src/app/app.controller.ts @@ -0,0 +1,13 @@ +import { Controller, Get } from '@nestjs/common'; + +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getData() { + return this.appService.getData(); + } +} diff --git a/apps/auth/src/app/app.module.ts b/apps/auth/src/app/app.module.ts new file mode 100644 index 0000000..6e2825d --- /dev/null +++ b/apps/auth/src/app/app.module.ts @@ -0,0 +1,45 @@ +import { ApolloServerPluginInlineTrace } from '@apollo/server/plugin/inlineTrace'; +import { + ApolloFederationDriver, + ApolloFederationDriverConfig, +} from '@nestjs/apollo'; +import { Module } from '@nestjs/common'; +import { ConfigModule } from '@nestjs/config'; +import { GraphQLModule } from '@nestjs/graphql'; +import { + databaseConfig, + authAppConfig, + awsConfig, + authConfig, +} from '@shared/config'; +import { AuthModule } from '@auth/interface-adapters'; + +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +@Module({ + imports: [ + ConfigModule.forRoot({ + isGlobal: true, + load: [authAppConfig, databaseConfig, awsConfig, authConfig], + }), + GraphQLModule.forRoot({ + driver: ApolloFederationDriver, + autoSchemaFile: { + /** + * MEMO: + * Because of this problem, so mush need specify the version + * https://github.com/nestjs/graphql/issues/2646#issuecomment-1567381944 + */ + federation: 2, + }, + playground: process.env['NODE_ENV'] !== 'production', + sortSchema: true, + plugins: [ApolloServerPluginInlineTrace()], + }), + AuthModule + ], + controllers: [AppController], + providers: [AppService], +}) +export class AppModule {} diff --git a/apps/auth/src/app/app.service.spec.ts b/apps/auth/src/app/app.service.spec.ts new file mode 100644 index 0000000..558591e --- /dev/null +++ b/apps/auth/src/app/app.service.spec.ts @@ -0,0 +1,21 @@ +import { Test } from '@nestjs/testing'; + +import { AppService } from './app.service'; + +describe('AppService', () => { + let service: AppService; + + beforeAll(async () => { + const app = await Test.createTestingModule({ + providers: [AppService], + }).compile(); + + service = app.get(AppService); + }); + + describe('getData', () => { + it('should return "Hello Auth Module"', () => { + expect(service.getData()).toEqual({ message: 'Hello Auth Module' }); + }); + }); +}); diff --git a/apps/auth/src/app/app.service.ts b/apps/auth/src/app/app.service.ts new file mode 100644 index 0000000..53c959f --- /dev/null +++ b/apps/auth/src/app/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getData(): { message: string } { + return { message: 'Hello Auth Module' }; + } +} diff --git a/apps/auth/src/assets/.gitkeep b/apps/auth/src/assets/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/apps/auth/src/main.ts b/apps/auth/src/main.ts new file mode 100644 index 0000000..bc7e2dd --- /dev/null +++ b/apps/auth/src/main.ts @@ -0,0 +1,24 @@ +/** + * This is not a production server yet! + * This is only a minimal backend to get started. + */ + +import { Logger } from '@nestjs/common'; +import { ConfigService } from '@nestjs/config'; +import { NestFactory } from '@nestjs/core'; + +import { AppModule } from './app/app.module'; + +async function bootstrap() { + const app = await NestFactory.create(AppModule); + + const configService = app.get(ConfigService); + const config = configService.get('authApp'); + + await app.listen(config.port); + Logger.log( + `🚀 Application is running on: ${config.protocol}://${config.host}:${config.port}`, + ); +} + +bootstrap(); diff --git a/apps/auth/tsconfig.app.json b/apps/auth/tsconfig.app.json new file mode 100644 index 0000000..77061cf --- /dev/null +++ b/apps/auth/tsconfig.app.json @@ -0,0 +1,17 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["node"], + "emitDecoratorMetadata": true, + "target": "es2021", + "strictNullChecks": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "forceConsistentCasingInFileNames": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts"], + "exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"] +} diff --git a/apps/auth/tsconfig.json b/apps/auth/tsconfig.json new file mode 100644 index 0000000..c1e2dd4 --- /dev/null +++ b/apps/auth/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.spec.json" + } + ], + "compilerOptions": { + "esModuleInterop": true + } +} diff --git a/apps/auth/tsconfig.spec.json b/apps/auth/tsconfig.spec.json new file mode 100644 index 0000000..9b2a121 --- /dev/null +++ b/apps/auth/tsconfig.spec.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "include": [ + "jest.config.ts", + "src/**/*.test.ts", + "src/**/*.spec.ts", + "src/**/*.d.ts" + ] +} diff --git a/apps/auth/webpack.config.js b/apps/auth/webpack.config.js new file mode 100644 index 0000000..97f4bec --- /dev/null +++ b/apps/auth/webpack.config.js @@ -0,0 +1,20 @@ +const { NxAppWebpackPlugin } = require('@nx/webpack/app-plugin'); +const { join } = require('path'); + +module.exports = { + output: { + path: join(__dirname, '../../dist/apps/auth'), + }, + plugins: [ + new NxAppWebpackPlugin({ + target: 'node', + compiler: 'tsc', + main: './src/main.ts', + tsConfig: './tsconfig.app.json', + assets: ['./src/assets'], + optimization: false, + outputHashing: 'none', + generatePackageJson: true, + }), + ], +}; diff --git a/apps/gateway/src/app/app.module.ts b/apps/gateway/src/app/app.module.ts index 9be50b6..46d3fc7 100644 --- a/apps/gateway/src/app/app.module.ts +++ b/apps/gateway/src/app/app.module.ts @@ -1,5 +1,5 @@ import { IntrospectAndCompose } from '@apollo/gateway'; -import { gatewayConfig, tasksAppConfig, usersAppConfig } from '@shared/config'; +import { gatewayConfig, tasksAppConfig, usersAppConfig, authAppConfig } from '@shared/config'; import { ApolloGatewayDriver, ApolloGatewayDriverConfig } from '@nestjs/apollo'; import { Module } from '@nestjs/common'; import { ConfigModule, ConfigService } from '@nestjs/config'; @@ -12,7 +12,7 @@ import { AppService } from './app.service'; imports: [ ConfigModule.forRoot({ isGlobal: true, - load: [gatewayConfig, usersAppConfig, tasksAppConfig], + load: [gatewayConfig, usersAppConfig, tasksAppConfig, authAppConfig], }), GraphQLModule.forRootAsync({ imports: [ConfigModule], @@ -21,6 +21,7 @@ import { AppService } from './app.service'; useFactory: (configService: ConfigService) => { const usersAppConfig = configService.get('usersApp'); const tasksAppConfig = configService.get('tasksApp'); + const authAppConfig = configService.get('authApp'); return { driver: ApolloGatewayDriver, @@ -35,6 +36,10 @@ import { AppService } from './app.service'; name: tasksAppConfig.name, url: `${tasksAppConfig.protocol}://${tasksAppConfig.host}:${tasksAppConfig.port}/graphql`, }, + { + name: authAppConfig.name, + url: `${authAppConfig.protocol}://${authAppConfig.host}:${authAppConfig.port}/graphql`, + }, ], }), }, diff --git a/libs/shared/config/src/lib/applications.config.ts b/libs/shared/config/src/lib/applications.config.ts index e6d5ca1..72df0a1 100644 --- a/libs/shared/config/src/lib/applications.config.ts +++ b/libs/shared/config/src/lib/applications.config.ts @@ -13,9 +13,10 @@ export type ServiceConfig = z.infer; const DEFAULT_PROTOCOL = 'http' as const; const DEFAULT_HOST = 'localhost' as const; const DEFAULT_PORT = { + gateway: 3333, users: 15001, tasks: 15002, - gateway: 3333, + auth: 15003, } as const; export const gatewayConfig = registerAs( @@ -52,3 +53,13 @@ export const tasksAppConfig = registerAs( name: 'tasks', }), ); + +export const authAppConfig = registerAs( + 'authApp', + (): ServiceConfig => ({ + protocol: process.env['PROTOCOL'] ?? DEFAULT_PROTOCOL, + host: process.env['AUTH_HOST'] ?? DEFAULT_HOST, + port: process.env['AUTH_PORT'] ? Number(process.env['AUTH_PORT']) : DEFAULT_PORT.auth, + name: 'auth', + }), +); diff --git a/nx.json b/nx.json index 1d4eb07..6015123 100644 --- a/nx.json +++ b/nx.json @@ -37,7 +37,8 @@ "exclude": [ "apps/gateway-e2e/**/*", "apps/users-e2e/**/*", - "apps/tasks-e2e/**/*" + "apps/tasks-e2e/**/*", + "apps/auth-e2e/**/*" ] } ]