forked from 2sys/shoutdotdev
90 lines
2.7 KiB
Rust
90 lines
2.7 KiB
Rust
|
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<AppState> {
|
||
|
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<SayQuery>,
|
||
|
) -> Result<impl IntoResponse, AppError> {
|
||
|
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 }))
|
||
|
}
|