prototype code upload

This commit is contained in:
Ilion Beyst 2021-12-30 23:41:47 +01:00
parent f5e8b4093a
commit 0f27ca80fb
4 changed files with 69 additions and 27 deletions

View file

@ -8,7 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
tokio = { version = "1.15", features = ["full"] } tokio = { version = "1.15", features = ["full"] }
hyper = "0.14" hyper = "0.14"
axum = { version = "0.4", features = ["json", "headers"] } axum = { version = "0.4", features = ["json", "headers", "multipart"] }
diesel = { version = "1.4.4", features = ["postgres", "chrono"] } diesel = { version = "1.4.4", features = ["postgres", "chrono"] }
bb8 = "0.7" bb8 = "0.7"
bb8-diesel = "0.2" bb8-diesel = "0.2"

View file

@ -36,7 +36,10 @@ pub async fn api() -> Router {
.route("/bots", post(routes::bots::create_bot)) .route("/bots", post(routes::bots::create_bot))
.route("/bots/my_bots", get(routes::bots::get_my_bots)) .route("/bots/my_bots", get(routes::bots::get_my_bots))
.route("/bots/:bot_id", get(routes::bots::get_bot)) .route("/bots/:bot_id", get(routes::bots::get_bot))
.route("/bots/:bot_id/upload", post(routes::bots::upload_bot_code)) .route(
"/bots/:bot_id/upload",
post(routes::bots::upload_code_multipart),
)
.layer(AddExtensionLayer::new(pool)); .layer(AddExtensionLayer::new(pool));
api api
} }

View file

@ -1,17 +1,21 @@
use axum::extract::{Path, RawBody}; use axum::extract::{Multipart, Path, RawBody};
use axum::http::StatusCode; use axum::http::StatusCode;
use axum::response::IntoResponse; use axum::response::IntoResponse;
use axum::Json; use axum::Json;
use rand::distributions::Alphanumeric;
use rand::Rng; use rand::Rng;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::io::Cursor; use std::io::Cursor;
use std::path; use std::path::{self, PathBuf};
use crate::db::bots::{self, CodeBundle}; use crate::db::bots::{self, CodeBundle};
use crate::db::users::User; use crate::db::users::User;
use crate::DatabaseConnection; use crate::DatabaseConnection;
use bots::Bot; use bots::Bot;
// TODO: make this a parameter
const BOTS_DIR: &str = "./data/bots";
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]
pub struct BotParams { pub struct BotParams {
name: String, name: String,
@ -46,41 +50,48 @@ pub async fn get_my_bots(
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR) .map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)
} }
// TODO: proper error handling // TODO: currently this only implements the happy flow
pub async fn upload_bot_code( pub async fn upload_code_multipart(
conn: DatabaseConnection, conn: DatabaseConnection,
user: User, user: User,
Path(bot_id): Path<i32>, Path(bot_id): Path<i32>,
RawBody(body): RawBody, mut multipart: Multipart,
) -> (StatusCode, Json<CodeBundle>) { ) -> Result<Json<CodeBundle>, StatusCode> {
// TODO: put in config somewhere let bots_dir = PathBuf::from(BOTS_DIR);
let data_path = "./data/bots";
let bot = bots::find_bot(bot_id, &conn).expect("Bot not found"); let bot = bots::find_bot(bot_id, &conn).map_err(|_| StatusCode::NOT_FOUND)?;
assert_eq!(user.id, bot.owner_id); if user.id != bot.owner_id {
return Err(StatusCode::FORBIDDEN);
}
// generate a random filename let data = multipart
let token: [u8; 16] = rand::thread_rng().gen(); .next_field()
let name = base64::encode(&token); .await
.map_err(|_| StatusCode::BAD_REQUEST)?
.ok_or(StatusCode::BAD_REQUEST)?
.bytes()
.await
.map_err(|_| StatusCode::BAD_REQUEST)?;
let path = path::Path::new(data_path).join(name); // TODO: this random path might be a bit redundant
// let capped_buf = data.open(10usize.megabytes()).into_bytes().await.unwrap(); let folder_name: String = rand::thread_rng()
// assert!(capped_buf.is_complete()); .sample_iter(&Alphanumeric)
// let buf = capped_buf.into_inner(); .take(16)
let buf = hyper::body::to_bytes(body).await.unwrap(); .map(char::from)
.collect();
zip::ZipArchive::new(Cursor::new(buf)) zip::ZipArchive::new(Cursor::new(data))
.unwrap() .map_err(|_| StatusCode::BAD_REQUEST)?
.extract(&path) .extract(bots_dir.join(&folder_name))
.unwrap(); .map_err(|_| StatusCode::BAD_REQUEST)?;
let bundle = bots::NewCodeBundle { let bundle = bots::NewCodeBundle {
bot_id: bot.id, bot_id: bot.id,
path: path.to_str().unwrap(), path: &folder_name,
}; };
let code_bundle = let code_bundle =
bots::create_code_bundle(&bundle, &conn).expect("Failed to create code bundle"); bots::create_code_bundle(&bundle, &conn).expect("Failed to create code bundle");
(StatusCode::CREATED, Json(code_bundle)) Ok(Json(code_bundle))
} }

View file

@ -6,7 +6,7 @@
const res = await fetch(`/api/bots/${page.params["bot_id"]}`, { const res = await fetch(`/api/bots/${page.params["bot_id"]}`, {
headers: { headers: {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${token}`, "Authorization": `Bearer ${token}`,
}, },
}); });
@ -27,8 +27,36 @@
<script lang="ts"> <script lang="ts">
export let bot: object; export let bot: object;
let files;
async function submitCode() {
console.log("click");
const token = get_session_token();
const formData = new FormData();
formData.append("File", files[0]);
const res = await fetch(`/api/bots/${bot["id"]}/upload`, {
method: "POST",
headers: {
// the content type header will be set by the browser
"Authorization": `Bearer ${token}`,
},
body: formData,
});
console.log(res.statusText);
}
</script> </script>
<div> <div>
{bot["name"]} {bot["name"]}
</div> </div>
<div>Upload code</div>
<form on:submit|preventDefault={submitCode}>
<input type="file" bind:files/>
<button type="submit">Submit</button>
</form>