draft-board 0.1.0-beta.0
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.
- package/app/backend/.env.example +9 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_patch.txt +195 -0
- package/app/backend/.smartkanban/evidence/8b383839-cbec-45af-86ee-c7708d075cbe/bddf2ed5-2e21-4d46-a62b-10b87f1642a6_stat.txt +6 -0
- package/app/backend/CURL_EXAMPLES.md +335 -0
- package/app/backend/ENV_SETUP.md +65 -0
- package/app/backend/alembic/env.py +71 -0
- package/app/backend/alembic/script.py.mako +28 -0
- package/app/backend/alembic/versions/001_initial_schema.py +104 -0
- package/app/backend/alembic/versions/002_add_jobs_table.py +52 -0
- package/app/backend/alembic/versions/003_add_workspace_table.py +48 -0
- package/app/backend/alembic/versions/004_add_evidence_table.py +56 -0
- package/app/backend/alembic/versions/005_add_verification_commands.py +32 -0
- package/app/backend/alembic/versions/006_add_planner_lock_table.py +39 -0
- package/app/backend/alembic/versions/007_add_revision_review_tables.py +126 -0
- package/app/backend/alembic/versions/008_add_revision_idempotency_and_traceability.py +52 -0
- package/app/backend/alembic/versions/009_add_job_health_fields.py +46 -0
- package/app/backend/alembic/versions/010_add_review_comment_line_content.py +36 -0
- package/app/backend/alembic/versions/011_add_analysis_cache.py +47 -0
- package/app/backend/alembic/versions/012_add_boards_table.py +102 -0
- package/app/backend/alembic/versions/013_add_ticket_blocking.py +45 -0
- package/app/backend/alembic/versions/014_add_agent_sessions.py +220 -0
- package/app/backend/alembic/versions/015_add_ticket_sort_order.py +33 -0
- package/app/backend/alembic/versions/03220f0b93ae_add_pr_fields_to_ticket.py +49 -0
- package/app/backend/alembic/versions/0c2d89fff3b1_seed_board_configs_from_yaml.py +206 -0
- package/app/backend/alembic/versions/3348e5cf54c1_add_merge_checklist_table.py +67 -0
- package/app/backend/alembic/versions/357c780ee445_add_goal_status.py +34 -0
- package/app/backend/alembic/versions/553340b7e26c_add_autonomy_fields_to_goal.py +65 -0
- package/app/backend/alembic/versions/774dc335c679_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/7b307e847cbd_merge_heads.py +23 -0
- package/app/backend/alembic/versions/82ecd978cc70_add_missing_indexes.py +48 -0
- package/app/backend/alembic/versions/8ef5054dc280_add_normalized_log_entries.py +173 -0
- package/app/backend/alembic/versions/8f3e2bd8ea3b_merge_migration_heads.py +23 -0
- package/app/backend/alembic/versions/9d17f0698d3b_add_config_column_to_boards_table.py +30 -0
- package/app/backend/alembic/versions/add_agent_conversation_history.py +72 -0
- package/app/backend/alembic/versions/add_job_variant.py +34 -0
- package/app/backend/alembic/versions/add_performance_indexes.py +95 -0
- package/app/backend/alembic/versions/add_repos_and_board_repos.py +174 -0
- package/app/backend/alembic/versions/add_session_id_to_jobs.py +27 -0
- package/app/backend/alembic/versions/add_sqlite_backend_tables.py +104 -0
- package/app/backend/alembic/versions/b10fb0b62240_add_diff_content_to_revisions.py +34 -0
- package/app/backend/alembic.ini +89 -0
- package/app/backend/app/__init__.py +3 -0
- package/app/backend/app/data_dir.py +85 -0
- package/app/backend/app/database.py +70 -0
- package/app/backend/app/database_sync.py +64 -0
- package/app/backend/app/dependencies/__init__.py +5 -0
- package/app/backend/app/dependencies/auth.py +80 -0
- package/app/backend/app/dependencies.py +43 -0
- package/app/backend/app/exceptions.py +178 -0
- package/app/backend/app/executors/__init__.py +1 -0
- package/app/backend/app/executors/adapters/__init__.py +1 -0
- package/app/backend/app/executors/adapters/aider.py +152 -0
- package/app/backend/app/executors/adapters/amazon_q.py +103 -0
- package/app/backend/app/executors/adapters/amp.py +123 -0
- package/app/backend/app/executors/adapters/claude.py +177 -0
- package/app/backend/app/executors/adapters/cline.py +127 -0
- package/app/backend/app/executors/adapters/codex.py +167 -0
- package/app/backend/app/executors/adapters/copilot.py +202 -0
- package/app/backend/app/executors/adapters/cursor.py +87 -0
- package/app/backend/app/executors/adapters/droid.py +123 -0
- package/app/backend/app/executors/adapters/gemini.py +132 -0
- package/app/backend/app/executors/adapters/goose.py +131 -0
- package/app/backend/app/executors/adapters/opencode.py +123 -0
- package/app/backend/app/executors/adapters/qwen.py +123 -0
- package/app/backend/app/executors/plugins/__init__.py +1 -0
- package/app/backend/app/executors/registry.py +202 -0
- package/app/backend/app/executors/spec.py +226 -0
- package/app/backend/app/main.py +486 -0
- package/app/backend/app/middleware/__init__.py +13 -0
- package/app/backend/app/middleware/idempotency.py +426 -0
- package/app/backend/app/middleware/rate_limit.py +312 -0
- package/app/backend/app/middleware/security_headers.py +43 -0
- package/app/backend/app/middleware/timeout.py +37 -0
- package/app/backend/app/models/__init__.py +56 -0
- package/app/backend/app/models/agent_conversation_history.py +56 -0
- package/app/backend/app/models/agent_session.py +127 -0
- package/app/backend/app/models/analysis_cache.py +49 -0
- package/app/backend/app/models/base.py +9 -0
- package/app/backend/app/models/board.py +79 -0
- package/app/backend/app/models/board_repo.py +68 -0
- package/app/backend/app/models/cost_budget.py +42 -0
- package/app/backend/app/models/enums.py +40 -0
- package/app/backend/app/models/evidence.py +132 -0
- package/app/backend/app/models/goal.py +102 -0
- package/app/backend/app/models/idempotency_entry.py +30 -0
- package/app/backend/app/models/job.py +163 -0
- package/app/backend/app/models/job_queue.py +39 -0
- package/app/backend/app/models/kv_store.py +28 -0
- package/app/backend/app/models/merge_checklist.py +87 -0
- package/app/backend/app/models/normalized_log.py +100 -0
- package/app/backend/app/models/planner_lock.py +43 -0
- package/app/backend/app/models/rate_limit_entry.py +25 -0
- package/app/backend/app/models/repo.py +66 -0
- package/app/backend/app/models/review_comment.py +91 -0
- package/app/backend/app/models/review_summary.py +69 -0
- package/app/backend/app/models/revision.py +130 -0
- package/app/backend/app/models/ticket.py +223 -0
- package/app/backend/app/models/ticket_event.py +83 -0
- package/app/backend/app/models/user.py +47 -0
- package/app/backend/app/models/workspace.py +71 -0
- package/app/backend/app/redis_client.py +119 -0
- package/app/backend/app/routers/__init__.py +29 -0
- package/app/backend/app/routers/agents.py +296 -0
- package/app/backend/app/routers/auth.py +94 -0
- package/app/backend/app/routers/board.py +885 -0
- package/app/backend/app/routers/dashboard.py +351 -0
- package/app/backend/app/routers/debug.py +528 -0
- package/app/backend/app/routers/evidence.py +96 -0
- package/app/backend/app/routers/executors.py +324 -0
- package/app/backend/app/routers/goals.py +574 -0
- package/app/backend/app/routers/jobs.py +448 -0
- package/app/backend/app/routers/maintenance.py +172 -0
- package/app/backend/app/routers/merge.py +360 -0
- package/app/backend/app/routers/planner.py +537 -0
- package/app/backend/app/routers/pull_requests.py +382 -0
- package/app/backend/app/routers/repos.py +263 -0
- package/app/backend/app/routers/revisions.py +939 -0
- package/app/backend/app/routers/settings.py +267 -0
- package/app/backend/app/routers/tickets.py +2003 -0
- package/app/backend/app/routers/webhooks.py +143 -0
- package/app/backend/app/routers/websocket.py +249 -0
- package/app/backend/app/schemas/__init__.py +109 -0
- package/app/backend/app/schemas/board.py +87 -0
- package/app/backend/app/schemas/common.py +33 -0
- package/app/backend/app/schemas/evidence.py +87 -0
- package/app/backend/app/schemas/goal.py +90 -0
- package/app/backend/app/schemas/job.py +97 -0
- package/app/backend/app/schemas/merge.py +139 -0
- package/app/backend/app/schemas/planner.py +500 -0
- package/app/backend/app/schemas/repo.py +187 -0
- package/app/backend/app/schemas/review.py +137 -0
- package/app/backend/app/schemas/revision.py +114 -0
- package/app/backend/app/schemas/ticket.py +238 -0
- package/app/backend/app/schemas/ticket_event.py +72 -0
- package/app/backend/app/schemas/workspace.py +19 -0
- package/app/backend/app/services/__init__.py +31 -0
- package/app/backend/app/services/agent_memory_service.py +223 -0
- package/app/backend/app/services/agent_registry.py +346 -0
- package/app/backend/app/services/agent_session_manager.py +318 -0
- package/app/backend/app/services/agent_session_service.py +219 -0
- package/app/backend/app/services/agent_tools.py +379 -0
- package/app/backend/app/services/auth_service.py +98 -0
- package/app/backend/app/services/autonomy_service.py +380 -0
- package/app/backend/app/services/board_repo_service.py +201 -0
- package/app/backend/app/services/board_service.py +326 -0
- package/app/backend/app/services/cleanup_service.py +1085 -0
- package/app/backend/app/services/config_service.py +908 -0
- package/app/backend/app/services/context_gatherer.py +557 -0
- package/app/backend/app/services/cost_tracking_service.py +293 -0
- package/app/backend/app/services/cursor_log_normalizer.py +536 -0
- package/app/backend/app/services/delivery_pipeline.py +440 -0
- package/app/backend/app/services/executor_service.py +634 -0
- package/app/backend/app/services/git_host/__init__.py +11 -0
- package/app/backend/app/services/git_host/factory.py +87 -0
- package/app/backend/app/services/git_host/github.py +270 -0
- package/app/backend/app/services/git_host/gitlab.py +194 -0
- package/app/backend/app/services/git_host/protocol.py +75 -0
- package/app/backend/app/services/git_merge_simple.py +346 -0
- package/app/backend/app/services/git_ops.py +384 -0
- package/app/backend/app/services/github_service.py +233 -0
- package/app/backend/app/services/goal_service.py +113 -0
- package/app/backend/app/services/job_service.py +423 -0
- package/app/backend/app/services/job_watchdog_service.py +424 -0
- package/app/backend/app/services/langchain_adapter.py +122 -0
- package/app/backend/app/services/llm_provider_clients.py +351 -0
- package/app/backend/app/services/llm_service.py +285 -0
- package/app/backend/app/services/log_normalizer.py +342 -0
- package/app/backend/app/services/log_stream_service.py +276 -0
- package/app/backend/app/services/merge_checklist_service.py +264 -0
- package/app/backend/app/services/merge_service.py +784 -0
- package/app/backend/app/services/orchestrator_log.py +84 -0
- package/app/backend/app/services/planner_service.py +1662 -0
- package/app/backend/app/services/planner_tick_sync.py +1040 -0
- package/app/backend/app/services/queued_message_service.py +156 -0
- package/app/backend/app/services/reliability_wrapper.py +389 -0
- package/app/backend/app/services/repo_discovery_service.py +318 -0
- package/app/backend/app/services/review_service.py +334 -0
- package/app/backend/app/services/revision_service.py +389 -0
- package/app/backend/app/services/safe_autopilot.py +510 -0
- package/app/backend/app/services/sqlite_worker.py +372 -0
- package/app/backend/app/services/task_dispatch.py +135 -0
- package/app/backend/app/services/ticket_generation_service.py +1781 -0
- package/app/backend/app/services/ticket_service.py +486 -0
- package/app/backend/app/services/udar_planner_service.py +1007 -0
- package/app/backend/app/services/webhook_service.py +126 -0
- package/app/backend/app/services/workspace_service.py +465 -0
- package/app/backend/app/services/worktree_file_service.py +92 -0
- package/app/backend/app/services/worktree_validator.py +213 -0
- package/app/backend/app/sqlite_kv.py +278 -0
- package/app/backend/app/state_machine.py +128 -0
- package/app/backend/app/templates/__init__.py +5 -0
- package/app/backend/app/templates/registry.py +243 -0
- package/app/backend/app/utils/__init__.py +5 -0
- package/app/backend/app/utils/artifact_reader.py +87 -0
- package/app/backend/app/utils/circuit_breaker.py +229 -0
- package/app/backend/app/utils/db_retry.py +136 -0
- package/app/backend/app/utils/ignored_fields.py +123 -0
- package/app/backend/app/utils/validators.py +54 -0
- package/app/backend/app/websocket/__init__.py +5 -0
- package/app/backend/app/websocket/manager.py +179 -0
- package/app/backend/app/websocket/state_tracker.py +113 -0
- package/app/backend/app/worker.py +3190 -0
- package/app/backend/calculator_tickets.json +40 -0
- package/app/backend/canary_tests.sh +591 -0
- package/app/backend/celerybeat-schedule +0 -0
- package/app/backend/celerybeat-schedule-shm +0 -0
- package/app/backend/celerybeat-schedule-wal +0 -0
- package/app/backend/logs/.gitkeep +3 -0
- package/app/backend/multiplication_division_implementation_tickets.json +55 -0
- package/app/backend/multiplication_division_tickets.json +42 -0
- package/app/backend/pyproject.toml +45 -0
- package/app/backend/requirements-dev.txt +8 -0
- package/app/backend/requirements.txt +20 -0
- package/app/backend/run.sh +30 -0
- package/app/backend/run_with_logs.sh +10 -0
- package/app/backend/scientific_calculator_tickets.json +40 -0
- package/app/backend/scripts/extract_openapi.py +21 -0
- package/app/backend/scripts/seed_demo.py +187 -0
- package/app/backend/setup_demo_review.py +302 -0
- package/app/backend/test_actual_parse.py +41 -0
- package/app/backend/test_agent_streaming.py +61 -0
- package/app/backend/test_parse.py +51 -0
- package/app/backend/test_streaming.py +51 -0
- package/app/backend/test_subprocess_streaming.py +50 -0
- package/app/backend/tests/__init__.py +1 -0
- package/app/backend/tests/conftest.py +46 -0
- package/app/backend/tests/test_auth.py +341 -0
- package/app/backend/tests/test_autonomy_service.py +391 -0
- package/app/backend/tests/test_cleanup_service_safety.py +417 -0
- package/app/backend/tests/test_middleware.py +279 -0
- package/app/backend/tests/test_planner_providers.py +290 -0
- package/app/backend/tests/test_planner_unblock.py +183 -0
- package/app/backend/tests/test_revision_invariants.py +618 -0
- package/app/backend/tests/test_sqlite_kv.py +290 -0
- package/app/backend/tests/test_sqlite_worker.py +353 -0
- package/app/backend/tests/test_task_dispatch.py +100 -0
- package/app/backend/tests/test_ticket_validation.py +304 -0
- package/app/backend/tests/test_udar_agent.py +693 -0
- package/app/backend/tests/test_webhook_service.py +184 -0
- package/app/backend/tickets_output.json +59 -0
- package/app/backend/user_management_tickets.json +50 -0
- package/app/backend/uvicorn.log +0 -0
- package/app/draft.yaml +313 -0
- package/app/frontend/dist/assets/index-LcjCczu5.js +155 -0
- package/app/frontend/dist/assets/index-_FP_279e.css +1 -0
- package/app/frontend/dist/index.html +14 -0
- package/app/frontend/dist/vite.svg +1 -0
- package/app/frontend/package.json +101 -0
- package/bin/cli.js +527 -0
- package/package.json +37 -0
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Backend environment variables
|
|
2
|
+
APP_ENV=development
|
|
3
|
+
DATABASE_URL=sqlite:///./kanban.db
|
|
4
|
+
FRONTEND_URL=http://localhost:5173
|
|
5
|
+
|
|
6
|
+
# LLM API keys (at least one needed for ticket generation / planner)
|
|
7
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
8
|
+
# OPENAI_API_KEY=sk-...
|
|
9
|
+
# AWS credentials auto-detected for Bedrock
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
diff --git a/package.json b/package.json
|
|
2
|
+
index 1a2b3c4..5d6e7f8 100644
|
|
3
|
+
--- a/package.json
|
|
4
|
+
+++ b/package.json
|
|
5
|
+
@@ -10,7 +10,9 @@
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"express": "^4.18.2",
|
|
8
|
+
"better-sqlite3": "^9.4.3",
|
|
9
|
+
- "cors": "^2.8.5"
|
|
10
|
+
+ "cors": "^2.8.5",
|
|
11
|
+
+ "jsonwebtoken": "^9.0.2",
|
|
12
|
+
+ "bcryptjs": "^2.4.3"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"jest": "^29.7.0",
|
|
16
|
+
diff --git a/src/models/db.js b/src/models/db.js
|
|
17
|
+
index 2b3c4d5..8e9f0a1 100644
|
|
18
|
+
--- a/src/models/db.js
|
|
19
|
+
+++ b/src/models/db.js
|
|
20
|
+
@@ -8,6 +8,16 @@ db.exec(`
|
|
21
|
+
)
|
|
22
|
+
`);
|
|
23
|
+
|
|
24
|
+
+db.exec(`
|
|
25
|
+
+ CREATE TABLE IF NOT EXISTS users (
|
|
26
|
+
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
+
+ username TEXT UNIQUE NOT NULL,
|
|
28
|
+
+ password TEXT NOT NULL,
|
|
29
|
+
+ email TEXT,
|
|
30
|
+
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
31
|
+
+ )
|
|
32
|
+
+`);
|
|
33
|
+
+
|
|
34
|
+
module.exports = db;
|
|
35
|
+
diff --git a/src/middleware/auth.js b/src/middleware/auth.js
|
|
36
|
+
new file mode 100644
|
|
37
|
+
index 0000000..3f4a5b6
|
|
38
|
+
--- /dev/null
|
|
39
|
+
+++ b/src/middleware/auth.js
|
|
40
|
+
@@ -0,0 +1,45 @@
|
|
41
|
+
+const jwt = require("jsonwebtoken");
|
|
42
|
+
+
|
|
43
|
+
+const JWT_SECRET = process.env.JWT_SECRET || "dev-secret-key";
|
|
44
|
+
+
|
|
45
|
+
+function authenticateToken(req, res, next) {
|
|
46
|
+
+ const authHeader = req.headers["authorization"];
|
|
47
|
+
+ const token = authHeader && authHeader.split(" ")[1];
|
|
48
|
+
+
|
|
49
|
+
+ if (!token) {
|
|
50
|
+
+ return res.status(401).json({ error: "Access token required" });
|
|
51
|
+
+ }
|
|
52
|
+
+
|
|
53
|
+
+ try {
|
|
54
|
+
+ const decoded = jwt.verify(token, JWT_SECRET);
|
|
55
|
+
+ req.user = decoded;
|
|
56
|
+
+ next();
|
|
57
|
+
+ } catch (err) {
|
|
58
|
+
+ return res.status(401).json({ error: "Invalid or expired token" });
|
|
59
|
+
+ }
|
|
60
|
+
+}
|
|
61
|
+
+
|
|
62
|
+
+function generateToken(user) {
|
|
63
|
+
+ return jwt.sign(
|
|
64
|
+
+ { id: user.id, username: user.username },
|
|
65
|
+
+ JWT_SECRET,
|
|
66
|
+
+ { expiresIn: "24h" }
|
|
67
|
+
+ );
|
|
68
|
+
+}
|
|
69
|
+
+
|
|
70
|
+
+function hashPassword(password) {
|
|
71
|
+
+ const bcrypt = require("bcryptjs");
|
|
72
|
+
+ return bcrypt.hashSync(password, 10);
|
|
73
|
+
+}
|
|
74
|
+
+
|
|
75
|
+
+function comparePassword(password, hash) {
|
|
76
|
+
+ const bcrypt = require("bcryptjs");
|
|
77
|
+
+ return bcrypt.compareSync(password, hash);
|
|
78
|
+
+}
|
|
79
|
+
+
|
|
80
|
+
+module.exports = {
|
|
81
|
+
+ authenticateToken,
|
|
82
|
+
+ generateToken,
|
|
83
|
+
+ hashPassword,
|
|
84
|
+
+ comparePassword,
|
|
85
|
+
+ JWT_SECRET
|
|
86
|
+
+};
|
|
87
|
+
diff --git a/src/routes/auth.js b/src/routes/auth.js
|
|
88
|
+
new file mode 100644
|
|
89
|
+
index 0000000..7c8d9e0
|
|
90
|
+
--- /dev/null
|
|
91
|
+
+++ b/src/routes/auth.js
|
|
92
|
+
@@ -0,0 +1,78 @@
|
|
93
|
+
+const express = require("express");
|
|
94
|
+
+const router = express.Router();
|
|
95
|
+
+const db = require("../models/db");
|
|
96
|
+
+const { generateToken, hashPassword, comparePassword } = require("../middleware/auth");
|
|
97
|
+
+
|
|
98
|
+
+// POST /api/auth/register
|
|
99
|
+
+router.post("/register", (req, res) => {
|
|
100
|
+
+ const { username, password, email } = req.body;
|
|
101
|
+
+
|
|
102
|
+
+ if (!username || !password) {
|
|
103
|
+
+ return res.status(400).json({ error: "Username and password are required" });
|
|
104
|
+
+ }
|
|
105
|
+
+
|
|
106
|
+
+ if (password.length < 6) {
|
|
107
|
+
+ return res.status(400).json({ error: "Password must be at least 6 characters" });
|
|
108
|
+
+ }
|
|
109
|
+
+
|
|
110
|
+
+ try {
|
|
111
|
+
+ const existing = db.prepare("SELECT id FROM users WHERE username = ?").get(username);
|
|
112
|
+
+ if (existing) {
|
|
113
|
+
+ return res.status(409).json({ error: "Username already exists" });
|
|
114
|
+
+ }
|
|
115
|
+
+
|
|
116
|
+
+ const hashedPassword = hashPassword(password);
|
|
117
|
+
+ const result = db.prepare(
|
|
118
|
+
+ "INSERT INTO users (username, password, email) VALUES (?, ?, ?)"
|
|
119
|
+
+ ).run(username, hashedPassword, email || null);
|
|
120
|
+
+
|
|
121
|
+
+ const user = { id: result.lastInsertRowid, username };
|
|
122
|
+
+ const token = generateToken(user);
|
|
123
|
+
+
|
|
124
|
+
+ res.status(201).json({ user: { id: user.id, username }, token });
|
|
125
|
+
+ } catch (err) {
|
|
126
|
+
+ res.status(500).json({ error: "Registration failed" });
|
|
127
|
+
+ }
|
|
128
|
+
+});
|
|
129
|
+
+
|
|
130
|
+
+// POST /api/auth/login
|
|
131
|
+
+router.post("/login", (req, res) => {
|
|
132
|
+
+ const { username, password } = req.body;
|
|
133
|
+
+
|
|
134
|
+
+ if (!username || !password) {
|
|
135
|
+
+ return res.status(400).json({ error: "Username and password are required" });
|
|
136
|
+
+ }
|
|
137
|
+
+
|
|
138
|
+
+ try {
|
|
139
|
+
+ const user = db.prepare("SELECT * FROM users WHERE username = ?").get(username);
|
|
140
|
+
+
|
|
141
|
+
+ if (!user) {
|
|
142
|
+
+ return res.status(401).json({ error: "Invalid credentials" });
|
|
143
|
+
+ }
|
|
144
|
+
+
|
|
145
|
+
+ if (!comparePassword(password, user.password)) {
|
|
146
|
+
+ return res.status(401).json({ error: "Invalid credentials" });
|
|
147
|
+
+ }
|
|
148
|
+
+
|
|
149
|
+
+ const token = generateToken({ id: user.id, username: user.username });
|
|
150
|
+
+ res.json({ user: { id: user.id, username: user.username }, token });
|
|
151
|
+
+ } catch (err) {
|
|
152
|
+
+ res.status(500).json({ error: "Login failed" });
|
|
153
|
+
+ }
|
|
154
|
+
+});
|
|
155
|
+
+
|
|
156
|
+
+// GET /api/auth/me - Get current user
|
|
157
|
+
+router.get("/me", (req, res) => {
|
|
158
|
+
+ if (!req.user) {
|
|
159
|
+
+ return res.status(401).json({ error: "Not authenticated" });
|
|
160
|
+
+ }
|
|
161
|
+
+
|
|
162
|
+
+ const user = db.prepare("SELECT id, username, email, created_at FROM users WHERE id = ?").get(req.user.id);
|
|
163
|
+
+ if (!user) {
|
|
164
|
+
+ return res.status(404).json({ error: "User not found" });
|
|
165
|
+
+ }
|
|
166
|
+
+
|
|
167
|
+
+ res.json({ user });
|
|
168
|
+
+});
|
|
169
|
+
+
|
|
170
|
+
+module.exports = router;
|
|
171
|
+
diff --git a/src/index.js b/src/index.js
|
|
172
|
+
index 4d5e6f7..9a0b1c2 100644
|
|
173
|
+
--- a/src/index.js
|
|
174
|
+
+++ b/src/index.js
|
|
175
|
+
@@ -2,6 +2,8 @@ const express = require("express");
|
|
176
|
+
const cors = require("cors");
|
|
177
|
+
const taskRoutes = require("./routes/tasks");
|
|
178
|
+
const projectRoutes = require("./routes/projects");
|
|
179
|
+
+const authRoutes = require("./routes/auth");
|
|
180
|
+
+const { authenticateToken } = require("./middleware/auth");
|
|
181
|
+
const { errorHandler } = require("./middleware/errorHandler");
|
|
182
|
+
|
|
183
|
+
const app = express();
|
|
184
|
+
@@ -12,8 +14,10 @@ app.use(express.json());
|
|
185
|
+
|
|
186
|
+
app.get("/health", (req, res) => res.json({ status: "ok" }));
|
|
187
|
+
|
|
188
|
+
-app.use("/api/tasks", taskRoutes);
|
|
189
|
+
-app.use("/api/projects", projectRoutes);
|
|
190
|
+
+app.use("/api/auth", authRoutes);
|
|
191
|
+
+
|
|
192
|
+
+app.use("/api/tasks", authenticateToken, taskRoutes);
|
|
193
|
+
+app.use("/api/projects", authenticateToken, projectRoutes);
|
|
194
|
+
|
|
195
|
+
app.use(errorHandler);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
src/middleware/auth.js | 45 +++++++++++++++++++++++++++++++++++++++++++++
|
|
2
|
+
src/routes/auth.js | 78 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
3
|
+
src/models/db.js | 12 ++++++++++--
|
|
4
|
+
src/index.js | 8 ++++++--
|
|
5
|
+
package.json | 4 +++-
|
|
6
|
+
5 files changed, 142 insertions(+), 5 deletions(-)
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
# Draft API - Example curl Commands
|
|
2
|
+
|
|
3
|
+
This document provides example curl commands to interact with the Draft API.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
Start the backend server:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
cd backend
|
|
11
|
+
source venv/bin/activate
|
|
12
|
+
uvicorn app.main:app --reload
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The API will be available at `http://localhost:8000`.
|
|
16
|
+
|
|
17
|
+
## Health Check
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
curl http://localhost:8000/health
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Expected response:
|
|
24
|
+
```json
|
|
25
|
+
{"status": "ok"}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Goals
|
|
29
|
+
|
|
30
|
+
### Create a Goal
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
curl -X POST http://localhost:8000/goals \
|
|
34
|
+
-H "Content-Type: application/json" \
|
|
35
|
+
-d '{
|
|
36
|
+
"title": "Implement User Authentication",
|
|
37
|
+
"description": "Add login, registration, and session management to the application"
|
|
38
|
+
}'
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Expected response:
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
45
|
+
"title": "Implement User Authentication",
|
|
46
|
+
"description": "Add login, registration, and session management to the application",
|
|
47
|
+
"created_at": "2026-01-05T10:00:00",
|
|
48
|
+
"updated_at": "2026-01-05T10:00:00"
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### List All Goals
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
curl http://localhost:8000/goals
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Expected response:
|
|
59
|
+
```json
|
|
60
|
+
{
|
|
61
|
+
"goals": [
|
|
62
|
+
{
|
|
63
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
64
|
+
"title": "Implement User Authentication",
|
|
65
|
+
"description": "Add login, registration, and session management to the application",
|
|
66
|
+
"created_at": "2026-01-05T10:00:00",
|
|
67
|
+
"updated_at": "2026-01-05T10:00:00"
|
|
68
|
+
}
|
|
69
|
+
],
|
|
70
|
+
"total": 1
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Get a Single Goal
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
curl http://localhost:8000/goals/{goal_id}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Replace `{goal_id}` with the actual goal UUID.
|
|
81
|
+
|
|
82
|
+
## Tickets
|
|
83
|
+
|
|
84
|
+
### Create a Ticket
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
curl -X POST http://localhost:8000/tickets \
|
|
88
|
+
-H "Content-Type: application/json" \
|
|
89
|
+
-d '{
|
|
90
|
+
"goal_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
91
|
+
"title": "Design login form UI",
|
|
92
|
+
"description": "Create a responsive login form with email and password fields",
|
|
93
|
+
"priority": 80,
|
|
94
|
+
"actor_type": "human",
|
|
95
|
+
"actor_id": "user-123"
|
|
96
|
+
}'
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Expected response:
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"id": "660e8400-e29b-41d4-a716-446655440001",
|
|
103
|
+
"goal_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
104
|
+
"title": "Design login form UI",
|
|
105
|
+
"description": "Create a responsive login form with email and password fields",
|
|
106
|
+
"state": "proposed",
|
|
107
|
+
"priority": 80,
|
|
108
|
+
"created_at": "2026-01-05T10:05:00",
|
|
109
|
+
"updated_at": "2026-01-05T10:05:00"
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Get a Ticket
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
curl http://localhost:8000/tickets/{ticket_id}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Replace `{ticket_id}` with the actual ticket UUID.
|
|
120
|
+
|
|
121
|
+
### Transition a Ticket (Valid)
|
|
122
|
+
|
|
123
|
+
Move from `proposed` to `planned`:
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
curl -X POST http://localhost:8000/tickets/{ticket_id}/transition \
|
|
127
|
+
-H "Content-Type: application/json" \
|
|
128
|
+
-d '{
|
|
129
|
+
"to_state": "planned",
|
|
130
|
+
"actor_type": "planner",
|
|
131
|
+
"actor_id": "ai-planner-1",
|
|
132
|
+
"reason": "Task has been reviewed and broken down"
|
|
133
|
+
}'
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Expected response (ticket with updated state):
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"id": "660e8400-e29b-41d4-a716-446655440001",
|
|
140
|
+
"goal_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
141
|
+
"title": "Design login form UI",
|
|
142
|
+
"description": "Create a responsive login form with email and password fields",
|
|
143
|
+
"state": "planned",
|
|
144
|
+
"priority": 80,
|
|
145
|
+
"created_at": "2026-01-05T10:05:00",
|
|
146
|
+
"updated_at": "2026-01-05T10:10:00"
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Complete Workflow: proposed → planned → executing → verifying → done
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Start executing (planned → executing)
|
|
154
|
+
curl -X POST http://localhost:8000/tickets/{ticket_id}/transition \
|
|
155
|
+
-H "Content-Type: application/json" \
|
|
156
|
+
-d '{
|
|
157
|
+
"to_state": "executing",
|
|
158
|
+
"actor_type": "executor",
|
|
159
|
+
"actor_id": "ai-executor-1",
|
|
160
|
+
"reason": "Starting implementation"
|
|
161
|
+
}'
|
|
162
|
+
|
|
163
|
+
# Submit for verification (executing → verifying)
|
|
164
|
+
curl -X POST http://localhost:8000/tickets/{ticket_id}/transition \
|
|
165
|
+
-H "Content-Type: application/json" \
|
|
166
|
+
-d '{
|
|
167
|
+
"to_state": "verifying",
|
|
168
|
+
"actor_type": "executor",
|
|
169
|
+
"actor_id": "ai-executor-1",
|
|
170
|
+
"reason": "Implementation complete, ready for review"
|
|
171
|
+
}'
|
|
172
|
+
|
|
173
|
+
# Mark as done (verifying → done)
|
|
174
|
+
curl -X POST http://localhost:8000/tickets/{ticket_id}/transition \
|
|
175
|
+
-H "Content-Type: application/json" \
|
|
176
|
+
-d '{
|
|
177
|
+
"to_state": "done",
|
|
178
|
+
"actor_type": "human",
|
|
179
|
+
"actor_id": "user-123",
|
|
180
|
+
"reason": "Verified and approved"
|
|
181
|
+
}'
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Transition a Ticket (Invalid - Should Fail)
|
|
185
|
+
|
|
186
|
+
Try to go directly from `proposed` to `done`:
|
|
187
|
+
|
|
188
|
+
```bash
|
|
189
|
+
curl -X POST http://localhost:8000/tickets/{ticket_id}/transition \
|
|
190
|
+
-H "Content-Type: application/json" \
|
|
191
|
+
-d '{
|
|
192
|
+
"to_state": "done",
|
|
193
|
+
"actor_type": "human",
|
|
194
|
+
"actor_id": "user-123",
|
|
195
|
+
"reason": "Trying to skip states"
|
|
196
|
+
}'
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Expected error response (HTTP 400):
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"detail": "Invalid transition from 'proposed' to 'done'",
|
|
203
|
+
"error_type": "invalid_state_transition",
|
|
204
|
+
"from_state": "proposed",
|
|
205
|
+
"to_state": "done"
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
### Get Ticket Events (Audit Log)
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
curl http://localhost:8000/tickets/{ticket_id}/events
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Expected response:
|
|
216
|
+
```json
|
|
217
|
+
{
|
|
218
|
+
"events": [
|
|
219
|
+
{
|
|
220
|
+
"id": "770e8400-e29b-41d4-a716-446655440001",
|
|
221
|
+
"ticket_id": "660e8400-e29b-41d4-a716-446655440001",
|
|
222
|
+
"event_type": "created",
|
|
223
|
+
"from_state": null,
|
|
224
|
+
"to_state": "proposed",
|
|
225
|
+
"actor_type": "human",
|
|
226
|
+
"actor_id": "user-123",
|
|
227
|
+
"reason": "Ticket created",
|
|
228
|
+
"payload": {
|
|
229
|
+
"title": "Design login form UI",
|
|
230
|
+
"description": "Create a responsive login form with email and password fields",
|
|
231
|
+
"goal_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
232
|
+
"priority": 80
|
|
233
|
+
},
|
|
234
|
+
"created_at": "2026-01-05T10:05:00"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"id": "770e8400-e29b-41d4-a716-446655440002",
|
|
238
|
+
"ticket_id": "660e8400-e29b-41d4-a716-446655440001",
|
|
239
|
+
"event_type": "transitioned",
|
|
240
|
+
"from_state": "proposed",
|
|
241
|
+
"to_state": "planned",
|
|
242
|
+
"actor_type": "planner",
|
|
243
|
+
"actor_id": "ai-planner-1",
|
|
244
|
+
"reason": "Task has been reviewed and broken down",
|
|
245
|
+
"payload": null,
|
|
246
|
+
"created_at": "2026-01-05T10:10:00"
|
|
247
|
+
}
|
|
248
|
+
],
|
|
249
|
+
"total": 2
|
|
250
|
+
}
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Board View
|
|
254
|
+
|
|
255
|
+
### Get Board (All Tickets Grouped by State)
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
curl http://localhost:8000/board
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Expected response:
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"columns": [
|
|
265
|
+
{
|
|
266
|
+
"state": "proposed",
|
|
267
|
+
"tickets": []
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
"state": "planned",
|
|
271
|
+
"tickets": [
|
|
272
|
+
{
|
|
273
|
+
"id": "660e8400-e29b-41d4-a716-446655440001",
|
|
274
|
+
"goal_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
275
|
+
"title": "Design login form UI",
|
|
276
|
+
"description": "Create a responsive login form",
|
|
277
|
+
"state": "planned",
|
|
278
|
+
"priority": 80,
|
|
279
|
+
"created_at": "2026-01-05T10:05:00",
|
|
280
|
+
"updated_at": "2026-01-05T10:10:00"
|
|
281
|
+
}
|
|
282
|
+
]
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
"state": "executing",
|
|
286
|
+
"tickets": []
|
|
287
|
+
},
|
|
288
|
+
{
|
|
289
|
+
"state": "verifying",
|
|
290
|
+
"tickets": []
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
"state": "needs_human",
|
|
294
|
+
"tickets": []
|
|
295
|
+
},
|
|
296
|
+
{
|
|
297
|
+
"state": "blocked",
|
|
298
|
+
"tickets": []
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
"state": "done",
|
|
302
|
+
"tickets": []
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
"state": "abandoned",
|
|
306
|
+
"tickets": []
|
|
307
|
+
}
|
|
308
|
+
],
|
|
309
|
+
"total_tickets": 1
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## State Machine Reference
|
|
314
|
+
|
|
315
|
+
### Valid State Transitions
|
|
316
|
+
|
|
317
|
+
| From State | Valid Next States |
|
|
318
|
+
|---------------|------------------------------------------|
|
|
319
|
+
| proposed | planned, abandoned |
|
|
320
|
+
| planned | executing, blocked, abandoned |
|
|
321
|
+
| executing | verifying, needs_human, blocked |
|
|
322
|
+
| verifying | done, executing, needs_human |
|
|
323
|
+
| needs_human | executing, planned, abandoned |
|
|
324
|
+
| blocked | planned, abandoned |
|
|
325
|
+
| done | (terminal state - no transitions) |
|
|
326
|
+
| abandoned | (terminal state - no transitions) |
|
|
327
|
+
|
|
328
|
+
### Actor Types
|
|
329
|
+
|
|
330
|
+
- `human` - Human user action
|
|
331
|
+
- `planner` - AI planner agent
|
|
332
|
+
- `executor` - AI executor agent
|
|
333
|
+
- `system` - Automated system action
|
|
334
|
+
|
|
335
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Environment Variables Setup
|
|
2
|
+
|
|
3
|
+
## AWS Bedrock Configuration
|
|
4
|
+
|
|
5
|
+
To use AWS Bedrock models with the Draft planner, add these variables to your `.env` file in the `backend/` directory:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Required AWS Credentials
|
|
9
|
+
AWS_ACCESS_KEY_ID=your-access-key-id-here
|
|
10
|
+
AWS_SECRET_ACCESS_KEY=your-secret-access-key-here
|
|
11
|
+
AWS_REGION_NAME=us-east-1
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
### Notes:
|
|
15
|
+
|
|
16
|
+
1. **Model Configuration**: The model is configured in `draft.yaml`:
|
|
17
|
+
```yaml
|
|
18
|
+
planner_config:
|
|
19
|
+
model: "bedrock/anthropic.claude-sonnet-4-5-20250929-v1:0"
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
2. **AWS Region**: Make sure your `AWS_REGION_NAME` matches where your Bedrock model is available. Common regions:
|
|
23
|
+
- `us-east-1` (N. Virginia)
|
|
24
|
+
- `us-west-2` (Oregon)
|
|
25
|
+
- Check AWS Bedrock documentation for model availability by region
|
|
26
|
+
|
|
27
|
+
3. **Bedrock Access**: Ensure your AWS account has:
|
|
28
|
+
- AWS Bedrock enabled
|
|
29
|
+
- Model access requested and approved for Claude Sonnet 4.5
|
|
30
|
+
- IAM permissions for `bedrock:InvokeModel`
|
|
31
|
+
|
|
32
|
+
4. **Alternative: AWS Profile**: Instead of access keys, you can use an AWS CLI profile:
|
|
33
|
+
```bash
|
|
34
|
+
AWS_PROFILE=your-profile-name
|
|
35
|
+
AWS_REGION_NAME=us-east-1
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
## Other Environment Variables
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Frontend URL for CORS (optional, defaults to http://localhost:5173)
|
|
42
|
+
FRONTEND_URL=http://localhost:5173
|
|
43
|
+
|
|
44
|
+
# Git repository path (optional, defaults to current directory)
|
|
45
|
+
GIT_REPO_PATH=/path/to/your/repo
|
|
46
|
+
|
|
47
|
+
# Database URL (optional, defaults to sqlite:///kanban.db)
|
|
48
|
+
DATABASE_URL=sqlite:///kanban.db
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Testing the Configuration
|
|
52
|
+
|
|
53
|
+
1. Make sure your `.env` file is in the `backend/` directory
|
|
54
|
+
2. Restart the backend server to load the new environment variables
|
|
55
|
+
3. Trigger a planner action (like proposing follow-ups for a blocked ticket) to test the LLM connection
|
|
56
|
+
4. Check the logs for any AWS authentication or model access errors
|
|
57
|
+
|
|
58
|
+
## Troubleshooting
|
|
59
|
+
|
|
60
|
+
- **Authentication errors**: Verify your AWS credentials are correct
|
|
61
|
+
- **Model not found**: Check if the model ID is correct and available in your region
|
|
62
|
+
- **Access denied**: Ensure your IAM user/role has Bedrock permissions
|
|
63
|
+
- **Region mismatch**: Verify the model is available in your configured region
|
|
64
|
+
|
|
65
|
+
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Alembic environment configuration for Draft."""
|
|
2
|
+
|
|
3
|
+
from logging.config import fileConfig
|
|
4
|
+
|
|
5
|
+
from sqlalchemy import engine_from_config, pool
|
|
6
|
+
|
|
7
|
+
from alembic import context
|
|
8
|
+
|
|
9
|
+
# Import models to ensure they are registered with SQLAlchemy
|
|
10
|
+
from app.models import Base # noqa: F401
|
|
11
|
+
|
|
12
|
+
# this is the Alembic Config object, which provides
|
|
13
|
+
# access to the values within the .ini file in use.
|
|
14
|
+
config = context.config
|
|
15
|
+
|
|
16
|
+
# Interpret the config file for Python logging.
|
|
17
|
+
# This line sets up loggers basically.
|
|
18
|
+
if config.config_file_name is not None:
|
|
19
|
+
fileConfig(config.config_file_name)
|
|
20
|
+
|
|
21
|
+
# add your model's MetaData object here
|
|
22
|
+
# for 'autogenerate' support
|
|
23
|
+
target_metadata = Base.metadata
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def run_migrations_offline() -> None:
|
|
27
|
+
"""Run migrations in 'offline' mode.
|
|
28
|
+
|
|
29
|
+
This configures the context with just a URL
|
|
30
|
+
and not an Engine, though an Engine is acceptable
|
|
31
|
+
here as well. By skipping the Engine creation
|
|
32
|
+
we don't even need a DBAPI to be available.
|
|
33
|
+
|
|
34
|
+
Calls to context.execute() here emit the given string to the
|
|
35
|
+
script output.
|
|
36
|
+
"""
|
|
37
|
+
url = config.get_main_option("sqlalchemy.url")
|
|
38
|
+
context.configure(
|
|
39
|
+
url=url,
|
|
40
|
+
target_metadata=target_metadata,
|
|
41
|
+
literal_binds=True,
|
|
42
|
+
dialect_opts={"paramstyle": "named"},
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
with context.begin_transaction():
|
|
46
|
+
context.run_migrations()
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def run_migrations_online() -> None:
|
|
50
|
+
"""Run migrations in 'online' mode.
|
|
51
|
+
|
|
52
|
+
In this scenario we need to create an Engine
|
|
53
|
+
and associate a connection with the context.
|
|
54
|
+
"""
|
|
55
|
+
connectable = engine_from_config(
|
|
56
|
+
config.get_section(config.config_ini_section, {}),
|
|
57
|
+
prefix="sqlalchemy.",
|
|
58
|
+
poolclass=pool.NullPool,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
with connectable.connect() as connection:
|
|
62
|
+
context.configure(connection=connection, target_metadata=target_metadata)
|
|
63
|
+
|
|
64
|
+
with context.begin_transaction():
|
|
65
|
+
context.run_migrations()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
if context.is_offline_mode():
|
|
69
|
+
run_migrations_offline()
|
|
70
|
+
else:
|
|
71
|
+
run_migrations_online()
|