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.
- package/README.md +107 -0
- package/dist/index.js +8 -0
- package/dist/run.js +112 -0
- package/dist/utils/addService.js +204 -0
- package/dist/utils/appName.js +75 -0
- package/dist/utils/deployNow.js +18 -0
- package/dist/utils/generateFiles.js +326 -0
- package/dist/utils/generateSchemas.js +58 -0
- package/dist/utils/listServices.js +24 -0
- package/dist/utils/replaceServiceNames.js +42 -0
- package/dist/utils/telemetryAddon.js +91 -0
- package/package.json +31 -0
- package/templates/core/audit/audit.controller.ts +125 -0
- package/templates/core/audit/audit.module.ts +10 -0
- package/templates/core/audit/audit.schema.ts +14 -0
- package/templates/core/audit/audit.service.ts +47 -0
- package/templates/core/auth/auth.controller.ts +207 -0
- package/templates/core/auth/auth.dto.ts +50 -0
- package/templates/core/auth/auth.module.ts +10 -0
- package/templates/core/auth/auth.service.ts +178 -0
- package/templates/core/auth/utils.ts +160 -0
- package/templates/core/constants/environment.db.ts +27 -0
- package/templates/core/constants/environment.module.ts +9 -0
- package/templates/core/constants/environment.service.ts +69 -0
- package/templates/core/core.controller.ts +35 -0
- package/templates/core/core.module.ts +22 -0
- package/templates/core/database/database.module.ts +20 -0
- package/templates/core/database/database.provider.ts +32 -0
- package/templates/core/database/database.service.ts +168 -0
- package/templates/core/database/database.types.ts +13 -0
- package/templates/core/filters/audit.decorator.ts +5 -0
- package/templates/core/filters/audit.interceptor.ts +74 -0
- package/templates/core/filters/http-exception.filter.ts +43 -0
- package/templates/core/filters/success-message.decorator.ts +5 -0
- package/templates/core/filters/success-response.interceptor.ts +35 -0
- package/templates/core/summarize/summarize.controller.ts +74 -0
- package/templates/core/summarize/summarize.dto.ts +13 -0
- package/templates/core/summarize/summarize.module.ts +9 -0
- package/templates/core/summarize/summarize.service.ts +54 -0
- package/templates/nest-cli.json +8 -0
- package/templates/package.json +52 -0
- package/templates/service/src/__name__.controller.ts +15 -0
- package/templates/service/src/__name__.module.ts +11 -0
- package/templates/service/src/__name__.schema.ts +15 -0
- package/templates/service/src/__name__.service.ts +12 -0
- package/templates/service/src/lambda.ts +60 -0
- package/templates/tsconfig.json +28 -0
- package/templates/ui/README.md +36 -0
- package/templates/ui/eslint.config.mjs +18 -0
- package/templates/ui/next.config.ts +8 -0
- package/templates/ui/package.json +33 -0
- package/templates/ui/postcss.config.mjs +7 -0
- package/templates/ui/public/file.svg +1 -0
- package/templates/ui/public/globe.svg +1 -0
- package/templates/ui/public/next.svg +1 -0
- package/templates/ui/public/vercel.svg +1 -0
- package/templates/ui/public/window.svg +1 -0
- package/templates/ui/src/app/LandingPage.tsx +98 -0
- package/templates/ui/src/app/ai/summarize/page.tsx +115 -0
- package/templates/ui/src/app/context/AuthContext.tsx +48 -0
- package/templates/ui/src/app/favicon.ico +0 -0
- package/templates/ui/src/app/globals.css +26 -0
- package/templates/ui/src/app/layout.tsx +37 -0
- package/templates/ui/src/app/page.tsx +7 -0
- package/templates/ui/src/app/services/page.tsx +99 -0
- package/templates/ui/src/components/Auth.css +252 -0
- package/templates/ui/src/components/Auth.tsx +455 -0
- package/templates/ui/src/components/Error.tsx +32 -0
- package/templates/ui/src/components/FormInput.tsx +77 -0
- package/templates/ui/src/components/Loading.tsx +10 -0
- package/templates/ui/src/components/Login.tsx +171 -0
- package/templates/ui/src/components/Popup.css +90 -0
- package/templates/ui/src/components/Signup.tsx +155 -0
- package/templates/ui/src/utils/axiosInstance.ts +37 -0
- package/templates/ui/src/utils/axiosRawInstance.ts +33 -0
- package/templates/ui/src/utils/util.constant.ts +0 -0
- package/templates/ui/src/utils/util.function.ts +165 -0
- package/templates/ui/src/utils/util.type.ts +64 -0
- package/templates/ui/src/utils/variables.ts +6 -0
- package/templates/ui/tailwind.config.js +8 -0
- package/templates/ui/tsconfig.json +43 -0
|
@@ -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,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,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 {}
|