Add setup script
This commit is contained in:
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "project_id!: Uuid",
|
"name": "project_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "title",
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "task_id!: Uuid",
|
"name": "task_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "worktree_path",
|
"name": "worktree_path",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT id as \"id!: Uuid\", name, git_repo_path, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\" FROM projects WHERE git_repo_path = $1 AND id != $2",
|
"query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\" FROM projects WHERE git_repo_path = $1 AND id != $2",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -19,14 +19,19 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "setup_script",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -36,9 +41,10 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "18bfb3eb9408e5268bccf7a17fe02424df8ae3130b70aaad64c5342c198011c9"
|
"hash": "205da45211b3aa413684ecd76d065fc59f793da42da075246464ac776016f5ff"
|
||||||
}
|
}
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "task_attempt_id!: Uuid",
|
"name": "task_attempt_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "status!: TaskAttemptStatus",
|
"name": "status!: TaskAttemptStatus",
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "project_id!: Uuid",
|
"name": "project_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "title",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT id as \"id!: Uuid\", name, git_repo_path, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\" FROM projects WHERE id = $1",
|
"query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\" FROM projects WHERE id = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -19,14 +19,19 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "setup_script",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -36,9 +41,10 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "0133ba2ace195776eaa714981cdf492d2eaa3dc97dc3379b549a3c4fb8309975"
|
"hash": "346d58b8e0628d6a5936675beadc0a43ffa2dca384ed4f4b3a3abfcd09592c07"
|
||||||
}
|
}
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "project_id!: Uuid",
|
"name": "project_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "title",
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "task_id!: Uuid",
|
"name": "task_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "worktree_path",
|
"name": "worktree_path",
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT id as \"id!: Uuid\", name, git_repo_path, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\" FROM projects ORDER BY created_at DESC",
|
"query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\" FROM projects ORDER BY created_at DESC",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -19,14 +19,19 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "setup_script",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -36,9 +41,10 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "022ee13a4082ece0cec1100f5c8c4dc5ecbf84018e1c6b735891ec4057e99f72"
|
"hash": "420c9eec0dd98062947b090bc695b67c2bcaba9862c06b701a9ba3d8a5b02abf"
|
||||||
}
|
}
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "task_attempt_id!: Uuid",
|
"name": "task_attempt_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "status!: TaskAttemptStatus",
|
"name": "status!: TaskAttemptStatus",
|
||||||
|
|||||||
50
backend/.sqlx/query-64fd750d2f767096f94b28650018dc657ad41c6a0af908215f694100319b4864.json
generated
Normal file
50
backend/.sqlx/query-64fd750d2f767096f94b28650018dc657ad41c6a0af908215f694100319b4864.json
generated
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT INTO projects (id, name, git_repo_path, setup_script) VALUES ($1, $2, $3, $4) RETURNING id as \"id!: Uuid\", name, git_repo_path, setup_script, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id!: Uuid",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "git_repo_path",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setup_script",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "64fd750d2f767096f94b28650018dc657ad41c6a0af908215f694100319b4864"
|
||||||
|
}
|
||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "project_id!: Uuid",
|
"name": "project_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "title",
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "project_id!: Uuid",
|
"name": "project_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "title",
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "task_id!: Uuid",
|
"name": "task_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "worktree_path",
|
"name": "worktree_path",
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "INSERT INTO projects (id, name, git_repo_path) VALUES ($1, $2, $3) RETURNING id as \"id!: Uuid\", name, git_repo_path, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\"",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id!: Uuid",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "git_repo_path",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created_at!: DateTime<Utc>",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 3
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "ab7c095d6ccb8bb41d159aa7dd4f7feb97505e31427c124b4219a6903e971f5a"
|
|
||||||
}
|
|
||||||
50
backend/.sqlx/query-b3bead952fd42b79bed0908db603726935c0e830ea74ff30064bac71185442fc.json
generated
Normal file
50
backend/.sqlx/query-b3bead952fd42b79bed0908db603726935c0e830ea74ff30064bac71185442fc.json
generated
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "UPDATE projects SET name = $2, git_repo_path = $3, setup_script = $4 WHERE id = $1 RETURNING id as \"id!: Uuid\", name, git_repo_path, setup_script, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\"",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "id!: Uuid",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Blob"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "git_repo_path",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "setup_script",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "created_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "b3bead952fd42b79bed0908db603726935c0e830ea74ff30064bac71185442fc"
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT id as \"id!: Uuid\", name, git_repo_path, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\" FROM projects WHERE git_repo_path = $1",
|
"query": "SELECT id as \"id!: Uuid\", name, git_repo_path, setup_script, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\" FROM projects WHERE git_repo_path = $1",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "name",
|
||||||
@@ -19,14 +19,19 @@
|
|||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "created_at!: DateTime<Utc>",
|
"name": "setup_script",
|
||||||
"ordinal": 3,
|
"ordinal": 3,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
"name": "created_at!: DateTime<Utc>",
|
||||||
"ordinal": 4,
|
"ordinal": 4,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "updated_at!: DateTime<Utc>",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
@@ -36,9 +41,10 @@
|
|||||||
true,
|
true,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
|
true,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "96aade8205a632c862e4dd11118b7b303cbe521c6a42f5a43eda3cf5f8e6ab2c"
|
"hash": "b62fa26fe7cdbee672504dbf63d3dbe19fca02a4a4f97d7df7143f340540efa0"
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "task_id!: Uuid",
|
"name": "task_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "worktree_path",
|
"name": "worktree_path",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|||||||
@@ -6,12 +6,12 @@
|
|||||||
{
|
{
|
||||||
"name": "id!: Uuid",
|
"name": "id!: Uuid",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "project_id!: Uuid",
|
"name": "project_id!: Uuid",
|
||||||
"ordinal": 1,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "title",
|
"name": "title",
|
||||||
|
|||||||
@@ -1,44 +0,0 @@
|
|||||||
{
|
|
||||||
"db_name": "SQLite",
|
|
||||||
"query": "UPDATE projects SET name = $2, git_repo_path = $3 WHERE id = $1 RETURNING id as \"id!: Uuid\", name, git_repo_path, created_at as \"created_at!: DateTime<Utc>\", updated_at as \"updated_at!: DateTime<Utc>\"",
|
|
||||||
"describe": {
|
|
||||||
"columns": [
|
|
||||||
{
|
|
||||||
"name": "id!: Uuid",
|
|
||||||
"ordinal": 0,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "name",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "git_repo_path",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "created_at!: DateTime<Utc>",
|
|
||||||
"ordinal": 3,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "updated_at!: DateTime<Utc>",
|
|
||||||
"ordinal": 4,
|
|
||||||
"type_info": "Text"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"parameters": {
|
|
||||||
"Right": 3
|
|
||||||
},
|
|
||||||
"nullable": [
|
|
||||||
true,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"hash": "eeede8a732db5214bb9054d6c2b7655989102264949864e719610f12cbb4c0c0"
|
|
||||||
}
|
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE projects ADD COLUMN setup_script TEXT;
|
||||||
@@ -10,6 +10,7 @@ pub struct Project {
|
|||||||
pub id: Uuid,
|
pub id: Uuid,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub git_repo_path: String,
|
pub git_repo_path: String,
|
||||||
|
pub setup_script: Option<String>,
|
||||||
|
|
||||||
#[ts(type = "Date")]
|
#[ts(type = "Date")]
|
||||||
pub created_at: DateTime<Utc>,
|
pub created_at: DateTime<Utc>,
|
||||||
@@ -23,6 +24,7 @@ pub struct CreateProject {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub git_repo_path: String,
|
pub git_repo_path: String,
|
||||||
pub use_existing_repo: bool,
|
pub use_existing_repo: bool,
|
||||||
|
pub setup_script: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, TS)]
|
#[derive(Debug, Deserialize, TS)]
|
||||||
@@ -30,6 +32,7 @@ pub struct CreateProject {
|
|||||||
pub struct UpdateProject {
|
pub struct UpdateProject {
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub git_repo_path: Option<String>,
|
pub git_repo_path: Option<String>,
|
||||||
|
pub setup_script: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Serialize, TS)]
|
#[derive(Debug, Serialize, TS)]
|
||||||
@@ -52,7 +55,7 @@ impl Project {
|
|||||||
pub async fn find_all(pool: &SqlitePool) -> Result<Vec<Self>, sqlx::Error> {
|
pub async fn find_all(pool: &SqlitePool) -> Result<Vec<Self>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Project,
|
Project,
|
||||||
r#"SELECT id as "id!: Uuid", name, git_repo_path, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>" FROM projects ORDER BY created_at DESC"#
|
r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>" FROM projects ORDER BY created_at DESC"#
|
||||||
)
|
)
|
||||||
.fetch_all(pool)
|
.fetch_all(pool)
|
||||||
.await
|
.await
|
||||||
@@ -61,7 +64,7 @@ impl Project {
|
|||||||
pub async fn find_by_id(pool: &SqlitePool, id: Uuid) -> Result<Option<Self>, sqlx::Error> {
|
pub async fn find_by_id(pool: &SqlitePool, id: Uuid) -> Result<Option<Self>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Project,
|
Project,
|
||||||
r#"SELECT id as "id!: Uuid", name, git_repo_path, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>" FROM projects WHERE id = $1"#,
|
r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>" FROM projects WHERE id = $1"#,
|
||||||
id
|
id
|
||||||
)
|
)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
@@ -74,7 +77,7 @@ impl Project {
|
|||||||
) -> Result<Option<Self>, sqlx::Error> {
|
) -> Result<Option<Self>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Project,
|
Project,
|
||||||
r#"SELECT id as "id!: Uuid", name, git_repo_path, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>" FROM projects WHERE git_repo_path = $1"#,
|
r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>" FROM projects WHERE git_repo_path = $1"#,
|
||||||
git_repo_path
|
git_repo_path
|
||||||
)
|
)
|
||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
@@ -88,7 +91,7 @@ impl Project {
|
|||||||
) -> Result<Option<Self>, sqlx::Error> {
|
) -> Result<Option<Self>, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Project,
|
Project,
|
||||||
r#"SELECT id as "id!: Uuid", name, git_repo_path, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>" FROM projects WHERE git_repo_path = $1 AND id != $2"#,
|
r#"SELECT id as "id!: Uuid", name, git_repo_path, setup_script, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>" FROM projects WHERE git_repo_path = $1 AND id != $2"#,
|
||||||
git_repo_path,
|
git_repo_path,
|
||||||
exclude_id
|
exclude_id
|
||||||
)
|
)
|
||||||
@@ -103,10 +106,11 @@ impl Project {
|
|||||||
) -> Result<Self, sqlx::Error> {
|
) -> Result<Self, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Project,
|
Project,
|
||||||
r#"INSERT INTO projects (id, name, git_repo_path) VALUES ($1, $2, $3) RETURNING id as "id!: Uuid", name, git_repo_path, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>""#,
|
r#"INSERT INTO projects (id, name, git_repo_path, setup_script) VALUES ($1, $2, $3, $4) RETURNING id as "id!: Uuid", name, git_repo_path, setup_script, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>""#,
|
||||||
project_id,
|
project_id,
|
||||||
data.name,
|
data.name,
|
||||||
data.git_repo_path
|
data.git_repo_path,
|
||||||
|
data.setup_script
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
@@ -117,13 +121,15 @@ impl Project {
|
|||||||
id: Uuid,
|
id: Uuid,
|
||||||
name: String,
|
name: String,
|
||||||
git_repo_path: String,
|
git_repo_path: String,
|
||||||
|
setup_script: Option<String>,
|
||||||
) -> Result<Self, sqlx::Error> {
|
) -> Result<Self, sqlx::Error> {
|
||||||
sqlx::query_as!(
|
sqlx::query_as!(
|
||||||
Project,
|
Project,
|
||||||
r#"UPDATE projects SET name = $2, git_repo_path = $3 WHERE id = $1 RETURNING id as "id!: Uuid", name, git_repo_path, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>""#,
|
r#"UPDATE projects SET name = $2, git_repo_path = $3, setup_script = $4 WHERE id = $1 RETURNING id as "id!: Uuid", name, git_repo_path, setup_script, created_at as "created_at!: DateTime<Utc>", updated_at as "updated_at!: DateTime<Utc>""#,
|
||||||
id,
|
id,
|
||||||
name,
|
name,
|
||||||
git_repo_path
|
git_repo_path,
|
||||||
|
setup_script
|
||||||
)
|
)
|
||||||
.fetch_one(pool)
|
.fetch_one(pool)
|
||||||
.await
|
.await
|
||||||
|
|||||||
@@ -181,6 +181,41 @@ impl TaskAttempt {
|
|||||||
let branch_name = format!("attempt-{}", attempt_id);
|
let branch_name = format!("attempt-{}", attempt_id);
|
||||||
repo.worktree(&branch_name, worktree_path, None)?;
|
repo.worktree(&branch_name, worktree_path, None)?;
|
||||||
|
|
||||||
|
// Run setup script if it exists
|
||||||
|
if let Some(setup_script) = &project.setup_script {
|
||||||
|
if !setup_script.trim().is_empty() {
|
||||||
|
tracing::info!("Running setup script for task attempt {}", attempt_id);
|
||||||
|
|
||||||
|
let output = std::process::Command::new("bash")
|
||||||
|
.arg("-c")
|
||||||
|
.arg(setup_script)
|
||||||
|
.current_dir(worktree_path)
|
||||||
|
.output()
|
||||||
|
.map_err(|e| {
|
||||||
|
TaskAttemptError::Git(git2::Error::from_str(&format!(
|
||||||
|
"Failed to execute setup script: {}",
|
||||||
|
e
|
||||||
|
)))
|
||||||
|
})?;
|
||||||
|
|
||||||
|
if !output.status.success() {
|
||||||
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||||
|
tracing::error!("Setup script failed for attempt {}: {}", attempt_id, stderr);
|
||||||
|
return Err(TaskAttemptError::Git(git2::Error::from_str(&format!(
|
||||||
|
"Setup script failed: {}",
|
||||||
|
stderr
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
let stdout = String::from_utf8_lossy(&output.stdout);
|
||||||
|
tracing::info!(
|
||||||
|
"Setup script completed for attempt {}: {}",
|
||||||
|
attempt_id,
|
||||||
|
stdout
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Insert the record into the database
|
// Insert the record into the database
|
||||||
Ok(sqlx::query_as!(
|
Ok(sqlx::query_as!(
|
||||||
TaskAttempt,
|
TaskAttempt,
|
||||||
|
|||||||
@@ -204,8 +204,9 @@ pub async fn update_project(
|
|||||||
let git_repo_path = payload
|
let git_repo_path = payload
|
||||||
.git_repo_path
|
.git_repo_path
|
||||||
.unwrap_or(existing_project.git_repo_path.clone());
|
.unwrap_or(existing_project.git_repo_path.clone());
|
||||||
|
let setup_script = payload.setup_script.or(existing_project.setup_script);
|
||||||
|
|
||||||
match Project::update(&pool, id, name, git_repo_path).await {
|
match Project::update(&pool, id, name, git_repo_path, setup_script).await {
|
||||||
Ok(project) => Ok(ResponseJson(ApiResponse {
|
Ok(project) => Ok(ResponseJson(ApiResponse {
|
||||||
success: true,
|
success: true,
|
||||||
data: Some(project),
|
data: Some(project),
|
||||||
@@ -387,350 +388,3 @@ pub fn projects_router() -> Router {
|
|||||||
)
|
)
|
||||||
.route("/projects/:id/search", get(search_project_files))
|
.route("/projects/:id/search", get(search_project_files))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
use crate::auth::{hash_password, AuthUser};
|
|
||||||
use crate::models::project::{CreateProject, UpdateProject};
|
|
||||||
use axum::extract::Extension;
|
|
||||||
use chrono::Utc;
|
|
||||||
use sqlx::SqlitePool;
|
|
||||||
use uuid::Uuid;
|
|
||||||
|
|
||||||
async fn create_test_user(
|
|
||||||
pool: &SqlitePool,
|
|
||||||
email: &str,
|
|
||||||
password: &str,
|
|
||||||
is_admin: bool,
|
|
||||||
) -> User {
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
let now = Utc::now();
|
|
||||||
let password_hash = hash_password(password).unwrap();
|
|
||||||
|
|
||||||
sqlx::query_as!(
|
|
||||||
User,
|
|
||||||
"INSERT INTO users (id, email, password_hash, is_admin, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, email, password_hash, is_admin, created_at, updated_at",
|
|
||||||
id,
|
|
||||||
email,
|
|
||||||
password_hash,
|
|
||||||
is_admin,
|
|
||||||
now,
|
|
||||||
now
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_test_project(
|
|
||||||
pool: &SqlitePool,
|
|
||||||
name: &str,
|
|
||||||
git_repo_path: &str,
|
|
||||||
owner_id: Uuid,
|
|
||||||
) -> Project {
|
|
||||||
let id = Uuid::new_v4();
|
|
||||||
let now = Utc::now();
|
|
||||||
|
|
||||||
sqlx::query_as!(
|
|
||||||
Project,
|
|
||||||
"INSERT INTO projects (id, name, git_repo_path, owner_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id, name, git_repo_path, owner_id, created_at, updated_at",
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
git_repo_path,
|
|
||||||
owner_id,
|
|
||||||
now,
|
|
||||||
now
|
|
||||||
)
|
|
||||||
.fetch_one(pool)
|
|
||||||
.await
|
|
||||||
.unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_projects_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
|
|
||||||
// Create multiple projects
|
|
||||||
create_test_project(&pool, "Project 1", "/tmp/test1", user.id).await;
|
|
||||||
create_test_project(&pool, "Project 2", "/tmp/test2", user.id).await;
|
|
||||||
create_test_project(&pool, "Project 3", "/tmp/test3", user.id).await;
|
|
||||||
|
|
||||||
let auth = AuthUser {
|
|
||||||
user_id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
is_admin: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = get_projects(auth, Extension(pool)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
assert_eq!(response.data.unwrap().len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_projects_empty(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
|
|
||||||
let auth = AuthUser {
|
|
||||||
user_id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
is_admin: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = get_projects(auth, Extension(pool)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
assert_eq!(response.data.unwrap().len(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_project_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Test Project", "/tmp/test", user.id).await;
|
|
||||||
|
|
||||||
let auth = AuthUser {
|
|
||||||
user_id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
is_admin: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = get_project(auth, Path(project.id), Extension(pool)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let returned_project = response.data.unwrap();
|
|
||||||
assert_eq!(returned_project.id, project.id);
|
|
||||||
assert_eq!(returned_project.name, project.name);
|
|
||||||
assert_eq!(returned_project.owner_id, project.owner_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_get_project_not_found(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let nonexistent_project_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
let auth = AuthUser {
|
|
||||||
user_id: user.id,
|
|
||||||
email: user.email,
|
|
||||||
is_admin: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = get_project(auth, Path(nonexistent_project_id), Extension(pool)).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_create_project_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
|
|
||||||
let auth = AuthUser {
|
|
||||||
user_id: user.id,
|
|
||||||
email: user.email.clone(),
|
|
||||||
is_admin: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let create_request = CreateProject {
|
|
||||||
name: "New Project".to_string(),
|
|
||||||
git_repo_path: "/tmp/new-project".to_string(),
|
|
||||||
use_existing_repo: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = create_project(auth.clone(), Extension(pool), Json(create_request)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let created_project = response.data.unwrap();
|
|
||||||
assert_eq!(created_project.name, "New Project");
|
|
||||||
assert_eq!(created_project.owner_id, auth.user_id);
|
|
||||||
assert_eq!(response.message.unwrap(), "Project created successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_create_project_as_admin(pool: SqlitePool) {
|
|
||||||
let admin_user = create_test_user(&pool, "admin@example.com", "password123", true).await;
|
|
||||||
|
|
||||||
let auth = AuthUser {
|
|
||||||
user_id: admin_user.id,
|
|
||||||
email: admin_user.email.clone(),
|
|
||||||
is_admin: true,
|
|
||||||
};
|
|
||||||
|
|
||||||
let create_request = CreateProject {
|
|
||||||
name: "Admin Project".to_string(),
|
|
||||||
git_repo_path: "/tmp/admin-project".to_string(),
|
|
||||||
use_existing_repo: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = create_project(auth.clone(), Extension(pool), Json(create_request)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let created_project = response.data.unwrap();
|
|
||||||
assert_eq!(created_project.name, "Admin Project");
|
|
||||||
assert_eq!(created_project.owner_id, auth.user_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_update_project_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Original Name", "/tmp/original", user.id).await;
|
|
||||||
|
|
||||||
let update_request = UpdateProject {
|
|
||||||
name: Some("Updated Name".to_string()),
|
|
||||||
git_repo_path: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = update_project(Path(project.id), Extension(pool), Json(update_request)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let updated_project = response.data.unwrap();
|
|
||||||
assert_eq!(updated_project.name, "Updated Name");
|
|
||||||
assert_eq!(updated_project.owner_id, project.owner_id);
|
|
||||||
assert_eq!(response.message.unwrap(), "Project updated successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_update_project_partial(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project = create_test_project(&pool, "Original Name", "/tmp/original", user.id).await;
|
|
||||||
|
|
||||||
// Update with no changes (None for name should keep existing name)
|
|
||||||
let update_request = UpdateProject {
|
|
||||||
name: None,
|
|
||||||
git_repo_path: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = update_project(Path(project.id), Extension(pool), Json(update_request)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert!(response.data.is_some());
|
|
||||||
let updated_project = response.data.unwrap();
|
|
||||||
assert_eq!(updated_project.name, "Original Name"); // Should remain unchanged
|
|
||||||
assert_eq!(updated_project.owner_id, project.owner_id);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_update_project_not_found(pool: SqlitePool) {
|
|
||||||
let nonexistent_project_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
let update_request = UpdateProject {
|
|
||||||
name: Some("Updated Name".to_string()),
|
|
||||||
git_repo_path: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let result = update_project(
|
|
||||||
Path(nonexistent_project_id),
|
|
||||||
Extension(pool),
|
|
||||||
Json(update_request),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_delete_project_success(pool: SqlitePool) {
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project =
|
|
||||||
create_test_project(&pool, "Project to Delete", "/tmp/to-delete", user.id).await;
|
|
||||||
|
|
||||||
let result = delete_project(Path(project.id), Extension(pool)).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
let response = result.unwrap().0;
|
|
||||||
assert!(response.success);
|
|
||||||
assert_eq!(response.message.unwrap(), "Project deleted successfully");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_delete_project_not_found(pool: SqlitePool) {
|
|
||||||
let nonexistent_project_id = Uuid::new_v4();
|
|
||||||
|
|
||||||
let result = delete_project(Path(nonexistent_project_id), Extension(pool)).await;
|
|
||||||
assert!(result.is_err());
|
|
||||||
assert_eq!(result.unwrap_err(), StatusCode::NOT_FOUND);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_delete_project_cascades_to_tasks(pool: SqlitePool) {
|
|
||||||
use crate::models::task::{Task, TaskStatus};
|
|
||||||
|
|
||||||
let user = create_test_user(&pool, "test@example.com", "password123", false).await;
|
|
||||||
let project =
|
|
||||||
create_test_project(&pool, "Project with Tasks", "/tmp/with-tasks", user.id).await;
|
|
||||||
|
|
||||||
// Create a task in the project
|
|
||||||
let task_id = Uuid::new_v4();
|
|
||||||
let now = Utc::now();
|
|
||||||
sqlx::query!(
|
|
||||||
"INSERT INTO tasks (id, project_id, title, description, status, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7)",
|
|
||||||
task_id,
|
|
||||||
project.id,
|
|
||||||
"Test Task",
|
|
||||||
Some("Test Description"),
|
|
||||||
TaskStatus::Todo as TaskStatus,
|
|
||||||
now,
|
|
||||||
now
|
|
||||||
)
|
|
||||||
.execute(&pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
// Verify task exists
|
|
||||||
let task_count_before = sqlx::query!(
|
|
||||||
"SELECT COUNT(*) as count FROM tasks WHERE project_id = $1",
|
|
||||||
project.id
|
|
||||||
)
|
|
||||||
.fetch_one(&pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(task_count_before.count.unwrap(), 1);
|
|
||||||
|
|
||||||
// Delete the project
|
|
||||||
let result = delete_project(Path(project.id), Extension(pool.clone())).await;
|
|
||||||
assert!(result.is_ok());
|
|
||||||
|
|
||||||
// Verify tasks were cascaded (deleted)
|
|
||||||
let task_count_after = sqlx::query!(
|
|
||||||
"SELECT COUNT(*) as count FROM tasks WHERE project_id = $1",
|
|
||||||
project.id
|
|
||||||
)
|
|
||||||
.fetch_one(&pool)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(task_count_after.count.unwrap(), 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[sqlx::test]
|
|
||||||
async fn test_projects_belong_to_users(pool: SqlitePool) {
|
|
||||||
let user1 = create_test_user(&pool, "user1@example.com", "password123", false).await;
|
|
||||||
let user2 = create_test_user(&pool, "user2@example.com", "password123", false).await;
|
|
||||||
|
|
||||||
let project1 = create_test_project(&pool, "User 1 Project", "/tmp/user1", user1.id).await;
|
|
||||||
let project2 = create_test_project(&pool, "User 2 Project", "/tmp/user2", user2.id).await;
|
|
||||||
|
|
||||||
// Verify project ownership
|
|
||||||
assert_eq!(project1.owner_id, user1.id);
|
|
||||||
assert_eq!(project2.owner_id, user2.id);
|
|
||||||
assert_ne!(project1.owner_id, project2.owner_id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@@ -31,6 +31,7 @@ export function ProjectForm({
|
|||||||
}: ProjectFormProps) {
|
}: ProjectFormProps) {
|
||||||
const [name, setName] = useState(project?.name || "");
|
const [name, setName] = useState(project?.name || "");
|
||||||
const [gitRepoPath, setGitRepoPath] = useState(project?.git_repo_path || "");
|
const [gitRepoPath, setGitRepoPath] = useState(project?.git_repo_path || "");
|
||||||
|
const [setupScript, setSetupScript] = useState(project?.setup_script ?? "");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState("");
|
const [error, setError] = useState("");
|
||||||
const [showFolderPicker, setShowFolderPicker] = useState(false);
|
const [showFolderPicker, setShowFolderPicker] = useState(false);
|
||||||
@@ -40,6 +41,19 @@ export function ProjectForm({
|
|||||||
|
|
||||||
const isEditing = !!project;
|
const isEditing = !!project;
|
||||||
|
|
||||||
|
// Update form fields when project prop changes
|
||||||
|
useEffect(() => {
|
||||||
|
if (project) {
|
||||||
|
setName(project.name || "");
|
||||||
|
setGitRepoPath(project.git_repo_path || "");
|
||||||
|
setSetupScript(project.setup_script ?? "");
|
||||||
|
} else {
|
||||||
|
setName("");
|
||||||
|
setGitRepoPath("");
|
||||||
|
setSetupScript("");
|
||||||
|
}
|
||||||
|
}, [project]);
|
||||||
|
|
||||||
// Auto-populate project name from directory name
|
// Auto-populate project name from directory name
|
||||||
const handleGitRepoPathChange = (path: string) => {
|
const handleGitRepoPathChange = (path: string) => {
|
||||||
setGitRepoPath(path);
|
setGitRepoPath(path);
|
||||||
@@ -75,6 +89,7 @@ export function ProjectForm({
|
|||||||
const updateData: UpdateProject = {
|
const updateData: UpdateProject = {
|
||||||
name,
|
name,
|
||||||
git_repo_path: finalGitRepoPath,
|
git_repo_path: finalGitRepoPath,
|
||||||
|
setup_script: setupScript.trim() || null,
|
||||||
};
|
};
|
||||||
const response = await makeRequest(
|
const response = await makeRequest(
|
||||||
`/api/projects/${project.id}`,
|
`/api/projects/${project.id}`,
|
||||||
@@ -97,6 +112,7 @@ export function ProjectForm({
|
|||||||
name,
|
name,
|
||||||
git_repo_path: finalGitRepoPath,
|
git_repo_path: finalGitRepoPath,
|
||||||
use_existing_repo: repoMode === "existing",
|
use_existing_repo: repoMode === "existing",
|
||||||
|
setup_script: setupScript.trim() || null,
|
||||||
};
|
};
|
||||||
const response = await makeRequest("/api/projects", {
|
const response = await makeRequest("/api/projects", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@@ -116,6 +132,7 @@ export function ProjectForm({
|
|||||||
onSuccess();
|
onSuccess();
|
||||||
setName("");
|
setName("");
|
||||||
setGitRepoPath("");
|
setGitRepoPath("");
|
||||||
|
setSetupScript("");
|
||||||
setParentPath("");
|
setParentPath("");
|
||||||
setFolderName("");
|
setFolderName("");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -126,8 +143,17 @@ export function ProjectForm({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleClose = () => {
|
const handleClose = () => {
|
||||||
setName(project?.name || "");
|
if (project) {
|
||||||
setGitRepoPath(project?.git_repo_path || "");
|
setName(project.name || "");
|
||||||
|
setGitRepoPath(project.git_repo_path || "");
|
||||||
|
setSetupScript(project.setup_script ?? "");
|
||||||
|
} else {
|
||||||
|
setName("");
|
||||||
|
setGitRepoPath("");
|
||||||
|
setSetupScript("");
|
||||||
|
}
|
||||||
|
setParentPath("");
|
||||||
|
setFolderName("");
|
||||||
setError("");
|
setError("");
|
||||||
onClose();
|
onClose();
|
||||||
};
|
};
|
||||||
@@ -274,6 +300,22 @@ export function ProjectForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="setup-script">Setup Script (Optional)</Label>
|
||||||
|
<textarea
|
||||||
|
id="setup-script"
|
||||||
|
value={setupScript}
|
||||||
|
onChange={(e) => setSetupScript(e.target.value)}
|
||||||
|
placeholder="#!/bin/bash npm install # Add any setup commands here..."
|
||||||
|
rows={4}
|
||||||
|
className="w-full px-3 py-2 border border-input bg-background text-foreground rounded-md resize-vertical focus:outline-none focus:ring-2 focus:ring-ring"
|
||||||
|
/>
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
This script will run after creating the worktree and before the executor starts.
|
||||||
|
Use it for setup tasks like installing dependencies or preparing the environment.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<Alert variant="destructive">
|
<Alert variant="destructive">
|
||||||
<AlertCircle className="h-4 w-4" />
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
|||||||
@@ -5,11 +5,11 @@ export type ApiResponse<T> = { success: boolean, data: T | null, message: string
|
|||||||
|
|
||||||
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" };
|
export type ExecutorConfig = { "type": "echo" } | { "type": "claude" } | { "type": "amp" };
|
||||||
|
|
||||||
export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, };
|
export type CreateProject = { name: string, git_repo_path: string, use_existing_repo: boolean, setup_script: string | null, };
|
||||||
|
|
||||||
export type Project = { id: string, name: string, git_repo_path: string, created_at: Date, updated_at: Date, };
|
export type Project = { id: string, name: string, git_repo_path: string, setup_script: string | null, created_at: Date, updated_at: Date, };
|
||||||
|
|
||||||
export type UpdateProject = { name: string | null, git_repo_path: string | null, };
|
export type UpdateProject = { name: string | null, git_repo_path: string | null, setup_script: string | null, };
|
||||||
|
|
||||||
export type SearchResult = { path: string, is_file: boolean, match_type: SearchMatchType, };
|
export type SearchResult = { path: string, is_file: boolean, match_type: SearchMatchType, };
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user