2022-01-01 16:32:55 +01:00
|
|
|
use axum::extract::{Multipart, Path};
|
2021-12-29 16:11:27 +01:00
|
|
|
use axum::http::StatusCode;
|
2022-03-06 16:53:02 +01:00
|
|
|
use axum::response::{IntoResponse, Response};
|
2022-03-24 20:07:03 +01:00
|
|
|
use axum::{body, Json};
|
2022-02-28 22:44:06 +01:00
|
|
|
use diesel::OptionalExtension;
|
2021-12-30 23:41:47 +01:00
|
|
|
use rand::distributions::Alphanumeric;
|
2021-12-19 00:16:46 +01:00
|
|
|
use rand::Rng;
|
|
|
|
use serde::{Deserialize, Serialize};
|
2022-03-06 16:53:02 +01:00
|
|
|
use serde_json::{self, json, value::Value as JsonValue};
|
2021-12-19 00:16:46 +01:00
|
|
|
use std::io::Cursor;
|
2022-01-01 16:32:55 +01:00
|
|
|
use std::path::PathBuf;
|
2022-04-15 19:03:58 +02:00
|
|
|
use thiserror;
|
2021-12-19 00:16:46 +01:00
|
|
|
|
2022-07-06 22:41:27 +02:00
|
|
|
use crate::db::bots::{self, BotVersion};
|
2022-06-20 22:01:26 +02:00
|
|
|
use crate::db::ratings::{self, RankedBot};
|
2021-12-19 00:16:46 +01:00
|
|
|
use crate::db::users::User;
|
2022-02-28 22:44:06 +01:00
|
|
|
use crate::modules::bots::save_code_bundle;
|
2022-01-01 16:32:55 +01:00
|
|
|
use crate::{DatabaseConnection, BOTS_DIR};
|
2021-12-19 00:16:46 +01:00
|
|
|
use bots::Bot;
|
|
|
|
|
2022-02-28 22:44:06 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct SaveBotParams {
|
|
|
|
pub bot_name: String,
|
|
|
|
pub code: String,
|
|
|
|
}
|
2022-03-06 16:53:02 +01:00
|
|
|
|
2022-04-15 19:03:58 +02:00
|
|
|
#[derive(Debug, thiserror::Error)]
|
2022-03-06 16:53:02 +01:00
|
|
|
pub enum SaveBotError {
|
2022-04-15 19:03:58 +02:00
|
|
|
#[error("database error")]
|
|
|
|
DatabaseError(#[from] diesel::result::Error),
|
|
|
|
#[error("validation failed")]
|
|
|
|
ValidationFailed(Vec<&'static str>),
|
|
|
|
#[error("bot name already exists")]
|
2022-03-06 16:53:02 +01:00
|
|
|
BotNameTaken,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl IntoResponse for SaveBotError {
|
|
|
|
fn into_response(self) -> Response {
|
|
|
|
let (status, value) = match self {
|
2022-04-15 19:03:58 +02:00
|
|
|
SaveBotError::BotNameTaken => (
|
|
|
|
StatusCode::FORBIDDEN,
|
|
|
|
json!({ "error": {
|
|
|
|
"type": "bot_name_taken",
|
|
|
|
} }),
|
|
|
|
),
|
|
|
|
SaveBotError::DatabaseError(_e) => (
|
|
|
|
StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
json!({ "error": {
|
|
|
|
"type": "internal_server_error",
|
|
|
|
} }),
|
|
|
|
),
|
|
|
|
SaveBotError::ValidationFailed(errors) => (
|
|
|
|
StatusCode::UNPROCESSABLE_ENTITY,
|
|
|
|
json!({ "error": {
|
|
|
|
"type": "validation_failed",
|
|
|
|
"validation_errors": errors,
|
|
|
|
} }),
|
|
|
|
),
|
2022-03-06 16:53:02 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
let encoded = serde_json::to_vec(&value).expect("could not encode response value");
|
|
|
|
|
|
|
|
Response::builder()
|
|
|
|
.status(status)
|
|
|
|
.body(body::boxed(body::Full::from(encoded)))
|
|
|
|
.expect("could not build response")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-04-15 19:03:58 +02:00
|
|
|
pub fn validate_bot_name(bot_name: &str) -> Result<(), SaveBotError> {
|
|
|
|
let mut errors = Vec::new();
|
|
|
|
|
|
|
|
if bot_name.len() < 3 {
|
|
|
|
errors.push("bot name must be at least 3 characters long");
|
|
|
|
}
|
|
|
|
|
|
|
|
if bot_name.len() > 32 {
|
|
|
|
errors.push("bot name must be at most 32 characters long");
|
|
|
|
}
|
|
|
|
|
|
|
|
if !bot_name
|
|
|
|
.chars()
|
|
|
|
.all(|c| !c.is_uppercase() && (c.is_ascii_alphanumeric() || c == '_' || c == '-'))
|
|
|
|
{
|
|
|
|
errors.push("only lowercase alphanumeric characters, underscores, and dashes are allowed in bot names");
|
|
|
|
}
|
|
|
|
|
|
|
|
if errors.is_empty() {
|
|
|
|
Ok(())
|
|
|
|
} else {
|
|
|
|
Err(SaveBotError::ValidationFailed(errors))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-02-28 22:44:06 +01:00
|
|
|
pub async fn save_bot(
|
|
|
|
Json(params): Json<SaveBotParams>,
|
2022-03-24 20:07:03 +01:00
|
|
|
user: User,
|
2022-02-28 22:44:06 +01:00
|
|
|
conn: DatabaseConnection,
|
2022-03-06 16:53:02 +01:00
|
|
|
) -> Result<Json<Bot>, SaveBotError> {
|
2022-02-28 22:44:06 +01:00
|
|
|
let res = bots::find_bot_by_name(¶ms.bot_name, &conn)
|
|
|
|
.optional()
|
|
|
|
.expect("could not run query");
|
2022-04-15 19:03:58 +02:00
|
|
|
|
2022-02-28 22:44:06 +01:00
|
|
|
let bot = match res {
|
2022-03-24 20:07:03 +01:00
|
|
|
Some(existing_bot) => {
|
|
|
|
if existing_bot.owner_id == Some(user.id) {
|
|
|
|
existing_bot
|
|
|
|
} else {
|
|
|
|
return Err(SaveBotError::BotNameTaken);
|
|
|
|
}
|
|
|
|
}
|
2022-02-28 22:44:06 +01:00
|
|
|
None => {
|
2022-04-15 19:03:58 +02:00
|
|
|
validate_bot_name(¶ms.bot_name)?;
|
2022-02-28 22:44:06 +01:00
|
|
|
let new_bot = bots::NewBot {
|
2022-03-24 20:07:03 +01:00
|
|
|
owner_id: Some(user.id),
|
2022-02-28 22:44:06 +01:00
|
|
|
name: ¶ms.bot_name,
|
|
|
|
};
|
2022-03-13 15:20:03 +01:00
|
|
|
|
|
|
|
bots::create_bot(&new_bot, &conn).expect("could not create bot")
|
2022-02-28 22:44:06 +01:00
|
|
|
}
|
|
|
|
};
|
|
|
|
let _code_bundle =
|
|
|
|
save_code_bundle(¶ms.code, Some(bot.id), &conn).expect("failed to save code bundle");
|
2022-03-01 20:23:31 +01:00
|
|
|
Ok(Json(bot))
|
2022-02-28 22:44:06 +01:00
|
|
|
}
|
|
|
|
|
2021-12-19 00:16:46 +01:00
|
|
|
#[derive(Serialize, Deserialize, Debug)]
|
|
|
|
pub struct BotParams {
|
|
|
|
name: String,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn create_bot(
|
2021-12-29 16:11:27 +01:00
|
|
|
conn: DatabaseConnection,
|
2021-12-19 00:16:46 +01:00
|
|
|
user: User,
|
|
|
|
params: Json<BotParams>,
|
2021-12-29 16:11:27 +01:00
|
|
|
) -> (StatusCode, Json<Bot>) {
|
|
|
|
let bot_params = bots::NewBot {
|
2022-02-26 23:07:13 +01:00
|
|
|
owner_id: Some(user.id),
|
2021-12-29 16:11:27 +01:00
|
|
|
name: ¶ms.name,
|
|
|
|
};
|
|
|
|
let bot = bots::create_bot(&bot_params, &conn).unwrap();
|
|
|
|
(StatusCode::CREATED, Json(bot))
|
2021-12-19 00:16:46 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: handle errors
|
2021-12-31 11:24:25 +01:00
|
|
|
pub async fn get_bot(
|
|
|
|
conn: DatabaseConnection,
|
|
|
|
Path(bot_id): Path<i32>,
|
|
|
|
) -> Result<Json<JsonValue>, StatusCode> {
|
|
|
|
let bot = bots::find_bot(bot_id, &conn).map_err(|_| StatusCode::NOT_FOUND)?;
|
2022-07-06 22:41:27 +02:00
|
|
|
let bundles =
|
|
|
|
bots::find_bot_versions(bot.id, &conn).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
|
2021-12-31 11:24:25 +01:00
|
|
|
Ok(Json(json!({
|
|
|
|
"bot": bot,
|
|
|
|
"bundles": bundles,
|
|
|
|
})))
|
2021-12-30 23:40:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn get_my_bots(
|
|
|
|
conn: DatabaseConnection,
|
|
|
|
user: User,
|
|
|
|
) -> Result<Json<Vec<Bot>>, StatusCode> {
|
|
|
|
bots::find_bots_by_owner(user.id, &conn)
|
|
|
|
.map(Json)
|
|
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
2021-12-19 00:16:46 +01:00
|
|
|
}
|
|
|
|
|
2022-01-01 11:26:49 +01:00
|
|
|
pub async fn list_bots(conn: DatabaseConnection) -> Result<Json<Vec<Bot>>, StatusCode> {
|
|
|
|
bots::find_all_bots(&conn)
|
|
|
|
.map(Json)
|
|
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
|
|
|
}
|
|
|
|
|
2022-05-17 21:13:29 +02:00
|
|
|
pub async fn get_ranking(conn: DatabaseConnection) -> Result<Json<Vec<RankedBot>>, StatusCode> {
|
|
|
|
ratings::get_bot_ranking(&conn)
|
|
|
|
.map(Json)
|
|
|
|
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
|
|
|
|
}
|
|
|
|
|
2021-12-30 23:41:47 +01:00
|
|
|
// TODO: currently this only implements the happy flow
|
|
|
|
pub async fn upload_code_multipart(
|
2021-12-29 16:11:27 +01:00
|
|
|
conn: DatabaseConnection,
|
2021-12-19 00:16:46 +01:00
|
|
|
user: User,
|
2021-12-29 16:11:27 +01:00
|
|
|
Path(bot_id): Path<i32>,
|
2021-12-30 23:41:47 +01:00
|
|
|
mut multipart: Multipart,
|
2022-07-06 22:41:27 +02:00
|
|
|
) -> Result<Json<BotVersion>, StatusCode> {
|
2021-12-30 23:41:47 +01:00
|
|
|
let bots_dir = PathBuf::from(BOTS_DIR);
|
2021-12-19 00:16:46 +01:00
|
|
|
|
2021-12-30 23:41:47 +01:00
|
|
|
let bot = bots::find_bot(bot_id, &conn).map_err(|_| StatusCode::NOT_FOUND)?;
|
2021-12-19 00:16:46 +01:00
|
|
|
|
2022-02-26 23:07:13 +01:00
|
|
|
if Some(user.id) != bot.owner_id {
|
2021-12-30 23:41:47 +01:00
|
|
|
return Err(StatusCode::FORBIDDEN);
|
|
|
|
}
|
2021-12-19 00:16:46 +01:00
|
|
|
|
2021-12-30 23:41:47 +01:00
|
|
|
let data = multipart
|
|
|
|
.next_field()
|
|
|
|
.await
|
|
|
|
.map_err(|_| StatusCode::BAD_REQUEST)?
|
|
|
|
.ok_or(StatusCode::BAD_REQUEST)?
|
|
|
|
.bytes()
|
|
|
|
.await
|
|
|
|
.map_err(|_| StatusCode::BAD_REQUEST)?;
|
2021-12-19 00:16:46 +01:00
|
|
|
|
2021-12-30 23:41:47 +01:00
|
|
|
// TODO: this random path might be a bit redundant
|
|
|
|
let folder_name: String = rand::thread_rng()
|
|
|
|
.sample_iter(&Alphanumeric)
|
|
|
|
.take(16)
|
|
|
|
.map(char::from)
|
|
|
|
.collect();
|
2021-12-19 00:16:46 +01:00
|
|
|
|
2021-12-30 23:41:47 +01:00
|
|
|
zip::ZipArchive::new(Cursor::new(data))
|
|
|
|
.map_err(|_| StatusCode::BAD_REQUEST)?
|
|
|
|
.extract(bots_dir.join(&folder_name))
|
|
|
|
.map_err(|_| StatusCode::BAD_REQUEST)?;
|
2021-12-19 00:16:46 +01:00
|
|
|
|
2022-07-07 18:57:46 +02:00
|
|
|
let bot_version = bots::NewBotVersion {
|
2022-02-27 20:35:22 +01:00
|
|
|
bot_id: Some(bot.id),
|
2022-07-07 18:57:46 +02:00
|
|
|
code_bundle_path: Some(&folder_name),
|
|
|
|
container_digest: None,
|
2021-12-29 16:11:27 +01:00
|
|
|
};
|
|
|
|
let code_bundle =
|
2022-07-07 18:57:46 +02:00
|
|
|
bots::create_bot_version(&bot_version, &conn).expect("Failed to create code bundle");
|
2021-12-19 00:16:46 +01:00
|
|
|
|
2021-12-30 23:41:47 +01:00
|
|
|
Ok(Json(code_bundle))
|
2021-12-19 00:16:46 +01:00
|
|
|
}
|