lacoctelera/routes/recipe/post.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
// Copyright 2024 Felipe Torres González
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
use crate::{
authentication::{check_access, AuthData},
domain::Recipe,
routes::recipe::utils::register_new_recipe,
};
use actix_web::{
post,
web::{Data, Json, Query},
HttpResponse,
};
use serde_json::json;
use sqlx::MySqlPool;
use std::error::Error;
use tracing::{debug, info, instrument};
/// POST method for the /recipe endpoint (Restricted)
///
/// # Description
///
/// This method creates new recipes in the DB using the data provided by authors. Recipes are identified by an unique
/// ID that is generated by the backend sw before inserting a recipe into the DB. This means that the same recipe
/// can be pushed several times either by the same author or by another one.
///
/// A previous search in the DB is advised to avoid having many similar recipes. However, every author is free to
/// add a recipe that is quite similar to another one if he/she likes to.
///
/// A new recipe shall populate all the fields marked with a red asterisk in the recipe's schema. Fields that are
/// optional (not marked with the red asterisk) are, most likely, being filled by the backend logic. However, the
/// following optional fields are meant to be populated by the author, but they are not mandatory:
/// - *author_tags*: Tags that can be freely assigned by the author.
/// - *description*: A free text input in which the author can describe in detail the recipe.
/// - *url*: Useful to link the recipe entry to another web resource.
#[utoipa::path(
post,
path = "/recipe",
tag = "Recipe",
security(
("api_key" = [])
),
responses(
(
status = 200,
description = "The Recipe was inserted in the DB.",
content_type = "application/json",
example = json!({"id": "0192e8d9-36cf-7ce3-82ef-0a7c9b2deefe"}),
headers(
("Content-Length"),
("Content-Type"),
("Date"),
("Vary", description = "Origin,Access-Control-Request-Method,Access-Control-Request-Headers")
),
),
(
status = 400,
description = "Missing API key. This endpoint is restricted to public access.",
),
(
status = 429, description = "**Too many requests.**",
headers(
("Cache-Control", description = "Cache control is set to *no-cache*."),
("Access-Control-Allow-Origin"),
("Retry-After", description = "Amount of time between requests (seconds).")
)
)
)
)]
#[instrument(skip(pool, token))]
#[post("")]
pub async fn post_recipe(
req: Json<Recipe>,
pool: Data<MySqlPool>,
token: Query<AuthData>,
) -> Result<HttpResponse, Box<dyn Error>> {
info!("Post new recipe: {:#?}", req.0);
// Access control
check_access(&pool, &token.api_key).await?;
debug!("Access granted");
let id = register_new_recipe(&pool, &req.0).await?;
Ok(HttpResponse::Ok().json(json!({"id": id.to_string()})))
}