refactor: migrate sqlx queries from macros to the .bind() method

This commit is contained in:
2026-02-25 16:23:37 -03:00
parent 5b3fc800c7
commit f36c53aed1
7 changed files with 235 additions and 254 deletions
+117 -137
View File
@@ -7,7 +7,7 @@ use common::auth::Claims;
use common::middleware::Org;
use common::models::{LessonRubric, Rubric, RubricCriterion, RubricLevel};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use sqlx::{PgPool, Row};
use uuid::Uuid;
// ==================== Payload Structs ====================
@@ -107,19 +107,18 @@ pub async fn create_rubric(
Path(course_id): Path<Uuid>,
Json(payload): Json<CreateRubricPayload>,
) -> Result<Json<Rubric>, (StatusCode, String)> {
let rubric = sqlx::query_as!(
Rubric,
let rubric: Rubric = sqlx::query_as(
r#"
INSERT INTO rubrics (organization_id, course_id, created_by, name, description)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
"#,
org_ctx.id,
payload.course_id.or(Some(course_id)),
claims.sub,
payload.name,
payload.description
"#
)
.bind(org_ctx.id)
.bind(payload.course_id.or(Some(course_id)))
.bind(claims.sub)
.bind(&payload.name)
.bind(&payload.description)
.fetch_one(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -133,17 +132,16 @@ pub async fn list_course_rubrics(
State(pool): State<PgPool>,
Path(course_id): Path<Uuid>,
) -> Result<Json<Vec<Rubric>>, (StatusCode, String)> {
let rubrics = sqlx::query_as!(
Rubric,
let rubrics: Vec<Rubric> = sqlx::query_as(
r#"
SELECT id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
FROM rubrics
WHERE organization_id = $1 AND (course_id = $2 OR course_id IS NULL)
ORDER BY created_at DESC
"#,
org_ctx.id,
course_id
"#
)
.bind(org_ctx.id)
.bind(course_id)
.fetch_all(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -158,32 +156,30 @@ pub async fn get_rubric_with_details(
Path(rubric_id): Path<Uuid>,
) -> Result<Json<RubricWithDetails>, (StatusCode, String)> {
// Get rubric
let rubric = sqlx::query_as!(
Rubric,
let rubric: Rubric = sqlx::query_as(
r#"
SELECT id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
FROM rubrics
WHERE id = $1 AND organization_id = $2
"#,
rubric_id,
org_ctx.id
"#
)
.bind(rubric_id)
.bind(org_ctx.id)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Rubric not found".to_string()))?;
// Get criteria
let criteria = sqlx::query_as!(
RubricCriterion,
let criteria: Vec<RubricCriterion> = sqlx::query_as(
r#"
SELECT id, rubric_id, name, description, max_points, position, created_at
FROM rubric_criteria
WHERE rubric_id = $1
ORDER BY position ASC
"#,
rubric_id
"#
)
.bind(rubric_id)
.fetch_all(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -191,16 +187,15 @@ pub async fn get_rubric_with_details(
// Get levels for each criterion
let mut criteria_with_levels = Vec::new();
for criterion in criteria {
let levels = sqlx::query_as!(
RubricLevel,
let levels: Vec<RubricLevel> = sqlx::query_as(
r#"
SELECT id, criterion_id, name, description, points, position, created_at
FROM rubric_levels
WHERE criterion_id = $1
ORDER BY position ASC
"#,
criterion.id
"#
)
.bind(criterion.id)
.fetch_all(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -221,8 +216,7 @@ pub async fn update_rubric(
Path(rubric_id): Path<Uuid>,
Json(payload): Json<UpdateRubricPayload>,
) -> Result<Json<Rubric>, (StatusCode, String)> {
let rubric = sqlx::query_as!(
Rubric,
let rubric: Rubric = sqlx::query_as(
r#"
UPDATE rubrics
SET name = COALESCE($1, name),
@@ -230,12 +224,12 @@ pub async fn update_rubric(
updated_at = NOW()
WHERE id = $3 AND organization_id = $4
RETURNING id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
"#,
payload.name,
payload.description,
rubric_id,
org_ctx.id
"#
)
.bind(payload.name)
.bind(payload.description)
.bind(rubric_id)
.bind(org_ctx.id)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
@@ -250,14 +244,12 @@ pub async fn delete_rubric(
State(pool): State<PgPool>,
Path(rubric_id): Path<Uuid>,
) -> Result<StatusCode, (StatusCode, String)> {
let result = sqlx::query!(
"DELETE FROM rubrics WHERE id = $1 AND organization_id = $2",
rubric_id,
org_ctx.id
)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let result = sqlx::query("DELETE FROM rubrics WHERE id = $1 AND organization_id = $2")
.bind(rubric_id)
.bind(org_ctx.id)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Rubric not found".to_string()));
@@ -276,45 +268,42 @@ pub async fn create_criterion(
Json(payload): Json<CreateCriterionPayload>,
) -> Result<Json<RubricCriterion>, (StatusCode, String)> {
// Verify rubric exists and belongs to org
let _rubric = sqlx::query!(
"SELECT id FROM rubrics WHERE id = $1 AND organization_id = $2",
rubric_id,
org_ctx.id
)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Rubric not found".to_string()))?;
let _rubric = sqlx::query("SELECT id FROM rubrics WHERE id = $1 AND organization_id = $2")
.bind(rubric_id)
.bind(org_ctx.id)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Rubric not found".to_string()))?;
let position = payload.position.unwrap_or(0);
let criterion = sqlx::query_as!(
RubricCriterion,
let criterion: RubricCriterion = sqlx::query_as(
r#"
INSERT INTO rubric_criteria (rubric_id, name, description, max_points, position)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, rubric_id, name, description, max_points, position, created_at
"#,
rubric_id,
payload.name,
payload.description,
payload.max_points,
position
"#
)
.bind(rubric_id)
.bind(&payload.name)
.bind(&payload.description)
.bind(payload.max_points)
.bind(position)
.fetch_one(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
// Update rubric total_points
let _= sqlx::query!(
let _= sqlx::query(
r#"
UPDATE rubrics
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
updated_at = NOW()
WHERE id = $1
"#,
rubric_id
"#
)
.bind(rubric_id)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -329,8 +318,7 @@ pub async fn update_criterion(
Path(criterion_id): Path<Uuid>,
Json(payload): Json<UpdateCriterionPayload>,
) -> Result<Json<RubricCriterion>, (StatusCode, String)> {
let criterion = sqlx::query_as!(
RubricCriterion,
let criterion: RubricCriterion = sqlx::query_as(
r#"
UPDATE rubric_criteria
SET name = COALESCE($1, name),
@@ -340,14 +328,14 @@ pub async fn update_criterion(
WHERE id = $5
AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $6)
RETURNING id, rubric_id, name, description, max_points, position, created_at
"#,
payload.name,
payload.description,
payload.max_points,
payload.position,
criterion_id,
org_ctx.id
"#
)
.bind(payload.name)
.bind(payload.description)
.bind(payload.max_points)
.bind(payload.position)
.bind(criterion_id)
.bind(org_ctx.id)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
@@ -355,15 +343,15 @@ pub async fn update_criterion(
// Update rubric total_points if max_points changed
if payload.max_points.is_some() {
let _ = sqlx::query!(
let _ = sqlx::query(
r#"
UPDATE rubrics
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
updated_at = NOW()
WHERE id = $1
"#,
criterion.rubric_id
"#
)
.bind(criterion.rubric_id)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -379,24 +367,24 @@ pub async fn delete_criterion(
Path(criterion_id): Path<Uuid>,
) -> Result<StatusCode, (StatusCode, String)> {
// Get rubric_id before deleting
let criterion = sqlx::query!(
"SELECT rubric_id FROM rubric_criteria WHERE id = $1",
criterion_id
)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Criterion not found".to_string()))?;
let criterion_row = sqlx::query("SELECT rubric_id FROM rubric_criteria WHERE id = $1")
.bind(criterion_id)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Criterion not found".to_string()))?;
let rubric_id: Uuid = criterion_row.get("rubric_id");
let result = sqlx::query!(
let result = sqlx::query(
r#"
DELETE FROM rubric_criteria
WHERE id = $1
AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $2)
"#,
criterion_id,
org_ctx.id
"#
)
.bind(criterion_id)
.bind(org_ctx.id)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -406,15 +394,15 @@ pub async fn delete_criterion(
}
// Update rubric total_points
let _ = sqlx::query!(
let _ = sqlx::query(
r#"
UPDATE rubrics
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
updated_at = NOW()
WHERE id = $1
"#,
criterion.rubric_id
"#
)
.bind(rubric_id)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -432,31 +420,28 @@ pub async fn create_level(
Json(payload): Json<CreateLevelPayload>,
) -> Result<Json<RubricLevel>, (StatusCode, String)> {
// Verify criterion exists and belongs to org
let _criterion = sqlx::query!(
"SELECT id FROM rubric_criteria WHERE id = $1 AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $2)",
criterion_id,
org_ctx.id
)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Criterion not found".to_string()))?;
let _criterion = sqlx::query("SELECT id FROM rubric_criteria WHERE id = $1 AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $2)")
.bind(criterion_id)
.bind(org_ctx.id)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
.ok_or((StatusCode::NOT_FOUND, "Criterion not found".to_string()))?;
let position = payload.position.unwrap_or(0);
let level = sqlx::query_as!(
RubricLevel,
let level: RubricLevel = sqlx::query_as(
r#"
INSERT INTO rubric_levels (criterion_id, name, description, points, position)
VALUES ($1, $2, $3, $4, $5)
RETURNING id, criterion_id, name, description, points, position, created_at
"#,
criterion_id,
payload.name,
payload.description,
payload.points,
position
"#
)
.bind(criterion_id)
.bind(&payload.name)
.bind(&payload.description)
.bind(payload.points)
.bind(position)
.fetch_one(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -471,8 +456,7 @@ pub async fn update_level(
Path(level_id): Path<Uuid>,
Json(payload): Json<UpdateLevelPayload>,
) -> Result<Json<RubricLevel>, (StatusCode, String)> {
let level = sqlx::query_as!(
RubricLevel,
let level: RubricLevel = sqlx::query_as(
r#"
UPDATE rubric_levels
SET name = COALESCE($1, name),
@@ -485,14 +469,14 @@ pub async fn update_level(
WHERE rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $6)
)
RETURNING id, criterion_id, name, description, points, position, created_at
"#,
payload.name,
payload.description,
payload.points,
payload.position,
level_id,
org_ctx.id
"#
)
.bind(payload.name)
.bind(payload.description)
.bind(payload.points)
.bind(payload.position)
.bind(level_id)
.bind(org_ctx.id)
.fetch_optional(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
@@ -507,7 +491,7 @@ pub async fn delete_level(
State(pool): State<PgPool>,
Path(level_id): Path<Uuid>,
) -> Result<StatusCode, (StatusCode, String)> {
let result = sqlx::query!(
let result = sqlx::query(
r#"
DELETE FROM rubric_levels
WHERE id = $1
@@ -515,10 +499,10 @@ pub async fn delete_level(
SELECT id FROM rubric_criteria
WHERE rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $2)
)
"#,
level_id,
org_ctx.id
"#
)
.bind(level_id)
.bind(org_ctx.id)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -538,17 +522,16 @@ pub async fn assign_rubric_to_lesson(
State(pool): State<PgPool>,
Path((lesson_id, rubric_id)): Path<(Uuid, Uuid)>,
) -> Result<Json<LessonRubric>, (StatusCode, String)> {
let lesson_rubric = sqlx::query_as!(
LessonRubric,
let lesson_rubric: LessonRubric = sqlx::query_as(
r#"
INSERT INTO lesson_rubrics (lesson_id, rubric_id, is_active)
VALUES ($1, $2, true)
ON CONFLICT (lesson_id, rubric_id) DO UPDATE SET is_active = true
RETURNING id, lesson_id, rubric_id, is_active, assigned_at
"#,
lesson_id,
rubric_id
"#
)
.bind(lesson_id)
.bind(rubric_id)
.fetch_one(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
@@ -562,14 +545,12 @@ pub async fn unassign_rubric_from_lesson(
State(pool): State<PgPool>,
Path((lesson_id, rubric_id)): Path<(Uuid, Uuid)>,
) -> Result<StatusCode, (StatusCode, String)> {
let result = sqlx::query!(
"DELETE FROM lesson_rubrics WHERE lesson_id = $1 AND rubric_id = $2",
lesson_id,
rubric_id
)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let result = sqlx::query("DELETE FROM lesson_rubrics WHERE lesson_id = $1 AND rubric_id = $2")
.bind(lesson_id)
.bind(rubric_id)
.execute(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
if result.rows_affected() == 0 {
return Err((StatusCode::NOT_FOUND, "Lesson rubric not found".to_string()));
@@ -584,18 +565,17 @@ pub async fn get_lesson_rubrics(
State(pool): State<PgPool>,
Path(lesson_id): Path<Uuid>,
) -> Result<Json<Vec<Rubric>>, (StatusCode, String)> {
let rubrics = sqlx::query_as!(
Rubric,
let rubrics: Vec<Rubric> = sqlx::query_as(
r#"
SELECT r.id, r.organization_id, r.course_id, r.created_by, r.name, r.description, r.total_points, r.created_at, r.updated_at
FROM rubrics r
INNER JOIN lesson_rubrics lr ON lr.rubric_id = r.id
WHERE lr.lesson_id = $1 AND lr.is_active = true AND r.organization_id = $2
ORDER BY lr.assigned_at DESC
"#,
lesson_id,
org_ctx.id
"#
)
.bind(lesson_id)
.bind(org_ctx.id)
.fetch_all(&pool)
.await
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;