nebula-starter-kit 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +107 -0
  2. package/dist/index.js +8 -0
  3. package/dist/run.js +112 -0
  4. package/dist/utils/addService.js +204 -0
  5. package/dist/utils/appName.js +75 -0
  6. package/dist/utils/deployNow.js +18 -0
  7. package/dist/utils/generateFiles.js +326 -0
  8. package/dist/utils/generateSchemas.js +58 -0
  9. package/dist/utils/listServices.js +24 -0
  10. package/dist/utils/replaceServiceNames.js +42 -0
  11. package/dist/utils/telemetryAddon.js +91 -0
  12. package/package.json +31 -0
  13. package/templates/core/audit/audit.controller.ts +125 -0
  14. package/templates/core/audit/audit.module.ts +10 -0
  15. package/templates/core/audit/audit.schema.ts +14 -0
  16. package/templates/core/audit/audit.service.ts +47 -0
  17. package/templates/core/auth/auth.controller.ts +207 -0
  18. package/templates/core/auth/auth.dto.ts +50 -0
  19. package/templates/core/auth/auth.module.ts +10 -0
  20. package/templates/core/auth/auth.service.ts +178 -0
  21. package/templates/core/auth/utils.ts +160 -0
  22. package/templates/core/constants/environment.db.ts +27 -0
  23. package/templates/core/constants/environment.module.ts +9 -0
  24. package/templates/core/constants/environment.service.ts +69 -0
  25. package/templates/core/core.controller.ts +35 -0
  26. package/templates/core/core.module.ts +22 -0
  27. package/templates/core/database/database.module.ts +20 -0
  28. package/templates/core/database/database.provider.ts +32 -0
  29. package/templates/core/database/database.service.ts +168 -0
  30. package/templates/core/database/database.types.ts +13 -0
  31. package/templates/core/filters/audit.decorator.ts +5 -0
  32. package/templates/core/filters/audit.interceptor.ts +74 -0
  33. package/templates/core/filters/http-exception.filter.ts +43 -0
  34. package/templates/core/filters/success-message.decorator.ts +5 -0
  35. package/templates/core/filters/success-response.interceptor.ts +35 -0
  36. package/templates/core/summarize/summarize.controller.ts +74 -0
  37. package/templates/core/summarize/summarize.dto.ts +13 -0
  38. package/templates/core/summarize/summarize.module.ts +9 -0
  39. package/templates/core/summarize/summarize.service.ts +54 -0
  40. package/templates/nest-cli.json +8 -0
  41. package/templates/package.json +52 -0
  42. package/templates/service/src/__name__.controller.ts +15 -0
  43. package/templates/service/src/__name__.module.ts +11 -0
  44. package/templates/service/src/__name__.schema.ts +15 -0
  45. package/templates/service/src/__name__.service.ts +12 -0
  46. package/templates/service/src/lambda.ts +60 -0
  47. package/templates/tsconfig.json +28 -0
  48. package/templates/ui/README.md +36 -0
  49. package/templates/ui/eslint.config.mjs +18 -0
  50. package/templates/ui/next.config.ts +8 -0
  51. package/templates/ui/package.json +33 -0
  52. package/templates/ui/postcss.config.mjs +7 -0
  53. package/templates/ui/public/file.svg +1 -0
  54. package/templates/ui/public/globe.svg +1 -0
  55. package/templates/ui/public/next.svg +1 -0
  56. package/templates/ui/public/vercel.svg +1 -0
  57. package/templates/ui/public/window.svg +1 -0
  58. package/templates/ui/src/app/LandingPage.tsx +98 -0
  59. package/templates/ui/src/app/ai/summarize/page.tsx +115 -0
  60. package/templates/ui/src/app/context/AuthContext.tsx +48 -0
  61. package/templates/ui/src/app/favicon.ico +0 -0
  62. package/templates/ui/src/app/globals.css +26 -0
  63. package/templates/ui/src/app/layout.tsx +37 -0
  64. package/templates/ui/src/app/page.tsx +7 -0
  65. package/templates/ui/src/app/services/page.tsx +99 -0
  66. package/templates/ui/src/components/Auth.css +252 -0
  67. package/templates/ui/src/components/Auth.tsx +455 -0
  68. package/templates/ui/src/components/Error.tsx +32 -0
  69. package/templates/ui/src/components/FormInput.tsx +77 -0
  70. package/templates/ui/src/components/Loading.tsx +10 -0
  71. package/templates/ui/src/components/Login.tsx +171 -0
  72. package/templates/ui/src/components/Popup.css +90 -0
  73. package/templates/ui/src/components/Signup.tsx +155 -0
  74. package/templates/ui/src/utils/axiosInstance.ts +37 -0
  75. package/templates/ui/src/utils/axiosRawInstance.ts +33 -0
  76. package/templates/ui/src/utils/util.constant.ts +0 -0
  77. package/templates/ui/src/utils/util.function.ts +165 -0
  78. package/templates/ui/src/utils/util.type.ts +64 -0
  79. package/templates/ui/src/utils/variables.ts +6 -0
  80. package/templates/ui/tailwind.config.js +8 -0
  81. package/templates/ui/tsconfig.json +43 -0
@@ -0,0 +1,9 @@
1
+ import { Global, Module } from '@nestjs/common';
2
+ import { EnvironmentService } from './environment.service';
3
+
4
+ @Global()
5
+ @Module({
6
+ providers: [EnvironmentService],
7
+ exports: [EnvironmentService],
8
+ })
9
+ export class EnvironmentModule {}
@@ -0,0 +1,69 @@
1
+ import { Injectable } from '@nestjs/common';
2
+ import { ConfigService } from '@nestjs/config';
3
+
4
+ @Injectable()
5
+ export class EnvironmentService {
6
+ constructor(private config: ConfigService) {}
7
+ // Environment variables
8
+ private get(key: string, required = true): string {
9
+ const value = this.config?.get<string>(key) || process.env[key];
10
+ if (required && !value) {
11
+ throw new Error(`Missing required environment variable: ${key}`);
12
+ }
13
+ return value ?? '';
14
+ }
15
+
16
+ isLocal(): boolean {
17
+ return this.get('NODE_ENV') !== 'production';
18
+ }
19
+
20
+ isProd(): boolean {
21
+ return this.get('NODE_ENV') === 'production';
22
+ }
23
+
24
+ isLambda(): boolean {
25
+ return !!this.get('AWS_LAMBDA_FUNCTION_NAME');
26
+ }
27
+
28
+ region(): string {
29
+ return this.get('REGION') || this.get('AWS_REGION') || 'us-east-1';
30
+ }
31
+
32
+ accessKeyId(): string {
33
+ return this.get('AWS_ACCESS_KEY_ID') || 'local';
34
+ }
35
+
36
+ secretAccessKey(): string {
37
+ return this.get('AWS_SECRET_ACCESS_KEY') || 'local';
38
+ }
39
+
40
+ dynamoEndpoint(): string {
41
+ return this.get('DYNAMO_ENDPOINT') || 'http://localhost:8000';
42
+ }
43
+
44
+ appPort(): number {
45
+ return Number(this.get('PORT')) || 3000;
46
+ }
47
+
48
+ appName(): string {
49
+ return this.get('APP_NAME') || 'NEBULA';
50
+ }
51
+
52
+ COGNITO_USER_POOL_ID(): string {
53
+ return this.get('COGNITO_USER_POOL_ID') || 'COGNITO_USER_POOL_ID';
54
+ }
55
+
56
+ COGNITO_CLIENT_ID(): string {
57
+ return this.get('COGNITO_CLIENT_ID') || 'COGNITO_CLIENT_ID';
58
+ }
59
+
60
+ tables(): string[] {
61
+ const tables = this.get('DYNAMODB_TABLES') || null;
62
+ console.log('tables', tables);
63
+ return tables ? tables.split(',') : ['users'];
64
+ }
65
+
66
+ BEDROCK_MODEL(): string {
67
+ return this.get('BEDROCK_MODEL') || 'BEDROCK_MODEL';
68
+ }
69
+ }
@@ -0,0 +1,35 @@
1
+ import { Controller, Get, Req, Res } from '@nestjs/common';
2
+ import { Request, Response } from 'express';
3
+ import { EnvironmentService } from './constants/environment.service';
4
+
5
+ import * as dotenv from 'dotenv';
6
+ dotenv.config();
7
+
8
+ @Controller('core')
9
+ export class CoreController {
10
+ constructor(private readonly config: EnvironmentService) {}
11
+
12
+ @Get('/env-config.js')
13
+ getEnvConfig(@Req() req: Request, @Res() res: Response) {
14
+ const protocol = req.headers['x-forwarded-proto'] ?? req.protocol;
15
+
16
+ const host = req.headers['host'];
17
+
18
+ const apiUrl = `${protocol}://${host}`;
19
+
20
+ const js = `
21
+ window.__ENV__ = {
22
+ API_BASE_URL: "${apiUrl}",
23
+ COGNITO_USER_POOL_ID: "${
24
+ this.config?.COGNITO_USER_POOL_ID() || process.env.COGNITO_USER_POOL_ID
25
+ }",
26
+ COGNITO_CLIENT_ID: "${
27
+ this.config?.COGNITO_CLIENT_ID() || process.env.COGNITO_CLIENT_ID
28
+ }"
29
+ };
30
+ `;
31
+
32
+ res.type('application/javascript');
33
+ res.send(js);
34
+ }
35
+ }
@@ -0,0 +1,22 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { ConfigModule } from '@nestjs/config';
3
+ import { EnvironmentModule } from './constants/environment.module';
4
+ import { CoreController } from './core.controller';
5
+ import { DatabaseModule } from './database/database.module';
6
+ import { AuthModule } from './auth/auth.module';
7
+ import { SummarizeModule } from './summarize/summarize.module';
8
+ import { AuditModule } from './audit/audit.module';
9
+
10
+ @Module({
11
+ imports: [
12
+ // __GENERATED_IMPORTS__,
13
+ ConfigModule.forRoot({ isGlobal: true, envFilePath: '.env' }),
14
+ EnvironmentModule,
15
+ DatabaseModule,
16
+ AuthModule,
17
+ AuditModule,
18
+ SummarizeModule,
19
+ ],
20
+ controllers: [CoreController],
21
+ })
22
+ export class CoreModule {}
@@ -0,0 +1,20 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { EnvironmentService } from '../constants/environment.service';
3
+ import { DatabaseService } from './database.service';
4
+ import {
5
+ DYNAMODB_DOC,
6
+ DYNAMODB_RAW,
7
+ DynamoDBDocProvider,
8
+ DynamoDBRawProvider,
9
+ } from './database.provider';
10
+
11
+ @Module({
12
+ providers: [
13
+ DynamoDBRawProvider,
14
+ DynamoDBDocProvider,
15
+ DatabaseService,
16
+ EnvironmentService,
17
+ ],
18
+ exports: [DYNAMODB_DOC, DYNAMODB_RAW, DatabaseService],
19
+ })
20
+ export class DatabaseModule {}
@@ -0,0 +1,32 @@
1
+ import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
2
+ import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
3
+ import { EnvironmentService } from '../constants/environment.service';
4
+
5
+ export const DYNAMODB_RAW = 'DYNAMODB_RAW';
6
+ export const DYNAMODB_DOC = 'DYNAMODB_DOC';
7
+
8
+ export const DynamoDBRawProvider = {
9
+ provide: DYNAMODB_RAW,
10
+ inject: [EnvironmentService],
11
+ useFactory: (env: EnvironmentService) => {
12
+ return new DynamoDBClient({
13
+ region: env.region(),
14
+ ...(!env.isLambda() && {
15
+ endpoint: env.dynamoEndpoint(),
16
+ credentials: {
17
+ accessKeyId: env.accessKeyId(),
18
+ secretAccessKey: env.secretAccessKey(),
19
+ },
20
+ }),
21
+ });
22
+ },
23
+ };
24
+
25
+ export const DynamoDBDocProvider = {
26
+ provide: DYNAMODB_DOC,
27
+ inject: [DYNAMODB_RAW],
28
+ useFactory: (client: DynamoDBClient) =>
29
+ DynamoDBDocumentClient.from(client, {
30
+ marshallOptions: { removeUndefinedValues: true },
31
+ }),
32
+ };
@@ -0,0 +1,168 @@
1
+ import { Inject, Injectable, OnModuleInit } from '@nestjs/common';
2
+ import {
3
+ DynamoDBClient,
4
+ CreateTableCommand,
5
+ DescribeTableCommand,
6
+ waitUntilTableExists,
7
+ KeyType,
8
+ UpdateTableCommand,
9
+ } from '@aws-sdk/client-dynamodb';
10
+ import { EnvironmentService } from '../constants/environment.service';
11
+ import { GSIConfig, TableConfig } from './database.types';
12
+ import { GENERATED_SCHEMAS } from './generated-schemas';
13
+ import { DYNAMODB_DOC } from './database.provider';
14
+
15
+ @Injectable()
16
+ export class DatabaseService implements OnModuleInit {
17
+ schemas;
18
+ constructor(
19
+ @Inject(DYNAMODB_DOC) private client: DynamoDBClient,
20
+ private env: EnvironmentService,
21
+ ) {
22
+ this.schemas = GENERATED_SCHEMAS;
23
+ console.log('DBSchemas', this.schemas);
24
+ }
25
+
26
+ async onModuleInit() {
27
+ await this.ensureTables(this.schemas);
28
+ }
29
+
30
+ public async ensureTables(tables: TableConfig[]) {
31
+ console.log(
32
+ 'ENVIRONMENT:',
33
+ `${this.env?.isLambda() ? 'PRODUCTION' : 'DEVELOPMENT'}`,
34
+ );
35
+ const results: { table: string; status: string; error?: string }[] = [];
36
+ for (const table of tables) {
37
+ try {
38
+ await this.ensureSingleTable(table);
39
+ results.push({ table: table.tableName, status: 'ensured' });
40
+ } catch (err: any) {
41
+ results.push({
42
+ table: table.tableName,
43
+ status: 'error',
44
+ error: err.message,
45
+ });
46
+ }
47
+ }
48
+ console.log('Returning results', results);
49
+ return results;
50
+ }
51
+
52
+ private buildAttributeDefinitions(
53
+ hashKey: string,
54
+ rangeKey: string,
55
+ gsis: GSIConfig[] = [],
56
+ ) {
57
+ const attributes = new Map<string, 'S' | 'N' | 'B'>();
58
+
59
+ // Table primary keys
60
+ attributes.set(hashKey, 'S');
61
+ attributes.set(rangeKey, 'S');
62
+
63
+ // GSI keys
64
+ for (const gsi of gsis) {
65
+ attributes.set(gsi.partitionKey, 'S');
66
+ if (gsi.sortKey) {
67
+ attributes.set(gsi.sortKey, 'S');
68
+ }
69
+ }
70
+
71
+ return Array.from(attributes.entries()).map(
72
+ ([AttributeName, AttributeType]) => ({
73
+ AttributeName,
74
+ AttributeType,
75
+ }),
76
+ );
77
+ }
78
+
79
+ private async ensureSingleTable(table: TableConfig) {
80
+ const { tableName, hashKey, rangeKey, gsis } = table;
81
+
82
+ try {
83
+ const describe = await this.client.send(
84
+ new DescribeTableCommand({ TableName: tableName }),
85
+ );
86
+
87
+ console.log(`Table "${tableName}" already exists`);
88
+
89
+ const existingGsis =
90
+ describe.Table?.GlobalSecondaryIndexes?.map((g) => g.IndexName) || [];
91
+
92
+ const gsisToAdd = gsis?.filter(
93
+ (g) => !existingGsis.includes(g.indexName),
94
+ );
95
+
96
+ if (gsisToAdd?.length) {
97
+ console.log(
98
+ `Adding GSIs: ${gsisToAdd.map((g) => g.indexName).join(', ')}`,
99
+ );
100
+ await this.client.send(
101
+ new UpdateTableCommand({
102
+ TableName: tableName,
103
+ AttributeDefinitions: this.buildAttributeDefinitions(
104
+ hashKey,
105
+ rangeKey,
106
+ gsisToAdd,
107
+ ),
108
+ GlobalSecondaryIndexUpdates: gsisToAdd.map((g) => ({
109
+ Create: {
110
+ IndexName: g.indexName,
111
+ KeySchema: [
112
+ { AttributeName: g.partitionKey, KeyType: KeyType.HASH },
113
+ { AttributeName: g.sortKey, KeyType: KeyType.RANGE },
114
+ ],
115
+ Projection: { ProjectionType: 'ALL' },
116
+ },
117
+ })),
118
+ }),
119
+ );
120
+ }
121
+
122
+ return { table: tableName, status: 'exists' };
123
+ } catch (err: any) {
124
+ if (err.name === 'ResourceNotFoundException') {
125
+ console.log(`Creating table "${tableName}"...`);
126
+
127
+ await this.client.send(
128
+ new CreateTableCommand({
129
+ TableName: tableName,
130
+ BillingMode: 'PAY_PER_REQUEST',
131
+ KeySchema: [
132
+ { AttributeName: hashKey, KeyType: KeyType.HASH },
133
+ { AttributeName: rangeKey, KeyType: KeyType.RANGE },
134
+ ],
135
+ AttributeDefinitions: this.buildAttributeDefinitions(
136
+ hashKey,
137
+ rangeKey,
138
+ gsis,
139
+ ),
140
+
141
+ GlobalSecondaryIndexes: gsis?.map((gsi: any) => ({
142
+ IndexName: gsi.indexName,
143
+ KeySchema: [
144
+ { AttributeName: gsi.partitionKey, KeyType: KeyType.HASH },
145
+ // { AttributeName: gsi.sortKey, KeyType: "RANGE" },
146
+ ...(gsi.sortKey
147
+ ? [{ AttributeName: gsi.sortKey, KeyType: KeyType.RANGE }]
148
+ : []),
149
+ ],
150
+ Projection: { ProjectionType: 'ALL' },
151
+ })),
152
+ }),
153
+ );
154
+
155
+ // Wait for the table to be active
156
+ await waitUntilTableExists(
157
+ { client: this.client, maxWaitTime: 30 },
158
+ { TableName: tableName },
159
+ );
160
+
161
+ console.log(`Table "${tableName}" created successfully`);
162
+ return { table: tableName, status: 'created' };
163
+ } else {
164
+ console.error(`Error checking table "${tableName}":`, err);
165
+ }
166
+ }
167
+ }
168
+ }
@@ -0,0 +1,13 @@
1
+ export type GSIConfig = {
2
+ indexName: string;
3
+ partitionKey: string;
4
+ sortKey?: string;
5
+ projectionType?: 'ALL' | 'KEYS_ONLY' | 'INCLUDE';
6
+ };
7
+
8
+ export type TableConfig = {
9
+ tableName: string;
10
+ hashKey: string;
11
+ rangeKey: string;
12
+ gsis?: GSIConfig[];
13
+ };
@@ -0,0 +1,5 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+
3
+ export const SKIP_AUDIT_LOG = 'skipAuditLog';
4
+
5
+ export const SkipAuditLog = () => SetMetadata(SKIP_AUDIT_LOG, true);
@@ -0,0 +1,74 @@
1
+ import { AuditService } from '@core/audit/audit.service';
2
+ import {
3
+ CallHandler,
4
+ ExecutionContext,
5
+ Injectable,
6
+ NestInterceptor,
7
+ } from '@nestjs/common';
8
+ import { catchError, Observable, tap, throwError } from 'rxjs';
9
+ import { SKIP_AUDIT_LOG } from './audit.decorator';
10
+ import { Reflector } from '@nestjs/core';
11
+
12
+ @Injectable()
13
+ export class AuditInterceptor implements NestInterceptor {
14
+ constructor(
15
+ private reflector: Reflector,
16
+ private readonly auditService: AuditService,
17
+ ) {}
18
+
19
+ intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
20
+ const shouldSkip = this.reflector.getAllAndOverride<boolean>(
21
+ SKIP_AUDIT_LOG,
22
+ [context.getHandler(), context.getClass()],
23
+ );
24
+
25
+ if (shouldSkip) {
26
+ return next.handle(); // 🚀 skip logging
27
+ }
28
+ const req = context.switchToHttp().getRequest();
29
+ const res = context.switchToHttp().getResponse();
30
+ const start = Date.now();
31
+
32
+ const log: any = {
33
+ userId: req.user?.id || 'anonymous',
34
+ action: this.getAction(req),
35
+ service: this.getServiceName(req),
36
+ method: req.method,
37
+ endpoint: req.originalUrl,
38
+ ipAddress: req.ip,
39
+ duration: Date.now() - start,
40
+ };
41
+ return next.handle().pipe(
42
+ tap(() => {
43
+ log.statusCode = res.statusCode;
44
+ log.duration = Date.now() - start;
45
+
46
+ // 🔥 fire-and-forget (don’t block response)
47
+ this.auditService.log(log).catch(() => {});
48
+ }),
49
+
50
+ catchError((error) => {
51
+ log.statusCode = error?.status || 500;
52
+ log.duration = Date.now() - start;
53
+
54
+ // 🔥 fire-and-forget
55
+ this.auditService.log(log).catch(() => {});
56
+
57
+ return throwError(() => error); // ✅ preserves error
58
+ }),
59
+ );
60
+ }
61
+
62
+ private getAction(req: any): string {
63
+ if (req.originalUrl.includes('login')) return 'LOGIN';
64
+ if (req.method === 'GET') return 'VIEW';
65
+ if (req.method === 'POST') return 'CREATE';
66
+ if (req.method === 'PUT') return 'UPDATE';
67
+ if (req.method === 'DELETE') return 'DELETE';
68
+ return 'UNKNOWN';
69
+ }
70
+
71
+ private getServiceName(req: any): string {
72
+ return req.originalUrl.split('/')[1] || 'general';
73
+ }
74
+ }
@@ -0,0 +1,43 @@
1
+ import { BadRequestException, HttpStatus } from '@nestjs/common';
2
+ import {
3
+ ExceptionFilter,
4
+ Catch,
5
+ ArgumentsHost,
6
+ HttpException,
7
+ } from '@nestjs/common';
8
+ import { Request, Response } from 'express';
9
+
10
+ @Catch()
11
+ export class HttpExceptionFilter implements ExceptionFilter {
12
+ catch(exception: any, host: ArgumentsHost) {
13
+ const ctx = host.switchToHttp();
14
+ const response = ctx.getResponse<Response>();
15
+ const request = ctx.getRequest<Request>();
16
+
17
+ let status = 500;
18
+ let message = 'Internal server error';
19
+
20
+ // If NestJS HttpException
21
+ if (exception instanceof HttpException) {
22
+ status = exception.getStatus();
23
+ const res: any = exception.getResponse();
24
+
25
+ if (res?.message) {
26
+ message = Array.isArray(res.message)
27
+ ? res.message.join(', ')
28
+ : res.message;
29
+ } else if (typeof res === 'string') {
30
+ message = res;
31
+ }
32
+ } else {
33
+ // Generic error (AWS, DynamoDB, etc)
34
+ message = exception?.message || message;
35
+ }
36
+ // console.error('🔥 ERROR CAUGHT:', message);
37
+
38
+ return response.status(status).json({
39
+ success: false,
40
+ message,
41
+ });
42
+ }
43
+ }
@@ -0,0 +1,5 @@
1
+ import { SetMetadata } from '@nestjs/common';
2
+
3
+ export const SUCCESS_MESSAGE_KEY = 'success_message';
4
+ export const SuccessMessage = (message: string) =>
5
+ SetMetadata(SUCCESS_MESSAGE_KEY, message);
@@ -0,0 +1,35 @@
1
+ import {
2
+ CallHandler,
3
+ ExecutionContext,
4
+ Injectable,
5
+ NestInterceptor,
6
+ } from '@nestjs/common';
7
+ import { Reflector } from '@nestjs/core';
8
+ import { map } from 'rxjs';
9
+ import { SUCCESS_MESSAGE_KEY } from './success-message.decorator';
10
+
11
+ export interface ApiResponse<T = any> {
12
+ success: boolean;
13
+ message: string;
14
+ data?: T;
15
+ }
16
+
17
+ @Injectable()
18
+ export class SuccessResponseInterceptor<T>
19
+ implements NestInterceptor<T, ApiResponse<T>>
20
+ {
21
+ intercept(context: ExecutionContext, next: CallHandler) {
22
+ const handler = context.getHandler();
23
+ const reflector = new Reflector();
24
+
25
+ const customMessage = reflector.get<string>(SUCCESS_MESSAGE_KEY, handler);
26
+
27
+ return next.handle().pipe(
28
+ map((data) => ({
29
+ success: true,
30
+ message: customMessage ?? 'Request successful',
31
+ data,
32
+ })),
33
+ );
34
+ }
35
+ }
@@ -0,0 +1,74 @@
1
+ import { Body, Controller, Post } from '@nestjs/common';
2
+ import { SummarizeDto } from './summarize.dto';
3
+ import {
4
+ BedrockRuntimeClient,
5
+ InvokeModelCommand,
6
+ } from '@aws-sdk/client-bedrock-runtime';
7
+ import { EnvironmentService } from '@core/constants/environment.service';
8
+
9
+ import * as dotenv from 'dotenv';
10
+ dotenv.config();
11
+
12
+ @Controller('summarize')
13
+ export class SummarizeController {
14
+ private client: BedrockRuntimeClient;
15
+ private bedrockModel!: any;
16
+ constructor(private readonly environment: EnvironmentService) {
17
+ console.log('Loading summarize');
18
+ this.client = new BedrockRuntimeClient({
19
+ region: this.environment?.region() || process.env.REGION,
20
+ });
21
+ this.bedrockModel =
22
+ this.environment?.BEDROCK_MODEL() || process.env.BEDROCK_MODEL;
23
+ console.log(
24
+ 'bedrockModel',
25
+ this.bedrockModel,
26
+ this.environment?.BEDROCK_MODEL(),
27
+ process.env.BEDROCK_MODEL,
28
+ );
29
+ }
30
+
31
+ @Post('summarize')
32
+ async summarize(@Body() dto: SummarizeDto) {
33
+ try {
34
+ const prompt = `
35
+ Summarize the following text in a short paragraph:
36
+
37
+ ${dto.text}
38
+ `;
39
+
40
+ const command = new InvokeModelCommand({
41
+ modelId: this.bedrockModel || 'anthropic.claude-3-haiku-20240307-v1:0',
42
+ contentType: 'application/json',
43
+ accept: 'application/json',
44
+ body: JSON.stringify({
45
+ anthropic_version: 'bedrock-2023-05-31',
46
+ max_tokens: 300,
47
+ messages: [
48
+ {
49
+ role: 'user',
50
+ content: [
51
+ {
52
+ type: 'text',
53
+ text: prompt,
54
+ },
55
+ ],
56
+ },
57
+ ],
58
+ }),
59
+ });
60
+
61
+ const response = await this.client.send(command);
62
+
63
+ const decoded = new TextDecoder().decode(response.body);
64
+
65
+ const parsed = JSON.parse(decoded);
66
+
67
+ return parsed.content[0].text;
68
+ } catch (err: any) {
69
+ console.error(err.message);
70
+ const msg = err?.message || JSON.stringify(err);
71
+ throw err;
72
+ }
73
+ }
74
+ }
@@ -0,0 +1,13 @@
1
+ import { ApiProperty } from '@nestjs/swagger';
2
+ import { IsString, IsNotEmpty } from 'class-validator';
3
+
4
+ export class SummarizeDto {
5
+ @ApiProperty({
6
+ example:
7
+ 'Serverless architecture allows developers to build and run applications without managing infrastructure. AWS Lambda automatically scales...',
8
+ description: 'Long text to summarize',
9
+ })
10
+ @IsNotEmpty()
11
+ @IsString()
12
+ text!: string;
13
+ }
@@ -0,0 +1,9 @@
1
+ import { Module } from '@nestjs/common';
2
+ import { SummarizeController } from './summarize.controller';
3
+ import { SummarizeService } from './summarize.service';
4
+
5
+ @Module({
6
+ controllers: [SummarizeController],
7
+ // providers: [SummarizeService],
8
+ })
9
+ export class SummarizeModule {}