trovesuite 1.0.1__py3-none-any.whl → 1.0.24__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.
@@ -13,24 +13,32 @@ logger = get_logger("database")
13
13
 
14
14
  # Database connection pool
15
15
  _connection_pool: Optional[psycopg2.pool.ThreadedConnectionPool] = None
16
- _sqlmodel_engine = None
17
16
 
18
17
 
19
18
  class DatabaseConfig:
20
19
  """Database configuration and connection management"""
21
-
20
+
22
21
  def __init__(self):
23
22
  self.settings = db_settings
23
+ self.database_url = self.settings.database_url
24
24
  self.pool_size = 5
25
25
  self.max_overflow = 10
26
-
27
- @property
28
- def database_url(self):
29
- """Get database URL (lazy evaluation)"""
30
- return self.settings.database_url
31
-
26
+
32
27
  def get_connection_params(self) -> dict:
33
28
  """Get database connection parameters"""
29
+ if self.settings.DATABASE_URL:
30
+ # Use full DATABASE_URL if available
31
+ return {
32
+ "dsn": self.settings.DATABASE_URL,
33
+ "cursor_factory": RealDictCursor,
34
+ "keepalives": 1,
35
+ "keepalives_idle": 30,
36
+ "keepalives_interval": 10,
37
+ "keepalives_count": 5,
38
+ "connect_timeout": 10
39
+ }
40
+
41
+ # fallback to individual DB_* variables
34
42
  return {
35
43
  "host": self.settings.DB_HOST,
36
44
  "port": self.settings.DB_PORT,
@@ -38,9 +46,14 @@ class DatabaseConfig:
38
46
  "user": self.settings.DB_USER,
39
47
  "password": self.settings.DB_PASSWORD,
40
48
  "cursor_factory": RealDictCursor,
41
- "application_name": f"{self.settings.APP_NAME}_{self.settings.ENVIRONMENT}"
49
+ "application_name": f"{self.settings.APP_NAME}_{self.settings.ENVIRONMENT}",
50
+ "keepalives": 1,
51
+ "keepalives_idle": 30,
52
+ "keepalives_interval": 10,
53
+ "keepalives_count": 5,
54
+ "connect_timeout": 10
42
55
  }
43
-
56
+
44
57
  def create_connection_pool(self) -> psycopg2.pool.ThreadedConnectionPool:
45
58
  """Create a connection pool for psycopg2"""
46
59
  try:
@@ -54,7 +67,7 @@ class DatabaseConfig:
54
67
  except Exception as e:
55
68
  logger.error(f"Failed to create database connection pool: {str(e)}")
56
69
  raise
57
-
70
+
58
71
  def test_connection(self) -> bool:
59
72
  """Test database connection"""
60
73
  try:
@@ -78,17 +91,17 @@ db_config = DatabaseConfig()
78
91
  def initialize_database():
79
92
  """Initialize database connections and pool"""
80
93
  global _connection_pool
81
-
94
+
82
95
  try:
83
96
  # Test connection first
84
97
  if not db_config.test_connection():
85
98
  raise Exception("Database connection test failed")
86
-
99
+
87
100
  # Create connection pool
88
101
  _connection_pool = db_config.create_connection_pool()
89
-
102
+
90
103
  logger.info("Database initialization completed successfully")
91
-
104
+
92
105
  except Exception as e:
93
106
  logger.error(f"Database initialization failed: {str(e)}")
94
107
  raise
@@ -98,16 +111,28 @@ def get_connection_pool() -> psycopg2.pool.ThreadedConnectionPool:
98
111
  """Get the database connection pool"""
99
112
  global _connection_pool
100
113
  if _connection_pool is None:
101
- raise Exception("Database not initialized. Call initialize_database() first.")
114
+ error_msg = (
115
+ "Database not initialized. This usually means:\n"
116
+ "1. Missing or incorrect .env file in app/ directory\n"
117
+ "2. Database credentials are wrong\n"
118
+ "3. Database container is not running\n"
119
+ "4. Database initialization failed during startup\n"
120
+ "Please check the startup logs for more details."
121
+ )
122
+ logger.error(error_msg)
123
+ raise Exception(error_msg)
102
124
  return _connection_pool
103
125
 
104
126
 
105
- def get_sqlmodel_engine():
106
- """Get the SQLModel engine"""
107
- global _sqlmodel_engine
108
- if _sqlmodel_engine is None:
109
- raise Exception("Database not initialized. Call initialize_database() first.")
110
- return _sqlmodel_engine
127
+ def _validate_connection(conn) -> bool:
128
+ """Validate if a connection is still alive"""
129
+ try:
130
+ # Test if connection is alive with a simple query
131
+ with conn.cursor() as cursor:
132
+ cursor.execute("SELECT 1")
133
+ return True
134
+ except (psycopg2.OperationalError, psycopg2.InterfaceError):
135
+ return False
111
136
 
112
137
 
113
138
  @contextmanager
@@ -117,52 +142,79 @@ def get_db_connection():
117
142
  conn = None
118
143
  try:
119
144
  conn = pool.getconn()
145
+
146
+ # Validate connection before using it
147
+ if not _validate_connection(conn):
148
+ logger.warning("Stale connection detected, getting new connection")
149
+ pool.putconn(conn, close=True)
150
+ conn = pool.getconn()
151
+
120
152
  logger.debug("Database connection acquired from pool")
121
153
  yield conn
122
154
  except Exception as e:
123
155
  logger.error(f"Database connection error: {str(e)}")
124
156
  if conn:
125
- conn.rollback()
157
+ try:
158
+ # Only rollback if connection is still open
159
+ if not conn.closed:
160
+ conn.rollback()
161
+ except (psycopg2.OperationalError, psycopg2.InterfaceError) as rollback_error:
162
+ logger.warning(f"Could not rollback closed connection: {str(rollback_error)}")
126
163
  raise
127
164
  finally:
128
165
  if conn:
129
- pool.putconn(conn)
130
- logger.debug("Database connection returned to pool")
166
+ try:
167
+ # If connection is broken, close it instead of returning to pool
168
+ if conn.closed:
169
+ pool.putconn(conn, close=True)
170
+ else:
171
+ pool.putconn(conn)
172
+ logger.debug("Database connection returned to pool")
173
+ except Exception as put_error:
174
+ logger.error(f"Error returning connection to pool: {str(put_error)}")
131
175
 
132
176
 
133
177
  @contextmanager
134
178
  def get_db_cursor():
135
179
  """Get a database cursor (context manager)"""
136
180
  with get_db_connection() as conn:
137
- cursor = conn.cursor()
181
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
138
182
  try:
139
183
  yield cursor
140
- conn.commit()
184
+ if not conn.closed:
185
+ conn.commit()
141
186
  except Exception as e:
142
- conn.rollback()
187
+ if not conn.closed:
188
+ try:
189
+ conn.rollback()
190
+ except (psycopg2.OperationalError, psycopg2.InterfaceError) as rollback_error:
191
+ logger.warning(f"Could not rollback transaction on closed connection: {str(rollback_error)}")
143
192
  logger.error(f"Database cursor error: {str(e)}")
144
193
  raise
145
194
  finally:
146
- cursor.close()
195
+ try:
196
+ cursor.close()
197
+ except Exception as close_error:
198
+ logger.warning(f"Error closing cursor: {str(close_error)}")
147
199
 
148
200
 
149
201
  class DatabaseManager:
150
202
  """Database manager for common operations"""
151
-
203
+
152
204
  @staticmethod
153
205
  def execute_query(query: str, params: tuple = None) -> list:
154
206
  """Execute a SELECT query and return results"""
155
207
  with get_db_cursor() as cursor:
156
208
  cursor.execute(query, params)
157
209
  return cursor.fetchall()
158
-
210
+
159
211
  @staticmethod
160
212
  def execute_update(query: str, params: tuple = None) -> int:
161
213
  """Execute an INSERT/UPDATE/DELETE query and return affected rows"""
162
214
  with get_db_cursor() as cursor:
163
215
  cursor.execute(query, params)
164
216
  return cursor.rowcount
165
-
217
+
166
218
  @staticmethod
167
219
  def execute_scalar(query: str, params: tuple = None):
168
220
  """Execute a query and return a single value"""
@@ -178,7 +230,41 @@ class DatabaseManager:
178
230
  # Handle tuple result
179
231
  return result[0] if len(result) > 0 else None
180
232
  return None
181
-
233
+
234
+ @staticmethod
235
+ @contextmanager
236
+ def transaction():
237
+ """
238
+ Context manager for database transactions.
239
+ Wraps multiple operations in a single transaction.
240
+
241
+ Usage:
242
+ with DatabaseManager.transaction() as cursor:
243
+ cursor.execute("INSERT INTO table1 ...")
244
+ cursor.execute("INSERT INTO table2 ...")
245
+ # Auto-commits on success, auto-rollbacks on exception
246
+ """
247
+ with get_db_connection() as conn:
248
+ cursor = conn.cursor(cursor_factory=RealDictCursor)
249
+ try:
250
+ yield cursor
251
+ if not conn.closed:
252
+ conn.commit()
253
+ logger.debug("Transaction committed successfully")
254
+ except Exception as e:
255
+ if not conn.closed:
256
+ try:
257
+ conn.rollback()
258
+ logger.warning(f"Transaction rolled back due to error: {str(e)}")
259
+ except (psycopg2.OperationalError, psycopg2.InterfaceError) as rollback_error:
260
+ logger.error(f"Could not rollback transaction: {str(rollback_error)}")
261
+ raise
262
+ finally:
263
+ try:
264
+ cursor.close()
265
+ except Exception as close_error:
266
+ logger.warning(f"Error closing transaction cursor: {str(close_error)}")
267
+
182
268
  @staticmethod
183
269
  def health_check() -> dict:
184
270
  """Perform database health check"""
@@ -186,7 +272,7 @@ class DatabaseManager:
186
272
  with get_db_cursor() as cursor:
187
273
  cursor.execute("SELECT version(), current_database(), current_user")
188
274
  result = cursor.fetchone()
189
-
275
+
190
276
  if result:
191
277
  # Handle RealDictRow (dictionary-like) result
192
278
  if hasattr(result, 'get'):
@@ -217,5 +303,13 @@ class DatabaseManager:
217
303
  }
218
304
 
219
305
 
220
- # Database initialization is done lazily when needed
221
- # Call initialize_database() explicitly when you need to use the database
306
+ # Database initialization on module import
307
+ try:
308
+ initialize_database()
309
+ except Exception as e:
310
+ logger.error(f"Failed to initialize database on startup: {str(e)}")
311
+ logger.error("⚠️ CRITICAL: Application started without database connection!")
312
+ logger.error("⚠️ Please check your .env file and database configuration")
313
+ logger.error("⚠️ The application will not function properly without a database connection")
314
+ # Don't raise here to allow the app to start even if DB is unavailable
315
+ # But log it clearly so it's obvious what's wrong
@@ -1,153 +1,103 @@
1
1
  import os
2
- import warnings
3
- from typing import Optional
4
-
5
2
  class Settings:
6
- """Settings configuration for TroveSuite Auth Service"""
7
-
8
- # =============================================================================
9
- # DATABASE CONFIGURATION
10
- # =============================================================================
11
- DATABASE_URL: str = os.getenv(
12
- "DATABASE_URL",
13
- "postgresql://username:password@localhost:5432/database_name"
14
- )
15
3
 
16
- # Alternative database configuration
17
- DB_USER: Optional[str] = os.getenv("DB_USER")
18
- DB_HOST: Optional[str] = os.getenv("DB_HOST")
19
- DB_NAME: Optional[str] = os.getenv("DB_NAME")
20
- DB_PORT: int = int(os.getenv("DB_PORT", "5432"))
21
- DB_PASSWORD: Optional[str] = os.getenv("DB_PASSWORD")
22
- ENVIRONMENT: str = os.getenv("ENVIRONMENT", "development")
4
+ # Database URL
5
+ DATABASE_URL: str = os.getenv("DATABASE_URL")
23
6
 
24
- # =============================================================================
25
- # APPLICATION SETTINGS
26
- # =============================================================================
27
- APP_NAME: str = os.getenv("APP_NAME", "TroveSuite Auth Service")
28
- DEBUG: bool = os.getenv("DEBUG", "False").lower() == "true"
7
+ DB_USER: str = os.getenv("DB_USER")
8
+ DB_HOST: str = os.getenv("DB_HOST")
9
+ DB_NAME: str = os.getenv("DB_NAME")
10
+ DB_PORT: str = os.getenv("DB_PORT")
11
+ DB_PASSWORD: str = os.getenv("DB_PASSWORD")
29
12
 
30
- # =============================================================================
31
- # SECURITY SETTINGS
32
- # =============================================================================
33
- ALGORITHM: str = os.getenv("ALGORITHM", "HS256")
34
- SECRET_KEY: str = os.getenv("SECRET_KEY", "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7")
35
- ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "60"))
13
+ # CORS Endpoint
14
+ # LOCAL_HOST_URL = os.getenv("LOCAL_HOST_URL")
15
+ # FRONTEND_SERVER_URL_1 = os.getenv("FRONTEND_SERVER_URL")
16
+ # FRONTEND_SERVER_URL_2 = os.getenv("FRONTEND_SERVER_URL")
17
+
18
+ # Application settings
19
+ DEBUG: bool = os.getenv("DEBUG", "True").lower() in ("true",1)
20
+ APP_NAME: str = os.getenv("APP_NAME", "Python Template API")
21
+ APP_VERSION: str = os.getenv("APP_VERSION", "1.0.0")
36
22
 
37
- # =============================================================================
38
- # LOGGING SETTINGS
39
- # =============================================================================
23
+ # Logging settings
40
24
  LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
41
25
  LOG_FORMAT: str = os.getenv("LOG_FORMAT", "detailed") # detailed, json, simple
42
- LOG_TO_FILE: bool = os.getenv("LOG_TO_FILE", "False").lower() == "false"
26
+ LOG_TO_FILE: bool = os.getenv("LOG_TO_FILE", "False").lower() in ("true", 1)
43
27
  LOG_MAX_SIZE: int = int(os.getenv("LOG_MAX_SIZE", "10485760")) # 10MB
44
28
  LOG_BACKUP_COUNT: int = int(os.getenv("LOG_BACKUP_COUNT", "5"))
45
29
  LOG_DIR: str = os.getenv("LOG_DIR", "logs")
46
-
30
+
31
+ # Security settings
32
+ ALGORITHM: str = os.getenv("ALGORITHM")
33
+ SECRET_KEY: str = os.getenv("SECRET_KEY")
34
+ ENVIRONMENT: str = os.getenv("ENVIRONMENT")
35
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "120"))
36
+
47
37
  # =============================================================================
48
- # DATABASE TABLE NAMES
38
+ # SHARED TABLES (core_platform schema)
49
39
  # =============================================================================
50
- # Main schema tables
51
- MAIN_TENANTS_TABLE: str = os.getenv("MAIN_TENANTS_TABLE", "tenants")
52
- ROLE_PERMISSIONS_TABLE: str = os.getenv("ROLE_PERMISSIONS_TABLE", "role_permissions")
53
-
54
- # Tenant-specific tables (used in queries with tenant schema)
55
- TENANT_LOGIN_SETTINGS_TABLE: str = os.getenv("TENANT_LOGIN_SETTINGS_TABLE", "login_settings")
56
- USER_GROUPS_TABLE: str = os.getenv("USER_GROUPS_TABLE", "user_groups")
57
- ASSIGN_ROLES_TABLE: str = os.getenv("ASSIGN_ROLES_TABLE", "assign_roles")
40
+ CORE_PLATFORM_TENANTS_TABLE = os.getenv("CORE_PLATFORM_TENANTS_TABLE", "core_platform.cp_tenants")
41
+ CORE_PLATFORM_TENANT_RESOURCE_ID_TABLE = os.getenv("CORE_PLATFORM_TENANT_RESOURCE_ID_TABLE", "core_platform.cp_resource_ids")
42
+ CORE_PLATFORM_SUBSCRIPTIONS_TABLE = os.getenv("CORE_PLATFORM_SUBSCRIPTIONS_TABLE", "core_platform.cp_subscriptions")
43
+ CORE_PLATFORM_APPS_TABLE = os.getenv("CORE_PLATFORM_APPS_TABLE", "core_platform.cp_apps")
44
+ CORE_PLATFORM_USERS_TABLE = os.getenv("CORE_PLATFORM_USERS_TABLE", "core_platform.cp_users")
45
+ CORE_PLATFORM_RESOURCE_TYPES_TABLE = os.getenv("CORE_PLATFORM_RESOURCE_TYPES_TABLE", "core_platform.cp_resource_types")
46
+ CORE_PLATFORM_RESOURCE_ID_TABLE = os.getenv("CORE_PLATFORM_RESOURCE_ID_TABLE", "core_platform.cp_shared_resource_ids")
47
+ CORE_PLATFORM_RESOURCES_TABLE = os.getenv("CORE_PLATFORM_RESOURCES_TABLE", "core_platform.resources")
48
+ CORE_PLATFORM_PERMISSIONS_TABLE = os.getenv("CORE_PLATFORM_PERMISSIONS_TABLE", "core_platform.cp_permissions")
49
+ CORE_PLATFORM_ROLES_TABLE = os.getenv("CORE_PLATFORM_ROLES_TABLE", "core_platform.cp_roles")
50
+ CORE_PLATFORM_ROLE_PERMISSIONS_TABLE = os.getenv("CORE_PLATFORM_ROLE_PERMISSIONS_TABLE", "core_platform.cp_role_permissions")
51
+ CORE_PLATFORM_USER_SUBSCRIPTIONS_TABLE = os.getenv("CORE_PLATFORM_USER_SUBSCRIPTIONS_TABLE", "core_platform.cp_user_subscriptions")
52
+ CORE_PLATFORM_USER_SUBSCRIPTION_HISTORY_TABLE = os.getenv("CORE_PLATFORM_USER_SUBSCRIPTION_HISTORY_TABLE", "core_platform.cp_user_subscription_histories")
53
+ CORE_PLATFORM_OTP = os.getenv("CORE_PLATFORM_OTP", "core_platform.cp_otps")
54
+ CORE_PLATFORM_PASSWORD_POLICY = os.getenv("CORE_PLATFORM_PASSWORD_POLICY", "core_platform.cp_password_policies")
55
+ CORE_PLATFORM_MULTI_FACTOR_SETTINGS = os.getenv("CORE_PLATFORM_MULTI_FACTOR_SETTINGS", "core_platform.cp_multi_factor_settings")
56
+ CORE_PLATFORM_USER_LOGIN_TRACKING = os.getenv("CORE_PLATFORM_USER_LOGIN_TRACKING", "core_platform.cp_user_login_tracking")
57
+ CORE_PLATFORM_ENTERPRISE_SUBSCRIPTIONS_TABLE = os.getenv("CORE_PLATFORM_ENTERPRISE_SUBSCRIPTIONS_TABLE", "core_platform.cp_enterprise_subscriptions")
58
+ CORE_PLATFORM_CHANGE_PASSWORD_POLICY_TABLE = os.getenv("CORE_PLATFORM_CHANGE_PASSWORD_POLICY_TABLE", "core_platform.cp_change_password_policy")
59
+ CORE_PLATFORM_APP_FEATURES_TABLE = os.getenv("CORE_PLATFORM_APP_FEATURES_TABLE", "core_platform.cp_app_features")
58
60
 
59
61
  # =============================================================================
60
- # AZURE CONFIGURATION (Optional - for queue functionality)
62
+ # CORE PLATFORM TABLES (prefixed with cp_, now in core_platform schema with tenant_id)
61
63
  # =============================================================================
62
- STORAGE_ACCOUNT_NAME: str = os.getenv("STORAGE_ACCOUNT_NAME", "")
63
- USER_ASSIGNED_MANAGED_IDENTITY: str = os.getenv("USER_ASSIGNED_MANAGED_IDENTITY", "")
64
-
64
+ # NOTE: These tables have been renamed from tenant_ prefix to cp_ (core platform).
65
+ # All tables include tenant_id column for multi-tenant isolation.
66
+ # Tables with is_system column can contain both user and system data.
67
+ # =============================================================================
68
+ CORE_PLATFORM_GROUPS_TABLE = os.getenv("CORE_PLATFORM_GROUPS_TABLE", "core_platform.cp_groups")
69
+ CORE_PLATFORM_LOGIN_SETTINGS_TABLE = os.getenv("CORE_PLATFORM_LOGIN_SETTINGS_TABLE", "core_platform.cp_login_settings")
70
+ CORE_PLATFORM_RESOURCES_TABLE = os.getenv("CORE_PLATFORM_RESOURCES_TABLE", "core_platform.cp_resources")
71
+ CORE_PLATFORM_ASSIGN_ROLES_TABLE = os.getenv("CORE_PLATFORM_ASSIGN_ROLES_TABLE", "core_platform.cp_assign_roles")
72
+ CORE_PLATFORM_RESOURCE_ID_TABLE = os.getenv("CORE_PLATFORM_RESOURCE_ID_TABLE", "core_platform.cp_resource_ids")
73
+ CORE_PLATFORM_SUBSCRIPTION_HISTORY_TABLE = os.getenv("CORE_PLATFORM_SUBSCRIPTION_HISTORY_TABLE", "core_platform.cp_user_subscription_histories")
74
+ CORE_PLATFORM_RESOURCE_DELETION_CHAT_HISTORY_TABLE = os.getenv("CORE_PLATFORM_RESOURCE_DELETION_CHAT_HISTORY_TABLE", "core_platform.cp_resource_deletion_chat_histories")
75
+ CORE_PLATFORM_USER_GROUPS_TABLE = os.getenv("CORE_PLATFORM_USER_GROUPS_TABLE", "core_platform.cp_user_groups")
76
+ CORE_PLATFORM_ACTIVITY_LOGS_TABLE = os.getenv("CORE_PLATFORM_ACTIVITY_LOGS_TABLE", "core_platform.cp_activity_logs")
77
+ CORE_PLATFORM_ORGANIZATIONS_TABLE = os.getenv("CORE_PLATFORM_ORGANIZATIONS_TABLE", "core_platform.cp_organizations")
78
+ CORE_PLATFORM_BUSINESSES_TABLE = os.getenv("CORE_PLATFORM_BUSINESSES_TABLE", "core_platform.cp_businesses")
79
+ CORE_PLATFORM_BUSINESS_APPS_TABLE = os.getenv("CORE_PLATFORM_BUSINESS_APPS_TABLE", "core_platform.cp_business_apps")
80
+ CORE_PLATFORM_LOCATIONS_TABLE = os.getenv("CORE_PLATFORM_LOCATIONS_TABLE", "core_platform.cp_locations")
81
+ CORE_PLATFORM_ASSIGN_LOCATIONS_TABLE = os.getenv("CORE_PLATFORM_ASSIGN_LOCATIONS_TABLE", "core_platform.cp_assign_locations")
82
+ CORE_PLATFORM_UNIT_OF_MEASURE_TABLE = os.getenv("CORE_PLATFORM_UNIT_OF_MEASURE_TABLE", "core_platform.cp_unit_of_measures")
83
+ CORE_PLATFORM_CURRENCY = os.getenv("CORE_PLATFORM_CURRENCY", "core_platform.cp_currencies")
84
+ CORE_PLATFORM_THEMES_TABLE = os.getenv("CORE_PLATFORM_THEMES_TABLE", "core_platform.cp_themes")
85
+
86
+ # Mail Configurations
87
+ MAIL_SENDER_EMAIL=os.getenv("MAIL_SENDER_EMAIL")
88
+ MAIL_SENDER_PWD=os.getenv("MAIL_SENDER_PWD")
89
+
90
+ # Application Configurations
91
+ APP_URL=os.getenv("APP_URL", "https://trovesuite.com")
92
+ USER_ASSIGNED_MANAGED_IDENTITY=os.getenv("USER_ASSIGNED_MANAGED_IDENTITY")
93
+
65
94
  @property
66
95
  def database_url(self) -> str:
67
- """Get the database URL, either from DATABASE_URL or constructed from individual components"""
68
- if self.DATABASE_URL != "postgresql://username:password@localhost:5432/database_name":
96
+ if self.DATABASE_URL:
69
97
  return self.DATABASE_URL
70
-
71
- # Validate individual components
72
- if not all([self.DB_USER, self.DB_HOST, self.DB_NAME, self.DB_PASSWORD]):
73
- missing = []
74
- if not self.DB_USER:
75
- missing.append("DB_USER")
76
- if not self.DB_HOST:
77
- missing.append("DB_HOST")
78
- if not self.DB_NAME:
79
- missing.append("DB_NAME")
80
- if not self.DB_PASSWORD:
81
- missing.append("DB_PASSWORD")
82
-
83
- raise ValueError(
84
- f"Database configuration incomplete. Missing environment variables: {', '.join(missing)}. "
85
- f"Please set these variables or provide a complete DATABASE_URL."
86
- )
87
-
88
- return f"postgresql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{self.DB_PORT}/{self.DB_NAME}"
89
-
90
- def validate_configuration(self) -> None:
91
- """Validate the current configuration and warn about potential issues"""
92
- warnings_list = []
93
-
94
- # Check for default secret key
95
- if self.SECRET_KEY == "09d25e094faa6ca2556c818166b7a9563b93f7099f6f0f4caa6cf63b88e8d3e7":
96
- warnings_list.append(
97
- "SECRET_KEY is using the default value. This is insecure for production. "
98
- "Please set a strong, unique SECRET_KEY environment variable."
99
- )
100
-
101
- # Check for development environment in production-like settings
102
- if self.ENVIRONMENT == "development" and self.DEBUG is False:
103
- warnings_list.append(
104
- "ENVIRONMENT is set to 'development' but DEBUG is False. "
105
- "Consider setting ENVIRONMENT to 'production' for production deployments."
106
- )
107
-
108
- # Check database configuration
109
- try:
110
- self.database_url
111
- except ValueError as e:
112
- warnings_list.append(f"Database configuration issue: {str(e)}")
113
-
114
- # Check for missing Azure configuration if needed
115
- if self.ENVIRONMENT == "production" and not self.STORAGE_ACCOUNT_NAME:
116
- warnings_list.append(
117
- "STORAGE_ACCOUNT_NAME is not set. Azure queue functionality may not work properly."
118
- )
119
-
120
- # Emit warnings
121
- for warning in warnings_list:
122
- warnings.warn(warning, UserWarning)
123
-
124
- def get_configuration_summary(self) -> dict:
125
- """Get a summary of the current configuration (excluding sensitive data)"""
126
- return {
127
- "app_name": self.APP_NAME,
128
- "environment": self.ENVIRONMENT,
129
- "debug": self.DEBUG,
130
- "database_host": self.DB_HOST,
131
- "database_port": self.DB_PORT,
132
- "database_name": self.DB_NAME,
133
- "database_user": self.DB_USER,
134
- "log_level": self.LOG_LEVEL,
135
- "log_format": self.LOG_FORMAT,
136
- "log_to_file": self.LOG_TO_FILE,
137
- "algorithm": self.ALGORITHM,
138
- "access_token_expire_minutes": self.ACCESS_TOKEN_EXPIRE_MINUTES,
139
- "MAIN_TENANTS_TABLE": self.MAIN_TENANTS_TABLE,
140
- "role_permissions_table": self.ROLE_PERMISSIONS_TABLE,
141
- "TENANT_LOGIN_SETTINGS_TABLE": self.TENANT_LOGIN_SETTINGS_TABLE,
142
- "user_groups_table": self.USER_GROUPS_TABLE,
143
- "assign_roles_table": self.ASSIGN_ROLES_TABLE,
144
- }
145
98
 
146
- # Global settings instance
147
- db_settings = Settings()
99
+ port = int(self.DB_PORT) if self.DB_PORT else 5432
100
+ return f"postgresql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{port}/{self.DB_NAME}"
148
101
 
149
- # Validate configuration on import
150
- try:
151
- db_settings.validate_configuration()
152
- except Exception as e:
153
- warnings.warn("Configuration validation failed: %s", str(e), UserWarning)
102
+ # Global settings instance
103
+ db_settings = Settings()
@@ -1,8 +1,8 @@
1
1
  from fastapi import APIRouter
2
- from src.entities.shared.shared_response import Respons
3
- from src.configs.settings import db_settings
4
- from src.configs.database import DatabaseManager
5
- from src.configs.logging import get_logger
2
+ from .sh_response import Respons
3
+ from ..configs.settings import db_settings
4
+ from ..configs.database import DatabaseManager
5
+ from ..configs.logging import get_logger
6
6
 
7
7
  health_check_router = APIRouter(tags=["Health Path"])
8
8
  logger = get_logger("health")
@@ -0,0 +1,14 @@
1
+ """
2
+ TroveSuite Notification Service
3
+
4
+ Provides email and SMS notification capabilities for TroveSuite applications.
5
+ """
6
+
7
+ from .notification_service import NotificationService
8
+ from .notification_write_dto import NotificationEmailServiceWriteDto, NotificationSMSServiceWriteDto
9
+
10
+ __all__ = [
11
+ "NotificationService",
12
+ "NotificationEmailServiceWriteDto",
13
+ "NotificationSMSServiceWriteDto"
14
+ ]
@@ -0,0 +1,13 @@
1
+ from typing import List, Optional, Union
2
+ from pydantic import BaseModel
3
+
4
+ class NotificationEmailBase(BaseModel):
5
+ sender_email: str
6
+ receiver_email: Union[str, List[str]]
7
+ password: str
8
+ subject: str
9
+ text_message: str
10
+ html_message: Optional[str] = None
11
+
12
+ class NotificationSMSBase(BaseModel):
13
+ pass
@@ -0,0 +1,21 @@
1
+ from .notification_write_dto import (
2
+ NotificationEmailControllerWriteDto,
3
+ NotificationSMSControllerWriteDto
4
+ )
5
+ from .notification_read_dto import (
6
+ NotificationEmailControllerReadDto,
7
+ NotificationSMSControllerReadDto
8
+ )
9
+ from .notification_service import NotificationService
10
+ from ..entities.sh_response import Respons
11
+ from fastapi import APIRouter
12
+
13
+ notification_router = APIRouter(tags=["Notification"])
14
+
15
+ @notification_router.post("/send_email", response_model=Respons[NotificationEmailControllerReadDto])
16
+ async def send_email(data: NotificationEmailControllerWriteDto):
17
+ return NotificationService.send_email(data=data)
18
+
19
+ @notification_router.post("/send_sms", response_model=Respons[NotificationSMSControllerReadDto])
20
+ async def send_sms(data: NotificationSMSControllerWriteDto):
21
+ return NotificationService.send_sms(data=data)
@@ -0,0 +1,21 @@
1
+ from .notification_base import (
2
+ NotificationEmailBase,
3
+ NotificationSMSBase
4
+ )
5
+ from pydantic import BaseModel
6
+
7
+ # EMAIL Notification
8
+
9
+ class NotificationEmailControllerReadDto(NotificationEmailBase):
10
+ pass
11
+
12
+ class NotificationEmailServiceReadDto(BaseModel):
13
+ pass
14
+
15
+ # SMS Notification
16
+
17
+ class NotificationSMSControllerReadDto(NotificationSMSBase):
18
+ pass
19
+
20
+ class NotificationSMSServiceReadDto(BaseModel):
21
+ pass