refactor: migrate sqlx queries from macros to the .bind() method
This commit is contained in:
@@ -95,20 +95,20 @@ pub async fn upload_asset(
|
|||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
|
|
||||||
// Record in DB
|
// Record in DB
|
||||||
sqlx::query!(
|
sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO assets (id, organization_id, uploaded_by, course_id, filename, storage_path, mimetype, size_bytes)
|
INSERT INTO assets (id, organization_id, uploaded_by, course_id, filename, storage_path, mimetype, size_bytes)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||||
"#,
|
"#,
|
||||||
asset_id,
|
|
||||||
org_ctx.id,
|
|
||||||
claims.sub,
|
|
||||||
course_id,
|
|
||||||
filename,
|
|
||||||
storage_path,
|
|
||||||
mimetype,
|
|
||||||
size_bytes
|
|
||||||
)
|
)
|
||||||
|
.bind(asset_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
|
.bind(claims.sub)
|
||||||
|
.bind(course_id)
|
||||||
|
.bind(&filename)
|
||||||
|
.bind(&storage_path)
|
||||||
|
.bind(&mimetype)
|
||||||
|
.bind(size_bytes)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -182,19 +182,19 @@ pub async fn delete_asset(
|
|||||||
Path(id): Path<Uuid>,
|
Path(id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, (StatusCode, String)> {
|
) -> Result<StatusCode, (StatusCode, String)> {
|
||||||
// 1. Get asset metadata to find file path
|
// 1. Get asset metadata to find file path
|
||||||
let asset = sqlx::query_as!(
|
let asset: Asset = sqlx::query_as(
|
||||||
Asset,
|
"SELECT * FROM assets WHERE id = $1 AND organization_id = $2"
|
||||||
"SELECT * FROM assets WHERE id = $1 AND organization_id = $2",
|
|
||||||
id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.fetch_optional(&pool)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
.ok_or((StatusCode::NOT_FOUND, "Asset not found".to_string()))?;
|
.ok_or((StatusCode::NOT_FOUND, "Asset not found".to_string()))?;
|
||||||
|
|
||||||
// 2. Delete from DB
|
// 2. Delete from DB
|
||||||
sqlx::query!("DELETE FROM assets WHERE id = $1", id)
|
sqlx::query("DELETE FROM assets WHERE id = $1")
|
||||||
|
.bind(id)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|||||||
@@ -37,20 +37,19 @@ pub async fn assign_dependency(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. Insertar la dependencia
|
// 2. Insertar la dependencia
|
||||||
let dependency = sqlx::query_as!(
|
let dependency: LessonDependency = sqlx::query_as(
|
||||||
LessonDependency,
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO lesson_dependencies (organization_id, lesson_id, prerequisite_lesson_id, min_score_percentage)
|
INSERT INTO lesson_dependencies (organization_id, lesson_id, prerequisite_lesson_id, min_score_percentage)
|
||||||
VALUES ($1, $2, $3, $4)
|
VALUES ($1, $2, $3, $4)
|
||||||
ON CONFLICT (lesson_id, prerequisite_lesson_id)
|
ON CONFLICT (lesson_id, prerequisite_lesson_id)
|
||||||
DO UPDATE SET min_score_percentage = EXCLUDED.min_score_percentage
|
DO UPDATE SET min_score_percentage = EXCLUDED.min_score_percentage
|
||||||
RETURNING id, organization_id, lesson_id, prerequisite_lesson_id, min_score_percentage, created_at
|
RETURNING id, organization_id, lesson_id, prerequisite_lesson_id, min_score_percentage, created_at
|
||||||
"#,
|
"#
|
||||||
org_ctx.id,
|
|
||||||
lesson_id,
|
|
||||||
payload.prerequisite_lesson_id,
|
|
||||||
payload.min_score_percentage
|
|
||||||
)
|
)
|
||||||
|
.bind(org_ctx.id)
|
||||||
|
.bind(lesson_id)
|
||||||
|
.bind(payload.prerequisite_lesson_id)
|
||||||
|
.bind(payload.min_score_percentage)
|
||||||
.fetch_one(&pool)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
@@ -66,12 +65,12 @@ pub async fn remove_dependency(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Path((lesson_id, prerequisite_id)): Path<(Uuid, Uuid)>,
|
Path((lesson_id, prerequisite_id)): Path<(Uuid, Uuid)>,
|
||||||
) -> Result<StatusCode, StatusCode> {
|
) -> Result<StatusCode, StatusCode> {
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query(
|
||||||
"DELETE FROM lesson_dependencies WHERE lesson_id = $1 AND prerequisite_lesson_id = $2 AND organization_id = $3",
|
"DELETE FROM lesson_dependencies WHERE lesson_id = $1 AND prerequisite_lesson_id = $2 AND organization_id = $3"
|
||||||
lesson_id,
|
|
||||||
prerequisite_id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(lesson_id)
|
||||||
|
.bind(prerequisite_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
@@ -88,12 +87,11 @@ pub async fn list_lesson_dependencies(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Path(lesson_id): Path<Uuid>,
|
Path(lesson_id): Path<Uuid>,
|
||||||
) -> Result<Json<Vec<LessonDependency>>, StatusCode> {
|
) -> Result<Json<Vec<LessonDependency>>, StatusCode> {
|
||||||
let dependencies = sqlx::query_as!(
|
let dependencies: Vec<LessonDependency> = sqlx::query_as(
|
||||||
LessonDependency,
|
"SELECT * FROM lesson_dependencies WHERE lesson_id = $1 AND organization_id = $2"
|
||||||
"SELECT * FROM lesson_dependencies WHERE lesson_id = $1 AND organization_id = $2",
|
|
||||||
lesson_id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(lesson_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
||||||
|
|||||||
@@ -24,21 +24,20 @@ pub async fn create_library_block(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Json(payload): Json<CreateLibraryBlockPayload>,
|
Json(payload): Json<CreateLibraryBlockPayload>,
|
||||||
) -> Result<Json<LibraryBlock>, (StatusCode, String)> {
|
) -> Result<Json<LibraryBlock>, (StatusCode, String)> {
|
||||||
let block = sqlx::query_as!(
|
let block: LibraryBlock = sqlx::query_as(
|
||||||
LibraryBlock,
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO library_blocks (organization_id, created_by, name, description, block_type, block_data, tags)
|
INSERT INTO library_blocks (organization_id, created_by, name, description, block_type, block_data, tags)
|
||||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||||
RETURNING id, organization_id, created_by, name, description, block_type, block_data, tags, usage_count as "usage_count!", created_at, updated_at
|
RETURNING id, organization_id, created_by, name, description, block_type, block_data, tags, usage_count, created_at, updated_at
|
||||||
"#,
|
"#
|
||||||
org_ctx.id,
|
|
||||||
claims.sub,
|
|
||||||
payload.name,
|
|
||||||
payload.description,
|
|
||||||
payload.block_type,
|
|
||||||
payload.block_data,
|
|
||||||
payload.tags.as_deref()
|
|
||||||
)
|
)
|
||||||
|
.bind(org_ctx.id)
|
||||||
|
.bind(claims.sub)
|
||||||
|
.bind(&payload.name)
|
||||||
|
.bind(&payload.description)
|
||||||
|
.bind(&payload.block_type)
|
||||||
|
.bind(&payload.block_data)
|
||||||
|
.bind(payload.tags.as_deref())
|
||||||
.fetch_one(&pool)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -110,12 +109,11 @@ pub async fn get_library_block(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Path(block_id): Path<Uuid>,
|
Path(block_id): Path<Uuid>,
|
||||||
) -> Result<Json<LibraryBlock>, (StatusCode, String)> {
|
) -> Result<Json<LibraryBlock>, (StatusCode, String)> {
|
||||||
let block = sqlx::query_as!(
|
let block: Option<LibraryBlock> = sqlx::query_as(
|
||||||
LibraryBlock,
|
r#"SELECT id, organization_id, created_by, name, description, block_type, block_data, tags, usage_count, created_at, updated_at FROM library_blocks WHERE id = $1 AND organization_id = $2"#
|
||||||
r#"SELECT id, organization_id, created_by, name, description, block_type, block_data, tags, usage_count as "usage_count!", created_at, updated_at FROM library_blocks WHERE id = $1 AND organization_id = $2"#,
|
|
||||||
block_id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(block_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.fetch_optional(&pool)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -134,11 +132,9 @@ pub async fn update_library_block(
|
|||||||
Json(payload): Json<UpdateLibraryBlockPayload>,
|
Json(payload): Json<UpdateLibraryBlockPayload>,
|
||||||
) -> Result<Json<LibraryBlock>, (StatusCode, String)> {
|
) -> Result<Json<LibraryBlock>, (StatusCode, String)> {
|
||||||
// Verificar que el bloque existe y pertenece a la org
|
// Verificar que el bloque existe y pertenece a la org
|
||||||
let existing = sqlx::query!(
|
let existing = sqlx::query("SELECT id FROM library_blocks WHERE id = $1 AND organization_id = $2")
|
||||||
"SELECT id FROM library_blocks WHERE id = $1 AND organization_id = $2",
|
.bind(block_id)
|
||||||
block_id,
|
.bind(org_ctx.id)
|
||||||
org_ctx.id
|
|
||||||
)
|
|
||||||
.fetch_optional(&pool)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -149,8 +145,7 @@ pub async fn update_library_block(
|
|||||||
|
|
||||||
// Update dinámico basado en campos provistos
|
// Update dinámico basado en campos provistos
|
||||||
let updated = if let Some(name) = &payload.name {
|
let updated = if let Some(name) = &payload.name {
|
||||||
sqlx::query_as!(
|
sqlx::query_as(
|
||||||
LibraryBlock,
|
|
||||||
r#"
|
r#"
|
||||||
UPDATE library_blocks
|
UPDATE library_blocks
|
||||||
SET name = COALESCE($1, name),
|
SET name = COALESCE($1, name),
|
||||||
@@ -158,33 +153,32 @@ pub async fn update_library_block(
|
|||||||
tags = COALESCE($3, tags),
|
tags = COALESCE($3, tags),
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $4 AND organization_id = $5
|
WHERE id = $4 AND organization_id = $5
|
||||||
RETURNING id, organization_id, created_by, name, description, block_type, block_data, tags, usage_count as "usage_count!", created_at, updated_at
|
RETURNING id, organization_id, created_by, name, description, block_type, block_data, tags, usage_count, created_at, updated_at
|
||||||
"#,
|
"#
|
||||||
Some(name),
|
|
||||||
payload.description,
|
|
||||||
payload.tags.as_deref(),
|
|
||||||
block_id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(Some(name))
|
||||||
|
.bind(payload.description)
|
||||||
|
.bind(payload.tags.as_deref())
|
||||||
|
.bind(block_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.fetch_one(&pool)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
} else {
|
} else {
|
||||||
sqlx::query_as!(
|
sqlx::query_as(
|
||||||
LibraryBlock,
|
|
||||||
r#"
|
r#"
|
||||||
UPDATE library_blocks
|
UPDATE library_blocks
|
||||||
SET description = COALESCE($1, description),
|
SET description = COALESCE($1, description),
|
||||||
tags = COALESCE($2, tags),
|
tags = COALESCE($2, tags),
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $3 AND organization_id = $4
|
WHERE id = $3 AND organization_id = $4
|
||||||
RETURNING id, organization_id, created_by, name, description, block_type, block_data, tags, usage_count as "usage_count!", created_at, updated_at
|
RETURNING id, organization_id, created_by, name, description, block_type, block_data, tags, usage_count, created_at, updated_at
|
||||||
"#,
|
"#
|
||||||
payload.description,
|
|
||||||
payload.tags.as_deref(),
|
|
||||||
block_id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(payload.description)
|
||||||
|
.bind(payload.tags.as_deref())
|
||||||
|
.bind(block_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.fetch_one(&pool)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
@@ -199,11 +193,9 @@ pub async fn delete_library_block(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Path(block_id): Path<Uuid>,
|
Path(block_id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, (StatusCode, String)> {
|
) -> Result<StatusCode, (StatusCode, String)> {
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query("DELETE FROM library_blocks WHERE id = $1 AND organization_id = $2")
|
||||||
"DELETE FROM library_blocks WHERE id = $1 AND organization_id = $2",
|
.bind(block_id)
|
||||||
block_id,
|
.bind(org_ctx.id)
|
||||||
org_ctx.id
|
|
||||||
)
|
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -221,11 +213,9 @@ pub async fn increment_block_usage(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Path(block_id): Path<Uuid>,
|
Path(block_id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, (StatusCode, String)> {
|
) -> Result<StatusCode, (StatusCode, String)> {
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query("UPDATE library_blocks SET usage_count = usage_count + 1 WHERE id = $1 AND organization_id = $2")
|
||||||
"UPDATE library_blocks SET usage_count = usage_count + 1 WHERE id = $1 AND organization_id = $2",
|
.bind(block_id)
|
||||||
block_id,
|
.bind(org_ctx.id)
|
||||||
org_ctx.id
|
|
||||||
)
|
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ use common::auth::Claims;
|
|||||||
use common::middleware::Org;
|
use common::middleware::Org;
|
||||||
use common::models::{LessonRubric, Rubric, RubricCriterion, RubricLevel};
|
use common::models::{LessonRubric, Rubric, RubricCriterion, RubricLevel};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::{PgPool, Row};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
// ==================== Payload Structs ====================
|
// ==================== Payload Structs ====================
|
||||||
@@ -107,19 +107,18 @@ pub async fn create_rubric(
|
|||||||
Path(course_id): Path<Uuid>,
|
Path(course_id): Path<Uuid>,
|
||||||
Json(payload): Json<CreateRubricPayload>,
|
Json(payload): Json<CreateRubricPayload>,
|
||||||
) -> Result<Json<Rubric>, (StatusCode, String)> {
|
) -> Result<Json<Rubric>, (StatusCode, String)> {
|
||||||
let rubric = sqlx::query_as!(
|
let rubric: Rubric = sqlx::query_as(
|
||||||
Rubric,
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO rubrics (organization_id, course_id, created_by, name, description)
|
INSERT INTO rubrics (organization_id, course_id, created_by, name, description)
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
RETURNING id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
|
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)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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>,
|
State(pool): State<PgPool>,
|
||||||
Path(course_id): Path<Uuid>,
|
Path(course_id): Path<Uuid>,
|
||||||
) -> Result<Json<Vec<Rubric>>, (StatusCode, String)> {
|
) -> Result<Json<Vec<Rubric>>, (StatusCode, String)> {
|
||||||
let rubrics = sqlx::query_as!(
|
let rubrics: Vec<Rubric> = sqlx::query_as(
|
||||||
Rubric,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
|
SELECT id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
|
||||||
FROM rubrics
|
FROM rubrics
|
||||||
WHERE organization_id = $1 AND (course_id = $2 OR course_id IS NULL)
|
WHERE organization_id = $1 AND (course_id = $2 OR course_id IS NULL)
|
||||||
ORDER BY created_at DESC
|
ORDER BY created_at DESC
|
||||||
"#,
|
"#
|
||||||
org_ctx.id,
|
|
||||||
course_id
|
|
||||||
)
|
)
|
||||||
|
.bind(org_ctx.id)
|
||||||
|
.bind(course_id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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>,
|
Path(rubric_id): Path<Uuid>,
|
||||||
) -> Result<Json<RubricWithDetails>, (StatusCode, String)> {
|
) -> Result<Json<RubricWithDetails>, (StatusCode, String)> {
|
||||||
// Get rubric
|
// Get rubric
|
||||||
let rubric = sqlx::query_as!(
|
let rubric: Rubric = sqlx::query_as(
|
||||||
Rubric,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
|
SELECT id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
|
||||||
FROM rubrics
|
FROM rubrics
|
||||||
WHERE id = $1 AND organization_id = $2
|
WHERE id = $1 AND organization_id = $2
|
||||||
"#,
|
"#
|
||||||
rubric_id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(rubric_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.fetch_optional(&pool)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
.ok_or((StatusCode::NOT_FOUND, "Rubric not found".to_string()))?;
|
.ok_or((StatusCode::NOT_FOUND, "Rubric not found".to_string()))?;
|
||||||
|
|
||||||
// Get criteria
|
// Get criteria
|
||||||
let criteria = sqlx::query_as!(
|
let criteria: Vec<RubricCriterion> = sqlx::query_as(
|
||||||
RubricCriterion,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, rubric_id, name, description, max_points, position, created_at
|
SELECT id, rubric_id, name, description, max_points, position, created_at
|
||||||
FROM rubric_criteria
|
FROM rubric_criteria
|
||||||
WHERE rubric_id = $1
|
WHERE rubric_id = $1
|
||||||
ORDER BY position ASC
|
ORDER BY position ASC
|
||||||
"#,
|
"#
|
||||||
rubric_id
|
|
||||||
)
|
)
|
||||||
|
.bind(rubric_id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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
|
// Get levels for each criterion
|
||||||
let mut criteria_with_levels = Vec::new();
|
let mut criteria_with_levels = Vec::new();
|
||||||
for criterion in criteria {
|
for criterion in criteria {
|
||||||
let levels = sqlx::query_as!(
|
let levels: Vec<RubricLevel> = sqlx::query_as(
|
||||||
RubricLevel,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT id, criterion_id, name, description, points, position, created_at
|
SELECT id, criterion_id, name, description, points, position, created_at
|
||||||
FROM rubric_levels
|
FROM rubric_levels
|
||||||
WHERE criterion_id = $1
|
WHERE criterion_id = $1
|
||||||
ORDER BY position ASC
|
ORDER BY position ASC
|
||||||
"#,
|
"#
|
||||||
criterion.id
|
|
||||||
)
|
)
|
||||||
|
.bind(criterion.id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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>,
|
Path(rubric_id): Path<Uuid>,
|
||||||
Json(payload): Json<UpdateRubricPayload>,
|
Json(payload): Json<UpdateRubricPayload>,
|
||||||
) -> Result<Json<Rubric>, (StatusCode, String)> {
|
) -> Result<Json<Rubric>, (StatusCode, String)> {
|
||||||
let rubric = sqlx::query_as!(
|
let rubric: Rubric = sqlx::query_as(
|
||||||
Rubric,
|
|
||||||
r#"
|
r#"
|
||||||
UPDATE rubrics
|
UPDATE rubrics
|
||||||
SET name = COALESCE($1, name),
|
SET name = COALESCE($1, name),
|
||||||
@@ -230,12 +224,12 @@ pub async fn update_rubric(
|
|||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $3 AND organization_id = $4
|
WHERE id = $3 AND organization_id = $4
|
||||||
RETURNING id, organization_id, course_id, created_by, name, description, total_points, created_at, updated_at
|
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)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
@@ -250,11 +244,9 @@ pub async fn delete_rubric(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Path(rubric_id): Path<Uuid>,
|
Path(rubric_id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, (StatusCode, String)> {
|
) -> Result<StatusCode, (StatusCode, String)> {
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query("DELETE FROM rubrics WHERE id = $1 AND organization_id = $2")
|
||||||
"DELETE FROM rubrics WHERE id = $1 AND organization_id = $2",
|
.bind(rubric_id)
|
||||||
rubric_id,
|
.bind(org_ctx.id)
|
||||||
org_ctx.id
|
|
||||||
)
|
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -276,11 +268,9 @@ pub async fn create_criterion(
|
|||||||
Json(payload): Json<CreateCriterionPayload>,
|
Json(payload): Json<CreateCriterionPayload>,
|
||||||
) -> Result<Json<RubricCriterion>, (StatusCode, String)> {
|
) -> Result<Json<RubricCriterion>, (StatusCode, String)> {
|
||||||
// Verify rubric exists and belongs to org
|
// Verify rubric exists and belongs to org
|
||||||
let _rubric = sqlx::query!(
|
let _rubric = sqlx::query("SELECT id FROM rubrics WHERE id = $1 AND organization_id = $2")
|
||||||
"SELECT id FROM rubrics WHERE id = $1 AND organization_id = $2",
|
.bind(rubric_id)
|
||||||
rubric_id,
|
.bind(org_ctx.id)
|
||||||
org_ctx.id
|
|
||||||
)
|
|
||||||
.fetch_optional(&pool)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
@@ -288,33 +278,32 @@ pub async fn create_criterion(
|
|||||||
|
|
||||||
let position = payload.position.unwrap_or(0);
|
let position = payload.position.unwrap_or(0);
|
||||||
|
|
||||||
let criterion = sqlx::query_as!(
|
let criterion: RubricCriterion = sqlx::query_as(
|
||||||
RubricCriterion,
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO rubric_criteria (rubric_id, name, description, max_points, position)
|
INSERT INTO rubric_criteria (rubric_id, name, description, max_points, position)
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
RETURNING id, rubric_id, name, description, max_points, position, created_at
|
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)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|
||||||
// Update rubric total_points
|
// Update rubric total_points
|
||||||
let _= sqlx::query!(
|
let _= sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
UPDATE rubrics
|
UPDATE rubrics
|
||||||
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
|
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
"#,
|
"#
|
||||||
rubric_id
|
|
||||||
)
|
)
|
||||||
|
.bind(rubric_id)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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>,
|
Path(criterion_id): Path<Uuid>,
|
||||||
Json(payload): Json<UpdateCriterionPayload>,
|
Json(payload): Json<UpdateCriterionPayload>,
|
||||||
) -> Result<Json<RubricCriterion>, (StatusCode, String)> {
|
) -> Result<Json<RubricCriterion>, (StatusCode, String)> {
|
||||||
let criterion = sqlx::query_as!(
|
let criterion: RubricCriterion = sqlx::query_as(
|
||||||
RubricCriterion,
|
|
||||||
r#"
|
r#"
|
||||||
UPDATE rubric_criteria
|
UPDATE rubric_criteria
|
||||||
SET name = COALESCE($1, name),
|
SET name = COALESCE($1, name),
|
||||||
@@ -340,14 +328,14 @@ pub async fn update_criterion(
|
|||||||
WHERE id = $5
|
WHERE id = $5
|
||||||
AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $6)
|
AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $6)
|
||||||
RETURNING id, rubric_id, name, description, max_points, position, created_at
|
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)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.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
|
// Update rubric total_points if max_points changed
|
||||||
if payload.max_points.is_some() {
|
if payload.max_points.is_some() {
|
||||||
let _ = sqlx::query!(
|
let _ = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
UPDATE rubrics
|
UPDATE rubrics
|
||||||
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
|
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
"#,
|
"#
|
||||||
criterion.rubric_id
|
|
||||||
)
|
)
|
||||||
|
.bind(criterion.rubric_id)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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>,
|
Path(criterion_id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, (StatusCode, String)> {
|
) -> Result<StatusCode, (StatusCode, String)> {
|
||||||
// Get rubric_id before deleting
|
// Get rubric_id before deleting
|
||||||
let criterion = sqlx::query!(
|
let criterion_row = sqlx::query("SELECT rubric_id FROM rubric_criteria WHERE id = $1")
|
||||||
"SELECT rubric_id FROM rubric_criteria WHERE id = $1",
|
.bind(criterion_id)
|
||||||
criterion_id
|
|
||||||
)
|
|
||||||
.fetch_optional(&pool)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
.ok_or((StatusCode::NOT_FOUND, "Criterion not found".to_string()))?;
|
.ok_or((StatusCode::NOT_FOUND, "Criterion not found".to_string()))?;
|
||||||
|
|
||||||
let result = sqlx::query!(
|
let rubric_id: Uuid = criterion_row.get("rubric_id");
|
||||||
|
|
||||||
|
let result = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
DELETE FROM rubric_criteria
|
DELETE FROM rubric_criteria
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $2)
|
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)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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
|
// Update rubric total_points
|
||||||
let _ = sqlx::query!(
|
let _ = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
UPDATE rubrics
|
UPDATE rubrics
|
||||||
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
|
SET total_points = (SELECT COALESCE(SUM(max_points), 0) FROM rubric_criteria WHERE rubric_id = $1),
|
||||||
updated_at = NOW()
|
updated_at = NOW()
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
"#,
|
"#
|
||||||
criterion.rubric_id
|
|
||||||
)
|
)
|
||||||
|
.bind(rubric_id)
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -432,11 +420,9 @@ pub async fn create_level(
|
|||||||
Json(payload): Json<CreateLevelPayload>,
|
Json(payload): Json<CreateLevelPayload>,
|
||||||
) -> Result<Json<RubricLevel>, (StatusCode, String)> {
|
) -> Result<Json<RubricLevel>, (StatusCode, String)> {
|
||||||
// Verify criterion exists and belongs to org
|
// Verify criterion exists and belongs to org
|
||||||
let _criterion = sqlx::query!(
|
let _criterion = sqlx::query("SELECT id FROM rubric_criteria WHERE id = $1 AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $2)")
|
||||||
"SELECT id FROM rubric_criteria WHERE id = $1 AND rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $2)",
|
.bind(criterion_id)
|
||||||
criterion_id,
|
.bind(org_ctx.id)
|
||||||
org_ctx.id
|
|
||||||
)
|
|
||||||
.fetch_optional(&pool)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
||||||
@@ -444,19 +430,18 @@ pub async fn create_level(
|
|||||||
|
|
||||||
let position = payload.position.unwrap_or(0);
|
let position = payload.position.unwrap_or(0);
|
||||||
|
|
||||||
let level = sqlx::query_as!(
|
let level: RubricLevel = sqlx::query_as(
|
||||||
RubricLevel,
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO rubric_levels (criterion_id, name, description, points, position)
|
INSERT INTO rubric_levels (criterion_id, name, description, points, position)
|
||||||
VALUES ($1, $2, $3, $4, $5)
|
VALUES ($1, $2, $3, $4, $5)
|
||||||
RETURNING id, criterion_id, name, description, points, position, created_at
|
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)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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>,
|
Path(level_id): Path<Uuid>,
|
||||||
Json(payload): Json<UpdateLevelPayload>,
|
Json(payload): Json<UpdateLevelPayload>,
|
||||||
) -> Result<Json<RubricLevel>, (StatusCode, String)> {
|
) -> Result<Json<RubricLevel>, (StatusCode, String)> {
|
||||||
let level = sqlx::query_as!(
|
let level: RubricLevel = sqlx::query_as(
|
||||||
RubricLevel,
|
|
||||||
r#"
|
r#"
|
||||||
UPDATE rubric_levels
|
UPDATE rubric_levels
|
||||||
SET name = COALESCE($1, name),
|
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)
|
WHERE rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $6)
|
||||||
)
|
)
|
||||||
RETURNING id, criterion_id, name, description, points, position, created_at
|
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)
|
.fetch_optional(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?
|
.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>,
|
State(pool): State<PgPool>,
|
||||||
Path(level_id): Path<Uuid>,
|
Path(level_id): Path<Uuid>,
|
||||||
) -> Result<StatusCode, (StatusCode, String)> {
|
) -> Result<StatusCode, (StatusCode, String)> {
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query(
|
||||||
r#"
|
r#"
|
||||||
DELETE FROM rubric_levels
|
DELETE FROM rubric_levels
|
||||||
WHERE id = $1
|
WHERE id = $1
|
||||||
@@ -515,10 +499,10 @@ pub async fn delete_level(
|
|||||||
SELECT id FROM rubric_criteria
|
SELECT id FROM rubric_criteria
|
||||||
WHERE rubric_id IN (SELECT id FROM rubrics WHERE organization_id = $2)
|
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)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.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>,
|
State(pool): State<PgPool>,
|
||||||
Path((lesson_id, rubric_id)): Path<(Uuid, Uuid)>,
|
Path((lesson_id, rubric_id)): Path<(Uuid, Uuid)>,
|
||||||
) -> Result<Json<LessonRubric>, (StatusCode, String)> {
|
) -> Result<Json<LessonRubric>, (StatusCode, String)> {
|
||||||
let lesson_rubric = sqlx::query_as!(
|
let lesson_rubric: LessonRubric = sqlx::query_as(
|
||||||
LessonRubric,
|
|
||||||
r#"
|
r#"
|
||||||
INSERT INTO lesson_rubrics (lesson_id, rubric_id, is_active)
|
INSERT INTO lesson_rubrics (lesson_id, rubric_id, is_active)
|
||||||
VALUES ($1, $2, true)
|
VALUES ($1, $2, true)
|
||||||
ON CONFLICT (lesson_id, rubric_id) DO UPDATE SET is_active = true
|
ON CONFLICT (lesson_id, rubric_id) DO UPDATE SET is_active = true
|
||||||
RETURNING id, lesson_id, rubric_id, is_active, assigned_at
|
RETURNING id, lesson_id, rubric_id, is_active, assigned_at
|
||||||
"#,
|
"#
|
||||||
lesson_id,
|
|
||||||
rubric_id
|
|
||||||
)
|
)
|
||||||
|
.bind(lesson_id)
|
||||||
|
.bind(rubric_id)
|
||||||
.fetch_one(&pool)
|
.fetch_one(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -562,11 +545,9 @@ pub async fn unassign_rubric_from_lesson(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Path((lesson_id, rubric_id)): Path<(Uuid, Uuid)>,
|
Path((lesson_id, rubric_id)): Path<(Uuid, Uuid)>,
|
||||||
) -> Result<StatusCode, (StatusCode, String)> {
|
) -> Result<StatusCode, (StatusCode, String)> {
|
||||||
let result = sqlx::query!(
|
let result = sqlx::query("DELETE FROM lesson_rubrics WHERE lesson_id = $1 AND rubric_id = $2")
|
||||||
"DELETE FROM lesson_rubrics WHERE lesson_id = $1 AND rubric_id = $2",
|
.bind(lesson_id)
|
||||||
lesson_id,
|
.bind(rubric_id)
|
||||||
rubric_id
|
|
||||||
)
|
|
||||||
.execute(&pool)
|
.execute(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -584,18 +565,17 @@ pub async fn get_lesson_rubrics(
|
|||||||
State(pool): State<PgPool>,
|
State(pool): State<PgPool>,
|
||||||
Path(lesson_id): Path<Uuid>,
|
Path(lesson_id): Path<Uuid>,
|
||||||
) -> Result<Json<Vec<Rubric>>, (StatusCode, String)> {
|
) -> Result<Json<Vec<Rubric>>, (StatusCode, String)> {
|
||||||
let rubrics = sqlx::query_as!(
|
let rubrics: Vec<Rubric> = sqlx::query_as(
|
||||||
Rubric,
|
|
||||||
r#"
|
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
|
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
|
FROM rubrics r
|
||||||
INNER JOIN lesson_rubrics lr ON lr.rubric_id = r.id
|
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
|
WHERE lr.lesson_id = $1 AND lr.is_active = true AND r.organization_id = $2
|
||||||
ORDER BY lr.assigned_at DESC
|
ORDER BY lr.assigned_at DESC
|
||||||
"#,
|
"#
|
||||||
lesson_id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(lesson_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|||||||
@@ -164,16 +164,27 @@ pub async fn export_course_grades(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 1. Get Categories
|
// 1. Get Categories
|
||||||
let categories = sqlx::query!(
|
#[derive(sqlx::FromRow)]
|
||||||
"SELECT id, name FROM grading_categories WHERE course_id = $1 ORDER BY name",
|
struct Cat { id: Uuid, name: String }
|
||||||
course_id
|
let categories: Vec<Cat> = sqlx::query_as(
|
||||||
|
"SELECT id, name FROM grading_categories WHERE course_id = $1 ORDER BY name"
|
||||||
)
|
)
|
||||||
|
.bind(course_id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|
||||||
// 2. Get Student general data
|
// 2. Get Student general data
|
||||||
let students = sqlx::query!(
|
#[derive(sqlx::FromRow)]
|
||||||
|
struct StudentRow {
|
||||||
|
id: Uuid,
|
||||||
|
full_name: String,
|
||||||
|
email: String,
|
||||||
|
progress: Option<f32>,
|
||||||
|
cohort_name: Option<String>,
|
||||||
|
average_score: Option<f32>,
|
||||||
|
}
|
||||||
|
let students: Vec<StudentRow> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
u.id,
|
u.id,
|
||||||
@@ -188,23 +199,23 @@ pub async fn export_course_grades(
|
|||||||
WHERE e.organization_id = $2
|
WHERE e.organization_id = $2
|
||||||
GROUP BY u.id, u.full_name, u.email
|
GROUP BY u.id, u.full_name, u.email
|
||||||
ORDER BY u.full_name
|
ORDER BY u.full_name
|
||||||
"#,
|
"#
|
||||||
course_id,
|
|
||||||
org_ctx.id
|
|
||||||
)
|
)
|
||||||
|
.bind(course_id)
|
||||||
|
.bind(org_ctx.id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|
||||||
// 3. Get detailed grades per user/category
|
// 3. Get detailed grades per user/category
|
||||||
|
#[derive(sqlx::FromRow)]
|
||||||
struct UserCategoryGrade {
|
struct UserCategoryGrade {
|
||||||
user_id: Uuid,
|
user_id: Uuid,
|
||||||
grading_category_id: Option<Uuid>,
|
grading_category_id: Option<Uuid>,
|
||||||
avg_score: Option<f32>,
|
avg_score: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
let detailed_grades = sqlx::query_as!(
|
let detailed_grades: Vec<UserCategoryGrade> = sqlx::query_as(
|
||||||
UserCategoryGrade,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT
|
SELECT
|
||||||
g.user_id,
|
g.user_id,
|
||||||
@@ -214,9 +225,9 @@ pub async fn export_course_grades(
|
|||||||
JOIN lessons l ON g.lesson_id = l.id
|
JOIN lessons l ON g.lesson_id = l.id
|
||||||
WHERE g.course_id = $1
|
WHERE g.course_id = $1
|
||||||
GROUP BY g.user_id, l.grading_category_id
|
GROUP BY g.user_id, l.grading_category_id
|
||||||
"#,
|
"#
|
||||||
course_id
|
|
||||||
)
|
)
|
||||||
|
.bind(course_id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e: sqlx::Error| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
@@ -906,17 +917,16 @@ pub async fn get_course_outline(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 6. Fetch all dependencies for this course
|
// 6. Fetch all dependencies for this course
|
||||||
let dependencies = sqlx::query_as!(
|
let dependencies: Vec<LessonDependency> = sqlx::query_as(
|
||||||
LessonDependency,
|
|
||||||
r#"
|
r#"
|
||||||
SELECT ld.*
|
SELECT ld.*
|
||||||
FROM lesson_dependencies ld
|
FROM lesson_dependencies ld
|
||||||
JOIN lessons l ON ld.lesson_id = l.id
|
JOIN lessons l ON ld.lesson_id = l.id
|
||||||
JOIN modules m ON l.module_id = m.id
|
JOIN modules m ON l.module_id = m.id
|
||||||
WHERE m.course_id = $1
|
WHERE m.course_id = $1
|
||||||
"#,
|
"#
|
||||||
id
|
|
||||||
)
|
)
|
||||||
|
.bind(id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| {
|
.map_err(|e: sqlx::Error| {
|
||||||
@@ -1011,12 +1021,15 @@ pub async fn get_lesson_content(
|
|||||||
return Ok(Json(lesson));
|
return Ok(Json(lesson));
|
||||||
}
|
}
|
||||||
// We check if there are any prerequisites that the user hasn't completed yet.
|
// We check if there are any prerequisites that the user hasn't completed yet.
|
||||||
// A prerequisite is completed if:
|
#[derive(sqlx::FromRow)]
|
||||||
// a) It's graded and the user has a grade >= min_score_percentage (default 0)
|
struct UnmetDep {
|
||||||
// b) It's not graded and the user has a 'complete' interaction
|
prerequisite_lesson_id: Uuid,
|
||||||
let unmet_dependencies = sqlx::query!(
|
prereq_title: String,
|
||||||
|
min_score_percentage: Option<f32>
|
||||||
|
}
|
||||||
|
let unmet_dependencies: Vec<UnmetDep> = sqlx::query_as(
|
||||||
r#"
|
r#"
|
||||||
SELECT ld.prerequisite_lesson_id, p.title as prereq_title, ld.min_score_percentage
|
SELECT ld.prerequisite_lesson_id, p.title as prereq_title, ld.min_score_percentage::float4 as min_score_percentage
|
||||||
FROM lesson_dependencies ld
|
FROM lesson_dependencies ld
|
||||||
JOIN lessons p ON ld.prerequisite_lesson_id = p.id
|
JOIN lessons p ON ld.prerequisite_lesson_id = p.id
|
||||||
LEFT JOIN user_grades ug ON ld.prerequisite_lesson_id = ug.lesson_id AND ug.user_id = $2
|
LEFT JOIN user_grades ug ON ld.prerequisite_lesson_id = ug.lesson_id AND ug.user_id = $2
|
||||||
@@ -1028,10 +1041,10 @@ pub async fn get_lesson_content(
|
|||||||
OR
|
OR
|
||||||
(p.is_graded = false AND li.id IS NULL)
|
(p.is_graded = false AND li.id IS NULL)
|
||||||
)
|
)
|
||||||
"#,
|
"#
|
||||||
id,
|
|
||||||
claims.sub
|
|
||||||
)
|
)
|
||||||
|
.bind(id)
|
||||||
|
.bind(claims.sub)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e: sqlx::Error| {
|
.map_err(|e: sqlx::Error| {
|
||||||
|
|||||||
@@ -52,16 +52,16 @@ pub async fn list_announcements(
|
|||||||
|
|
||||||
// Attach cohort_ids to each announcement
|
// Attach cohort_ids to each announcement
|
||||||
for a in &mut announcements {
|
for a in &mut announcements {
|
||||||
let cohorts = sqlx::query!(
|
let cohorts: Vec<(Uuid,)> = sqlx::query_as(
|
||||||
"SELECT cohort_id FROM announcement_cohorts WHERE announcement_id = $1",
|
"SELECT cohort_id FROM announcement_cohorts WHERE announcement_id = $1"
|
||||||
a.id
|
|
||||||
)
|
)
|
||||||
|
.bind(a.id)
|
||||||
.fetch_all(&pool)
|
.fetch_all(&pool)
|
||||||
.await
|
.await
|
||||||
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
|
||||||
|
|
||||||
if !cohorts.is_empty() {
|
if !cohorts.is_empty() {
|
||||||
a.cohort_ids = Some(cohorts.into_iter().map(|c| c.cohort_id).collect());
|
a.cohort_ids = Some(cohorts.into_iter().map(|c| c.0).collect());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use common::models::{
|
|||||||
SubmitPeerReviewPayload,
|
SubmitPeerReviewPayload,
|
||||||
};
|
};
|
||||||
use common::{auth::Claims, middleware::Org};
|
use common::{auth::Claims, middleware::Org};
|
||||||
use sqlx::PgPool;
|
use sqlx::{PgPool, Row};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub async fn submit_assignment(
|
pub async fn submit_assignment(
|
||||||
|
|||||||
Reference in New Issue
Block a user