@fnd-platform/api 1.0.0-alpha.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/LICENSE +21 -0
- package/README.md +204 -0
- package/lib/api-project.d.ts +99 -0
- package/lib/api-project.d.ts.map +1 -0
- package/lib/api-project.js +668 -0
- package/lib/api-project.js.map +1 -0
- package/lib/handlers/content.d.ts +52 -0
- package/lib/handlers/content.d.ts.map +1 -0
- package/lib/handlers/content.js +122 -0
- package/lib/handlers/content.js.map +1 -0
- package/lib/handlers/health.d.ts +43 -0
- package/lib/handlers/health.d.ts.map +1 -0
- package/lib/handlers/health.js +43 -0
- package/lib/handlers/health.js.map +1 -0
- package/lib/handlers/media.d.ts +86 -0
- package/lib/handlers/media.d.ts.map +1 -0
- package/lib/handlers/media.js +287 -0
- package/lib/handlers/media.js.map +1 -0
- package/lib/index.d.ts +22 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +50 -0
- package/lib/index.js.map +1 -0
- package/lib/lib/errors.d.ts +114 -0
- package/lib/lib/errors.d.ts.map +1 -0
- package/lib/lib/errors.js +154 -0
- package/lib/lib/errors.js.map +1 -0
- package/lib/lib/middleware.d.ts +33 -0
- package/lib/lib/middleware.d.ts.map +1 -0
- package/lib/lib/middleware.js +36 -0
- package/lib/lib/middleware.js.map +1 -0
- package/lib/lib/request.d.ts +90 -0
- package/lib/lib/request.d.ts.map +1 -0
- package/lib/lib/request.js +115 -0
- package/lib/lib/request.js.map +1 -0
- package/lib/lib/response.d.ts +131 -0
- package/lib/lib/response.d.ts.map +1 -0
- package/lib/lib/response.js +163 -0
- package/lib/lib/response.js.map +1 -0
- package/lib/middleware/auth.d.ts +57 -0
- package/lib/middleware/auth.d.ts.map +1 -0
- package/lib/middleware/auth.js +62 -0
- package/lib/middleware/auth.js.map +1 -0
- package/lib/middleware/cors.d.ts +51 -0
- package/lib/middleware/cors.d.ts.map +1 -0
- package/lib/middleware/cors.js +62 -0
- package/lib/middleware/cors.js.map +1 -0
- package/lib/middleware/error-handler.d.ts +38 -0
- package/lib/middleware/error-handler.d.ts.map +1 -0
- package/lib/middleware/error-handler.js +49 -0
- package/lib/middleware/error-handler.js.map +1 -0
- package/lib/middleware/index.d.ts +15 -0
- package/lib/middleware/index.d.ts.map +1 -0
- package/lib/middleware/index.js +49 -0
- package/lib/middleware/index.js.map +1 -0
- package/lib/middleware/logging.d.ts +48 -0
- package/lib/middleware/logging.d.ts.map +1 -0
- package/lib/middleware/logging.js +58 -0
- package/lib/middleware/logging.js.map +1 -0
- package/lib/middleware/validation.d.ts +41 -0
- package/lib/middleware/validation.d.ts.map +1 -0
- package/lib/middleware/validation.js +76 -0
- package/lib/middleware/validation.js.map +1 -0
- package/lib/options.d.ts +48 -0
- package/lib/options.d.ts.map +1 -0
- package/lib/options.js +3 -0
- package/lib/options.js.map +1 -0
- package/lib/types/api.d.ts +108 -0
- package/lib/types/api.d.ts.map +1 -0
- package/lib/types/api.js +11 -0
- package/lib/types/api.js.map +1 -0
- package/lib/types/middleware.d.ts +59 -0
- package/lib/types/middleware.d.ts.map +1 -0
- package/lib/types/middleware.js +11 -0
- package/lib/types/middleware.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content CRUD handler template.
|
|
3
|
+
*
|
|
4
|
+
* This template demonstrates the standard patterns for building
|
|
5
|
+
* CRUD (Create, Read, Update, Delete) API endpoints using
|
|
6
|
+
* fnd-platform utilities.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
import type { APIGatewayProxyHandler } from 'aws-lambda';
|
|
11
|
+
/**
|
|
12
|
+
* GET /content - List all content items.
|
|
13
|
+
*
|
|
14
|
+
* Supports pagination via query parameters:
|
|
15
|
+
* - limit: Maximum items per page (default: 20)
|
|
16
|
+
* - cursor: Pagination cursor for next page
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* GET /content?limit=10&cursor=abc123
|
|
20
|
+
*/
|
|
21
|
+
export declare const list: APIGatewayProxyHandler;
|
|
22
|
+
/**
|
|
23
|
+
* GET /content/:id - Get a single content item.
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* GET /content/abc123
|
|
27
|
+
*/
|
|
28
|
+
export declare const get: APIGatewayProxyHandler;
|
|
29
|
+
/**
|
|
30
|
+
* POST /content - Create a new content item.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* POST /content
|
|
34
|
+
* Body: { "title": "My Post", "content": "Hello world" }
|
|
35
|
+
*/
|
|
36
|
+
export declare const create: APIGatewayProxyHandler;
|
|
37
|
+
/**
|
|
38
|
+
* PUT /content/:id - Update an existing content item.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* PUT /content/abc123
|
|
42
|
+
* Body: { "title": "Updated Title" }
|
|
43
|
+
*/
|
|
44
|
+
export declare const update: APIGatewayProxyHandler;
|
|
45
|
+
/**
|
|
46
|
+
* DELETE /content/:id - Delete a content item.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* DELETE /content/abc123
|
|
50
|
+
*/
|
|
51
|
+
export declare const remove: APIGatewayProxyHandler;
|
|
52
|
+
//# sourceMappingURL=content.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.d.ts","sourceRoot":"","sources":["../../src/handlers/content.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAgBzD;;;;;;;;;GASG;AACH,eAAO,MAAM,IAAI,EAAE,sBAmBlB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,GAAG,EAAE,sBAYjB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,EAAE,sBAiBpB,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,MAAM,EAAE,sBAcpB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,MAAM,EAAE,sBAWpB,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Content CRUD handler template.
|
|
4
|
+
*
|
|
5
|
+
* This template demonstrates the standard patterns for building
|
|
6
|
+
* CRUD (Create, Read, Update, Delete) API endpoints using
|
|
7
|
+
* fnd-platform utilities.
|
|
8
|
+
*
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.remove = exports.update = exports.create = exports.get = exports.list = void 0;
|
|
13
|
+
const response_1 = require("../lib/response");
|
|
14
|
+
const request_1 = require("../lib/request");
|
|
15
|
+
/**
|
|
16
|
+
* GET /content - List all content items.
|
|
17
|
+
*
|
|
18
|
+
* Supports pagination via query parameters:
|
|
19
|
+
* - limit: Maximum items per page (default: 20)
|
|
20
|
+
* - cursor: Pagination cursor for next page
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* GET /content?limit=10&cursor=abc123
|
|
24
|
+
*/
|
|
25
|
+
const list = async (event) => {
|
|
26
|
+
const _limit = parseInt((0, request_1.getQueryParam)(event, 'limit', '20') ?? '20', 10);
|
|
27
|
+
const _cursor = (0, request_1.getQueryParam)(event, 'cursor');
|
|
28
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
29
|
+
// const result = await db.query({
|
|
30
|
+
// limit,
|
|
31
|
+
// cursor,
|
|
32
|
+
// indexName: 'GSI1',
|
|
33
|
+
// });
|
|
34
|
+
const items = [];
|
|
35
|
+
const response = {
|
|
36
|
+
items,
|
|
37
|
+
nextCursor: undefined,
|
|
38
|
+
total: 0,
|
|
39
|
+
};
|
|
40
|
+
return (0, response_1.success)(response);
|
|
41
|
+
};
|
|
42
|
+
exports.list = list;
|
|
43
|
+
/**
|
|
44
|
+
* GET /content/:id - Get a single content item.
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* GET /content/abc123
|
|
48
|
+
*/
|
|
49
|
+
const get = async (event) => {
|
|
50
|
+
const id = (0, request_1.requirePathParam)(event, 'id');
|
|
51
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
52
|
+
// const item = await db.get('CONTENT', id);
|
|
53
|
+
// if (!item) {
|
|
54
|
+
// return notFound(`Content with id '${id}' not found`);
|
|
55
|
+
// }
|
|
56
|
+
// return success(item);
|
|
57
|
+
// Placeholder: return not found until DynamoDB is implemented
|
|
58
|
+
return (0, response_1.notFound)(`Content with id '${id}' not found`);
|
|
59
|
+
};
|
|
60
|
+
exports.get = get;
|
|
61
|
+
/**
|
|
62
|
+
* POST /content - Create a new content item.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* POST /content
|
|
66
|
+
* Body: { "title": "My Post", "content": "Hello world" }
|
|
67
|
+
*/
|
|
68
|
+
const create = async (event) => {
|
|
69
|
+
const body = (0, request_1.parseBody)(event);
|
|
70
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
71
|
+
// const item = await db.create('CONTENT', {
|
|
72
|
+
// title: body.title,
|
|
73
|
+
// content: body.content,
|
|
74
|
+
// });
|
|
75
|
+
const item = {
|
|
76
|
+
id: crypto.randomUUID(),
|
|
77
|
+
title: body.title,
|
|
78
|
+
content: body.content,
|
|
79
|
+
createdAt: new Date().toISOString(),
|
|
80
|
+
};
|
|
81
|
+
return (0, response_1.created)(item);
|
|
82
|
+
};
|
|
83
|
+
exports.create = create;
|
|
84
|
+
/**
|
|
85
|
+
* PUT /content/:id - Update an existing content item.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* PUT /content/abc123
|
|
89
|
+
* Body: { "title": "Updated Title" }
|
|
90
|
+
*/
|
|
91
|
+
const update = async (event) => {
|
|
92
|
+
const id = (0, request_1.requirePathParam)(event, 'id');
|
|
93
|
+
const _body = (0, request_1.parseBody)(event);
|
|
94
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
95
|
+
// const item = await db.get('CONTENT', id);
|
|
96
|
+
// if (!item) {
|
|
97
|
+
// return notFound(`Content with id '${id}' not found`);
|
|
98
|
+
// }
|
|
99
|
+
// const updated = await db.update('CONTENT', id, body);
|
|
100
|
+
// return success(updated);
|
|
101
|
+
// Placeholder: return not found until DynamoDB is implemented
|
|
102
|
+
return (0, response_1.notFound)(`Content with id '${id}' not found`);
|
|
103
|
+
};
|
|
104
|
+
exports.update = update;
|
|
105
|
+
/**
|
|
106
|
+
* DELETE /content/:id - Delete a content item.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* DELETE /content/abc123
|
|
110
|
+
*/
|
|
111
|
+
const remove = async (event) => {
|
|
112
|
+
const id = (0, request_1.requirePathParam)(event, 'id');
|
|
113
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
114
|
+
// const item = await db.get('CONTENT', id);
|
|
115
|
+
// if (!item) {
|
|
116
|
+
// return notFound(`Content with id '${id}' not found`);
|
|
117
|
+
// }
|
|
118
|
+
// await db.delete('CONTENT', id);
|
|
119
|
+
return (0, response_1.success)({ deleted: true, id });
|
|
120
|
+
};
|
|
121
|
+
exports.remove = remove;
|
|
122
|
+
//# sourceMappingURL=content.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"content.js","sourceRoot":"","sources":["../../src/handlers/content.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAGH,8CAA6D;AAC7D,4CAA4E;AAc5E;;;;;;;;;GASG;AACI,MAAM,IAAI,GAA2B,KAAK,EAAE,KAAK,EAAE,EAAE;IAC1D,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAA,uBAAa,EAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE,CAAC,CAAC;IACzE,MAAM,OAAO,GAAG,IAAA,uBAAa,EAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;IAE/C,2CAA2C;IAC3C,kCAAkC;IAClC,WAAW;IACX,YAAY;IACZ,uBAAuB;IACvB,MAAM;IAEN,MAAM,KAAK,GAAkB,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAmC;QAC/C,KAAK;QACL,UAAU,EAAE,SAAS;QACrB,KAAK,EAAE,CAAC;KACT,CAAC;IAEF,OAAO,IAAA,kBAAO,EAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC,CAAC;AAnBW,QAAA,IAAI,QAmBf;AAEF;;;;;GAKG;AACI,MAAM,GAAG,GAA2B,KAAK,EAAE,KAAK,EAAE,EAAE;IACzD,MAAM,EAAE,GAAG,IAAA,0BAAgB,EAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAEzC,2CAA2C;IAC3C,4CAA4C;IAC5C,eAAe;IACf,0DAA0D;IAC1D,IAAI;IACJ,wBAAwB;IAExB,8DAA8D;IAC9D,OAAO,IAAA,mBAAQ,EAAC,oBAAoB,EAAE,aAAa,CAAC,CAAC;AACvD,CAAC,CAAC;AAZW,QAAA,GAAG,OAYd;AAEF;;;;;;GAMG;AACI,MAAM,MAAM,GAA2B,KAAK,EAAE,KAAK,EAAE,EAAE;IAC5D,MAAM,IAAI,GAAG,IAAA,mBAAS,EAAqC,KAAK,CAAC,CAAC;IAElE,2CAA2C;IAC3C,4CAA4C;IAC5C,uBAAuB;IACvB,2BAA2B;IAC3B,MAAM;IAEN,MAAM,IAAI,GAAgB;QACxB,EAAE,EAAE,MAAM,CAAC,UAAU,EAAE;QACvB,KAAK,EAAE,IAAI,CAAC,KAAK;QACjB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;KACpC,CAAC;IAEF,OAAO,IAAA,kBAAO,EAAC,IAAI,CAAC,CAAC;AACvB,CAAC,CAAC;AAjBW,QAAA,MAAM,UAiBjB;AAEF;;;;;;GAMG;AACI,MAAM,MAAM,GAA2B,KAAK,EAAE,KAAK,EAAE,EAAE;IAC5D,MAAM,EAAE,GAAG,IAAA,0BAAgB,EAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACzC,MAAM,KAAK,GAAG,IAAA,mBAAS,EAAuC,KAAK,CAAC,CAAC;IAErE,2CAA2C;IAC3C,4CAA4C;IAC5C,eAAe;IACf,0DAA0D;IAC1D,IAAI;IACJ,wDAAwD;IACxD,2BAA2B;IAE3B,8DAA8D;IAC9D,OAAO,IAAA,mBAAQ,EAAC,oBAAoB,EAAE,aAAa,CAAC,CAAC;AACvD,CAAC,CAAC;AAdW,QAAA,MAAM,UAcjB;AAEF;;;;;GAKG;AACI,MAAM,MAAM,GAA2B,KAAK,EAAE,KAAK,EAAE,EAAE;IAC5D,MAAM,EAAE,GAAG,IAAA,0BAAgB,EAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAEzC,2CAA2C;IAC3C,4CAA4C;IAC5C,eAAe;IACf,0DAA0D;IAC1D,IAAI;IACJ,kCAAkC;IAElC,OAAO,IAAA,kBAAO,EAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;AACxC,CAAC,CAAC;AAXW,QAAA,MAAM,UAWjB"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Health check endpoint handler.
|
|
3
|
+
*
|
|
4
|
+
* Provides a simple health check endpoint for monitoring
|
|
5
|
+
* and load balancer health checks.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import type { APIGatewayProxyHandler } from 'aws-lambda';
|
|
10
|
+
/**
|
|
11
|
+
* Health check response structure.
|
|
12
|
+
*/
|
|
13
|
+
export interface HealthCheckResponse {
|
|
14
|
+
/** Health status indicator */
|
|
15
|
+
status: 'healthy' | 'unhealthy';
|
|
16
|
+
/** ISO 8601 timestamp of the check */
|
|
17
|
+
timestamp: string;
|
|
18
|
+
/** API version from environment variable */
|
|
19
|
+
version: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* GET /health - Health check endpoint.
|
|
23
|
+
*
|
|
24
|
+
* Returns the current health status of the API.
|
|
25
|
+
* Used for monitoring and load balancer health checks.
|
|
26
|
+
*
|
|
27
|
+
* @returns Health status with timestamp and version
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* Response:
|
|
31
|
+
* ```json
|
|
32
|
+
* {
|
|
33
|
+
* "success": true,
|
|
34
|
+
* "data": {
|
|
35
|
+
* "status": "healthy",
|
|
36
|
+
* "timestamp": "2024-01-15T12:00:00.000Z",
|
|
37
|
+
* "version": "1.0.0"
|
|
38
|
+
* }
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export declare const handler: APIGatewayProxyHandler;
|
|
43
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../src/handlers/health.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAyB,MAAM,YAAY,CAAC;AAGhF;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,8BAA8B;IAC9B,MAAM,EAAE,SAAS,GAAG,WAAW,CAAC;IAChC,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,OAAO,EAAE,sBAQrB,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Health check endpoint handler.
|
|
4
|
+
*
|
|
5
|
+
* Provides a simple health check endpoint for monitoring
|
|
6
|
+
* and load balancer health checks.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
11
|
+
exports.handler = void 0;
|
|
12
|
+
const response_1 = require('../lib/response');
|
|
13
|
+
/**
|
|
14
|
+
* GET /health - Health check endpoint.
|
|
15
|
+
*
|
|
16
|
+
* Returns the current health status of the API.
|
|
17
|
+
* Used for monitoring and load balancer health checks.
|
|
18
|
+
*
|
|
19
|
+
* @returns Health status with timestamp and version
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* Response:
|
|
23
|
+
* ```json
|
|
24
|
+
* {
|
|
25
|
+
* "success": true,
|
|
26
|
+
* "data": {
|
|
27
|
+
* "status": "healthy",
|
|
28
|
+
* "timestamp": "2024-01-15T12:00:00.000Z",
|
|
29
|
+
* "version": "1.0.0"
|
|
30
|
+
* }
|
|
31
|
+
* }
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
const handler = async () => {
|
|
35
|
+
const response = {
|
|
36
|
+
status: 'healthy',
|
|
37
|
+
timestamp: new Date().toISOString(),
|
|
38
|
+
version: process.env.API_VERSION ?? '0.0.0',
|
|
39
|
+
};
|
|
40
|
+
return (0, response_1.success)(response);
|
|
41
|
+
};
|
|
42
|
+
exports.handler = handler;
|
|
43
|
+
//# sourceMappingURL=health.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/handlers/health.ts"],"names":[],"mappings":";AAAA;;;;;;;GAOG;;;AAGH,8CAA0C;AAc1C;;;;;;;;;;;;;;;;;;;;GAoBG;AACI,MAAM,OAAO,GAA2B,KAAK,IAAoC,EAAE;IACxF,MAAM,QAAQ,GAAwB;QACpC,MAAM,EAAE,SAAS;QACjB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,OAAO;KAC5C,CAAC;IAEF,OAAO,IAAA,kBAAO,EAAC,QAAQ,CAAC,CAAC;AAC3B,CAAC,CAAC;AARW,QAAA,OAAO,WAQlB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Media upload and management handler template.
|
|
3
|
+
*
|
|
4
|
+
* This template demonstrates the standard patterns for building
|
|
5
|
+
* media upload endpoints using presigned URLs for direct S3 uploads.
|
|
6
|
+
*
|
|
7
|
+
* @packageDocumentation
|
|
8
|
+
*/
|
|
9
|
+
import type { APIGatewayProxyHandler } from 'aws-lambda';
|
|
10
|
+
/**
|
|
11
|
+
* Media item interface.
|
|
12
|
+
*/
|
|
13
|
+
export interface MediaItem {
|
|
14
|
+
id: string;
|
|
15
|
+
key: string;
|
|
16
|
+
filename: string;
|
|
17
|
+
mimeType: string;
|
|
18
|
+
size: number;
|
|
19
|
+
url: string;
|
|
20
|
+
uploadedAt: string;
|
|
21
|
+
uploadedBy?: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* POST /media/upload-url - Get a presigned URL for direct S3 upload.
|
|
25
|
+
*
|
|
26
|
+
* Request body:
|
|
27
|
+
* - filename: Original filename
|
|
28
|
+
* - contentType: MIME type of the file
|
|
29
|
+
* - size: File size in bytes
|
|
30
|
+
*
|
|
31
|
+
* Response:
|
|
32
|
+
* - uploadUrl: Presigned PUT URL for direct upload
|
|
33
|
+
* - key: S3 object key (needed to create media record after upload)
|
|
34
|
+
* - expiresIn: URL expiration time in seconds
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* POST /media/upload-url
|
|
38
|
+
* Body: { "filename": "photo.jpg", "contentType": "image/jpeg", "size": 1024000 }
|
|
39
|
+
*/
|
|
40
|
+
export declare const getUploadUrl: APIGatewayProxyHandler;
|
|
41
|
+
/**
|
|
42
|
+
* POST /media - Create a media record after successful S3 upload.
|
|
43
|
+
*
|
|
44
|
+
* Request body:
|
|
45
|
+
* - id: Media ID from upload-url response
|
|
46
|
+
* - key: S3 object key from upload-url response
|
|
47
|
+
* - filename: Original filename
|
|
48
|
+
* - mimeType: MIME type of the file
|
|
49
|
+
* - size: File size in bytes
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* POST /media
|
|
53
|
+
* Body: { "id": "abc123", "key": "uploads/2024-01/abc123.jpg", "filename": "photo.jpg", "mimeType": "image/jpeg", "size": 1024000 }
|
|
54
|
+
*/
|
|
55
|
+
export declare const create: APIGatewayProxyHandler;
|
|
56
|
+
/**
|
|
57
|
+
* GET /media - List media items with pagination and filtering.
|
|
58
|
+
*
|
|
59
|
+
* Query parameters:
|
|
60
|
+
* - limit: Maximum items per page (default: 20)
|
|
61
|
+
* - cursor: Pagination cursor for next page
|
|
62
|
+
* - type: Filter by media type (image, video, audio, document)
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* GET /media?limit=10&type=image
|
|
66
|
+
*/
|
|
67
|
+
export declare const list: APIGatewayProxyHandler;
|
|
68
|
+
/**
|
|
69
|
+
* GET /media/:id - Get a single media item.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* GET /media/abc123
|
|
73
|
+
*/
|
|
74
|
+
export declare const get: APIGatewayProxyHandler;
|
|
75
|
+
/**
|
|
76
|
+
* DELETE /media/:id - Delete a media item from S3 and DynamoDB.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* DELETE /media/abc123
|
|
80
|
+
*/
|
|
81
|
+
export declare const remove: APIGatewayProxyHandler;
|
|
82
|
+
/**
|
|
83
|
+
* Helper to get MIME type filter for type category.
|
|
84
|
+
*/
|
|
85
|
+
export declare function getTypeFilter(type: string): string[];
|
|
86
|
+
//# sourceMappingURL=media.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.d.ts","sourceRoot":"","sources":["../../src/handlers/media.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAyDzD;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAuCD;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,YAAY,EAAE,sBAqD1B,CAAC;AAEF;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,MAAM,EAAE,sBAwCpB,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,IAAI,EAAE,sBAsBlB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,GAAG,EAAE,sBAWjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,MAAM,EAAE,sBAwBpB,CAAC;AAEF;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAapD"}
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Media upload and management handler template.
|
|
4
|
+
*
|
|
5
|
+
* This template demonstrates the standard patterns for building
|
|
6
|
+
* media upload endpoints using presigned URLs for direct S3 uploads.
|
|
7
|
+
*
|
|
8
|
+
* @packageDocumentation
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.remove = exports.get = exports.list = exports.create = exports.getUploadUrl = void 0;
|
|
12
|
+
exports.getTypeFilter = getTypeFilter;
|
|
13
|
+
const client_s3_1 = require("@aws-sdk/client-s3");
|
|
14
|
+
const s3_request_presigner_1 = require("@aws-sdk/s3-request-presigner");
|
|
15
|
+
const response_1 = require("../lib/response");
|
|
16
|
+
const request_1 = require("../lib/request");
|
|
17
|
+
/**
|
|
18
|
+
* Allowed media MIME types.
|
|
19
|
+
*/
|
|
20
|
+
const ALLOWED_MIME_TYPES = [
|
|
21
|
+
// Images
|
|
22
|
+
'image/jpeg',
|
|
23
|
+
'image/png',
|
|
24
|
+
'image/gif',
|
|
25
|
+
'image/webp',
|
|
26
|
+
'image/svg+xml',
|
|
27
|
+
// Documents
|
|
28
|
+
'application/pdf',
|
|
29
|
+
// Video
|
|
30
|
+
'video/mp4',
|
|
31
|
+
'video/webm',
|
|
32
|
+
// Audio
|
|
33
|
+
'audio/mpeg',
|
|
34
|
+
'audio/wav',
|
|
35
|
+
'audio/ogg',
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Maximum file size in bytes (50MB default).
|
|
39
|
+
*/
|
|
40
|
+
const MAX_FILE_SIZE = parseInt(process.env.MAX_FILE_SIZE || '52428800', 10);
|
|
41
|
+
/**
|
|
42
|
+
* S3 bucket name for media storage.
|
|
43
|
+
*/
|
|
44
|
+
const BUCKET_NAME = process.env.MEDIA_BUCKET_NAME || '';
|
|
45
|
+
/**
|
|
46
|
+
* Base URL for media access (CloudFront or S3).
|
|
47
|
+
*/
|
|
48
|
+
const MEDIA_BASE_URL = process.env.MEDIA_BASE_URL || '';
|
|
49
|
+
/**
|
|
50
|
+
* Presigned URL expiration time in seconds.
|
|
51
|
+
*/
|
|
52
|
+
const PRESIGNED_URL_EXPIRES_IN = 300; // 5 minutes
|
|
53
|
+
/**
|
|
54
|
+
* S3 client instance.
|
|
55
|
+
*/
|
|
56
|
+
const s3Client = new client_s3_1.S3Client({});
|
|
57
|
+
/**
|
|
58
|
+
* Generates a unique ID for media items.
|
|
59
|
+
* Uses a combination of timestamp and random string for uniqueness.
|
|
60
|
+
*/
|
|
61
|
+
function generateId() {
|
|
62
|
+
const timestamp = Date.now().toString(36);
|
|
63
|
+
const randomPart = Math.random().toString(36).substring(2, 10);
|
|
64
|
+
return `${timestamp}${randomPart}`;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Extracts file extension from MIME type.
|
|
68
|
+
*/
|
|
69
|
+
function getExtensionFromMimeType(mimeType) {
|
|
70
|
+
const mimeToExt = {
|
|
71
|
+
'image/jpeg': 'jpg',
|
|
72
|
+
'image/png': 'png',
|
|
73
|
+
'image/gif': 'gif',
|
|
74
|
+
'image/webp': 'webp',
|
|
75
|
+
'image/svg+xml': 'svg',
|
|
76
|
+
'application/pdf': 'pdf',
|
|
77
|
+
'video/mp4': 'mp4',
|
|
78
|
+
'video/webm': 'webm',
|
|
79
|
+
'audio/mpeg': 'mp3',
|
|
80
|
+
'audio/wav': 'wav',
|
|
81
|
+
'audio/ogg': 'ogg',
|
|
82
|
+
};
|
|
83
|
+
return mimeToExt[mimeType] || 'bin';
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Validates the content type is allowed.
|
|
87
|
+
*/
|
|
88
|
+
function isValidContentType(contentType) {
|
|
89
|
+
return ALLOWED_MIME_TYPES.includes(contentType);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* POST /media/upload-url - Get a presigned URL for direct S3 upload.
|
|
93
|
+
*
|
|
94
|
+
* Request body:
|
|
95
|
+
* - filename: Original filename
|
|
96
|
+
* - contentType: MIME type of the file
|
|
97
|
+
* - size: File size in bytes
|
|
98
|
+
*
|
|
99
|
+
* Response:
|
|
100
|
+
* - uploadUrl: Presigned PUT URL for direct upload
|
|
101
|
+
* - key: S3 object key (needed to create media record after upload)
|
|
102
|
+
* - expiresIn: URL expiration time in seconds
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* POST /media/upload-url
|
|
106
|
+
* Body: { "filename": "photo.jpg", "contentType": "image/jpeg", "size": 1024000 }
|
|
107
|
+
*/
|
|
108
|
+
const getUploadUrl = async (event) => {
|
|
109
|
+
const body = (0, request_1.parseBody)(event);
|
|
110
|
+
// Validate required fields
|
|
111
|
+
if (!body.filename || !body.contentType || !body.size) {
|
|
112
|
+
return (0, response_1.badRequest)('Missing required fields: filename, contentType, size');
|
|
113
|
+
}
|
|
114
|
+
// Validate content type
|
|
115
|
+
if (!isValidContentType(body.contentType)) {
|
|
116
|
+
return (0, response_1.badRequest)(`Invalid content type: ${body.contentType}. Allowed types: ${ALLOWED_MIME_TYPES.join(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
// Validate file size
|
|
119
|
+
if (body.size > MAX_FILE_SIZE) {
|
|
120
|
+
return (0, response_1.badRequest)(`File size ${body.size} exceeds maximum allowed size of ${MAX_FILE_SIZE} bytes`);
|
|
121
|
+
}
|
|
122
|
+
// Generate unique key with date-based path
|
|
123
|
+
const id = generateId();
|
|
124
|
+
const ext = getExtensionFromMimeType(body.contentType);
|
|
125
|
+
const datePath = new Date().toISOString().slice(0, 7); // YYYY-MM
|
|
126
|
+
const key = `uploads/${datePath}/${id}.${ext}`;
|
|
127
|
+
// Generate presigned URL
|
|
128
|
+
const command = new client_s3_1.PutObjectCommand({
|
|
129
|
+
Bucket: BUCKET_NAME,
|
|
130
|
+
Key: key,
|
|
131
|
+
ContentType: body.contentType,
|
|
132
|
+
ContentLength: body.size,
|
|
133
|
+
Metadata: {
|
|
134
|
+
'original-filename': encodeURIComponent(body.filename),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
const uploadUrl = await (0, s3_request_presigner_1.getSignedUrl)(s3Client, command, {
|
|
138
|
+
expiresIn: PRESIGNED_URL_EXPIRES_IN,
|
|
139
|
+
});
|
|
140
|
+
return (0, response_1.success)({
|
|
141
|
+
uploadUrl,
|
|
142
|
+
key,
|
|
143
|
+
id,
|
|
144
|
+
expiresIn: PRESIGNED_URL_EXPIRES_IN,
|
|
145
|
+
});
|
|
146
|
+
};
|
|
147
|
+
exports.getUploadUrl = getUploadUrl;
|
|
148
|
+
/**
|
|
149
|
+
* POST /media - Create a media record after successful S3 upload.
|
|
150
|
+
*
|
|
151
|
+
* Request body:
|
|
152
|
+
* - id: Media ID from upload-url response
|
|
153
|
+
* - key: S3 object key from upload-url response
|
|
154
|
+
* - filename: Original filename
|
|
155
|
+
* - mimeType: MIME type of the file
|
|
156
|
+
* - size: File size in bytes
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
* POST /media
|
|
160
|
+
* Body: { "id": "abc123", "key": "uploads/2024-01/abc123.jpg", "filename": "photo.jpg", "mimeType": "image/jpeg", "size": 1024000 }
|
|
161
|
+
*/
|
|
162
|
+
const create = async (event) => {
|
|
163
|
+
const body = (0, request_1.parseBody)(event);
|
|
164
|
+
// Validate required fields
|
|
165
|
+
if (!body.id || !body.key || !body.filename || !body.mimeType || !body.size) {
|
|
166
|
+
return (0, response_1.badRequest)('Missing required fields: id, key, filename, mimeType, size');
|
|
167
|
+
}
|
|
168
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
169
|
+
// const media = await db.create('MEDIA', {
|
|
170
|
+
// id: body.id,
|
|
171
|
+
// key: body.key,
|
|
172
|
+
// filename: body.filename,
|
|
173
|
+
// mimeType: body.mimeType,
|
|
174
|
+
// size: body.size,
|
|
175
|
+
// uploadedAt: new Date().toISOString(),
|
|
176
|
+
// });
|
|
177
|
+
// Construct the media URL
|
|
178
|
+
const url = MEDIA_BASE_URL
|
|
179
|
+
? `${MEDIA_BASE_URL}/${body.key}`
|
|
180
|
+
: `https://${BUCKET_NAME}.s3.amazonaws.com/${body.key}`;
|
|
181
|
+
const media = {
|
|
182
|
+
id: body.id,
|
|
183
|
+
key: body.key,
|
|
184
|
+
filename: body.filename,
|
|
185
|
+
mimeType: body.mimeType,
|
|
186
|
+
size: body.size,
|
|
187
|
+
url,
|
|
188
|
+
uploadedAt: new Date().toISOString(),
|
|
189
|
+
};
|
|
190
|
+
return (0, response_1.created)(media);
|
|
191
|
+
};
|
|
192
|
+
exports.create = create;
|
|
193
|
+
/**
|
|
194
|
+
* GET /media - List media items with pagination and filtering.
|
|
195
|
+
*
|
|
196
|
+
* Query parameters:
|
|
197
|
+
* - limit: Maximum items per page (default: 20)
|
|
198
|
+
* - cursor: Pagination cursor for next page
|
|
199
|
+
* - type: Filter by media type (image, video, audio, document)
|
|
200
|
+
*
|
|
201
|
+
* @example
|
|
202
|
+
* GET /media?limit=10&type=image
|
|
203
|
+
*/
|
|
204
|
+
const list = async (event) => {
|
|
205
|
+
const _limit = parseInt((0, request_1.getQueryParam)(event, 'limit', '20') ?? '20', 10);
|
|
206
|
+
const _cursor = (0, request_1.getQueryParam)(event, 'cursor');
|
|
207
|
+
const _type = (0, request_1.getQueryParam)(event, 'type');
|
|
208
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
209
|
+
// const typeFilter = type ? getTypeFilter(type) : undefined;
|
|
210
|
+
// const result = await db.query({
|
|
211
|
+
// limit,
|
|
212
|
+
// cursor,
|
|
213
|
+
// indexName: 'GSI2',
|
|
214
|
+
// filter: typeFilter,
|
|
215
|
+
// });
|
|
216
|
+
const items = [];
|
|
217
|
+
const response = {
|
|
218
|
+
items,
|
|
219
|
+
nextCursor: undefined,
|
|
220
|
+
total: 0,
|
|
221
|
+
};
|
|
222
|
+
return (0, response_1.success)(response);
|
|
223
|
+
};
|
|
224
|
+
exports.list = list;
|
|
225
|
+
/**
|
|
226
|
+
* GET /media/:id - Get a single media item.
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* GET /media/abc123
|
|
230
|
+
*/
|
|
231
|
+
const get = async (event) => {
|
|
232
|
+
const id = (0, request_1.requirePathParam)(event, 'id');
|
|
233
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
234
|
+
// const media = await db.get('MEDIA', id);
|
|
235
|
+
// if (!media) {
|
|
236
|
+
// return notFound(`Media with id '${id}' not found`);
|
|
237
|
+
// }
|
|
238
|
+
// return success(media);
|
|
239
|
+
return (0, response_1.notFound)(`Media with id '${id}' not found`);
|
|
240
|
+
};
|
|
241
|
+
exports.get = get;
|
|
242
|
+
/**
|
|
243
|
+
* DELETE /media/:id - Delete a media item from S3 and DynamoDB.
|
|
244
|
+
*
|
|
245
|
+
* @example
|
|
246
|
+
* DELETE /media/abc123
|
|
247
|
+
*/
|
|
248
|
+
const remove = async (event) => {
|
|
249
|
+
const id = (0, request_1.requirePathParam)(event, 'id');
|
|
250
|
+
// TODO: Implement with DynamoDB in Phase 3
|
|
251
|
+
// const media = await db.get('MEDIA', id);
|
|
252
|
+
// if (!media) {
|
|
253
|
+
// return notFound(`Media with id '${id}' not found`);
|
|
254
|
+
// }
|
|
255
|
+
// For now, we'll demonstrate the S3 deletion pattern
|
|
256
|
+
// In production, you would:
|
|
257
|
+
// 1. Get the media record from DynamoDB
|
|
258
|
+
// 2. Delete from S3
|
|
259
|
+
// 3. Delete from DynamoDB
|
|
260
|
+
// Example S3 deletion (commented out until DynamoDB is implemented)
|
|
261
|
+
// const deleteCommand = new DeleteObjectCommand({
|
|
262
|
+
// Bucket: BUCKET_NAME,
|
|
263
|
+
// Key: media.key,
|
|
264
|
+
// });
|
|
265
|
+
// await s3Client.send(deleteCommand);
|
|
266
|
+
// await db.delete('MEDIA', id);
|
|
267
|
+
return (0, response_1.success)({ deleted: true, id });
|
|
268
|
+
};
|
|
269
|
+
exports.remove = remove;
|
|
270
|
+
/**
|
|
271
|
+
* Helper to get MIME type filter for type category.
|
|
272
|
+
*/
|
|
273
|
+
function getTypeFilter(type) {
|
|
274
|
+
switch (type) {
|
|
275
|
+
case 'image':
|
|
276
|
+
return ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
|
|
277
|
+
case 'video':
|
|
278
|
+
return ['video/mp4', 'video/webm'];
|
|
279
|
+
case 'audio':
|
|
280
|
+
return ['audio/mpeg', 'audio/wav', 'audio/ogg'];
|
|
281
|
+
case 'document':
|
|
282
|
+
return ['application/pdf'];
|
|
283
|
+
default:
|
|
284
|
+
return [];
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
//# sourceMappingURL=media.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"media.js","sourceRoot":"","sources":["../../src/handlers/media.ts"],"names":[],"mappings":"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"}
|