avina-api 1.2.1

Rust API server for the LRZ-specific features of the Openstack-based LRZ Compute Cloud.
use actix_web::{
    HttpResponse,
    web::{Data, Json, Path, ReqData},
};
use anyhow::Context;
use avina_wire::{
    accounting::{ServerState, ServerStateModifyData},
    user::User,
};
use sqlx::{Executor, MySql, MySqlPool, Transaction};

use super::ServerStateIdParam;
use crate::{
    authorization::require_admin_user,
    database::{
        accounting::server_state::select_server_state_from_db,
        resources::flavor::select_flavor_name_from_db,
        user::user::select_user_name_from_db,
    },
    error::{NotFoundOrUnexpectedApiError, OptionApiError},
};

#[tracing::instrument(name = "server_state_modify")]
pub async fn server_state_modify(
    user: ReqData<User>,
    db_pool: Data<MySqlPool>,
    data: Json<ServerStateModifyData>,
    params: Path<ServerStateIdParam>,
) -> Result<HttpResponse, OptionApiError> {
    require_admin_user(&user)?;
    // TODO: do further validation
    if data.id != params.server_state_id {
        return Err(OptionApiError::ValidationError(
            "ID in URL does not match ID in body".to_string(),
        ));
    }
    let mut transaction = db_pool
        .begin()
        .await
        .context("Failed to begin transaction")?;
    let server_state =
        update_server_state_in_db(&mut transaction, &data).await?;
    transaction
        .commit()
        .await
        .context("Failed to commit transaction")?;
    Ok(HttpResponse::Ok()
        .content_type("application/json")
        .json(server_state))
}

#[tracing::instrument(
    name = "update_server_state_in_db",
    skip(data, transaction)
)]
pub async fn update_server_state_in_db(
    transaction: &mut Transaction<'_, MySql>,
    data: &ServerStateModifyData,
) -> Result<ServerState, NotFoundOrUnexpectedApiError> {
    let row = select_server_state_from_db(transaction, data.id as u64).await?;
    let begin = data.begin.unwrap_or(row.begin);
    let mut end = data.end;
    if end.is_none() {
        end = row.end;
    }
    let instance_id = data.instance_id.unwrap_or(row.instance_id);
    let instance_name = data.instance_name.clone().unwrap_or(row.instance_name);
    let status = data.status.clone().unwrap_or(row.status);
    let user = data.user.unwrap_or(row.user);
    let username = select_user_name_from_db(transaction, user as u64).await?;
    let flavor = data.flavor.unwrap_or(row.flavor);
    let flavor_name =
        select_flavor_name_from_db(transaction, user as u64).await?;
    let query1 = sqlx::query!(
        r#"
        UPDATE accounting_state
        SET
            begin = ?,
            end = ?
        WHERE id = ?
        "#,
        begin.to_utc(),
        end.map(|end| end.to_utc()),
        data.id,
    );
    transaction
        .execute(query1)
        .await
        .context("Failed to execute update first query")?;
    let query2 = sqlx::query!(
        r#"
        UPDATE accounting_serverstate
        SET
            instance_id = ?,
            instance_name = ?,
            flavor_id = ?,
            status = ?,
            user_id = ?
        WHERE state_ptr_id = ?
        "#,
        instance_id.to_string(),
        instance_name,
        flavor,
        status,
        user,
        data.id,
    );
    transaction
        .execute(query2)
        .await
        .context("Failed to execute update second query")?;
    let price = ServerState {
        id: data.id,
        begin,
        end,
        instance_id,
        instance_name,
        flavor,
        flavor_name,
        status,
        user,
        username,
    };
    Ok(price)
}