@novha/anlia-core-lib 0.1.0 → 0.2.0
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/cdk/bin/app.ts +21 -0
- package/cdk/bin/infrastructure.ts +16 -6
- package/cdk/lib/app.ts +34 -0
- package/cdk/lib/infrastructures/event-stack.ts +74 -0
- package/cdk/lib/infrastructures/storage-stack.ts +57 -0
- package/cdk.json +1 -1
- package/cdk.out/.cache/dc7aa0aafa98afcdd2faf6146c00c1c90f0e795c782e819001cf6258fc79c93e.zip +0 -0
- package/cdk.out/anlia-core-lib-app-dev.assets.json +36 -0
- package/cdk.out/anlia-core-lib-app-dev.metadata.json +109 -0
- package/cdk.out/anlia-core-lib-app-dev.template.json +157 -0
- package/cdk.out/anlia-core-lib-dev.assets.json +21 -0
- package/cdk.out/anlia-core-lib-dev.metadata.json +223 -0
- package/cdk.out/anlia-core-lib-dev.template.json +232 -0
- package/cdk.out/asset.dc7aa0aafa98afcdd2faf6146c00c1c90f0e795c782e819001cf6258fc79c93e/index.js +38 -0
- package/cdk.out/cdk.out +1 -0
- package/cdk.out/manifest.json +548 -0
- package/cdk.out/tree.json +1 -0
- package/dist/cdk/bin/app.d.ts +1 -0
- package/dist/cdk/bin/app.js +21 -0
- package/dist/cdk/bin/infrastructure.js +13 -6
- package/dist/cdk/lib/app.d.ts +6 -0
- package/dist/cdk/lib/app.js +48 -0
- package/dist/cdk/lib/infrastructure.js +29 -0
- package/dist/cdk/lib/infrastructures/event-stack.d.ts +6 -0
- package/dist/cdk/lib/infrastructures/event-stack.js +61 -0
- package/dist/cdk/lib/infrastructures/storage-stack.d.ts +8 -0
- package/dist/cdk/lib/infrastructures/storage-stack.js +46 -0
- package/dist/src/dynamoDb/DynamoDbModule.d.ts +2 -0
- package/dist/src/dynamoDb/DynamoDbModule.js +29 -0
- package/dist/src/dynamoDb/index.d.ts +4 -0
- package/dist/src/dynamoDb/index.js +18 -0
- package/dist/src/dynamoDb/ioc/index.d.ts +3 -0
- package/dist/src/dynamoDb/ioc/index.js +6 -0
- package/dist/src/dynamoDb/service/DynamoDbService.d.ts +34 -0
- package/dist/src/dynamoDb/service/DynamoDbService.js +2 -0
- package/dist/src/dynamoDb/service/DynamoDbServiceImpl.d.ts +16 -0
- package/dist/src/dynamoDb/service/DynamoDbServiceImpl.js +108 -0
- package/dist/src/eventBridge/EventBridgeModule.js +5 -1
- package/dist/src/handlers/EventLoggerHandler.d.ts +2 -0
- package/dist/src/handlers/EventLoggerHandler.js +13 -0
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.js +2 -0
- package/dist/src/orders/index.d.ts +1 -0
- package/dist/src/s3/S3Module.d.ts +2 -0
- package/dist/src/s3/S3Module.js +31 -0
- package/dist/src/s3/index.d.ts +4 -0
- package/dist/src/s3/index.js +18 -0
- package/dist/src/s3/ioc/index.d.ts +3 -0
- package/dist/src/s3/ioc/index.js +6 -0
- package/dist/src/s3/service/S3Service.d.ts +27 -0
- package/dist/src/s3/service/S3Service.js +2 -0
- package/dist/src/s3/service/S3ServiceImpl.d.ts +17 -0
- package/dist/src/s3/service/S3ServiceImpl.js +104 -0
- package/package.json +8 -3
- package/src/dynamoDb/DynamoDbModule.ts +18 -0
- package/src/dynamoDb/index.ts +4 -0
- package/src/dynamoDb/ioc/index.ts +3 -0
- package/src/dynamoDb/service/DynamoDbService.ts +37 -0
- package/src/dynamoDb/service/DynamoDbServiceImpl.ts +132 -0
- package/src/eventBridge/EventBridgeModule.ts +6 -1
- package/src/handlers/EventLoggerHandler.ts +11 -0
- package/src/index.ts +3 -1
- package/src/orders/index.ts +2 -1
- package/src/s3/S3Module.ts +20 -0
- package/src/s3/index.ts +4 -0
- package/src/s3/ioc/index.ts +3 -0
- package/src/s3/service/S3Service.ts +30 -0
- package/src/s3/service/S3ServiceImpl.ts +116 -0
- package/tsconfig.json +1 -2
- package/cdk/lib/infrastructure.ts +0 -37
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
5
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
6
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
7
|
+
};
|
|
8
|
+
var __metadata = (this && this.__metadata) || function (k, v) {
|
|
9
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.S3ServiceImpl = void 0;
|
|
13
|
+
const node_crypto_1 = require("node:crypto");
|
|
14
|
+
const node_path_1 = require("node:path");
|
|
15
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
16
|
+
const common_1 = require("@nestjs/common");
|
|
17
|
+
let S3ServiceImpl = class S3ServiceImpl {
|
|
18
|
+
constructor(options) {
|
|
19
|
+
this.bucket = options.bucket;
|
|
20
|
+
this.s3Client = new client_s3_1.S3Client({
|
|
21
|
+
region: options.region ?? process.env.AWS_REGION ?? 'us-east-1',
|
|
22
|
+
endpoint: options.endpoint || undefined,
|
|
23
|
+
forcePathStyle: options.forcePathStyle ?? false,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async uploadFile(request) {
|
|
27
|
+
const bucket = this.getBucketName();
|
|
28
|
+
const key = this.buildObjectKey(request.file.originalname, request.folder);
|
|
29
|
+
const contentType = request.file.mimetype || 'application/octet-stream';
|
|
30
|
+
try {
|
|
31
|
+
await this.s3Client.send(new client_s3_1.PutObjectCommand({
|
|
32
|
+
Bucket: bucket,
|
|
33
|
+
Key: key,
|
|
34
|
+
Body: request.file.buffer,
|
|
35
|
+
ContentType: contentType,
|
|
36
|
+
ContentLength: request.file.size,
|
|
37
|
+
Metadata: {
|
|
38
|
+
originalname: request.file.originalname,
|
|
39
|
+
},
|
|
40
|
+
}));
|
|
41
|
+
return {
|
|
42
|
+
bucket,
|
|
43
|
+
key,
|
|
44
|
+
contentType,
|
|
45
|
+
size: request.file.size,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
console.error('Error uploading file to S3:', error);
|
|
50
|
+
throw new common_1.InternalServerErrorException('Unable to upload file to S3.');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
async fetchFile(key) {
|
|
54
|
+
const bucket = this.getBucketName();
|
|
55
|
+
try {
|
|
56
|
+
const response = await this.s3Client.send(new client_s3_1.GetObjectCommand({
|
|
57
|
+
Bucket: bucket,
|
|
58
|
+
Key: key,
|
|
59
|
+
}));
|
|
60
|
+
if (!response.Body) {
|
|
61
|
+
throw new common_1.NotFoundException(`No file found for key: ${key}`);
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
body: Buffer.from(await response.Body.transformToByteArray()),
|
|
65
|
+
contentType: response.ContentType,
|
|
66
|
+
contentLength: response.ContentLength,
|
|
67
|
+
fileName: response.Metadata?.originalname ?? this.getFileNameFromKey(key),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
if (error instanceof common_1.NotFoundException) {
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if (error instanceof Error && ['NoSuchKey', 'NotFound'].includes(error.name)) {
|
|
75
|
+
throw new common_1.NotFoundException(`No file found for key: ${key}`);
|
|
76
|
+
}
|
|
77
|
+
throw new common_1.InternalServerErrorException('Unable to fetch file from S3.');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
getBucketName() {
|
|
81
|
+
return this.bucket;
|
|
82
|
+
}
|
|
83
|
+
buildObjectKey(originalName, folder) {
|
|
84
|
+
const extension = (0, node_path_1.extname)(originalName);
|
|
85
|
+
const baseName = (0, node_path_1.basename)(originalName, extension)
|
|
86
|
+
.trim()
|
|
87
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
88
|
+
.replace(/^-+|-+$/g, '') || 'file';
|
|
89
|
+
const normalizedFolder = folder
|
|
90
|
+
?.trim()
|
|
91
|
+
.replace(/^\/+|\/+$/g, '')
|
|
92
|
+
.replace(/\.\./g, '');
|
|
93
|
+
const fileName = `${baseName}-${(0, node_crypto_1.randomUUID)()}${extension}`;
|
|
94
|
+
return normalizedFolder ? `${normalizedFolder}/${fileName}` : fileName;
|
|
95
|
+
}
|
|
96
|
+
getFileNameFromKey(key) {
|
|
97
|
+
return key.split('/').at(-1) ?? key;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
exports.S3ServiceImpl = S3ServiceImpl;
|
|
101
|
+
exports.S3ServiceImpl = S3ServiceImpl = __decorate([
|
|
102
|
+
(0, common_1.Injectable)(),
|
|
103
|
+
__metadata("design:paramtypes", [Object])
|
|
104
|
+
], S3ServiceImpl);
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@novha/anlia-core-lib",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/
|
|
5
|
+
"main": "dist/src/index.js",
|
|
6
|
+
"types": "dist/src/index.d.ts",
|
|
7
7
|
"bin": {
|
|
8
8
|
"anlia-core-lib.js": "bin/anlia-core-lib.js"
|
|
9
9
|
},
|
|
@@ -14,17 +14,22 @@
|
|
|
14
14
|
"cdk": "cdk"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
17
|
+
"@types/aws-lambda": "^8.10.162",
|
|
17
18
|
"@types/jest": "^29.5.12",
|
|
18
19
|
"@types/node": "20.12.7",
|
|
19
20
|
"aws-cdk": "2.142.1",
|
|
21
|
+
"esbuild": "^0.28.1",
|
|
20
22
|
"jest": "^29.7.0",
|
|
21
23
|
"ts-jest": "^29.1.2",
|
|
22
24
|
"ts-node": "^10.9.2",
|
|
23
25
|
"typescript": "~5.4.5"
|
|
24
26
|
},
|
|
25
27
|
"dependencies": {
|
|
28
|
+
"@aws-sdk/client-dynamodb": "^3.1095.0",
|
|
26
29
|
"@aws-sdk/client-eventbridge": "^3.1090.0",
|
|
30
|
+
"@aws-sdk/client-s3": "^3.1095.0",
|
|
27
31
|
"@aws-sdk/client-secrets-manager": "^3.721.0",
|
|
32
|
+
"@aws-sdk/lib-dynamodb": "^3.1095.0",
|
|
28
33
|
"@nestjs/common": "^11.1.28",
|
|
29
34
|
"@nestjs/core": "^11.1.28",
|
|
30
35
|
"aws-cdk-lib": "^2.261.0",
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { DynamoDbSymbols } from './ioc';
|
|
3
|
+
import { DynamoDbServiceImpl } from './service/DynamoDbServiceImpl';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
providers: [
|
|
7
|
+
{
|
|
8
|
+
provide: DynamoDbSymbols.DynamoDbService,
|
|
9
|
+
useFactory: () =>
|
|
10
|
+
new DynamoDbServiceImpl({
|
|
11
|
+
region: process.env.AWS_REGION ?? 'us-east-1',
|
|
12
|
+
endpoint: process.env.DYNAMODB_ENDPOINT,
|
|
13
|
+
}),
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
exports: [DynamoDbSymbols.DynamoDbService],
|
|
17
|
+
})
|
|
18
|
+
export class DynamoDbModule {}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface QueryParams {
|
|
2
|
+
tableName: string;
|
|
3
|
+
keyConditionExpression: string;
|
|
4
|
+
expressionAttributeValues: Record<string, unknown>;
|
|
5
|
+
expressionAttributeNames?: Record<string, string>;
|
|
6
|
+
filterExpression?: string;
|
|
7
|
+
indexName?: string;
|
|
8
|
+
limit?: number;
|
|
9
|
+
exclusiveStartKey?: Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface ScanParams {
|
|
13
|
+
tableName: string;
|
|
14
|
+
filterExpression?: string;
|
|
15
|
+
expressionAttributeValues?: Record<string, unknown>;
|
|
16
|
+
expressionAttributeNames?: Record<string, string>;
|
|
17
|
+
limit?: number;
|
|
18
|
+
exclusiveStartKey?: Record<string, unknown>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface UpdateParams {
|
|
22
|
+
tableName: string;
|
|
23
|
+
key: Record<string, unknown>;
|
|
24
|
+
updateExpression: string;
|
|
25
|
+
expressionAttributeValues: Record<string, unknown>;
|
|
26
|
+
expressionAttributeNames?: Record<string, string>;
|
|
27
|
+
conditionExpression?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface IDynamoDbService {
|
|
31
|
+
get<T>(tableName: string, key: Record<string, unknown>): Promise<T | undefined>;
|
|
32
|
+
put(tableName: string, item: Record<string, unknown>): Promise<void>;
|
|
33
|
+
update(params: UpdateParams): Promise<void>;
|
|
34
|
+
delete(tableName: string, key: Record<string, unknown>): Promise<void>;
|
|
35
|
+
query<T>(params: QueryParams): Promise<T[]>;
|
|
36
|
+
scan<T>(params: ScanParams): Promise<T[]>;
|
|
37
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
|
2
|
+
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
|
|
3
|
+
import {
|
|
4
|
+
DynamoDBDocumentClient,
|
|
5
|
+
GetCommand,
|
|
6
|
+
PutCommand,
|
|
7
|
+
UpdateCommand,
|
|
8
|
+
DeleteCommand,
|
|
9
|
+
QueryCommand,
|
|
10
|
+
ScanCommand,
|
|
11
|
+
} from '@aws-sdk/lib-dynamodb';
|
|
12
|
+
import type { IDynamoDbService, QueryParams, ScanParams, UpdateParams } from './DynamoDbService';
|
|
13
|
+
|
|
14
|
+
export interface DynamoDbOptions {
|
|
15
|
+
region?: string;
|
|
16
|
+
/** Local DynamoDB endpoint, e.g. http://localhost:8000 */
|
|
17
|
+
endpoint?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Injectable()
|
|
21
|
+
export class DynamoDbServiceImpl implements IDynamoDbService {
|
|
22
|
+
private readonly client: DynamoDBDocumentClient;
|
|
23
|
+
|
|
24
|
+
constructor(options: DynamoDbOptions = {}) {
|
|
25
|
+
const base = new DynamoDBClient({
|
|
26
|
+
region: options.region ?? process.env.AWS_REGION ?? 'us-east-1',
|
|
27
|
+
endpoint: options.endpoint || undefined,
|
|
28
|
+
});
|
|
29
|
+
this.client = DynamoDBDocumentClient.from(base, {
|
|
30
|
+
marshallOptions: { removeUndefinedValues: true },
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async get<T>(tableName: string, key: Record<string, unknown>): Promise<T | undefined> {
|
|
35
|
+
try {
|
|
36
|
+
const { Item } = await this.client.send(
|
|
37
|
+
new GetCommand({ TableName: tableName, Key: key }),
|
|
38
|
+
);
|
|
39
|
+
return Item as T | undefined;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new InternalServerErrorException(
|
|
42
|
+
`DynamoDB get failed for table "${tableName}": ${(error as Error).message}`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async put(tableName: string, item: Record<string, unknown>): Promise<void> {
|
|
48
|
+
try {
|
|
49
|
+
await this.client.send(new PutCommand({ TableName: tableName, Item: item }));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new InternalServerErrorException(
|
|
52
|
+
`DynamoDB put failed for table "${tableName}": ${(error as Error).message}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async update(params: UpdateParams): Promise<void> {
|
|
58
|
+
try {
|
|
59
|
+
await this.client.send(
|
|
60
|
+
new UpdateCommand({
|
|
61
|
+
TableName: params.tableName,
|
|
62
|
+
Key: params.key,
|
|
63
|
+
UpdateExpression: params.updateExpression,
|
|
64
|
+
ExpressionAttributeValues: params.expressionAttributeValues,
|
|
65
|
+
ExpressionAttributeNames: params.expressionAttributeNames,
|
|
66
|
+
ConditionExpression: params.conditionExpression,
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
} catch (error) {
|
|
70
|
+
if ((error as Error).name === 'ConditionalCheckFailedException') {
|
|
71
|
+
throw new NotFoundException(
|
|
72
|
+
`Item not found in table "${params.tableName}".`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
throw new InternalServerErrorException(
|
|
76
|
+
`DynamoDB update failed for table "${params.tableName}": ${(error as Error).message}`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async delete(tableName: string, key: Record<string, unknown>): Promise<void> {
|
|
82
|
+
try {
|
|
83
|
+
await this.client.send(new DeleteCommand({ TableName: tableName, Key: key }));
|
|
84
|
+
} catch (error) {
|
|
85
|
+
throw new InternalServerErrorException(
|
|
86
|
+
`DynamoDB delete failed for table "${tableName}": ${(error as Error).message}`,
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async query<T>(params: QueryParams): Promise<T[]> {
|
|
92
|
+
try {
|
|
93
|
+
const { Items } = await this.client.send(
|
|
94
|
+
new QueryCommand({
|
|
95
|
+
TableName: params.tableName,
|
|
96
|
+
IndexName: params.indexName,
|
|
97
|
+
KeyConditionExpression: params.keyConditionExpression,
|
|
98
|
+
FilterExpression: params.filterExpression,
|
|
99
|
+
ExpressionAttributeValues: params.expressionAttributeValues,
|
|
100
|
+
ExpressionAttributeNames: params.expressionAttributeNames,
|
|
101
|
+
Limit: params.limit,
|
|
102
|
+
ExclusiveStartKey: params.exclusiveStartKey as Record<string, unknown>,
|
|
103
|
+
}),
|
|
104
|
+
);
|
|
105
|
+
return (Items ?? []) as T[];
|
|
106
|
+
} catch (error) {
|
|
107
|
+
throw new InternalServerErrorException(
|
|
108
|
+
`DynamoDB query failed for table "${params.tableName}": ${(error as Error).message}`,
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async scan<T>(params: ScanParams): Promise<T[]> {
|
|
114
|
+
try {
|
|
115
|
+
const { Items } = await this.client.send(
|
|
116
|
+
new ScanCommand({
|
|
117
|
+
TableName: params.tableName,
|
|
118
|
+
FilterExpression: params.filterExpression,
|
|
119
|
+
ExpressionAttributeValues: params.expressionAttributeValues,
|
|
120
|
+
ExpressionAttributeNames: params.expressionAttributeNames,
|
|
121
|
+
Limit: params.limit,
|
|
122
|
+
ExclusiveStartKey: params.exclusiveStartKey as Record<string, unknown>,
|
|
123
|
+
}),
|
|
124
|
+
);
|
|
125
|
+
return (Items ?? []) as T[];
|
|
126
|
+
} catch (error) {
|
|
127
|
+
throw new InternalServerErrorException(
|
|
128
|
+
`DynamoDB scan failed for table "${params.tableName}": ${(error as Error).message}`,
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -6,7 +6,12 @@ import { EventBridgeSymbols } from "./ioc";
|
|
|
6
6
|
providers: [
|
|
7
7
|
{
|
|
8
8
|
provide: EventBridgeSymbols.EventBridgeService,
|
|
9
|
-
|
|
9
|
+
useFactory: () =>
|
|
10
|
+
new EventBridgeServiceImpl({
|
|
11
|
+
eventBusName: process.env.EVENT_BUS_NAME ?? "default",
|
|
12
|
+
source: process.env.EVENT_SOURCE ?? "com.anlia",
|
|
13
|
+
region: process.env.AWS_REGION ?? "us-east-1",
|
|
14
|
+
}),
|
|
10
15
|
},
|
|
11
16
|
],
|
|
12
17
|
exports: [EventBridgeSymbols.EventBridgeService],
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { SQSEvent } from "aws-lambda";
|
|
2
|
+
|
|
3
|
+
export const handler = async (event: SQSEvent): Promise<void> => {
|
|
4
|
+
for (const record of event.Records) {
|
|
5
|
+
console.log(JSON.stringify({
|
|
6
|
+
messageId: record.messageId,
|
|
7
|
+
eventSource: record.eventSource,
|
|
8
|
+
body: JSON.parse(record.body),
|
|
9
|
+
}));
|
|
10
|
+
}
|
|
11
|
+
};
|
package/src/index.ts
CHANGED
package/src/orders/index.ts
CHANGED
|
@@ -4,4 +4,5 @@ export * from "./domain/Enums";
|
|
|
4
4
|
export * from "./ioc";
|
|
5
5
|
export * from "./OrderModule";
|
|
6
6
|
export * from "./domain/posDto/SquareOrder";
|
|
7
|
-
export { SquareOrderServiceImpl as SquareOrderService } from "./services/square/SquareOrderServiceImpl";
|
|
7
|
+
export { SquareOrderServiceImpl as SquareOrderService } from "./services/square/SquareOrderServiceImpl";
|
|
8
|
+
export type { ISquareOrderService } from "./services/square/SquareOrderService";
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Module } from '@nestjs/common';
|
|
2
|
+
import { S3Symbols } from './ioc';
|
|
3
|
+
import { S3ServiceImpl } from './service/S3ServiceImpl';
|
|
4
|
+
|
|
5
|
+
@Module({
|
|
6
|
+
providers: [
|
|
7
|
+
{
|
|
8
|
+
provide: S3Symbols.S3Service,
|
|
9
|
+
useFactory: () =>
|
|
10
|
+
new S3ServiceImpl({
|
|
11
|
+
bucket: process.env.AWS_S3_BUCKET ?? '',
|
|
12
|
+
region: process.env.AWS_REGION ?? 'us-east-1',
|
|
13
|
+
endpoint: process.env.AWS_S3_ENDPOINT,
|
|
14
|
+
forcePathStyle: process.env.AWS_S3_FORCE_PATH_STYLE === 'true',
|
|
15
|
+
}),
|
|
16
|
+
},
|
|
17
|
+
],
|
|
18
|
+
exports: [S3Symbols.S3Service],
|
|
19
|
+
})
|
|
20
|
+
export class S3Module {}
|
package/src/s3/index.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export type S3UploadFile = {
|
|
2
|
+
buffer: Buffer;
|
|
3
|
+
originalname: string;
|
|
4
|
+
mimetype: string;
|
|
5
|
+
size: number;
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
export type UploadS3FileRequest = {
|
|
9
|
+
file: S3UploadFile;
|
|
10
|
+
folder?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type UploadS3FileResponse = {
|
|
14
|
+
bucket: string;
|
|
15
|
+
key: string;
|
|
16
|
+
contentType: string;
|
|
17
|
+
size: number;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type FetchS3FileResponse = {
|
|
21
|
+
body: Buffer;
|
|
22
|
+
contentType?: string;
|
|
23
|
+
contentLength?: number;
|
|
24
|
+
fileName: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export interface S3Service {
|
|
28
|
+
uploadFile(request: UploadS3FileRequest): Promise<UploadS3FileResponse>;
|
|
29
|
+
fetchFile(key: string): Promise<FetchS3FileResponse>;
|
|
30
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { basename, extname } from 'node:path';
|
|
3
|
+
import { GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
4
|
+
import { Injectable, InternalServerErrorException, NotFoundException } from '@nestjs/common';
|
|
5
|
+
import type { FetchS3FileResponse, S3Service, UploadS3FileRequest, UploadS3FileResponse } from './S3Service';
|
|
6
|
+
|
|
7
|
+
export interface S3Options {
|
|
8
|
+
bucket: string;
|
|
9
|
+
region?: string;
|
|
10
|
+
endpoint?: string;
|
|
11
|
+
forcePathStyle?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
@Injectable()
|
|
15
|
+
export class S3ServiceImpl implements S3Service {
|
|
16
|
+
private readonly s3Client: S3Client;
|
|
17
|
+
private readonly bucket: string;
|
|
18
|
+
|
|
19
|
+
constructor(options: S3Options) {
|
|
20
|
+
this.bucket = options.bucket;
|
|
21
|
+
this.s3Client = new S3Client({
|
|
22
|
+
region: options.region ?? process.env.AWS_REGION ?? 'us-east-1',
|
|
23
|
+
endpoint: options.endpoint || undefined,
|
|
24
|
+
forcePathStyle: options.forcePathStyle ?? false,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async uploadFile(request: UploadS3FileRequest): Promise<UploadS3FileResponse> {
|
|
29
|
+
const bucket = this.getBucketName();
|
|
30
|
+
const key = this.buildObjectKey(request.file.originalname, request.folder);
|
|
31
|
+
const contentType = request.file.mimetype || 'application/octet-stream';
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
await this.s3Client.send(
|
|
35
|
+
new PutObjectCommand({
|
|
36
|
+
Bucket: bucket,
|
|
37
|
+
Key: key,
|
|
38
|
+
Body: request.file.buffer,
|
|
39
|
+
ContentType: contentType,
|
|
40
|
+
ContentLength: request.file.size,
|
|
41
|
+
Metadata: {
|
|
42
|
+
originalname: request.file.originalname,
|
|
43
|
+
},
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
bucket,
|
|
49
|
+
key,
|
|
50
|
+
contentType,
|
|
51
|
+
size: request.file.size,
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
console.error('Error uploading file to S3:', error);
|
|
55
|
+
throw new InternalServerErrorException('Unable to upload file to S3.');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async fetchFile(key: string): Promise<FetchS3FileResponse> {
|
|
60
|
+
const bucket = this.getBucketName();
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const response = await this.s3Client.send(
|
|
64
|
+
new GetObjectCommand({
|
|
65
|
+
Bucket: bucket,
|
|
66
|
+
Key: key,
|
|
67
|
+
}),
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
if (!response.Body) {
|
|
71
|
+
throw new NotFoundException(`No file found for key: ${key}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
body: Buffer.from(await response.Body.transformToByteArray()),
|
|
76
|
+
contentType: response.ContentType,
|
|
77
|
+
contentLength: response.ContentLength,
|
|
78
|
+
fileName: response.Metadata?.originalname ?? this.getFileNameFromKey(key),
|
|
79
|
+
};
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error instanceof NotFoundException) {
|
|
82
|
+
throw error;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (error instanceof Error && ['NoSuchKey', 'NotFound'].includes(error.name)) {
|
|
86
|
+
throw new NotFoundException(`No file found for key: ${key}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
throw new InternalServerErrorException('Unable to fetch file from S3.');
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
private getBucketName(): string {
|
|
94
|
+
return this.bucket;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private buildObjectKey(originalName: string, folder?: string): string {
|
|
98
|
+
const extension = extname(originalName);
|
|
99
|
+
const baseName =
|
|
100
|
+
basename(originalName, extension)
|
|
101
|
+
.trim()
|
|
102
|
+
.replace(/[^a-zA-Z0-9_-]+/g, '-')
|
|
103
|
+
.replace(/^-+|-+$/g, '') || 'file';
|
|
104
|
+
const normalizedFolder = folder
|
|
105
|
+
?.trim()
|
|
106
|
+
.replace(/^\/+|\/+$/g, '')
|
|
107
|
+
.replace(/\.\./g, '');
|
|
108
|
+
const fileName = `${baseName}-${randomUUID()}${extension}`;
|
|
109
|
+
|
|
110
|
+
return normalizedFolder ? `${normalizedFolder}/${fileName}` : fileName;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private getFileNameFromKey(key: string): string {
|
|
114
|
+
return key.split('/').at(-1) ?? key;
|
|
115
|
+
}
|
|
116
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
"compilerOptions": {
|
|
3
3
|
"target": "ES2020",
|
|
4
4
|
"module": "commonjs",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
5
|
"lib": ["es2020"],
|
|
7
6
|
"declaration": true,
|
|
8
7
|
"strict": true,
|
|
@@ -12,7 +11,7 @@
|
|
|
12
11
|
"emitDecoratorMetadata": true,
|
|
13
12
|
"skipLibCheck": true,
|
|
14
13
|
"types": ["node", "jest"],
|
|
15
|
-
"outDir": "dist"
|
|
14
|
+
"outDir": "dist",
|
|
16
15
|
},
|
|
17
16
|
"include": ["cdk/**/*.ts", "src/**/*.ts"]
|
|
18
17
|
}
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import { CfnOutput, Stack } from "aws-cdk-lib";
|
|
2
|
-
import { Construct } from "constructs";
|
|
3
|
-
import { EventBus } from "aws-cdk-lib/aws-events";
|
|
4
|
-
import { Bucket } from "aws-cdk-lib/aws-s3";
|
|
5
|
-
import { CommonProps } from "../bin/project-names";
|
|
6
|
-
|
|
7
|
-
export class InfrastructureStack extends Stack {
|
|
8
|
-
constructor(scope: Construct, id: string, props: CommonProps) {
|
|
9
|
-
super(scope, id, props);
|
|
10
|
-
|
|
11
|
-
const eventBus = new EventBus(this, "EventBus", {
|
|
12
|
-
eventBusName: `${props.projectName}-${props.stage}-event-bus`,
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const bucket = new Bucket(this, "Bucket");
|
|
16
|
-
|
|
17
|
-
new CfnOutput(this, "EventBusArn", {
|
|
18
|
-
value: eventBus.eventBusArn,
|
|
19
|
-
exportName: `${props.projectName}-${props.stage}-event-bus-arn`,
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
new CfnOutput(this, "EventBusName", {
|
|
23
|
-
value: eventBus.eventBusName,
|
|
24
|
-
exportName: `${props.projectName}-${props.stage}-event-bus-name`,
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
new CfnOutput(this, "BucketArn", {
|
|
28
|
-
value: bucket.bucketArn,
|
|
29
|
-
exportName: `${props.projectName}-${props.stage}-bucket-arn`,
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
new CfnOutput(this, "BucketName", {
|
|
33
|
-
value: bucket.bucketName,
|
|
34
|
-
exportName: `${props.projectName}-${props.stage}-bucket-name`,
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|