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.
- geek_cafe_saas_sdk/__init__.py +2 -2
- geek_cafe_saas_sdk/domains/files/handlers/README.md +446 -0
- geek_cafe_saas_sdk/domains/files/handlers/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/files/handlers/files/create/app.py +121 -0
- geek_cafe_saas_sdk/domains/files/handlers/files/download/app.py +80 -0
- geek_cafe_saas_sdk/domains/files/handlers/files/get/app.py +62 -0
- geek_cafe_saas_sdk/domains/files/handlers/files/list/app.py +72 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/create_derived/app.py +99 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/create_main/app.py +104 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/download_bundle/app.py +99 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/get_lineage/app.py +68 -0
- geek_cafe_saas_sdk/domains/files/handlers/lineage/prepare_bundle/app.py +76 -0
- geek_cafe_saas_sdk/domains/files/models/__init__.py +17 -0
- geek_cafe_saas_sdk/domains/files/models/directory.py +42 -6
- geek_cafe_saas_sdk/domains/files/models/file.py +158 -16
- geek_cafe_saas_sdk/domains/files/models/file_share.py +33 -0
- geek_cafe_saas_sdk/domains/files/models/file_version.py +24 -0
- geek_cafe_saas_sdk/domains/files/services/__init__.py +21 -0
- geek_cafe_saas_sdk/domains/files/services/directory_service.py +54 -135
- geek_cafe_saas_sdk/domains/files/services/file_lineage_service.py +487 -0
- geek_cafe_saas_sdk/domains/files/services/file_share_service.py +37 -120
- geek_cafe_saas_sdk/domains/files/services/file_system_service.py +67 -103
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py +44 -124
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +55 -7
- geek_cafe_saas_sdk/domains/notifications/__init__.py +18 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/__init__.py +1 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/create_webhook/app.py +73 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/get/app.py +40 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/get_preferences/app.py +34 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/list/app.py +43 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/list_webhooks/app.py +40 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/mark_read/app.py +40 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/send/app.py +83 -0
- geek_cafe_saas_sdk/domains/notifications/handlers/update_preferences/app.py +45 -0
- geek_cafe_saas_sdk/domains/notifications/models/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/notifications/models/notification.py +717 -0
- geek_cafe_saas_sdk/domains/notifications/models/notification_preference.py +365 -0
- geek_cafe_saas_sdk/domains/notifications/models/webhook_subscription.py +339 -0
- geek_cafe_saas_sdk/domains/notifications/services/__init__.py +10 -0
- geek_cafe_saas_sdk/domains/notifications/services/notification_service.py +576 -0
- geek_cafe_saas_sdk/domains/payments/__init__.py +16 -0
- geek_cafe_saas_sdk/domains/payments/handlers/README.md +334 -0
- geek_cafe_saas_sdk/domains/payments/handlers/__init__.py +6 -0
- geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/create/app.py +105 -0
- geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/get/app.py +60 -0
- geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/update/app.py +97 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/create/app.py +97 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/get/app.py +60 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payments/get/app.py +60 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payments/list/app.py +68 -0
- geek_cafe_saas_sdk/domains/payments/handlers/payments/record/app.py +118 -0
- geek_cafe_saas_sdk/domains/payments/handlers/refunds/create/app.py +89 -0
- geek_cafe_saas_sdk/domains/payments/handlers/refunds/get/app.py +60 -0
- geek_cafe_saas_sdk/domains/payments/models/__init__.py +17 -0
- geek_cafe_saas_sdk/domains/payments/models/billing_account.py +521 -0
- geek_cafe_saas_sdk/domains/payments/models/payment.py +639 -0
- geek_cafe_saas_sdk/domains/payments/models/payment_intent_ref.py +539 -0
- geek_cafe_saas_sdk/domains/payments/models/refund.py +404 -0
- geek_cafe_saas_sdk/domains/payments/services/__init__.py +11 -0
- geek_cafe_saas_sdk/domains/payments/services/payment_service.py +405 -0
- geek_cafe_saas_sdk/domains/subscriptions/__init__.py +19 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/README.md +408 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/__init__.py +1 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/create/app.py +81 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/get/app.py +48 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/list/app.py +54 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/update/app.py +54 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/create/app.py +83 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/get/app.py +47 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/validate/app.py +62 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/create/app.py +82 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/get/app.py +48 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/list/app.py +66 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/update/app.py +54 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/aggregate/app.py +72 -0
- geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/record/app.py +89 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/__init__.py +13 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/addon.py +604 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/discount.py +492 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/plan.py +569 -0
- geek_cafe_saas_sdk/domains/subscriptions/models/usage_record.py +300 -0
- geek_cafe_saas_sdk/domains/subscriptions/services/__init__.py +10 -0
- geek_cafe_saas_sdk/domains/subscriptions/services/subscription_manager_service.py +694 -0
- geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +123 -1
- geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +213 -0
- geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +7 -0
- geek_cafe_saas_sdk/services/database_service.py +10 -6
- geek_cafe_saas_sdk/utilities/cognito_utility.py +16 -26
- geek_cafe_saas_sdk/utilities/environment_variables.py +16 -0
- geek_cafe_saas_sdk/utilities/logging_utility.py +77 -0
- {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/METADATA +11 -11
- {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/RECORD +94 -23
- {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/WHEEL +0 -0
- {geek_cafe_saas_sdk-0.6.0.dist-info → geek_cafe_saas_sdk-0.7.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -109,27 +109,8 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
109
109
|
share.access_count = 0
|
|
110
110
|
|
|
111
111
|
# Save to DynamoDB
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
item = share.to_dictionary()
|
|
116
|
-
item["pk"] = pk
|
|
117
|
-
item["sk"] = sk
|
|
118
|
-
|
|
119
|
-
# GSI1: Shares by file
|
|
120
|
-
item["gsi1_pk"] = f"FILE#{tenant_id}#{file_id}"
|
|
121
|
-
item["gsi1_sk"] = f"USER#{shared_with_user_id}"
|
|
122
|
-
|
|
123
|
-
# GSI2: Shares with user
|
|
124
|
-
item["gsi2_pk"] = f"TENANT#{tenant_id}#USER#{shared_with_user_id}"
|
|
125
|
-
item["gsi2_sk"] = f"FILE#{file_id}#{share.created_utc_ts}"
|
|
126
|
-
|
|
127
|
-
self.dynamodb.save(
|
|
128
|
-
item=item,
|
|
129
|
-
table_name=self.table_name
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
return ServiceResult.success_result(share)
|
|
112
|
+
share.prep_for_save()
|
|
113
|
+
return self._save_model(share)
|
|
133
114
|
|
|
134
115
|
except (ValidationError, AccessDeniedError) as e:
|
|
135
116
|
return ServiceResult.error_result(
|
|
@@ -163,23 +144,12 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
163
144
|
ServiceResult with FileShare model
|
|
164
145
|
"""
|
|
165
146
|
try:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
pk = f"FILE#{tenant_id}#{file_id}"
|
|
170
|
-
sk = f"SHARE#{resource_id}"
|
|
147
|
+
# Use helper method with tenant check
|
|
148
|
+
share = self._get_model_by_id_with_tenant_check(resource_id, FileShare, tenant_id)
|
|
171
149
|
|
|
172
|
-
|
|
173
|
-
table_name=self.table_name,
|
|
174
|
-
key={"pk": pk, "sk": sk}
|
|
175
|
-
)
|
|
176
|
-
|
|
177
|
-
if not result or 'Item' not in result:
|
|
150
|
+
if not share:
|
|
178
151
|
raise NotFoundError(f"Share not found: {resource_id}")
|
|
179
152
|
|
|
180
|
-
share = FileShare()
|
|
181
|
-
share.map(result['Item'])
|
|
182
|
-
|
|
183
153
|
# Access control: user must be sharer or sharee
|
|
184
154
|
if share.shared_by != user_id and share.shared_with_user_id != user_id:
|
|
185
155
|
raise AccessDeniedError("You do not have access to this share")
|
|
@@ -256,25 +226,8 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
256
226
|
share.updated_utc_ts = dt.datetime.now(dt.UTC).timestamp()
|
|
257
227
|
|
|
258
228
|
# Save to DynamoDB
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
item = share.to_dictionary()
|
|
263
|
-
item["pk"] = pk
|
|
264
|
-
item["sk"] = sk
|
|
265
|
-
|
|
266
|
-
# Preserve GSI keys
|
|
267
|
-
item["gsi1_pk"] = f"FILE#{tenant_id}#{file_id}"
|
|
268
|
-
item["gsi1_sk"] = f"USER#{share.shared_with_user_id}"
|
|
269
|
-
item["gsi2_pk"] = f"TENANT#{tenant_id}#USER#{share.shared_with_user_id}"
|
|
270
|
-
item["gsi2_sk"] = f"FILE#{file_id}#{share.created_utc_ts}"
|
|
271
|
-
|
|
272
|
-
self.dynamodb.save(
|
|
273
|
-
item=item,
|
|
274
|
-
table_name=self.table_name
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
return ServiceResult.success_result(share)
|
|
229
|
+
share.prep_for_save()
|
|
230
|
+
return self._save_model(share)
|
|
278
231
|
|
|
279
232
|
except (ValidationError, AccessDeniedError) as e:
|
|
280
233
|
return ServiceResult.error_result(
|
|
@@ -323,23 +276,11 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
323
276
|
share.status = "revoked"
|
|
324
277
|
share.revoked_at = dt.datetime.now(dt.UTC).timestamp()
|
|
325
278
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
item = share.to_dictionary()
|
|
330
|
-
item["pk"] = pk
|
|
331
|
-
item["sk"] = sk
|
|
332
|
-
|
|
333
|
-
# Preserve GSI keys
|
|
334
|
-
item["gsi1_pk"] = f"FILE#{tenant_id}#{file_id}"
|
|
335
|
-
item["gsi1_sk"] = f"USER#{share.shared_with_user_id}"
|
|
336
|
-
item["gsi2_pk"] = f"TENANT#{tenant_id}#USER#{share.shared_with_user_id}"
|
|
337
|
-
item["gsi2_sk"] = f"FILE#{file_id}#{share.created_utc_ts}"
|
|
279
|
+
share.prep_for_save()
|
|
280
|
+
save_result = self._save_model(share)
|
|
338
281
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
table_name=self.table_name
|
|
342
|
-
)
|
|
282
|
+
if not save_result.success:
|
|
283
|
+
return save_result
|
|
343
284
|
|
|
344
285
|
return ServiceResult.success_result(True)
|
|
345
286
|
|
|
@@ -383,21 +324,19 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
383
324
|
error_code=ErrorCode.ACCESS_DENIED
|
|
384
325
|
)
|
|
385
326
|
|
|
386
|
-
#
|
|
387
|
-
|
|
327
|
+
# Use GSI1 to query shares by file
|
|
328
|
+
temp_share = FileShare()
|
|
329
|
+
temp_share.file_id = file_id
|
|
388
330
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
table_name=self.table_name,
|
|
392
|
-
index_name="gsi1",
|
|
393
|
-
limit=limit
|
|
394
|
-
)
|
|
331
|
+
# Query using helper method
|
|
332
|
+
query_result = self._query_by_index(temp_share, "gsi1", limit=limit, ascending=False)
|
|
395
333
|
|
|
334
|
+
if not query_result.success:
|
|
335
|
+
return query_result
|
|
336
|
+
|
|
337
|
+
# Filter results
|
|
396
338
|
shares = []
|
|
397
|
-
for
|
|
398
|
-
share = FileShare()
|
|
399
|
-
share.map(item)
|
|
400
|
-
|
|
339
|
+
for share in query_result.data:
|
|
401
340
|
# Include active and expired shares, exclude revoked
|
|
402
341
|
if share.status != "revoked":
|
|
403
342
|
shares.append(share)
|
|
@@ -429,21 +368,19 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
429
368
|
ServiceResult with list of FileShare models
|
|
430
369
|
"""
|
|
431
370
|
try:
|
|
432
|
-
#
|
|
433
|
-
|
|
371
|
+
# Use GSI2 to query shares by shared_with_user
|
|
372
|
+
temp_share = FileShare()
|
|
373
|
+
temp_share.shared_with_user_id = user_id
|
|
434
374
|
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
)
|
|
375
|
+
# Query using helper method
|
|
376
|
+
query_result = self._query_by_index(temp_share, "gsi2", limit=limit, ascending=False)
|
|
377
|
+
|
|
378
|
+
if not query_result.success:
|
|
379
|
+
return query_result
|
|
441
380
|
|
|
381
|
+
# Filter results
|
|
442
382
|
shares = []
|
|
443
|
-
for
|
|
444
|
-
share = FileShare()
|
|
445
|
-
share.map(item)
|
|
446
|
-
|
|
383
|
+
for share in query_result.data:
|
|
447
384
|
# Only include active, non-expired shares
|
|
448
385
|
if share.is_active:
|
|
449
386
|
shares.append(share)
|
|
@@ -543,20 +480,12 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
543
480
|
def _get_file(self, tenant_id: str, file_id: str, user_id: str) -> ServiceResult[File]:
|
|
544
481
|
"""Get file with access control."""
|
|
545
482
|
try:
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
result = self.dynamodb.get(
|
|
550
|
-
table_name=self.table_name,
|
|
551
|
-
key={"pk": pk, "sk": sk}
|
|
552
|
-
)
|
|
483
|
+
# Use helper method with tenant check
|
|
484
|
+
file = self._get_model_by_id_with_tenant_check(file_id, File, tenant_id)
|
|
553
485
|
|
|
554
|
-
if not
|
|
486
|
+
if not file:
|
|
555
487
|
raise NotFoundError(f"File not found: {file_id}")
|
|
556
488
|
|
|
557
|
-
file = File()
|
|
558
|
-
file.map(result['Item'])
|
|
559
|
-
|
|
560
489
|
if file.owner_id != user_id:
|
|
561
490
|
raise AccessDeniedError("You do not have access to this file")
|
|
562
491
|
|
|
@@ -572,7 +501,7 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
572
501
|
"""Get file without access control (for internal use)."""
|
|
573
502
|
try:
|
|
574
503
|
pk = f"FILE#{tenant_id}#{file_id}"
|
|
575
|
-
sk = "
|
|
504
|
+
sk = "metadata"
|
|
576
505
|
|
|
577
506
|
result = self.dynamodb.get(
|
|
578
507
|
table_name=self.table_name,
|
|
@@ -645,19 +574,7 @@ class FileShareService(DatabaseService[FileShare]):
|
|
|
645
574
|
share.access_count += 1
|
|
646
575
|
share.last_accessed_at = dt.datetime.now(dt.UTC).timestamp()
|
|
647
576
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
item["sk"] = sk
|
|
651
|
-
|
|
652
|
-
# Preserve GSI keys
|
|
653
|
-
item["gsi1_pk"] = result['Item'].get('gsi1_pk')
|
|
654
|
-
item["gsi1_sk"] = result['Item'].get('gsi1_sk')
|
|
655
|
-
item["gsi2_pk"] = result['Item'].get('gsi2_pk')
|
|
656
|
-
item["gsi2_sk"] = result['Item'].get('gsi2_sk')
|
|
657
|
-
|
|
658
|
-
self.dynamodb.save(
|
|
659
|
-
item=item,
|
|
660
|
-
table_name=self.table_name
|
|
661
|
-
)
|
|
577
|
+
share.prep_for_save()
|
|
578
|
+
self._save_model(share)
|
|
662
579
|
except Exception:
|
|
663
580
|
pass # Best effort
|
|
@@ -62,6 +62,12 @@ class FileSystemService(DatabaseService[File]):
|
|
|
62
62
|
versioning_strategy: str = "explicit",
|
|
63
63
|
description: Optional[str] = None,
|
|
64
64
|
tags: Optional[List[str]] = None,
|
|
65
|
+
file_role: Optional[str] = None,
|
|
66
|
+
parent_file_id: Optional[str] = None,
|
|
67
|
+
original_file_id: Optional[str] = None,
|
|
68
|
+
transformation_type: Optional[str] = None,
|
|
69
|
+
transformation_operation: Optional[str] = None,
|
|
70
|
+
transformation_metadata: Optional[Dict[str, Any]] = None,
|
|
65
71
|
**kwargs
|
|
66
72
|
) -> ServiceResult[File]:
|
|
67
73
|
"""
|
|
@@ -77,6 +83,12 @@ class FileSystemService(DatabaseService[File]):
|
|
|
77
83
|
versioning_strategy: "s3_native" or "explicit"
|
|
78
84
|
description: Optional description
|
|
79
85
|
tags: Optional tags
|
|
86
|
+
file_role: Optional lineage role ("standalone", "original", "main", "derived")
|
|
87
|
+
parent_file_id: Optional parent file ID for lineage
|
|
88
|
+
original_file_id: Optional original file ID for lineage
|
|
89
|
+
transformation_type: Optional transformation type ("convert", "clean", "process")
|
|
90
|
+
transformation_operation: Optional operation name
|
|
91
|
+
transformation_metadata: Optional transformation metadata dict
|
|
80
92
|
|
|
81
93
|
Returns:
|
|
82
94
|
ServiceResult with File model
|
|
@@ -109,6 +121,20 @@ class FileSystemService(DatabaseService[File]):
|
|
|
109
121
|
file.tags = tags or []
|
|
110
122
|
file.status = "active"
|
|
111
123
|
|
|
124
|
+
# Set lineage fields if provided
|
|
125
|
+
if file_role:
|
|
126
|
+
file.file_role = file_role
|
|
127
|
+
if parent_file_id:
|
|
128
|
+
file.parent_file_id = parent_file_id
|
|
129
|
+
if original_file_id:
|
|
130
|
+
file.original_file_id = original_file_id
|
|
131
|
+
if transformation_type:
|
|
132
|
+
file.transformation_type = transformation_type
|
|
133
|
+
if transformation_operation:
|
|
134
|
+
file.transformation_operation = transformation_operation
|
|
135
|
+
if transformation_metadata:
|
|
136
|
+
file.transformation_metadata = transformation_metadata
|
|
137
|
+
|
|
112
138
|
# Extract file extension
|
|
113
139
|
file_path = Path(file_name)
|
|
114
140
|
file.file_extension = file_path.suffix if file_path.suffix else None
|
|
@@ -148,28 +174,11 @@ class FileSystemService(DatabaseService[File]):
|
|
|
148
174
|
)
|
|
149
175
|
|
|
150
176
|
# Save metadata to DynamoDB
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
item = file.to_dictionary()
|
|
155
|
-
item["pk"] = pk
|
|
156
|
-
item["sk"] = sk
|
|
157
|
-
|
|
158
|
-
# GSI1: Files by directory
|
|
159
|
-
item["gsi1_pk"] = f"TENANT#{tenant_id}"
|
|
160
|
-
if directory_id:
|
|
161
|
-
item["gsi1_sk"] = f"DIRECTORY#{directory_id}#{file_name}"
|
|
162
|
-
else:
|
|
163
|
-
item["gsi1_sk"] = f"DIRECTORY#ROOT#{file_name}"
|
|
164
|
-
|
|
165
|
-
# GSI2: Files by owner
|
|
166
|
-
item["gsi2_pk"] = f"TENANT#{tenant_id}#USER#{user_id}"
|
|
167
|
-
item["gsi2_sk"] = f"FILE#{file.created_utc_ts}"
|
|
177
|
+
file.prep_for_save()
|
|
178
|
+
save_result = self._save_model(file)
|
|
168
179
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
item=item
|
|
172
|
-
)
|
|
180
|
+
if not save_result.success:
|
|
181
|
+
return save_result
|
|
173
182
|
|
|
174
183
|
return ServiceResult.success_result(file)
|
|
175
184
|
|
|
@@ -203,23 +212,13 @@ class FileSystemService(DatabaseService[File]):
|
|
|
203
212
|
ServiceResult with File model
|
|
204
213
|
"""
|
|
205
214
|
try:
|
|
206
|
-
|
|
207
|
-
|
|
215
|
+
# Use helper method with tenant check
|
|
216
|
+
file = self._get_model_by_id_with_tenant_check(resource_id, File, tenant_id)
|
|
208
217
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
key={"pk": pk, "sk": sk}
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
# Check if file exists first (before checking ownership)
|
|
215
|
-
# DynamoDB.get returns {'Item': {...}} or {'ResponseMetadata': {...}}
|
|
216
|
-
if not result or 'Item' not in result:
|
|
218
|
+
# Check if file exists
|
|
219
|
+
if not file:
|
|
217
220
|
raise NotFoundError(f"File not found: {resource_id}")
|
|
218
221
|
|
|
219
|
-
# Convert to File model
|
|
220
|
-
file = File()
|
|
221
|
-
file.map(result['Item'])
|
|
222
|
-
|
|
223
222
|
# Access control: Check if user is owner or has share access
|
|
224
223
|
if file.owner_id != user_id:
|
|
225
224
|
# TODO: Check FileShare for access
|
|
@@ -278,7 +277,7 @@ class FileSystemService(DatabaseService[File]):
|
|
|
278
277
|
# Apply updates (only allowed fields)
|
|
279
278
|
allowed_fields = [
|
|
280
279
|
"file_name", "description", "tags", "directory_id",
|
|
281
|
-
"status"
|
|
280
|
+
"status", "derived_file_count"
|
|
282
281
|
]
|
|
283
282
|
|
|
284
283
|
for field, value in updates.items():
|
|
@@ -290,27 +289,8 @@ class FileSystemService(DatabaseService[File]):
|
|
|
290
289
|
file.updated_utc_ts = dt.datetime.now(dt.UTC).timestamp()
|
|
291
290
|
|
|
292
291
|
# Save to DynamoDB
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
item = file.to_dictionary()
|
|
297
|
-
item["pk"] = pk
|
|
298
|
-
item["sk"] = sk
|
|
299
|
-
|
|
300
|
-
# Update GSI keys if directory changed
|
|
301
|
-
if "directory_id" in updates:
|
|
302
|
-
item["gsi1_pk"] = f"TENANT#{tenant_id}"
|
|
303
|
-
if updates["directory_id"]:
|
|
304
|
-
item["gsi1_sk"] = f"DIRECTORY#{updates['directory_id']}#{file.file_name}"
|
|
305
|
-
else:
|
|
306
|
-
item["gsi1_sk"] = f"DIRECTORY#ROOT#{file.file_name}"
|
|
307
|
-
|
|
308
|
-
self.dynamodb.save(
|
|
309
|
-
table_name=self.table_name,
|
|
310
|
-
item=item
|
|
311
|
-
)
|
|
312
|
-
|
|
313
|
-
return ServiceResult.success_result(file)
|
|
292
|
+
file.prep_for_save()
|
|
293
|
+
return self._save_model(file)
|
|
314
294
|
|
|
315
295
|
except AccessDeniedError as e:
|
|
316
296
|
return ServiceResult.error_result(
|
|
@@ -371,7 +351,7 @@ class FileSystemService(DatabaseService[File]):
|
|
|
371
351
|
|
|
372
352
|
# Delete from DynamoDB
|
|
373
353
|
pk = f"FILE#{tenant_id}#{resource_id}"
|
|
374
|
-
sk = "
|
|
354
|
+
sk = "metadata"
|
|
375
355
|
|
|
376
356
|
self.dynamodb.delete(
|
|
377
357
|
primary_key={"pk": pk, "sk": sk},
|
|
@@ -383,17 +363,11 @@ class FileSystemService(DatabaseService[File]):
|
|
|
383
363
|
file.status = "deleted"
|
|
384
364
|
file.deleted_utc_ts = dt.datetime.now(dt.UTC).timestamp()
|
|
385
365
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
item = file.to_dictionary()
|
|
390
|
-
item["pk"] = pk
|
|
391
|
-
item["sk"] = sk
|
|
366
|
+
file.prep_for_save()
|
|
367
|
+
save_result = self._save_model(file)
|
|
392
368
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
item=item
|
|
396
|
-
)
|
|
369
|
+
if not save_result.success:
|
|
370
|
+
return save_result
|
|
397
371
|
|
|
398
372
|
return ServiceResult.success_result(True)
|
|
399
373
|
|
|
@@ -481,32 +455,23 @@ class FileSystemService(DatabaseService[File]):
|
|
|
481
455
|
ServiceResult with list of File models
|
|
482
456
|
"""
|
|
483
457
|
try:
|
|
484
|
-
|
|
458
|
+
# Use GSI1 to query files by directory
|
|
459
|
+
temp_file = File()
|
|
460
|
+
temp_file.tenant_id = tenant_id
|
|
461
|
+
temp_file.directory_id = directory_id # None for root files
|
|
485
462
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
# Query GSI1
|
|
492
|
-
results = self.dynamodb.query(
|
|
493
|
-
key=Key('gsi1_pk').eq(gsi1_pk) & Key('gsi1_sk').begins_with(gsi1_sk_prefix),
|
|
494
|
-
table_name=self.table_name,
|
|
495
|
-
index_name="gsi1",
|
|
496
|
-
limit=limit
|
|
497
|
-
)
|
|
463
|
+
# Query using helper method
|
|
464
|
+
query_result = self._query_by_index(temp_file, "gsi1", limit=limit, ascending=True)
|
|
465
|
+
|
|
466
|
+
if not query_result.success:
|
|
467
|
+
return query_result
|
|
498
468
|
|
|
469
|
+
# Filter results
|
|
499
470
|
files = []
|
|
500
|
-
for
|
|
501
|
-
|
|
502
|
-
file.
|
|
503
|
-
|
|
504
|
-
# Filter out deleted files
|
|
505
|
-
if file.status != "deleted":
|
|
506
|
-
# Basic access control: show only owned files or shared files
|
|
507
|
-
# TODO: Check FileShare for shared access
|
|
508
|
-
if file.owner_id == user_id:
|
|
509
|
-
files.append(file)
|
|
471
|
+
for file in query_result.data:
|
|
472
|
+
# Filter out deleted files and apply access control
|
|
473
|
+
if file.status != "deleted" and file.owner_id == user_id:
|
|
474
|
+
files.append(file)
|
|
510
475
|
|
|
511
476
|
return ServiceResult.success_result(files)
|
|
512
477
|
|
|
@@ -541,21 +506,20 @@ class FileSystemService(DatabaseService[File]):
|
|
|
541
506
|
if owner_id != user_id:
|
|
542
507
|
raise AccessDeniedError("You can only list your own files")
|
|
543
508
|
|
|
544
|
-
|
|
509
|
+
# Use GSI2 to query files by owner
|
|
510
|
+
temp_file = File()
|
|
511
|
+
temp_file.tenant_id = tenant_id
|
|
512
|
+
temp_file.owner_id = owner_id
|
|
545
513
|
|
|
546
|
-
# Query
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
limit=limit
|
|
552
|
-
)
|
|
514
|
+
# Query using helper method
|
|
515
|
+
query_result = self._query_by_index(temp_file, "gsi2", limit=limit, ascending=False)
|
|
516
|
+
|
|
517
|
+
if not query_result.success:
|
|
518
|
+
return query_result
|
|
553
519
|
|
|
520
|
+
# Filter results
|
|
554
521
|
files = []
|
|
555
|
-
for
|
|
556
|
-
file = File()
|
|
557
|
-
file.map(item)
|
|
558
|
-
|
|
522
|
+
for file in query_result.data:
|
|
559
523
|
# Filter out deleted files
|
|
560
524
|
if file.status != "deleted":
|
|
561
525
|
files.append(file)
|