quantum-framework 0.18.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (333) hide show
  1. quantum/__init__.py +1 -0
  2. quantum/cli/__init__.py +2 -0
  3. quantum/cli/admin.py +131 -0
  4. quantum/cli/check.py +223 -0
  5. quantum/cli/commands/__init__.py +15 -0
  6. quantum/cli/commands/build.py +414 -0
  7. quantum/cli/commands/dev.py +185 -0
  8. quantum/cli/commands/docs.py +329 -0
  9. quantum/cli/commands/lint.py +523 -0
  10. quantum/cli/commands/migrate.py +548 -0
  11. quantum/cli/commands/new.py +622 -0
  12. quantum/cli/commands/serve.py +190 -0
  13. quantum/cli/commands/test.py +193 -0
  14. quantum/cli/deploy.py +810 -0
  15. quantum/cli/hot_reload.py +951 -0
  16. quantum/cli/jobs.py +356 -0
  17. quantum/cli/mq.py +582 -0
  18. quantum/cli/pkg.py +390 -0
  19. quantum/cli/runner.py +565 -0
  20. quantum/cli/server_process.py +159 -0
  21. quantum/cli/utils.py +334 -0
  22. quantum/compiler/__init__.py +30 -0
  23. quantum/compiler/base_generator.py +367 -0
  24. quantum/compiler/cli.py +295 -0
  25. quantum/compiler/expression_transformer.py +444 -0
  26. quantum/compiler/javascript/__init__.py +10 -0
  27. quantum/compiler/javascript/generator.py +659 -0
  28. quantum/compiler/optimizer.py +270 -0
  29. quantum/compiler/python/__init__.py +10 -0
  30. quantum/compiler/python/generator.py +883 -0
  31. quantum/compiler/python/runtime.py +863 -0
  32. quantum/compiler/transpiler.py +330 -0
  33. quantum/core/__init__.py +3 -0
  34. quantum/core/ast_nodes.py +2619 -0
  35. quantum/core/config_env.py +54 -0
  36. quantum/core/docs_links.py +8 -0
  37. quantum/core/expression_diagnostics.py +87 -0
  38. quantum/core/expression_stdlib.py +208 -0
  39. quantum/core/expressions.py +677 -0
  40. quantum/core/features/agents/src/__init__.py +30 -0
  41. quantum/core/features/agents/src/ast_node.py +540 -0
  42. quantum/core/features/conditionals/src/__init__.py +8 -0
  43. quantum/core/features/conditionals/src/ast_node.py +68 -0
  44. quantum/core/features/data_import/src/__init__.py +0 -0
  45. quantum/core/features/data_import/src/ast_node.py +292 -0
  46. quantum/core/features/data_import/src/runtime.py +533 -0
  47. quantum/core/features/dump/src/__init__.py +12 -0
  48. quantum/core/features/dump/src/ast_node.py +106 -0
  49. quantum/core/features/dump/src/parser.py +61 -0
  50. quantum/core/features/dump/src/runtime.py +246 -0
  51. quantum/core/features/functions/src/__init__.py +8 -0
  52. quantum/core/features/functions/src/ast_node.py +149 -0
  53. quantum/core/features/game_engine_2d/src/__init__.py +19 -0
  54. quantum/core/features/game_engine_2d/src/ast_nodes.py +1719 -0
  55. quantum/core/features/game_engine_2d/src/parser.py +983 -0
  56. quantum/core/features/invocation/src/__init__.py +0 -0
  57. quantum/core/features/invocation/src/ast_node.py +147 -0
  58. quantum/core/features/invocation/src/runtime.py +332 -0
  59. quantum/core/features/knowledge_base/src/__init__.py +6 -0
  60. quantum/core/features/knowledge_base/src/ast_node.py +113 -0
  61. quantum/core/features/knowledge_base/src/parser.py +82 -0
  62. quantum/core/features/logging/src/__init__.py +12 -0
  63. quantum/core/features/logging/src/ast_node.py +111 -0
  64. quantum/core/features/logging/src/parser.py +50 -0
  65. quantum/core/features/logging/src/runtime.py +190 -0
  66. quantum/core/features/loops/src/__init__.py +8 -0
  67. quantum/core/features/loops/src/ast_node.py +60 -0
  68. quantum/core/features/query/src/__init__.py +0 -0
  69. quantum/core/features/query/src/query_validators.py +20 -0
  70. quantum/core/features/state_management/src/__init__.py +11 -0
  71. quantum/core/features/state_management/src/ast_node.py +121 -0
  72. quantum/core/features/terminal_engine/src/__init__.py +21 -0
  73. quantum/core/features/terminal_engine/src/ast_nodes.py +560 -0
  74. quantum/core/features/terminal_engine/src/parser.py +361 -0
  75. quantum/core/features/testing_engine/src/__init__.py +41 -0
  76. quantum/core/features/testing_engine/src/ast_nodes.py +1212 -0
  77. quantum/core/features/testing_engine/src/parser.py +604 -0
  78. quantum/core/features/theming/src/__init__.py +48 -0
  79. quantum/core/features/theming/src/ast_node.py +137 -0
  80. quantum/core/features/theming/src/presets.py +405 -0
  81. quantum/core/features/ui_engine/src/__init__.py +20 -0
  82. quantum/core/features/ui_engine/src/ast_nodes.py +1913 -0
  83. quantum/core/features/ui_engine/src/parser.py +1192 -0
  84. quantum/core/features/websocket/src/__init__.py +24 -0
  85. quantum/core/features/websocket/src/ast_node.py +247 -0
  86. quantum/core/guards.py +53 -0
  87. quantum/core/html_compat.py +299 -0
  88. quantum/core/parser.py +1551 -0
  89. quantum/core/parser_registry.py +213 -0
  90. quantum/core/parsers/__init__.py +76 -0
  91. quantum/core/parsers/ai/__init__.py +12 -0
  92. quantum/core/parsers/ai/agent_parser.py +106 -0
  93. quantum/core/parsers/ai/knowledge_parser.py +86 -0
  94. quantum/core/parsers/ai/llm_parser.py +78 -0
  95. quantum/core/parsers/ai/team_parser.py +88 -0
  96. quantum/core/parsers/base.py +322 -0
  97. quantum/core/parsers/composition/__init__.py +10 -0
  98. quantum/core/parsers/composition/import_parser.py +50 -0
  99. quantum/core/parsers/composition/slot_parser.py +48 -0
  100. quantum/core/parsers/control_flow/__init__.py +11 -0
  101. quantum/core/parsers/control_flow/if_parser.py +71 -0
  102. quantum/core/parsers/control_flow/loop_parser.py +105 -0
  103. quantum/core/parsers/control_flow/set_parser.py +93 -0
  104. quantum/core/parsers/data/__init__.py +12 -0
  105. quantum/core/parsers/data/data_parser.py +229 -0
  106. quantum/core/parsers/data/invoke_parser.py +133 -0
  107. quantum/core/parsers/data/query_parser.py +213 -0
  108. quantum/core/parsers/data/transaction_parser.py +86 -0
  109. quantum/core/parsers/events/__init__.py +9 -0
  110. quantum/core/parsers/events/dispatch_event_parser.py +44 -0
  111. quantum/core/parsers/forms/__init__.py +11 -0
  112. quantum/core/parsers/forms/action_parser.py +79 -0
  113. quantum/core/parsers/forms/flash_parser.py +34 -0
  114. quantum/core/parsers/forms/redirect_parser.py +33 -0
  115. quantum/core/parsers/functions/__init__.py +11 -0
  116. quantum/core/parsers/functions/function_parser.py +98 -0
  117. quantum/core/parsers/functions/param_parser.py +73 -0
  118. quantum/core/parsers/functions/return_parser.py +31 -0
  119. quantum/core/parsers/html/__init__.py +10 -0
  120. quantum/core/parsers/html/component_call_parser.py +130 -0
  121. quantum/core/parsers/html/html_parser.py +115 -0
  122. quantum/core/parsers/jobs/__init__.py +11 -0
  123. quantum/core/parsers/jobs/job_parser.py +71 -0
  124. quantum/core/parsers/jobs/schedule_parser.py +62 -0
  125. quantum/core/parsers/jobs/thread_parser.py +57 -0
  126. quantum/core/parsers/messaging/__init__.py +17 -0
  127. quantum/core/parsers/messaging/message_ack_parser.py +30 -0
  128. quantum/core/parsers/messaging/message_nack_parser.py +30 -0
  129. quantum/core/parsers/messaging/message_parser.py +114 -0
  130. quantum/core/parsers/messaging/queue_parser.py +61 -0
  131. quantum/core/parsers/messaging/websocket_parser.py +121 -0
  132. quantum/core/parsers/routing/__init__.py +9 -0
  133. quantum/core/parsers/routing/route_parser.py +41 -0
  134. quantum/core/parsers/scripting/__init__.py +12 -0
  135. quantum/core/parsers/scripting/pyclass_parser.py +62 -0
  136. quantum/core/parsers/scripting/pydecorator_parser.py +68 -0
  137. quantum/core/parsers/scripting/pyimport_parser.py +52 -0
  138. quantum/core/parsers/scripting/python_parser.py +49 -0
  139. quantum/core/parsers/services/__init__.py +12 -0
  140. quantum/core/parsers/services/dump_parser.py +52 -0
  141. quantum/core/parsers/services/file_parser.py +48 -0
  142. quantum/core/parsers/services/log_parser.py +46 -0
  143. quantum/core/parsers/services/mail_parser.py +65 -0
  144. quantum/core/tiers.py +164 -0
  145. quantum/core/xml_lines.py +81 -0
  146. quantum/packages/__init__.py +28 -0
  147. quantum/packages/manager.py +413 -0
  148. quantum/packages/manifest.py +351 -0
  149. quantum/packages/registry.py +399 -0
  150. quantum/packages/resolver.py +336 -0
  151. quantum/plugins/__init__.py +33 -0
  152. quantum/plugins/hooks.py +329 -0
  153. quantum/plugins/loader.py +479 -0
  154. quantum/plugins/manifest.py +336 -0
  155. quantum/plugins/registry.py +371 -0
  156. quantum/runtime/__init__.py +25 -0
  157. quantum/runtime/action_handler.py +532 -0
  158. quantum/runtime/adapters/__init__.py +88 -0
  159. quantum/runtime/adapters/memory_adapter.py +690 -0
  160. quantum/runtime/adapters/rabbitmq_adapter.py +715 -0
  161. quantum/runtime/adapters/redis_adapter.py +582 -0
  162. quantum/runtime/adapters/sqlite_adapter.py +414 -0
  163. quantum/runtime/agent_service.py +1133 -0
  164. quantum/runtime/ast_cache.py +506 -0
  165. quantum/runtime/auth_service.py +267 -0
  166. quantum/runtime/component.py +1048 -0
  167. quantum/runtime/component_composer.py +133 -0
  168. quantum/runtime/component_resolver.py +174 -0
  169. quantum/runtime/conditions.py +54 -0
  170. quantum/runtime/database_service.py +595 -0
  171. quantum/runtime/db_schema.py +100 -0
  172. quantum/runtime/dev_panel.py +178 -0
  173. quantum/runtime/email_service.py +162 -0
  174. quantum/runtime/error_handler.py +295 -0
  175. quantum/runtime/execution_context.py +283 -0
  176. quantum/runtime/executor_registry.py +178 -0
  177. quantum/runtime/executors/__init__.py +71 -0
  178. quantum/runtime/executors/ai/__init__.py +12 -0
  179. quantum/runtime/executors/ai/agent_executor.py +217 -0
  180. quantum/runtime/executors/ai/knowledge_executor.py +114 -0
  181. quantum/runtime/executors/ai/llm_executor.py +153 -0
  182. quantum/runtime/executors/ai/team_executor.py +171 -0
  183. quantum/runtime/executors/base.py +264 -0
  184. quantum/runtime/executors/control_flow/__init__.py +12 -0
  185. quantum/runtime/executors/control_flow/if_executor.py +98 -0
  186. quantum/runtime/executors/control_flow/loop_executor.py +308 -0
  187. quantum/runtime/executors/control_flow/redirect_executor.py +43 -0
  188. quantum/runtime/executors/control_flow/set_executor.py +464 -0
  189. quantum/runtime/executors/data/__init__.py +12 -0
  190. quantum/runtime/executors/data/data_executor.py +156 -0
  191. quantum/runtime/executors/data/invoke_executor.py +228 -0
  192. quantum/runtime/executors/data/query_executor.py +290 -0
  193. quantum/runtime/executors/data/transaction_executor.py +91 -0
  194. quantum/runtime/executors/jobs/__init__.py +11 -0
  195. quantum/runtime/executors/jobs/job_executor.py +190 -0
  196. quantum/runtime/executors/jobs/schedule_executor.py +132 -0
  197. quantum/runtime/executors/jobs/thread_executor.py +127 -0
  198. quantum/runtime/executors/messaging/__init__.py +17 -0
  199. quantum/runtime/executors/messaging/message_ack_executor.py +51 -0
  200. quantum/runtime/executors/messaging/message_executor.py +174 -0
  201. quantum/runtime/executors/messaging/queue_executor.py +103 -0
  202. quantum/runtime/executors/messaging/websocket_executor.py +197 -0
  203. quantum/runtime/executors/scripting/__init__.py +11 -0
  204. quantum/runtime/executors/scripting/pyclass_executor.py +90 -0
  205. quantum/runtime/executors/scripting/pyimport_executor.py +81 -0
  206. quantum/runtime/executors/scripting/python_executor.py +249 -0
  207. quantum/runtime/executors/services/__init__.py +12 -0
  208. quantum/runtime/executors/services/dump_executor.py +72 -0
  209. quantum/runtime/executors/services/file_executor.py +89 -0
  210. quantum/runtime/executors/services/log_executor.py +77 -0
  211. quantum/runtime/executors/services/mail_executor.py +81 -0
  212. quantum/runtime/expression_cache.py +498 -0
  213. quantum/runtime/file_upload_service.py +326 -0
  214. quantum/runtime/function_registry.py +118 -0
  215. quantum/runtime/game_builder.py +166 -0
  216. quantum/runtime/game_code_generator.py +2371 -0
  217. quantum/runtime/game_templates.py +2006 -0
  218. quantum/runtime/godot_code_generator.py +4681 -0
  219. quantum/runtime/godot_templates.py +1449 -0
  220. quantum/runtime/job_executor.py +1599 -0
  221. quantum/runtime/knowledge_service.py +546 -0
  222. quantum/runtime/llm_cache.py +100 -0
  223. quantum/runtime/llm_providers.py +704 -0
  224. quantum/runtime/llm_service.py +287 -0
  225. quantum/runtime/local_server.py +28 -0
  226. quantum/runtime/logging_setup.py +141 -0
  227. quantum/runtime/message_broker.py +364 -0
  228. quantum/runtime/message_queue_service.py +571 -0
  229. quantum/runtime/param_validation.py +218 -0
  230. quantum/runtime/pypy_compat.py +317 -0
  231. quantum/runtime/python_bridge.py +698 -0
  232. quantum/runtime/query_validators.py +304 -0
  233. quantum/runtime/renderer.py +734 -0
  234. quantum/runtime/service_container.py +470 -0
  235. quantum/runtime/table_params.py +163 -0
  236. quantum/runtime/terminal_builder.py +76 -0
  237. quantum/runtime/terminal_code_generator.py +607 -0
  238. quantum/runtime/terminal_templates.py +243 -0
  239. quantum/runtime/testing_builder.py +77 -0
  240. quantum/runtime/testing_code_generator.py +833 -0
  241. quantum/runtime/testing_templates.py +85 -0
  242. quantum/runtime/ui_builder.py +222 -0
  243. quantum/runtime/ui_console.py +405 -0
  244. quantum/runtime/ui_desktop.py +56 -0
  245. quantum/runtime/ui_form_rules.py +193 -0
  246. quantum/runtime/ui_html_adapter.py +2567 -0
  247. quantum/runtime/ui_html_templates.py +2106 -0
  248. quantum/runtime/ui_mobile_adapter.py +1832 -0
  249. quantum/runtime/ui_mobile_templates.py +1003 -0
  250. quantum/runtime/ui_page_renderer.py +392 -0
  251. quantum/runtime/ui_pager.py +73 -0
  252. quantum/runtime/ui_table_edit.py +139 -0
  253. quantum/runtime/ui_textual_adapter.py +1866 -0
  254. quantum/runtime/ui_textual_templates.py +45 -0
  255. quantum/runtime/ui_tokens.py +465 -0
  256. quantum/runtime/ui_validator.py +365 -0
  257. quantum/runtime/ui_view.py +221 -0
  258. quantum/runtime/validators.py +256 -0
  259. quantum/runtime/web_server.py +2131 -0
  260. quantum/runtime/websocket_adapter.py +501 -0
  261. quantum/runtime/websocket_service.py +585 -0
  262. quantum/runtime/websocket_transport.py +289 -0
  263. quantum/runtime/wsgi.py +101 -0
  264. quantum/services.py +109 -0
  265. quantum/utils/__init__.py +1 -0
  266. quantum_admin/__init__.py +4 -0
  267. quantum_admin/assets/quantum-admin-connectors.js +123 -0
  268. quantum_admin/assets/quantum-admin.css +2058 -0
  269. quantum_admin/assets/quantum-source-highlight.js +106 -0
  270. quantum_admin/backend/__init__.py +4 -0
  271. quantum_admin/backend/audit_service.py +346 -0
  272. quantum_admin/backend/auth_service.py +394 -0
  273. quantum_admin/backend/component_discovery.py +766 -0
  274. quantum_admin/backend/config_generator.py +586 -0
  275. quantum_admin/backend/connector_service.py +1016 -0
  276. quantum_admin/backend/crud.py +557 -0
  277. quantum_admin/backend/database.py +226 -0
  278. quantum_admin/backend/db_setup_service.py +193 -0
  279. quantum_admin/backend/deploy_service.py +698 -0
  280. quantum_admin/backend/docker_service.py +422 -0
  281. quantum_admin/backend/environment_service.py +418 -0
  282. quantum_admin/backend/git_service.py +370 -0
  283. quantum_admin/backend/health_service.py +390 -0
  284. quantum_admin/backend/job_service.py +347 -0
  285. quantum_admin/backend/log_service.py +331 -0
  286. quantum_admin/backend/main.py +14426 -0
  287. quantum_admin/backend/models.py +986 -0
  288. quantum_admin/backend/pipeline_service.py +812 -0
  289. quantum_admin/backend/resource_manager.py +1118 -0
  290. quantum_admin/backend/schemas.py +419 -0
  291. quantum_admin/backend/secret_manager.py +277 -0
  292. quantum_admin/backend/settings_service.py +488 -0
  293. quantum_admin/backend/template_service.py +710 -0
  294. quantum_admin/backend/test_execution_service.py +364 -0
  295. quantum_admin/backend/test_generator.py +782 -0
  296. quantum_admin/backend/webhook_service.py +454 -0
  297. quantum_admin/backend/websocket_manager.py +360 -0
  298. quantum_admin/components/admin/_layout/AdminShell.q +121 -0
  299. quantum_admin/components/admin/agents.q +107 -0
  300. quantum_admin/components/admin/app/[name].q +558 -0
  301. quantum_admin/components/admin/applications.q +160 -0
  302. quantum_admin/components/admin/component/[...path].q +205 -0
  303. quantum_admin/components/admin/components.q +59 -0
  304. quantum_admin/components/admin/connectors.q +283 -0
  305. quantum_admin/components/admin/dashboard.q +86 -0
  306. quantum_admin/components/admin/database.q +95 -0
  307. quantum_admin/components/admin/features.q +69 -0
  308. quantum_admin/components/admin/index.q +12 -0
  309. quantum_admin/components/admin/jobs.q +75 -0
  310. quantum_admin/components/admin/login.q +65 -0
  311. quantum_admin/components/admin/logout.q +11 -0
  312. quantum_admin/components/admin/settings.q +174 -0
  313. quantum_admin/components/admin/source.q +45 -0
  314. quantum_admin/components/admin/tests.q +73 -0
  315. quantum_admin/run.py +13 -0
  316. quantum_admin/services/__init__.py +16 -0
  317. quantum_admin/services/_base.py +84 -0
  318. quantum_admin/services/_component_test_generator.py +205 -0
  319. quantum_admin/services/_yaml_editor.py +82 -0
  320. quantum_admin/services/apps.py +330 -0
  321. quantum_admin/services/auth.py +61 -0
  322. quantum_admin/services/components.py +210 -0
  323. quantum_admin/services/connectors.py +164 -0
  324. quantum_admin/services/projects.py +189 -0
  325. quantum_admin/services/repository.py +223 -0
  326. quantum_admin/services/settings.py +150 -0
  327. quantum_admin/services/yaml_import.py +118 -0
  328. quantum_framework-0.18.0.dist-info/METADATA +281 -0
  329. quantum_framework-0.18.0.dist-info/RECORD +333 -0
  330. quantum_framework-0.18.0.dist-info/WHEEL +5 -0
  331. quantum_framework-0.18.0.dist-info/entry_points.txt +2 -0
  332. quantum_framework-0.18.0.dist-info/licenses/LICENSE +21 -0
  333. quantum_framework-0.18.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,986 @@
1
+ """
2
+ SQLAlchemy models for Quantum Admin
3
+ """
4
+ from sqlalchemy import Column, Integer, String, Boolean, DateTime, ForeignKey, Text, UniqueConstraint
5
+ from sqlalchemy.orm import relationship, declarative_base
6
+ from datetime import datetime
7
+
8
+ Base = declarative_base()
9
+
10
+
11
+ class Project(Base):
12
+ """Project represents a Quantum application"""
13
+ __tablename__ = 'projects'
14
+
15
+ id = Column(Integer, primary_key=True)
16
+ name = Column(String(255), unique=True, nullable=False, index=True)
17
+ description = Column(Text)
18
+ status = Column(String(50), default='active') # active, inactive
19
+
20
+ # Source Code Configuration
21
+ source_path = Column(String(512)) # Local path to source code (e.g., /home/user/projects/myapp)
22
+ git_url = Column(String(512)) # Git repository URL (e.g., https://github.com/user/repo.git)
23
+ git_branch = Column(String(100), default='main') # Default branch to use
24
+
25
+ created_at = Column(DateTime, default=datetime.utcnow)
26
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
27
+
28
+ # Relationships
29
+ datasources = relationship("Datasource", back_populates="project", cascade="all, delete-orphan")
30
+ components = relationship("Component", back_populates="project", cascade="all, delete-orphan")
31
+ endpoints = relationship("Endpoint", back_populates="project", cascade="all, delete-orphan")
32
+
33
+ def __repr__(self):
34
+ return f"<Project(id={self.id}, name='{self.name}', status='{self.status}')>"
35
+
36
+
37
+ class Datasource(Base):
38
+ """Datasource represents a database connection (Docker or direct)"""
39
+ __tablename__ = 'datasources'
40
+
41
+ id = Column(Integer, primary_key=True)
42
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
43
+ name = Column(String(255), nullable=False)
44
+ type = Column(String(50), nullable=False) # postgres, mysql, mongodb
45
+ connection_type = Column(String(50), nullable=False) # docker, direct
46
+
47
+ # Docker-specific fields
48
+ container_id = Column(String(255))
49
+ image = Column(String(255))
50
+ port = Column(Integer)
51
+
52
+ # Connection details
53
+ host = Column(String(255))
54
+ database_name = Column(String(255))
55
+ username = Column(String(255))
56
+ password_encrypted = Column(Text) # Encrypted password
57
+
58
+ # Status
59
+ status = Column(String(50), default='stopped') # running, stopped, error
60
+ health_status = Column(String(50), default='unknown') # healthy, unhealthy, unknown
61
+ setup_status = Column(String(50), default='pending') # pending, configuring, ready, error
62
+ auto_start = Column(Boolean, default=False)
63
+
64
+ # Visibility/Sharing
65
+ visibility = Column(String(20), default='private') # private, shared, public
66
+ shared_with = Column(Text, default='[]') # JSON array of project_ids for "shared" visibility
67
+
68
+ # Metadata
69
+ created_at = Column(DateTime, default=datetime.utcnow)
70
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
71
+
72
+ # Relationships
73
+ project = relationship("Project", back_populates="datasources")
74
+ migrations = relationship("Migration", back_populates="datasource", cascade="all, delete-orphan")
75
+
76
+ def __repr__(self):
77
+ return f"<Datasource(id={self.id}, name='{self.name}', type='{self.type}', status='{self.status}')>"
78
+
79
+
80
+ class Migration(Base):
81
+ """Migration tracks schema changes for datasources"""
82
+ __tablename__ = 'migrations'
83
+
84
+ id = Column(Integer, primary_key=True)
85
+ datasource_id = Column(Integer, ForeignKey('datasources.id'), nullable=False, index=True)
86
+ version = Column(String(50), nullable=False)
87
+ name = Column(String(255), nullable=False)
88
+ sql_content = Column(Text, nullable=False)
89
+ applied_at = Column(DateTime)
90
+ status = Column(String(50), default='pending') # pending, applied, failed, rolled_back
91
+ error_message = Column(Text)
92
+
93
+ # Relationships
94
+ datasource = relationship("Datasource", back_populates="migrations")
95
+
96
+ def __repr__(self):
97
+ return f"<Migration(id={self.id}, version='{self.version}', status='{self.status}')>"
98
+
99
+
100
+ class Component(Base):
101
+ """Component represents a .q file in a project"""
102
+ __tablename__ = 'components'
103
+
104
+ id = Column(Integer, primary_key=True)
105
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
106
+ name = Column(String(255), nullable=False)
107
+ file_path = Column(String(512), nullable=False)
108
+ status = Column(String(50), default='unknown') # active, error, inactive, unknown
109
+ error_message = Column(Text)
110
+ last_compiled = Column(DateTime)
111
+
112
+ # Metadata counters (cached from parsing)
113
+ function_count = Column(Integer, default=0)
114
+ endpoint_count = Column(Integer, default=0)
115
+ query_count = Column(Integer, default=0)
116
+
117
+ # Test tracking
118
+ test_count = Column(Integer, default=0) # Total tests associated
119
+ tests_passing = Column(Integer, default=0) # Tests passing
120
+ tests_failing = Column(Integer, default=0) # Tests failing
121
+ last_test_run_id = Column(Integer, ForeignKey('test_runs.id'), nullable=True)
122
+ last_test_run_at = Column(DateTime)
123
+
124
+ # Relationships
125
+ project = relationship("Project", back_populates="components")
126
+ endpoints = relationship("Endpoint", back_populates="component")
127
+ last_test_run = relationship("TestRun", foreign_keys=[last_test_run_id])
128
+ tests = relationship("ComponentTest", back_populates="component", cascade="all, delete-orphan")
129
+
130
+ def __repr__(self):
131
+ return f"<Component(id={self.id}, name='{self.name}', status='{self.status}')>"
132
+
133
+ @property
134
+ def has_tests(self) -> bool:
135
+ return self.test_count > 0
136
+
137
+ @property
138
+ def test_coverage(self) -> int:
139
+ """Return test coverage percentage (0-100)"""
140
+ if self.test_count == 0:
141
+ return 0
142
+ return int((self.tests_passing / self.test_count) * 100) if self.test_count > 0 else 0
143
+
144
+
145
+ class Endpoint(Base):
146
+ """Endpoint represents a REST API endpoint defined in a component"""
147
+ __tablename__ = 'endpoints'
148
+
149
+ id = Column(Integer, primary_key=True)
150
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
151
+ component_id = Column(Integer, ForeignKey('components.id'))
152
+ method = Column(String(10), nullable=False) # GET, POST, PUT, DELETE, PATCH
153
+ path = Column(String(512), nullable=False)
154
+ function_name = Column(String(255), nullable=False)
155
+ description = Column(Text)
156
+
157
+ # Relationships
158
+ project = relationship("Project", back_populates="endpoints")
159
+ component = relationship("Component", back_populates="endpoints")
160
+
161
+ def __repr__(self):
162
+ return f"<Endpoint(id={self.id}, method='{self.method}', path='{self.path}')>"
163
+
164
+
165
+ class TestRun(Base):
166
+ """TestRun represents a test execution session"""
167
+ __tablename__ = 'test_runs'
168
+
169
+ id = Column(Integer, primary_key=True)
170
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
171
+ status = Column(String(50), default='running') # running, completed, failed, cancelled
172
+ started_at = Column(DateTime, default=datetime.utcnow, nullable=False)
173
+ completed_at = Column(DateTime)
174
+ total_tests = Column(Integer, default=0)
175
+ passed_tests = Column(Integer, default=0)
176
+ failed_tests = Column(Integer, default=0)
177
+ duration_seconds = Column(Integer)
178
+ triggered_by = Column(String(255)) # user, ci/cd, schedule
179
+ suite_filter = Column(String(255)) # Optional suite name filter
180
+ error_message = Column(Text)
181
+
182
+ # Relationships
183
+ project = relationship("Project", backref="test_runs")
184
+ test_results = relationship("TestResult", back_populates="test_run", cascade="all, delete-orphan")
185
+
186
+ def __repr__(self):
187
+ return f"<TestRun(id={self.id}, status='{self.status}', passed={self.passed_tests}/{self.total_tests})>"
188
+
189
+
190
+ class TestResult(Base):
191
+ """TestResult represents individual test execution results"""
192
+ __tablename__ = 'test_results'
193
+
194
+ id = Column(Integer, primary_key=True)
195
+ test_run_id = Column(Integer, ForeignKey('test_runs.id'), nullable=False, index=True)
196
+ suite_name = Column(String(255), nullable=False)
197
+ test_file = Column(String(512), nullable=False)
198
+ status = Column(String(50), nullable=False) # passed, failed, error, skipped
199
+ duration_seconds = Column(Integer)
200
+ error_message = Column(Text)
201
+ output = Column(Text)
202
+
203
+ # Relationships
204
+ test_run = relationship("TestRun", back_populates="test_results")
205
+
206
+ def __repr__(self):
207
+ return f"<TestResult(id={self.id}, suite='{self.suite_name}', status='{self.status}')>"
208
+
209
+
210
+ class ComponentTest(Base):
211
+ """ComponentTest tracks tests associated with components"""
212
+ __tablename__ = 'component_tests'
213
+
214
+ id = Column(Integer, primary_key=True)
215
+ component_id = Column(Integer, ForeignKey('components.id'), nullable=False, index=True)
216
+
217
+ # Test info
218
+ test_file = Column(String(512), nullable=False) # tests/test_user_login.py
219
+ test_name = Column(String(255), nullable=False) # test_login_success
220
+ test_type = Column(String(50), default='unit') # unit, integration, api, e2e
221
+
222
+ # Generation info
223
+ generated_at = Column(DateTime)
224
+ generated_by = Column(String(50)) # auto, manual
225
+
226
+ # Last execution
227
+ last_run_id = Column(Integer, ForeignKey('test_runs.id'), nullable=True)
228
+ last_status = Column(String(50)) # passed, failed, skipped, pending
229
+ last_run_at = Column(DateTime)
230
+ last_duration_seconds = Column(Integer)
231
+ last_error_message = Column(Text)
232
+
233
+ # Metadata
234
+ created_at = Column(DateTime, default=datetime.utcnow)
235
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
236
+
237
+ # Relationships
238
+ component = relationship("Component", back_populates="tests")
239
+ last_run = relationship("TestRun", foreign_keys=[last_run_id])
240
+
241
+ def __repr__(self):
242
+ return f"<ComponentTest(id={self.id}, name='{self.test_name}', status='{self.last_status}')>"
243
+
244
+ def to_dict(self):
245
+ return {
246
+ "id": self.id,
247
+ "component_id": self.component_id,
248
+ "test_file": self.test_file,
249
+ "test_name": self.test_name,
250
+ "test_type": self.test_type,
251
+ "generated_at": self.generated_at.isoformat() if self.generated_at else None,
252
+ "generated_by": self.generated_by,
253
+ "last_status": self.last_status,
254
+ "last_run_at": self.last_run_at.isoformat() if self.last_run_at else None,
255
+ "last_duration_seconds": self.last_duration_seconds,
256
+ "created_at": self.created_at.isoformat() if self.created_at else None
257
+ }
258
+
259
+
260
+ class EnvironmentVariable(Base):
261
+ """EnvironmentVariable represents a project environment variable or secret"""
262
+ __tablename__ = 'environment_variables'
263
+
264
+ id = Column(Integer, primary_key=True)
265
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
266
+ key = Column(String(255), nullable=False)
267
+ value_encrypted = Column(Text, nullable=False) # Encrypted value
268
+ description = Column(Text)
269
+ is_secret = Column(Boolean, default=False) # If true, value is masked in UI
270
+ created_at = Column(DateTime, default=datetime.utcnow)
271
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
272
+
273
+ # Relationships
274
+ project = relationship("Project", backref="environment_variables")
275
+
276
+ def __repr__(self):
277
+ return f"<EnvironmentVariable(id={self.id}, key='{self.key}', is_secret={self.is_secret})>"
278
+
279
+
280
+ class ConfigurationHistory(Base):
281
+ """ConfigurationHistory tracks changes to project configuration"""
282
+ __tablename__ = 'configuration_history'
283
+
284
+ id = Column(Integer, primary_key=True)
285
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
286
+ version = Column(Integer, nullable=False)
287
+ changed_at = Column(DateTime, default=datetime.utcnow, nullable=False)
288
+ changed_by = Column(String(255)) # user, api, system
289
+ changes_json = Column(Text) # JSON describing what changed
290
+ snapshot_json = Column(Text) # Full configuration snapshot
291
+
292
+ # Relationships
293
+ project = relationship("Project", backref="configuration_history")
294
+
295
+ def __repr__(self):
296
+ return f"<ConfigurationHistory(id={self.id}, project_id={self.project_id}, version={self.version})>"
297
+
298
+
299
+ class Connector(Base):
300
+ """Connector represents an infrastructure connection (database, queue, cache, storage, AI)"""
301
+ __tablename__ = 'connectors'
302
+
303
+ id = Column(String(50), primary_key=True)
304
+ name = Column(String(255), nullable=False)
305
+ type = Column(String(50), nullable=False) # database, mq, cache, storage, ai
306
+ provider = Column(String(50), nullable=False) # postgres, mysql, rabbitmq, redis, s3, ollama, etc.
307
+ host = Column(String(255), default='localhost')
308
+ port = Column(Integer, default=0)
309
+ username = Column(String(255), default='')
310
+ password_encrypted = Column(Text, default='') # Encrypted password/API key
311
+ database = Column(String(255), default='') # or bucket, vhost, etc.
312
+ options_json = Column(Text, default='{}') # JSON for provider-specific options
313
+ is_default = Column(Boolean, default=False)
314
+ docker_auto = Column(Boolean, default=False)
315
+ docker_image = Column(String(255), default='')
316
+ docker_container_id = Column(String(255), default='')
317
+ status = Column(String(50), default='unknown') # unknown, connected, disconnected, error
318
+ last_tested = Column(DateTime)
319
+ created_at = Column(DateTime, default=datetime.utcnow)
320
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
321
+ # Visibility: private (project-only), shared (specific projects), public (all projects)
322
+ visibility = Column(String(20), default='public') # private, shared, public
323
+ shared_with = Column(Text, default='[]') # JSON array of project_ids for "shared" visibility
324
+ owner_project_id = Column(Integer, ForeignKey('projects.id'), nullable=True) # Project that owns this connector
325
+
326
+ # Relationship to application (Project)
327
+ application = relationship("Project", backref="connectors")
328
+
329
+ def __repr__(self):
330
+ scope_info = f"app={self.application_id}" if self.scope == 'application' else 'public'
331
+ return f"<Connector(id={self.id}, name='{self.name}', {scope_info})>"
332
+
333
+
334
+ class DeploymentTarget(Base):
335
+ """DeploymentTarget represents a deployment destination"""
336
+ __tablename__ = 'deployment_targets'
337
+
338
+ id = Column(Integer, primary_key=True)
339
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
340
+ name = Column(String(255), nullable=False)
341
+ type = Column(String(50), nullable=False) # local, docker, ssh, kubernetes
342
+ config_json = Column(Text) # JSON configuration for target (host, port, paths, etc.)
343
+ is_active = Column(Boolean, default=True)
344
+ created_at = Column(DateTime, default=datetime.utcnow)
345
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
346
+
347
+ # Relationships
348
+ project = relationship("Project", backref="deployment_targets")
349
+ deployments = relationship("Deployment", back_populates="target", cascade="all, delete-orphan")
350
+
351
+ def __repr__(self):
352
+ return f"<DeploymentTarget(id={self.id}, name='{self.name}', type='{self.type}')>"
353
+
354
+
355
+ class Deployment(Base):
356
+ """Deployment represents a deployment execution"""
357
+ __tablename__ = 'deployments'
358
+
359
+ id = Column(Integer, primary_key=True)
360
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
361
+ target_id = Column(Integer, ForeignKey('deployment_targets.id'), nullable=False, index=True)
362
+ version = Column(String(100)) # Git commit, tag, or version number
363
+ status = Column(String(50), default='pending') # pending, building, deploying, completed, failed, rolled_back
364
+ started_at = Column(DateTime, default=datetime.utcnow)
365
+ completed_at = Column(DateTime)
366
+ duration_seconds = Column(Integer)
367
+ triggered_by = Column(String(255)) # user, ci/cd, schedule
368
+ build_log = Column(Text)
369
+ deploy_log = Column(Text)
370
+ error_message = Column(Text)
371
+ rollback_from = Column(Integer) # ID of deployment being rolled back
372
+
373
+ # Relationships
374
+ project = relationship("Project", backref="deployments")
375
+ target = relationship("DeploymentTarget", back_populates="deployments")
376
+ versions = relationship("DeploymentVersion", back_populates="deployment")
377
+
378
+ def __repr__(self):
379
+ return f"<Deployment(id={self.id}, version='{self.version}', status='{self.status}')>"
380
+
381
+
382
+ class Environment(Base):
383
+ """Environment represents a deployment environment (dev, staging, production)"""
384
+ __tablename__ = 'environments'
385
+
386
+ id = Column(Integer, primary_key=True)
387
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
388
+ name = Column(String(50), nullable=False) # dev, staging, production
389
+ display_name = Column(String(100)) # Development, Staging, Production
390
+ order = Column(Integer, default=1) # 1, 2, 3 (promotion order)
391
+
392
+ # Deployment Config
393
+ docker_host = Column(String(255)) # ssh://user@host or unix://
394
+ docker_registry = Column(String(255))
395
+ deploy_path = Column(String(512)) # /var/www/app
396
+ container_name = Column(String(100))
397
+ port = Column(Integer)
398
+
399
+ # Git Config
400
+ branch = Column(String(100), default='main') # main, staging, develop
401
+ auto_deploy = Column(Boolean, default=False) # Deploy on push to branch
402
+
403
+ # Approval
404
+ requires_approval = Column(Boolean, default=False) # Gate for production
405
+ approved_by = Column(String(255)) # Last approver
406
+
407
+ # Health Check
408
+ health_url = Column(String(512)) # http://host:port/health
409
+ health_timeout = Column(Integer, default=30)
410
+
411
+ # Secrets (env vars for this environment)
412
+ env_vars_json = Column(Text) # {"DB_HOST": "prod-db.example.com"}
413
+
414
+ # Status
415
+ is_active = Column(Boolean, default=True)
416
+ created_at = Column(DateTime, default=datetime.utcnow)
417
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
418
+
419
+ # Relationships
420
+ project = relationship("Project", backref="environments")
421
+ deployment_versions = relationship("DeploymentVersion", back_populates="environment")
422
+
423
+ def __repr__(self):
424
+ return f"<Environment(id={self.id}, name='{self.name}', project_id={self.project_id})>"
425
+
426
+ def to_dict(self):
427
+ return {
428
+ "id": self.id,
429
+ "project_id": self.project_id,
430
+ "name": self.name,
431
+ "display_name": self.display_name,
432
+ "order": self.order,
433
+ "docker_host": self.docker_host,
434
+ "docker_registry": self.docker_registry,
435
+ "deploy_path": self.deploy_path,
436
+ "container_name": self.container_name,
437
+ "port": self.port,
438
+ "branch": self.branch,
439
+ "auto_deploy": self.auto_deploy,
440
+ "requires_approval": self.requires_approval,
441
+ "health_url": self.health_url,
442
+ "health_timeout": self.health_timeout,
443
+ "is_active": self.is_active,
444
+ "created_at": self.created_at.isoformat() if self.created_at else None,
445
+ "updated_at": self.updated_at.isoformat() if self.updated_at else None
446
+ }
447
+
448
+
449
+ class DeploymentVersion(Base):
450
+ """DeploymentVersion tracks deployed versions with git and docker info"""
451
+ __tablename__ = 'deployment_versions'
452
+
453
+ id = Column(Integer, primary_key=True)
454
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
455
+ environment_id = Column(Integer, ForeignKey('environments.id'), nullable=False, index=True)
456
+
457
+ # Git Info
458
+ git_commit = Column(String(40)) # Full SHA
459
+ git_branch = Column(String(100))
460
+ git_message = Column(Text) # Commit message
461
+ git_author = Column(String(255))
462
+
463
+ # Docker Info
464
+ docker_image = Column(String(255)) # registry/app:commit-sha
465
+ docker_tag = Column(String(100)) # commit-sha or latest
466
+
467
+ # Deploy Status
468
+ deployment_id = Column(Integer, ForeignKey('deployments.id'))
469
+ is_current = Column(Boolean, default=False) # Currently deployed version
470
+ is_rollback_target = Column(Boolean, default=True) # Available for rollback
471
+
472
+ created_at = Column(DateTime, default=datetime.utcnow)
473
+
474
+ # Relationships
475
+ project = relationship("Project", backref="deployment_versions")
476
+ environment = relationship("Environment", back_populates="deployment_versions")
477
+ deployment = relationship("Deployment", back_populates="versions")
478
+
479
+ def __repr__(self):
480
+ return f"<DeploymentVersion(id={self.id}, commit='{self.git_commit[:7] if self.git_commit else 'N/A'}', env={self.environment_id})>"
481
+
482
+ def to_dict(self):
483
+ return {
484
+ "id": self.id,
485
+ "project_id": self.project_id,
486
+ "environment_id": self.environment_id,
487
+ "git_commit": self.git_commit,
488
+ "git_branch": self.git_branch,
489
+ "git_message": self.git_message,
490
+ "git_author": self.git_author,
491
+ "docker_image": self.docker_image,
492
+ "docker_tag": self.docker_tag,
493
+ "deployment_id": self.deployment_id,
494
+ "is_current": self.is_current,
495
+ "is_rollback_target": self.is_rollback_target,
496
+ "created_at": self.created_at.isoformat() if self.created_at else None
497
+ }
498
+
499
+
500
+ class WebhookEvent(Base):
501
+ """WebhookEvent tracks incoming webhook events from GitHub/GitLab"""
502
+ __tablename__ = 'webhook_events'
503
+
504
+ id = Column(Integer, primary_key=True)
505
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=True, index=True)
506
+
507
+ # Event Info
508
+ provider = Column(String(50), nullable=False) # github, gitlab, bitbucket
509
+ event_type = Column(String(50), nullable=False) # push, pull_request, tag
510
+ payload_json = Column(Text)
511
+
512
+ # Git Info
513
+ branch = Column(String(100))
514
+ commit = Column(String(40))
515
+ author = Column(String(255))
516
+ message = Column(Text)
517
+
518
+ # Processing
519
+ processed = Column(Boolean, default=False)
520
+ deployment_id = Column(Integer, ForeignKey('deployments.id'))
521
+ error_message = Column(Text)
522
+
523
+ received_at = Column(DateTime, default=datetime.utcnow)
524
+
525
+ # Relationships
526
+ project = relationship("Project", backref="webhook_events")
527
+ deployment = relationship("Deployment", backref="webhook_event")
528
+
529
+ def __repr__(self):
530
+ return f"<WebhookEvent(id={self.id}, provider='{self.provider}', type='{self.event_type}')>"
531
+
532
+ def to_dict(self):
533
+ return {
534
+ "id": self.id,
535
+ "project_id": self.project_id,
536
+ "provider": self.provider,
537
+ "event_type": self.event_type,
538
+ "branch": self.branch,
539
+ "commit": self.commit,
540
+ "author": self.author,
541
+ "message": self.message,
542
+ "processed": self.processed,
543
+ "deployment_id": self.deployment_id,
544
+ "error_message": self.error_message,
545
+ "received_at": self.received_at.isoformat() if self.received_at else None
546
+ }
547
+
548
+
549
+ # =============================================================================
550
+ # RESOURCE MANAGER MODELS
551
+ # =============================================================================
552
+
553
+ class PortAllocation(Base):
554
+ """Track all port allocations across hosts"""
555
+ __tablename__ = 'port_allocations'
556
+
557
+ id = Column(Integer, primary_key=True)
558
+
559
+ # What owns this port
560
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=True, index=True)
561
+ environment_id = Column(Integer, ForeignKey('environments.id'), nullable=True, index=True)
562
+ service_name = Column(String(100), nullable=False)
563
+
564
+ # Port info
565
+ port = Column(Integer, nullable=False)
566
+ port_type = Column(String(50)) # app, database, cache, web, custom
567
+ host = Column(String(255), default='localhost')
568
+
569
+ # Status
570
+ status = Column(String(50), default='allocated') # allocated, reserved, released
571
+ is_reserved = Column(Boolean, default=False)
572
+ reserved_reason = Column(String(255))
573
+
574
+ # Metadata
575
+ allocated_at = Column(DateTime, default=datetime.utcnow)
576
+ released_at = Column(DateTime, nullable=True)
577
+ last_health_check = Column(DateTime, nullable=True)
578
+ health_status = Column(String(50)) # healthy, unhealthy, unknown
579
+
580
+ # Relationships
581
+ project = relationship("Project", backref="port_allocations")
582
+ environment = relationship("Environment", backref="port_allocations")
583
+
584
+ __table_args__ = (
585
+ UniqueConstraint('port', 'host', name='uq_port_host'),
586
+ )
587
+
588
+ def to_dict(self):
589
+ return {
590
+ "id": self.id,
591
+ "project_id": self.project_id,
592
+ "environment_id": self.environment_id,
593
+ "service_name": self.service_name,
594
+ "port": self.port,
595
+ "port_type": self.port_type,
596
+ "host": self.host,
597
+ "status": self.status,
598
+ "is_reserved": self.is_reserved,
599
+ "reserved_reason": self.reserved_reason,
600
+ "allocated_at": self.allocated_at.isoformat() if self.allocated_at else None,
601
+ "health_status": self.health_status
602
+ }
603
+
604
+
605
+ class PortRange(Base):
606
+ """Define port ranges for different service types"""
607
+ __tablename__ = 'port_ranges'
608
+
609
+ id = Column(Integer, primary_key=True)
610
+ name = Column(String(50), unique=True, nullable=False)
611
+ start_port = Column(Integer, nullable=False)
612
+ end_port = Column(Integer, nullable=False)
613
+ host = Column(String(255), default='*') # * = all hosts
614
+ description = Column(String(255))
615
+
616
+ def to_dict(self):
617
+ return {
618
+ "id": self.id,
619
+ "name": self.name,
620
+ "start_port": self.start_port,
621
+ "end_port": self.end_port,
622
+ "host": self.host,
623
+ "description": self.description
624
+ }
625
+
626
+
627
+ class Secret(Base):
628
+ """Encrypted secrets storage"""
629
+ __tablename__ = 'secrets'
630
+
631
+ id = Column(Integer, primary_key=True)
632
+
633
+ # Scope
634
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=True, index=True)
635
+ environment_id = Column(Integer, ForeignKey('environments.id'), nullable=True, index=True)
636
+ scope = Column(String(50), default='global') # global, project, environment
637
+
638
+ # Secret data
639
+ key = Column(String(255), nullable=False)
640
+ encrypted_value = Column(Text, nullable=False)
641
+
642
+ # Metadata
643
+ description = Column(String(500))
644
+ is_sensitive = Column(Boolean, default=True)
645
+ created_at = Column(DateTime, default=datetime.utcnow)
646
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
647
+ last_rotated = Column(DateTime)
648
+
649
+ # Relationships
650
+ project = relationship("Project", backref="secrets")
651
+ environment = relationship("Environment", backref="secrets")
652
+
653
+ def to_dict(self, include_value: bool = False):
654
+ result = {
655
+ "id": self.id,
656
+ "project_id": self.project_id,
657
+ "environment_id": self.environment_id,
658
+ "scope": self.scope,
659
+ "key": self.key,
660
+ "description": self.description,
661
+ "is_sensitive": self.is_sensitive,
662
+ "created_at": self.created_at.isoformat() if self.created_at else None,
663
+ "last_rotated": self.last_rotated.isoformat() if self.last_rotated else None
664
+ }
665
+ if include_value:
666
+ result["encrypted_value"] = self.encrypted_value
667
+ return result
668
+
669
+
670
+ class ServiceRegistry(Base):
671
+ """Service discovery registry"""
672
+ __tablename__ = 'service_registry'
673
+
674
+ id = Column(Integer, primary_key=True)
675
+
676
+ # Service identity
677
+ name = Column(String(100), nullable=False, unique=True)
678
+ service_type = Column(String(50)) # app, database, cache, queue, gateway
679
+
680
+ # Location
681
+ host = Column(String(255), nullable=False)
682
+ port = Column(Integer, nullable=False)
683
+ protocol = Column(String(20), default='http')
684
+
685
+ # Health
686
+ health_endpoint = Column(String(255))
687
+ health_interval = Column(Integer, default=30)
688
+ last_health_check = Column(DateTime)
689
+ health_status = Column(String(50), default='unknown')
690
+ consecutive_failures = Column(Integer, default=0)
691
+
692
+ # Metadata
693
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=True, index=True)
694
+ environment_id = Column(Integer, ForeignKey('environments.id'), nullable=True, index=True)
695
+ metadata_json = Column(Text)
696
+
697
+ registered_at = Column(DateTime, default=datetime.utcnow)
698
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
699
+
700
+ # Relationships
701
+ project = relationship("Project", backref="services")
702
+ environment = relationship("Environment", backref="services")
703
+
704
+ def to_dict(self):
705
+ import json
706
+ return {
707
+ "id": self.id,
708
+ "name": self.name,
709
+ "service_type": self.service_type,
710
+ "host": self.host,
711
+ "port": self.port,
712
+ "protocol": self.protocol,
713
+ "health_endpoint": self.health_endpoint,
714
+ "health_status": self.health_status,
715
+ "last_health_check": self.last_health_check.isoformat() if self.last_health_check else None,
716
+ "project_id": self.project_id,
717
+ "environment_id": self.environment_id,
718
+ "metadata": json.loads(self.metadata_json) if self.metadata_json else {},
719
+ "registered_at": self.registered_at.isoformat() if self.registered_at else None
720
+ }
721
+
722
+
723
+ # =============================================================================
724
+ # AUDIT LOG MODEL
725
+ # =============================================================================
726
+
727
+ class AuditLog(Base):
728
+ """Track all user actions for auditing purposes"""
729
+ __tablename__ = 'audit_logs'
730
+
731
+ id = Column(Integer, primary_key=True)
732
+ timestamp = Column(DateTime, default=datetime.utcnow, index=True)
733
+
734
+ # User info
735
+ user_id = Column(String(100))
736
+ user_ip = Column(String(45)) # IPv6 compatible
737
+
738
+ # Action info
739
+ action = Column(String(50), nullable=False) # create, update, delete, deploy, login, logout, etc.
740
+ resource_type = Column(String(50)) # project, connector, environment, deployment, etc.
741
+ resource_id = Column(Integer)
742
+ resource_name = Column(String(255))
743
+
744
+ # Additional context
745
+ details = Column(Text) # JSON with additional details
746
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=True, index=True)
747
+
748
+ # Request info
749
+ request_method = Column(String(10))
750
+ request_path = Column(String(512))
751
+
752
+ # Relationships
753
+ project = relationship("Project", backref="audit_logs")
754
+
755
+ def __repr__(self):
756
+ return f"<AuditLog(id={self.id}, action='{self.action}', resource='{self.resource_type}:{self.resource_id}')>"
757
+
758
+ def to_dict(self):
759
+ import json
760
+ return {
761
+ "id": self.id,
762
+ "timestamp": self.timestamp.isoformat() if self.timestamp else None,
763
+ "user_id": self.user_id,
764
+ "user_ip": self.user_ip,
765
+ "action": self.action,
766
+ "resource_type": self.resource_type,
767
+ "resource_id": self.resource_id,
768
+ "resource_name": self.resource_name,
769
+ "details": json.loads(self.details) if self.details else None,
770
+ "project_id": self.project_id,
771
+ "request_method": self.request_method,
772
+ "request_path": self.request_path
773
+ }
774
+
775
+
776
+ # =============================================================================
777
+ # HEALTH CHECK & INCIDENT MODELS
778
+ # =============================================================================
779
+
780
+ class HealthCheck(Base):
781
+ """Health check configuration for services"""
782
+ __tablename__ = 'health_checks'
783
+
784
+ id = Column(Integer, primary_key=True)
785
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=True, index=True)
786
+ environment_id = Column(Integer, ForeignKey('environments.id'), nullable=True, index=True)
787
+
788
+ # Check configuration
789
+ name = Column(String(100), nullable=False)
790
+ endpoint = Column(String(512), nullable=False)
791
+ method = Column(String(10), default='GET')
792
+ expected_status = Column(Integer, default=200)
793
+ timeout_seconds = Column(Integer, default=30)
794
+ interval_seconds = Column(Integer, default=60)
795
+
796
+ # Status
797
+ is_active = Column(Boolean, default=True)
798
+ last_check = Column(DateTime)
799
+ last_status = Column(String(50)) # healthy, unhealthy, unknown
800
+ consecutive_failures = Column(Integer, default=0)
801
+
802
+ created_at = Column(DateTime, default=datetime.utcnow)
803
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
804
+
805
+ # Relationships
806
+ project = relationship("Project", backref="health_checks")
807
+ environment = relationship("Environment", backref="health_checks")
808
+
809
+ def to_dict(self):
810
+ return {
811
+ "id": self.id,
812
+ "project_id": self.project_id,
813
+ "environment_id": self.environment_id,
814
+ "name": self.name,
815
+ "endpoint": self.endpoint,
816
+ "method": self.method,
817
+ "expected_status": self.expected_status,
818
+ "timeout_seconds": self.timeout_seconds,
819
+ "interval_seconds": self.interval_seconds,
820
+ "is_active": self.is_active,
821
+ "last_check": self.last_check.isoformat() if self.last_check else None,
822
+ "last_status": self.last_status,
823
+ "consecutive_failures": self.consecutive_failures
824
+ }
825
+
826
+
827
+ class Incident(Base):
828
+ """Track health check incidents"""
829
+ __tablename__ = 'incidents'
830
+
831
+ id = Column(Integer, primary_key=True)
832
+ health_check_id = Column(Integer, ForeignKey('health_checks.id'), nullable=False, index=True)
833
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=True, index=True)
834
+
835
+ # Incident details
836
+ started_at = Column(DateTime, default=datetime.utcnow, nullable=False)
837
+ resolved_at = Column(DateTime)
838
+ status = Column(String(50), default='active') # active, resolved, acknowledged
839
+
840
+ # Error info
841
+ error_message = Column(Text)
842
+ error_code = Column(Integer)
843
+ response_time_ms = Column(Integer)
844
+
845
+ # Resolution
846
+ resolved_by = Column(String(100))
847
+ resolution_notes = Column(Text)
848
+
849
+ # Relationships
850
+ health_check = relationship("HealthCheck", backref="incidents")
851
+ project = relationship("Project", backref="incidents")
852
+
853
+ def to_dict(self):
854
+ return {
855
+ "id": self.id,
856
+ "health_check_id": self.health_check_id,
857
+ "project_id": self.project_id,
858
+ "started_at": self.started_at.isoformat() if self.started_at else None,
859
+ "resolved_at": self.resolved_at.isoformat() if self.resolved_at else None,
860
+ "status": self.status,
861
+ "error_message": self.error_message,
862
+ "error_code": self.error_code,
863
+ "response_time_ms": self.response_time_ms,
864
+ "resolved_by": self.resolved_by,
865
+ "resolution_notes": self.resolution_notes
866
+ }
867
+
868
+
869
+ # =============================================================================
870
+ # APPLICATION LOG MODEL
871
+ # =============================================================================
872
+
873
+ class ApplicationLog(Base):
874
+ """Store application runtime logs"""
875
+ __tablename__ = 'application_logs'
876
+
877
+ id = Column(Integer, primary_key=True)
878
+ project_id = Column(Integer, ForeignKey('projects.id'), nullable=False, index=True)
879
+ environment_id = Column(Integer, ForeignKey('environments.id'), nullable=True, index=True)
880
+
881
+ # Log entry
882
+ timestamp = Column(DateTime, default=datetime.utcnow, index=True)
883
+ level = Column(String(20), nullable=False) # DEBUG, INFO, WARNING, ERROR, CRITICAL
884
+ source = Column(String(50)) # app, db, access, deploy, system
885
+ message = Column(Text, nullable=False)
886
+
887
+ # Context
888
+ component_name = Column(String(255))
889
+ function_name = Column(String(255))
890
+ line_number = Column(Integer)
891
+
892
+ # Request context (for access logs)
893
+ request_id = Column(String(50))
894
+ request_method = Column(String(10))
895
+ request_path = Column(String(512))
896
+ response_status = Column(Integer)
897
+ response_time_ms = Column(Integer)
898
+
899
+ # Additional data
900
+ extra_data = Column(Text) # JSON
901
+
902
+ # Relationships
903
+ project = relationship("Project", backref="application_logs")
904
+ environment = relationship("Environment", backref="application_logs")
905
+
906
+ def to_dict(self):
907
+ import json
908
+ return {
909
+ "id": self.id,
910
+ "project_id": self.project_id,
911
+ "environment_id": self.environment_id,
912
+ "timestamp": self.timestamp.isoformat() if self.timestamp else None,
913
+ "level": self.level,
914
+ "source": self.source,
915
+ "message": self.message,
916
+ "component_name": self.component_name,
917
+ "function_name": self.function_name,
918
+ "request_id": self.request_id,
919
+ "request_method": self.request_method,
920
+ "request_path": self.request_path,
921
+ "response_status": self.response_status,
922
+ "response_time_ms": self.response_time_ms,
923
+ "extra_data": json.loads(self.extra_data) if self.extra_data else None
924
+ }
925
+
926
+
927
+ # =============================================================================
928
+ # CLOUD INTEGRATION MODEL
929
+ # =============================================================================
930
+
931
+ class CloudIntegration(Base):
932
+ """Cloud provider integration configuration"""
933
+ __tablename__ = 'cloud_integrations'
934
+
935
+ id = Column(Integer, primary_key=True)
936
+ name = Column(String(100), nullable=False)
937
+ provider = Column(String(50), nullable=False) # aws, kubernetes, azure, gcp
938
+
939
+ # Encrypted credentials (JSON)
940
+ credentials_encrypted = Column(Text, nullable=False)
941
+
942
+ # Configuration
943
+ region = Column(String(50))
944
+ default_config_json = Column(Text) # Default deployment config
945
+
946
+ # Status
947
+ is_active = Column(Boolean, default=True)
948
+ is_default = Column(Boolean, default=False) # Default for deploy
949
+ last_tested = Column(DateTime)
950
+ test_status = Column(String(50)) # success, failed, unknown
951
+
952
+ # Metadata
953
+ created_at = Column(DateTime, default=datetime.utcnow)
954
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
955
+
956
+ def __repr__(self):
957
+ return f"<CloudIntegration(id={self.id}, name='{self.name}', provider='{self.provider}')>"
958
+
959
+ def to_dict(self, include_credentials: bool = False):
960
+ import json
961
+ result = {
962
+ "id": self.id,
963
+ "name": self.name,
964
+ "provider": self.provider,
965
+ "region": self.region,
966
+ "default_config": json.loads(self.default_config_json) if self.default_config_json else {},
967
+ "is_active": self.is_active,
968
+ "is_default": self.is_default,
969
+ "last_tested": self.last_tested.isoformat() if self.last_tested else None,
970
+ "test_status": self.test_status,
971
+ "created_at": self.created_at.isoformat() if self.created_at else None,
972
+ "updated_at": self.updated_at.isoformat() if self.updated_at else None
973
+ }
974
+ if include_credentials:
975
+ # The column is encrypted now (it was plain JSON under an
976
+ # "_encrypted" name). decrypt_or_legacy also reads rows written
977
+ # before that, so existing configuration is not lost.
978
+ try:
979
+ from .secret_manager import decrypt_or_legacy
980
+ except ImportError:
981
+ from secret_manager import decrypt_or_legacy
982
+ result["credentials"] = (
983
+ json.loads(decrypt_or_legacy(self.credentials_encrypted))
984
+ if self.credentials_encrypted else {}
985
+ )
986
+ return result