diff --git a/AGENT.md b/AGENT.md
index 71bf66be..64033489 100644
--- a/AGENT.md
+++ b/AGENT.md
@@ -68,3 +68,8 @@ When working on any task that involves changes to the backend and the frontend,
# Testing your work
Try to build the Typescript project after any frontend changes `npm run build`
+
+# Backend data models
+
+SQLX queries should be located in backend/src/models/\*
+Use getters and setters instead of raw SQL queries where possible.
diff --git a/backend/Cargo.toml b/backend/Cargo.toml
index e31b386c..3bcc2775 100644
--- a/backend/Cargo.toml
+++ b/backend/Cargo.toml
@@ -26,6 +26,7 @@ bcrypt = "0.15"
jsonwebtoken = "9.2"
ts-rs = { version = "9.0", features = ["uuid-impl", "chrono-impl"] }
dirs = "5.0"
+git2 = "0.18"
[build-dependencies]
ts-rs = { version = "9.0", features = ["uuid-impl", "chrono-impl"] }
diff --git a/backend/src/models/task.rs b/backend/src/models/task.rs
index 830208a5..b1842e98 100644
--- a/backend/src/models/task.rs
+++ b/backend/src/models/task.rs
@@ -58,6 +58,18 @@ impl Task {
.await
}
+ pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result, sqlx::Error> {
+ sqlx::query_as!(
+ Task,
+ r#"SELECT id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at
+ FROM tasks
+ WHERE id = $1"#,
+ id
+ )
+ .fetch_optional(pool)
+ .await
+ }
+
pub async fn find_by_id_and_project_id(pool: &PgPool, id: Uuid, project_id: Uuid) -> Result , sqlx::Error> {
sqlx::query_as!(
Task,
diff --git a/backend/src/models/task_attempt.rs b/backend/src/models/task_attempt.rs
index ef891124..0da2a59c 100644
--- a/backend/src/models/task_attempt.rs
+++ b/backend/src/models/task_attempt.rs
@@ -3,6 +3,44 @@ use serde::{Deserialize, Serialize};
use sqlx::{FromRow, Type, PgPool};
use ts_rs::TS;
use uuid::Uuid;
+use git2::{Repository, Error as GitError};
+use std::path::Path;
+
+use super::task::Task;
+use super::project::Project;
+
+#[derive(Debug)]
+pub enum TaskAttemptError {
+ Database(sqlx::Error),
+ Git(GitError),
+ TaskNotFound,
+ ProjectNotFound,
+}
+
+impl std::fmt::Display for TaskAttemptError {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ TaskAttemptError::Database(e) => write!(f, "Database error: {}", e),
+ TaskAttemptError::Git(e) => write!(f, "Git error: {}", e),
+ TaskAttemptError::TaskNotFound => write!(f, "Task not found"),
+ TaskAttemptError::ProjectNotFound => write!(f, "Project not found"),
+ }
+ }
+}
+
+impl std::error::Error for TaskAttemptError {}
+
+impl From for TaskAttemptError {
+ fn from(err: sqlx::Error) -> Self {
+ TaskAttemptError::Database(err)
+ }
+}
+
+impl From for TaskAttemptError {
+ fn from(err: GitError) -> Self {
+ TaskAttemptError::Git(err)
+ }
+}
#[derive(Debug, Clone, Type, Serialize, Deserialize, PartialEq, TS)]
#[sqlx(type_name = "task_attempt_status", rename_all = "lowercase")]
@@ -57,10 +95,34 @@ impl TaskAttempt {
.await
}
- pub async fn create(pool: &PgPool, data: &CreateTaskAttempt, attempt_id: Uuid) -> Result {
+ pub async fn create(pool: &PgPool, data: &CreateTaskAttempt, attempt_id: Uuid) -> Result {
let now = Utc::now();
- sqlx::query_as!(
+ // First, get the task to get the project_id
+ let task = Task::find_by_id(pool, data.task_id)
+ .await?
+ .ok_or(TaskAttemptError::TaskNotFound)?;
+
+ // Then get the project using the project_id
+ let project = Project::find_by_id(pool, task.project_id)
+ .await?
+ .ok_or(TaskAttemptError::ProjectNotFound)?;
+
+ // Create the worktree using git2
+ let repo = Repository::open(&project.git_repo_path)?;
+ let worktree_path = Path::new(&data.worktree_path);
+
+ // Create the worktree directory if it doesn't exist
+ if let Some(parent) = worktree_path.parent() {
+ std::fs::create_dir_all(parent).map_err(|e| TaskAttemptError::Git(GitError::from_str(&e.to_string())))?;
+ }
+
+ // Create the worktree at the specified path
+ let branch_name = format!("attempt-{}", attempt_id);
+ repo.worktree(&branch_name, worktree_path, None)?;
+
+ // Insert the record into the database
+ let task_attempt = sqlx::query_as!(
TaskAttempt,
r#"INSERT INTO task_attempts (id, task_id, worktree_path, base_commit, merge_commit, created_at, updated_at)
VALUES ($1, $2, $3, $4, $5, $6, $7)
@@ -74,7 +136,9 @@ impl TaskAttempt {
now
)
.fetch_one(pool)
- .await
+ .await?;
+
+ Ok(task_attempt)
}
pub async fn exists_for_task(pool: &PgPool, attempt_id: Uuid, task_id: Uuid, project_id: Uuid) -> Result {