geek-cafe-saas-sdk 0.7.0__py3-none-any.whl → 0.7.2__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 (79) hide show
  1. geek_cafe_saas_sdk/__init__.py +1 -1
  2. geek_cafe_saas_sdk/domains/files/models/directory.py +42 -6
  3. geek_cafe_saas_sdk/domains/files/models/file.py +40 -4
  4. geek_cafe_saas_sdk/domains/files/models/file_share.py +33 -0
  5. geek_cafe_saas_sdk/domains/files/models/file_version.py +24 -0
  6. geek_cafe_saas_sdk/domains/files/services/directory_service.py +54 -135
  7. geek_cafe_saas_sdk/domains/files/services/file_share_service.py +60 -136
  8. geek_cafe_saas_sdk/domains/files/services/file_system_service.py +43 -104
  9. geek_cafe_saas_sdk/domains/files/services/file_version_service.py +57 -131
  10. geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +55 -7
  11. geek_cafe_saas_sdk/domains/notifications/__init__.py +18 -0
  12. geek_cafe_saas_sdk/domains/notifications/handlers/__init__.py +1 -0
  13. geek_cafe_saas_sdk/domains/notifications/handlers/create_webhook/app.py +73 -0
  14. geek_cafe_saas_sdk/domains/notifications/handlers/get/app.py +40 -0
  15. geek_cafe_saas_sdk/domains/notifications/handlers/get_preferences/app.py +34 -0
  16. geek_cafe_saas_sdk/domains/notifications/handlers/list/app.py +43 -0
  17. geek_cafe_saas_sdk/domains/notifications/handlers/list_webhooks/app.py +40 -0
  18. geek_cafe_saas_sdk/domains/notifications/handlers/mark_read/app.py +40 -0
  19. geek_cafe_saas_sdk/domains/notifications/handlers/send/app.py +83 -0
  20. geek_cafe_saas_sdk/domains/notifications/handlers/update_preferences/app.py +45 -0
  21. geek_cafe_saas_sdk/domains/notifications/models/__init__.py +16 -0
  22. geek_cafe_saas_sdk/domains/notifications/models/notification.py +717 -0
  23. geek_cafe_saas_sdk/domains/notifications/models/notification_preference.py +365 -0
  24. geek_cafe_saas_sdk/domains/notifications/models/webhook_subscription.py +339 -0
  25. geek_cafe_saas_sdk/domains/notifications/services/__init__.py +10 -0
  26. geek_cafe_saas_sdk/domains/notifications/services/notification_service.py +576 -0
  27. geek_cafe_saas_sdk/domains/payments/__init__.py +16 -0
  28. geek_cafe_saas_sdk/domains/payments/handlers/README.md +334 -0
  29. geek_cafe_saas_sdk/domains/payments/handlers/__init__.py +6 -0
  30. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/create/app.py +105 -0
  31. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/get/app.py +60 -0
  32. geek_cafe_saas_sdk/domains/payments/handlers/billing_accounts/update/app.py +97 -0
  33. geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/create/app.py +97 -0
  34. geek_cafe_saas_sdk/domains/payments/handlers/payment_intents/get/app.py +60 -0
  35. geek_cafe_saas_sdk/domains/payments/handlers/payments/get/app.py +60 -0
  36. geek_cafe_saas_sdk/domains/payments/handlers/payments/list/app.py +68 -0
  37. geek_cafe_saas_sdk/domains/payments/handlers/payments/record/app.py +118 -0
  38. geek_cafe_saas_sdk/domains/payments/handlers/refunds/create/app.py +89 -0
  39. geek_cafe_saas_sdk/domains/payments/handlers/refunds/get/app.py +60 -0
  40. geek_cafe_saas_sdk/domains/payments/models/__init__.py +17 -0
  41. geek_cafe_saas_sdk/domains/payments/models/billing_account.py +521 -0
  42. geek_cafe_saas_sdk/domains/payments/models/payment.py +639 -0
  43. geek_cafe_saas_sdk/domains/payments/models/payment_intent_ref.py +539 -0
  44. geek_cafe_saas_sdk/domains/payments/models/refund.py +404 -0
  45. geek_cafe_saas_sdk/domains/payments/services/__init__.py +11 -0
  46. geek_cafe_saas_sdk/domains/payments/services/payment_service.py +405 -0
  47. geek_cafe_saas_sdk/domains/subscriptions/__init__.py +19 -0
  48. geek_cafe_saas_sdk/domains/subscriptions/handlers/README.md +408 -0
  49. geek_cafe_saas_sdk/domains/subscriptions/handlers/__init__.py +1 -0
  50. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/create/app.py +81 -0
  51. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/get/app.py +48 -0
  52. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/list/app.py +54 -0
  53. geek_cafe_saas_sdk/domains/subscriptions/handlers/addons/update/app.py +54 -0
  54. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/create/app.py +83 -0
  55. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/get/app.py +47 -0
  56. geek_cafe_saas_sdk/domains/subscriptions/handlers/discounts/validate/app.py +62 -0
  57. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/create/app.py +82 -0
  58. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/get/app.py +48 -0
  59. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/list/app.py +66 -0
  60. geek_cafe_saas_sdk/domains/subscriptions/handlers/plans/update/app.py +54 -0
  61. geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/aggregate/app.py +72 -0
  62. geek_cafe_saas_sdk/domains/subscriptions/handlers/usage/record/app.py +89 -0
  63. geek_cafe_saas_sdk/domains/subscriptions/models/__init__.py +13 -0
  64. geek_cafe_saas_sdk/domains/subscriptions/models/addon.py +604 -0
  65. geek_cafe_saas_sdk/domains/subscriptions/models/discount.py +492 -0
  66. geek_cafe_saas_sdk/domains/subscriptions/models/plan.py +569 -0
  67. geek_cafe_saas_sdk/domains/subscriptions/models/usage_record.py +300 -0
  68. geek_cafe_saas_sdk/domains/subscriptions/services/__init__.py +10 -0
  69. geek_cafe_saas_sdk/domains/subscriptions/services/subscription_manager_service.py +694 -0
  70. geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +123 -1
  71. geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +213 -0
  72. geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +7 -0
  73. geek_cafe_saas_sdk/services/database_service.py +10 -6
  74. geek_cafe_saas_sdk/utilities/environment_variables.py +16 -0
  75. geek_cafe_saas_sdk/utilities/logging_utility.py +77 -0
  76. {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/METADATA +1 -1
  77. {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/RECORD +79 -20
  78. {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.dist-info}/WHEEL +0 -0
  79. {geek_cafe_saas_sdk-0.7.0.dist-info → geek_cafe_saas_sdk-0.7.2.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
- pk = f"FILE#{tenant_id}#{file_id}"
113
- sk = f"SHARE#{share.share_id}"
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
- if not file_id:
167
- raise ValidationError("file_id is required", "file_id")
168
-
169
- pk = f"FILE#{tenant_id}#{file_id}"
170
- sk = f"SHARE#{resource_id}"
171
-
172
- result = self.dynamodb.get(
173
- table_name=self.table_name,
174
- key={"pk": pk, "sk": sk}
175
- )
147
+ # Use helper method with tenant check
148
+ share = self._get_model_by_id_with_tenant_check(resource_id, FileShare, tenant_id)
176
149
 
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
- pk = f"FILE#{tenant_id}#{file_id}"
260
- sk = f"SHARE#{resource_id}"
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
- pk = f"FILE#{tenant_id}#{file_id}"
327
- sk = f"SHARE#{resource_id}"
328
-
329
- item = share.to_dictionary()
330
- item["pk"] = pk
331
- item["sk"] = sk
279
+ share.prep_for_save()
280
+ save_result = self._save_model(share)
332
281
 
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}"
338
-
339
- self.dynamodb.save(
340
- item=item,
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
- # Query GSI1
387
- gsi1_pk = f"FILE#{tenant_id}#{file_id}"
327
+ # Use GSI1 to query shares by file
328
+ temp_share = FileShare()
329
+ temp_share.file_id = file_id
388
330
 
389
- results = self.dynamodb.query(
390
- key=Key('gsi1_pk').eq(gsi1_pk) & Key('gsi1_sk').begins_with("USER#"),
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 item in results.get('Items', []):
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
- # Query GSI2
433
- gsi2_pk = f"TENANT#{tenant_id}#USER#{user_id}"
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
- results = self.dynamodb.query(
436
- key=Key('gsi2_pk').eq(gsi2_pk) & Key('gsi2_sk').begins_with("FILE#"),
437
- table_name=self.table_name,
438
- index_name="gsi2",
439
- limit=limit
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 item in results.get('Items', []):
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
- pk = f"FILE#{tenant_id}#{file_id}"
547
- sk = "METADATA"
483
+ # Use helper method with tenant check
484
+ file = self._get_model_by_id_with_tenant_check(file_id, File, tenant_id)
548
485
 
549
- result = self.dynamodb.get(
550
- table_name=self.table_name,
551
- key={"pk": pk, "sk": sk}
552
- )
553
-
554
- if not result or 'Item' not in result:
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
 
@@ -571,12 +500,14 @@ class FileShareService(DatabaseService[FileShare]):
571
500
  def _get_file_any_user(self, tenant_id: str, file_id: str) -> ServiceResult[File]:
572
501
  """Get file without access control (for internal use)."""
573
502
  try:
574
- pk = f"FILE#{tenant_id}#{file_id}"
575
- sk = "METADATA"
503
+
504
+ file = File()
505
+ file.id = file_id
506
+ file.tenant_id = tenant_id
576
507
 
577
508
  result = self.dynamodb.get(
578
509
  table_name=self.table_name,
579
- key={"pk": pk, "sk": sk}
510
+ model=file
580
511
  )
581
512
 
582
513
  if not result or 'Item' not in result:
@@ -601,21 +532,21 @@ class FileShareService(DatabaseService[FileShare]):
601
532
  ) -> Optional[FileShare]:
602
533
  """Check if share already exists."""
603
534
  try:
604
- gsi1_pk = f"FILE#{tenant_id}#{file_id}"
605
- gsi1_sk = f"USER#{shared_with_user_id}"
535
+ # Query GSI1 by file_id to get all shares for this file
536
+ temp_share = FileShare()
537
+ temp_share.file_id = file_id
606
538
 
607
- results = self.dynamodb.query(
608
- key=Key('gsi1_pk').eq(gsi1_pk) & Key('gsi1_sk').eq(gsi1_sk),
609
- table_name=self.table_name,
610
- index_name="gsi1",
611
- limit=1
612
- )
539
+ result = self._query_by_index(temp_share, "gsi1", limit=100)
613
540
 
614
- items = results.get('Items', [])
615
- if items:
616
- share = FileShare()
617
- share.map(items[0])
618
- return share
541
+ if not result.success:
542
+ return None
543
+
544
+ # Filter for matching tenant and user (active shares only)
545
+ for share in result.data:
546
+ if (share.tenant_id == tenant_id and
547
+ share.shared_with_user_id == shared_with_user_id and
548
+ share.status == "active"):
549
+ return share
619
550
 
620
551
  return None
621
552
 
@@ -630,12 +561,14 @@ class FileShareService(DatabaseService[FileShare]):
630
561
  ) -> None:
631
562
  """Increment share access count."""
632
563
  try:
633
- pk = f"FILE#{tenant_id}#{file_id}"
634
- sk = f"SHARE#{share_id}"
564
+ share = FileShare()
565
+ share.id = share_id
566
+ share.tenant_id = tenant_id
567
+ share.file_id = file_id
635
568
 
636
569
  result = self.dynamodb.get(
637
570
  table_name=self.table_name,
638
- key={"pk": pk, "sk": sk}
571
+ model=share
639
572
  )
640
573
 
641
574
  if result and 'Item' in result:
@@ -645,19 +578,10 @@ class FileShareService(DatabaseService[FileShare]):
645
578
  share.access_count += 1
646
579
  share.last_accessed_at = dt.datetime.now(dt.UTC).timestamp()
647
580
 
648
- item = share.to_dictionary()
649
- item["pk"] = pk
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
-
581
+ share.prep_for_save()
658
582
  self.dynamodb.save(
659
- item=item,
660
- table_name=self.table_name
583
+ table_name=self.table_name,
584
+ item=share
661
585
  )
662
586
  except Exception:
663
587
  pass # Best effort
@@ -174,28 +174,11 @@ class FileSystemService(DatabaseService[File]):
174
174
  )
175
175
 
176
176
  # Save metadata to DynamoDB
177
- pk = f"FILE#{tenant_id}#{file.file_id}"
178
- sk = "METADATA"
179
-
180
- item = file.to_dictionary()
181
- item["pk"] = pk
182
- item["sk"] = sk
183
-
184
- # GSI1: Files by directory
185
- item["gsi1_pk"] = f"TENANT#{tenant_id}"
186
- if directory_id:
187
- item["gsi1_sk"] = f"DIRECTORY#{directory_id}#{file_name}"
188
- else:
189
- item["gsi1_sk"] = f"DIRECTORY#ROOT#{file_name}"
190
-
191
- # GSI2: Files by owner
192
- item["gsi2_pk"] = f"TENANT#{tenant_id}#USER#{user_id}"
193
- item["gsi2_sk"] = f"FILE#{file.created_utc_ts}"
177
+ file.prep_for_save()
178
+ save_result = self._save_model(file)
194
179
 
195
- self.dynamodb.save(
196
- table_name=self.table_name,
197
- item=item
198
- )
180
+ if not save_result.success:
181
+ return save_result
199
182
 
200
183
  return ServiceResult.success_result(file)
201
184
 
@@ -229,23 +212,13 @@ class FileSystemService(DatabaseService[File]):
229
212
  ServiceResult with File model
230
213
  """
231
214
  try:
232
- pk = f"FILE#{tenant_id}#{resource_id}"
233
- sk = "METADATA"
234
-
235
- result = self.dynamodb.get(
236
- table_name=self.table_name,
237
- key={"pk": pk, "sk": sk}
238
- )
215
+ # Use helper method with tenant check
216
+ file = self._get_model_by_id_with_tenant_check(resource_id, File, tenant_id)
239
217
 
240
- # Check if file exists first (before checking ownership)
241
- # DynamoDB.get returns {'Item': {...}} or {'ResponseMetadata': {...}}
242
- if not result or 'Item' not in result:
218
+ # Check if file exists
219
+ if not file:
243
220
  raise NotFoundError(f"File not found: {resource_id}")
244
221
 
245
- # Convert to File model
246
- file = File()
247
- file.map(result['Item'])
248
-
249
222
  # Access control: Check if user is owner or has share access
250
223
  if file.owner_id != user_id:
251
224
  # TODO: Check FileShare for access
@@ -316,27 +289,8 @@ class FileSystemService(DatabaseService[File]):
316
289
  file.updated_utc_ts = dt.datetime.now(dt.UTC).timestamp()
317
290
 
318
291
  # Save to DynamoDB
319
- pk = f"FILE#{tenant_id}#{resource_id}"
320
- sk = "METADATA"
321
-
322
- item = file.to_dictionary()
323
- item["pk"] = pk
324
- item["sk"] = sk
325
-
326
- # Update GSI keys if directory changed
327
- if "directory_id" in updates:
328
- item["gsi1_pk"] = f"TENANT#{tenant_id}"
329
- if updates["directory_id"]:
330
- item["gsi1_sk"] = f"DIRECTORY#{updates['directory_id']}#{file.file_name}"
331
- else:
332
- item["gsi1_sk"] = f"DIRECTORY#ROOT#{file.file_name}"
333
-
334
- self.dynamodb.save(
335
- table_name=self.table_name,
336
- item=item
337
- )
338
-
339
- return ServiceResult.success_result(file)
292
+ file.prep_for_save()
293
+ return self._save_model(file)
340
294
 
341
295
  except AccessDeniedError as e:
342
296
  return ServiceResult.error_result(
@@ -396,11 +350,12 @@ class FileSystemService(DatabaseService[File]):
396
350
  )
397
351
 
398
352
  # Delete from DynamoDB
399
- pk = f"FILE#{tenant_id}#{resource_id}"
400
- sk = "METADATA"
353
+ file = File()
354
+ file.id = resource_id
355
+ file.tenant_id = tenant_id
401
356
 
402
357
  self.dynamodb.delete(
403
- primary_key={"pk": pk, "sk": sk},
358
+ model=file,
404
359
  table_name=self.table_name
405
360
  )
406
361
  else:
@@ -409,17 +364,11 @@ class FileSystemService(DatabaseService[File]):
409
364
  file.status = "deleted"
410
365
  file.deleted_utc_ts = dt.datetime.now(dt.UTC).timestamp()
411
366
 
412
- pk = f"FILE#{tenant_id}#{resource_id}"
413
- sk = "METADATA"
367
+ file.prep_for_save()
368
+ save_result = self._save_model(file)
414
369
 
415
- item = file.to_dictionary()
416
- item["pk"] = pk
417
- item["sk"] = sk
418
-
419
- self.dynamodb.save(
420
- table_name=self.table_name,
421
- item=item
422
- )
370
+ if not save_result.success:
371
+ return save_result
423
372
 
424
373
  return ServiceResult.success_result(True)
425
374
 
@@ -507,32 +456,23 @@ class FileSystemService(DatabaseService[File]):
507
456
  ServiceResult with list of File models
508
457
  """
509
458
  try:
510
- gsi1_pk = f"TENANT#{tenant_id}"
459
+ # Use GSI1 to query files by directory
460
+ temp_file = File()
461
+ temp_file.tenant_id = tenant_id
462
+ temp_file.directory_id = directory_id # None for root files
511
463
 
512
- if directory_id:
513
- gsi1_sk_prefix = f"DIRECTORY#{directory_id}#"
514
- else:
515
- gsi1_sk_prefix = "DIRECTORY#ROOT#"
516
-
517
- # Query GSI1
518
- results = self.dynamodb.query(
519
- key=Key('gsi1_pk').eq(gsi1_pk) & Key('gsi1_sk').begins_with(gsi1_sk_prefix),
520
- table_name=self.table_name,
521
- index_name="gsi1",
522
- limit=limit
523
- )
464
+ # Query using helper method
465
+ query_result = self._query_by_index(temp_file, "gsi1", limit=limit, ascending=True)
466
+
467
+ if not query_result.success:
468
+ return query_result
524
469
 
470
+ # Filter results
525
471
  files = []
526
- for item in results.get('Items', []):
527
- file = File()
528
- file.map(item)
529
-
530
- # Filter out deleted files
531
- if file.status != "deleted":
532
- # Basic access control: show only owned files or shared files
533
- # TODO: Check FileShare for shared access
534
- if file.owner_id == user_id:
535
- files.append(file)
472
+ for file in query_result.data:
473
+ # Filter out deleted files and apply access control
474
+ if file.status != "deleted" and file.owner_id == user_id:
475
+ files.append(file)
536
476
 
537
477
  return ServiceResult.success_result(files)
538
478
 
@@ -567,21 +507,20 @@ class FileSystemService(DatabaseService[File]):
567
507
  if owner_id != user_id:
568
508
  raise AccessDeniedError("You can only list your own files")
569
509
 
570
- gsi2_pk = f"TENANT#{tenant_id}#USER#{owner_id}"
510
+ # Use GSI2 to query files by owner
511
+ temp_file = File()
512
+ temp_file.tenant_id = tenant_id
513
+ temp_file.owner_id = owner_id
571
514
 
572
- # Query GSI2
573
- results = self.dynamodb.query(
574
- key=Key('gsi2_pk').eq(gsi2_pk) & Key('gsi2_sk').begins_with("FILE#"),
575
- table_name=self.table_name,
576
- index_name="gsi2",
577
- limit=limit
578
- )
515
+ # Query using helper method
516
+ query_result = self._query_by_index(temp_file, "gsi2", limit=limit, ascending=False)
579
517
 
518
+ if not query_result.success:
519
+ return query_result
520
+
521
+ # Filter results
580
522
  files = []
581
- for item in results.get('Items', []):
582
- file = File()
583
- file.map(item)
584
-
523
+ for file in query_result.data:
585
524
  # Filter out deleted files
586
525
  if file.status != "deleted":
587
526
  files.append(file)