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,324 @@
1
+ from boto3_assist.dynamodb.dynamodb_index import DynamoDBIndex, DynamoDBKey
2
+ from typing import List, Optional, Dict, Any
3
+ from geek_cafe_saas_sdk.models.base_model import BaseModel
4
+
5
+
6
+ class EventAttendee(BaseModel):
7
+ """
8
+ Event attendee/invitation record (adjacent record pattern).
9
+
10
+ Similar to ChatChannelMember, this enables:
11
+ - Unlimited attendees (no DynamoDB item size limit)
12
+ - Full RSVP tracking with history
13
+ - Multiple hosts/co-organizers per event
14
+ - Guest +1 support
15
+ - Custom registration data
16
+ - Check-in tracking
17
+
18
+ Each attendee gets their own record with their RSVP status and role.
19
+ """
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+ # Mark as multi-record (adjacent to Event)
24
+ self.is_multi_record = True
25
+
26
+ # Relationship
27
+ self._event_id: str | None = None
28
+ self._user_id: str | None = None # Also in BaseModel, but key for this model
29
+
30
+ # RSVP Status
31
+ self._rsvp_status: str | None = None # invited, accepted, declined, tentative, waitlist (defaults to "invited" when saved)
32
+ self._invited_at_utc_ts: float | None = None
33
+ self._responded_at_utc_ts: float | None = None
34
+ self._invited_by_user_id: str | None = None
35
+
36
+ # Role (enables multiple hosts)
37
+ self._role: str | None = None # organizer, co_host, attendee, speaker, volunteer (defaults to "attendee" when saved)
38
+
39
+ # Guest +1
40
+ self._plus_one_count: int = 0
41
+ self._plus_one_names: List[str] = []
42
+
43
+ # Check-in
44
+ self._checked_in: bool = False
45
+ self._checked_in_at_utc_ts: float | None = None
46
+ self._checked_in_by_user_id: str | None = None
47
+
48
+ # Custom Registration
49
+ self._registration_data: Dict[str, Any] = {} # Answers to custom fields
50
+ self._registration_notes: str | None = None
51
+
52
+ # Notifications
53
+ self._notification_preferences: Dict[str, bool] = {
54
+ "event_updates": True,
55
+ "reminders": True,
56
+ "cancellations": True
57
+ }
58
+ self._reminder_sent: bool = False
59
+ self._reminder_sent_at_utc_ts: float | None = None
60
+
61
+ self._setup_indexes()
62
+
63
+ def _setup_indexes(self):
64
+ """Setup DynamoDB indexes for event attendee queries."""
65
+
66
+ # Primary index: attendee by composite ID
67
+ primary: DynamoDBIndex = DynamoDBIndex()
68
+ primary.name = "primary"
69
+ primary.partition_key.attribute_name = "pk"
70
+ primary.partition_key.value = lambda: DynamoDBKey.build_key(("event", self.event_id), ("user", self.user_id))
71
+ primary.sort_key.attribute_name = "sk"
72
+ primary.sort_key.value = lambda: DynamoDBKey.build_key(("attendee", self.id))
73
+ self.indexes.add_primary(primary)
74
+
75
+ ## GSI1: Attendees by event (most common query)
76
+ gsi: DynamoDBIndex = DynamoDBIndex()
77
+ gsi.name = "gsi1"
78
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
79
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("event", self.event_id))
80
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
81
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
82
+ ("role", self.role),
83
+ ("status", self.rsvp_status),
84
+ ("ts", self.invited_at_utc_ts)
85
+ )
86
+ self.indexes.add_secondary(gsi)
87
+
88
+ ## GSI2: User's events (my RSVPs)
89
+ gsi: DynamoDBIndex = DynamoDBIndex()
90
+ gsi.name = "gsi2"
91
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
92
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("user", self.user_id))
93
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
94
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(
95
+ ("status", self.rsvp_status),
96
+ ("ts", self.invited_at_utc_ts)
97
+ )
98
+ self.indexes.add_secondary(gsi)
99
+
100
+ ## GSI3: Event hosts (organizers and co-hosts)
101
+ gsi: DynamoDBIndex = DynamoDBIndex()
102
+ gsi.name = "gsi3"
103
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
104
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
105
+ ("event", self.event_id),
106
+ ("role", self.role if self.role in ["organizer", "co_host"] else None)
107
+ )
108
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
109
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(("ts", self.invited_at_utc_ts))
110
+ self.indexes.add_secondary(gsi)
111
+
112
+ ## GSI4: Confirmed attendees (for display)
113
+ gsi: DynamoDBIndex = DynamoDBIndex()
114
+ gsi.name = "gsi4"
115
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
116
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(
117
+ ("event", self.event_id),
118
+ ("status", self.rsvp_status if self.rsvp_status == "accepted" else None)
119
+ )
120
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
121
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(("ts", self.responded_at_utc_ts))
122
+ self.indexes.add_secondary(gsi)
123
+
124
+ ## GSI5: Who invited this user (networking)
125
+ gsi: DynamoDBIndex = DynamoDBIndex()
126
+ gsi.name = "gsi5"
127
+ gsi.partition_key.attribute_name = f"{gsi.name}_pk"
128
+ gsi.partition_key.value = lambda: DynamoDBKey.build_key(("inviter", self.invited_by_user_id))
129
+ gsi.sort_key.attribute_name = f"{gsi.name}_sk"
130
+ gsi.sort_key.value = lambda: DynamoDBKey.build_key(("ts", self.invited_at_utc_ts))
131
+ self.indexes.add_secondary(gsi)
132
+
133
+ # Properties - Relationship
134
+ @property
135
+ def event_id(self) -> str | None:
136
+ """Event ID."""
137
+ return self._event_id
138
+
139
+ @event_id.setter
140
+ def event_id(self, value: str | None):
141
+ self._event_id = value
142
+
143
+ # user_id is inherited from BaseModel but we need it for this model
144
+ # No need to override, just use self.user_id from BaseModel
145
+
146
+ # Properties - RSVP Status
147
+ @property
148
+ def rsvp_status(self) -> str:
149
+ """RSVP status: invited, accepted, declined, tentative, waitlist."""
150
+ return self._rsvp_status
151
+
152
+ @rsvp_status.setter
153
+ def rsvp_status(self, value: str | None):
154
+ if value is None or value in ["invited", "accepted", "declined", "tentative", "waitlist"]:
155
+ self._rsvp_status = value
156
+
157
+ @property
158
+ def invited_at_utc_ts(self) -> float | None:
159
+ """When invitation was sent (UTC timestamp)."""
160
+ return self._invited_at_utc_ts
161
+
162
+ @invited_at_utc_ts.setter
163
+ def invited_at_utc_ts(self, value: float | None):
164
+ self._invited_at_utc_ts = value
165
+
166
+ @property
167
+ def responded_at_utc_ts(self) -> float | None:
168
+ """When user responded to RSVP (UTC timestamp)."""
169
+ return self._responded_at_utc_ts
170
+
171
+ @responded_at_utc_ts.setter
172
+ def responded_at_utc_ts(self, value: float | None):
173
+ self._responded_at_utc_ts = value
174
+
175
+ @property
176
+ def invited_by_user_id(self) -> str | None:
177
+ """Who invited this attendee."""
178
+ return self._invited_by_user_id
179
+
180
+ @invited_by_user_id.setter
181
+ def invited_by_user_id(self, value: str | None):
182
+ self._invited_by_user_id = value
183
+
184
+ # Properties - Role
185
+ @property
186
+ def role(self) -> str:
187
+ """Attendee role: organizer, co_host, attendee, speaker, volunteer."""
188
+ return self._role
189
+
190
+ @role.setter
191
+ def role(self, value: str | None):
192
+ if value is None or value in ["organizer", "co_host", "attendee", "speaker", "volunteer"]:
193
+ self._role = value
194
+
195
+ # Properties - Guest +1
196
+ @property
197
+ def plus_one_count(self) -> int:
198
+ """Number of +1 guests."""
199
+ return self._plus_one_count
200
+
201
+ @plus_one_count.setter
202
+ def plus_one_count(self, value: int):
203
+ self._plus_one_count = max(0, value)
204
+
205
+ @property
206
+ def plus_one_names(self) -> List[str]:
207
+ """Names of +1 guests."""
208
+ return self._plus_one_names
209
+
210
+ @plus_one_names.setter
211
+ def plus_one_names(self, value: List[str] | None):
212
+ self._plus_one_names = value if isinstance(value, list) else []
213
+
214
+ # Properties - Check-in
215
+ @property
216
+ def checked_in(self) -> bool:
217
+ """Has attendee checked in."""
218
+ return self._checked_in
219
+
220
+ @checked_in.setter
221
+ def checked_in(self, value: bool):
222
+ self._checked_in = bool(value)
223
+
224
+ @property
225
+ def checked_in_at_utc_ts(self) -> float | None:
226
+ """When attendee checked in (UTC timestamp)."""
227
+ return self._checked_in_at_utc_ts
228
+
229
+ @checked_in_at_utc_ts.setter
230
+ def checked_in_at_utc_ts(self, value: float | None):
231
+ self._checked_in_at_utc_ts = value
232
+
233
+ @property
234
+ def checked_in_by_user_id(self) -> str | None:
235
+ """Who checked in this attendee."""
236
+ return self._checked_in_by_user_id
237
+
238
+ @checked_in_by_user_id.setter
239
+ def checked_in_by_user_id(self, value: str | None):
240
+ self._checked_in_by_user_id = value
241
+
242
+ # Properties - Custom Registration
243
+ @property
244
+ def registration_data(self) -> Dict[str, Any]:
245
+ """Custom registration field answers."""
246
+ return self._registration_data
247
+
248
+ @registration_data.setter
249
+ def registration_data(self, value: Dict[str, Any] | None):
250
+ self._registration_data = value if isinstance(value, dict) else {}
251
+
252
+ @property
253
+ def registration_notes(self) -> str | None:
254
+ """Additional registration notes."""
255
+ return self._registration_notes
256
+
257
+ @registration_notes.setter
258
+ def registration_notes(self, value: str | None):
259
+ self._registration_notes = value
260
+
261
+ # Properties - Notifications
262
+ @property
263
+ def notification_preferences(self) -> Dict[str, bool]:
264
+ """Notification preferences."""
265
+ return self._notification_preferences
266
+
267
+ @notification_preferences.setter
268
+ def notification_preferences(self, value: Dict[str, bool] | None):
269
+ self._notification_preferences = value if isinstance(value, dict) else {}
270
+
271
+ @property
272
+ def reminder_sent(self) -> bool:
273
+ """Has reminder been sent."""
274
+ return self._reminder_sent
275
+
276
+ @reminder_sent.setter
277
+ def reminder_sent(self, value: bool):
278
+ self._reminder_sent = bool(value)
279
+
280
+ @property
281
+ def reminder_sent_at_utc_ts(self) -> float | None:
282
+ """When reminder was sent (UTC timestamp)."""
283
+ return self._reminder_sent_at_utc_ts
284
+
285
+ @reminder_sent_at_utc_ts.setter
286
+ def reminder_sent_at_utc_ts(self, value: float | None):
287
+ self._reminder_sent_at_utc_ts = value
288
+
289
+ # Helper Methods
290
+ def is_organizer(self) -> bool:
291
+ """Check if this attendee is an organizer."""
292
+ return self._role == "organizer"
293
+
294
+ def is_host(self) -> bool:
295
+ """Check if this attendee is organizer or co-host."""
296
+ return self._role in ["organizer", "co_host"]
297
+
298
+ def has_accepted(self) -> bool:
299
+ """Check if attendee accepted invitation."""
300
+ return self._rsvp_status == "accepted"
301
+
302
+ def has_declined(self) -> bool:
303
+ """Check if attendee declined invitation."""
304
+ return self._rsvp_status == "declined"
305
+
306
+ def is_on_waitlist(self) -> bool:
307
+ """Check if attendee is on waitlist."""
308
+ return self._rsvp_status == "waitlist"
309
+
310
+ def has_responded(self) -> bool:
311
+ """Check if attendee has responded to invitation."""
312
+ return self._rsvp_status != "invited"
313
+
314
+ def has_plus_ones(self) -> bool:
315
+ """Check if attendee is bringing +1 guests."""
316
+ return self._plus_one_count > 0
317
+
318
+ def total_attendee_count(self) -> int:
319
+ """Total number of people attending (including +1s)."""
320
+ return 1 + (self._plus_one_count or 0)
321
+
322
+ def can_check_in(self) -> bool:
323
+ """Check if attendee can check in (must be accepted)."""
324
+ return self.has_accepted() and not self._checked_in
@@ -0,0 +1,9 @@
1
+ # Events Domain Services
2
+
3
+ from .event_attendee_service import EventAttendeeService
4
+ from .event_service import EventService
5
+
6
+ __all__ = [
7
+ "EventAttendeeService",
8
+ "EventService",
9
+ ]