trovesuite 1.0.9__py3-none-any.whl → 1.0.10__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.
trovesuite/__init__.py CHANGED
@@ -1,16 +1,21 @@
1
1
  """
2
- TroveSuite Auth Package
2
+ TroveSuite Package
3
3
 
4
- A comprehensive authentication and authorization service for ERP systems.
5
- Provides JWT token validation, user authorization, and permission checking.
4
+ A comprehensive authentication, authorization, notification, and storage service for ERP systems.
5
+ Provides JWT token validation, user authorization, permission checking, notification capabilities,
6
+ and Azure Storage blob management.
6
7
  """
7
8
 
8
9
  from .auth import AuthService
10
+ from .notification import NotificationService
11
+ from .storage import StorageService
9
12
 
10
- __version__ = "1.0.8"
13
+ __version__ = "1.0.7"
11
14
  __author__ = "Bright Debrah Owusu"
12
15
  __email__ = "owusu.debrah@deladetech.com"
13
16
 
14
17
  __all__ = [
15
- "AuthService"
18
+ "AuthService",
19
+ "NotificationService",
20
+ "StorageService"
16
21
  ]
@@ -5,12 +5,10 @@ Authentication and authorization services for ERP systems.
5
5
  """
6
6
 
7
7
  from .auth_service import AuthService
8
- from .auth_base import AuthBase
9
- from .auth_read_dto import AuthServiceReadDto, AuthControllerReadDto
8
+ from .auth_write_dto import AuthServiceWriteDto
10
9
 
11
10
  __all__ = [
12
11
  "AuthService",
13
- "AuthBase",
14
- "AuthServiceReadDto",
15
- "AuthControllerReadDto"
12
+ "AuthServiceWriteDto"
16
13
  ]
14
+
@@ -2,3 +2,4 @@ from pydantic import BaseModel
2
2
 
3
3
  class AuthBase(BaseModel):
4
4
  token: str
5
+
@@ -1,10 +1,11 @@
1
1
  from fastapi import APIRouter
2
- from src.trovesuite.auth.auth_write_dto import AuthControllerWriteDto
3
- from src.trovesuite.auth.auth_read_dto import AuthControllerReadDto
4
- from src.trovesuite.auth.auth_service import AuthService
2
+ from .auth_write_dto import AuthControllerWriteDto
3
+ from .auth_read_dto import AuthControllerReadDto
4
+ from .auth_service import AuthService
5
+ from ..entities.sh_response import Respons
5
6
 
6
- auth_router = APIRouter()
7
+ auth_router = APIRouter(tags=["Auth"])
7
8
 
8
- @auth_router.post("/auth", response_model=AuthControllerReadDto)
9
+ @auth_router.post("/auth", response_model=Respons[AuthControllerReadDto])
9
10
  async def authorize(data: AuthControllerWriteDto):
10
11
  return AuthService.authorize(data=data)
@@ -11,8 +11,8 @@ class AuthControllerReadDto(BaseModel):
11
11
  role_id: Optional[str] = None
12
12
  tenant_id: Optional[str] = None
13
13
  permissions: Optional[List[str]] = None
14
- shared_resource_id: Optional[str] = None
15
14
  resource_id: Optional[str] = None
16
15
 
17
16
  class AuthServiceReadDto(AuthControllerReadDto):
18
17
  pass
18
+
@@ -48,7 +48,7 @@ class AuthService:
48
48
 
49
49
  user_id: str = data.user_id
50
50
  tenant_id: str = data.tenant_id
51
-
51
+
52
52
  """Check if a user is authorized based on login settings and roles"""
53
53
  # Input validation
54
54
  if not user_id or not isinstance(user_id, str):
@@ -68,14 +68,14 @@ class AuthService:
68
68
  status_code=400,
69
69
  error="INVALID_TENANT_ID"
70
70
  )
71
-
71
+
72
72
  try:
73
73
 
74
74
  is_tenant_verified = DatabaseManager.execute_query(
75
75
  f"SELECT is_verified FROM {db_settings.MAIN_TENANTS_TABLE} WHERE delete_status = 'NOT_DELETED' AND id = %s",
76
76
  (tenant_id,),
77
77
  )
78
-
78
+
79
79
  if not is_tenant_verified or len(is_tenant_verified) == 0:
80
80
  logger.warning("Login failed - tenant not found: %s", tenant_id)
81
81
  return Respons[AuthServiceReadDto](
@@ -156,7 +156,7 @@ class AuthService:
156
156
 
157
157
  # 1️⃣ Get all groups the user belongs to
158
158
  user_groups = DatabaseManager.execute_query(
159
- f"""SELECT group_id FROM "{tenant_id}".{db_settings.USER_GROUPS_TABLE}
159
+ f"""SELECT group_id FROM "{tenant_id}".{db_settings.TENANT_USER_GROUPS_TABLE}
160
160
  WHERE delete_status = 'NOT_DELETED' AND is_active = true AND user_id = %s""",(user_id,),
161
161
  )
162
162
 
@@ -169,7 +169,7 @@ class AuthService:
169
169
  f"""
170
170
  SELECT DISTINCT ON (org_id, group_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id)
171
171
  org_id, group_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id
172
- FROM "{tenant_id}".{db_settings.ASSIGN_ROLES_TABLE}
172
+ FROM "{tenant_id}".{db_settings.TENANT_ASSIGN_ROLES_TABLE}
173
173
  WHERE delete_status = 'NOT_DELETED'
174
174
  AND is_active = true
175
175
  AND (user_id = %s OR group_id = ANY(%s))
@@ -183,7 +183,7 @@ class AuthService:
183
183
  f"""
184
184
  SELECT DISTINCT ON (org_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id)
185
185
  org_id, bus_id, app_id, shared_resource_id, resource_id, user_id, role_id
186
- FROM "{tenant_id}".{db_settings.ASSIGN_ROLES_TABLE}
186
+ FROM "{tenant_id}".{db_settings.TENANT_ASSIGN_ROLES_TABLE}
187
187
  WHERE delete_status = 'NOT_DELETED'
188
188
  AND is_active = true
189
189
  AND user_id = %s
@@ -196,7 +196,7 @@ class AuthService:
196
196
  get_user_roles_with_tenant_and_permissions = []
197
197
  for role in get_user_roles:
198
198
  permissions = DatabaseManager.execute_query(
199
- f"""SELECT permission_id FROM {db_settings.ROLE_PERMISSIONS_TABLE} WHERE role_id = %s""",
199
+ f"""SELECT permission_id FROM {db_settings.MAIN_ROLE_PERMISSIONS_TABLE} WHERE role_id = %s""",
200
200
  params=(role["role_id"],),)
201
201
 
202
202
  role_dict = {**role, "tenant_id": tenant_id, "permissions": [p['permission_id'] for p in permissions]}
@@ -4,7 +4,8 @@ from pydantic import BaseModel
4
4
 
5
5
  class AuthControllerWriteDto(BaseModel):
6
6
  user_id: Optional[str] = None
7
- tenant: Optional[str] = None
7
+ tenant_id: Optional[str] = None
8
8
 
9
9
  class AuthServiceWriteDto(AuthControllerWriteDto):
10
10
  pass
11
+
@@ -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,57 @@
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
3
 
8
- # =============================================================================
9
- # DATABASE CONFIGURATION
10
- # =============================================================================
11
- DATABASE_URL: str = os.getenv(
12
- "DATABASE_URL",
13
- "postgresql://username:password@localhost:5432/database_name"
14
- )
4
+ # Database URL
5
+ DATABASE_URL: str = os.getenv("DATABASE_URL")
15
6
 
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")
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")
23
12
 
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"
29
-
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
+ # Application settings
14
+ APP_NAME: str = os.getenv("APP_NAME", "Python Template API")
15
+ DEBUG: bool = os.getenv("DEBUG", "True").lower() in ("true",1)
16
+ APP_VERSION: str = os.getenv("APP_VERSION", "1.0.0")
36
17
 
37
- # =============================================================================
38
- # LOGGING SETTINGS
39
- # =============================================================================
18
+ # Logging settings
40
19
  LOG_LEVEL: str = os.getenv("LOG_LEVEL", "INFO")
41
20
  LOG_FORMAT: str = os.getenv("LOG_FORMAT", "detailed") # detailed, json, simple
42
- LOG_TO_FILE: bool = os.getenv("LOG_TO_FILE", "False").lower() == "false"
21
+ LOG_TO_FILE: bool = os.getenv("LOG_TO_FILE", "False").lower() in ("true", 1)
43
22
  LOG_MAX_SIZE: int = int(os.getenv("LOG_MAX_SIZE", "10485760")) # 10MB
44
23
  LOG_BACKUP_COUNT: int = int(os.getenv("LOG_BACKUP_COUNT", "5"))
45
24
  LOG_DIR: str = os.getenv("LOG_DIR", "logs")
46
-
25
+
26
+ # Security settings
27
+ ENVIRONMENT: str = os.getenv("ENVIRONMENT")
28
+ ALGORITHM: str = os.getenv("ALGORITHM")
29
+ SECRET_KEY: str = os.getenv("SECRET_KEY")
30
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "120"))
31
+
47
32
  # =============================================================================
48
- # DATABASE TABLE NAMES
33
+ # SHARED TABLES (main schema)
49
34
  # =============================================================================
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")
35
+ MAIN_TENANTS_TABLE = os.getenv("MAIN_TENANTS_TABLE")
36
+ MAIN_ROLE_PERMISSIONS_TABLE = os.getenv("MAIN_ROLE_PERMISSIONS_TABLE")
37
+ MAIN_USER_SUBSCRIPTIONS_TABLE = os.getenv("MAIN_USER_SUBSCRIPTIONS_TABLE")
38
+ MAIN_USER_SUBSCRIPTION_HISTORY_TABLE = os.getenv("MAIN_USER_SUBSCRIPTION_HISTORY_TABLE")
39
+ MAIN_SUBSCRIPTIONS_TABLE = os.getenv("MAIN_SUBSCRIPTIONS_TABLE")
58
40
 
59
41
  # =============================================================================
60
- # AZURE CONFIGURATION (Optional - for queue functionality)
42
+ # TENANT-SPECIFIC TABLES (tenant schemas)
61
43
  # =============================================================================
62
- STORAGE_ACCOUNT_NAME: str = os.getenv("STORAGE_ACCOUNT_NAME", "")
63
- USER_ASSIGNED_MANAGED_IDENTITY: str = os.getenv("USER_ASSIGNED_MANAGED_IDENTITY", "")
64
-
44
+ TENANT_LOGIN_SETTINGS_TABLE = os.getenv("TENANT_LOGIN_SETTINGS_TABLE")
45
+ TENANT_ASSIGN_ROLES_TABLE = os.getenv("TENANT_ASSIGN_ROLES_TABLE")
46
+ TENANT_USER_GROUPS_TABLE = os.getenv("TENANT_USER_GROUPS_TABLE")
47
+
65
48
  @property
66
49
  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":
50
+ if self.DATABASE_URL:
69
51
  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
52
 
146
- # Global settings instance
147
- db_settings = Settings()
53
+ port = int(self.DB_PORT) if self.DB_PORT else 5432
54
+ return f"postgresql://{self.DB_USER}:{self.DB_PASSWORD}@{self.DB_HOST}:{port}/{self.DB_NAME}"
148
55
 
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)
56
+ # Global settings instance
57
+ 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