use anyhow::Context; use axum::{ extract::Query, response::{IntoResponse, Json}, routing::get, Router, }; use diesel::{dsl::insert_into, prelude::*, update}; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ api_keys::ApiKey, app_error::AppError, app_state::{AppState, DbConn}, messages::Message, projects::Project, schema::{api_keys, messages, projects}, }; pub fn new_router(state: AppState) -> Router { Router::new().route("/say", get(say_get)).with_state(state) } #[derive(Deserialize)] struct SayQuery { key: Uuid, project: String, message: String, } async fn say_get( DbConn(db_conn): DbConn, Query(query): Query, ) -> Result { let key = query.key.clone(); let maybe_api_key = db_conn .interact(move |conn| { update(api_keys::table.filter(ApiKey::with_id(key))) .set(api_keys::last_used_at.eq(diesel::dsl::now)) .returning(ApiKey::as_returning()) .get_result(conn) .optional() }) .await .unwrap() .context("unable to get API key")?; let api_key = match maybe_api_key { Some(api_key) => api_key, None => return Err(AppError::ForbiddenError("key not accepted".to_string())), }; let project_name = query.project.to_lowercase(); let project = db_conn .interact(move |conn| { insert_into(projects::table) .values(( projects::id.eq(Uuid::now_v7()), projects::team_id.eq(api_key.team_id.clone()), projects::name.eq(project_name.clone()), )) .on_conflict((projects::team_id, projects::name)) .do_nothing() .execute(conn)?; // It would be nice to merge these two database operations into one, // but it's not trivial to do so without faking an update; refer to: // https://stackoverflow.com/a/42217872 Project::all() .filter(Project::with_team(api_key.team_id)) .filter(Project::with_name(project_name)) .first(conn) }) .await .unwrap() .context("unable to get project")?; db_conn .interact(move |conn| { insert_into(messages::table) .values(Message::values_now(project.id, query.message)) .execute(conn) }) .await .unwrap() .context("unable to insert message")?; #[derive(Serialize)] struct ResponseBody { ok: bool, } Ok(Json(ResponseBody { ok: true })) }