Separation of concerns
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use sqlx::{FromRow, PgPool};
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -31,3 +31,91 @@ pub struct UpdateProject {
|
||||
pub name: Option<String>,
|
||||
pub git_repo_path: Option<String>,
|
||||
}
|
||||
|
||||
impl Project {
|
||||
pub async fn find_all(pool: &PgPool) -> Result<Vec<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
"SELECT id, name, git_repo_path, owner_id, created_at, updated_at FROM projects ORDER BY created_at DESC"
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result<Option<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
"SELECT id, name, git_repo_path, owner_id, created_at, updated_at FROM projects WHERE id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_git_repo_path(pool: &PgPool, git_repo_path: &str) -> Result<Option<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
"SELECT id, name, git_repo_path, owner_id, created_at, updated_at FROM projects WHERE git_repo_path = $1",
|
||||
git_repo_path
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_git_repo_path_excluding_id(pool: &PgPool, git_repo_path: &str, exclude_id: Uuid) -> Result<Option<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
"SELECT id, name, git_repo_path, owner_id, created_at, updated_at FROM projects WHERE git_repo_path = $1 AND id != $2",
|
||||
git_repo_path,
|
||||
exclude_id
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create(pool: &PgPool, data: &CreateProject, owner_id: Uuid, project_id: Uuid) -> Result<Self, sqlx::Error> {
|
||||
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",
|
||||
project_id,
|
||||
data.name,
|
||||
data.git_repo_path,
|
||||
owner_id,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update(pool: &PgPool, id: Uuid, name: String, git_repo_path: String) -> Result<Self, sqlx::Error> {
|
||||
let now = Utc::now();
|
||||
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
"UPDATE projects SET name = $2, git_repo_path = $3, updated_at = $4 WHERE id = $1 RETURNING id, name, git_repo_path, owner_id, created_at, updated_at",
|
||||
id,
|
||||
name,
|
||||
git_repo_path,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(pool: &PgPool, id: Uuid) -> Result<u64, sqlx::Error> {
|
||||
let result = sqlx::query!("DELETE FROM projects WHERE id = $1", id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
pub async fn exists(pool: &PgPool, id: Uuid) -> Result<bool, sqlx::Error> {
|
||||
let result = sqlx::query!("SELECT id FROM projects WHERE id = $1", id)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
Ok(result.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, Type};
|
||||
use sqlx::{FromRow, Type, PgPool};
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -43,3 +43,93 @@ pub struct UpdateTask {
|
||||
pub description: Option<String>,
|
||||
pub status: Option<TaskStatus>,
|
||||
}
|
||||
|
||||
impl Task {
|
||||
pub async fn find_by_project_id(pool: &PgPool, project_id: Uuid) -> Result<Vec<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
Task,
|
||||
r#"SELECT id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at
|
||||
FROM tasks
|
||||
WHERE project_id = $1
|
||||
ORDER BY created_at DESC"#,
|
||||
project_id
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_id_and_project_id(pool: &PgPool, id: Uuid, project_id: Uuid) -> Result<Option<Self>, 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 AND project_id = $2"#,
|
||||
id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create(pool: &PgPool, data: &CreateTask, task_id: Uuid) -> Result<Self, sqlx::Error> {
|
||||
let now = Utc::now();
|
||||
|
||||
sqlx::query_as!(
|
||||
Task,
|
||||
r#"INSERT INTO tasks (id, project_id, title, description, status, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at"#,
|
||||
task_id,
|
||||
data.project_id,
|
||||
data.title,
|
||||
data.description,
|
||||
TaskStatus::Todo as TaskStatus,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update(pool: &PgPool, id: Uuid, project_id: Uuid, title: String, description: Option<String>, status: TaskStatus) -> Result<Self, sqlx::Error> {
|
||||
let now = Utc::now();
|
||||
|
||||
sqlx::query_as!(
|
||||
Task,
|
||||
r#"UPDATE tasks
|
||||
SET title = $3, description = $4, status = $5, updated_at = $6
|
||||
WHERE id = $1 AND project_id = $2
|
||||
RETURNING id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at"#,
|
||||
id,
|
||||
project_id,
|
||||
title,
|
||||
description,
|
||||
status as TaskStatus,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(pool: &PgPool, id: Uuid, project_id: Uuid) -> Result<u64, sqlx::Error> {
|
||||
let result = sqlx::query!(
|
||||
"DELETE FROM tasks WHERE id = $1 AND project_id = $2",
|
||||
id,
|
||||
project_id
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
pub async fn exists(pool: &PgPool, id: Uuid, project_id: Uuid) -> Result<bool, sqlx::Error> {
|
||||
let result = sqlx::query!(
|
||||
"SELECT id FROM tasks WHERE id = $1 AND project_id = $2",
|
||||
id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
Ok(result.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, Type};
|
||||
use sqlx::{FromRow, Type, PgPool};
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -42,3 +42,52 @@ pub struct UpdateTaskAttempt {
|
||||
pub base_commit: Option<String>,
|
||||
pub merge_commit: Option<String>,
|
||||
}
|
||||
|
||||
impl TaskAttempt {
|
||||
pub async fn find_by_task_id(pool: &PgPool, task_id: Uuid) -> Result<Vec<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
TaskAttempt,
|
||||
r#"SELECT id, task_id, worktree_path, base_commit, merge_commit, created_at, updated_at
|
||||
FROM task_attempts
|
||||
WHERE task_id = $1
|
||||
ORDER BY created_at DESC"#,
|
||||
task_id
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create(pool: &PgPool, data: &CreateTaskAttempt, attempt_id: Uuid) -> Result<Self, sqlx::Error> {
|
||||
let now = Utc::now();
|
||||
|
||||
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)
|
||||
RETURNING id, task_id, worktree_path, base_commit, merge_commit, created_at, updated_at"#,
|
||||
attempt_id,
|
||||
data.task_id,
|
||||
data.worktree_path,
|
||||
data.base_commit,
|
||||
data.merge_commit,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn exists_for_task(pool: &PgPool, attempt_id: Uuid, task_id: Uuid, project_id: Uuid) -> Result<bool, sqlx::Error> {
|
||||
let result = sqlx::query!(
|
||||
"SELECT ta.id FROM task_attempts ta
|
||||
JOIN tasks t ON ta.task_id = t.id
|
||||
WHERE ta.id = $1 AND t.id = $2 AND t.project_id = $3",
|
||||
attempt_id,
|
||||
task_id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
Ok(result.is_some())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use sqlx::{FromRow, PgPool};
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -23,3 +23,53 @@ pub struct CreateTaskAttemptActivity {
|
||||
pub status: Option<TaskAttemptStatus>, // Default to Init if not provided
|
||||
pub note: Option<String>,
|
||||
}
|
||||
|
||||
impl TaskAttemptActivity {
|
||||
pub async fn find_by_attempt_id(pool: &PgPool, attempt_id: Uuid) -> Result<Vec<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
TaskAttemptActivity,
|
||||
r#"SELECT id, task_attempt_id, status as "status!: TaskAttemptStatus", note, created_at
|
||||
FROM task_attempt_activities
|
||||
WHERE task_attempt_id = $1
|
||||
ORDER BY created_at DESC"#,
|
||||
attempt_id
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create(pool: &PgPool, data: &CreateTaskAttemptActivity, activity_id: Uuid, status: TaskAttemptStatus) -> Result<Self, sqlx::Error> {
|
||||
let now = Utc::now();
|
||||
|
||||
sqlx::query_as!(
|
||||
TaskAttemptActivity,
|
||||
r#"INSERT INTO task_attempt_activities (id, task_attempt_id, status, note, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, task_attempt_id, status as "status!: TaskAttemptStatus", note, created_at"#,
|
||||
activity_id,
|
||||
data.task_attempt_id,
|
||||
status as TaskAttemptStatus,
|
||||
data.note,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create_initial(pool: &PgPool, attempt_id: Uuid, activity_id: Uuid) -> Result<(), sqlx::Error> {
|
||||
let now = Utc::now();
|
||||
|
||||
sqlx::query!(
|
||||
r#"INSERT INTO task_attempt_activities (id, task_attempt_id, status, note, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5)"#,
|
||||
activity_id,
|
||||
attempt_id,
|
||||
TaskAttemptStatus::Init as TaskAttemptStatus,
|
||||
Option::<String>::None,
|
||||
now
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use sqlx::{FromRow, PgPool};
|
||||
use ts_rs::TS;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -69,3 +69,75 @@ impl From<User> for UserResponse {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub async fn find_by_email(pool: &PgPool, email: &str) -> Result<Option<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, email, password_hash, is_admin, created_at, updated_at FROM users WHERE email = $1",
|
||||
email
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_all(pool: &PgPool) -> Result<Vec<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, email, password_hash, is_admin, created_at, updated_at FROM users ORDER BY created_at DESC"
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn find_by_id(pool: &PgPool, id: Uuid) -> Result<Option<Self>, sqlx::Error> {
|
||||
sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, email, password_hash, is_admin, created_at, updated_at FROM users WHERE id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn create(pool: &PgPool, data: &CreateUser, password_hash: String, user_id: Uuid) -> Result<Self, sqlx::Error> {
|
||||
let now = Utc::now();
|
||||
let is_admin = data.is_admin.unwrap_or(false);
|
||||
|
||||
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",
|
||||
user_id,
|
||||
data.email,
|
||||
password_hash,
|
||||
is_admin,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn update(pool: &PgPool, id: Uuid, email: String, password_hash: String, is_admin: bool) -> Result<Self, sqlx::Error> {
|
||||
let now = Utc::now();
|
||||
|
||||
sqlx::query_as!(
|
||||
User,
|
||||
"UPDATE users SET email = $2, password_hash = $3, is_admin = $4, updated_at = $5 WHERE id = $1 RETURNING id, email, password_hash, is_admin, created_at, updated_at",
|
||||
id,
|
||||
email,
|
||||
password_hash,
|
||||
is_admin,
|
||||
now
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await
|
||||
}
|
||||
|
||||
pub async fn delete(pool: &PgPool, id: Uuid) -> Result<u64, sqlx::Error> {
|
||||
let result = sqlx::query!("DELETE FROM users WHERE id = $1", id)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,22 +8,15 @@ use axum::{
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::models::{ApiResponse, project::{Project, CreateProject, UpdateProject}};
|
||||
use crate::auth::AuthUser;
|
||||
|
||||
pub async fn get_projects(
|
||||
auth: AuthUser,
|
||||
_auth: AuthUser,
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<Vec<Project>>>, StatusCode> {
|
||||
match sqlx::query_as!(
|
||||
Project,
|
||||
"SELECT id, name, git_repo_path, owner_id, created_at, updated_at FROM projects ORDER BY created_at DESC"
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
{
|
||||
match Project::find_all(&pool).await {
|
||||
Ok(projects) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(projects),
|
||||
@@ -37,18 +30,11 @@ pub async fn get_projects(
|
||||
}
|
||||
|
||||
pub async fn get_project(
|
||||
auth: AuthUser,
|
||||
_auth: AuthUser,
|
||||
Path(id): Path<Uuid>,
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<Project>>, StatusCode> {
|
||||
match sqlx::query_as!(
|
||||
Project,
|
||||
"SELECT id, name, git_repo_path, owner_id, created_at, updated_at FROM projects WHERE id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
{
|
||||
match Project::find_by_id(&pool, id).await {
|
||||
Ok(Some(project)) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(project),
|
||||
@@ -68,19 +54,11 @@ pub async fn create_project(
|
||||
Json(payload): Json<CreateProject>
|
||||
) -> Result<ResponseJson<ApiResponse<Project>>, StatusCode> {
|
||||
let id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
|
||||
tracing::debug!("Creating project '{}' for user {}", payload.name, auth.user_id);
|
||||
|
||||
// Check if git repo path is already used by another project
|
||||
let existing_project = sqlx::query!(
|
||||
"SELECT id FROM projects WHERE git_repo_path = $1",
|
||||
payload.git_repo_path
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
match existing_project {
|
||||
match Project::find_by_git_repo_path(&pool, &payload.git_repo_path).await {
|
||||
Ok(Some(_)) => {
|
||||
return Ok(ResponseJson(ApiResponse {
|
||||
success: false,
|
||||
@@ -170,19 +148,7 @@ pub async fn create_project(
|
||||
}
|
||||
}
|
||||
|
||||
match 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,
|
||||
payload.name,
|
||||
payload.git_repo_path,
|
||||
auth.user_id,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
{
|
||||
match Project::create(&pool, &payload, auth.user_id, id).await {
|
||||
Ok(project) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(project),
|
||||
@@ -200,18 +166,8 @@ pub async fn update_project(
|
||||
Extension(pool): Extension<PgPool>,
|
||||
Json(payload): Json<UpdateProject>
|
||||
) -> Result<ResponseJson<ApiResponse<Project>>, StatusCode> {
|
||||
let now = Utc::now();
|
||||
|
||||
// Check if project exists first
|
||||
let existing_project = sqlx::query_as!(
|
||||
Project,
|
||||
"SELECT id, name, git_repo_path, owner_id, created_at, updated_at FROM projects WHERE id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
let existing_project = match existing_project {
|
||||
let existing_project = match Project::find_by_id(&pool, id).await {
|
||||
Ok(Some(project)) => project,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
@@ -223,15 +179,7 @@ pub async fn update_project(
|
||||
// If git_repo_path is being changed, check if the new path is already used by another project
|
||||
if let Some(new_git_repo_path) = &payload.git_repo_path {
|
||||
if new_git_repo_path != &existing_project.git_repo_path {
|
||||
let duplicate_project = sqlx::query!(
|
||||
"SELECT id FROM projects WHERE git_repo_path = $1 AND id != $2",
|
||||
new_git_repo_path,
|
||||
id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
match duplicate_project {
|
||||
match Project::find_by_git_repo_path_excluding_id(&pool, new_git_repo_path, id).await {
|
||||
Ok(Some(_)) => {
|
||||
return Ok(ResponseJson(ApiResponse {
|
||||
success: false,
|
||||
@@ -254,17 +202,7 @@ pub async fn update_project(
|
||||
let name = payload.name.unwrap_or(existing_project.name);
|
||||
let git_repo_path = payload.git_repo_path.unwrap_or(existing_project.git_repo_path.clone());
|
||||
|
||||
match sqlx::query_as!(
|
||||
Project,
|
||||
"UPDATE projects SET name = $2, git_repo_path = $3, updated_at = $4 WHERE id = $1 RETURNING id, name, git_repo_path, owner_id, created_at, updated_at",
|
||||
id,
|
||||
name,
|
||||
git_repo_path,
|
||||
now
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
{
|
||||
match Project::update(&pool, id, name, git_repo_path).await {
|
||||
Ok(project) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(project),
|
||||
@@ -281,12 +219,9 @@ pub async fn delete_project(
|
||||
Path(id): Path<Uuid>,
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
|
||||
match sqlx::query!("DELETE FROM projects WHERE id = $1", id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
if result.rows_affected() == 0 {
|
||||
match Project::delete(&pool, id).await {
|
||||
Ok(rows_affected) => {
|
||||
if rows_affected == 0 {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
} else {
|
||||
Ok(ResponseJson(ApiResponse {
|
||||
@@ -453,6 +388,7 @@ mod tests {
|
||||
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;
|
||||
@@ -480,6 +416,7 @@ mod tests {
|
||||
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;
|
||||
|
||||
@@ -8,32 +8,22 @@ use axum::{
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::models::{
|
||||
ApiResponse,
|
||||
task::{Task, CreateTask, UpdateTask, TaskStatus},
|
||||
task_attempt::{TaskAttempt, CreateTaskAttempt, UpdateTaskAttempt, TaskAttemptStatus},
|
||||
project::Project,
|
||||
task::{Task, CreateTask, UpdateTask},
|
||||
task_attempt::{TaskAttempt, CreateTaskAttempt, TaskAttemptStatus},
|
||||
task_attempt_activity::{TaskAttemptActivity, CreateTaskAttemptActivity}
|
||||
};
|
||||
use crate::auth::AuthUser;
|
||||
|
||||
pub async fn get_project_tasks(
|
||||
auth: AuthUser,
|
||||
_auth: AuthUser,
|
||||
Path(project_id): Path<Uuid>,
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<Vec<Task>>>, StatusCode> {
|
||||
match sqlx::query_as!(
|
||||
Task,
|
||||
r#"SELECT id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at
|
||||
FROM tasks
|
||||
WHERE project_id = $1
|
||||
ORDER BY created_at DESC"#,
|
||||
project_id
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
{
|
||||
match Task::find_by_project_id(&pool, project_id).await {
|
||||
Ok(tasks) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(tasks),
|
||||
@@ -47,21 +37,11 @@ pub async fn get_project_tasks(
|
||||
}
|
||||
|
||||
pub async fn get_task(
|
||||
auth: AuthUser,
|
||||
_auth: AuthUser,
|
||||
Path((project_id, task_id)): Path<(Uuid, Uuid)>,
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<Task>>, StatusCode> {
|
||||
match sqlx::query_as!(
|
||||
Task,
|
||||
r#"SELECT id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at
|
||||
FROM tasks
|
||||
WHERE id = $1 AND project_id = $2"#,
|
||||
task_id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
{
|
||||
match Task::find_by_id_and_project_id(&pool, task_id, project_id).await {
|
||||
Ok(Some(task)) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(task),
|
||||
@@ -82,43 +62,23 @@ pub async fn create_task(
|
||||
Json(mut payload): Json<CreateTask>
|
||||
) -> Result<ResponseJson<ApiResponse<Task>>, StatusCode> {
|
||||
let id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
|
||||
// Ensure the project_id in the payload matches the path parameter
|
||||
payload.project_id = project_id;
|
||||
|
||||
// Verify project exists first
|
||||
let project_exists = sqlx::query!("SELECT id FROM projects WHERE id = $1", project_id)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
match project_exists {
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
match Project::exists(&pool, project_id).await {
|
||||
Ok(false) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to check project existence: {}", e);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
Ok(Some(_)) => {}
|
||||
Ok(true) => {}
|
||||
}
|
||||
|
||||
tracing::debug!("Creating task '{}' in project {} for user {}", payload.title, project_id, auth.user_id);
|
||||
|
||||
match sqlx::query_as!(
|
||||
Task,
|
||||
r#"INSERT INTO tasks (id, project_id, title, description, status, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at"#,
|
||||
id,
|
||||
payload.project_id,
|
||||
payload.title,
|
||||
payload.description,
|
||||
TaskStatus::Todo as TaskStatus,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
{
|
||||
match Task::create(&pool, &payload, id).await {
|
||||
Ok(task) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(task),
|
||||
@@ -136,21 +96,8 @@ pub async fn update_task(
|
||||
Extension(pool): Extension<PgPool>,
|
||||
Json(payload): Json<UpdateTask>
|
||||
) -> Result<ResponseJson<ApiResponse<Task>>, StatusCode> {
|
||||
let now = Utc::now();
|
||||
|
||||
// Check if task exists in the specified project
|
||||
let existing_task = sqlx::query_as!(
|
||||
Task,
|
||||
r#"SELECT id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at
|
||||
FROM tasks
|
||||
WHERE id = $1 AND project_id = $2"#,
|
||||
task_id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
let existing_task = match existing_task {
|
||||
let existing_task = match Task::find_by_id_and_project_id(&pool, task_id, project_id).await {
|
||||
Ok(Some(task)) => task,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
@@ -164,22 +111,7 @@ pub async fn update_task(
|
||||
let description = payload.description.or(existing_task.description);
|
||||
let status = payload.status.unwrap_or(existing_task.status);
|
||||
|
||||
match sqlx::query_as!(
|
||||
Task,
|
||||
r#"UPDATE tasks
|
||||
SET title = $3, description = $4, status = $5, updated_at = $6
|
||||
WHERE id = $1 AND project_id = $2
|
||||
RETURNING id, project_id, title, description, status as "status!: TaskStatus", created_at, updated_at"#,
|
||||
task_id,
|
||||
project_id,
|
||||
title,
|
||||
description,
|
||||
status as TaskStatus,
|
||||
now
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
{
|
||||
match Task::update(&pool, task_id, project_id, title, description, status).await {
|
||||
Ok(task) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(task),
|
||||
@@ -196,16 +128,9 @@ pub async fn delete_task(
|
||||
Path((project_id, task_id)): Path<(Uuid, Uuid)>,
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<()>>, StatusCode> {
|
||||
match sqlx::query!(
|
||||
"DELETE FROM tasks WHERE id = $1 AND project_id = $2",
|
||||
task_id,
|
||||
project_id
|
||||
)
|
||||
.execute(&pool)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
if result.rows_affected() == 0 {
|
||||
match Task::delete(&pool, task_id, project_id).await {
|
||||
Ok(rows_affected) => {
|
||||
if rows_affected == 0 {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
} else {
|
||||
Ok(ResponseJson(ApiResponse {
|
||||
@@ -229,34 +154,16 @@ pub async fn get_task_attempts(
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<Vec<TaskAttempt>>>, StatusCode> {
|
||||
// Verify task exists in project first
|
||||
let task_exists = sqlx::query!(
|
||||
"SELECT id FROM tasks WHERE id = $1 AND project_id = $2",
|
||||
task_id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
match task_exists {
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
match Task::exists(&pool, task_id, project_id).await {
|
||||
Ok(false) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to check task existence: {}", e);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
Ok(Some(_)) => {}
|
||||
Ok(true) => {}
|
||||
}
|
||||
|
||||
match sqlx::query_as!(
|
||||
TaskAttempt,
|
||||
r#"SELECT id, task_id, worktree_path, base_commit, merge_commit, created_at, updated_at
|
||||
FROM task_attempts
|
||||
WHERE task_id = $1
|
||||
ORDER BY created_at DESC"#,
|
||||
task_id
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
{
|
||||
match TaskAttempt::find_by_task_id(&pool, task_id).await {
|
||||
Ok(attempts) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(attempts),
|
||||
@@ -275,37 +182,16 @@ pub async fn get_task_attempt_activities(
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<Vec<TaskAttemptActivity>>>, StatusCode> {
|
||||
// Verify task attempt exists and belongs to the correct task
|
||||
let attempt_exists = sqlx::query!(
|
||||
"SELECT ta.id FROM task_attempts ta
|
||||
JOIN tasks t ON ta.task_id = t.id
|
||||
WHERE ta.id = $1 AND t.id = $2 AND t.project_id = $3",
|
||||
attempt_id,
|
||||
task_id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
match attempt_exists {
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
match TaskAttempt::exists_for_task(&pool, attempt_id, task_id, project_id).await {
|
||||
Ok(false) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to check task attempt existence: {}", e);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
Ok(Some(_)) => {}
|
||||
Ok(true) => {}
|
||||
}
|
||||
|
||||
match sqlx::query_as!(
|
||||
TaskAttemptActivity,
|
||||
r#"SELECT id, task_attempt_id, status as "status!: TaskAttemptStatus", note, created_at
|
||||
FROM task_attempt_activities
|
||||
WHERE task_attempt_id = $1
|
||||
ORDER BY created_at DESC"#,
|
||||
attempt_id
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
{
|
||||
match TaskAttemptActivity::find_by_attempt_id(&pool, attempt_id).await {
|
||||
Ok(activities) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(activities),
|
||||
@@ -325,59 +211,25 @@ pub async fn create_task_attempt(
|
||||
Json(mut payload): Json<CreateTaskAttempt>
|
||||
) -> Result<ResponseJson<ApiResponse<TaskAttempt>>, StatusCode> {
|
||||
// Verify task exists in project first
|
||||
let task_exists = sqlx::query!(
|
||||
"SELECT id FROM tasks WHERE id = $1 AND project_id = $2",
|
||||
task_id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
match task_exists {
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
match Task::exists(&pool, task_id, project_id).await {
|
||||
Ok(false) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to check task existence: {}", e);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
Ok(Some(_)) => {}
|
||||
Ok(true) => {}
|
||||
}
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
|
||||
// Ensure the task_id in the payload matches the path parameter
|
||||
payload.task_id = task_id;
|
||||
|
||||
match 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)
|
||||
RETURNING id, task_id, worktree_path, base_commit, merge_commit, created_at, updated_at"#,
|
||||
id,
|
||||
payload.task_id,
|
||||
payload.worktree_path,
|
||||
payload.base_commit,
|
||||
payload.merge_commit,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
{
|
||||
match TaskAttempt::create(&pool, &payload, id).await {
|
||||
Ok(attempt) => {
|
||||
// Create initial activity record
|
||||
let activity_id = Uuid::new_v4();
|
||||
let _ = sqlx::query!(
|
||||
r#"INSERT INTO task_attempt_activities (id, task_attempt_id, status, note, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5)"#,
|
||||
activity_id,
|
||||
attempt.id,
|
||||
TaskAttemptStatus::Init as TaskAttemptStatus,
|
||||
Option::<String>::None,
|
||||
now
|
||||
)
|
||||
.execute(&pool)
|
||||
.await;
|
||||
let _ = TaskAttemptActivity::create_initial(&pool, attempt.id, activity_id).await;
|
||||
|
||||
Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
@@ -399,49 +251,24 @@ pub async fn create_task_attempt_activity(
|
||||
Json(mut payload): Json<CreateTaskAttemptActivity>
|
||||
) -> Result<ResponseJson<ApiResponse<TaskAttemptActivity>>, StatusCode> {
|
||||
// Verify task attempt exists and belongs to the correct task
|
||||
let attempt_exists = sqlx::query!(
|
||||
"SELECT ta.id FROM task_attempts ta
|
||||
JOIN tasks t ON ta.task_id = t.id
|
||||
WHERE ta.id = $1 AND t.id = $2 AND t.project_id = $3",
|
||||
attempt_id,
|
||||
task_id,
|
||||
project_id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await;
|
||||
|
||||
match attempt_exists {
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
match TaskAttempt::exists_for_task(&pool, attempt_id, task_id, project_id).await {
|
||||
Ok(false) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
tracing::error!("Failed to check task attempt existence: {}", e);
|
||||
return Err(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
Ok(Some(_)) => {}
|
||||
Ok(true) => {}
|
||||
}
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
|
||||
// Ensure the task_attempt_id in the payload matches the path parameter
|
||||
payload.task_attempt_id = attempt_id;
|
||||
|
||||
// Default to Init status if not provided
|
||||
let status = payload.status.unwrap_or(TaskAttemptStatus::Init);
|
||||
let status = payload.status.clone().unwrap_or(TaskAttemptStatus::Init);
|
||||
|
||||
match sqlx::query_as!(
|
||||
TaskAttemptActivity,
|
||||
r#"INSERT INTO task_attempt_activities (id, task_attempt_id, status, note, created_at)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id, task_attempt_id, status as "status!: TaskAttemptStatus", note, created_at"#,
|
||||
id,
|
||||
payload.task_attempt_id,
|
||||
status as TaskAttemptStatus,
|
||||
payload.note,
|
||||
now
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
{
|
||||
match TaskAttemptActivity::create(&pool, &payload, id, status).await {
|
||||
Ok(activity) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(activity),
|
||||
@@ -497,12 +324,14 @@ mod tests {
|
||||
async fn create_test_project(pool: &PgPool, name: &str, owner_id: Uuid) -> Project {
|
||||
let id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
let git_repo_path = format!("/tmp/test-repo-{}", id);
|
||||
|
||||
sqlx::query_as!(
|
||||
Project,
|
||||
"INSERT INTO projects (id, name, owner_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5) RETURNING id, name, owner_id, created_at, updated_at",
|
||||
"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
|
||||
|
||||
@@ -8,7 +8,6 @@ use axum::{
|
||||
};
|
||||
use sqlx::PgPool;
|
||||
use uuid::Uuid;
|
||||
use chrono::Utc;
|
||||
|
||||
use crate::models::{ApiResponse, user::{User, CreateUser, UpdateUser, LoginRequest, LoginResponse, UserResponse}};
|
||||
use crate::auth::{AuthUser, create_token, hash_password, verify_password};
|
||||
@@ -17,14 +16,7 @@ pub async fn login(
|
||||
Extension(pool): Extension<PgPool>,
|
||||
Json(payload): Json<LoginRequest>
|
||||
) -> Result<ResponseJson<ApiResponse<LoginResponse>>, StatusCode> {
|
||||
match sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, email, password_hash, is_admin, created_at, updated_at FROM users WHERE email = $1",
|
||||
payload.email
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
{
|
||||
match User::find_by_email(&pool, &payload.email).await {
|
||||
Ok(Some(user)) => {
|
||||
match verify_password(&payload.password, &user.password_hash) {
|
||||
Ok(true) => {
|
||||
@@ -64,13 +56,7 @@ pub async fn get_users(
|
||||
_auth: AuthUser,
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<Vec<UserResponse>>>, StatusCode> {
|
||||
match sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, email, password_hash, is_admin, created_at, updated_at FROM users ORDER BY created_at DESC"
|
||||
)
|
||||
.fetch_all(&pool)
|
||||
.await
|
||||
{
|
||||
match User::find_all(&pool).await {
|
||||
Ok(users) => {
|
||||
let user_responses: Vec<UserResponse> = users.into_iter().map(|u| u.into()).collect();
|
||||
Ok(ResponseJson(ApiResponse {
|
||||
@@ -96,14 +82,7 @@ pub async fn get_user(
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
match sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, email, password_hash, is_admin, created_at, updated_at FROM users WHERE id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
{
|
||||
match User::find_by_id(&pool, id).await {
|
||||
Ok(Some(user)) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(user.into()),
|
||||
@@ -128,27 +107,13 @@ pub async fn create_user(
|
||||
}
|
||||
|
||||
let id = Uuid::new_v4();
|
||||
let now = Utc::now();
|
||||
let is_admin = payload.is_admin.unwrap_or(false);
|
||||
|
||||
let password_hash = match hash_password(&payload.password) {
|
||||
Ok(hash) => hash,
|
||||
Err(_) => return Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
};
|
||||
|
||||
match 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,
|
||||
payload.email,
|
||||
password_hash,
|
||||
is_admin,
|
||||
now,
|
||||
now
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
{
|
||||
match User::create(&pool, &payload, password_hash, id).await {
|
||||
Ok(user) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(user.into()),
|
||||
@@ -176,17 +141,8 @@ pub async fn update_user(
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
let now = Utc::now();
|
||||
|
||||
// Get existing user
|
||||
let existing_user = match sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, email, password_hash, is_admin, created_at, updated_at FROM users WHERE id = $1",
|
||||
id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
{
|
||||
let existing_user = match User::find_by_id(&pool, id).await {
|
||||
Ok(Some(user)) => user,
|
||||
Ok(None) => return Err(StatusCode::NOT_FOUND),
|
||||
Err(e) => {
|
||||
@@ -211,18 +167,7 @@ pub async fn update_user(
|
||||
existing_user.password_hash
|
||||
};
|
||||
|
||||
match sqlx::query_as!(
|
||||
User,
|
||||
"UPDATE users SET email = $2, password_hash = $3, is_admin = $4, updated_at = $5 WHERE id = $1 RETURNING id, email, password_hash, is_admin, created_at, updated_at",
|
||||
id,
|
||||
email,
|
||||
password_hash,
|
||||
is_admin,
|
||||
now
|
||||
)
|
||||
.fetch_one(&pool)
|
||||
.await
|
||||
{
|
||||
match User::update(&pool, id, email, password_hash, is_admin).await {
|
||||
Ok(user) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(user.into()),
|
||||
@@ -245,12 +190,9 @@ pub async fn delete_user(
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
match sqlx::query!("DELETE FROM users WHERE id = $1", id)
|
||||
.execute(&pool)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
if result.rows_affected() == 0 {
|
||||
match User::delete(&pool, id).await {
|
||||
Ok(rows_affected) => {
|
||||
if rows_affected == 0 {
|
||||
Err(StatusCode::NOT_FOUND)
|
||||
} else {
|
||||
Ok(ResponseJson(ApiResponse {
|
||||
@@ -271,14 +213,7 @@ pub async fn get_current_user(
|
||||
auth: AuthUser,
|
||||
Extension(pool): Extension<PgPool>
|
||||
) -> Result<ResponseJson<ApiResponse<UserResponse>>, StatusCode> {
|
||||
match sqlx::query_as!(
|
||||
User,
|
||||
"SELECT id, email, password_hash, is_admin, created_at, updated_at FROM users WHERE id = $1",
|
||||
auth.user_id
|
||||
)
|
||||
.fetch_optional(&pool)
|
||||
.await
|
||||
{
|
||||
match User::find_by_id(&pool, auth.user_id).await {
|
||||
Ok(Some(user)) => Ok(ResponseJson(ApiResponse {
|
||||
success: true,
|
||||
data: Some(user.into()),
|
||||
|
||||
Reference in New Issue
Block a user