createsonline 0.1.26__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.
Files changed (152) hide show
  1. createsonline/__init__.py +46 -0
  2. createsonline/admin/__init__.py +7 -0
  3. createsonline/admin/content.py +526 -0
  4. createsonline/admin/crud.py +805 -0
  5. createsonline/admin/field_builder.py +559 -0
  6. createsonline/admin/integration.py +482 -0
  7. createsonline/admin/interface.py +2562 -0
  8. createsonline/admin/model_creator.py +513 -0
  9. createsonline/admin/model_manager.py +388 -0
  10. createsonline/admin/modern_dashboard.py +498 -0
  11. createsonline/admin/permissions.py +264 -0
  12. createsonline/admin/user_forms.py +594 -0
  13. createsonline/ai/__init__.py +202 -0
  14. createsonline/ai/fields.py +1226 -0
  15. createsonline/ai/orm.py +325 -0
  16. createsonline/ai/services.py +1244 -0
  17. createsonline/app.py +506 -0
  18. createsonline/auth/__init__.py +8 -0
  19. createsonline/auth/management.py +228 -0
  20. createsonline/auth/models.py +552 -0
  21. createsonline/cli/__init__.py +5 -0
  22. createsonline/cli/commands/__init__.py +122 -0
  23. createsonline/cli/commands/database.py +416 -0
  24. createsonline/cli/commands/info.py +173 -0
  25. createsonline/cli/commands/initdb.py +218 -0
  26. createsonline/cli/commands/project.py +545 -0
  27. createsonline/cli/commands/serve.py +173 -0
  28. createsonline/cli/commands/shell.py +93 -0
  29. createsonline/cli/commands/users.py +148 -0
  30. createsonline/cli/main.py +2041 -0
  31. createsonline/cli/manage.py +274 -0
  32. createsonline/config/__init__.py +9 -0
  33. createsonline/config/app.py +2577 -0
  34. createsonline/config/database.py +179 -0
  35. createsonline/config/docs.py +384 -0
  36. createsonline/config/errors.py +160 -0
  37. createsonline/config/orm.py +43 -0
  38. createsonline/config/request.py +93 -0
  39. createsonline/config/settings.py +176 -0
  40. createsonline/data/__init__.py +23 -0
  41. createsonline/data/dataframe.py +925 -0
  42. createsonline/data/io.py +453 -0
  43. createsonline/data/series.py +557 -0
  44. createsonline/database/__init__.py +60 -0
  45. createsonline/database/abstraction.py +440 -0
  46. createsonline/database/assistant.py +585 -0
  47. createsonline/database/fields.py +442 -0
  48. createsonline/database/migrations.py +132 -0
  49. createsonline/database/models.py +604 -0
  50. createsonline/database.py +438 -0
  51. createsonline/http/__init__.py +28 -0
  52. createsonline/http/client.py +535 -0
  53. createsonline/ml/__init__.py +55 -0
  54. createsonline/ml/classification.py +552 -0
  55. createsonline/ml/clustering.py +680 -0
  56. createsonline/ml/metrics.py +542 -0
  57. createsonline/ml/neural.py +560 -0
  58. createsonline/ml/preprocessing.py +784 -0
  59. createsonline/ml/regression.py +501 -0
  60. createsonline/performance/__init__.py +19 -0
  61. createsonline/performance/cache.py +444 -0
  62. createsonline/performance/compression.py +335 -0
  63. createsonline/performance/core.py +419 -0
  64. createsonline/project_init.py +789 -0
  65. createsonline/routing.py +528 -0
  66. createsonline/security/__init__.py +34 -0
  67. createsonline/security/core.py +811 -0
  68. createsonline/security/encryption.py +349 -0
  69. createsonline/server.py +295 -0
  70. createsonline/static/css/admin.css +263 -0
  71. createsonline/static/css/common.css +358 -0
  72. createsonline/static/css/dashboard.css +89 -0
  73. createsonline/static/favicon.ico +0 -0
  74. createsonline/static/icons/icon-128x128.png +0 -0
  75. createsonline/static/icons/icon-128x128.webp +0 -0
  76. createsonline/static/icons/icon-16x16.png +0 -0
  77. createsonline/static/icons/icon-16x16.webp +0 -0
  78. createsonline/static/icons/icon-180x180.png +0 -0
  79. createsonline/static/icons/icon-180x180.webp +0 -0
  80. createsonline/static/icons/icon-192x192.png +0 -0
  81. createsonline/static/icons/icon-192x192.webp +0 -0
  82. createsonline/static/icons/icon-256x256.png +0 -0
  83. createsonline/static/icons/icon-256x256.webp +0 -0
  84. createsonline/static/icons/icon-32x32.png +0 -0
  85. createsonline/static/icons/icon-32x32.webp +0 -0
  86. createsonline/static/icons/icon-384x384.png +0 -0
  87. createsonline/static/icons/icon-384x384.webp +0 -0
  88. createsonline/static/icons/icon-48x48.png +0 -0
  89. createsonline/static/icons/icon-48x48.webp +0 -0
  90. createsonline/static/icons/icon-512x512.png +0 -0
  91. createsonline/static/icons/icon-512x512.webp +0 -0
  92. createsonline/static/icons/icon-64x64.png +0 -0
  93. createsonline/static/icons/icon-64x64.webp +0 -0
  94. createsonline/static/image/android-chrome-192x192.png +0 -0
  95. createsonline/static/image/android-chrome-512x512.png +0 -0
  96. createsonline/static/image/apple-touch-icon.png +0 -0
  97. createsonline/static/image/favicon-16x16.png +0 -0
  98. createsonline/static/image/favicon-32x32.png +0 -0
  99. createsonline/static/image/favicon.ico +0 -0
  100. createsonline/static/image/favicon.svg +17 -0
  101. createsonline/static/image/icon-128x128.png +0 -0
  102. createsonline/static/image/icon-128x128.webp +0 -0
  103. createsonline/static/image/icon-16x16.png +0 -0
  104. createsonline/static/image/icon-16x16.webp +0 -0
  105. createsonline/static/image/icon-180x180.png +0 -0
  106. createsonline/static/image/icon-180x180.webp +0 -0
  107. createsonline/static/image/icon-192x192.png +0 -0
  108. createsonline/static/image/icon-192x192.webp +0 -0
  109. createsonline/static/image/icon-256x256.png +0 -0
  110. createsonline/static/image/icon-256x256.webp +0 -0
  111. createsonline/static/image/icon-32x32.png +0 -0
  112. createsonline/static/image/icon-32x32.webp +0 -0
  113. createsonline/static/image/icon-384x384.png +0 -0
  114. createsonline/static/image/icon-384x384.webp +0 -0
  115. createsonline/static/image/icon-48x48.png +0 -0
  116. createsonline/static/image/icon-48x48.webp +0 -0
  117. createsonline/static/image/icon-512x512.png +0 -0
  118. createsonline/static/image/icon-512x512.webp +0 -0
  119. createsonline/static/image/icon-64x64.png +0 -0
  120. createsonline/static/image/icon-64x64.webp +0 -0
  121. createsonline/static/image/logo-header-h100.png +0 -0
  122. createsonline/static/image/logo-header-h100.webp +0 -0
  123. createsonline/static/image/logo-header-h200@2x.png +0 -0
  124. createsonline/static/image/logo-header-h200@2x.webp +0 -0
  125. createsonline/static/image/logo.png +0 -0
  126. createsonline/static/js/admin.js +274 -0
  127. createsonline/static/site.webmanifest +35 -0
  128. createsonline/static/templates/admin/base.html +87 -0
  129. createsonline/static/templates/admin/dashboard.html +217 -0
  130. createsonline/static/templates/admin/model_form.html +270 -0
  131. createsonline/static/templates/admin/model_list.html +202 -0
  132. createsonline/static/test_script.js +15 -0
  133. createsonline/static/test_styles.css +59 -0
  134. createsonline/static_files.py +365 -0
  135. createsonline/templates/404.html +100 -0
  136. createsonline/templates/admin_login.html +169 -0
  137. createsonline/templates/base.html +102 -0
  138. createsonline/templates/index.html +151 -0
  139. createsonline/templates.py +205 -0
  140. createsonline/testing.py +322 -0
  141. createsonline/utils.py +448 -0
  142. createsonline/validation/__init__.py +49 -0
  143. createsonline/validation/fields.py +598 -0
  144. createsonline/validation/models.py +504 -0
  145. createsonline/validation/validators.py +561 -0
  146. createsonline/views.py +184 -0
  147. createsonline-0.1.26.dist-info/METADATA +46 -0
  148. createsonline-0.1.26.dist-info/RECORD +152 -0
  149. createsonline-0.1.26.dist-info/WHEEL +5 -0
  150. createsonline-0.1.26.dist-info/entry_points.txt +2 -0
  151. createsonline-0.1.26.dist-info/licenses/LICENSE +21 -0
  152. createsonline-0.1.26.dist-info/top_level.txt +1 -0
@@ -0,0 +1,438 @@
1
+ # createsonline/database.py
2
+ """
3
+ CREATESONLINE Database Connection Module
4
+
5
+ Provides database connectivity for SQLite (built-in) and PostgreSQL (optional).
6
+ Core functionality has zero external dependencies.
7
+
8
+ Optional Dependencies:
9
+ - python-dotenv: For .env file support (pip install python-dotenv)
10
+ - psycopg2: For PostgreSQL support (pip install psycopg2-binary)
11
+
12
+ Without these, the module falls back to:
13
+ - System environment variables only (no .env)
14
+ - SQLite-only database support
15
+ """
16
+
17
+ import os
18
+ import json
19
+ import sqlite3
20
+ import hashlib
21
+ import logging
22
+ from datetime import datetime
23
+ from typing import Dict, Any, Optional, List
24
+ from enum import Enum
25
+
26
+ # Setup logging
27
+ logger = logging.getLogger("createsonline.database")
28
+
29
+ # Try to load dotenv if available (optional dependency)
30
+ try:
31
+ from dotenv import load_dotenv
32
+ load_dotenv()
33
+ logger.info("Environment variables loaded from .env file")
34
+ except ImportError:
35
+ logger.debug("dotenv not available - using system environment only")
36
+
37
+
38
+ class ParamStyle(Enum):
39
+ """SQL parameter styles for different databases"""
40
+ SQLITE = "?"
41
+ POSTGRESQL = "%s"
42
+
43
+
44
+ class DatabaseConnection:
45
+ """
46
+ Pure Python database connection handler for CREATESONLINE.
47
+ Supports SQLite (built-in) and PostgreSQL (via optional psycopg2).
48
+ """
49
+
50
+ def __init__(
51
+ self,
52
+ database_url: Optional[str] = None,
53
+ auto_create_tables: bool = True
54
+ ):
55
+ self.database_url = database_url or os.getenv('DATABASE_URL', 'sqlite:///createsonline.db')
56
+ self.connection = None
57
+ self.db_type = self._detect_db_type()
58
+ self.param_style = ParamStyle.SQLITE if self.db_type == 'sqlite' else ParamStyle.POSTGRESQL
59
+ self.auto_create_tables = auto_create_tables
60
+
61
+ # Initialize connection
62
+ self._connect()
63
+
64
+ if self.auto_create_tables:
65
+ self._create_default_tables()
66
+
67
+ def _detect_db_type(self) -> str:
68
+ """Detect database type from URL"""
69
+ if self.database_url.startswith('postgresql://'):
70
+ return 'postgresql'
71
+ elif self.database_url.startswith('sqlite://'):
72
+ return 'sqlite'
73
+ else:
74
+ return 'sqlite' # Default to SQLite
75
+
76
+ def _get_placeholder(self) -> str:
77
+ """Get appropriate SQL placeholder for database type"""
78
+ return self.param_style.value
79
+
80
+ def _validate_identifier(self, identifier: str) -> str:
81
+ """Validate and sanitize SQL identifiers (table/column names)"""
82
+ # Reserved SQL keywords to prevent injection and conflicts
83
+ reserved_words = {
84
+ 'select', 'from', 'where', 'insert', 'update', 'delete', 'drop', 'create',
85
+ 'table', 'database', 'index', 'alter', 'grant', 'revoke', 'commit', 'rollback',
86
+ 'transaction', 'begin', 'end', 'union', 'join', 'inner', 'outer', 'left', 'right',
87
+ 'group', 'order', 'having', 'distinct', 'count', 'sum', 'avg', 'max', 'min',
88
+ 'and', 'or', 'not', 'null', 'true', 'false', 'is', 'like', 'in', 'exists'
89
+ }
90
+
91
+ # Allow only alphanumeric characters, underscores, and dots
92
+ import re
93
+ if not re.match(r'^[a-zA-Z][a-zA-Z0-9_]*(\.[a-zA-Z][a-zA-Z0-9_]*)?$', identifier):
94
+ raise ValueError(f"Invalid SQL identifier: {identifier}")
95
+
96
+ # Check against reserved words (case-insensitive)
97
+ if identifier.lower() in reserved_words:
98
+ raise ValueError(f"SQL identifier '{identifier}' is a reserved word")
99
+
100
+ return identifier
101
+
102
+ def _connect(self):
103
+ """Establish database connection"""
104
+ if self.db_type == 'sqlite':
105
+ # Extract SQLite path from URL
106
+ db_path = self.database_url.replace('sqlite:///', '').replace('sqlite://', '')
107
+ if db_path == ':memory:':
108
+ self.connection = sqlite3.connect(':memory:', check_same_thread=False)
109
+ else:
110
+ # Create directory if it doesn't exist
111
+ os.makedirs(os.path.dirname(db_path) if os.path.dirname(db_path) else '.', exist_ok=True)
112
+ self.connection = sqlite3.connect(db_path, check_same_thread=False)
113
+
114
+ # Enable row factory for dict-like access
115
+ self.connection.row_factory = sqlite3.Row
116
+ logger.info(f"Connected to SQLite database: {db_path}")
117
+
118
+ elif self.db_type == 'postgresql':
119
+ try:
120
+ import psycopg2
121
+ import psycopg2.extras
122
+ self.connection = psycopg2.connect(
123
+ self.database_url,
124
+ cursor_factory=psycopg2.extras.RealDictCursor
125
+ )
126
+ logger.info("Connected to PostgreSQL database")
127
+ except ImportError:
128
+ logger.warning("PostgreSQL support requires psycopg2. Falling back to SQLite.")
129
+ self.database_url = 'sqlite:///createsonline.db'
130
+ self.db_type = 'sqlite'
131
+ self.param_style = ParamStyle.SQLITE
132
+ return self._connect()
133
+ except Exception as e:
134
+ logger.error(f"Failed to connect to PostgreSQL: {e}")
135
+ raise
136
+
137
+ def _create_default_tables(self):
138
+ """Create default CREATESONLINE framework tables"""
139
+ # Consolidated user table (removing duplicate users table)
140
+ user_table_sql = '''
141
+ CREATE TABLE IF NOT EXISTS createsonline_users (
142
+ id SERIAL PRIMARY KEY,
143
+ username VARCHAR(80) UNIQUE NOT NULL,
144
+ email VARCHAR(120) UNIQUE NOT NULL,
145
+ first_name VARCHAR(50),
146
+ last_name VARCHAR(50),
147
+ password_hash VARCHAR(128) NOT NULL,
148
+ is_active BOOLEAN DEFAULT TRUE,
149
+ is_staff BOOLEAN DEFAULT FALSE,
150
+ is_superuser BOOLEAN DEFAULT FALSE,
151
+ date_joined TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
152
+ last_login TIMESTAMP,
153
+ profile_picture TEXT,
154
+ bio TEXT,
155
+ failed_login_attempts INTEGER DEFAULT 0,
156
+ account_locked_until TIMESTAMP,
157
+ password_reset_token VARCHAR(128),
158
+ email_verification_token VARCHAR(128),
159
+ email_verified BOOLEAN DEFAULT FALSE
160
+ )
161
+ ''' if self.db_type == 'postgresql' else '''
162
+ CREATE TABLE IF NOT EXISTS createsonline_users (
163
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
164
+ username VARCHAR(80) UNIQUE NOT NULL,
165
+ email VARCHAR(120) UNIQUE NOT NULL,
166
+ first_name VARCHAR(50),
167
+ last_name VARCHAR(50),
168
+ password_hash VARCHAR(128) NOT NULL,
169
+ is_active BOOLEAN DEFAULT TRUE,
170
+ is_staff BOOLEAN DEFAULT FALSE,
171
+ is_superuser BOOLEAN DEFAULT FALSE,
172
+ date_joined TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
173
+ last_login TIMESTAMP,
174
+ profile_picture TEXT,
175
+ bio TEXT,
176
+ failed_login_attempts INTEGER DEFAULT 0,
177
+ account_locked_until TIMESTAMP,
178
+ password_reset_token VARCHAR(128),
179
+ email_verification_token VARCHAR(128),
180
+ email_verified BOOLEAN DEFAULT FALSE
181
+ )
182
+ '''
183
+
184
+ tables = {
185
+ 'createsonline_users': user_table_sql,
186
+ 'ai_conversations': f'''
187
+ CREATE TABLE IF NOT EXISTS ai_conversations (
188
+ id {'SERIAL' if self.db_type == 'postgresql' else 'INTEGER'} PRIMARY KEY{' AUTOINCREMENT' if self.db_type == 'sqlite' else ''},
189
+ user_id INTEGER REFERENCES createsonline_users(id),
190
+ conversation_data TEXT NOT NULL,
191
+ ai_model VARCHAR(50) DEFAULT 'createsonline-internal',
192
+ tokens_used INTEGER DEFAULT 0,
193
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
194
+ )
195
+ ''',
196
+ 'app_settings': f'''
197
+ CREATE TABLE IF NOT EXISTS app_settings (
198
+ id {'SERIAL' if self.db_type == 'postgresql' else 'INTEGER'} PRIMARY KEY{' AUTOINCREMENT' if self.db_type == 'sqlite' else ''},
199
+ key VARCHAR(100) UNIQUE NOT NULL,
200
+ value TEXT NOT NULL,
201
+ description TEXT,
202
+ is_system BOOLEAN DEFAULT FALSE,
203
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
204
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
205
+ )
206
+ ''',
207
+ 'admin_sessions': f'''
208
+ CREATE TABLE IF NOT EXISTS admin_sessions (
209
+ id {'SERIAL' if self.db_type == 'postgresql' else 'INTEGER'} PRIMARY KEY{' AUTOINCREMENT' if self.db_type == 'sqlite' else ''},
210
+ user_id INTEGER REFERENCES createsonline_users(id),
211
+ session_token VARCHAR(128) UNIQUE NOT NULL,
212
+ expires_at TIMESTAMP NOT NULL,
213
+ ip_address VARCHAR(45),
214
+ user_agent TEXT,
215
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
216
+ )
217
+ '''
218
+ }
219
+
220
+ for table_name, sql in tables.items():
221
+ try:
222
+ cursor = self.connection.cursor()
223
+ cursor.execute(sql)
224
+ self.connection.commit()
225
+ logger.info(f"Table '{table_name}' ready")
226
+ except Exception as e:
227
+ logger.error(f"Error creating table '{table_name}': {e}")
228
+ raise
229
+
230
+ def execute(self, query: str, params: tuple = ()) -> List[Dict]:
231
+ """Execute SQL query and return results"""
232
+ try:
233
+ cursor = self.connection.cursor()
234
+ cursor.execute(query, params)
235
+
236
+ if query.strip().upper().startswith('SELECT'):
237
+ if self.db_type == 'sqlite':
238
+ return [dict(row) for row in cursor.fetchall()]
239
+ else:
240
+ return cursor.fetchall()
241
+ else:
242
+ self.connection.commit()
243
+ return [{"affected_rows": cursor.rowcount}]
244
+
245
+ except Exception as e:
246
+ logger.exception(f"Database query failed: {query[:100]}...")
247
+ self.connection.rollback()
248
+ raise # Re-raise the exception instead of swallowing it
249
+
250
+ def insert(self, table: str, data: Dict[str, Any]) -> Optional[int]:
251
+ """Insert data into table and return ID"""
252
+ safe_table = self._validate_identifier(table)
253
+ columns = ', '.join([self._validate_identifier(k) for k in data.keys()])
254
+ placeholders = ', '.join([self._get_placeholder()] * len(data))
255
+
256
+ if self.db_type == 'postgresql':
257
+ # PostgreSQL needs RETURNING for ID
258
+ query = f"INSERT INTO {safe_table} ({columns}) VALUES ({placeholders}) RETURNING id"
259
+ else:
260
+ query = f"INSERT INTO {safe_table} ({columns}) VALUES ({placeholders})"
261
+
262
+ try:
263
+ cursor = self.connection.cursor()
264
+ cursor.execute(query, tuple(data.values()))
265
+
266
+ # Return last inserted ID
267
+ if self.db_type == 'sqlite':
268
+ row_id = cursor.lastrowid
269
+ self.connection.commit()
270
+ return row_id
271
+ else:
272
+ # PostgreSQL with RETURNING
273
+ row_id = cursor.fetchone()['id'] if cursor.rowcount > 0 else None
274
+ self.connection.commit()
275
+ return row_id
276
+
277
+ except Exception as e:
278
+ logger.error(f"Insert error: {e}")
279
+ self.connection.rollback()
280
+ return None
281
+
282
+ def update(self, table: str, data: Dict[str, Any], where: Dict[str, Any]) -> int:
283
+ """Update data in table"""
284
+ safe_table = self._validate_identifier(table)
285
+ placeholder = self._get_placeholder()
286
+ set_clause = ', '.join([f"{self._validate_identifier(k)} = {placeholder}" for k in data.keys()])
287
+ where_clause = ' AND '.join([f"{self._validate_identifier(k)} = {placeholder}" for k in where.keys()])
288
+
289
+ query = f"UPDATE {safe_table} SET {set_clause} WHERE {where_clause}"
290
+ params = tuple(data.values()) + tuple(where.values())
291
+
292
+ try:
293
+ cursor = self.connection.cursor()
294
+ cursor.execute(query, params)
295
+ self.connection.commit()
296
+ return cursor.rowcount
297
+ except Exception as e:
298
+ logger.error(f"Update error: {e}")
299
+ self.connection.rollback()
300
+ return 0
301
+
302
+ def delete(self, table: str, where: Dict[str, Any]) -> int:
303
+ """Delete data from table"""
304
+ safe_table = self._validate_identifier(table)
305
+ placeholder = self._get_placeholder()
306
+ where_clause = ' AND '.join([f"{self._validate_identifier(k)} = {placeholder}" for k in where.keys()])
307
+ query = f"DELETE FROM {safe_table} WHERE {where_clause}"
308
+
309
+ try:
310
+ cursor = self.connection.cursor()
311
+ cursor.execute(query, tuple(where.values()))
312
+ self.connection.commit()
313
+ return cursor.rowcount
314
+ except Exception as e:
315
+ logger.error(f"Delete error: {e}")
316
+ self.connection.rollback()
317
+ return 0
318
+
319
+ def get_user_by_username(self, username: str) -> Optional[Dict]:
320
+ """Get user by username from consolidated users table"""
321
+ placeholder = self._get_placeholder()
322
+ result = self.execute(f"SELECT * FROM createsonline_users WHERE username = {placeholder}", (username,))
323
+ return result[0] if result else None
324
+
325
+ def create_admin_user(self, username: str, email: str, password: str) -> Optional[int]:
326
+ """Create admin user with hashed password"""
327
+ password_hash = hashlib.sha256(password.encode()).hexdigest()
328
+
329
+ return self.insert('createsonline_users', {
330
+ 'username': username,
331
+ 'email': email,
332
+ 'password_hash': password_hash,
333
+ 'is_superuser': True,
334
+ 'is_staff': True,
335
+ 'is_active': True,
336
+ 'email_verified': True
337
+ })
338
+
339
+ def create_session(self, user_id: int, ip_address: str = None, user_agent: str = None) -> str:
340
+ """Create admin session and return token"""
341
+ import secrets
342
+ session_token = secrets.token_urlsafe(32)
343
+
344
+ # Session expires in 24 hours
345
+ from datetime import timedelta
346
+ expires_at = datetime.now() + timedelta(hours=24)
347
+
348
+ self.insert('admin_sessions', {
349
+ 'user_id': user_id,
350
+ 'session_token': session_token,
351
+ 'expires_at': expires_at.isoformat(),
352
+ 'ip_address': ip_address,
353
+ 'user_agent': user_agent
354
+ })
355
+
356
+ return session_token
357
+
358
+ def validate_session(self, session_token: str) -> Optional[Dict]:
359
+ """Validate session token and return user data"""
360
+ placeholder = self._get_placeholder()
361
+ query = f'''
362
+ SELECT u.*, s.expires_at
363
+ FROM createsonline_users u
364
+ JOIN admin_sessions s ON u.id = s.user_id
365
+ WHERE s.session_token = {placeholder} AND s.expires_at > CURRENT_TIMESTAMP
366
+ '''
367
+ result = self.execute(query, (session_token,))
368
+ return result[0] if result else None
369
+
370
+ def get_app_setting(self, key: str, default: Any = None) -> Any:
371
+ """Get application setting"""
372
+ placeholder = self._get_placeholder()
373
+ result = self.execute(f"SELECT value FROM app_settings WHERE key = {placeholder}", (key,))
374
+ if result:
375
+ try:
376
+ return json.loads(result[0]['value'])
377
+ except:
378
+ return result[0]['value']
379
+ return default
380
+
381
+ def set_app_setting(self, key: str, value: Any, description: str = None):
382
+ """Set application setting"""
383
+ # Check if setting exists
384
+ placeholder = self._get_placeholder()
385
+ existing = self.execute(f"SELECT id FROM app_settings WHERE key = {placeholder}", (key,))
386
+
387
+ value_str = json.dumps(value) if not isinstance(value, str) else value
388
+
389
+ if existing:
390
+ self.update('app_settings',
391
+ {'value': value_str, 'updated_at': datetime.now().isoformat()},
392
+ {'key': key})
393
+ else:
394
+ self.insert('app_settings', {
395
+ 'key': key,
396
+ 'value': value_str,
397
+ 'description': description or f"Setting for {key}"
398
+ })
399
+
400
+ def verify_password(self, password: str, password_hash: str) -> bool:
401
+ """Verify password against hash (uses consolidated SecurityUtils)"""
402
+ try:
403
+ from .utils import SecurityUtils
404
+ return SecurityUtils.verify_password(password, password_hash)
405
+ except ImportError:
406
+ # Fallback to simple verification for standalone use
407
+ import hashlib
408
+ import secrets
409
+ computed_hash = hashlib.sha256(password.encode()).hexdigest()
410
+ return secrets.compare_digest(computed_hash, password_hash)
411
+
412
+ def close(self):
413
+ """Close database connection"""
414
+ if self.connection:
415
+ self.connection.close()
416
+
417
+ def __enter__(self):
418
+ return self
419
+
420
+ def __exit__(self, exc_type, exc_val, exc_tb):
421
+ self.close()
422
+
423
+
424
+ # Global database instance
425
+ _db_instance = None
426
+
427
+ def get_database() -> DatabaseConnection:
428
+ """Get global database instance"""
429
+ global _db_instance
430
+ if _db_instance is None:
431
+ _db_instance = DatabaseConnection()
432
+ return _db_instance
433
+
434
+ def init_database(database_url: str = None) -> DatabaseConnection:
435
+ """Initialize database with custom URL"""
436
+ global _db_instance
437
+ _db_instance = DatabaseConnection(database_url)
438
+ return _db_instance
@@ -0,0 +1,28 @@
1
+ """
2
+ CREATESONLINE Internal HTTP Client Module
3
+
4
+ Pure Python HTTP client implementation with zero external dependencies.
5
+ Supports both synchronous and asynchronous operations.
6
+ """
7
+
8
+ from .client import (
9
+ HTTPClient,
10
+ AsyncHTTPClient,
11
+ HTTPRequest,
12
+ HTTPResponse,
13
+ HTTPError,
14
+ ConnectionError,
15
+ TimeoutError,
16
+ RequestError
17
+ )
18
+
19
+ __all__ = [
20
+ 'HTTPClient',
21
+ 'AsyncHTTPClient',
22
+ 'HTTPRequest',
23
+ 'HTTPResponse',
24
+ 'HTTPError',
25
+ 'ConnectionError',
26
+ 'TimeoutError',
27
+ 'RequestError'
28
+ ]