@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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +204 -0
  3. package/lib/api-project.d.ts +99 -0
  4. package/lib/api-project.d.ts.map +1 -0
  5. package/lib/api-project.js +668 -0
  6. package/lib/api-project.js.map +1 -0
  7. package/lib/handlers/content.d.ts +52 -0
  8. package/lib/handlers/content.d.ts.map +1 -0
  9. package/lib/handlers/content.js +122 -0
  10. package/lib/handlers/content.js.map +1 -0
  11. package/lib/handlers/health.d.ts +43 -0
  12. package/lib/handlers/health.d.ts.map +1 -0
  13. package/lib/handlers/health.js +43 -0
  14. package/lib/handlers/health.js.map +1 -0
  15. package/lib/handlers/media.d.ts +86 -0
  16. package/lib/handlers/media.d.ts.map +1 -0
  17. package/lib/handlers/media.js +287 -0
  18. package/lib/handlers/media.js.map +1 -0
  19. package/lib/index.d.ts +22 -0
  20. package/lib/index.d.ts.map +1 -0
  21. package/lib/index.js +50 -0
  22. package/lib/index.js.map +1 -0
  23. package/lib/lib/errors.d.ts +114 -0
  24. package/lib/lib/errors.d.ts.map +1 -0
  25. package/lib/lib/errors.js +154 -0
  26. package/lib/lib/errors.js.map +1 -0
  27. package/lib/lib/middleware.d.ts +33 -0
  28. package/lib/lib/middleware.d.ts.map +1 -0
  29. package/lib/lib/middleware.js +36 -0
  30. package/lib/lib/middleware.js.map +1 -0
  31. package/lib/lib/request.d.ts +90 -0
  32. package/lib/lib/request.d.ts.map +1 -0
  33. package/lib/lib/request.js +115 -0
  34. package/lib/lib/request.js.map +1 -0
  35. package/lib/lib/response.d.ts +131 -0
  36. package/lib/lib/response.d.ts.map +1 -0
  37. package/lib/lib/response.js +163 -0
  38. package/lib/lib/response.js.map +1 -0
  39. package/lib/middleware/auth.d.ts +57 -0
  40. package/lib/middleware/auth.d.ts.map +1 -0
  41. package/lib/middleware/auth.js +62 -0
  42. package/lib/middleware/auth.js.map +1 -0
  43. package/lib/middleware/cors.d.ts +51 -0
  44. package/lib/middleware/cors.d.ts.map +1 -0
  45. package/lib/middleware/cors.js +62 -0
  46. package/lib/middleware/cors.js.map +1 -0
  47. package/lib/middleware/error-handler.d.ts +38 -0
  48. package/lib/middleware/error-handler.d.ts.map +1 -0
  49. package/lib/middleware/error-handler.js +49 -0
  50. package/lib/middleware/error-handler.js.map +1 -0
  51. package/lib/middleware/index.d.ts +15 -0
  52. package/lib/middleware/index.d.ts.map +1 -0
  53. package/lib/middleware/index.js +49 -0
  54. package/lib/middleware/index.js.map +1 -0
  55. package/lib/middleware/logging.d.ts +48 -0
  56. package/lib/middleware/logging.d.ts.map +1 -0
  57. package/lib/middleware/logging.js +58 -0
  58. package/lib/middleware/logging.js.map +1 -0
  59. package/lib/middleware/validation.d.ts +41 -0
  60. package/lib/middleware/validation.d.ts.map +1 -0
  61. package/lib/middleware/validation.js +76 -0
  62. package/lib/middleware/validation.js.map +1 -0
  63. package/lib/options.d.ts +48 -0
  64. package/lib/options.d.ts.map +1 -0
  65. package/lib/options.js +3 -0
  66. package/lib/options.js.map +1 -0
  67. package/lib/types/api.d.ts +108 -0
  68. package/lib/types/api.d.ts.map +1 -0
  69. package/lib/types/api.js +11 -0
  70. package/lib/types/api.js.map +1 -0
  71. package/lib/types/middleware.d.ts +59 -0
  72. package/lib/types/middleware.d.ts.map +1 -0
  73. package/lib/types/middleware.js +11 -0
  74. package/lib/types/middleware.js.map +1 -0
  75. 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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"}