api/routes/modules/assignments/
mod.rs

1//! Assignment routes module.
2//!
3//! Provides the `/assignments` route group with full CRUD and nested functionality.
4//!
5//! Routes include:
6//! - Create, read, update, delete assignments (single and bulk)
7//! - Open/close assignments
8//! - Assignment stats and readiness checks
9//! - Nested routes for tasks, config, memo output, mark allocation, submissions, files, interpreter, tickets, and plagiarism
10//!
11//! Access control is enforced via middleware guards for lecturers, assistants, and assigned users.
12
13use crate::auth::guards::{
14    require_assigned_to_module, require_lecturer_or_assistant_lecturer, require_ready_assignment,
15};
16use axum::{
17    Router,
18    middleware::from_fn_with_state,
19    routing::{delete, get, post, put},
20};
21use config::config_routes;
22use delete::{bulk_delete_assignments, delete_assignment};
23use files::files_routes;
24use get::{get_assignment, get_assignment_readiness, get_assignment_stats, get_assignments};
25use interpreter::interpreter_routes;
26use mark_allocator::mark_allocator_routes;
27use memo_output::memo_output_routes;
28use plagiarism::plagiarism_routes;
29use post::create_assignment;
30use put::{bulk_update_assignments, close_assignment, edit_assignment, open_assignment};
31use submissions::submission_routes;
32use tasks::tasks_routes;
33use tickets::ticket_routes;
34use util::state::AppState;
35
36pub mod common;
37pub mod config;
38pub mod delete;
39pub mod files;
40pub mod get;
41pub mod interpreter;
42pub mod mark_allocator;
43pub mod memo_output;
44pub mod plagiarism;
45pub mod post;
46pub mod put;
47pub mod submissions;
48pub mod tasks;
49pub mod tickets;
50
51/// Expects a module ID.
52/// If an assignment ID is included it will be modified or deleted.
53///
54/// Builds and returns the `/assignments` route group.
55///
56/// Routes:
57/// - `POST   /assignments`                               → Create a new assignment (requires lecturer)
58/// - `GET    /assignments`                               → List assignments
59/// - `DELETE /assignments/bulk`                          → Bulk delete assignments (requires lecturer)
60/// - `PUT    /assignments/bulk`                          → Bulk edit assignments (requires lecturer, cannot edit status)
61/// - `GET    /assignments/:assignment_id`                → Get assignment details
62/// - `PUT    /assignments/:assignment_id`                → Edit assignment (requires lecturer, cannot edit status)
63/// - `PUT    /assignments/:assignment_id/open`           → Open assignment (requires lecturer, only if currently Ready, Closed, or Archived)
64/// - `PUT    /assignments/:assignment_id/close`          → Close assignment (requires lecturer, only if currently Open)
65/// - `DELETE /assignments/:assignment_id`                → Delete assignment (requires lecturer)
66/// - `GET    /assignments/:assignment_id/stats`          → Assignment statistics (lecturer only)
67/// - `GET    /assignments/:assignment_id/readiness`      → Assignment readiness (lecturer or admin only)
68///
69/// Nested routes:
70/// - Tasks routes                  → `tasks_routes`
71/// - Config routes                 → `config_routes`
72/// - Memo output routes            → `memo_output_routes`
73/// - Mark allocator routes         → `mark_allocator_routes`
74/// - Submissions routes            → `submission_routes`
75/// - Files routes                  → `files_routes`
76/// - Tickets routes                → `ticket_routes`
77/// - Plagiarism routes             → `plagiarism_routes`
78pub fn assignment_routes(app_state: AppState) -> Router<AppState> {
79    Router::new()
80        .route("/", post(create_assignment).route_layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
81        .route("/", get(get_assignments).route_layer(from_fn_with_state(app_state.clone(), require_assigned_to_module)))
82        .route("/{assignment_id}", get(get_assignment).route_layer(from_fn_with_state(app_state.clone(), require_assigned_to_module)))
83        .route("/{assignment_id}", put(edit_assignment).route_layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
84        .route("/{assignment_id}", delete(delete_assignment).route_layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
85        .route("/{assignment_id}/open", put(open_assignment).route_layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)).route_layer(from_fn_with_state(app_state.clone(), require_ready_assignment)))
86        .route("/{assignment_id}/close", put(close_assignment).layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)).route_layer(from_fn_with_state(app_state.clone(), require_ready_assignment)))
87        .route("/bulk", delete(bulk_delete_assignments).layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
88        .route("/bulk", put(bulk_update_assignments).layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
89        .route("/{assignment_id}/stats", get(get_assignment_stats).route_layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
90        .route("/{assignment_id}/readiness", get(get_assignment_readiness).route_layer(from_fn_with_state(app_state.clone(), require_assigned_to_module)))
91        .nest("/{assignment_id}/tasks", tasks_routes().route_layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
92        .nest("/{assignment_id}/config", config_routes().layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
93        .nest("/{assignment_id}/memo_output", memo_output_routes().layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
94        .nest("/{assignment_id}/mark_allocator", mark_allocator_routes().route_layer(from_fn_with_state(app_state.clone(), require_lecturer_or_assistant_lecturer)))
95        .nest("/{assignment_id}/submissions", submission_routes(app_state.clone()).route_layer(from_fn_with_state(app_state.clone(), require_assigned_to_module)))
96        .nest("/{assignment_id}/files", files_routes(app_state.clone()))
97        .nest("/{assignment_id}/interpreter",interpreter_routes(app_state.clone()))
98        .nest("/{assignment_id}/tickets",ticket_routes(app_state.clone()).route_layer(from_fn_with_state(app_state.clone(),require_assigned_to_module)))
99        .nest("/{assignment_id}/plagiarism",plagiarism_routes().route_layer(from_fn_with_state(app_state.clone(),require_assigned_to_module,)).route_layer(from_fn_with_state(app_state.clone(),require_lecturer_or_assistant_lecturer)))
100}