diff --git a/crates/backend/src/controller.rs b/crates/backend/src/controller.rs index b5e90a0..144b422 100644 --- a/crates/backend/src/controller.rs +++ b/crates/backend/src/controller.rs @@ -3,6 +3,7 @@ use actix_web::web::{self, ServiceConfig}; // TODO: Refactor to use re-exports instead of making module public pub mod auth; pub mod class; +pub mod feedback; pub mod group; pub mod project; pub mod template; @@ -15,6 +16,7 @@ pub fn register_controllers(cfg: &mut ServiceConfig) { .service(web::scope("/class").configure(class::setup)) .service(web::scope("/template").configure(template::setup)) .service(web::scope("/auth").configure(auth::setup)) + .service(web::scope("/feedback").configure(feedback::setup)) .service( web::resource("/ok").to(|| async { actix_web::HttpResponse::Ok().body("available") }), ); diff --git a/crates/backend/src/controller/feedback.rs b/crates/backend/src/controller/feedback.rs new file mode 100644 index 0000000..054be0e --- /dev/null +++ b/crates/backend/src/controller/feedback.rs @@ -0,0 +1,59 @@ +use actix_web::{HttpResponse, Responder, delete, get, post, web::ServiceConfig}; + +use crate::error::{ApiError, MessageResponse}; + +pub fn setup(cfg: &mut ServiceConfig) { + cfg.service(get_feedback_form) + .service(submit_feedback) + .service(get_feedback_status) + .service(reset_feedback); +} + +#[utoipa::path(get, path = "/api/v1/feedback/{token}", tag = "feedback")] +#[get("/feedback/{token}")] +pub async fn get_feedback_form() -> Result { + // TODO: Implement feedback form retrieval + // 1. Validate token exists and is not completed + // 2. Get project template and group information + // 3. Return form structure + + Ok(HttpResponse::Ok().json("{}")) +} + +#[utoipa::path(post, path = "/api/v1/feedback/{token}", tag = "feedback")] +#[post("/feedback/{token}")] +pub async fn submit_feedback() -> Result { + // TODO: Implement feedback submission + // 1. Validate token and ensure not already completed + // 2. Store feedback responses + // 3. Mark as completed with timestamp + + Ok(HttpResponse::Ok().json(MessageResponse { + message: "Feedback submitted successfully".to_string(), + })) +} + +#[utoipa::path(get, path = "/api/v1/feedback/{token}/status", tag = "feedback")] +#[get("/feedback/{token}/status")] +pub async fn get_feedback_status() -> Result { + // TODO: Implement status checking + // 1. Get completion status for this token + // 2. Check if all group members completed feedback + // 3. Calculate final grade if group grade exists + // 4. Get anonymized feedback received + + Ok(HttpResponse::Ok().json("{}")) +} + +#[utoipa::path(delete, path = "/api/v1/feedback/{token}/reset", tag = "feedback")] +#[delete("/feedback/{token}/reset")] +pub async fn reset_feedback() -> Result { + // TODO: Implement feedback reset + // 1. Verify teacher authorization + // 2. Reset completion status and clear responses + // 3. Optionally regenerate token + + Ok(HttpResponse::Ok().json(MessageResponse { + message: "Feedback reset successfully".to_string(), + })) +} diff --git a/crates/backend/src/controller/group.rs b/crates/backend/src/controller/group.rs index 8df024d..389bde7 100644 --- a/crates/backend/src/controller/group.rs +++ b/crates/backend/src/controller/group.rs @@ -5,7 +5,11 @@ pub fn setup(cfg: &mut actix_web::web::ServiceConfig) { .service(get_groups_for_project) .service(create_group) .service(update_group) - .service(delete_group); + .service(delete_group) + .service(generate_group_feedback_tokens) + .service(get_group_feedback_tokens) + .service(set_group_grade) + .service(set_individual_grades); } #[utoipa::path( @@ -88,3 +92,47 @@ async fn update_group() -> impl Responder { async fn delete_group() -> impl Responder { "" } + +#[utoipa::path( + post, + path = "/api/v1/group/{id}/generate-feedback-tokens", + tag = "groups" +)] +#[post("/{id}/generate-feedback-tokens")] +async fn generate_group_feedback_tokens() -> impl Responder { + // TODO: Generate feedback tokens for all students in this group + // 1. Get all UserGroupProject entries for this group + // 2. Generate UUID tokens for each student + // 3. Update database with tokens + "" +} + +#[utoipa::path(get, path = "/api/v1/group/{id}/feedback-tokens", tag = "groups")] +#[get("/{id}/feedback-tokens")] +async fn get_group_feedback_tokens() -> impl Responder { + // TODO: Get all feedback tokens for students in this group + // 1. List all tokens with completion status + // 2. Include student information for teacher view + "" +} + +#[utoipa::path(post, path = "/api/v1/group/{id}/grade", tag = "groups")] +#[post("/{id}/grade")] +async fn set_group_grade() -> impl Responder { + // TODO: Set the group grade (same grade for all students) + // 1. Validate teacher authorization + // 2. Store group grade for all students in group + // 3. Apply same grade to all UserGroupProject entries + "" +} + +#[utoipa::path(post, path = "/api/v1/group/{id}/individual-grades", tag = "groups")] +#[post("/{id}/individual-grades")] +async fn set_individual_grades() -> impl Responder { + // TODO: Set individual grades for each student in group + // 1. Validate teacher authorization + // 2. Accept array of student_id -> grade mappings + // 3. Store individual grades in UserGroupProject entries + // 4. Calculate final grades with peer feedback if available + "" +} diff --git a/crates/migration/src/baseline.rs b/crates/migration/src/baseline.rs index c4d6961..dc2643d 100644 --- a/crates/migration/src/baseline.rs +++ b/crates/migration/src/baseline.rs @@ -64,6 +64,9 @@ impl MigrationTrait for Migration { .col(uuid(UserGroupProject::UserId)) .col(uuid(UserGroupProject::GroupId)) .col(uuid(UserGroupProject::ProjectId)) + .col(uuid_uniq(UserGroupProject::FeedbackId).null()) + .col(boolean(UserGroupProject::FeedbackCompleted).default(false)) + .col(date_time(UserGroupProject::FeedbackCompletedAt).null()) .primary_key( Index::create() .col(UserGroupProject::UserId) @@ -173,6 +176,10 @@ enum UserGroupProject { UserId, GroupId, ProjectId, + // Feedback + FeedbackId, + FeedbackCompleted, + FeedbackCompletedAt, } #[derive(DeriveIden)]