api/routes/modules/announcements/post.rs
1//! Create announcement handler.
2//!
3//! Provides an endpoint to create a new announcement for a specific module.
4//!
5//! **Permissions:** Only authorized users (lecturer/assistant) can create announcements.
6
7use axum::{extract::{Path, State}, http::StatusCode, response::IntoResponse, Extension, Json};
8use util::state::AppState;
9use crate::{auth::AuthUser, response::ApiResponse, routes::modules::announcements::common::AnnouncementRequest};
10use db::models::announcements::Model as AnnouncementModel;
11
12/// POST /api/modules/{module_id}/announcements
13///
14/// Creates a new announcement for the specified module.
15///
16/// # AuthZ / AuthN
17/// - Requires a valid `Bearer` token (JWT).
18/// - Caller must be **lecturer** or **assistant_lecturer** on the target module
19/// (enforced by `require_lecturer_or_assistant_lecturer` route layer).
20///
21/// # Path Parameters
22/// - `module_id` — ID of the module to create the announcement under.
23///
24/// # Request Body
25/// JSON matching `AnnouncementRequest`:
26/// ```json
27/// {
28/// "title": "Exam Schedule",
29/// "body": "The exam will be held next **Friday** at 09:00.",
30/// "pinned": true
31/// }
32/// ```
33///
34/// # Example cURL
35/// ```bash
36/// curl -X POST "https://your.api/api/modules/101/announcements" \
37/// -H "Authorization: Bearer <JWT>" \
38/// -H "Content-Type: application/json" \
39/// -d '{
40/// "title": "Exam Schedule",
41/// "body": "The exam will be held next **Friday** at 09:00.",
42/// "pinned": true
43/// }'
44/// ```
45///
46/// # Responses
47/// - `200 OK` — Announcement created successfully. Returns the created record.
48/// - `400 BAD REQUEST` — Malformed JSON.
49/// - `401 UNAUTHORIZED` — Missing/invalid token.
50/// - `403 FORBIDDEN` — Authenticated but not lecturer/assistant on this module.
51/// - `422 UNPROCESSABLE ENTITY` — JSON is valid but required fields missing/invalid.
52/// - `500 INTERNAL SERVER ERROR` — Database error.
53///
54/// ## 200 OK — Example
55/// ```json
56/// {
57/// "success": true,
58/// "data": {
59/// "id": 1234,
60/// "module_id": 101,
61/// "user_id": 55,
62/// "title": "Exam Schedule",
63/// "body": "The exam will be held next **Friday** at 09:00.",
64/// "pinned": true,
65/// "created_at": "2025-02-10T12:34:56Z",
66/// "updated_at": "2025-02-10T12:34:56Z"
67/// },
68/// "message": "Announcement created successfully"
69/// }
70/// ```
71///
72/// ## 400 Bad Request — Example (invalid JSON)
73/// ```json
74/// {
75/// "success": false,
76/// "message": "invalid JSON body"
77/// }
78/// ```
79///
80/// ## 422 Unprocessable Entity — Example (missing fields)
81/// ```json
82/// {
83/// "success": false,
84/// "message": "Unprocessable Entity"
85/// }
86/// ```
87///
88/// ## 403 Forbidden — Example
89/// ```json
90/// {
91/// "success": false,
92/// "message": "Forbidden"
93/// }
94/// ```
95///
96/// ## 500 Internal Server Error — Example
97/// ```json
98/// {
99/// "success": false,
100/// "message": "Failed to create announcement: database error detail ..."
101/// }
102/// ```
103pub async fn create_announcement(
104 State(app_state): State<AppState>,
105 Path(module_id): Path<i64>,
106 Extension(AuthUser(claims)): Extension<AuthUser>,
107 Json(req): Json<AnnouncementRequest>,
108) -> impl IntoResponse {
109 let db = app_state.db();
110 let user_id = claims.sub;
111
112 match AnnouncementModel::create(db, module_id, user_id, &req.title, &req.body, req.pinned).await {
113 Ok(announcement) => (
114 StatusCode::OK,
115 Json(ApiResponse::success(
116 announcement,
117 "Announcement created successfully",
118 )),
119 ),
120 Err(err) => (
121 StatusCode::INTERNAL_SERVER_ERROR,
122 Json(ApiResponse::error(
123 format!("Failed to create announcement: {}", err),
124 )),
125 ),
126 }
127}