geek-cafe-saas-sdk 0.7.2__py3-none-any.whl → 0.7.4__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.

@@ -3,7 +3,7 @@ Geek Cafe Services - Base Reusable Services for SaaS
3
3
 
4
4
  Version 0.6.0 adds File System Service
5
5
  """
6
- __version__ = "0.7.2"
6
+ __version__ = "0.7.4"
7
7
 
8
8
  # Import main modules for easier access
9
9
  from . import services
@@ -515,21 +515,12 @@ class FileVersionService(DatabaseService[FileVersion]):
515
515
  def _get_file(self, tenant_id: str, file_id: str, user_id: str) -> ServiceResult[File]:
516
516
  """Get file with access control."""
517
517
  try:
518
- file = File()
519
- file.id = file_id
520
- file.tenant_id = tenant_id
518
+ # Use DatabaseService helper to get file
519
+ file = self._get_model_by_id_with_tenant_check(file_id, File, tenant_id)
521
520
 
522
- result = self.dynamodb.get(
523
- table_name=self.table_name,
524
- model=file
525
- )
526
-
527
- if not result or 'Item' not in result:
521
+ if not file:
528
522
  raise NotFoundError(f"File not found: {file_id}")
529
523
 
530
- file = File()
531
- file.map(result['Item'])
532
-
533
524
  if file.owner_id != user_id:
534
525
  raise AccessDeniedError("You do not have access to this file")
535
526
 
@@ -544,24 +535,14 @@ class FileVersionService(DatabaseService[FileVersion]):
544
535
  def _get_latest_version_number(self, tenant_id: str, file_id: str) -> int:
545
536
  """Get the latest version number for a file."""
546
537
  try:
547
- file = File()
548
- file.id = file_id
549
- file.tenant_id = tenant_id
538
+ # Query versions for this file using DatabaseService helper
539
+ temp_version = FileVersion()
540
+ temp_version.file_id = file_id
550
541
 
551
- key = file.get_key("gsi1")
552
- results = self.dynamodb.query(
553
- key=key,
554
- table_name=self.table_name,
555
- index_name="gsi1",
556
- limit=1,
557
- ascending=False # Get highest version number
558
- )
542
+ result = self._query_by_index(temp_version, "gsi1", limit=1, ascending=False)
559
543
 
560
- items = results.get('Items', [])
561
- if items:
562
- version = FileVersion()
563
- version.map(items[0])
564
- return version.version_number
544
+ if result.success and result.data:
545
+ return result.data[0].version_number
565
546
 
566
547
  return 0 # No versions yet
567
548
 
@@ -576,19 +557,10 @@ class FileVersionService(DatabaseService[FileVersion]):
576
557
  ) -> None:
577
558
  """Mark a version as not current."""
578
559
  try:
579
- version = FileVersion()
580
- version.id = version_id
581
- version.tenant_id = tenant_id
582
- version.file_id = file_id
583
-
584
- result = self.dynamodb.get(
585
- table_name=self.table_name,
586
- model=version
587
- )
560
+ # Use DatabaseService helper to get version
561
+ version = self._get_model_by_id_with_tenant_check(version_id, FileVersion, tenant_id)
588
562
 
589
- if result and 'Item' in result:
590
- version = FileVersion()
591
- version.map(result['Item'])
563
+ if version:
592
564
  version.is_current = False
593
565
 
594
566
  version.prep_for_save()
@@ -605,12 +577,15 @@ class FileVersionService(DatabaseService[FileVersion]):
605
577
  ) -> None:
606
578
  """Update file record with current version info."""
607
579
  try:
608
- pk = f"FILE#{tenant_id}#{file_id}"
609
- sk = "metadata"
580
+ file = File()
581
+ file.id = file_id
582
+ file.tenant_id = tenant_id
583
+
584
+ key = file.get_key("gsi1")
610
585
 
611
586
  result = self.dynamodb.get(
612
587
  table_name=self.table_name,
613
- key={"pk": pk, "sk": sk}
588
+ key=key
614
589
  )
615
590
 
616
591
  if result and 'Item' in result:
@@ -33,13 +33,13 @@ class ContactThread(BaseModel):
33
33
  super().__init__()
34
34
  self._subject: str | None = None
35
35
  self._status: str = "open" # open, in_progress, resolved, closed
36
- self._priority: str = "medium" # low, medium, high, urgent
36
+ self._priority: Optional[str] = "medium" # low, medium, high, urgent
37
37
 
38
38
  # Sender information (guest or authenticated user)
39
39
  self._sender: Dict[str, Any] = {} # {id, name, email, session_id}
40
40
 
41
41
  # Assignment and routing
42
- self._assigned_to: str | None = None # Staff user ID
42
+ self._assigned_to: Optional[str] = None # Staff user ID
43
43
  self._inbox_id: str = "support" # support, sales, billing, etc.
44
44
 
45
45
  # Messages embedded in thread (suitable for low volume)
@@ -178,7 +178,7 @@ class ContactThread(BaseModel):
178
178
 
179
179
  @priority.setter
180
180
  def priority(self, value: str | None):
181
- valid_priorities = ["low", "medium", "high", "urgent"]
181
+ valid_priorities = ["low", "medium", "high", "urgent", None]
182
182
  if value in valid_priorities:
183
183
  self._priority = value
184
184
  else:
@@ -245,7 +245,7 @@ class ContactThreadService(DatabaseService[ContactThread]):
245
245
  tenant_id=tenant_id, status=status)
246
246
 
247
247
  def list_by_assigned_user(self, assigned_to: str, tenant_id: str,
248
- status: str = None, limit: int = 50) -> ServiceResult[List[ContactThread]]:
248
+ *, status: str = None, priority: str = None, limit: int = 50) -> ServiceResult[List[ContactThread]]:
249
249
  """
250
250
  List contact threads assigned to a specific user using GSI3.
251
251
 
@@ -263,6 +263,13 @@ class ContactThreadService(DatabaseService[ContactThread]):
263
263
  temp_thread.assigned_to = assigned_to
264
264
  if status:
265
265
  temp_thread.status = status
266
+ else:
267
+ temp_thread.status = None
268
+
269
+ if priority:
270
+ temp_thread.priority = priority
271
+ else:
272
+ temp_thread.priority = None
266
273
 
267
274
  result = self._query_by_index(
268
275
  temp_thread,
@@ -2,14 +2,17 @@
2
2
 
3
3
  from abc import ABC, abstractmethod
4
4
  from typing import Generic, TypeVar, Dict, Any, List, Optional
5
- from boto3_assist.dynamodb.dynamodb import DynamoDB
5
+ from boto3_assist.dynamodb.dynamodb import DynamoDB, DynamoDBIndex
6
6
  from ..core.service_result import ServiceResult
7
7
  from ..core.service_errors import ValidationError, AccessDeniedError, NotFoundError
8
8
  from ..core.error_codes import ErrorCode
9
9
  import os
10
+ from aws_lambda_powertools import Logger
10
11
 
11
12
  T = TypeVar("T")
12
13
 
14
+ logger = Logger()
15
+
13
16
 
14
17
  class DatabaseService(ABC, Generic[T]):
15
18
  """Base service class for database operations."""
@@ -23,6 +26,8 @@ class DatabaseService(ABC, Generic[T]):
23
26
  if not self.table_name:
24
27
  raise ValueError("Table name is required")
25
28
 
29
+ self.LOG_DYNAMO_DB_QUERY = os.getenv("LOG_DYNAMO_DB_QUERY", False)
30
+
26
31
  @abstractmethod
27
32
  def create(self, tenant_id: str, user_id: str, **kwargs) -> ServiceResult[T]:
28
33
  """Create a new resource."""
@@ -226,6 +231,26 @@ class DatabaseService(ABC, Generic[T]):
226
231
  # Get the key for the specified index from the provided model
227
232
  key = model.get_key(index_name).key()
228
233
 
234
+ # key.to_string()
235
+ # extract_key_values(key_expression) is coming soon from boto3_assist
236
+ if self.LOG_DYNAMO_DB_QUERY:
237
+ if hasattr(DynamoDBIndex, 'extract_key_values'):
238
+ values = DynamoDBIndex.extract_key_values(key)
239
+ logger.info(f"Querying index {index_name} with key {values}")
240
+ else:
241
+ try:
242
+ value = {
243
+ 'pk': key._values[0]._values[1],
244
+ 'sk': key._values[1]._values[1],
245
+ 'operator': key._values[1].expression_operator,
246
+ 'format': key._values[1].expression_format,
247
+ 'index_name': index_name
248
+ }
249
+ logger.info(f"Querying index {index_name} with key {value}")
250
+ except Exception as e:
251
+ logger.error(f"Failed to extract key values: {e}")
252
+
253
+
229
254
  # Execute the query
230
255
  response = self.dynamodb.query(
231
256
  table_name=self.table_name,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geek_cafe_saas_sdk
3
- Version: 0.7.2
3
+ Version: 0.7.4
4
4
  Summary: Base Reusable Services for SaaS
5
5
  Project-URL: Homepage, https://github.com/geekcafe/geek-cafe-services
6
6
  Project-URL: Documentation, https://github.com/geekcafe/geek-cafe-services/blob/main/README.md
@@ -1,4 +1,4 @@
1
- geek_cafe_saas_sdk/__init__.py,sha256=6akqUf1q618584Azt24YKOhlq0r6YcNVl0Be6BQjh2A,187
1
+ geek_cafe_saas_sdk/__init__.py,sha256=2Ex9QlY1D-7pBzaGYh1jeR1Fsh85yC3mC1as9C8jSIQ,187
2
2
  geek_cafe_saas_sdk/core/__init__.py,sha256=3o3-n1ojO_a_X2bEfzhnxmcjAzuzAU73VTcuRva_f5U,279
3
3
  geek_cafe_saas_sdk/core/audit_mixin.py,sha256=hQz0XYUr_Trqpv4JXxywVNxqJJehQwLu79Y1NBpRwzo,1150
4
4
  geek_cafe_saas_sdk/core/error_codes.py,sha256=vf82TDaJ0qIQLzgjTu96nK9cAaSWK7pYfnOW8HCvSxE,5010
@@ -88,7 +88,7 @@ geek_cafe_saas_sdk/domains/files/services/directory_service.py,sha256=4WKyEB5ho_
88
88
  geek_cafe_saas_sdk/domains/files/services/file_lineage_service.py,sha256=TdYPTu0fun71RtGaFzrnMI0ONSb1Je7NE8qSkBZz0eM,17545
89
89
  geek_cafe_saas_sdk/domains/files/services/file_share_service.py,sha256=mlw2k5t8VeVJ-6RAqOBgbSuJ3F1vVC4FZpYNnRoALEU,20376
90
90
  geek_cafe_saas_sdk/domains/files/services/file_system_service.py,sha256=uoO3RT_FZuGZxA4S3YmpXUIKymj6M8qcnL2DIyoCD9o,19079
91
- geek_cafe_saas_sdk/domains/files/services/file_version_service.py,sha256=nzGVoQb94QbfZbtt7pvCrTEPnuY0uzwLEGDWS8eQ_No,23498
91
+ geek_cafe_saas_sdk/domains/files/services/file_version_service.py,sha256=A5x8Dlo9p07Tnqwumj4JwHoonFg4Jzx4zY2sC7dDtUs,22886
92
92
  geek_cafe_saas_sdk/domains/files/services/s3_file_service.py,sha256=T0lyUK97w-L-PrqzTRhdqxKmiW_w15k2y3BslE6WvQw,16920
93
93
  geek_cafe_saas_sdk/domains/messaging/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
94
94
  geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -111,11 +111,11 @@ geek_cafe_saas_sdk/domains/messaging/models/__init__.py,sha256=VtJUxBdtCOxUJzXbD
111
111
  geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py,sha256=xrzP3gItEOIlLDaJHIRysFcpMOnAdK84sE-4pakLEkk,10565
112
112
  geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py,sha256=KyOfP54Ho2oU7upZa5Z-bMDwJfGq8oX5_apFCjxg4fY,6058
113
113
  geek_cafe_saas_sdk/domains/messaging/models/chat_message.py,sha256=sFXp4hTQPlI9pTWp_K4H-XUdz7IO3FozX0PphQm0kYs,13273
114
- geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py,sha256=PTMtwY6OTy002Y1OwQFtvyENIDZIeLzDj88siXZUhrA,12836
114
+ geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py,sha256=v4VzsY7TvuCU2YXouNHd9pMFyPl732jpQ7Hmzr6qPtw,12855
115
115
  geek_cafe_saas_sdk/domains/messaging/services/__init__.py,sha256=qTcn7zYppwdcUSWLCCWNNorJhauCizmhlup7pqiAGXs,287
116
116
  geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py,sha256=ismv9N2ZKwRVErvsSXS9_IBl8kPX-AbRkAvrq4zNywM,26604
117
117
  geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py,sha256=OVG3E-9Xz86s6OeYNY7dShil-rv9N6R6EJO54xNM948,19100
118
- geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py,sha256=Z0B3WvElgzdaEbXOPfh9XT8BSlGe0csQ0-rJCOGZlYI,20685
118
+ geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py,sha256=k4-qecEgTB3KTFeWAeEoih8vRQR04gkRf63DefBuruE,20918
119
119
  geek_cafe_saas_sdk/domains/notifications/__init__.py,sha256=3T6Y-gVQ9BD-RxgUVnfmk2Ss9NndwLGy2C4rNgDKV3E,424
120
120
  geek_cafe_saas_sdk/domains/notifications/handlers/__init__.py,sha256=TV8USs_qzUi7V8IzgthzwJ69hUC-6WCREg3YN6Glhxs,32
121
121
  geek_cafe_saas_sdk/domains/notifications/handlers/create_webhook/app.py,sha256=hQZEuj6Zj8005Q1Ix7t8cmtBN25ILJUens13mEMvS74,2087
@@ -241,7 +241,7 @@ geek_cafe_saas_sdk/middleware/validation.py,sha256=0KnHaBDHo72lAkAUIzEqGl-JCgyIm
241
241
  geek_cafe_saas_sdk/models/__init__.py,sha256=M9p2n9YvwVMw9DMgMJaS-DcmmTJ7EfCmuuIFDF4wnUc,655
242
242
  geek_cafe_saas_sdk/models/base_model.py,sha256=H-t7ZQEUwmkvV9xPyd0jVbbieAUwu9n5MCtjvqFwtYw,7353
243
243
  geek_cafe_saas_sdk/services/__init__.py,sha256=guO3SxaTEy7NAWNG0orrsjRaUT25XvP0pY7W3lRwePY,614
244
- geek_cafe_saas_sdk/services/database_service.py,sha256=Ej0TO_dDERDsxPguMypiUhd9U5MNAOOKo3PTRDyX-QQ,16979
244
+ geek_cafe_saas_sdk/services/database_service.py,sha256=jdiFyVke6g0QXBaw0C8jiGGIYqGpW2evzWyuyejaCjQ,18148
245
245
  geek_cafe_saas_sdk/utilities/__init__.py,sha256=g8voZ8KYeFJQ6HKYhrp5q6mMHgP-AV0ezmouXRiGbDU,2064
246
246
  geek_cafe_saas_sdk/utilities/cognito_utility.py,sha256=S36jAwtJAgeBMN6bL7WKjfLU97f78lRptzpISAQb1kQ,20208
247
247
  geek_cafe_saas_sdk/utilities/custom_exceptions.py,sha256=Yc5Kg2kYXeYZEzuX1u48WczT9jtBZYUiWyTv7NsNDPs,5173
@@ -259,7 +259,7 @@ geek_cafe_saas_sdk/utilities/logging_utility.py,sha256=3VwPGleoRw7c4Bn43_Kujl1CQ
259
259
  geek_cafe_saas_sdk/utilities/message_query_helper.py,sha256=8iMuacRPfom_T06-VMhaSp-90D8604q7waM-GVcpPNQ,11500
260
260
  geek_cafe_saas_sdk/utilities/response.py,sha256=0sylVbm_ieIsNGsm9mWXSIdLSyOeoACwFZQQUxyim3s,5964
261
261
  geek_cafe_saas_sdk/utilities/string_functions.py,sha256=_1F4dGyzuD3fAcV1w7A8bv1e-3p0DbNAv_i6-fsekg8,5973
262
- geek_cafe_saas_sdk-0.7.2.dist-info/METADATA,sha256=2ZK6Bof_jeNVM1sbJyp4ttGPK4HKAOlbPoHZKnE5b2o,16068
263
- geek_cafe_saas_sdk-0.7.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
264
- geek_cafe_saas_sdk-0.7.2.dist-info/licenses/LICENSE,sha256=EHsHc4GFN0U63LgqDSY2aQRKRupbq6QgixCwopdIU2E,2097
265
- geek_cafe_saas_sdk-0.7.2.dist-info/RECORD,,
262
+ geek_cafe_saas_sdk-0.7.4.dist-info/METADATA,sha256=TRV57_1X5fdSit3wpWlQTi3T6XjZytZsOSNOBLPZEm0,16068
263
+ geek_cafe_saas_sdk-0.7.4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
264
+ geek_cafe_saas_sdk-0.7.4.dist-info/licenses/LICENSE,sha256=EHsHc4GFN0U63LgqDSY2aQRKRupbq6QgixCwopdIU2E,2097
265
+ geek_cafe_saas_sdk-0.7.4.dist-info/RECORD,,