diff --git a/astroplant-api/src/bin/astroplant-api.rs b/astroplant-api/src/bin/astroplant-api.rs index acf5ac7..56d9aee 100644 --- a/astroplant-api/src/bin/astroplant-api.rs +++ b/astroplant-api/src/bin/astroplant-api.rs @@ -65,6 +65,7 @@ async fn main() -> anyhow::Result<()> { .route("/kits/:kit_serial", patch(kit::patch_kit)) .route("/kits/:kit_serial", delete(kit::delete_kit)) .route("/kits/:kit_serial/members", get(kit::get_members)) + .route("/kits/:kit_serial/members", post(kit::add_member)) .route( "/kits/:kit_serial/member-suggestions", get(kit::get_member_suggestions), diff --git a/astroplant-api/src/controllers/kit/mod.rs b/astroplant-api/src/controllers/kit/mod.rs index 2468d50..a5f3756 100644 --- a/astroplant-api/src/controllers/kit/mod.rs +++ b/astroplant-api/src/controllers/kit/mod.rs @@ -1,5 +1,6 @@ use axum::extract::Path; use axum::Extension; +use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use crate::authorization::KitAction; @@ -291,6 +292,90 @@ pub async fn get_members( Ok(ResponseBuilder::ok().body(v)) } +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AddMember { + username: String, + access_configure: bool, + access_super: bool, +} + +/// Handles the `POST /kits/{kitSerial}/members` route. +pub async fn add_member( + Extension(pg): Extension, + Path(kit_serial): Path, + user_id: Option, + crate::extract::Json(member): crate::extract::Json, +) -> Result { + let action = if member.access_super { + crate::authorization::KitAction::EditSuperMembers + } else { + crate::authorization::KitAction::EditMembers + }; + let (_, _, kit) = + helpers::fut_kit_permission_or_forbidden(pg.clone(), user_id, kit_serial, action).await?; + + let conn = pg.get().await?; + + let kit_id = kit.id; + let membership = conn + .interact_flatten_err(move |conn| { + use diesel::prelude::*; + use schema::kit_memberships; + use schema::users; + + conn.build_transaction().serializable().run(move |conn| { + let user: User = users::table + .filter(users::username.eq(&member.username)) + .first(conn)?; + + let existing_membership: Option = kit_memberships::table + .filter(kit_memberships::kit_id.eq(kit_id)) + .filter(kit_memberships::user_id.eq(user.id)) + .get_result(conn) + .optional()?; + + if let Some(existing_membership) = existing_membership { + // This membership already exists, do nothing + return Ok::<_, Problem>( + views::KitMembership::from(existing_membership) + .with_user(views::User::from(user)) + .with_kit(views::Kit::from(kit)), + ); + } + + #[derive(Insertable)] + #[diesel(table_name = kit_memberships)] + struct NewKitMembership { + user_id: i32, + kit_id: i32, + access_super: bool, + access_configure: bool, + datetime_linked: DateTime, + } + + let membership = NewKitMembership { + user_id: user.id, + kit_id, + access_super: member.access_super, + access_configure: member.access_configure, + datetime_linked: Utc::now(), + }; + let membership: KitMembership = membership + .insert_into(kit_memberships::table) + .get_result(conn)?; + + Ok::<_, Problem>( + views::KitMembership::from(membership) + .with_user(views::User::from(user)) + .with_kit(views::Kit::from(kit)), + ) + }) + }) + .await?; + + Ok(ResponseBuilder::ok().body(membership)) +} #[derive(Deserialize)] pub struct MemberSuggestions { diff --git a/openapi.yaml b/openapi.yaml index e42ab5b..3498c28 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -258,6 +258,42 @@ paths: description: The serial of the kit to get the members of. schema: type: string + responses: + '200': + description: The kit's members. + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/KitMembership" + '401': + $ref: "#/components/responses/ErrorUnauthorized" + '429': + $ref: "#/components/responses/ErrorRateLimit" + '500': + $ref: "#/components/responses/ErrorInternalServer" + post: + summary: Add a membership to a kit. + operationId: addKitMember + security: + - bearerAuth: [] + tags: + - kits + parameters: + - name: kitSerial + in: path + required: true + description: The serial of the kit to add a membership to. + schema: + type: string + requestBody: + description: The membership to add. + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/NewKitMembership" responses: '200': description: The kit's members. @@ -1406,6 +1442,19 @@ components: datetimeLinked: type: string format: "date-time" + NewKitMembership: + type: object + required: + - username + - accessConfigure + - accessSuper + properties: + username: + type: string + accessConfigure: + type: boolean + accessSuper: + type: boolean PatchKitMembership: type: object properties: