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,326 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.generateNextJSEnv = exports.generateRootEnv = exports.generateServerlessFiles = exports.generateInfraCognitoFile = exports.generateInfraBucketFile = exports.generateInfraApiFile = exports.generateNebulaJson = exports.generateRootLambda = void 0;
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const addService_1 = require("./addService");
|
|
7
|
+
const replaceServiceNames_1 = require("./replaceServiceNames");
|
|
8
|
+
const telemetryAddon_1 = require("./telemetryAddon");
|
|
9
|
+
const region = (0, telemetryAddon_1.getAwsRegion)();
|
|
10
|
+
const generateRootLambda = (apiName = 'API', services = [], directory) => {
|
|
11
|
+
console.log(services, apiName);
|
|
12
|
+
apiName = apiName.charAt(0).toUpperCase() + apiName.slice(1);
|
|
13
|
+
const fileContent = `export const handler = async () => {
|
|
14
|
+
|
|
15
|
+
const services = ${JSON.stringify(services)};
|
|
16
|
+
|
|
17
|
+
return {
|
|
18
|
+
statusCode: 200,
|
|
19
|
+
headers: {
|
|
20
|
+
"Content-Type": "application/json",
|
|
21
|
+
'Access-Control-Allow-Origin': '*', // match your cors config
|
|
22
|
+
'Access-Control-Allow-Headers': 'Content-Type,Authorization',
|
|
23
|
+
'Access-Control-Allow-Methods': 'OPTIONS,POST,GET,PUT,DELETE',
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
name: "${apiName} API",
|
|
27
|
+
services,
|
|
28
|
+
message: "Welcome to the ${apiName} API"
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
`;
|
|
33
|
+
const outputPath = path.join(directory, 'infra/api/src/root.ts');
|
|
34
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
35
|
+
fs.writeFileSync(outputPath, fileContent);
|
|
36
|
+
console.log('✅ Root API lambda generated from nebula.json');
|
|
37
|
+
};
|
|
38
|
+
exports.generateRootLambda = generateRootLambda;
|
|
39
|
+
const generateNebulaJson = (appName, selectedServices, OUTPUT) => {
|
|
40
|
+
const nebulaConfig = {
|
|
41
|
+
name: appName,
|
|
42
|
+
runtime: 'nestjs',
|
|
43
|
+
architecture: 'multi-lambda',
|
|
44
|
+
deployment: 'serverless-compose',
|
|
45
|
+
services: selectedServices,
|
|
46
|
+
region,
|
|
47
|
+
};
|
|
48
|
+
fs.writeJsonSync(path.join(OUTPUT, 'nebula.json'), nebulaConfig, {
|
|
49
|
+
spaces: 2,
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
exports.generateNebulaJson = generateNebulaJson;
|
|
53
|
+
const generateInfraApiFile = async (appName, OUTPUT) => {
|
|
54
|
+
const infraDir = path.join(OUTPUT, 'infra/api');
|
|
55
|
+
await fs.ensureDir(infraDir);
|
|
56
|
+
const infraServerlessTemplate = `service: ${appName}-shared-api
|
|
57
|
+
|
|
58
|
+
provider:
|
|
59
|
+
name: aws
|
|
60
|
+
runtime: nodejs20.x
|
|
61
|
+
stage: \${opt:stage, 'dev'}
|
|
62
|
+
region: ${region}
|
|
63
|
+
|
|
64
|
+
functions:
|
|
65
|
+
root:
|
|
66
|
+
handler: src/root.handler
|
|
67
|
+
events:
|
|
68
|
+
- http:
|
|
69
|
+
path: /
|
|
70
|
+
method: get
|
|
71
|
+
cors: true
|
|
72
|
+
|
|
73
|
+
resources:
|
|
74
|
+
Resources:
|
|
75
|
+
ApiGatewayRestApi:
|
|
76
|
+
Type: AWS::ApiGateway::RestApi
|
|
77
|
+
Properties:
|
|
78
|
+
Name: ${appName}-shared-api
|
|
79
|
+
|
|
80
|
+
Outputs:
|
|
81
|
+
ApiGatewayRestApiId:
|
|
82
|
+
Value: !Ref ApiGatewayRestApi
|
|
83
|
+
|
|
84
|
+
ApiGatewayRootResourceId:
|
|
85
|
+
Value: !GetAtt ApiGatewayRestApi.RootResourceId
|
|
86
|
+
`;
|
|
87
|
+
await fs.writeFile(path.join(infraDir, 'serverless.yml'), infraServerlessTemplate);
|
|
88
|
+
};
|
|
89
|
+
exports.generateInfraApiFile = generateInfraApiFile;
|
|
90
|
+
const generateInfraBucketFile = async (appName, OUTPUT) => {
|
|
91
|
+
const bucketDir = path.join(OUTPUT, 'infra/buckets');
|
|
92
|
+
await fs.ensureDir(bucketDir);
|
|
93
|
+
const bucketServerlessTemplate = `service: ${appName}-buckets
|
|
94
|
+
|
|
95
|
+
plugins:
|
|
96
|
+
- serverless-s3-sync
|
|
97
|
+
|
|
98
|
+
custom:
|
|
99
|
+
# cloudfrontDistributionId: E2OW0GLR4XXOBS
|
|
100
|
+
# scripts:
|
|
101
|
+
# hooks:
|
|
102
|
+
# after:deploy:deploy: >
|
|
103
|
+
# aws cloudfront create-invalidation
|
|
104
|
+
# --distribution-id \${self:custom.cloudfrontDistributionId}
|
|
105
|
+
# --paths "/*"
|
|
106
|
+
# scripts:
|
|
107
|
+
# hooks:
|
|
108
|
+
# # Run BEFORE upload to S3
|
|
109
|
+
# before:deploy:deploy: node scripts/generate-env.js
|
|
110
|
+
|
|
111
|
+
s3Sync:
|
|
112
|
+
- bucketName: ${appName}-bucket-\${aws:accountId}-\${self:provider.stage}
|
|
113
|
+
localDir: ../../ui/out
|
|
114
|
+
deleteRemoved: true
|
|
115
|
+
|
|
116
|
+
params:
|
|
117
|
+
dev:
|
|
118
|
+
region: ${region}
|
|
119
|
+
disableLogs: true
|
|
120
|
+
# publicCloudfrontSubdomain: "${appName}.nebulalogix.io"
|
|
121
|
+
|
|
122
|
+
provider:
|
|
123
|
+
name: aws
|
|
124
|
+
stage: \${opt:stage, 'dev'}
|
|
125
|
+
region: ${region}
|
|
126
|
+
environment:
|
|
127
|
+
DEPLOYMENT_TIME: \${sls:instanceId}
|
|
128
|
+
|
|
129
|
+
resources:
|
|
130
|
+
Resources:
|
|
131
|
+
S3WebsiteBucket:
|
|
132
|
+
Type: AWS::S3::Bucket
|
|
133
|
+
Properties:
|
|
134
|
+
BucketName: ${appName}-bucket-\${aws:accountId}-\${self:provider.stage}
|
|
135
|
+
WebsiteConfiguration:
|
|
136
|
+
IndexDocument: index.html
|
|
137
|
+
ErrorDocument: index.html
|
|
138
|
+
PublicAccessBlockConfiguration:
|
|
139
|
+
BlockPublicAcls: false
|
|
140
|
+
IgnorePublicAcls: false
|
|
141
|
+
BlockPublicPolicy: false
|
|
142
|
+
RestrictPublicBuckets: false
|
|
143
|
+
|
|
144
|
+
S3BucketPolicy:
|
|
145
|
+
Type: AWS::S3::BucketPolicy
|
|
146
|
+
Properties:
|
|
147
|
+
Bucket: !Ref S3WebsiteBucket
|
|
148
|
+
PolicyDocument:
|
|
149
|
+
Version: "2012-10-17"
|
|
150
|
+
Statement:
|
|
151
|
+
- Effect: Allow
|
|
152
|
+
Principal: "*"
|
|
153
|
+
Action:
|
|
154
|
+
- s3:GetObject
|
|
155
|
+
Resource: !Sub "\${S3WebsiteBucket.Arn}/*"
|
|
156
|
+
|
|
157
|
+
# Allow your AWS account to deploy files
|
|
158
|
+
- Effect: Allow
|
|
159
|
+
Principal:
|
|
160
|
+
AWS: !Sub "arn:aws:iam::\${AWS::AccountId}:root"
|
|
161
|
+
Action:
|
|
162
|
+
- s3:PutObject
|
|
163
|
+
- s3:DeleteObject
|
|
164
|
+
- s3:ListBucket
|
|
165
|
+
Resource:
|
|
166
|
+
- !Sub "\${S3WebsiteBucket.Arn}"
|
|
167
|
+
- !Sub "\${S3WebsiteBucket.Arn}/*"
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
Outputs:
|
|
171
|
+
WebsiteURL:
|
|
172
|
+
Value: !GetAtt S3WebsiteBucket.WebsiteURL
|
|
173
|
+
Description: Public website URL
|
|
174
|
+
`;
|
|
175
|
+
await fs.writeFile(path.join(bucketDir, 'serverless.yml'), bucketServerlessTemplate);
|
|
176
|
+
};
|
|
177
|
+
exports.generateInfraBucketFile = generateInfraBucketFile;
|
|
178
|
+
const generateInfraCognitoFile = async (appName, OUTPUT) => {
|
|
179
|
+
const cognitoDir = path.join(OUTPUT, 'infra/cognito');
|
|
180
|
+
await fs.ensureDir(cognitoDir);
|
|
181
|
+
const cognitoServerlessTemplate = `service: ${appName}-cognito
|
|
182
|
+
|
|
183
|
+
provider:
|
|
184
|
+
name: aws
|
|
185
|
+
runtime: nodejs20.x
|
|
186
|
+
stage: \${opt:stage, 'dev'}
|
|
187
|
+
region: us-east-1
|
|
188
|
+
|
|
189
|
+
resources:
|
|
190
|
+
Resources:
|
|
191
|
+
# Cognito User Pool
|
|
192
|
+
UserPool:
|
|
193
|
+
Type: AWS::Cognito::UserPool
|
|
194
|
+
Properties:
|
|
195
|
+
UserPoolName: ${appName}-userpool
|
|
196
|
+
AutoVerifiedAttributes:
|
|
197
|
+
- email
|
|
198
|
+
UsernameAttributes:
|
|
199
|
+
- email
|
|
200
|
+
Policies:
|
|
201
|
+
PasswordPolicy:
|
|
202
|
+
MinimumLength: 8
|
|
203
|
+
RequireLowercase: true
|
|
204
|
+
RequireUppercase: true
|
|
205
|
+
RequireNumbers: true
|
|
206
|
+
|
|
207
|
+
# Cognito User Pool Client
|
|
208
|
+
UserPoolClient:
|
|
209
|
+
Type: AWS::Cognito::UserPoolClient
|
|
210
|
+
Properties:
|
|
211
|
+
ClientName: ${appName}-userpoolclient
|
|
212
|
+
UserPoolId: !Ref UserPool
|
|
213
|
+
ExplicitAuthFlows:
|
|
214
|
+
- ALLOW_USER_PASSWORD_AUTH
|
|
215
|
+
- ALLOW_REFRESH_TOKEN_AUTH
|
|
216
|
+
GenerateSecret: false
|
|
217
|
+
|
|
218
|
+
# Cognito User Pool Domain
|
|
219
|
+
UserPoolDomain:
|
|
220
|
+
Type: AWS::Cognito::UserPoolDomain
|
|
221
|
+
Properties:
|
|
222
|
+
Domain: ${appName}-userpooldomain
|
|
223
|
+
UserPoolId: !Ref UserPool
|
|
224
|
+
|
|
225
|
+
# 4️⃣ Lambda Role with Cognito permissions
|
|
226
|
+
LambdaCognitoRole:
|
|
227
|
+
Type: AWS::IAM::Role
|
|
228
|
+
Properties:
|
|
229
|
+
RoleName: ${appName}-lambda-cognito-role-\${self:provider.stage}
|
|
230
|
+
AssumeRolePolicyDocument:
|
|
231
|
+
Version: '2012-10-17'
|
|
232
|
+
Statement:
|
|
233
|
+
- Effect: Allow
|
|
234
|
+
Principal:
|
|
235
|
+
Service:
|
|
236
|
+
- lambda.amazonaws.com
|
|
237
|
+
Action:
|
|
238
|
+
- sts:AssumeRole
|
|
239
|
+
Policies:
|
|
240
|
+
- PolicyName: CognitoAccessPolicy
|
|
241
|
+
PolicyDocument:
|
|
242
|
+
Version: '2012-10-17'
|
|
243
|
+
Statement:
|
|
244
|
+
- Effect: Allow
|
|
245
|
+
Action:
|
|
246
|
+
- cognito-idp:AdminCreateUser
|
|
247
|
+
- cognito-idp:AdminDeleteUser
|
|
248
|
+
- cognito-idp:AdminGetUser
|
|
249
|
+
- cognito-idp:AdminUpdateUserAttributes
|
|
250
|
+
- cognito-idp:ListUsers
|
|
251
|
+
- cognito-idp:AdminInitiateAuth
|
|
252
|
+
Resource: !GetAtt UserPool.Arn
|
|
253
|
+
|
|
254
|
+
Outputs:
|
|
255
|
+
# Export the User Pool ID
|
|
256
|
+
CognitoUserPoolId:
|
|
257
|
+
Value: !Ref UserPool
|
|
258
|
+
|
|
259
|
+
# Export the User Pool Client ID
|
|
260
|
+
CognitoClientId:
|
|
261
|
+
Value: !Ref UserPoolClient
|
|
262
|
+
|
|
263
|
+
# Export the User Pool Domain
|
|
264
|
+
CognitoUserPoolDomain:
|
|
265
|
+
Value: !Ref UserPoolDomain
|
|
266
|
+
`;
|
|
267
|
+
await fs.writeFile(path.join(cognitoDir, 'serverless.yml'), cognitoServerlessTemplate);
|
|
268
|
+
};
|
|
269
|
+
exports.generateInfraCognitoFile = generateInfraCognitoFile;
|
|
270
|
+
const generateServerlessFiles = async (selectedServices, ROOT, OUTPUT, appNamePlaceHolder) => {
|
|
271
|
+
for (const service of selectedServices) {
|
|
272
|
+
const serviceOutput = path.join(OUTPUT, 'services', service);
|
|
273
|
+
await fs.ensureDir(serviceOutput);
|
|
274
|
+
// copy base nest template
|
|
275
|
+
fs.copySync(path.join(ROOT, 'templates/service'), serviceOutput);
|
|
276
|
+
// update service names
|
|
277
|
+
await (0, replaceServiceNames_1.replaceServiceNames)(serviceOutput, service, appNamePlaceHolder);
|
|
278
|
+
console.log(`✅ Service ${service} created\n`);
|
|
279
|
+
// Generate serverless.yml per service
|
|
280
|
+
await fs.writeFile(path.join(serviceOutput, 'serverless.yml'), (0, addService_1.serverlessTemplate)(service, appNamePlaceHolder));
|
|
281
|
+
// Generate root serverless-compose.yml
|
|
282
|
+
let composeContent = `
|
|
283
|
+
services:
|
|
284
|
+
api:
|
|
285
|
+
path: infra/api
|
|
286
|
+
|
|
287
|
+
cognito:
|
|
288
|
+
path: infra/cognito
|
|
289
|
+
|
|
290
|
+
buckets:
|
|
291
|
+
path: infra/buckets
|
|
292
|
+
`;
|
|
293
|
+
for (const service of selectedServices) {
|
|
294
|
+
composeContent += `
|
|
295
|
+
${service}:
|
|
296
|
+
path: services/${service}
|
|
297
|
+
params:
|
|
298
|
+
ApiGatewayRestApiId: \${api.ApiGatewayRestApiId}
|
|
299
|
+
ApiGatewayRootResourceId: \${api.ApiGatewayRootResourceId}
|
|
300
|
+
CognitoClientId: \${cognito.CognitoClientId}
|
|
301
|
+
CognitoUserPoolId: \${cognito.CognitoUserPoolId}
|
|
302
|
+
`;
|
|
303
|
+
}
|
|
304
|
+
await fs.writeFile(path.join(OUTPUT, 'serverless-compose.yml'), composeContent);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
exports.generateServerlessFiles = generateServerlessFiles;
|
|
308
|
+
const generateRootEnv = (OUTPUT) => {
|
|
309
|
+
const envRootContent = `COGNITO_USER_POOL_ID=
|
|
310
|
+
COGNITO_CLIENT_ID=
|
|
311
|
+
`;
|
|
312
|
+
fs.writeFileSync(path.join(OUTPUT, '.env'), envRootContent.trim());
|
|
313
|
+
console.log('✅ Root env generated:');
|
|
314
|
+
console.log(envRootContent);
|
|
315
|
+
};
|
|
316
|
+
exports.generateRootEnv = generateRootEnv;
|
|
317
|
+
const generateNextJSEnv = (appName, uiDir, selectedServices) => {
|
|
318
|
+
const envContent = `NEXT_PUBLIC_APP_NAME=${appName.charAt(0).toUpperCase() + appName.slice(1)}
|
|
319
|
+
NEXT_PUBLIC_API_URL=
|
|
320
|
+
NEXT_PUBLIC_SERVICES=${selectedServices.join(',')}
|
|
321
|
+
`;
|
|
322
|
+
fs.writeFileSync(path.join(uiDir, '.env.local'), envContent.trim());
|
|
323
|
+
console.log('✅ Next.js env generated:');
|
|
324
|
+
console.log(envContent);
|
|
325
|
+
};
|
|
326
|
+
exports.generateNextJSEnv = generateNextJSEnv;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.generateSchemas = void 0;
|
|
37
|
+
const fs = __importStar(require("fs-extra"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const generateSchemas = (OUTPUT) => {
|
|
40
|
+
const nebula = JSON.parse(fs.readFileSync(path.join(OUTPUT, 'nebula.json'), 'utf-8'));
|
|
41
|
+
let imports = '';
|
|
42
|
+
let exportsArr = [];
|
|
43
|
+
for (const service of nebula.services) {
|
|
44
|
+
const importName = `${service.toUpperCase()}_TABLE_SCHEMA`;
|
|
45
|
+
imports += `import { ${importName} } from "@services/${service}/src/${service}.schema";\n`;
|
|
46
|
+
exportsArr.push(importName);
|
|
47
|
+
}
|
|
48
|
+
const content = `
|
|
49
|
+
${imports}
|
|
50
|
+
|
|
51
|
+
export const GENERATED_SCHEMAS = [
|
|
52
|
+
${exportsArr.join(',\n ')}
|
|
53
|
+
];
|
|
54
|
+
`;
|
|
55
|
+
fs.writeFileSync(path.join(OUTPUT, 'core/database/generated-schemas.ts'), content);
|
|
56
|
+
console.log('✅ Schemas generated');
|
|
57
|
+
};
|
|
58
|
+
exports.generateSchemas = generateSchemas;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.listServices = listServices;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
function listServices() {
|
|
10
|
+
const ROOT = process.cwd();
|
|
11
|
+
const nebulaPath = path_1.default.join(ROOT, 'nebula.json');
|
|
12
|
+
if (!fs_extra_1.default.existsSync(nebulaPath)) {
|
|
13
|
+
throw new Error('Not a Nebula project');
|
|
14
|
+
}
|
|
15
|
+
const config = fs_extra_1.default.readJsonSync(nebulaPath);
|
|
16
|
+
if (config.services.length) {
|
|
17
|
+
console.log('Services:');
|
|
18
|
+
config.services.forEach((s) => console.log(`- ${s}`));
|
|
19
|
+
console.log('\n');
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.log('No services present');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.replaceServiceNames = replaceServiceNames;
|
|
7
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
async function replaceServiceNames(dir, service, appName) {
|
|
10
|
+
const name = service.toLowerCase();
|
|
11
|
+
const NAME = service.toUpperCase();
|
|
12
|
+
const Name = name.charAt(0).toUpperCase() + name.slice(1);
|
|
13
|
+
const files = await fs_extra_1.default.readdir(dir);
|
|
14
|
+
for (const file of files) {
|
|
15
|
+
const oldPath = path_1.default.join(dir, file);
|
|
16
|
+
const stat = await fs_extra_1.default.stat(oldPath);
|
|
17
|
+
if (stat.isDirectory()) {
|
|
18
|
+
await replaceServiceNames(oldPath, service, appName);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
// ✅ rename file if contains placeholder
|
|
22
|
+
const newFile = file
|
|
23
|
+
.replace(/__name__/g, name)
|
|
24
|
+
.replace(/__Name__/g, Name)
|
|
25
|
+
.replace(/__NAME__/g, NAME);
|
|
26
|
+
const newPath = path_1.default.join(dir, newFile);
|
|
27
|
+
if (newFile !== file) {
|
|
28
|
+
await fs_extra_1.default.rename(oldPath, newPath);
|
|
29
|
+
}
|
|
30
|
+
// ✅ replace content
|
|
31
|
+
let content = await fs_extra_1.default.readFile(newPath, 'utf8');
|
|
32
|
+
if (appName) {
|
|
33
|
+
content = content.replace(/__appName__/g, appName);
|
|
34
|
+
}
|
|
35
|
+
content = content
|
|
36
|
+
.replace(/__name__/g, name)
|
|
37
|
+
.replace(/__Name__/g, Name)
|
|
38
|
+
.replace(/__NAME__/g, NAME);
|
|
39
|
+
await fs_extra_1.default.writeFile(newPath, content);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getAnonymousId = getAnonymousId;
|
|
7
|
+
exports.sendTelemetry = sendTelemetry;
|
|
8
|
+
exports.promptTelemetry = promptTelemetry;
|
|
9
|
+
exports.getAwsRegion = getAwsRegion;
|
|
10
|
+
const https_1 = __importDefault(require("https"));
|
|
11
|
+
const fs_extra_1 = __importDefault(require("fs-extra"));
|
|
12
|
+
const os_1 = __importDefault(require("os"));
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
const inquirer_1 = __importDefault(require("inquirer"));
|
|
15
|
+
const package_json_1 = __importDefault(require("../../package.json"));
|
|
16
|
+
const CONFIG_PATH = path_1.default.join(os_1.default.homedir(), '.nebula', 'config.json');
|
|
17
|
+
async function getAnonymousId() {
|
|
18
|
+
if (await fs_extra_1.default.pathExists(CONFIG_PATH)) {
|
|
19
|
+
return (await fs_extra_1.default.readJson(CONFIG_PATH)).anonymousId;
|
|
20
|
+
}
|
|
21
|
+
const anonymousId = crypto.randomUUID();
|
|
22
|
+
await fs_extra_1.default.ensureDir(path_1.default.dirname(CONFIG_PATH));
|
|
23
|
+
await fs_extra_1.default.writeJson(CONFIG_PATH, { anonymousId });
|
|
24
|
+
console.log('✅ Created anonymous ID for telemetry:', anonymousId);
|
|
25
|
+
return anonymousId;
|
|
26
|
+
}
|
|
27
|
+
function sendTelemetry(payload) {
|
|
28
|
+
const data = JSON.stringify(payload, null, 2);
|
|
29
|
+
console.log(`✅ Sending telemetry: ${data}`);
|
|
30
|
+
const req = https_1.default.request('https://telemetry.yourdomain.com/events', {
|
|
31
|
+
method: 'POST',
|
|
32
|
+
headers: {
|
|
33
|
+
'Content-Type': 'application/json',
|
|
34
|
+
'Content-Length': data.length,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
req.on('error', () => { }); // NEVER crash CLI
|
|
38
|
+
req.write(data);
|
|
39
|
+
req.end();
|
|
40
|
+
}
|
|
41
|
+
async function promptTelemetry(appName, selectedServices) {
|
|
42
|
+
const { collectTelemetry } = await inquirer_1.default.prompt([
|
|
43
|
+
{
|
|
44
|
+
type: 'confirm',
|
|
45
|
+
name: 'collectTelemetry',
|
|
46
|
+
message: 'Would you like to share anonymous usage data to improve NebulaLogix StarterKit?',
|
|
47
|
+
default: false,
|
|
48
|
+
},
|
|
49
|
+
]);
|
|
50
|
+
const anonymousId = await getAnonymousId();
|
|
51
|
+
if (!collectTelemetry) {
|
|
52
|
+
sendTelemetry({
|
|
53
|
+
event: 'telemetry_opt_out',
|
|
54
|
+
timestamp: new Date().toISOString(),
|
|
55
|
+
anonymousId,
|
|
56
|
+
});
|
|
57
|
+
console.log('📊 Telemetry disabled');
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
const systemPayload = {
|
|
61
|
+
cliVersion: package_json_1.default.version,
|
|
62
|
+
projectName: appName,
|
|
63
|
+
selectedServices,
|
|
64
|
+
anonymousId,
|
|
65
|
+
environment: {
|
|
66
|
+
os: os_1.default.platform(),
|
|
67
|
+
arch: os_1.default.arch(),
|
|
68
|
+
node: process.version,
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
sendTelemetry({
|
|
72
|
+
event: 'telemetry_opt_in',
|
|
73
|
+
timestamp: new Date().toISOString(),
|
|
74
|
+
...systemPayload,
|
|
75
|
+
});
|
|
76
|
+
console.log('📊 Telemetry enabled. Thank you for helping us improve!');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
function getAwsRegion() {
|
|
80
|
+
try {
|
|
81
|
+
const configPath = path_1.default.join(os_1.default.homedir(), '.aws', 'config');
|
|
82
|
+
if (!fs_extra_1.default.existsSync(configPath))
|
|
83
|
+
return null;
|
|
84
|
+
const content = fs_extra_1.default.readFileSync(configPath, 'utf8');
|
|
85
|
+
const match = content.match(/region\s*=\s*(.+)/);
|
|
86
|
+
return match ? match[1].trim() : null;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nebula-starter-kit",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"bin": {
|
|
5
|
+
"nebula": "./dist/index.js"
|
|
6
|
+
},
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "tsc && chmod +x dist/index.js",
|
|
10
|
+
"dev": "ts-node ./src/index.ts",
|
|
11
|
+
"test": "jest",
|
|
12
|
+
"format": "prettier --write \"../**/*.ts\""
|
|
13
|
+
},
|
|
14
|
+
"files": ["dist", "templates"],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"fs-extra": "^11.1.0",
|
|
17
|
+
"inquirer": "^9.0.0",
|
|
18
|
+
"yaml": "^2.8.2"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/fs-extra": "^11.0.4",
|
|
22
|
+
"@types/inquirer": "^9.0.9",
|
|
23
|
+
"@types/jest": "^30.0.0",
|
|
24
|
+
"@types/node": "^25.0.3",
|
|
25
|
+
"jest": "^30.3.0",
|
|
26
|
+
"prettier": "^2.3.2",
|
|
27
|
+
"ts-jest": "^29.4.6",
|
|
28
|
+
"ts-node": "^10.9.2",
|
|
29
|
+
"typescript": "^5.3.3"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { BadRequestException, Controller, Get, Query } from '@nestjs/common';
|
|
2
|
+
import * as dotenv from 'dotenv';
|
|
3
|
+
import { EnvironmentService } from '@core/constants/environment.service';
|
|
4
|
+
import { AuditService } from './audit.service';
|
|
5
|
+
import { createDynamoClient } from '@core/constants/environment.db';
|
|
6
|
+
import { AUDIT_TABLE } from './audit.schema';
|
|
7
|
+
import { QueryCommand, ScanCommand } from '@aws-sdk/lib-dynamodb';
|
|
8
|
+
import { SkipAuditLog } from '@core/filters/audit.decorator';
|
|
9
|
+
dotenv.config();
|
|
10
|
+
|
|
11
|
+
let cached: { db: any } | undefined;
|
|
12
|
+
|
|
13
|
+
@SkipAuditLog()
|
|
14
|
+
@Controller('audit-logs')
|
|
15
|
+
export class AuditController {
|
|
16
|
+
constructor(
|
|
17
|
+
private readonly auditTable = AUDIT_TABLE,
|
|
18
|
+
private readonly auditService: AuditService,
|
|
19
|
+
private readonly config: EnvironmentService,
|
|
20
|
+
) {}
|
|
21
|
+
|
|
22
|
+
public repository(): { db: any } {
|
|
23
|
+
try {
|
|
24
|
+
if (!cached) {
|
|
25
|
+
const db = createDynamoClient(this.config);
|
|
26
|
+
|
|
27
|
+
cached = { db };
|
|
28
|
+
}
|
|
29
|
+
return cached!;
|
|
30
|
+
} catch (error) {
|
|
31
|
+
throw error;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async getAllAudits({ limit: parsedLimit, nextToken }: any): Promise<any> {
|
|
36
|
+
let userId: string = 'anonymous';
|
|
37
|
+
try {
|
|
38
|
+
const params: any = {
|
|
39
|
+
TableName: this.auditTable,
|
|
40
|
+
KeyConditionExpression: 'userId = :userId',
|
|
41
|
+
ExpressionAttributeValues: {
|
|
42
|
+
':userId': `${userId}`,
|
|
43
|
+
},
|
|
44
|
+
Limit: parsedLimit,
|
|
45
|
+
ScanIndexForward: false,
|
|
46
|
+
};
|
|
47
|
+
if (nextToken) {
|
|
48
|
+
params.ExclusiveStartKey = nextToken;
|
|
49
|
+
}
|
|
50
|
+
const { db } = this.repository();
|
|
51
|
+
return await db.send(new QueryCommand(params));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
throw error;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async getAuditsCount(): Promise<number> {
|
|
58
|
+
try {
|
|
59
|
+
const { db } = this.repository();
|
|
60
|
+
const { Count } = await db.send(
|
|
61
|
+
new ScanCommand({
|
|
62
|
+
TableName: this.auditTable,
|
|
63
|
+
Select: 'COUNT',
|
|
64
|
+
}),
|
|
65
|
+
);
|
|
66
|
+
return Count ?? 0;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
@Get()
|
|
73
|
+
async getAudits(@Query() query: any): Promise<any> {
|
|
74
|
+
try {
|
|
75
|
+
let { limit, nextToken } = query;
|
|
76
|
+
let prevPage: any = 1;
|
|
77
|
+
try {
|
|
78
|
+
const validToken = (val: string) =>
|
|
79
|
+
val && val !== 'null' && val !== 'undefined';
|
|
80
|
+
if (validToken(nextToken)) {
|
|
81
|
+
const splitToken: any = nextToken?.split(':');
|
|
82
|
+
nextToken = splitToken[0];
|
|
83
|
+
prevPage = splitToken[1];
|
|
84
|
+
nextToken = JSON.parse(
|
|
85
|
+
Buffer.from(nextToken as string, 'base64').toString('utf-8'),
|
|
86
|
+
);
|
|
87
|
+
} else {
|
|
88
|
+
nextToken = undefined;
|
|
89
|
+
}
|
|
90
|
+
} catch (err) {
|
|
91
|
+
throw new BadRequestException('Invalid pagination token');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let audits;
|
|
95
|
+
|
|
96
|
+
const parsedLimit = Number(limit ?? '10');
|
|
97
|
+
const total = await this.getAuditsCount();
|
|
98
|
+
const result = await this.getAllAudits({
|
|
99
|
+
limit: parsedLimit,
|
|
100
|
+
nextToken,
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
const page = !nextToken ? prevPage : Number(prevPage) + 1;
|
|
104
|
+
nextToken = result.LastEvaluatedKey
|
|
105
|
+
? Buffer.from(JSON.stringify(result.LastEvaluatedKey)).toString(
|
|
106
|
+
'base64',
|
|
107
|
+
)
|
|
108
|
+
: undefined;
|
|
109
|
+
const response = {
|
|
110
|
+
logs: result.Items,
|
|
111
|
+
count: result.Items?.length,
|
|
112
|
+
total,
|
|
113
|
+
page,
|
|
114
|
+
pageSize: parsedLimit,
|
|
115
|
+
nextToken:
|
|
116
|
+
!nextToken || page * Number(limit) === total
|
|
117
|
+
? null
|
|
118
|
+
: `${nextToken}:${page}`,
|
|
119
|
+
};
|
|
120
|
+
return response;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|