mishrabp commited on
Commit
97dab2a
·
verified ·
1 Parent(s): 35f3e23

Upload folder using huggingface_hub

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .gitattributes +6 -0
  2. .gitignore +56 -0
  3. .npmrc +2 -0
  4. .prettierrc +4 -0
  5. Dockerfile +23 -0
  6. ENV_VARIABLES +11 -0
  7. README.md +9 -5
  8. apps/apigateway/README.md +11 -0
  9. apps/apigateway/src/apigateway.controller.spec.ts +22 -0
  10. apps/apigateway/src/apigateway.controller.ts +19 -0
  11. apps/apigateway/src/apigateway.module.ts +62 -0
  12. apps/apigateway/src/common/circuitbraker/circuitbraker.module.ts +20 -0
  13. apps/apigateway/src/common/circuitbraker/circuitbraker.service.spec.ts +18 -0
  14. apps/apigateway/src/common/circuitbraker/circuitbraker.service.ts +93 -0
  15. apps/apigateway/src/main.ts +58 -0
  16. apps/apigateway/src/middlewares/logger/logger.middleware.spec.ts +10 -0
  17. apps/apigateway/src/middlewares/logger/logger.middleware.ts +15 -0
  18. apps/apigateway/src/modules/azure/azure.controller.spec.ts +18 -0
  19. apps/apigateway/src/modules/azure/azure.controller.ts +27 -0
  20. apps/apigateway/src/modules/azure/azure.module.ts +33 -0
  21. apps/apigateway/src/modules/northwind/northwind.controller.spec.ts +18 -0
  22. apps/apigateway/src/modules/northwind/northwind.controller.ts +78 -0
  23. apps/apigateway/src/modules/northwind/northwind.module.ts +31 -0
  24. apps/apigateway/src/modules/northwind/northwind.service.spec.ts +18 -0
  25. apps/apigateway/src/modules/northwind/northwind.service.ts +79 -0
  26. apps/apigateway/src/modules/weather/weather.controller.spec.ts +18 -0
  27. apps/apigateway/src/modules/weather/weather.controller.ts +22 -0
  28. apps/apigateway/src/modules/weather/weather.module.ts +37 -0
  29. apps/apigateway/src/modules/weather/weather.service.spec.ts +18 -0
  30. apps/apigateway/src/modules/weather/weather.service.ts +61 -0
  31. apps/apigateway/src/service-map.json +7 -0
  32. apps/apigateway/test/app.e2e-spec.ts +24 -0
  33. apps/apigateway/test/jest-e2e.json +9 -0
  34. apps/apigateway/tsconfig.app.json +10 -0
  35. apps/azureapi/src/azureapi.controller.spec.ts +22 -0
  36. apps/azureapi/src/azureapi.controller.ts +16 -0
  37. apps/azureapi/src/azureapi.module.ts +11 -0
  38. apps/azureapi/src/azureapi.service.ts +25 -0
  39. apps/azureapi/src/main.ts +49 -0
  40. apps/azureapi/test/app.e2e-spec.ts +24 -0
  41. apps/azureapi/test/jest-e2e.json +9 -0
  42. apps/azureapi/tsconfig.app.json +9 -0
  43. apps/northwindapi/src/config/typeorm.config.ts +81 -0
  44. apps/northwindapi/src/database/migrations/1745487689382-migration.ts +91 -0
  45. apps/northwindapi/src/database/migrations/1748636404589-migration.ts +14 -0
  46. apps/northwindapi/src/database/seed/index.ts +36 -0
  47. apps/northwindapi/src/database/seed/seed-categories.ts +30 -0
  48. apps/northwindapi/src/database/seed/seed-customers.ts +148 -0
  49. apps/northwindapi/src/database/seed/seed-employees.ts +173 -0
  50. apps/northwindapi/src/database/seed/seed-order-details.ts +49 -0
.gitattributes CHANGED
@@ -33,3 +33,9 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ apps/oauthapp/src/public/images/OpenSource[[:space:]]Website[[:space:]]Load[[:space:]]After[[:space:]]dB[[:space:]]Restoration.png filter=lfs diff=lfs merge=lfs -text
37
+ apps/oauthapp/src/public/images/OpenSource[[:space:]]dB[[:space:]]Restoration[[:space:]]Proof.png filter=lfs diff=lfs merge=lfs -text
38
+ apps/oauthapp/src/public/images/apim.png filter=lfs diff=lfs merge=lfs -text
39
+ apps/oauthapp/src/public/images/apimpolicy.png filter=lfs diff=lfs merge=lfs -text
40
+ apps/oauthapp/src/public/images/botecosystem.png filter=lfs diff=lfs merge=lfs -text
41
+ apps/oauthapp/src/public/images/redisusages.png filter=lfs diff=lfs merge=lfs -text
.gitignore ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # compiled output
2
+ /dist
3
+ /node_modules
4
+ /build
5
+
6
+ # Logs
7
+ logs
8
+ *.log
9
+ npm-debug.log*
10
+ pnpm-debug.log*
11
+ yarn-debug.log*
12
+ yarn-error.log*
13
+ lerna-debug.log*
14
+
15
+ # OS
16
+ .DS_Store
17
+
18
+ # Tests
19
+ /coverage
20
+ /.nyc_output
21
+
22
+ # IDEs and editors
23
+ /.idea
24
+ .project
25
+ .classpath
26
+ .c9/
27
+ *.launch
28
+ .settings/
29
+ *.sublime-workspace
30
+
31
+ # IDE - VSCode
32
+ .vscode/*
33
+ !.vscode/settings.json
34
+ !.vscode/tasks.json
35
+ !.vscode/launch.json
36
+ !.vscode/extensions.json
37
+
38
+ # dotenv environment variable files
39
+ .env
40
+ .env.development.local
41
+ .env.test.local
42
+ .env.production.local
43
+ .env.local
44
+
45
+ # temp directory
46
+ .temp
47
+ .tmp
48
+
49
+ # Runtime data
50
+ pids
51
+ *.pid
52
+ *.seed
53
+ *.pid.lock
54
+
55
+ # Diagnostic reports (https://nodejs.org/api/report.html)
56
+ report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
.npmrc ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ @bibhu2020:registry=https://npm.pkg.github.com
2
+ //npm.pkg.github.com/:_authToken=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
.prettierrc ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "singleQuote": true,
3
+ "trailingComma": "all"
4
+ }
Dockerfile ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Use an official Node.js runtime as a parent image
2
+ FROM node:20-alpine AS builder
3
+
4
+ # Set working directory
5
+ WORKDIR /app
6
+
7
+ # Install dependencies
8
+ COPY package*.json ./
9
+ RUN npm install
10
+
11
+ # Copy the rest of the app's source code
12
+ COPY . .
13
+
14
+ # Build the NestJS app
15
+ RUN npm run build:apigateway
16
+
17
+ RUN mkdir -p dist/libs/proto && cp -r libs/proto dist/libs
18
+
19
+ # Start the application
20
+ CMD ["node", "dist/apps/apigateway/main.js"]
21
+
22
+ # Expose app port
23
+ EXPOSE 8080
ENV_VARIABLES ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ DATABASE_URL=""
2
+ # uncomment next line if you use Prisma <5.10
3
+ # DATABASE_URL_UNPOOLED="postgresql://neondb_owner:npg_vkaFtG8d3XPJ@ep-fancy-hill-a8tm0pqc.eastus2.azure.neon.tech/neondb?sslmode=require"
4
+ AUTH_REDIRCET_URL=http://localhost:8080/auth/redirect
5
+ AZURE_CLIENT_ID=908e42d2-fe86-41b7-8220-6bd41930b3b4
6
+ AZURE_CLIENT_SECRET=
7
+ AZURE_TENANT_ID=95595fde-7f04-4030-ace9-d08cf1e81bdc
8
+ MANAGED_IDENTITY_CLIENT_ID=ddd
9
+ NODE_ENV=development
10
+ # Weather Service 3rd Party API (optional if you do not want to test 3rd party API: https://api.openweathermap.org)
11
+ WEATHER_SERVICE_API_KEY=
README.md CHANGED
@@ -1,10 +1,14 @@
1
  ---
2
- title: Northwind Api Gateway
3
- emoji: 📊
4
- colorFrom: purple
5
- colorTo: purple
6
  sdk: docker
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
1
  ---
2
+ title: Northwind API Gateway
3
+ emoji: 🐳
4
+ colorFrom: blue
5
+ colorTo: indigo
6
  sdk: docker
7
+ app_file: app.py
8
  pinned: false
9
  ---
10
 
11
+ # 🐳 Northwind API Gateway
12
+
13
+ Dockerized NestJS API Gateway for the Northwind dataset, running on Node.js 20 and exposing port 8080.
14
+ EOF
apps/apigateway/README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## Required Packages
2
+ ```bash
3
+ npm install @nestjs/axios @nestjs/config @nestjs/throttler opossum
4
+
5
+ ```
6
+
7
+ ## Add logging middleware to the monorepo
8
+ this middleware intercept all requests hitting the microservices, and log them.
9
+ ```bash
10
+ nest g middleware logger --project apigateway
11
+ ```
apps/apigateway/src/apigateway.controller.spec.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { ApigatewayController } from './apigateway.controller';
3
+ import { ApigatewayService } from './apigateway.service';
4
+
5
+ describe('ApigatewayController', () => {
6
+ let apigatewayController: ApigatewayController;
7
+
8
+ beforeEach(async () => {
9
+ const app: TestingModule = await Test.createTestingModule({
10
+ controllers: [ApigatewayController],
11
+ providers: [ApigatewayService],
12
+ }).compile();
13
+
14
+ apigatewayController = app.get<ApigatewayController>(ApigatewayController);
15
+ });
16
+
17
+ describe('root', () => {
18
+ it('should return "Hello World!"', () => {
19
+ expect(apigatewayController.getHello()).toBe('Hello World!');
20
+ });
21
+ });
22
+ });
apps/apigateway/src/apigateway.controller.ts ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Controller, Req, Res, All, Get, Post } from '@nestjs/common';
2
+ import { Request, Response } from 'express';
3
+
4
+ @Controller(['/', '/healthz'])
5
+ export class ApigatewayController {
6
+
7
+ @Get()
8
+ async healthz(@Req() req: Request, @Res() res: Response) {
9
+ console.log('Request received:', req.method, req.url);
10
+ res.send('🚀 ApiGateway is running...............');
11
+ }
12
+
13
+ @All()
14
+ async test(@Req() req: Request, @Res() res: Response) {
15
+ console.log('POST Request received:', req.method, req.url);
16
+ res.send('🚀 ApiGateway is running...............');
17
+ }
18
+
19
+ }
apps/apigateway/src/apigateway.module.ts ADDED
@@ -0,0 +1,62 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
2
+ import { ConfigModule } from '@nestjs/config';
3
+ import { ThrottlerModule } from '@nestjs/throttler';
4
+ import { HttpModule } from '@nestjs/axios';
5
+
6
+ import { ApigatewayController } from './apigateway.controller';
7
+ import { ApploggerModule } from '@bpm/common';
8
+ import { LoggerMiddleware } from './middlewares/logger/logger.middleware';
9
+
10
+
11
+ import { NorthwindModule } from './modules/northwind/northwind.module';
12
+ import { AzureModule } from './modules/azure/azure.module';
13
+ import { WeatherModule } from './modules/weather/weather.module';
14
+
15
+
16
+ @Module({
17
+ imports: [
18
+ // ✅ Correct usage of ThrottlerModule (Rate Limiting)
19
+ // The limit is the maximum number of requests allowed within the ttl
20
+ // This configuration allows for 3 requests per 1 second (short limit)
21
+ // and 100 requests per 1 minute (long limit)
22
+ ThrottlerModule.forRoot([
23
+ {
24
+ name: 'short',
25
+ ttl: 1000, // 3 requests per 1 seconds (short limit)
26
+ limit: 1,
27
+ },
28
+ {
29
+ name: 'long',
30
+ ttl: 60000, // 100 requests per 1 minute (long limit)
31
+ limit: 100,
32
+ }
33
+ ]),
34
+
35
+ //ConfigModule should be imported with isGlobal: true
36
+ // This makes the configuration available globally in the application
37
+ ConfigModule.forRoot({ isGlobal: true }),
38
+
39
+ // ✅ HttpModule should not be passed directly like a class
40
+ // It provides a simple and easy-to-use interface for making HTTP requests
41
+ HttpModule,
42
+
43
+ ApploggerModule,
44
+
45
+ NorthwindModule,
46
+
47
+ AzureModule,
48
+
49
+ WeatherModule,
50
+
51
+ ],
52
+ controllers: [ApigatewayController],
53
+ providers: [LoggerMiddleware],
54
+ })
55
+ export class ApigatewayModule implements NestModule {
56
+ // No need to bind manually if LoggerMiddleware is injectable
57
+ configure(consumer: MiddlewareConsumer) {
58
+ consumer
59
+ .apply(LoggerMiddleware) // Apply LoggerMiddleware directly
60
+ .forRoutes('*'); // Apply to all routes
61
+ }
62
+ }
apps/apigateway/src/common/circuitbraker/circuitbraker.module.ts ADDED
@@ -0,0 +1,20 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { CircuitbrakerService } from './circuitbraker.service';
3
+ import { ApploggerService } from '@bpm/common';
4
+
5
+ @Module({
6
+ providers: [{
7
+ provide: CircuitbrakerService,
8
+ useFactory: (logger: ApploggerService) => {
9
+ return new CircuitbrakerService({
10
+ failureThreshold: 3, // Fail after 3 consecutive failures
11
+ successThreshold: 2, // Recover after 2 consecutive successes
12
+ timeout: 5000, // Timeout after 5 seconds
13
+ serviceName: 'apigateway', // The name of the service being protected by the circuit breaker
14
+ }, logger);
15
+ },
16
+ inject: [ApploggerService],
17
+ }],
18
+ exports: [CircuitbrakerService], // <-- must export it
19
+ })
20
+ export class CircuitbrakerModule {}
apps/apigateway/src/common/circuitbraker/circuitbraker.service.spec.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { CircuitbrakerService } from './circuitbraker.service';
3
+
4
+ describe('CircuitbrakerService', () => {
5
+ let service: CircuitbrakerService;
6
+
7
+ beforeEach(async () => {
8
+ const module: TestingModule = await Test.createTestingModule({
9
+ providers: [CircuitbrakerService],
10
+ }).compile();
11
+
12
+ service = module.get<CircuitbrakerService>(CircuitbrakerService);
13
+ });
14
+
15
+ it('should be defined', () => {
16
+ expect(service).toBeDefined();
17
+ });
18
+ });
apps/apigateway/src/common/circuitbraker/circuitbraker.service.ts ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ApploggerService } from '@bpm/common';
2
+ import { Injectable } from '@nestjs/common';
3
+
4
+ export type CircuitBreakerState = 'CLOSED' | 'OPEN' | 'HALF_OPEN';
5
+
6
+ interface CircuitBreakerOptions {
7
+ failureThreshold: number;
8
+ successThreshold: number;
9
+ timeout: number; // in ms
10
+ serviceName: string; // Service name to track which service is failing
11
+ }
12
+
13
+ @Injectable()
14
+ export class CircuitbrakerService {
15
+ private state: CircuitBreakerState = 'CLOSED';
16
+ private failureCount = 0;
17
+ private successCount = 0;
18
+ private nextAttempt = Date.now();
19
+
20
+ constructor(private options: CircuitBreakerOptions,
21
+ private readonly logger: ApploggerService
22
+ ) {}
23
+
24
+ async call<T>(fn: () => Promise<T>): Promise<T> {
25
+ if (this.state === 'OPEN') {
26
+ if (Date.now() > this.nextAttempt) {
27
+ this.state = 'HALF_OPEN';
28
+ //this.logger.log(`${this.options.serviceName} - Circuit is HALF_OPEN. Attempting recovery.`, CircuitbrakerService.name);
29
+ } else {
30
+ //this.logger.log(`${this.options.serviceName} - Circuit is OPEN. Request blocked.`, CircuitbrakerService.name);
31
+ throw new Error(`${this.options.serviceName} - Circuit is OPEN`);
32
+ }
33
+ }
34
+
35
+ try {
36
+ const result = await fn();
37
+ this.onSuccess();
38
+ return result;
39
+ } catch (err) {
40
+ this.onFailure(err);
41
+ throw err;
42
+ }
43
+ }
44
+
45
+ private onSuccess() {
46
+ if (this.state === 'HALF_OPEN') {
47
+ this.successCount++;
48
+ //this.logger.log(`${this.options.serviceName} - Success count: ${this.successCount}`, CircuitbrakerService.name);
49
+ if (this.successCount >= this.options.successThreshold) {
50
+ this.reset();
51
+ //this.logger.log(`${this.options.serviceName} - Circuit is now CLOSED`, CircuitbrakerService.name);
52
+ }
53
+ } else {
54
+ this.reset();
55
+ //this.logger.log(`${this.options.serviceName} - Circuit is CLOSED after success`, CircuitbrakerService.name);
56
+ }
57
+ }
58
+
59
+ private onFailure(err: any) {
60
+ const status = err?.response?.status || err?.status || 500;
61
+
62
+ if (status <= 400) {
63
+ //this.logger.log(`${this.options.serviceName} - Ignoring non-error status: ${status}`, CircuitbrakerService.name);
64
+ return; // Don't count it as failure
65
+ }
66
+
67
+ this.failureCount++;
68
+ //this.logger.log(`${this.options.serviceName} - Failure count: ${this.failureCount}`, CircuitbrakerService.name);
69
+ this.logger.error(`${this.options.serviceName} - Failure: ${err.message || 'Unknown error'}`, CircuitbrakerService.name);
70
+
71
+ if (this.failureCount >= this.options.failureThreshold) {
72
+ this.trip();
73
+ //this.logger.log(`${this.options.serviceName} - Circuit is OPEN due to repeated failures`, CircuitbrakerService.name);
74
+ }
75
+ }
76
+
77
+ private reset() {
78
+ this.failureCount = 0;
79
+ this.successCount = 0;
80
+ this.state = 'CLOSED';
81
+ //this.logger.log(`${this.options.serviceName} - Circuit is RESET to CLOSED`, CircuitbrakerService.name);
82
+ }
83
+
84
+ private trip() {
85
+ this.state = 'OPEN';
86
+ this.nextAttempt = Date.now() + this.options.timeout;
87
+ //this.logger.log(`${this.options.serviceName} - Circuit is TRIPPED to OPEN. Next attempt will be after ${this.options.timeout}ms.`, CircuitbrakerService.name);
88
+ }
89
+
90
+ public getState() {
91
+ return this.state;
92
+ }
93
+ }
apps/apigateway/src/main.ts ADDED
@@ -0,0 +1,58 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { ApigatewayModule } from './apigateway.module';
3
+ import { ApploggerService } from '@bpm/common';
4
+
5
+ async function bootstrap() {
6
+ const context = 'apigateway';
7
+ const logger = new ApploggerService(context);
8
+
9
+ logger.log('🛠️ Starting up the NestJS Application...\n', context);
10
+ logger.log('📦 Loading modules...\n', context);
11
+
12
+ const app = await NestFactory.create(ApigatewayModule);
13
+
14
+ app.enableShutdownHooks(); // 👈 Add this
15
+
16
+
17
+ app.enableCors({
18
+ origin: [/http:\/\/localhost:\d+$/, 'https://srepoc-northwind-vuejs-ctbmh6bufhgufqea.b01.azurefd.net'], // Allow all requests from localhost on any port
19
+ credentials: true, // Enable credentials if needed (cookies, auth headers, etc.)
20
+ });
21
+
22
+ app.setGlobalPrefix('api'); // 👈 This sets /api as the prefix
23
+
24
+ const port = process.env.PORT || 3001;
25
+
26
+ try {
27
+ await app.listen(port, () => {
28
+ logger.log('✅ App Modules initialized successfully\n', context);
29
+ logger.log('🌐 Enabling global configurations...\n', context);
30
+
31
+ logger.log(
32
+ `🚀 Application is running at: http://localhost:${port}\n`,
33
+ context,
34
+ );
35
+ logger.log('📡 Ready to accept incoming requests!\n', context);
36
+ logger.log('🧠 Powered by NestJS ❤️\n', context);
37
+ });
38
+ } catch (err) {
39
+ logger.error('❌ Failed to start application\n', err.stack);
40
+ if (app) {
41
+ await app.close();
42
+ }
43
+ }
44
+
45
+ // OS signal listeners (optional)
46
+ process.on('SIGINT', async () => {
47
+ logger.warn('🛑 SIGINT received. Gracefully shutting down...', context);
48
+ await app.close();
49
+ process.exit(1);
50
+ });
51
+
52
+ process.on('SIGTERM', async () => {
53
+ logger.warn('🛑 SIGTERM received. Gracefully shutting down...', context);
54
+ await app.close();
55
+ process.exit(1);
56
+ });
57
+ }
58
+ bootstrap();
apps/apigateway/src/middlewares/logger/logger.middleware.spec.ts ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ import { LoggerMiddleware } from './logger.middleware';
2
+
3
+ import { ApploggerService } from '@bpm/common/app-logger/applogger.service';
4
+
5
+ describe('LoggerMiddleware', () => {
6
+ it('should be defined', () => {
7
+ const mockLogger = {} as ApploggerService;
8
+ expect(new LoggerMiddleware(mockLogger)).toBeDefined();
9
+ });
10
+ });
apps/apigateway/src/middlewares/logger/logger.middleware.ts ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { ApploggerService } from '@bpm/common';
2
+ import { Injectable, NestMiddleware } from '@nestjs/common';
3
+
4
+ @Injectable()
5
+ export class LoggerMiddleware implements NestMiddleware {
6
+ constructor(private readonly logger: ApploggerService) {}
7
+
8
+ use(req: any, res: any, next: () => void) {
9
+ // Make sure to log relevant request details
10
+ this.logger.log(`[${req.method}] ${req.originalUrl}`, "apigateway"); // Log method and URL
11
+ // Log headers or any other information if needed
12
+ //this.logger.log(`Headers: ${JSON.stringify(req.headers)}`, "apigateway");
13
+ next();
14
+ }
15
+ }
apps/apigateway/src/modules/azure/azure.controller.spec.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { AzureController } from './azure.controller';
3
+
4
+ describe('AzureController', () => {
5
+ let controller: AzureController;
6
+
7
+ beforeEach(async () => {
8
+ const module: TestingModule = await Test.createTestingModule({
9
+ controllers: [AzureController],
10
+ }).compile();
11
+
12
+ controller = module.get<AzureController>(AzureController);
13
+ });
14
+
15
+ it('should be defined', () => {
16
+ expect(controller).toBeDefined();
17
+ });
18
+ });
apps/apigateway/src/modules/azure/azure.controller.ts ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Controller, Get, Inject, OnModuleInit, Query } from '@nestjs/common';
2
+ import { ClientGrpc } from '@nestjs/microservices';
3
+ import { Observable } from 'rxjs';
4
+
5
+ interface AzureApiService {
6
+ readSecret(data: { kvName: string; secretName: string }): Observable<{
7
+ kvName: string;
8
+ secretName: string;
9
+ secretValue: string;
10
+ }>;
11
+ }
12
+
13
+ @Controller('azure')
14
+ export class AzureController implements OnModuleInit {
15
+ private grpcService: AzureApiService;
16
+
17
+ constructor(@Inject('AZURE_API_PACKAGE') private readonly client: ClientGrpc) {}
18
+
19
+ onModuleInit() {
20
+ this.grpcService = this.client.getService<AzureApiService>('AzureApiService');
21
+ }
22
+
23
+ @Get('secret')
24
+ async getSecret(@Query('kv') kv: string, @Query('secret') secret: string) {
25
+ return this.grpcService.readSecret({ kvName: kv, secretName: secret });
26
+ }
27
+ }
apps/apigateway/src/modules/azure/azure.module.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { ClientsModule, Transport } from '@nestjs/microservices';
3
+ import { join } from 'path';
4
+ import { AzureController } from './azure.controller';
5
+
6
+ const isProd = process.env.NODE_ENV === 'production';
7
+ const protoPath = isProd
8
+ ? '../../../libs/proto/azureapi.proto'
9
+ : '../../../../../libs/proto/azureapi.proto';
10
+
11
+ //console.log('protoPath', join(__dirname, protoPath));
12
+ const port = process.env.PORT || 3021;
13
+ const apiURL = isProd
14
+ ? process.env.PROD_AZUREAPI_URL || 'weatherapi-nestjs-service.riskiq.svc.cluster.local'
15
+ : 'localhost:' + port;
16
+
17
+ @Module({
18
+ imports: [
19
+ ClientsModule.register([
20
+ {
21
+ name: 'AZURE_API_PACKAGE',
22
+ transport: Transport.GRPC,
23
+ options: {
24
+ package: 'azureapi',
25
+ protoPath: join(__dirname, protoPath), // 👈 Adjust if needed
26
+ url: apiURL, // 👈 Adjust to your gRPC server host/port
27
+ },
28
+ },
29
+ ]),
30
+ ],
31
+ controllers: [AzureController]
32
+ })
33
+ export class AzureModule {}
apps/apigateway/src/modules/northwind/northwind.controller.spec.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { NorthwindController } from './northwind.controller';
3
+
4
+ describe('NorthwindapiController', () => {
5
+ let controller: NorthwindController;
6
+
7
+ beforeEach(async () => {
8
+ const module: TestingModule = await Test.createTestingModule({
9
+ controllers: [NorthwindController],
10
+ }).compile();
11
+
12
+ controller = module.get<NorthwindController>(NorthwindController);
13
+ });
14
+
15
+ it('should be defined', () => {
16
+ expect(controller).toBeDefined();
17
+ });
18
+ });
apps/apigateway/src/modules/northwind/northwind.controller.ts ADDED
@@ -0,0 +1,78 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Controller, Req, Res, All, Get } from '@nestjs/common';
2
+ import { NorthwindService } from './northwind.service';
3
+ import { Request, Response } from 'express';
4
+
5
+ @Controller('northwind')
6
+ export class NorthwindController {
7
+ constructor(private readonly gateway: NorthwindService) {}
8
+
9
+ @Get('/')
10
+ async get(@Req() req: Request, @Res() res: Response) {
11
+ //console.log('Request received:', req.method, req.url);
12
+ res.send('🚀 You have reached northwind service...............');
13
+ }
14
+
15
+ @Get(['/swagger', '/swagger/*'])
16
+ async swagger(@Req() req: Request, @Res() res: Response) {
17
+ const baseUrl = "/api/northwind";
18
+ const strippedPath = req.url.toLocaleLowerCase().slice(baseUrl.length);
19
+ const isProd = process.env.NODE_ENV === 'production';
20
+
21
+ const targetBaseUrl = isProd
22
+ ? 'http://northwindapi-nestjs-service.riskiq.svc.cluster.local'
23
+ : 'http://localhost:3002';
24
+
25
+ console.log(strippedPath);
26
+
27
+ const result = await this.gateway.forwardRequest(
28
+ targetBaseUrl,
29
+ req.method as any,
30
+ strippedPath,
31
+ req.headers,
32
+ undefined
33
+ );
34
+
35
+ // Attempt to parse and transform JSON paths
36
+ if (typeof result === 'object' && result?.paths) {
37
+ const transformed = {
38
+ ...result,
39
+ paths: {},
40
+ };
41
+
42
+ for (const [path, value] of Object.entries(result.paths)) {
43
+ const newPath = path.replace(/^\/api/, baseUrl);
44
+ transformed.paths[newPath] = value;
45
+ }
46
+
47
+ return res.json(transformed);
48
+ }
49
+
50
+ // Fallback to regular response
51
+ return res.send(result);
52
+ }
53
+
54
+
55
+
56
+ @All('*')
57
+ async allRequests(@Req() req: Request, @Res() res: Response) {
58
+ const baseUrl = "/api/northwind";
59
+ const strippedPath = req.url.toLocaleLowerCase().slice(baseUrl.length);
60
+ //console.log('Request received:', req.method, strippedPath);
61
+ const isProd = process.env.NODE_ENV === 'production';
62
+ let targetBaseUrl = isProd
63
+ ? 'http://northwindapi-nestjs-service.riskiq.svc.cluster.local/api'
64
+ : 'http://localhost:3002/api';
65
+
66
+ const hasBody = !['GET', 'HEAD'].includes(req.method.toUpperCase());
67
+
68
+ const result = await this.gateway.forwardRequest(
69
+ targetBaseUrl,
70
+ req.method as any,
71
+ strippedPath,
72
+ req.headers,
73
+ hasBody ? req.body : undefined
74
+ );
75
+
76
+ res.send(result);
77
+ }
78
+ }
apps/apigateway/src/modules/northwind/northwind.module.ts ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { NorthwindController } from './northwind.controller';
3
+ import { NorthwindService } from './northwind.service';
4
+ import { CircuitbrakerModule } from '../../common/circuitbraker/circuitbraker.module';
5
+ import { CircuitbrakerService } from '../../common/circuitbraker/circuitbraker.service';
6
+ import { HttpModule } from '@nestjs/axios';
7
+ import { ApploggerService } from '@bpm/common';
8
+
9
+ @Module({
10
+ imports: [
11
+ HttpModule, // ✅ Import this
12
+ CircuitbrakerModule // ✅ Import CircuitbreakerService from its module
13
+ ],
14
+ controllers: [NorthwindController],
15
+ providers: [NorthwindService,
16
+ {
17
+ provide: CircuitbrakerService,
18
+ useFactory: (logger: ApploggerService) => {
19
+ return new CircuitbrakerService({
20
+ failureThreshold: 3, // Fail after 3 consecutive failures
21
+ successThreshold: 2, // Recover after 2 consecutive successes
22
+ timeout: 5000, // Timeout after 5 seconds
23
+ serviceName: 'apigateway', // The name of the service being protected by the circuit breaker
24
+ }, logger);
25
+ },
26
+ inject: [ApploggerService],
27
+ },
28
+ ],
29
+ exports: [NorthwindService],
30
+ })
31
+ export class NorthwindModule {}
apps/apigateway/src/modules/northwind/northwind.service.spec.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { NorthwindService } from './northwind.service';
3
+
4
+ describe('NorthwindapiService', () => {
5
+ let service: NorthwindService;
6
+
7
+ beforeEach(async () => {
8
+ const module: TestingModule = await Test.createTestingModule({
9
+ providers: [NorthwindService],
10
+ }).compile();
11
+
12
+ service = module.get<NorthwindService>(NorthwindService);
13
+ });
14
+
15
+ it('should be defined', () => {
16
+ expect(service).toBeDefined();
17
+ });
18
+ });
apps/apigateway/src/modules/northwind/northwind.service.ts ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable, InternalServerErrorException } from '@nestjs/common';
2
+ import { HttpService } from '@nestjs/axios';
3
+ //import { lastValueFrom } from 'rxjs';
4
+ import { AxiosRequestConfig } from 'axios';
5
+ import { CircuitbrakerService } from '../../common/circuitbraker/circuitbraker.service';
6
+ import { ApploggerService } from '@bpm/common';
7
+
8
+ @Injectable()
9
+ export class NorthwindService {
10
+ constructor(
11
+ private readonly http: HttpService,
12
+ private readonly circuitBreaker: CircuitbrakerService,
13
+ private readonly logger: ApploggerService
14
+ ) {}
15
+
16
+ async forwardRequest(
17
+ baseUrl: string,
18
+ method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD',
19
+ path: string,
20
+ headers: any,
21
+ body: any,
22
+ ) {
23
+ const url = `${baseUrl.replace(/\/$/, '')}/${path.replace(/^\//, '')}`;
24
+ this.logger.log(`Forwarding to ${url} using ${method}`, NorthwindService.name);
25
+
26
+ // Prepare default config
27
+ const config: AxiosRequestConfig = {
28
+ headers: {...headers },
29
+ url,
30
+ method,
31
+ validateStatus: (status) => status <= 500, // Avoid errors for 304, etc.
32
+ };
33
+
34
+ // Conditionally merge headers based on HTTP method
35
+ if (method === 'GET' || method === 'HEAD') {
36
+ // Create a copy of headers without 'content-type'
37
+ const { ['content-type']: _, ...headersWithoutContentType } = headers;
38
+
39
+ config.headers = {
40
+ ...headersWithoutContentType,
41
+ accept: 'application/json',
42
+ 'cache-control': 'no-cache',
43
+ // 'if-none-match': '', // explicitly force full response (disable ETag)
44
+ }; // Keep original headers for GET and HEAD
45
+ } else {
46
+ config.headers = {
47
+ accept: 'application/json',
48
+ 'content-type': 'application/json', // Only for POST, PUT, DELETE, etc.
49
+ 'cache-control': 'no-cache',
50
+ 'if-none-match': '', // explicitly force full response (disable ETag)
51
+ };
52
+ config.data = body; // Add data only for methods other than GET/HEAD
53
+ }
54
+
55
+
56
+ //this.logger.log(config, NorthwindService.name);
57
+
58
+ try {
59
+ //this.logger.log('Sending request to service through circuit breaker', NorthwindService.name);
60
+ const response = await this.circuitBreaker.call(() =>
61
+ this.http.request(config).toPromise(), // toPromise() converts observable to promise
62
+ );
63
+ // Check if response is defined
64
+ if (!response) {
65
+ throw new InternalServerErrorException('No response received from service');
66
+ }
67
+ return response.data;
68
+ } catch (error) {
69
+ console.error(`Request failed:`, {
70
+ message: error?.message,
71
+ code: error?.code,
72
+ response: error?.response?.data,
73
+ status: error?.response?.status,
74
+ stack: error?.stack,
75
+ });
76
+ throw new InternalServerErrorException('Failed to reach service');
77
+ }
78
+ }
79
+ }
apps/apigateway/src/modules/weather/weather.controller.spec.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { WeatherController } from './weather.controller';
3
+
4
+ describe('WeatherController', () => {
5
+ let controller: WeatherController;
6
+
7
+ beforeEach(async () => {
8
+ const module: TestingModule = await Test.createTestingModule({
9
+ controllers: [WeatherController],
10
+ }).compile();
11
+
12
+ controller = module.get<WeatherController>(WeatherController);
13
+ });
14
+
15
+ it('should be defined', () => {
16
+ expect(controller).toBeDefined();
17
+ });
18
+ });
apps/apigateway/src/modules/weather/weather.controller.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // apps/apigateway/src/weather/weather.controller.ts
2
+ import { Controller, Get, Query } from '@nestjs/common';
3
+ import { WeatherService } from './weather.service';
4
+
5
+ @Controller('weather')
6
+ export class WeatherController {
7
+ constructor(private readonly weatherService: WeatherService) {}
8
+
9
+ @Get('current')
10
+ getCurrentWeather(@Query('city') city: string, @Query('country') country: string) {
11
+ return this.weatherService.getCurrentWeather(city, country);
12
+ }
13
+
14
+ @Get('forecast')
15
+ getForecast(
16
+ @Query('city') city: string,
17
+ @Query('country') country: string,
18
+ @Query('days') days: string,
19
+ ) {
20
+ return this.weatherService.getForecast(city, country, parseInt(days));
21
+ }
22
+ }
apps/apigateway/src/modules/weather/weather.module.ts ADDED
@@ -0,0 +1,37 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { ClientsModule, Transport } from '@nestjs/microservices';
3
+ import { join } from 'path';
4
+ import { WeatherController } from './weather.controller';
5
+ import { WeatherService } from './weather.service';
6
+
7
+ const isProd = process.env.NODE_ENV === 'production';
8
+ const protoPath = isProd
9
+ ? '../../../libs/proto/weatherapi.proto'
10
+ : '../../../../../libs/proto/weatherapi.proto';
11
+
12
+ //console.log('protoPath', join(__dirname, protoPath));
13
+ const port = process.env.PORT || 3022;
14
+ const apiURL = isProd
15
+ ? process.env.PROD_WEATHERAPI_URL || 'weatherapi-nestjs-service.riskiq.svc.cluster.local'
16
+ : 'localhost:' + port;
17
+
18
+
19
+
20
+ @Module({
21
+ imports: [
22
+ ClientsModule.register([
23
+ {
24
+ name: 'WEATHER_API_PACKAGE',
25
+ transport: Transport.GRPC,
26
+ options: {
27
+ package: 'weatherapi',
28
+ protoPath: join(__dirname, protoPath), // 👈 Adjust if needed
29
+ url: apiURL, // 👈 Adjust to your gRPC server host/port
30
+ },
31
+ },
32
+ ]),
33
+ ],
34
+ controllers: [WeatherController],
35
+ providers: [WeatherService]
36
+ })
37
+ export class WeatherModule {}
apps/apigateway/src/modules/weather/weather.service.spec.ts ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { WeatherService } from './weather.service';
3
+
4
+ describe('WeatherService', () => {
5
+ let service: WeatherService;
6
+
7
+ beforeEach(async () => {
8
+ const module: TestingModule = await Test.createTestingModule({
9
+ providers: [WeatherService],
10
+ }).compile();
11
+
12
+ service = module.get<WeatherService>(WeatherService);
13
+ });
14
+
15
+ it('should be defined', () => {
16
+ expect(service).toBeDefined();
17
+ });
18
+ });
apps/apigateway/src/modules/weather/weather.service.ts ADDED
@@ -0,0 +1,61 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // apps/apigateway/src/weather/weather.service.ts
2
+ import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
3
+ import { ClientGrpc } from '@nestjs/microservices';
4
+ import { Observable, lastValueFrom } from 'rxjs';
5
+ import * as opossum from 'opossum';
6
+
7
+ interface WeatherServiceClient {
8
+ GetCurrentWeather(data: { city: string; country: string }): Observable<any>;
9
+ GetForecast(data: { city: string; country: string; days: number }): Observable<any>;
10
+ }
11
+
12
+ @Injectable()
13
+ export class WeatherService implements OnModuleInit {
14
+ private weatherService: WeatherServiceClient;
15
+
16
+ private currentWeatherBreaker: opossum<any, any>;
17
+ private forecastBreaker: opossum<any, any>;
18
+
19
+ constructor(@Inject('WEATHER_API_PACKAGE') private readonly client: ClientGrpc) {}
20
+
21
+ onModuleInit() {
22
+ this.weatherService = this.client.getService<WeatherServiceClient>('WeatherApiService');
23
+
24
+ // Circuit breaker for GetCurrentWeather
25
+ this.currentWeatherBreaker = new opossum(
26
+ (data: { city: string; country: string }) =>
27
+ lastValueFrom(this.weatherService.GetCurrentWeather(data)),
28
+ {
29
+ timeout: 5000, // 5 seconds timeout
30
+ errorThresholdPercentage: 50,
31
+ resetTimeout: 10000, // 10 seconds before retry
32
+ }
33
+ );
34
+
35
+ // Circuit breaker for GetForecast
36
+ this.forecastBreaker = new opossum(
37
+ (data: { city: string; country: string; days: number }) =>
38
+ lastValueFrom(this.weatherService.GetForecast(data)),
39
+ {
40
+ timeout: 5000,
41
+ errorThresholdPercentage: 50,
42
+ resetTimeout: 10000,
43
+ }
44
+ );
45
+
46
+ this.currentWeatherBreaker.on('open', () => console.warn('⚠️ GetCurrentWeather breaker opened'));
47
+ this.currentWeatherBreaker.on('close', () => console.log('✅ GetCurrentWeather breaker closed'));
48
+
49
+ this.forecastBreaker.on('open', () => console.warn('⚠️ GetForecast breaker opened'));
50
+ this.forecastBreaker.on('close', () => console.log('✅ GetForecast breaker closed'));
51
+
52
+ }
53
+
54
+ async getCurrentWeather(city: string, country: string) {
55
+ return this.currentWeatherBreaker.fire({ city, country });
56
+ }
57
+
58
+ async getForecast(city: string, country: string, days: number) {
59
+ return this.forecastBreaker.fire({ city, country, days });
60
+ }
61
+ }
apps/apigateway/src/service-map.json ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ {
2
+ "/proxy/customers": "http://localhost:3002",
3
+ "/oauth": "http://localhost:3004",
4
+ "/weather": "http://localhost:3005",
5
+ "default": "http://localhost:3000"
6
+ }
7
+
apps/apigateway/test/app.e2e-spec.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { INestApplication } from '@nestjs/common';
3
+ import * as request from 'supertest';
4
+ import { ApigatewayModule } from './../src/apigateway.module';
5
+
6
+ describe('ApigatewayController (e2e)', () => {
7
+ let app: INestApplication;
8
+
9
+ beforeEach(async () => {
10
+ const moduleFixture: TestingModule = await Test.createTestingModule({
11
+ imports: [ApigatewayModule],
12
+ }).compile();
13
+
14
+ app = moduleFixture.createNestApplication();
15
+ await app.init();
16
+ });
17
+
18
+ it('/ (GET)', () => {
19
+ return request(app.getHttpServer())
20
+ .get('/')
21
+ .expect(200)
22
+ .expect('Hello World!');
23
+ });
24
+ });
apps/apigateway/test/jest-e2e.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "moduleFileExtensions": ["js", "json", "ts"],
3
+ "rootDir": ".",
4
+ "testEnvironment": "node",
5
+ "testRegex": ".e2e-spec.ts$",
6
+ "transform": {
7
+ "^.+\\.(t|j)s$": "ts-jest"
8
+ }
9
+ }
apps/apigateway/tsconfig.app.json ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "declaration": false,
5
+ "outDir": "../../dist/apps/apigateway",
6
+ "sourceMap": false
7
+ },
8
+ "include": ["src/**/*"],
9
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
10
+ }
apps/azureapi/src/azureapi.controller.spec.ts ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { AzureapiController } from './azureapi.controller';
3
+ import { AzureapiService } from './azureapi.service';
4
+
5
+ describe('AzureapiController', () => {
6
+ let azureapiController: AzureapiController;
7
+
8
+ beforeEach(async () => {
9
+ const app: TestingModule = await Test.createTestingModule({
10
+ controllers: [AzureapiController],
11
+ providers: [AzureapiService],
12
+ }).compile();
13
+
14
+ azureapiController = app.get<AzureapiController>(AzureapiController);
15
+ });
16
+
17
+ describe('root', () => {
18
+ it('should return "Hello World!"', () => {
19
+ expect(azureapiController.getHello()).toBe('Hello World!');
20
+ });
21
+ });
22
+ });
apps/azureapi/src/azureapi.controller.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // azureapi.controller.ts
2
+ import { Controller } from '@nestjs/common';
3
+ import { GrpcMethod } from '@nestjs/microservices';
4
+ import { AzureApiService } from './azureapi.service';
5
+
6
+ @Controller()
7
+ export class AzureApiController {
8
+ constructor(private readonly azureApiService: AzureApiService) {}
9
+
10
+
11
+ @GrpcMethod('AzureApiService', 'readSecret') // <== Matches proto service & method
12
+ readSecret(data: { kvName: string; secretName: string }) {
13
+ const { kvName, secretName } = data;
14
+ return this.azureApiService.readSecret({ kvName, secretName });
15
+ }
16
+ }
apps/azureapi/src/azureapi.module.ts ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Module } from '@nestjs/common';
2
+ import { AzureApiController } from './azureapi.controller';
3
+ import { AzureApiService } from './azureapi.service';
4
+ import { AzureTokenService } from '@bpm/common';
5
+
6
+ @Module({
7
+ imports: [],
8
+ controllers: [AzureApiController],
9
+ providers: [AzureApiService, AzureTokenService],
10
+ })
11
+ export class AzureapiModule {}
apps/azureapi/src/azureapi.service.ts ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable } from '@nestjs/common';
2
+ import { AzureTokenService } from '@bpm/common';
3
+ import { SecretClient } from '@azure/keyvault-secrets';
4
+
5
+ @Injectable()
6
+ export class AzureApiService {
7
+ constructor(private readonly azureTokenService: AzureTokenService) {}
8
+
9
+
10
+ readSecret({ kvName, secretName }: { kvName: string; secretName: string }) {
11
+ //console.log('Token:', this.azureTokenService.getToken());
12
+ const credential = this.azureTokenService.getTokenCredential();
13
+ if (!credential) {
14
+ console.error('Token is not available');
15
+ return null;
16
+ }
17
+ else {
18
+ return {
19
+ kvName,
20
+ secretName,
21
+ secretValue: `Value-of-${secretName}-from-${kvName}`,
22
+ };
23
+ }
24
+ }
25
+ }
apps/azureapi/src/main.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NestFactory } from '@nestjs/core';
2
+ import { AzureapiModule } from './azureapi.module';
3
+ import { Transport, MicroserviceOptions } from '@nestjs/microservices';
4
+ import { join } from 'path';
5
+ import { ApploggerService } from '@bpm/common';
6
+ import * as dotenv from 'dotenv';
7
+
8
+ // Load environment variables from .env file
9
+ dotenv.config();
10
+
11
+ async function bootstrap() {
12
+ const context = 'azureapi';
13
+ const logger = new ApploggerService(context);
14
+
15
+ const isProd = process.env.NODE_ENV === 'production';
16
+ const protoPath = isProd
17
+ ? '../../libs/proto/azureapi.proto'
18
+ : '../../../libs/proto/azureapi.proto';
19
+
20
+ //console.log('protoPath', join(__dirname, protoPath));
21
+
22
+ const port = process.env.PORT || 3021;
23
+ const hostingURL = 'localhost:' + port;
24
+
25
+ NestFactory.createMicroservice<MicroserviceOptions>(AzureapiModule, {
26
+ transport: Transport.GRPC,
27
+ options: {
28
+ package: 'azureapi',
29
+ protoPath: join(__dirname, protoPath),
30
+ url: hostingURL,
31
+ },
32
+ })
33
+ .then(app => {
34
+ logger.log('🛠️ Starting up the NestJS Application...\n', context);
35
+
36
+ return app.listen().then(() => {
37
+ logger.log('✅ App Modules initialized successfully\n', context);
38
+ logger.log('🌐 Enabling global configurations...\n', context);
39
+ logger.log('📡 Ready to accept incoming requests!\n', context);
40
+ logger.log('🧠 Powered by NestJS ❤️\n', context);
41
+ });
42
+ })
43
+ .catch(err => {
44
+ logger.error('❌ Failed to start WeatherAPI microservice: ' + err, context);
45
+ process.exit(1);
46
+ });
47
+
48
+ }
49
+ bootstrap();
apps/azureapi/test/app.e2e-spec.ts ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Test, TestingModule } from '@nestjs/testing';
2
+ import { INestApplication } from '@nestjs/common';
3
+ import * as request from 'supertest';
4
+ import { AzureapiModule } from './../src/azureapi.module';
5
+
6
+ describe('AzureapiController (e2e)', () => {
7
+ let app: INestApplication;
8
+
9
+ beforeEach(async () => {
10
+ const moduleFixture: TestingModule = await Test.createTestingModule({
11
+ imports: [AzureapiModule],
12
+ }).compile();
13
+
14
+ app = moduleFixture.createNestApplication();
15
+ await app.init();
16
+ });
17
+
18
+ it('/ (GET)', () => {
19
+ return request(app.getHttpServer())
20
+ .get('/')
21
+ .expect(200)
22
+ .expect('Hello World!');
23
+ });
24
+ });
apps/azureapi/test/jest-e2e.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "moduleFileExtensions": ["js", "json", "ts"],
3
+ "rootDir": ".",
4
+ "testEnvironment": "node",
5
+ "testRegex": ".e2e-spec.ts$",
6
+ "transform": {
7
+ "^.+\\.(t|j)s$": "ts-jest"
8
+ }
9
+ }
apps/azureapi/tsconfig.app.json ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "extends": "../../tsconfig.json",
3
+ "compilerOptions": {
4
+ "declaration": false,
5
+ "outDir": "../../dist/apps/azureapi"
6
+ },
7
+ "include": ["src/**/*"],
8
+ "exclude": ["node_modules", "dist", "test", "**/*spec.ts"]
9
+ }
apps/northwindapi/src/config/typeorm.config.ts ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DataSource } from 'typeorm';
2
+ import * as Entities from '@bpm/data/models';
3
+ import { config } from 'dotenv';
4
+ config(); // Ensure environment variables are loaded
5
+ import * as path from 'path';
6
+
7
+ // console.log('Database URL:', process.env.DATABASE_URL);
8
+
9
+ const appRoot = path.resolve(__dirname, '..', '..');
10
+ console.log('App root:', appRoot);
11
+
12
+ const dbUrl = new URL(process.env.DATABASE_URL || '');
13
+ // const appDirectory = process.cwd();
14
+ const isDev = process.env.NODE_ENV !== 'production';
15
+ // const sourceDir = isDev ? 'src' : 'dist';
16
+
17
+ // console.log('Current directory:', appDirectory);
18
+ // console.log('Root directory:', rootDir);
19
+ // console.log('isDev:', isDev);
20
+
21
+
22
+ // const entitiesPath = path.join(
23
+ // appRoot,
24
+ // "src",
25
+ // 'models',
26
+ // `*.entity.${isDev ? 'ts' : 'js'}`,
27
+ // );
28
+ // const migrationsPath = path.join(
29
+ // appRoot,
30
+ // "src",
31
+ // 'database',
32
+ // 'migrations',
33
+ // `*-migration.${isDev ? 'ts' : 'js'}`,
34
+ // );
35
+
36
+
37
+ // const entities = path.join(
38
+ // appRoot,
39
+ // "src",
40
+ // 'models',
41
+ // `*.entity.${isDev ? 'ts' : 'js'}`,
42
+ // );
43
+ const migrations = path.join(
44
+ appRoot,
45
+ "src",
46
+ 'database',
47
+ 'migrations',
48
+ `*-migration.${isDev ? 'ts' : 'js'}`,
49
+ );
50
+
51
+ // console.log('Entities path:', entities);
52
+ // console.log('Migrations path:', migrations);
53
+
54
+
55
+
56
+ const AppDataSource = new DataSource({
57
+ type: 'postgres',
58
+ host: dbUrl.hostname,
59
+ port: Number(dbUrl.port) || 5432, // Default to 5432 if not set
60
+ username: dbUrl.username,
61
+ password: dbUrl.password,
62
+ database: dbUrl.pathname.slice(1), // Remove the leading slash from the database name
63
+ // ssl: dbUrl.protocol === 'postgresqls:', // Use SSL if the protocol is postgresqls
64
+ ssl: {
65
+ rejectUnauthorized: false,
66
+ },
67
+ extra: {
68
+ ssl: {
69
+ rejectUnauthorized: false,
70
+ },
71
+ },
72
+ // entities: [entities],
73
+ // migrations: [migrations],
74
+ entities: Object.values(Entities),
75
+ migrations: [migrations],
76
+ synchronize: false, // Set to false in production
77
+ migrationsRun: false,
78
+ logging: false,
79
+ });
80
+
81
+ export default AppDataSource;
apps/northwindapi/src/database/migrations/1745487689382-migration.ts ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MigrationInterface, QueryRunner } from 'typeorm';
2
+
3
+ export class Migration1745487689382 implements MigrationInterface {
4
+ name = 'Migration1745487689382';
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(
8
+ `CREATE TABLE "Shippers" ("ShipperID" SERIAL NOT NULL, "CompanyName" character varying(40) NOT NULL, "Phone" character varying(24), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_475819881aa030a55cbb4dfa077" PRIMARY KEY ("ShipperID"))`,
9
+ );
10
+ await queryRunner.query(
11
+ `CREATE TABLE "Customers" ("CustomerID" character(5) NOT NULL, "CompanyName" character varying(40) NOT NULL, "ContactName" character varying(30), "ContactTitle" character varying(30), "Address" character varying(60), "City" character varying(15), "Region" character varying(15), "PostalCode" character varying(10), "Country" character varying(15), "Phone" character varying(24), "Fax" character varying(24), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_20d9e62f5dfe25e72bc90e46257" PRIMARY KEY ("CustomerID"))`,
12
+ );
13
+ await queryRunner.query(
14
+ `CREATE TABLE "Employees" ("EmployeeID" SERIAL NOT NULL, "LastName" character varying(20) NOT NULL, "FirstName" character varying(10) NOT NULL, "Title" character varying(30), "TitleOfCourtesy" character varying(25), "BirthDate" TIMESTAMP, "HireDate" TIMESTAMP, "Address" character varying(60), "City" character varying(15), "Region" character varying(15), "PostalCode" character varying(10), "Country" character varying(15), "HomePhone" character varying(24), "Extension" character varying(4), "Notes" text, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "ReportsTo" integer, CONSTRAINT "PK_31149b984f38111c8faf85124c7" PRIMARY KEY ("EmployeeID"))`,
15
+ );
16
+ await queryRunner.query(
17
+ `CREATE TABLE "Orders" ("OrderID" SERIAL NOT NULL, "OrderDate" TIMESTAMP, "RequiredDate" TIMESTAMP, "ShippedDate" TIMESTAMP, "Freight" numeric(18,2), "ShipName" character varying(60), "ShipAddress" character varying(60), "ShipCity" character varying(15), "ShipRegion" character varying(15), "ShipPostalCode" character varying(10), "ShipCountry" character varying(15), "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "CustomerID" character(5), "EmployeeID" integer, "ShipVia" integer, CONSTRAINT "PK_55f8443f4d79e9a848cf42b69d9" PRIMARY KEY ("OrderID"))`,
18
+ );
19
+ await queryRunner.query(
20
+ `CREATE TABLE "Suppliers" ("SupplierID" SERIAL NOT NULL, "CompanyName" character varying(40) NOT NULL, "ContactName" character varying(30), "ContactTitle" character varying(30), "Address" character varying(60), "City" character varying(15), "Region" character varying(15), "PostalCode" character varying(10), "Country" character varying(15), "Phone" character varying(24), "Fax" character varying(24), "HomePage" text, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_9af75be88249fe42e9a8fb47629" PRIMARY KEY ("SupplierID"))`,
21
+ );
22
+ await queryRunner.query(
23
+ `CREATE TABLE "Categories" ("CategoryID" SERIAL NOT NULL, "CategoryName" character varying(15) NOT NULL, "Description" text, "Picture" bytea, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), CONSTRAINT "PK_8fb0727baad4afaed25ac7c9861" PRIMARY KEY ("CategoryID"))`,
24
+ );
25
+ await queryRunner.query(
26
+ `CREATE TABLE "Products" ("ProductID" SERIAL NOT NULL, "ProductName" character varying(40) NOT NULL, "QuantityPerUnit" character varying(20), "UnitPrice" numeric(18,2), "UnitsInStock" smallint, "UnitsOnOrder" smallint, "ReorderLevel" smallint, "Discontinued" boolean NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "SupplierID" integer, "CategoryID" integer, CONSTRAINT "PK_07f84037390838453c1426c7cb5" PRIMARY KEY ("ProductID"))`,
27
+ );
28
+ await queryRunner.query(
29
+ `CREATE TABLE "OrderDetails" ("OrderDetailID" SERIAL NOT NULL, "UnitPrice" numeric(18,2) NOT NULL, "Quantity" smallint NOT NULL, "Discount" real NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "OrderID" integer, "ProductID" integer, CONSTRAINT "PK_62169a0638c62361a12768293f8" PRIMARY KEY ("OrderDetailID"))`,
30
+ );
31
+ await queryRunner.query(
32
+ `ALTER TABLE "Employees" ADD CONSTRAINT "FK_0a2e430ee66d427d2fd728ce671" FOREIGN KEY ("ReportsTo") REFERENCES "Employees"("EmployeeID") ON DELETE NO ACTION ON UPDATE NO ACTION`,
33
+ );
34
+ await queryRunner.query(
35
+ `ALTER TABLE "Orders" ADD CONSTRAINT "FK_fcb27b11e453edc543d0a5436eb" FOREIGN KEY ("CustomerID") REFERENCES "Customers"("CustomerID") ON DELETE NO ACTION ON UPDATE NO ACTION`,
36
+ );
37
+ await queryRunner.query(
38
+ `ALTER TABLE "Orders" ADD CONSTRAINT "FK_4ef049bfb564e1dbab6b55d9503" FOREIGN KEY ("EmployeeID") REFERENCES "Employees"("EmployeeID") ON DELETE NO ACTION ON UPDATE NO ACTION`,
39
+ );
40
+ await queryRunner.query(
41
+ `ALTER TABLE "Orders" ADD CONSTRAINT "FK_bd8071f28699758415c62dfd7be" FOREIGN KEY ("ShipVia") REFERENCES "Shippers"("ShipperID") ON DELETE NO ACTION ON UPDATE NO ACTION`,
42
+ );
43
+ await queryRunner.query(
44
+ `ALTER TABLE "Products" ADD CONSTRAINT "FK_3631250b029818892d266a3a0a8" FOREIGN KEY ("SupplierID") REFERENCES "Suppliers"("SupplierID") ON DELETE NO ACTION ON UPDATE NO ACTION`,
45
+ );
46
+ await queryRunner.query(
47
+ `ALTER TABLE "Products" ADD CONSTRAINT "FK_9d404e9029f724e36f0ce2f0024" FOREIGN KEY ("CategoryID") REFERENCES "Categories"("CategoryID") ON DELETE NO ACTION ON UPDATE NO ACTION`,
48
+ );
49
+ await queryRunner.query(
50
+ `ALTER TABLE "OrderDetails" ADD CONSTRAINT "FK_36af61326d32a5b6853c79642f1" FOREIGN KEY ("OrderID") REFERENCES "Orders"("OrderID") ON DELETE NO ACTION ON UPDATE NO ACTION`,
51
+ );
52
+ await queryRunner.query(
53
+ `ALTER TABLE "OrderDetails" ADD CONSTRAINT "FK_2dd62647d008dcfcc7846aee102" FOREIGN KEY ("ProductID") REFERENCES "Products"("ProductID") ON DELETE NO ACTION ON UPDATE NO ACTION`,
54
+ );
55
+ }
56
+
57
+ public async down(queryRunner: QueryRunner): Promise<void> {
58
+ await queryRunner.query(
59
+ `ALTER TABLE "OrderDetails" DROP CONSTRAINT "FK_2dd62647d008dcfcc7846aee102"`,
60
+ );
61
+ await queryRunner.query(
62
+ `ALTER TABLE "OrderDetails" DROP CONSTRAINT "FK_36af61326d32a5b6853c79642f1"`,
63
+ );
64
+ await queryRunner.query(
65
+ `ALTER TABLE "Products" DROP CONSTRAINT "FK_9d404e9029f724e36f0ce2f0024"`,
66
+ );
67
+ await queryRunner.query(
68
+ `ALTER TABLE "Products" DROP CONSTRAINT "FK_3631250b029818892d266a3a0a8"`,
69
+ );
70
+ await queryRunner.query(
71
+ `ALTER TABLE "Orders" DROP CONSTRAINT "FK_bd8071f28699758415c62dfd7be"`,
72
+ );
73
+ await queryRunner.query(
74
+ `ALTER TABLE "Orders" DROP CONSTRAINT "FK_4ef049bfb564e1dbab6b55d9503"`,
75
+ );
76
+ await queryRunner.query(
77
+ `ALTER TABLE "Orders" DROP CONSTRAINT "FK_fcb27b11e453edc543d0a5436eb"`,
78
+ );
79
+ await queryRunner.query(
80
+ `ALTER TABLE "Employees" DROP CONSTRAINT "FK_0a2e430ee66d427d2fd728ce671"`,
81
+ );
82
+ await queryRunner.query(`DROP TABLE "OrderDetails"`);
83
+ await queryRunner.query(`DROP TABLE "Products"`);
84
+ await queryRunner.query(`DROP TABLE "Categories"`);
85
+ await queryRunner.query(`DROP TABLE "Suppliers"`);
86
+ await queryRunner.query(`DROP TABLE "Orders"`);
87
+ await queryRunner.query(`DROP TABLE "Employees"`);
88
+ await queryRunner.query(`DROP TABLE "Customers"`);
89
+ await queryRunner.query(`DROP TABLE "Shippers"`);
90
+ }
91
+ }
apps/northwindapi/src/database/migrations/1748636404589-migration.ts ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { MigrationInterface, QueryRunner } from "typeorm";
2
+
3
+ export class Migration1748636404589 implements MigrationInterface {
4
+ name = 'Migration1748636404589'
5
+
6
+ public async up(queryRunner: QueryRunner): Promise<void> {
7
+ await queryRunner.query(`ALTER TABLE "Customers" ADD "PhotoURL" character varying(60)`);
8
+ }
9
+
10
+ public async down(queryRunner: QueryRunner): Promise<void> {
11
+ await queryRunner.query(`ALTER TABLE "Customers" DROP COLUMN "PhotoURL"`);
12
+ }
13
+
14
+ }
apps/northwindapi/src/database/seed/index.ts ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import AppDataSource from '../../config/typeorm.config';
2
+ import { seedCategories } from './seed-categories';
3
+ import { seedShippers } from './seed-shippers';
4
+ import { seedCustomers } from './seed-customers';
5
+ import { seedEmployees } from './seed-employees';
6
+ import { seedSuppliers } from './seed-suppliers';
7
+ import { seedProducts } from './seed-products';
8
+ import { seedOrders } from './seed-orders';
9
+ import { orderDetailSeed } from './seed-order-details';
10
+
11
+ async function runSeeding() {
12
+ try {
13
+ await AppDataSource.initialize();
14
+ console.log('🚀 DataSource initialized');
15
+
16
+ // Debug: List the loaded entities
17
+ console.log(AppDataSource.entityMetadatas);
18
+
19
+ await seedCategories(AppDataSource);
20
+ await seedShippers(AppDataSource);
21
+ await seedCustomers(AppDataSource);
22
+ await seedEmployees(AppDataSource);
23
+ await seedSuppliers(AppDataSource);
24
+ await seedProducts(AppDataSource);
25
+ await seedOrders(AppDataSource);
26
+ await orderDetailSeed(AppDataSource);
27
+
28
+ await AppDataSource.destroy();
29
+ console.log('✅ Seeding completed, connection closed');
30
+ } catch (error) {
31
+ console.error('❌ Error during seeding:', error);
32
+ process.exit(1);
33
+ }
34
+ }
35
+
36
+ runSeeding();
apps/northwindapi/src/database/seed/seed-categories.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DataSource } from 'typeorm';
2
+ import { Category } from '@bpm/data/models/category.entity';
3
+
4
+ export async function seedCategories(dataSource: DataSource) {
5
+ const repo = dataSource.getRepository(Category);
6
+ const existing = await repo.count();
7
+ if (existing > 0) {
8
+ console.log('🛑 Categories already exist. Skipping...');
9
+ return;
10
+ }
11
+
12
+ const categories = [
13
+ { CategoryName: 'Beverages', Description: 'Soft drinks, tea, coffee' },
14
+ { CategoryName: 'Condiments', Description: 'Sauces and seasonings' },
15
+ { CategoryName: 'Confections', Description: 'Desserts and sweet breads' },
16
+ { CategoryName: 'Dairy Products', Description: 'Cheeses and other dairy' },
17
+ { CategoryName: 'Grains/Cereals', Description: 'Breads, crackers, pasta' },
18
+ { CategoryName: 'Meat/Poultry', Description: 'Prepared meats' },
19
+ {
20
+ CategoryName: 'Produce',
21
+ Description: 'Dried and fresh fruit/vegetables',
22
+ },
23
+ { CategoryName: 'Seafood', Description: 'Seaweed, fish' },
24
+ { CategoryName: 'Snacks', Description: 'Chips and small packs' },
25
+ { CategoryName: 'Health', Description: 'Supplements and vitamins' },
26
+ ];
27
+
28
+ await repo.save(categories);
29
+ console.log('✅ Seeded categories');
30
+ }
apps/northwindapi/src/database/seed/seed-customers.ts ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // src/database/seed/seed-customers.ts
2
+ import { DataSource } from 'typeorm';
3
+ import { Customer } from '@bpm/data/models/customer.entity';
4
+
5
+ export async function seedCustomers(dataSource: DataSource) {
6
+ const repo = dataSource.getRepository(Customer);
7
+ const existing = await repo.count();
8
+ if (existing > 0) {
9
+ console.log('🛑 Customers already exist. Skipping...');
10
+ return;
11
+ }
12
+
13
+ const customers: Partial<Customer>[] = [
14
+ {
15
+ CustomerID: 'ALFKI',
16
+ CompanyName: 'Alfreds Futterkiste',
17
+ ContactName: 'Maria Anders',
18
+ ContactTitle: 'Sales Representative',
19
+ Address: 'Obere Str. 57',
20
+ City: 'Berlin',
21
+ PostalCode: '12209',
22
+ Country: 'Germany',
23
+ Phone: '030-0074321',
24
+ Fax: '030-0076545',
25
+ PhotoURL: 'https://randomuser.me/api/portraits/men/1.jpg'
26
+ },
27
+ {
28
+ CustomerID: 'ANATR',
29
+ CompanyName: 'Ana Trujillo Emparedados y helados',
30
+ ContactName: 'Ana Trujillo',
31
+ ContactTitle: 'Owner',
32
+ Address: 'Avda. de la Constitución 2222',
33
+ City: 'México D.F.',
34
+ PostalCode: '05021',
35
+ Country: 'Mexico',
36
+ Phone: '(5) 555-4729',
37
+ Fax: '(5) 555-3745',
38
+ PhotoURL: 'https://randomuser.me/api/portraits/men/2.jpg'
39
+ },
40
+ {
41
+ CustomerID: 'ANTON',
42
+ CompanyName: 'Antonio Moreno Taquería',
43
+ ContactName: 'Antonio Moreno',
44
+ ContactTitle: 'Owner',
45
+ Address: 'Mataderos 2312',
46
+ City: 'México D.F.',
47
+ PostalCode: '05023',
48
+ Country: 'Mexico',
49
+ Phone: '(5) 555-3932',
50
+ PhotoURL: 'https://randomuser.me/api/portraits/women/3.jpg'
51
+ },
52
+ {
53
+ CustomerID: 'AROUT',
54
+ CompanyName: 'Around the Horn',
55
+ ContactName: 'Thomas Hardy',
56
+ ContactTitle: 'Sales Representative',
57
+ Address: '120 Hanover Sq.',
58
+ City: 'London',
59
+ PostalCode: 'WA1 1DP',
60
+ Country: 'UK',
61
+ Phone: '(171) 555-7788',
62
+ Fax: '(171) 555-6750',
63
+ PhotoURL: 'https://randomuser.me/api/portraits/men/4.jpg'
64
+ },
65
+ {
66
+ CustomerID: 'BERGS',
67
+ CompanyName: 'Berglunds snabbköp',
68
+ ContactName: 'Christina Berglund',
69
+ ContactTitle: 'Order Administrator',
70
+ Address: 'Berguvsvägen 8',
71
+ City: 'Luleå',
72
+ PostalCode: 'S-958 22',
73
+ Country: 'Sweden',
74
+ Phone: '0921-12 34 65',
75
+ Fax: '0921-12 34 67',
76
+ PhotoURL: 'https://randomuser.me/api/portraits/women/5.jpg'
77
+ },
78
+ {
79
+ CustomerID: 'BLAUS',
80
+ CompanyName: 'Blauer See Delikatessen',
81
+ ContactName: 'Hanna Moos',
82
+ ContactTitle: 'Sales Representative',
83
+ Address: 'Forsterstr. 57',
84
+ City: 'Mannheim',
85
+ PostalCode: '68306',
86
+ Country: 'Germany',
87
+ Phone: '0621-08460',
88
+ Fax: '0621-08924',
89
+ PhotoURL: 'https://randomuser.me/api/portraits/women/6.jpg'
90
+ },
91
+ {
92
+ CustomerID: 'BLONP',
93
+ CompanyName: 'Blondel père et fils',
94
+ ContactName: 'Frédérique Citeaux',
95
+ ContactTitle: 'Marketing Manager',
96
+ Address: '24, place Kléber',
97
+ City: 'Strasbourg',
98
+ PostalCode: '67000',
99
+ Country: 'France',
100
+ Phone: '88.60.15.31',
101
+ Fax: '88.60.15.32',
102
+ PhotoURL: 'https://randomuser.me/api/portraits/men/7.jpg'
103
+ },
104
+ {
105
+ CustomerID: 'BOLID',
106
+ CompanyName: 'Bólido Comidas preparadas',
107
+ ContactName: 'Martín Sommer',
108
+ ContactTitle: 'Owner',
109
+ Address: 'C/ Araquil, 67',
110
+ City: 'Madrid',
111
+ PostalCode: '28023',
112
+ Country: 'Spain',
113
+ Phone: '(91) 555 22 82',
114
+ Fax: '(91) 555 91 99',
115
+ PhotoURL: 'https://randomuser.me/api/portraits/women/8.jpg'
116
+ },
117
+ {
118
+ CustomerID: 'BONAP',
119
+ CompanyName: "Bon app'",
120
+ ContactName: 'Laurence Lebihan',
121
+ ContactTitle: 'Owner',
122
+ Address: '12, rue des Bouchers',
123
+ City: 'Marseille',
124
+ PostalCode: '13008',
125
+ Country: 'France',
126
+ Phone: '91.24.45.40',
127
+ Fax: '91.24.45.41',
128
+ PhotoURL: 'https://randomuser.me/api/portraits/women/9.jpg'
129
+ },
130
+ {
131
+ CustomerID: 'BOTTM',
132
+ CompanyName: 'Bottom-Dollar Markets',
133
+ ContactName: 'Elizabeth Lincoln',
134
+ ContactTitle: 'Accounting Manager',
135
+ Address: '23 Tsawassen Blvd.',
136
+ City: 'Tsawassen',
137
+ Region: 'BC',
138
+ PostalCode: 'T2F 8M4',
139
+ Country: 'Canada',
140
+ Phone: '(604) 555-4729',
141
+ Fax: '(604) 555-3745',
142
+ PhotoURL: 'https://randomuser.me/api/portraits/men/10.jpg'
143
+ },
144
+ ];
145
+
146
+ await repo.save(customers);
147
+ console.log('✅ Seeded customers');
148
+ }
apps/northwindapi/src/database/seed/seed-employees.ts ADDED
@@ -0,0 +1,173 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ../../database/seed/seed-employees.ts
2
+ import { DataSource, DeepPartial } from 'typeorm';
3
+ import { Employee } from '@bpm/data/models/employee.entity';
4
+
5
+ export async function seedEmployees(dataSource: DataSource) {
6
+ const repo = dataSource.getRepository(Employee);
7
+ const existing = await repo.count();
8
+ if (existing > 0) {
9
+ console.log('🛑 Employees already exist. Skipping...');
10
+ return;
11
+ }
12
+
13
+ const now = new Date();
14
+ const employees = repo.create([
15
+ {
16
+ FirstName: 'Nancy',
17
+ LastName: 'Davolio',
18
+ Title: 'Sales Representative',
19
+ TitleOfCourtesy: 'Ms.',
20
+ BirthDate: new Date('1948-12-08'),
21
+ HireDate: new Date('1992-05-01'),
22
+ Address: '507 - 20th Ave. E. Apt. 2A',
23
+ City: 'Seattle',
24
+ Region: 'WA',
25
+ PostalCode: '98122',
26
+ Country: 'USA',
27
+ HomePhone: '(206) 555-9857',
28
+ Extension: '5467',
29
+ Notes: 'Education includes a BA in psychology.',
30
+ },
31
+ {
32
+ FirstName: 'Andrew',
33
+ LastName: 'Fuller',
34
+ Title: 'Vice President, Sales',
35
+ TitleOfCourtesy: 'Dr.',
36
+ BirthDate: new Date('1952-02-19'),
37
+ HireDate: new Date('1992-08-14'),
38
+ Address: '908 W. Capital Way',
39
+ City: 'Tacoma',
40
+ Region: 'WA',
41
+ PostalCode: '98401',
42
+ Country: 'USA',
43
+ HomePhone: '(206) 555-9482',
44
+ Extension: '3457',
45
+ Notes: 'Andrew received his BTS commercial in 1974.',
46
+ },
47
+ {
48
+ FirstName: 'Janet',
49
+ LastName: 'Leverling',
50
+ Title: 'Sales Representative',
51
+ TitleOfCourtesy: 'Ms.',
52
+ BirthDate: new Date('1963-08-30'),
53
+ HireDate: new Date('1992-04-01'),
54
+ Address: '722 Moss Bay Blvd.',
55
+ City: 'Kirkland',
56
+ Region: 'WA',
57
+ PostalCode: '98033',
58
+ Country: 'USA',
59
+ HomePhone: '(206) 555-3412',
60
+ Extension: '3355',
61
+ Notes: 'Janet has a BS degree in chemistry.',
62
+ },
63
+ {
64
+ FirstName: 'Margaret',
65
+ LastName: 'Peacock',
66
+ Title: 'Sales Representative',
67
+ TitleOfCourtesy: 'Mrs.',
68
+ BirthDate: new Date('1937-09-19'),
69
+ HireDate: new Date('1993-05-03'),
70
+ Address: '4110 Old Redmond Rd.',
71
+ City: 'Redmond',
72
+ Region: 'WA',
73
+ PostalCode: '98052',
74
+ Country: 'USA',
75
+ HomePhone: '(206) 555-8122',
76
+ Extension: '5176',
77
+ Notes: 'Margaret holds a BA in English literature.',
78
+ },
79
+ {
80
+ FirstName: 'Steven',
81
+ LastName: 'Buchanan',
82
+ Title: 'Sales Manager',
83
+ TitleOfCourtesy: 'Mr.',
84
+ BirthDate: new Date('1955-03-04'),
85
+ HireDate: new Date('1993-10-17'),
86
+ Address: '14 Garrett Hill',
87
+ City: 'London',
88
+ Region: null,
89
+ PostalCode: 'SW1 8JR',
90
+ Country: 'UK',
91
+ HomePhone: '(71) 555-4848',
92
+ Extension: '3453',
93
+ Notes: 'Steven was a Navy officer before his sales career.',
94
+ },
95
+ {
96
+ FirstName: 'Michael',
97
+ LastName: 'Suyama',
98
+ Title: 'Sales Representative',
99
+ TitleOfCourtesy: 'Mr.',
100
+ BirthDate: new Date('1963-07-02'),
101
+ HireDate: new Date('1993-10-17'),
102
+ Address: 'Coventry House Miner Rd.',
103
+ City: 'London',
104
+ Region: null,
105
+ PostalCode: 'EC2 7JR',
106
+ Country: 'UK',
107
+ HomePhone: '(71) 555-7773',
108
+ Extension: '428',
109
+ Notes: 'Michael enjoys tennis and classical music.',
110
+ },
111
+ {
112
+ FirstName: 'Robert',
113
+ LastName: 'King',
114
+ Title: 'Sales Representative',
115
+ TitleOfCourtesy: 'Mr.',
116
+ BirthDate: new Date('1960-05-29'),
117
+ HireDate: new Date('1994-01-02'),
118
+ Address: 'Edgeham Hollow Winchester Way',
119
+ City: 'London',
120
+ Region: null,
121
+ PostalCode: 'RG1 9SP',
122
+ Country: 'UK',
123
+ HomePhone: '(71) 555-5598',
124
+ Extension: '465',
125
+ Notes: 'Robert is a certified Salesforce administrator.',
126
+ },
127
+ {
128
+ FirstName: 'Laura',
129
+ LastName: 'Callahan',
130
+ Title: 'Inside Sales Coordinator',
131
+ TitleOfCourtesy: 'Ms.',
132
+ BirthDate: new Date('1958-01-09'),
133
+ HireDate: new Date('1994-03-05'),
134
+ Address: '4726 - 11th Ave. N.E.',
135
+ City: 'Seattle',
136
+ Region: 'WA',
137
+ PostalCode: '98105',
138
+ Country: 'USA',
139
+ HomePhone: '(206) 555-1189',
140
+ Extension: '2344',
141
+ Notes: 'Laura is passionate about volunteer work.',
142
+ },
143
+ {
144
+ FirstName: 'Anne',
145
+ LastName: 'Dodsworth',
146
+ Title: 'Sales Representative',
147
+ TitleOfCourtesy: 'Ms.',
148
+ BirthDate: new Date('1966-01-27'),
149
+ HireDate: new Date('1994-11-15'),
150
+ Address: '7 Houndstooth Rd.',
151
+ City: 'London',
152
+ Region: null,
153
+ PostalCode: 'WG2 7LT',
154
+ Country: 'UK',
155
+ HomePhone: '(71) 555-4444',
156
+ Extension: '452',
157
+ Notes: 'Anne enjoys travel and gourmet cooking.',
158
+ },
159
+ ] as DeepPartial<Employee>[]);
160
+
161
+ const savedEmployees = await repo.save(employees);
162
+
163
+ // Add ReportsTo relationships
164
+ await repo.save([
165
+ { ...savedEmployees[0], ReportsTo: savedEmployees[1] }, // Nancy -> Andrew
166
+ { ...savedEmployees[2], ReportsTo: savedEmployees[1] }, // Janet -> Andrew
167
+ { ...savedEmployees[3], ReportsTo: savedEmployees[4] }, // Margaret -> Steven
168
+ { ...savedEmployees[5], ReportsTo: savedEmployees[4] }, // Michael -> Steven
169
+ { ...savedEmployees[6], ReportsTo: savedEmployees[4] }, // Robert -> Steven
170
+ { ...savedEmployees[8], ReportsTo: savedEmployees[4] }, // Anne -> Steven
171
+ ]);
172
+ console.log('✅ Seeded employees');
173
+ }
apps/northwindapi/src/database/seed/seed-order-details.ts ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { DataSource } from 'typeorm';
2
+ import { OrderDetail } from '@bpm/data/models/orderdetail.entity';
3
+ import { Order } from '@bpm/data/models/order.entity';
4
+ import { Product } from '@bpm/data/models/product.entity';
5
+
6
+ export async function orderDetailSeed(dataSource: DataSource) {
7
+ const orderRepo = dataSource.getRepository(Order);
8
+ const productRepo = dataSource.getRepository(Product);
9
+ const orderDetailRepo = dataSource.getRepository(OrderDetail);
10
+
11
+ const existing = await orderDetailRepo.count();
12
+ if (existing > 0) {
13
+ console.log('🛑 OrderDetail already exist. Skipping...');
14
+ return;
15
+ }
16
+
17
+ const orders = await orderRepo.find();
18
+ const products = await productRepo.find();
19
+
20
+ if (orders.length === 0 || products.length === 0) {
21
+ throw new Error(
22
+ '🛑 Orders or Products must be seeded before OrderDetails.',
23
+ );
24
+ }
25
+
26
+ // const orders = await orderRepo.find({ take: 10 }); // adjust as needed
27
+ // const products = await productRepo.find({ take: 30 });
28
+
29
+ const orderDetails: OrderDetail[] = [];
30
+
31
+ for (let i = 0; i < 20; i++) {
32
+ const order = orders[Math.floor(Math.random() * orders.length)];
33
+ const product = products[Math.floor(Math.random() * products.length)];
34
+
35
+ const detail = new OrderDetail();
36
+ detail.Order = order;
37
+ detail.OrderID = order.OrderID;
38
+ detail.Product = product;
39
+ detail.ProductID = product.ProductID;
40
+ detail.UnitPrice = parseFloat((Math.random() * 100 + 10).toFixed(2));
41
+ detail.Quantity = Math.floor(Math.random() * 10) + 1;
42
+ detail.Discount = parseFloat((Math.random() * 0.5).toFixed(2));
43
+
44
+ orderDetails.push(detail);
45
+ }
46
+
47
+ await orderDetailRepo.save(orderDetails);
48
+ console.log('✅ Seeded OrderDetails with realistic product lines.');
49
+ }