geek-cafe-saas-sdk 0.6.0__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 (194) hide show
  1. geek_cafe_saas_sdk/__init__.py +9 -0
  2. geek_cafe_saas_sdk/core/__init__.py +11 -0
  3. geek_cafe_saas_sdk/core/audit_mixin.py +33 -0
  4. geek_cafe_saas_sdk/core/error_codes.py +132 -0
  5. geek_cafe_saas_sdk/core/service_errors.py +19 -0
  6. geek_cafe_saas_sdk/core/service_result.py +121 -0
  7. geek_cafe_saas_sdk/decorators/__init__.py +64 -0
  8. geek_cafe_saas_sdk/decorators/auth.py +373 -0
  9. geek_cafe_saas_sdk/decorators/core.py +358 -0
  10. geek_cafe_saas_sdk/domains/__init__.py +0 -0
  11. geek_cafe_saas_sdk/domains/analytics/__init__.py +0 -0
  12. geek_cafe_saas_sdk/domains/analytics/handlers/__init__.py +0 -0
  13. geek_cafe_saas_sdk/domains/analytics/models/__init__.py +9 -0
  14. geek_cafe_saas_sdk/domains/analytics/models/website_analytics.py +219 -0
  15. geek_cafe_saas_sdk/domains/analytics/models/website_analytics_summary.py +220 -0
  16. geek_cafe_saas_sdk/domains/analytics/services/__init__.py +11 -0
  17. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_service.py +232 -0
  18. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_summary_service.py +212 -0
  19. geek_cafe_saas_sdk/domains/analytics/services/website_analytics_tally_service.py +610 -0
  20. geek_cafe_saas_sdk/domains/auth/__init__.py +0 -0
  21. geek_cafe_saas_sdk/domains/auth/handlers/__init__.py +0 -0
  22. geek_cafe_saas_sdk/domains/auth/handlers/users/create/app.py +41 -0
  23. geek_cafe_saas_sdk/domains/auth/handlers/users/delete/app.py +41 -0
  24. geek_cafe_saas_sdk/domains/auth/handlers/users/get/app.py +39 -0
  25. geek_cafe_saas_sdk/domains/auth/handlers/users/list/app.py +36 -0
  26. geek_cafe_saas_sdk/domains/auth/handlers/users/update/app.py +44 -0
  27. geek_cafe_saas_sdk/domains/auth/models/__init__.py +13 -0
  28. geek_cafe_saas_sdk/domains/auth/models/permission.py +134 -0
  29. geek_cafe_saas_sdk/domains/auth/models/resource_permission.py +245 -0
  30. geek_cafe_saas_sdk/domains/auth/models/role.py +213 -0
  31. geek_cafe_saas_sdk/domains/auth/models/user.py +285 -0
  32. geek_cafe_saas_sdk/domains/auth/services/__init__.py +16 -0
  33. geek_cafe_saas_sdk/domains/auth/services/authorization_service.py +376 -0
  34. geek_cafe_saas_sdk/domains/auth/services/permission_registry.py +464 -0
  35. geek_cafe_saas_sdk/domains/auth/services/resource_permission_service.py +408 -0
  36. geek_cafe_saas_sdk/domains/auth/services/user_service.py +274 -0
  37. geek_cafe_saas_sdk/domains/communities/__init__.py +0 -0
  38. geek_cafe_saas_sdk/domains/communities/handlers/__init__.py +0 -0
  39. geek_cafe_saas_sdk/domains/communities/handlers/communities/create/app.py +41 -0
  40. geek_cafe_saas_sdk/domains/communities/handlers/communities/delete/app.py +41 -0
  41. geek_cafe_saas_sdk/domains/communities/handlers/communities/get/app.py +39 -0
  42. geek_cafe_saas_sdk/domains/communities/handlers/communities/list/app.py +36 -0
  43. geek_cafe_saas_sdk/domains/communities/handlers/communities/update/app.py +44 -0
  44. geek_cafe_saas_sdk/domains/communities/models/__init__.py +6 -0
  45. geek_cafe_saas_sdk/domains/communities/models/community.py +326 -0
  46. geek_cafe_saas_sdk/domains/communities/models/community_member.py +227 -0
  47. geek_cafe_saas_sdk/domains/communities/services/__init__.py +6 -0
  48. geek_cafe_saas_sdk/domains/communities/services/community_member_service.py +412 -0
  49. geek_cafe_saas_sdk/domains/communities/services/community_service.py +479 -0
  50. geek_cafe_saas_sdk/domains/events/__init__.py +0 -0
  51. geek_cafe_saas_sdk/domains/events/handlers/__init__.py +0 -0
  52. geek_cafe_saas_sdk/domains/events/handlers/attendees/app.py +67 -0
  53. geek_cafe_saas_sdk/domains/events/handlers/cancel/app.py +66 -0
  54. geek_cafe_saas_sdk/domains/events/handlers/check_in/app.py +60 -0
  55. geek_cafe_saas_sdk/domains/events/handlers/create/app.py +93 -0
  56. geek_cafe_saas_sdk/domains/events/handlers/delete/app.py +42 -0
  57. geek_cafe_saas_sdk/domains/events/handlers/get/app.py +39 -0
  58. geek_cafe_saas_sdk/domains/events/handlers/invite/app.py +98 -0
  59. geek_cafe_saas_sdk/domains/events/handlers/list/app.py +125 -0
  60. geek_cafe_saas_sdk/domains/events/handlers/publish/app.py +49 -0
  61. geek_cafe_saas_sdk/domains/events/handlers/rsvp/app.py +83 -0
  62. geek_cafe_saas_sdk/domains/events/handlers/update/app.py +44 -0
  63. geek_cafe_saas_sdk/domains/events/models/__init__.py +3 -0
  64. geek_cafe_saas_sdk/domains/events/models/event.py +681 -0
  65. geek_cafe_saas_sdk/domains/events/models/event_attendee.py +324 -0
  66. geek_cafe_saas_sdk/domains/events/services/__init__.py +9 -0
  67. geek_cafe_saas_sdk/domains/events/services/event_attendee_service.py +571 -0
  68. geek_cafe_saas_sdk/domains/events/services/event_service.py +684 -0
  69. geek_cafe_saas_sdk/domains/files/__init__.py +0 -0
  70. geek_cafe_saas_sdk/domains/files/models/__init__.py +0 -0
  71. geek_cafe_saas_sdk/domains/files/models/directory.py +258 -0
  72. geek_cafe_saas_sdk/domains/files/models/file.py +312 -0
  73. geek_cafe_saas_sdk/domains/files/models/file_share.py +268 -0
  74. geek_cafe_saas_sdk/domains/files/models/file_version.py +216 -0
  75. geek_cafe_saas_sdk/domains/files/services/__init__.py +0 -0
  76. geek_cafe_saas_sdk/domains/files/services/directory_service.py +701 -0
  77. geek_cafe_saas_sdk/domains/files/services/file_share_service.py +663 -0
  78. geek_cafe_saas_sdk/domains/files/services/file_system_service.py +575 -0
  79. geek_cafe_saas_sdk/domains/files/services/file_version_service.py +739 -0
  80. geek_cafe_saas_sdk/domains/files/services/s3_file_service.py +501 -0
  81. geek_cafe_saas_sdk/domains/messaging/__init__.py +0 -0
  82. geek_cafe_saas_sdk/domains/messaging/handlers/__init__.py +0 -0
  83. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/create/app.py +86 -0
  84. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/delete/app.py +65 -0
  85. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/get/app.py +64 -0
  86. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/list/app.py +97 -0
  87. geek_cafe_saas_sdk/domains/messaging/handlers/chat_channels/update/app.py +149 -0
  88. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/create/app.py +67 -0
  89. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/delete/app.py +65 -0
  90. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/get/app.py +64 -0
  91. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/list/app.py +102 -0
  92. geek_cafe_saas_sdk/domains/messaging/handlers/chat_messages/update/app.py +127 -0
  93. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/create/app.py +94 -0
  94. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/delete/app.py +66 -0
  95. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/get/app.py +67 -0
  96. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/list/app.py +95 -0
  97. geek_cafe_saas_sdk/domains/messaging/handlers/contact_threads/update/app.py +156 -0
  98. geek_cafe_saas_sdk/domains/messaging/models/__init__.py +13 -0
  99. geek_cafe_saas_sdk/domains/messaging/models/chat_channel.py +337 -0
  100. geek_cafe_saas_sdk/domains/messaging/models/chat_channel_member.py +180 -0
  101. geek_cafe_saas_sdk/domains/messaging/models/chat_message.py +426 -0
  102. geek_cafe_saas_sdk/domains/messaging/models/contact_thread.py +392 -0
  103. geek_cafe_saas_sdk/domains/messaging/services/__init__.py +11 -0
  104. geek_cafe_saas_sdk/domains/messaging/services/chat_channel_service.py +700 -0
  105. geek_cafe_saas_sdk/domains/messaging/services/chat_message_service.py +491 -0
  106. geek_cafe_saas_sdk/domains/messaging/services/contact_thread_service.py +497 -0
  107. geek_cafe_saas_sdk/domains/tenancy/__init__.py +0 -0
  108. geek_cafe_saas_sdk/domains/tenancy/handlers/__init__.py +0 -0
  109. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/activate/app.py +52 -0
  110. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/active/app.py +37 -0
  111. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/cancel/app.py +55 -0
  112. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/get/app.py +39 -0
  113. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/list/app.py +44 -0
  114. geek_cafe_saas_sdk/domains/tenancy/handlers/subscriptions/record_payment/app.py +56 -0
  115. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/get/app.py +39 -0
  116. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/me/app.py +37 -0
  117. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/signup/app.py +61 -0
  118. geek_cafe_saas_sdk/domains/tenancy/handlers/tenants/update/app.py +44 -0
  119. geek_cafe_saas_sdk/domains/tenancy/models/__init__.py +6 -0
  120. geek_cafe_saas_sdk/domains/tenancy/models/subscription.py +440 -0
  121. geek_cafe_saas_sdk/domains/tenancy/models/tenant.py +258 -0
  122. geek_cafe_saas_sdk/domains/tenancy/services/__init__.py +6 -0
  123. geek_cafe_saas_sdk/domains/tenancy/services/subscription_service.py +557 -0
  124. geek_cafe_saas_sdk/domains/tenancy/services/tenant_service.py +575 -0
  125. geek_cafe_saas_sdk/domains/voting/__init__.py +0 -0
  126. geek_cafe_saas_sdk/domains/voting/handlers/__init__.py +0 -0
  127. geek_cafe_saas_sdk/domains/voting/handlers/votes/create/app.py +128 -0
  128. geek_cafe_saas_sdk/domains/voting/handlers/votes/delete/app.py +41 -0
  129. geek_cafe_saas_sdk/domains/voting/handlers/votes/get/app.py +39 -0
  130. geek_cafe_saas_sdk/domains/voting/handlers/votes/list/app.py +38 -0
  131. geek_cafe_saas_sdk/domains/voting/handlers/votes/summerize/README.md +3 -0
  132. geek_cafe_saas_sdk/domains/voting/handlers/votes/update/app.py +44 -0
  133. geek_cafe_saas_sdk/domains/voting/models/__init__.py +9 -0
  134. geek_cafe_saas_sdk/domains/voting/models/vote.py +231 -0
  135. geek_cafe_saas_sdk/domains/voting/models/vote_summary.py +193 -0
  136. geek_cafe_saas_sdk/domains/voting/services/__init__.py +11 -0
  137. geek_cafe_saas_sdk/domains/voting/services/vote_service.py +264 -0
  138. geek_cafe_saas_sdk/domains/voting/services/vote_summary_service.py +198 -0
  139. geek_cafe_saas_sdk/domains/voting/services/vote_tally_service.py +533 -0
  140. geek_cafe_saas_sdk/lambda_handlers/README.md +404 -0
  141. geek_cafe_saas_sdk/lambda_handlers/__init__.py +67 -0
  142. geek_cafe_saas_sdk/lambda_handlers/_base/__init__.py +25 -0
  143. geek_cafe_saas_sdk/lambda_handlers/_base/api_key_handler.py +129 -0
  144. geek_cafe_saas_sdk/lambda_handlers/_base/authorized_secure_handler.py +218 -0
  145. geek_cafe_saas_sdk/lambda_handlers/_base/base_handler.py +185 -0
  146. geek_cafe_saas_sdk/lambda_handlers/_base/handler_factory.py +256 -0
  147. geek_cafe_saas_sdk/lambda_handlers/_base/public_handler.py +53 -0
  148. geek_cafe_saas_sdk/lambda_handlers/_base/secure_handler.py +89 -0
  149. geek_cafe_saas_sdk/lambda_handlers/_base/service_pool.py +94 -0
  150. geek_cafe_saas_sdk/lambda_handlers/directories/create/app.py +79 -0
  151. geek_cafe_saas_sdk/lambda_handlers/directories/delete/app.py +76 -0
  152. geek_cafe_saas_sdk/lambda_handlers/directories/get/app.py +74 -0
  153. geek_cafe_saas_sdk/lambda_handlers/directories/list/app.py +75 -0
  154. geek_cafe_saas_sdk/lambda_handlers/directories/move/app.py +79 -0
  155. geek_cafe_saas_sdk/lambda_handlers/files/delete/app.py +121 -0
  156. geek_cafe_saas_sdk/lambda_handlers/files/download/app.py +187 -0
  157. geek_cafe_saas_sdk/lambda_handlers/files/get/app.py +127 -0
  158. geek_cafe_saas_sdk/lambda_handlers/files/list/app.py +108 -0
  159. geek_cafe_saas_sdk/lambda_handlers/files/share/app.py +83 -0
  160. geek_cafe_saas_sdk/lambda_handlers/files/shares/list/app.py +84 -0
  161. geek_cafe_saas_sdk/lambda_handlers/files/shares/revoke/app.py +76 -0
  162. geek_cafe_saas_sdk/lambda_handlers/files/update/app.py +143 -0
  163. geek_cafe_saas_sdk/lambda_handlers/files/upload/app.py +151 -0
  164. geek_cafe_saas_sdk/middleware/__init__.py +36 -0
  165. geek_cafe_saas_sdk/middleware/auth.py +85 -0
  166. geek_cafe_saas_sdk/middleware/authorization.py +523 -0
  167. geek_cafe_saas_sdk/middleware/cors.py +63 -0
  168. geek_cafe_saas_sdk/middleware/error_handling.py +114 -0
  169. geek_cafe_saas_sdk/middleware/validation.py +80 -0
  170. geek_cafe_saas_sdk/models/__init__.py +20 -0
  171. geek_cafe_saas_sdk/models/base_model.py +233 -0
  172. geek_cafe_saas_sdk/services/__init__.py +18 -0
  173. geek_cafe_saas_sdk/services/database_service.py +441 -0
  174. geek_cafe_saas_sdk/utilities/__init__.py +88 -0
  175. geek_cafe_saas_sdk/utilities/cognito_utility.py +568 -0
  176. geek_cafe_saas_sdk/utilities/custom_exceptions.py +183 -0
  177. geek_cafe_saas_sdk/utilities/datetime_utility.py +410 -0
  178. geek_cafe_saas_sdk/utilities/dictionary_utility.py +78 -0
  179. geek_cafe_saas_sdk/utilities/dynamodb_utils.py +151 -0
  180. geek_cafe_saas_sdk/utilities/environment_loader.py +149 -0
  181. geek_cafe_saas_sdk/utilities/environment_variables.py +228 -0
  182. geek_cafe_saas_sdk/utilities/http_body_parameters.py +44 -0
  183. geek_cafe_saas_sdk/utilities/http_path_parameters.py +60 -0
  184. geek_cafe_saas_sdk/utilities/http_status_code.py +63 -0
  185. geek_cafe_saas_sdk/utilities/jwt_utility.py +234 -0
  186. geek_cafe_saas_sdk/utilities/lambda_event_utility.py +776 -0
  187. geek_cafe_saas_sdk/utilities/logging_utility.py +64 -0
  188. geek_cafe_saas_sdk/utilities/message_query_helper.py +340 -0
  189. geek_cafe_saas_sdk/utilities/response.py +209 -0
  190. geek_cafe_saas_sdk/utilities/string_functions.py +180 -0
  191. geek_cafe_saas_sdk-0.6.0.dist-info/METADATA +397 -0
  192. geek_cafe_saas_sdk-0.6.0.dist-info/RECORD +194 -0
  193. geek_cafe_saas_sdk-0.6.0.dist-info/WHEEL +4 -0
  194. geek_cafe_saas_sdk-0.6.0.dist-info/licenses/LICENSE +47 -0
@@ -0,0 +1,220 @@
1
+ from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
2
+ from boto3_assist.utilities.string_utility import StringUtility
3
+ import datetime as dt
4
+ from typing import Dict, Any
5
+ from geek_cafe_saas_sdk.models.base_model import BaseModel
6
+
7
+
8
+ class WebsiteAnalyticsSummary(BaseModel):
9
+ """
10
+ Model for storing aggregated website analytics data.
11
+
12
+ This model stores hourly/daily summaries of analytics data for efficient querying.
13
+ Aggregated by an EventBridge scheduled job via the tally service.
14
+ """
15
+
16
+ def __init__(self):
17
+ super().__init__()
18
+ self._route: str | None = None # URL route this summary is for
19
+ self._slug: str | None = None # Slug for the page
20
+ self._analytics_type: str = "general" # Type of analytics summarized
21
+ self._period_start_ts: float | None = None # Start of aggregation period
22
+ self._period_end_ts: float | None = None # End of aggregation period
23
+ self._period_type: str = "hourly" # hourly, daily, weekly, monthly
24
+
25
+ # Aggregated metrics
26
+ self._total_events: int = 0 # Total number of events in period
27
+ self._unique_sessions: int = 0 # Number of unique sessions
28
+ self._unique_users: int = 0 # Number of unique users
29
+
30
+ # Aggregated data - flexible storage for computed metrics
31
+ self._metrics: Dict[str, Any] = {}
32
+
33
+ # Additional metadata
34
+ self._content: Dict[str, Any] = {}
35
+
36
+ self._setup_indexes()
37
+
38
+ def _setup_indexes(self):
39
+ # Primary index: summary by ID
40
+ primary: DynamoDBIndex = DynamoDBIndex()
41
+ primary.name = "primary"
42
+ primary.partition_key.attribute_name = "pk"
43
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(
44
+ ("analytics-summary", self.id)
45
+ )
46
+ primary.sort_key.attribute_name = "sk"
47
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("analytics-summary", self.id))
48
+ self.indexes.add_primary(primary)
49
+
50
+ ## GSI: 1
51
+ # GSI: all analytics summaries sorted by period start
52
+ gsi: DynamoDBIndex = DynamoDBIndex()
53
+ gsi.name = "gsi1"
54
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
55
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("analytics-summary", "all"))
56
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
57
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
58
+ ("period", self.period_start_ts)
59
+ )
60
+ self.indexes.add_secondary(gsi)
61
+
62
+ ## GSI: 2
63
+ # GSI: summaries by route/slug for page-specific queries
64
+ gsi: DynamoDBIndex = DynamoDBIndex()
65
+ gsi.name = "gsi2"
66
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
67
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
68
+ ("route", self.route or self.slug)
69
+ )
70
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
71
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
72
+ ("period", self.period_start_ts)
73
+ )
74
+ self.indexes.add_secondary(gsi)
75
+
76
+ ## GSI: 3
77
+ # GSI: summaries by tenant sorted by period
78
+ gsi: DynamoDBIndex = DynamoDBIndex()
79
+ gsi.name = "gsi3"
80
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
81
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("tenant", self.tenant_id))
82
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
83
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
84
+ ("model", "analytics-summary"), ("period", self.period_start_ts)
85
+ )
86
+ self.indexes.add_secondary(gsi)
87
+
88
+ ## GSI: 4
89
+ # GSI: summaries by type and period
90
+ gsi: DynamoDBIndex = DynamoDBIndex()
91
+ gsi.name = "gsi4"
92
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
93
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
94
+ ("analytics-type", self.analytics_type)
95
+ )
96
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
97
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
98
+ ("period", self.period_start_ts)
99
+ )
100
+ self.indexes.add_secondary(gsi)
101
+
102
+ ## GSI: 5
103
+ # GSI: summaries by tenant, type, and period for filtered queries
104
+ gsi: DynamoDBIndex = DynamoDBIndex()
105
+ gsi.name = "gsi5"
106
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
107
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
108
+ ("tenant", self.tenant_id), ("type", self.analytics_type)
109
+ )
110
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
111
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
112
+ ("period", self.period_start_ts)
113
+ )
114
+ self.indexes.add_secondary(gsi)
115
+
116
+ @property
117
+ def route(self) -> str | None:
118
+ return self._route
119
+
120
+ @route.setter
121
+ def route(self, value: str | None):
122
+ self._route = value
123
+
124
+ @property
125
+ def slug(self) -> str | None:
126
+ return self._slug
127
+
128
+ @slug.setter
129
+ def slug(self, value: str | None):
130
+ self._slug = value
131
+
132
+ @property
133
+ def analytics_type(self) -> str:
134
+ return self._analytics_type
135
+
136
+ @analytics_type.setter
137
+ def analytics_type(self, value: str):
138
+ self._analytics_type = value
139
+
140
+ @property
141
+ def period_start_ts(self) -> float | None:
142
+ return self._period_start_ts
143
+
144
+ @period_start_ts.setter
145
+ def period_start_ts(self, value: float | None):
146
+ self._period_start_ts = value
147
+
148
+ @property
149
+ def period_end_ts(self) -> float | None:
150
+ return self._period_end_ts
151
+
152
+ @period_end_ts.setter
153
+ def period_end_ts(self, value: float | None):
154
+ self._period_end_ts = value
155
+
156
+ @property
157
+ def period_type(self) -> str:
158
+ return self._period_type
159
+
160
+ @period_type.setter
161
+ def period_type(self, value: str):
162
+ self._period_type = value
163
+
164
+ @property
165
+ def total_events(self) -> int:
166
+ return self._total_events
167
+
168
+ @total_events.setter
169
+ def total_events(self, value: int):
170
+ self._total_events = value
171
+
172
+ @property
173
+ def unique_sessions(self) -> int:
174
+ return self._unique_sessions
175
+
176
+ @unique_sessions.setter
177
+ def unique_sessions(self, value: int):
178
+ self._unique_sessions = value
179
+
180
+ @property
181
+ def unique_users(self) -> int:
182
+ return self._unique_users
183
+
184
+ @unique_users.setter
185
+ def unique_users(self, value: int):
186
+ self._unique_users = value
187
+
188
+ @property
189
+ def metrics(self) -> Dict[str, Any]:
190
+ """Get metrics (boto3-assist v0.30.0+ auto-converts Decimals to float)."""
191
+ return self._metrics
192
+
193
+ @metrics.setter
194
+ def metrics(self, value: Dict[str, Any]):
195
+ """Set metrics."""
196
+ self._metrics = value if value is not None else {}
197
+
198
+ @property
199
+ def content(self) -> Dict[str, Any]:
200
+ return self._content
201
+
202
+ @content.setter
203
+ def content(self, value: Dict[str, Any]):
204
+ self._content = value
205
+
206
+ # Helper methods
207
+ def get_metric(self, key: str, default: Any = None) -> Any:
208
+ """Get a specific metric value."""
209
+ return self.metrics.get(key, default)
210
+
211
+ def set_metric(self, key: str, value: Any):
212
+ """Set a specific metric value."""
213
+ self.metrics[key] = value
214
+
215
+ def calculate_average(self, metric_key: str) -> float:
216
+ """Calculate average for a metric stored as a list."""
217
+ values = self.metrics.get(metric_key, [])
218
+ if not values or not isinstance(values, list):
219
+ return 0.0
220
+ return sum(values) / len(values)
@@ -0,0 +1,11 @@
1
+ # Analytics Domain Services
2
+
3
+ from .website_analytics_service import WebsiteAnalyticsService
4
+ from .website_analytics_summary_service import WebsiteAnalyticsSummaryService
5
+ from .website_analytics_tally_service import WebsiteAnalyticsTallyService
6
+
7
+ __all__ = [
8
+ "WebsiteAnalyticsService",
9
+ "WebsiteAnalyticsSummaryService",
10
+ "WebsiteAnalyticsTallyService",
11
+ ]
@@ -0,0 +1,232 @@
1
+ # Website Analytics Service
2
+
3
+ from typing import Dict, Any, List, Optional
4
+ from boto3_assist.dynamodb.dynamodb import DynamoDB
5
+ from geek_cafe_saas_sdk.services.database_service import DatabaseService
6
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
7
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError, NotFoundError
8
+ from geek_cafe_saas_sdk.domains.analytics.models import WebsiteAnalytics
9
+
10
+
11
+ class WebsiteAnalyticsService(DatabaseService[WebsiteAnalytics]):
12
+ """Service for WebsiteAnalytics database operations."""
13
+
14
+ def __init__(self, *, dynamodb: DynamoDB = None, table_name: str = None):
15
+ super().__init__(dynamodb=dynamodb, table_name=table_name)
16
+
17
+ def create(self, tenant_id: str, user_id: str, **kwargs) -> ServiceResult[WebsiteAnalytics]:
18
+ """Create a new analytics record."""
19
+ try:
20
+ # Validate required fields
21
+ required_fields = ['analytics_type']
22
+ self._validate_required_fields(kwargs, required_fields)
23
+
24
+ # At least one of route or slug should be provided
25
+ if not kwargs.get('route') and not kwargs.get('slug'):
26
+ raise ValidationError("Either 'route' or 'slug' must be provided", "route")
27
+
28
+ # Create new analytics instance
29
+ analytics = WebsiteAnalytics()
30
+ analytics.tenant_id = tenant_id
31
+ analytics.user_id = user_id
32
+ analytics.created_by_id = user_id
33
+
34
+ # Set analytics fields
35
+ analytics.route = kwargs.get('route')
36
+ analytics.slug = kwargs.get('slug')
37
+ analytics.analytics_type = kwargs.get('analytics_type', 'general')
38
+ analytics.data = kwargs.get('data', {})
39
+ analytics.session_id = kwargs.get('session_id')
40
+ analytics.user_agent = kwargs.get('user_agent')
41
+ analytics.ip_address = kwargs.get('ip_address')
42
+ analytics.referrer = kwargs.get('referrer')
43
+
44
+ # Prepare for save (sets ID and timestamps)
45
+ analytics.prep_for_save()
46
+
47
+ # Save to database
48
+ return self._save_model(analytics)
49
+
50
+ except Exception as e:
51
+ return self._handle_service_exception(e, 'create_analytics', tenant_id=tenant_id, user_id=user_id)
52
+
53
+ # Convenience methods for different analytics types
54
+ def create_page_view(self, tenant_id: str, user_id: str, route: str, **kwargs) -> ServiceResult[WebsiteAnalytics]:
55
+ """Create a page view analytics record."""
56
+ analytics = WebsiteAnalytics()
57
+ analytics.tenant_id = tenant_id
58
+ analytics.user_id = user_id
59
+ analytics.created_by_id = user_id
60
+ analytics.set_page_view(route, **kwargs)
61
+
62
+ # Set optional fields
63
+ analytics.session_id = kwargs.get('session_id')
64
+ analytics.user_agent = kwargs.get('user_agent')
65
+ analytics.ip_address = kwargs.get('ip_address')
66
+ analytics.referrer = kwargs.get('referrer')
67
+
68
+ analytics.prep_for_save()
69
+ return self._save_model(analytics)
70
+
71
+ def create_error_log(self, tenant_id: str, user_id: str, route: str,
72
+ error_message: str, **kwargs) -> ServiceResult[WebsiteAnalytics]:
73
+ """Create an error analytics record."""
74
+ analytics = WebsiteAnalytics()
75
+ analytics.tenant_id = tenant_id
76
+ analytics.user_id = user_id
77
+ analytics.created_by_id = user_id
78
+ analytics.set_error(route, error_message, **kwargs)
79
+
80
+ # Set optional fields
81
+ analytics.session_id = kwargs.get('session_id')
82
+ analytics.user_agent = kwargs.get('user_agent')
83
+ analytics.ip_address = kwargs.get('ip_address')
84
+ analytics.referrer = kwargs.get('referrer')
85
+
86
+ analytics.prep_for_save()
87
+ return self._save_model(analytics)
88
+
89
+ def create_performance_log(self, tenant_id: str, user_id: str, route: str,
90
+ **kwargs) -> ServiceResult[WebsiteAnalytics]:
91
+ """Create a performance analytics record."""
92
+ analytics = WebsiteAnalytics()
93
+ analytics.tenant_id = tenant_id
94
+ analytics.user_id = user_id
95
+ analytics.created_by_id = user_id
96
+ analytics.set_performance(route, **kwargs)
97
+
98
+ # Set optional fields
99
+ analytics.session_id = kwargs.get('session_id')
100
+ analytics.user_agent = kwargs.get('user_agent')
101
+ analytics.ip_address = kwargs.get('ip_address')
102
+ analytics.referrer = kwargs.get('referrer')
103
+
104
+ analytics.prep_for_save()
105
+ return self._save_model(analytics)
106
+
107
+ def create_custom_event(self, tenant_id: str, user_id: str, route: str,
108
+ event_name: str, **kwargs) -> ServiceResult[WebsiteAnalytics]:
109
+ """Create a custom event analytics record."""
110
+ analytics = WebsiteAnalytics()
111
+ analytics.tenant_id = tenant_id
112
+ analytics.user_id = user_id
113
+ analytics.created_by_id = user_id
114
+ analytics.set_custom_event(route, event_name, **kwargs)
115
+
116
+ # Set optional fields
117
+ analytics.session_id = kwargs.get('session_id')
118
+ analytics.user_agent = kwargs.get('user_agent')
119
+ analytics.ip_address = kwargs.get('ip_address')
120
+ analytics.referrer = kwargs.get('referrer')
121
+
122
+ analytics.prep_for_save()
123
+ return self._save_model(analytics)
124
+
125
+ def get_by_id(self, resource_id: str, tenant_id: str, user_id: str) -> ServiceResult[WebsiteAnalytics]:
126
+ """Get analytics record by ID with access control."""
127
+ try:
128
+ analytics = self._get_model_by_id(resource_id, WebsiteAnalytics)
129
+
130
+ if not analytics:
131
+ raise NotFoundError(f"WebsiteAnalytics with ID {resource_id} not found")
132
+
133
+ # Validate tenant access
134
+ if hasattr(analytics, 'tenant_id'):
135
+ self._validate_tenant_access(analytics.tenant_id, tenant_id)
136
+
137
+ return ServiceResult.success_result(analytics)
138
+
139
+ except Exception as e:
140
+ return self._handle_service_exception(e, 'get_analytics', resource_id=resource_id, tenant_id=tenant_id)
141
+
142
+ def update(self, resource_id: str, tenant_id: str, user_id: str,
143
+ updates: Dict[str, Any]) -> ServiceResult[WebsiteAnalytics]:
144
+ """Update analytics record with access control."""
145
+ try:
146
+ # Get existing analytics
147
+ analytics = self._get_model_by_id(resource_id, WebsiteAnalytics)
148
+
149
+ if not analytics:
150
+ raise NotFoundError(f"WebsiteAnalytics with ID {resource_id} not found")
151
+
152
+ # Validate tenant access
153
+ if hasattr(analytics, 'tenant_id'):
154
+ self._validate_tenant_access(analytics.tenant_id, tenant_id)
155
+
156
+ # Apply updates
157
+ for field, value in updates.items():
158
+ if hasattr(analytics, field) and field not in ['id', 'created_utc_ts', 'tenant_id']:
159
+ setattr(analytics, field, value)
160
+
161
+ # Update metadata
162
+ analytics.updated_by_id = user_id
163
+ analytics.prep_for_save() # Updates timestamp
164
+
165
+ # Save updated analytics
166
+ return self._save_model(analytics)
167
+
168
+ except Exception as e:
169
+ return self._handle_service_exception(e, 'update_analytics', resource_id=resource_id, tenant_id=tenant_id)
170
+
171
+ def delete(self, resource_id: str, tenant_id: str, user_id: str) -> ServiceResult[bool]:
172
+ """Delete analytics record with access control."""
173
+ try:
174
+ analytics = self._get_model_by_id(resource_id, WebsiteAnalytics)
175
+
176
+ if not analytics:
177
+ raise NotFoundError(f"WebsiteAnalytics with ID {resource_id} not found")
178
+
179
+ if hasattr(analytics, 'tenant_id'):
180
+ self._validate_tenant_access(analytics.tenant_id, tenant_id)
181
+
182
+ return self._delete_model(analytics)
183
+
184
+ except Exception as e:
185
+ return self._handle_service_exception(e, 'delete_analytics', resource_id=resource_id, tenant_id=tenant_id)
186
+
187
+ def list_by_route(self, route: str, start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalytics]]:
188
+ """List analytics records by route/slug."""
189
+ try:
190
+ model = WebsiteAnalytics()
191
+ model.route = route
192
+ return self._query_by_index(model, "gsi2", start_key=start_key, limit=limit)
193
+ except Exception as e:
194
+ return self._handle_service_exception(e, 'list_analytics_by_route', route=route)
195
+
196
+ def list_by_tenant(self, tenant_id: str, start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalytics]]:
197
+ """List analytics records by tenant."""
198
+ try:
199
+ model = WebsiteAnalytics()
200
+ model.tenant_id = tenant_id
201
+ return self._query_by_index(model, "gsi3", start_key=start_key, limit=limit)
202
+ except Exception as e:
203
+ return self._handle_service_exception(e, 'list_analytics_by_tenant', tenant_id=tenant_id)
204
+
205
+ def list_by_type(self, analytics_type: str, start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalytics]]:
206
+ """List analytics records by type (general, error, performance, custom)."""
207
+ try:
208
+ model = WebsiteAnalytics()
209
+ model.analytics_type = analytics_type
210
+ return self._query_by_index(model, "gsi4", start_key=start_key, limit=limit)
211
+ except Exception as e:
212
+ return self._handle_service_exception(e, 'list_analytics_by_type', analytics_type=analytics_type)
213
+
214
+ def list_by_tenant_and_type(self, tenant_id: str, analytics_type: str,
215
+ start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalytics]]:
216
+ """List analytics records by tenant and type."""
217
+ try:
218
+ model = WebsiteAnalytics()
219
+ model.tenant_id = tenant_id
220
+ model.analytics_type = analytics_type
221
+ return self._query_by_index(model, "gsi5", start_key=start_key, limit=limit)
222
+ except Exception as e:
223
+ return self._handle_service_exception(e, 'list_analytics_by_tenant_and_type',
224
+ tenant_id=tenant_id, analytics_type=analytics_type)
225
+
226
+ def list_all(self, start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalytics]]:
227
+ """List all analytics records."""
228
+ try:
229
+ model = WebsiteAnalytics()
230
+ return self._query_by_index(model, "gsi1", start_key=start_key, limit=limit)
231
+ except Exception as e:
232
+ return self._handle_service_exception(e, 'list_all_analytics')
@@ -0,0 +1,212 @@
1
+ # Website Analytics Summary Service
2
+
3
+ from typing import Dict, Any, List, Optional
4
+ from boto3_assist.dynamodb.dynamodb import DynamoDB
5
+ from geek_cafe_saas_sdk.services.database_service import DatabaseService
6
+ from geek_cafe_saas_sdk.core.service_result import ServiceResult
7
+ from geek_cafe_saas_sdk.core.service_errors import ValidationError, NotFoundError
8
+ from geek_cafe_saas_sdk.domains.analytics.models import WebsiteAnalyticsSummary
9
+
10
+
11
+ class WebsiteAnalyticsSummaryService(DatabaseService[WebsiteAnalyticsSummary]):
12
+ """Service for WebsiteAnalyticsSummary database operations."""
13
+
14
+ def __init__(self, *, dynamodb: DynamoDB = None, table_name: str = None):
15
+ super().__init__(dynamodb=dynamodb, table_name=table_name)
16
+
17
+ def create(self, tenant_id: str, user_id: str, **kwargs) -> ServiceResult[WebsiteAnalyticsSummary]:
18
+ """Create or update (upsert) an analytics summary."""
19
+ try:
20
+ # Validate required fields
21
+ required_fields = ['analytics_type', 'period_start_ts', 'period_end_ts']
22
+ self._validate_required_fields(kwargs, required_fields)
23
+
24
+ # At least one of route or slug should be provided
25
+ if not kwargs.get('route') and not kwargs.get('slug'):
26
+ raise ValidationError("Either 'route' or 'slug' must be provided", "route")
27
+
28
+ # Check if a summary already exists for this route/period
29
+ existing = self._get_by_route_and_period(
30
+ kwargs.get('route') or kwargs.get('slug'),
31
+ kwargs.get('period_start_ts'),
32
+ kwargs.get('analytics_type')
33
+ )
34
+
35
+ if existing:
36
+ # Update the existing summary
37
+ return self._update_existing_summary(existing, tenant_id, user_id, **kwargs)
38
+
39
+ # Create new summary instance
40
+ summary = WebsiteAnalyticsSummary()
41
+ summary.tenant_id = tenant_id
42
+ summary.user_id = user_id
43
+ summary.created_by_id = user_id
44
+
45
+ # Set summary fields
46
+ summary.route = kwargs.get('route')
47
+ summary.slug = kwargs.get('slug')
48
+ summary.analytics_type = kwargs.get('analytics_type', 'general')
49
+ summary.period_start_ts = float(kwargs.get('period_start_ts'))
50
+ summary.period_end_ts = float(kwargs.get('period_end_ts'))
51
+ summary.period_type = kwargs.get('period_type', 'hourly')
52
+
53
+ # Set aggregated metrics
54
+ summary.total_events = int(kwargs.get('total_events', 0) or 0)
55
+ summary.unique_sessions = int(kwargs.get('unique_sessions', 0) or 0)
56
+ summary.unique_users = int(kwargs.get('unique_users', 0) or 0)
57
+ summary.metrics = kwargs.get('metrics', {})
58
+ summary.content = kwargs.get('content', {})
59
+
60
+ # Prepare for save (sets ID and timestamps)
61
+ summary.prep_for_save()
62
+
63
+ # Save to database
64
+ return self._save_model(summary)
65
+
66
+ except Exception as e:
67
+ return self._handle_service_exception(e, 'create_analytics_summary', tenant_id=tenant_id, user_id=user_id)
68
+
69
+ def _update_existing_summary(self, existing_summary: WebsiteAnalyticsSummary,
70
+ tenant_id: str, user_id: str, **kwargs) -> ServiceResult[WebsiteAnalyticsSummary]:
71
+ """Update an existing summary with new data."""
72
+ # Update fields
73
+ existing_summary.period_end_ts = float(kwargs.get('period_end_ts', existing_summary.period_end_ts))
74
+ existing_summary.period_type = kwargs.get('period_type', existing_summary.period_type)
75
+
76
+ # Update aggregated metrics
77
+ existing_summary.total_events = int(kwargs.get('total_events', existing_summary.total_events) or 0)
78
+ existing_summary.unique_sessions = int(kwargs.get('unique_sessions', existing_summary.unique_sessions) or 0)
79
+ existing_summary.unique_users = int(kwargs.get('unique_users', existing_summary.unique_users) or 0)
80
+ existing_summary.metrics = kwargs.get('metrics', existing_summary.metrics or {})
81
+ existing_summary.content = kwargs.get('content', existing_summary.content or {})
82
+
83
+ # Update metadata
84
+ existing_summary.updated_by_id = user_id
85
+ existing_summary.prep_for_save() # Updates timestamp
86
+
87
+ # Save updated summary
88
+ return self._save_model(existing_summary)
89
+
90
+ def _get_by_route_and_period(self, route: str, period_start_ts: float,
91
+ analytics_type: str) -> WebsiteAnalyticsSummary | None:
92
+ """Helper: get a summary by route and period via GSI2."""
93
+ model = WebsiteAnalyticsSummary()
94
+ model.route = route
95
+ result = self._query_by_index(model, "gsi2")
96
+
97
+ if result.success and result.data:
98
+ # Filter by period_start_ts and analytics_type
99
+ for summary in result.data:
100
+ if (summary.period_start_ts == period_start_ts and
101
+ summary.analytics_type == analytics_type):
102
+ return summary
103
+ return None
104
+
105
+ def get_by_id(self, resource_id: str, tenant_id: str, user_id: str) -> ServiceResult[WebsiteAnalyticsSummary]:
106
+ """Get analytics summary by ID with access control."""
107
+ try:
108
+ summary = self._get_model_by_id(resource_id, WebsiteAnalyticsSummary)
109
+
110
+ if not summary:
111
+ raise NotFoundError(f"WebsiteAnalyticsSummary with ID {resource_id} not found")
112
+
113
+ # Validate tenant access
114
+ if hasattr(summary, 'tenant_id'):
115
+ self._validate_tenant_access(summary.tenant_id, tenant_id)
116
+
117
+ return ServiceResult.success_result(summary)
118
+
119
+ except Exception as e:
120
+ return self._handle_service_exception(e, 'get_analytics_summary', resource_id=resource_id, tenant_id=tenant_id)
121
+
122
+ def update(self, resource_id: str, tenant_id: str, user_id: str,
123
+ updates: Dict[str, Any]) -> ServiceResult[WebsiteAnalyticsSummary]:
124
+ """Update analytics summary with access control."""
125
+ try:
126
+ # Get existing summary
127
+ summary = self._get_model_by_id(resource_id, WebsiteAnalyticsSummary)
128
+
129
+ if not summary:
130
+ raise NotFoundError(f"WebsiteAnalyticsSummary with ID {resource_id} not found")
131
+
132
+ # Validate tenant access
133
+ if hasattr(summary, 'tenant_id'):
134
+ self._validate_tenant_access(summary.tenant_id, tenant_id)
135
+
136
+ # Apply updates
137
+ for field, value in updates.items():
138
+ if hasattr(summary, field) and field not in ['id', 'created_utc_ts', 'tenant_id']:
139
+ setattr(summary, field, value)
140
+
141
+ # Update metadata
142
+ summary.updated_by_id = user_id
143
+ summary.prep_for_save() # Updates timestamp
144
+
145
+ # Save updated summary
146
+ return self._save_model(summary)
147
+
148
+ except Exception as e:
149
+ return self._handle_service_exception(e, 'update_analytics_summary', resource_id=resource_id, tenant_id=tenant_id)
150
+
151
+ def delete(self, resource_id: str, tenant_id: str, user_id: str) -> ServiceResult[bool]:
152
+ """Delete analytics summary with access control."""
153
+ try:
154
+ summary = self._get_model_by_id(resource_id, WebsiteAnalyticsSummary)
155
+
156
+ if not summary:
157
+ raise NotFoundError(f"WebsiteAnalyticsSummary with ID {resource_id} not found")
158
+
159
+ if hasattr(summary, 'tenant_id'):
160
+ self._validate_tenant_access(summary.tenant_id, tenant_id)
161
+
162
+ return self._delete_model(summary)
163
+
164
+ except Exception as e:
165
+ return self._handle_service_exception(e, 'delete_analytics_summary', resource_id=resource_id, tenant_id=tenant_id)
166
+
167
+ def list_by_route(self, route: str, start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalyticsSummary]]:
168
+ """List analytics summaries by route/slug."""
169
+ try:
170
+ model = WebsiteAnalyticsSummary()
171
+ model.route = route
172
+ return self._query_by_index(model, "gsi2", start_key=start_key, limit=limit)
173
+ except Exception as e:
174
+ return self._handle_service_exception(e, 'list_summaries_by_route', route=route)
175
+
176
+ def list_by_tenant(self, tenant_id: str, start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalyticsSummary]]:
177
+ """List analytics summaries by tenant."""
178
+ try:
179
+ model = WebsiteAnalyticsSummary()
180
+ model.tenant_id = tenant_id
181
+ return self._query_by_index(model, "gsi3", start_key=start_key, limit=limit)
182
+ except Exception as e:
183
+ return self._handle_service_exception(e, 'list_summaries_by_tenant', tenant_id=tenant_id)
184
+
185
+ def list_by_type(self, analytics_type: str, start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalyticsSummary]]:
186
+ """List analytics summaries by type."""
187
+ try:
188
+ model = WebsiteAnalyticsSummary()
189
+ model.analytics_type = analytics_type
190
+ return self._query_by_index(model, "gsi4", start_key=start_key, limit=limit)
191
+ except Exception as e:
192
+ return self._handle_service_exception(e, 'list_summaries_by_type', analytics_type=analytics_type)
193
+
194
+ def list_by_tenant_and_type(self, tenant_id: str, analytics_type: str,
195
+ start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalyticsSummary]]:
196
+ """List analytics summaries by tenant and type."""
197
+ try:
198
+ model = WebsiteAnalyticsSummary()
199
+ model.tenant_id = tenant_id
200
+ model.analytics_type = analytics_type
201
+ return self._query_by_index(model, "gsi5", start_key=start_key, limit=limit)
202
+ except Exception as e:
203
+ return self._handle_service_exception(e, 'list_summaries_by_tenant_and_type',
204
+ tenant_id=tenant_id, analytics_type=analytics_type)
205
+
206
+ def list_all(self, start_key: dict = None, limit: int = None) -> ServiceResult[List[WebsiteAnalyticsSummary]]:
207
+ """List all analytics summaries."""
208
+ try:
209
+ model = WebsiteAnalyticsSummary()
210
+ return self._query_by_index(model, "gsi1", start_key=start_key, limit=limit)
211
+ except Exception as e:
212
+ return self._handle_service_exception(e, 'list_all_summaries')