geek-cafe-saas-sdk 0.6.0__py3-none-any.whl → 0.7.1__py3-none-any.whl

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.

Potentially problematic release.


This version of geek-cafe-saas-sdk might be problematic. Click here for more details.

Files changed (94) hide show
  1. geek_cafe_saas_sdk/__init__.py +2 -2
  2. geek_cafe_saas_sdk/domains/files/handlers/README.md +446 -0
  3. geek_cafe_saas_sdk/domains/files/handlers/__init__.py +6 -0
  4. geek_cafe_saas_sdk/domains/files/handlers/files/create/app.py +121 -0
  5. geek_cafe_saas_sdk/domains/files/handlers/files/download/app.py +80 -0
  6. geek_cafe_saas_sdk/domains/files/handlers/files/get/app.py +62 -0
  7. geek_cafe_saas_sdk/domains/files/handlers/files/list/app.py +72 -0
  8. geek_cafe_saas_sdk/domains/files/handlers/lineage/create_derived/app.py +99 -0
  9. geek_cafe_saas_sdk/domains/files/handlers/lineage/create_main/app.py +104 -0
  10. geek_cafe_saas_sdk/domains/files/handlers/lineage/download_bundle/app.py +99 -0
  11. geek_cafe_saas_sdk/domains/files/handlers/lineage/get_lineage/app.py +68 -0
  12. geek_cafe_saas_sdk/domains/files/handlers/lineage/prepare_bundle/app.py +76 -0
  13. geek_cafe_saas_sdk/domains/files/models/__init__.py +17 -0
  14. geek_cafe_saas_sdk/domains/files/models/directory.py +42 -6
  15. geek_cafe_saas_sdk/domains/files/models/file.py +158 -16
  16. geek_cafe_saas_sdk/domains/files/models/file_share.py +33 -0
  17. geek_cafe_saas_sdk/domains/files/models/file_version.py +24 -0
  18. geek_cafe_saas_sdk/domains/files/services/__init__.py +21 -0
  19. geek_cafe_saas_sdk/domains/files/services/directory_service.py +54 -135
  20. geek_cafe_saas_sdk/domains/files/services/file_lineage_service.py +487 -0
  21. geek_cafe_saas_sdk/domains/files/services/file_share_service.py +37 -120
  22. geek_cafe_saas_sdk/domains/files/services/file_system_service.py +67 -103
  23. geek_cafe_saas_sdk/domains/files/services/file_version_service.py +44 -124
  24. geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +55 -7
  25. geek_cafe_saas_sdk/domains/notifications/__init__.py +18 -0
  26. geek_cafe_saas_sdk/domains/notifications/handlers/__init__.py +1 -0
  27. geek_cafe_saas_sdk/domains/notifications/handlers/create_webhook/app.py +73 -0
  28. geek_cafe_saas_sdk/domains/notifications/handlers/get/app.py +40 -0
  29. geek_cafe_saas_sdk/domains/notifications/handlers/get_preferences/app.py +34 -0
  30. geek_cafe_saas_sdk/domains/notifications/handlers/list/app.py +43 -0
  31. geek_cafe_saas_sdk/domains/notifications/handlers/list_webhooks/app.py +40 -0
  32. geek_cafe_saas_sdk/domains/notifications/handlers/mark_read/app.py +40 -0
  33. geek_cafe_saas_sdk/domains/notifications/handlers/send/app.py +83 -0
  34. geek_cafe_saas_sdk/domains/notifications/handlers/update_preferences/app.py +45 -0
  35. geek_cafe_saas_sdk/domains/notifications/models/__init__.py +16 -0
  36. geek_cafe_saas_sdk/domains/notifications/models/notification.py +717 -0
  37. geek_cafe_saas_sdk/domains/notifications/models/notification_preference.py +365 -0
  38. geek_cafe_saas_sdk/domains/notifications/models/webhook_subscription.py +339 -0
  39. geek_cafe_saas_sdk/domains/notifications/services/__init__.py +10 -0
  40. geek_cafe_saas_sdk/domains/notifications/services/notification_service.py +576 -0
  41. geek_cafe_saas_sdk/domains/payments/__init__.py +16 -0
  42. geek_cafe_saas_sdk/domains/payments/handlers/README.md +334 -0
  43. geek_cafe_saas_sdk/domains/payments/handlers/__init__.py +6 -0
  44. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/create/app.py +105 -0
  45. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/get/app.py +60 -0
  46. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/update/app.py +97 -0
  47. geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/create/app.py +97 -0
  48. geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/get/app.py +60 -0
  49. geek_cafe_saas_sdk/domains/payments/handlers/payments/get/app.py +60 -0
  50. geek_cafe_saas_sdk/domains/payments/handlers/payments/list/app.py +68 -0
  51. geek_cafe_saas_sdk/domains/payments/handlers/payments/record/app.py +118 -0
  52. geek_cafe_saas_sdk/domains/payments/handlers/refunds/create/app.py +89 -0
  53. geek_cafe_saas_sdk/domains/payments/handlers/refunds/get/app.py +60 -0
  54. geek_cafe_saas_sdk/domains/payments/models/__init__.py +17 -0
  55. geek_cafe_saas_sdk/domains/payments/models/billing_account.py +521 -0
  56. geek_cafe_saas_sdk/domains/payments/models/payment.py +639 -0
  57. geek_cafe_saas_sdk/domains/payments/models/payment_intent_ref.py +539 -0
  58. geek_cafe_saas_sdk/domains/payments/models/refund.py +404 -0
  59. geek_cafe_saas_sdk/domains/payments/services/__init__.py +11 -0
  60. geek_cafe_saas_sdk/domains/payments/services/payment_service.py +405 -0
  61. geek_cafe_saas_sdk/domains/subscriptions/__init__.py +19 -0
  62. geek_cafe_saas_sdk/domains/subscriptions/handlers/README.md +408 -0
  63. geek_cafe_saas_sdk/domains/subscriptions/handlers/__init__.py +1 -0
  64. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/create/app.py +81 -0
  65. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/get/app.py +48 -0
  66. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/list/app.py +54 -0
  67. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/update/app.py +54 -0
  68. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/create/app.py +83 -0
  69. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/get/app.py +47 -0
  70. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/validate/app.py +62 -0
  71. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/create/app.py +82 -0
  72. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/get/app.py +48 -0
  73. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/list/app.py +66 -0
  74. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/update/app.py +54 -0
  75. geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/aggregate/app.py +72 -0
  76. geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/record/app.py +89 -0
  77. geek_cafe_saas_sdk/domains/subscriptions/models/__init__.py +13 -0
  78. geek_cafe_saas_sdk/domains/subscriptions/models/addon.py +604 -0
  79. geek_cafe_saas_sdk/domains/subscriptions/models/discount.py +492 -0
  80. geek_cafe_saas_sdk/domains/subscriptions/models/plan.py +569 -0
  81. geek_cafe_saas_sdk/domains/subscriptions/models/usage_record.py +300 -0
  82. geek_cafe_saas_sdk/domains/subscriptions/services/__init__.py +10 -0
  83. geek_cafe_saas_sdk/domains/subscriptions/services/subscription_manager_service.py +694 -0
  84. geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +123 -1
  85. geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +213 -0
  86. geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +7 -0
  87. geek_cafe_saas_sdk/services/database_service.py +10 -6
  88. geek_cafe_saas_sdk/utilities/cognito_utility.py +16 -26
  89. geek_cafe_saas_sdk/utilities/environment_variables.py +16 -0
  90. geek_cafe_saas_sdk/utilities/logging_utility.py +77 -0
  91. {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/METADATA +11 -11
  92. {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/RECORD +94 -23
  93. {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/WHEEL +0 -0
  94. {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,9 +1,9 @@
1
1
  """
2
2
  Geek Cafe Services - Base Reusable Services for SaaS
3
3
 
4
- Version 0.2.0 adds Lambda Handler Wrappers for reducing boilerplate code.
4
+ Version 0.6.0 adds File System Service
5
5
  """
6
- __version__ = "0.6.0"
6
+ __version__ = "0.7.1"
7
7
 
8
8
  # Import main modules for easier access
9
9
  from . import services
@@ -0,0 +1,446 @@
1
+ # File System Lambda Handlers
2
+
3
+ Lambda handlers for the file system with lineage tracking support.
4
+
5
+ ---
6
+
7
+ ## Handler Structure
8
+
9
+ ```
10
+ handlers/
11
+ ├── files/ # Basic file operations
12
+ │ ├── create/ # Upload file
13
+ │ ├── get/ # Get file metadata
14
+ │ ├── download/ # Download file content
15
+ │ └── list/ # List files
16
+
17
+ └── lineage/ # Lineage operations
18
+ ├── get_lineage/ # Get file lineage
19
+ ├── create_main/ # Create main file from original
20
+ ├── create_derived/ # Create derived file from main
21
+ ├── prepare_bundle/ # Prepare lineage bundle (metadata only)
22
+ └── download_bundle/# Download complete bundle (with content)
23
+ ```
24
+
25
+ ---
26
+
27
+ ## File Operations
28
+
29
+ ### POST /files - Upload File
30
+
31
+ **Handler:** `files/create/app.py`
32
+
33
+ **Body:**
34
+ ```json
35
+ {
36
+ "fileName": "document.pdf",
37
+ "fileData": "base64_encoded_content",
38
+ "mimeType": "application/pdf",
39
+ "directoryId": "dir-123",
40
+ "versioningStrategy": "explicit",
41
+ "description": "Q1 Report",
42
+ "tags": ["report", "2024"],
43
+
44
+ // Optional lineage fields:
45
+ "fileRole": "original",
46
+ "parentFileId": "file-parent",
47
+ "originalFileId": "file-original",
48
+ "transformationType": "convert",
49
+ "transformationOperation": "xls_to_csv",
50
+ "transformationMetadata": {
51
+ "source_format": "xls",
52
+ "target_format": "csv"
53
+ }
54
+ }
55
+ ```
56
+
57
+ **Response:** `201` with file metadata
58
+
59
+ ---
60
+
61
+ ### GET /files/{fileId} - Get File Metadata
62
+
63
+ **Handler:** `files/get/app.py`
64
+
65
+ **Path Parameters:**
66
+ - `fileId` - File ID
67
+
68
+ **Response:** `200` with file metadata including lineage fields
69
+
70
+ ---
71
+
72
+ ### GET /files/{fileId}/download - Download File
73
+
74
+ **Handler:** `files/download/app.py`
75
+
76
+ **Path Parameters:**
77
+ - `fileId` - File ID
78
+
79
+ **Response:** `200` with:
80
+ ```json
81
+ {
82
+ "file_id": "file-123",
83
+ "file_name": "document.pdf",
84
+ "mime_type": "application/pdf",
85
+ "file_size": 12345,
86
+ "file_data": "base64_encoded_content",
87
+ "content_type": "application/pdf"
88
+ }
89
+ ```
90
+
91
+ ---
92
+
93
+ ### GET /files - List Files
94
+
95
+ **Handler:** `files/list/app.py`
96
+
97
+ **Query Parameters:**
98
+ - `directoryId` (optional) - Filter by directory
99
+ - `ownerId` (optional) - Filter by owner (defaults to current user)
100
+ - `limit` (optional) - Max results (default: 100)
101
+
102
+ **Response:** `200` with array of file objects
103
+
104
+ ---
105
+
106
+ ## Lineage Operations
107
+
108
+ ### GET /files/{fileId}/lineage - Get File Lineage
109
+
110
+ **Handler:** `lineage/get_lineage/app.py`
111
+
112
+ **Path Parameters:**
113
+ - `fileId` - File ID
114
+
115
+ **Response:** `200` with:
116
+ ```json
117
+ {
118
+ "selected": {file object},
119
+ "main": {file object or null},
120
+ "original": {file object or null},
121
+ "allDerived": [{file objects}]
122
+ }
123
+ ```
124
+
125
+ ---
126
+
127
+ ### POST /files/lineage/main - Create Main File
128
+
129
+ **Handler:** `lineage/create_main/app.py`
130
+
131
+ Convert an original file to a main file (e.g., XLS → CSV).
132
+
133
+ **Body:**
134
+ ```json
135
+ {
136
+ "originalFileId": "file-123",
137
+ "fileName": "data.csv",
138
+ "fileData": "base64_encoded_content",
139
+ "mimeType": "text/csv",
140
+ "transformationOperation": "xls_to_csv",
141
+ "transformationMetadata": {
142
+ "source_format": "xls",
143
+ "target_format": "csv",
144
+ "converter_version": "1.0"
145
+ },
146
+ "directoryId": "dir-456"
147
+ }
148
+ ```
149
+
150
+ **Response:** `201` with main file metadata
151
+
152
+ ---
153
+
154
+ ### POST /files/lineage/derived - Create Derived File
155
+
156
+ **Handler:** `lineage/create_derived/app.py`
157
+
158
+ Create a derived file from a main file (e.g., data cleaning).
159
+
160
+ **Body:**
161
+ ```json
162
+ {
163
+ "mainFileId": "file-456",
164
+ "fileName": "data_clean_v1.csv",
165
+ "fileData": "base64_encoded_content",
166
+ "transformationOperation": "data_cleaning_v1",
167
+ "transformationMetadata": {
168
+ "cleaning_version": 1,
169
+ "operations": ["remove_nulls", "normalize_units"],
170
+ "rows_processed": 1000
171
+ },
172
+ "directoryId": "dir-789"
173
+ }
174
+ ```
175
+
176
+ **Response:** `201` with derived file metadata
177
+
178
+ ---
179
+
180
+ ### GET /files/{fileId}/bundle - Prepare Lineage Bundle
181
+
182
+ **Handler:** `lineage/prepare_bundle/app.py`
183
+
184
+ Get bundle metadata without file content (lightweight).
185
+
186
+ **Path Parameters:**
187
+ - `fileId` - File ID
188
+
189
+ **Response:** `200` with:
190
+ ```json
191
+ {
192
+ "selectedFile": {file object},
193
+ "mainFile": {file object},
194
+ "originalFile": {file object},
195
+ "metadata": {
196
+ "selectedFileId": "...",
197
+ "selectedFileName": "...",
198
+ "transformationChain": [
199
+ {"step": 1, "type": "original", "fileId": "...", "fileName": "..."},
200
+ {"step": 2, "type": "convert", "fileId": "...", "fileName": "...", "operation": "..."},
201
+ {"step": 3, "type": "clean", "fileId": "...", "fileName": "...", "operation": "..."}
202
+ ]
203
+ }
204
+ }
205
+ ```
206
+
207
+ ---
208
+
209
+ ### GET /files/{fileId}/bundle/download - Download Lineage Bundle
210
+
211
+ **Handler:** `lineage/download_bundle/app.py`
212
+
213
+ Download complete bundle with file content (heavier).
214
+
215
+ **Path Parameters:**
216
+ - `fileId` - File ID
217
+
218
+ **Response:** `200` with:
219
+ ```json
220
+ {
221
+ "selected": {
222
+ "file": {file object},
223
+ "data": "base64_encoded_content"
224
+ },
225
+ "main": {
226
+ "file": {file object},
227
+ "data": "base64_encoded_content"
228
+ },
229
+ "original": {
230
+ "file": {file object},
231
+ "data": "base64_encoded_content"
232
+ },
233
+ "metadata": {transformation chain}
234
+ }
235
+ ```
236
+
237
+ ---
238
+
239
+ ## API Gateway Configuration
240
+
241
+ ### Example SAM Template
242
+
243
+ ```yaml
244
+ Resources:
245
+ # File Operations
246
+ FileUpload:
247
+ Type: AWS::Serverless::Function
248
+ Properties:
249
+ CodeUri: handlers/files/create/
250
+ Handler: app.handler
251
+ Events:
252
+ ApiEvent:
253
+ Type: Api
254
+ Properties:
255
+ Path: /files
256
+ Method: POST
257
+ Auth:
258
+ Authorizer: CognitoAuthorizer
259
+
260
+ FileGet:
261
+ Type: AWS::Serverless::Function
262
+ Properties:
263
+ CodeUri: handlers/files/get/
264
+ Handler: app.handler
265
+ Events:
266
+ ApiEvent:
267
+ Type: Api
268
+ Properties:
269
+ Path: /files/{fileId}
270
+ Method: GET
271
+ Auth:
272
+ Authorizer: CognitoAuthorizer
273
+
274
+ FileDownload:
275
+ Type: AWS::Serverless::Function
276
+ Properties:
277
+ CodeUri: handlers/files/download/
278
+ Handler: app.handler
279
+ Events:
280
+ ApiEvent:
281
+ Type: Api
282
+ Properties:
283
+ Path: /files/{fileId}/download
284
+ Method: GET
285
+ Auth:
286
+ Authorizer: CognitoAuthorizer
287
+
288
+ FileList:
289
+ Type: AWS::Serverless::Function
290
+ Properties:
291
+ CodeUri: handlers/files/list/
292
+ Handler: app.handler
293
+ Events:
294
+ ApiEvent:
295
+ Type: Api
296
+ Properties:
297
+ Path: /files
298
+ Method: GET
299
+ Auth:
300
+ Authorizer: CognitoAuthorizer
301
+
302
+ # Lineage Operations
303
+ GetLineage:
304
+ Type: AWS::Serverless::Function
305
+ Properties:
306
+ CodeUri: handlers/lineage/get_lineage/
307
+ Handler: app.handler
308
+ Events:
309
+ ApiEvent:
310
+ Type: Api
311
+ Properties:
312
+ Path: /files/{fileId}/lineage
313
+ Method: GET
314
+ Auth:
315
+ Authorizer: CognitoAuthorizer
316
+
317
+ CreateMain:
318
+ Type: AWS::Serverless::Function
319
+ Properties:
320
+ CodeUri: handlers/lineage/create_main/
321
+ Handler: app.handler
322
+ Events:
323
+ ApiEvent:
324
+ Type: Api
325
+ Properties:
326
+ Path: /files/lineage/main
327
+ Method: POST
328
+ Auth:
329
+ Authorizer: CognitoAuthorizer
330
+
331
+ CreateDerived:
332
+ Type: AWS::Serverless::Function
333
+ Properties:
334
+ CodeUri: handlers/lineage/create_derived/
335
+ Handler: app.handler
336
+ Events:
337
+ ApiEvent:
338
+ Type: Api
339
+ Properties:
340
+ Path: /files/lineage/derived
341
+ Method: POST
342
+ Auth:
343
+ Authorizer: CognitoAuthorizer
344
+
345
+ PrepareBundle:
346
+ Type: AWS::Serverless::Function
347
+ Properties:
348
+ CodeUri: handlers/lineage/prepare_bundle/
349
+ Handler: app.handler
350
+ Events:
351
+ ApiEvent:
352
+ Type: Api
353
+ Properties:
354
+ Path: /files/{fileId}/bundle
355
+ Method: GET
356
+ Auth:
357
+ Authorizer: CognitoAuthorizer
358
+
359
+ DownloadBundle:
360
+ Type: AWS::Serverless::Function
361
+ Properties:
362
+ CodeUri: handlers/lineage/download_bundle/
363
+ Handler: app.handler
364
+ Events:
365
+ ApiEvent:
366
+ Type: Api
367
+ Properties:
368
+ Path: /files/{fileId}/bundle/download
369
+ Method: GET
370
+ Auth:
371
+ Authorizer: CognitoAuthorizer
372
+ ```
373
+
374
+ ---
375
+
376
+ ## Authentication
377
+
378
+ All handlers require secure authentication (Cognito JWT tokens).
379
+
380
+ **Required User Context:**
381
+ - `user_id` - User performing the action
382
+ - `tenant_id` - Organization/tenant ID
383
+ - `email` - User email (optional)
384
+
385
+ ---
386
+
387
+ ## Error Responses
388
+
389
+ All handlers return standard error responses:
390
+
391
+ ```json
392
+ {
393
+ "error": {
394
+ "code": "VALIDATION_ERROR",
395
+ "message": "file_name is required",
396
+ "details": {"field": "file_name"}
397
+ }
398
+ }
399
+ ```
400
+
401
+ **Common Error Codes:**
402
+ - `VALIDATION_ERROR` - Invalid input
403
+ - `NOT_FOUND` - File not found
404
+ - `ACCESS_DENIED` - Insufficient permissions
405
+ - `INVALID_FILE_ROLE` - Lineage role mismatch
406
+
407
+ ---
408
+
409
+ ## Testing
410
+
411
+ Each handler can be tested independently:
412
+
413
+ ```python
414
+ from handlers.files.create import app
415
+
416
+ # Mock event
417
+ event = {
418
+ "body": json.dumps({
419
+ "fileName": "test.txt",
420
+ "fileData": base64.b64encode(b"test").decode(),
421
+ "mimeType": "text/plain"
422
+ }),
423
+ "requestContext": {
424
+ "authorizer": {
425
+ "claims": {
426
+ "sub": "user-123",
427
+ "custom:tenant_id": "tenant-456"
428
+ }
429
+ }
430
+ }
431
+ }
432
+
433
+ # Test
434
+ result = app.handler(event, None)
435
+ print(result)
436
+ ```
437
+
438
+ ---
439
+
440
+ ## Next Steps
441
+
442
+ 1. Deploy handlers using SAM/CloudFormation
443
+ 2. Configure API Gateway routes
444
+ 3. Set up Cognito authorizer
445
+ 4. Test each endpoint
446
+ 5. Monitor CloudWatch logs
@@ -0,0 +1,6 @@
1
+ """
2
+ File handlers.
3
+
4
+ Geek Cafe, LLC
5
+ MIT License. See Project Root for the license information.
6
+ """
@@ -0,0 +1,121 @@
1
+ """
2
+ Lambda handler for creating/uploading files.
3
+
4
+ Requires authentication (secure mode).
5
+ """
6
+
7
+ from typing import Dict, Any
8
+ from geek_cafe_saas_sdk.lambda_handlers import create_handler
9
+ from geek_cafe_saas_sdk.domains.files.services import FileSystemService
10
+ import base64
11
+
12
+
13
+ # Factory creates handler (defaults to secure auth)
14
+ handler_wrapper = create_handler(
15
+ service_class=FileSystemService,
16
+ require_body=True,
17
+ convert_case=True
18
+ )
19
+
20
+
21
+ def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
22
+ """
23
+ Upload a new file.
24
+
25
+ Args:
26
+ event: Lambda event from API Gateway
27
+ context: Lambda context
28
+ injected_service: Optional FileSystemService for testing
29
+
30
+ Expected body (camelCase from frontend):
31
+ {
32
+ "fileName": "document.pdf",
33
+ "fileData": "base64_encoded_content",
34
+ "mimeType": "application/pdf",
35
+ "directoryId": "dir-123", # Optional
36
+ "versioningStrategy": "explicit", # Optional: "s3_native" or "explicit"
37
+ "description": "Q1 Report", # Optional
38
+ "tags": ["report", "2024"], # Optional
39
+
40
+ # Optional lineage fields:
41
+ "fileRole": "original", # "standalone", "original", "main", "derived"
42
+ "parentFileId": "file-parent", # For lineage tracking
43
+ "originalFileId": "file-original", # For lineage tracking
44
+ "transformationType": "convert", # "convert", "clean", "process"
45
+ "transformationOperation": "xls_to_csv", # Operation name
46
+ "transformationMetadata": { # Operation details
47
+ "source_format": "xls",
48
+ "target_format": "csv"
49
+ }
50
+ }
51
+
52
+ Returns 201 with created file metadata
53
+ """
54
+ return handler_wrapper.execute(event, context, upload_file, injected_service)
55
+
56
+
57
+ def upload_file(
58
+ event: Dict[str, Any],
59
+ service: FileSystemService,
60
+ user_context: Dict[str, str]
61
+ ) -> Any:
62
+ """
63
+ Business logic for uploading files.
64
+ """
65
+ payload = event["parsed_body"]
66
+
67
+ tenant_id = user_context.get("tenant_id")
68
+ user_id = user_context.get("user_id")
69
+
70
+ # Extract required fields
71
+ file_name = payload.get("file_name")
72
+ file_data_b64 = payload.get("file_data")
73
+ mime_type = payload.get("mime_type")
74
+
75
+ if not file_name:
76
+ raise ValueError("file_name is required")
77
+ if not file_data_b64:
78
+ raise ValueError("file_data is required")
79
+ if not mime_type:
80
+ raise ValueError("mime_type is required")
81
+
82
+ # Decode base64 file data
83
+ try:
84
+ file_data = base64.b64decode(file_data_b64)
85
+ except Exception as e:
86
+ raise ValueError(f"Invalid base64 file_data: {str(e)}")
87
+
88
+ # Extract optional fields
89
+ directory_id = payload.get("directory_id")
90
+ versioning_strategy = payload.get("versioning_strategy", "explicit")
91
+ description = payload.get("description")
92
+ tags = payload.get("tags", [])
93
+
94
+ # Extract lineage fields
95
+ file_role = payload.get("file_role")
96
+ parent_file_id = payload.get("parent_file_id")
97
+ original_file_id = payload.get("original_file_id")
98
+ transformation_type = payload.get("transformation_type")
99
+ transformation_operation = payload.get("transformation_operation")
100
+ transformation_metadata = payload.get("transformation_metadata")
101
+
102
+ # Upload file
103
+ result = service.create(
104
+ tenant_id=tenant_id,
105
+ user_id=user_id,
106
+ file_name=file_name,
107
+ file_data=file_data,
108
+ mime_type=mime_type,
109
+ directory_id=directory_id,
110
+ versioning_strategy=versioning_strategy,
111
+ description=description,
112
+ tags=tags,
113
+ file_role=file_role,
114
+ parent_file_id=parent_file_id,
115
+ original_file_id=original_file_id,
116
+ transformation_type=transformation_type,
117
+ transformation_operation=transformation_operation,
118
+ transformation_metadata=transformation_metadata
119
+ )
120
+
121
+ return result
@@ -0,0 +1,80 @@
1
+ """
2
+ Lambda handler for downloading file content.
3
+
4
+ Requires authentication (secure mode).
5
+ """
6
+
7
+ from typing import Dict, Any
8
+ from geek_cafe_saas_sdk.lambda_handlers import create_handler
9
+ from geek_cafe_saas_sdk.domains.files.services import FileSystemService
10
+ import base64
11
+
12
+
13
+ # Factory creates handler (defaults to secure auth)
14
+ handler_wrapper = create_handler(
15
+ service_class=FileSystemService,
16
+ require_body=False,
17
+ convert_case=True
18
+ )
19
+
20
+
21
+ def handler(event: Dict[str, Any], context: Any, injected_service=None) -> Dict[str, Any]:
22
+ """
23
+ Download file content.
24
+
25
+ Args:
26
+ event: Lambda event from API Gateway
27
+ context: Lambda context
28
+ injected_service: Optional FileSystemService for testing
29
+
30
+ Path parameters:
31
+ fileId: File ID
32
+
33
+ Returns 200 with file content (base64 encoded for binary files)
34
+ """
35
+ return handler_wrapper.execute(event, context, download_file, injected_service)
36
+
37
+
38
+ def download_file(
39
+ event: Dict[str, Any],
40
+ service: FileSystemService,
41
+ user_context: Dict[str, str]
42
+ ) -> Dict[str, Any]:
43
+ """
44
+ Business logic for downloading file content.
45
+ """
46
+ tenant_id = user_context.get("tenant_id")
47
+ user_id = user_context.get("user_id")
48
+
49
+ # Get file ID from path parameters
50
+ path_params = event.get("pathParameters", {})
51
+ file_id = path_params.get("fileId") or path_params.get("id")
52
+
53
+ if not file_id:
54
+ raise ValueError("fileId path parameter is required")
55
+
56
+ # Download file
57
+ result = service.download_file(
58
+ tenant_id=tenant_id,
59
+ file_id=file_id,
60
+ user_id=user_id
61
+ )
62
+
63
+ if result.success:
64
+ file_data = result.data['data']
65
+ file_info = result.data['file']
66
+
67
+ # Encode binary data as base64 for JSON response
68
+ encoded_data = base64.b64encode(file_data).decode('utf-8')
69
+
70
+ # Return with metadata
71
+ return {
72
+ 'file_id': file_info.file_id,
73
+ 'file_name': file_info.file_name,
74
+ 'mime_type': file_info.mime_type,
75
+ 'file_size': file_info.file_size,
76
+ 'file_data': encoded_data,
77
+ 'content_type': file_info.mime_type
78
+ }
79
+
80
+ return result