headless_lms_server/controllers/main_frontend/
code_giveaways.rs

1//! Controllers for requests starting with `/api/v0/main-frontend/code-giveaways`.
2use chrono::Utc;
3use domain::csv_export::{code_giveaway_codes::CodeGiveawayCodesExportOperation, general_export};
4use headless_lms_models::{
5    code_giveaway_codes::CodeGiveawayCode,
6    code_giveaways::{CodeGiveaway, NewCodeGiveaway},
7};
8use utoipa::OpenApi;
9
10use crate::prelude::*;
11
12#[derive(OpenApi)]
13#[openapi(paths(
14    get_code_giveaways_by_course,
15    create_code_giveaway,
16    get_code_giveaway_by_id,
17    add_codes_to_code_giveaway,
18    get_codes_by_code_giveaway_id,
19    delete_code_giveaway_code,
20    get_codes_by_code_giveaway_id_csv
21))]
22pub(crate) struct MainFrontendCodeGiveawaysApiDoc;
23
24/**
25GET `/api/v0/main-frontend/code-giveaways/by-course/:course_id` - Returns code giveaways for a course.
26 */
27#[utoipa::path(
28    get,
29    path = "/by-course/{course_id}",
30    operation_id = "getCodeGiveawaysByCourse",
31    tag = "code_giveaways",
32    params(
33        ("course_id" = Uuid, Path, description = "Course id")
34    ),
35    responses(
36        (status = 200, description = "Code giveaways for course", body = Vec<CodeGiveaway>)
37    )
38)]
39#[instrument(skip(pool))]
40async fn get_code_giveaways_by_course(
41    pool: web::Data<PgPool>,
42    course_id: web::Path<Uuid>,
43    user: AuthUser,
44) -> ControllerResult<web::Json<Vec<CodeGiveaway>>> {
45    let mut conn = pool.acquire().await?;
46
47    let token = authorize(&mut conn, Act::Edit, Some(user.id), Res::Course(*course_id)).await?;
48
49    let code_giveaways = models::code_giveaways::get_all_for_course(&mut conn, *course_id).await?;
50
51    token.authorized_ok(web::Json(code_giveaways))
52}
53
54/**
55 * POST `/api/v0/main-frontend/code-giveaways - Creates a new code giveaway.
56 */
57#[utoipa::path(
58    post,
59    path = "",
60    operation_id = "createCodeGiveaway",
61    tag = "code_giveaways",
62    request_body = NewCodeGiveaway,
63    responses(
64        (status = 200, description = "Created code giveaway", body = CodeGiveaway)
65    )
66)]
67#[instrument(skip(pool))]
68async fn create_code_giveaway(
69    pool: web::Data<PgPool>,
70    code_giveaway: web::Json<NewCodeGiveaway>,
71    user: AuthUser,
72) -> ControllerResult<web::Json<CodeGiveaway>> {
73    let mut conn = pool.acquire().await?;
74
75    let token = authorize(
76        &mut conn,
77        Act::Edit,
78        Some(user.id),
79        Res::Course(code_giveaway.course_id),
80    )
81    .await?;
82
83    let code_giveaway = models::code_giveaways::insert(&mut conn, &code_giveaway).await?;
84
85    token.authorized_ok(web::Json(code_giveaway))
86}
87
88/**
89 * GET `/api/v0/main-frontend/code-giveaways/:id - Gets a code giveaway by ID.
90 */
91#[utoipa::path(
92    get,
93    path = "/{id}",
94    operation_id = "getCodeGiveawayById",
95    tag = "code_giveaways",
96    params(
97        ("id" = Uuid, Path, description = "Code giveaway id")
98    ),
99    responses(
100        (status = 200, description = "Code giveaway", body = CodeGiveaway)
101    )
102)]
103#[instrument(skip(pool))]
104async fn get_code_giveaway_by_id(
105    pool: web::Data<PgPool>,
106    id: web::Path<Uuid>,
107    user: AuthUser,
108) -> ControllerResult<web::Json<CodeGiveaway>> {
109    let mut conn = pool.acquire().await?;
110
111    let code_giveaway = models::code_giveaways::get_by_id(&mut conn, *id).await?;
112
113    let token = authorize(
114        &mut conn,
115        Act::Edit,
116        Some(user.id),
117        Res::Course(code_giveaway.course_id),
118    )
119    .await?;
120
121    token.authorized_ok(web::Json(code_giveaway))
122}
123
124/**
125 * POST `/api/v0/main-frontend/code-giveaways/:id/codes - Adds new codes to a code giveaway.
126 */
127#[utoipa::path(
128    post,
129    path = "/{id}/codes",
130    operation_id = "addCodeGiveawayCodes",
131    tag = "code_giveaways",
132    params(
133        ("id" = Uuid, Path, description = "Code giveaway id")
134    ),
135    request_body = Vec<String>,
136    responses(
137        (status = 200, description = "Added giveaway codes", body = Vec<CodeGiveawayCode>)
138    )
139)]
140#[instrument(skip(pool))]
141async fn add_codes_to_code_giveaway(
142    pool: web::Data<PgPool>,
143    id: web::Path<Uuid>,
144    codes: web::Json<Vec<String>>,
145    user: AuthUser,
146) -> ControllerResult<web::Json<Vec<CodeGiveawayCode>>> {
147    let mut conn = pool.acquire().await?;
148
149    let code_giveaway = models::code_giveaways::get_by_id(&mut conn, *id).await?;
150
151    let token = authorize(
152        &mut conn,
153        Act::Edit,
154        Some(user.id),
155        Res::Course(code_giveaway.course_id),
156    )
157    .await?;
158
159    let codes = models::code_giveaway_codes::insert_many(&mut conn, *id, &codes, user.id).await?;
160
161    token.authorized_ok(web::Json(codes))
162}
163
164/**
165 * GET `/api/v0/main-frontend/code-giveaways/:id/codes - Gets codes for a code giveaway by ID.
166 */
167#[utoipa::path(
168    get,
169    path = "/{id}/codes",
170    operation_id = "getCodeGiveawayCodes",
171    tag = "code_giveaways",
172    params(
173        ("id" = Uuid, Path, description = "Code giveaway id")
174    ),
175    responses(
176        (status = 200, description = "Giveaway codes", body = Vec<CodeGiveawayCode>)
177    )
178)]
179#[instrument(skip(pool))]
180async fn get_codes_by_code_giveaway_id(
181    pool: web::Data<PgPool>,
182    id: web::Path<Uuid>,
183    user: AuthUser,
184) -> ControllerResult<web::Json<Vec<CodeGiveawayCode>>> {
185    let mut conn = pool.acquire().await?;
186
187    let code_giveaway = models::code_giveaways::get_by_id(&mut conn, *id).await?;
188
189    let token = authorize(
190        &mut conn,
191        Act::Edit,
192        Some(user.id),
193        Res::Course(code_giveaway.course_id),
194    )
195    .await?;
196
197    let codes = models::code_giveaway_codes::get_all_by_code_giveaway_id(&mut conn, *id).await?;
198
199    token.authorized_ok(web::Json(codes))
200}
201
202/**
203 * DELETE `/api/v0/main-frontend/code-giveaways/:id/codes/:code_id - Deletes a code giveaway code.
204 */
205#[utoipa::path(
206    delete,
207    path = "/{id}/codes/{code_id}",
208    operation_id = "deleteCodeGiveawayCode",
209    tag = "code_giveaways",
210    params(
211        ("id" = Uuid, Path, description = "Code giveaway id"),
212        ("code_id" = Uuid, Path, description = "Giveaway code id")
213    ),
214    responses(
215        (status = 200, description = "Deleted giveaway code")
216    )
217)]
218#[instrument(skip(pool))]
219async fn delete_code_giveaway_code(
220    pool: web::Data<PgPool>,
221    path: web::Path<(Uuid, Uuid)>,
222    user: AuthUser,
223) -> ControllerResult<web::Json<()>> {
224    let (id, code_id) = path.into_inner();
225    let mut conn = pool.acquire().await?;
226
227    let giveaway = models::code_giveaways::get_by_id(&mut conn, id).await?;
228    let code = models::code_giveaway_codes::get_by_id(&mut conn, code_id).await?;
229
230    if id != code.code_giveaway_id {
231        return Err(ControllerError::new(
232            ControllerErrorType::BadRequest,
233            "Code giveaway and code course mismatch",
234            None,
235        ));
236    }
237
238    let token = authorize(
239        &mut conn,
240        Act::Edit,
241        Some(user.id),
242        Res::Course(giveaway.course_id),
243    )
244    .await?;
245
246    models::code_giveaway_codes::delete_by_id(&mut conn, code_id).await?;
247
248    token.authorized_ok(web::Json(()))
249}
250
251/**
252 * GET `/api/v0/main-frontend/code-giveaways/:id/codes/csv - Gets codes for a code giveaway by ID as CSV.
253 */
254#[utoipa::path(
255    get,
256    path = "/{id}/codes/csv",
257    operation_id = "downloadCodeGiveawayCodesCsv",
258    tag = "code_giveaways",
259    params(
260        ("id" = Uuid, Path, description = "Code giveaway id")
261    ),
262    responses(
263        (status = 200, description = "Giveaway codes CSV", content_type = "text/csv", body = String)
264    )
265)]
266#[instrument(skip(pool))]
267async fn get_codes_by_code_giveaway_id_csv(
268    pool: web::Data<PgPool>,
269    id: web::Path<Uuid>,
270    user: AuthUser,
271) -> ControllerResult<HttpResponse> {
272    let mut conn = pool.acquire().await?;
273
274    let code_giveaway = models::code_giveaways::get_by_id(&mut conn, *id).await?;
275    let course = models::courses::get_course(&mut conn, code_giveaway.course_id).await?;
276
277    let token = authorize(
278        &mut conn,
279        Act::Edit,
280        Some(user.id),
281        Res::Course(code_giveaway.course_id),
282    )
283    .await?;
284
285    general_export(
286        pool,
287        &format!(
288            "attachment; filename=\"Giveaway codes {} / {} - {}.csv\"",
289            code_giveaway.name,
290            course.name,
291            Utc::now().format("%Y-%m-%d")
292        ),
293        CodeGiveawayCodesExportOperation {
294            code_giveaway_id: *id,
295        },
296        token,
297    )
298    .await
299}
300
301/**
302Add a route for each controller in this module.
303
304The name starts with an underline in order to appear before other functions in the module documentation.
305
306We add the routes by calling the route method instead of using the route annotations because this method preserves the function signatures for documentation.
307*/
308pub fn _add_routes(cfg: &mut ServiceConfig) {
309    cfg.route(
310        "by-course/{course_id}",
311        web::get().to(get_code_giveaways_by_course),
312    )
313    .route("", web::post().to(create_code_giveaway))
314    .route("{id}", web::get().to(get_code_giveaway_by_id))
315    .route("{id}/codes", web::get().to(get_codes_by_code_giveaway_id))
316    .route(
317        "{id}/codes/csv",
318        web::get().to(get_codes_by_code_giveaway_id_csv),
319    )
320    .route("{id}/codes", web::post().to(add_codes_to_code_giveaway))
321    .route(
322        "{id}/codes/{code_id}",
323        web::delete().to(delete_code_giveaway_code),
324    );
325}