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.
- trovesuite/__init__.py +12 -5
- trovesuite/auth/__init__.py +2 -5
- trovesuite/auth/auth_controller.py +6 -5
- trovesuite/auth/auth_read_dto.py +3 -3
- trovesuite/auth/auth_service.py +223 -80
- trovesuite/auth/auth_write_dto.py +1 -1
- trovesuite/configs/database.py +130 -36
- trovesuite/configs/settings.py +82 -132
- trovesuite/entities/health.py +4 -4
- trovesuite/notification/__init__.py +14 -0
- trovesuite/notification/notification_base.py +13 -0
- trovesuite/notification/notification_controller.py +21 -0
- trovesuite/notification/notification_read_dto.py +21 -0
- trovesuite/notification/notification_service.py +73 -0
- trovesuite/notification/notification_write_dto.py +21 -0
- trovesuite/storage/__init__.py +42 -0
- trovesuite/storage/storage_base.py +63 -0
- trovesuite/storage/storage_controller.py +198 -0
- trovesuite/storage/storage_read_dto.py +74 -0
- trovesuite/storage/storage_service.py +529 -0
- trovesuite/storage/storage_write_dto.py +70 -0
- trovesuite/utils/__init__.py +3 -1
- trovesuite/utils/helper.py +625 -5
- trovesuite/utils/templates.py +487 -0
- {trovesuite-1.0.1.dist-info → trovesuite-1.0.24.dist-info}/METADATA +184 -9
- trovesuite-1.0.24.dist-info/RECORD +34 -0
- trovesuite-1.0.1.dist-info/RECORD +0 -21
- {trovesuite-1.0.1.dist-info → trovesuite-1.0.24.dist-info}/WHEEL +0 -0
- {trovesuite-1.0.1.dist-info → trovesuite-1.0.24.dist-info}/licenses/LICENSE +0 -0
- {trovesuite-1.0.1.dist-info → trovesuite-1.0.24.dist-info}/top_level.txt +0 -0
trovesuite/configs/database.py
CHANGED
|
@@ -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
|
-
|
|
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
|
|
106
|
-
"""
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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.
|
|
184
|
+
if not conn.closed:
|
|
185
|
+
conn.commit()
|
|
141
186
|
except Exception as e:
|
|
142
|
-
conn.
|
|
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
|
-
|
|
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
|
|
221
|
-
|
|
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
|
trovesuite/configs/settings.py
CHANGED
|
@@ -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
|
-
#
|
|
17
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
#
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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()
|
|
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
|
-
#
|
|
38
|
+
# SHARED TABLES (core_platform schema)
|
|
49
39
|
# =============================================================================
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
-
#
|
|
62
|
+
# CORE PLATFORM TABLES (prefixed with cp_, now in core_platform schema with tenant_id)
|
|
61
63
|
# =============================================================================
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
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
|
-
|
|
147
|
-
|
|
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
|
-
#
|
|
150
|
-
|
|
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()
|
trovesuite/entities/health.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from fastapi import APIRouter
|
|
2
|
-
from
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
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
|