prototype code upload
This commit is contained in:
parent
f5e8b4093a
commit
0f27ca80fb
4 changed files with 69 additions and 27 deletions
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
Loading…
Reference in a new issue