Compare commits
2 Commits
2cabcc46aa
...
00f76d4f4b
Author | SHA1 | Date |
---|---|---|
Savanni D'Gerinel | 00f76d4f4b | |
Savanni D'Gerinel | 791adbb085 |
File diff suppressed because it is too large
Load Diff
|
@ -7,29 +7,28 @@ edition = "2018"
|
|||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
build_html = { version = "2" }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
hex-string = "0.1.0"
|
||||
http = { version = "0.2" }
|
||||
image = "0.23.5"
|
||||
iron = "0.6.1"
|
||||
logger = "*"
|
||||
mime = "0.3.16"
|
||||
mime_guess = "2.0.3"
|
||||
mustache = "0.9.0"
|
||||
orizentic = { path = "../orizentic" }
|
||||
params = "*"
|
||||
router = "*"
|
||||
serde_json = "*"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sha2 = "0.10"
|
||||
thiserror = "1.0.20"
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
uuid = { version = "0.4", features = [ "serde", "v4" ] }
|
||||
warp = { version = "0.3" }
|
||||
pretty_env_logger = { version = "0.5" }
|
||||
log = { version = "0.4" }
|
||||
bytes = { version = "1" }
|
||||
futures-util = { version = "0.3" }
|
||||
cool_asserts = { version = "2" }
|
||||
tempdir = { version = "0.3" }
|
||||
build_html = { version = "2" }
|
||||
bytes = { version = "1" }
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
futures-util = { version = "0.3" }
|
||||
hex-string = "0.1.0"
|
||||
http = { version = "0.2" }
|
||||
image = "0.23.5"
|
||||
logger = "*"
|
||||
log = { version = "0.4" }
|
||||
mime = "0.3.16"
|
||||
mime_guess = "2.0.3"
|
||||
pretty_env_logger = { version = "0.5" }
|
||||
serde_json = "*"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
sha2 = "0.10"
|
||||
sqlx = { version = "0.7", features = [ "runtime-tokio", "sqlite" ] }
|
||||
thiserror = "1.0.20"
|
||||
tokio = { version = "1", features = [ "full" ] }
|
||||
uuid = { version = "0.4", features = [ "serde", "v4" ] }
|
||||
warp = { version = "0.3" }
|
||||
|
||||
[dev-dependencies]
|
||||
cool_asserts = { version = "2" }
|
||||
tempdir = { version = "0.3" }
|
||||
|
||||
|
|
|
@ -0,0 +1,123 @@
|
|||
use build_html::Html;
|
||||
use http::{Error, StatusCode};
|
||||
use std::{collections::HashMap, future::Future};
|
||||
use warp::http::Response;
|
||||
|
||||
use crate::{pages, App, AuthToken, FileHandle, FileId, FileInfo, ReadFileError, SessionToken};
|
||||
|
||||
pub async fn handle_index(
|
||||
app: App,
|
||||
token: Option<SessionToken>,
|
||||
) -> Result<Response<String>, Error> {
|
||||
match token {
|
||||
Some(token) => match app.auth_session(token).await {
|
||||
Ok(_) => render_gallery_page(app).await,
|
||||
Err(err) => render_auth_page(Some(format!("authentication refused: {:?}", err))),
|
||||
},
|
||||
None => render_auth_page(None),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render_auth_page(message: Option<String>) -> Result<Response<String>, Error> {
|
||||
Response::builder()
|
||||
.status(StatusCode::OK)
|
||||
.body(pages::auth(message).to_html_string())
|
||||
}
|
||||
|
||||
pub async fn render_gallery_page(app: App) -> Result<Response<String>, Error> {
|
||||
match app.list_files().await {
|
||||
Ok(ids) => {
|
||||
let mut files = vec![];
|
||||
for id in ids.into_iter() {
|
||||
let file = app.get_file(&id).await;
|
||||
files.push(file);
|
||||
}
|
||||
Response::builder()
|
||||
.header("content-type", "text/html")
|
||||
.status(StatusCode::OK)
|
||||
.body(pages::gallery(files).to_html_string())
|
||||
}
|
||||
Err(_) => Response::builder()
|
||||
.header("content-type", "text/html")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn thumbnail(
|
||||
app: App,
|
||||
id: String,
|
||||
old_etags: Option<String>,
|
||||
) -> Result<Response<Vec<u8>>, Error> {
|
||||
match app.get_file(&FileId::from(id)).await {
|
||||
Ok(file) => serve_file(file.info.clone(), || file.thumbnail(), old_etags),
|
||||
Err(_err) => Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn file(
|
||||
app: App,
|
||||
id: String,
|
||||
old_etags: Option<String>,
|
||||
) -> Result<Response<Vec<u8>>, Error> {
|
||||
match app.get_file(&FileId::from(id)).await {
|
||||
Ok(file) => serve_file(file.info.clone(), || file.thumbnail(), old_etags),
|
||||
Err(_err) => Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_auth(
|
||||
app: App,
|
||||
form: HashMap<String, String>,
|
||||
) -> Result<http::Response<String>, Error> {
|
||||
match form.get("token") {
|
||||
Some(token) => match app
|
||||
.auth_token(AuthToken::from(AuthToken::from(token.clone())))
|
||||
.await
|
||||
{
|
||||
Ok(_session_token) => render_gallery_page(app).await,
|
||||
Err(_) => render_auth_page(Some(format!("invalid auth token"))),
|
||||
},
|
||||
None => render_auth_page(Some(format!("no token available"))),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_upload(
|
||||
_app: App,
|
||||
_token: SessionToken,
|
||||
) -> Result<http::Response<String>, Error> {
|
||||
println!("handle_upload");
|
||||
Response::builder()
|
||||
.status(StatusCode::NOT_IMPLEMENTED)
|
||||
.body("".to_owned())
|
||||
}
|
||||
|
||||
fn serve_file<F>(
|
||||
info: FileInfo,
|
||||
file: F,
|
||||
old_etags: Option<String>,
|
||||
) -> http::Result<http::Response<Vec<u8>>>
|
||||
where
|
||||
F: FnOnce() -> Result<Vec<u8>, ReadFileError>,
|
||||
{
|
||||
match old_etags {
|
||||
Some(old_etags) if old_etags != info.hash => Response::builder()
|
||||
.header("content-type", info.file_type)
|
||||
.status(StatusCode::NOT_MODIFIED)
|
||||
.body(vec![]),
|
||||
_ => match file() {
|
||||
Ok(content) => Response::builder()
|
||||
.header("content-type", info.file_type)
|
||||
.header("etag", info.hash)
|
||||
.status(StatusCode::OK)
|
||||
.body(content),
|
||||
Err(_) => Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(vec![]),
|
||||
},
|
||||
}
|
||||
}
|
|
@ -1,250 +1,46 @@
|
|||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
use handlers::{file, handle_auth, handle_upload, thumbnail};
|
||||
use http::status::StatusCode;
|
||||
// use mustache::{compile_path, Template};
|
||||
// use orizentic::{Permissions, ResourceName, Secret};
|
||||
use build_html::Html;
|
||||
use bytes::Buf;
|
||||
use futures_util::StreamExt;
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
convert::Infallible,
|
||||
io::Read,
|
||||
net::{IpAddr, Ipv4Addr, SocketAddr},
|
||||
path::PathBuf,
|
||||
sync::{Arc, RwLock},
|
||||
};
|
||||
use warp::{filters::multipart::Part, Filter};
|
||||
use store::Username;
|
||||
use warp::{filters::multipart::Part, Filter, Rejection};
|
||||
|
||||
// mod cookies;
|
||||
mod handlers;
|
||||
mod html;
|
||||
// mod middleware;
|
||||
mod pages;
|
||||
mod store;
|
||||
|
||||
pub use store::{FileHandle, FileId, FileInfo, ReadFileError, Store};
|
||||
pub use handlers::handle_index;
|
||||
pub use store::{
|
||||
App, AuthDB, AuthToken, FileHandle, FileId, FileInfo, ReadFileError, SessionToken, Store,
|
||||
};
|
||||
|
||||
/*
|
||||
fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool {
|
||||
let Permissions(perms) = permissions;
|
||||
ResourceName(String::from(
|
||||
"https://savanni.luminescent-dreams.com/file-service/",
|
||||
)) == *resource
|
||||
&& perms.contains(&String::from("admin"))
|
||||
}
|
||||
|
||||
pub fn compare_etags(info: FileInfo, etag_list: &headers::IfNoneMatch) -> bool {
|
||||
let current_etag = headers::EntityTag::new(false, info.hash);
|
||||
match etag_list {
|
||||
headers::IfNoneMatch::Any => false,
|
||||
headers::IfNoneMatch::Items(lst) => lst.iter().any(|etag| etag.weak_eq(¤t_etag)),
|
||||
async fn authenticate_user(app: App, auth_token: String) -> Result<Username, warp::Rejection> {
|
||||
match app.auth_session(SessionToken::from(auth_token)).await {
|
||||
Ok(username) => Ok(username),
|
||||
Err(_) => Err(warp::reject::not_found()),
|
||||
}
|
||||
}
|
||||
|
||||
mod files {
|
||||
use super::*;
|
||||
|
||||
pub struct GetHandler {
|
||||
pub app: Arc<RwLock<App>>,
|
||||
}
|
||||
|
||||
impl Handler for GetHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let app = self.app.read().unwrap();
|
||||
let capture = req.extensions.get::<Router>().unwrap().clone();
|
||||
let old_etags = req.headers.get::<headers::IfNoneMatch>();
|
||||
match capture.find("id") {
|
||||
Some(id) => {
|
||||
let info = app.get_metadata(String::from(id));
|
||||
match (info, old_etags) {
|
||||
(Ok(info_), Some(if_none_match)) => {
|
||||
if compare_etags(info_, if_none_match) {
|
||||
return Ok(Response::with(status::NotModified));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
match app.get_file(String::from(id)) {
|
||||
Ok((info, stream)) => Ok(Response::with((
|
||||
status::Ok,
|
||||
Header(headers::ContentType(
|
||||
info.file_type.parse::<iron::mime::Mime>().unwrap(),
|
||||
)),
|
||||
Header(headers::ETag(headers::EntityTag::new(false, info.hash))),
|
||||
BodyReader(stream),
|
||||
))),
|
||||
Err(_err) => Ok(Response::with(status::NotFound)),
|
||||
}
|
||||
}
|
||||
_ => Ok(Response::with(status::BadRequest)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct GetThumbnailHandler {
|
||||
pub app: Arc<RwLock<App>>,
|
||||
}
|
||||
|
||||
impl Handler for GetThumbnailHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let app = self.app.read().unwrap();
|
||||
let capture = req.extensions.get::<Router>().unwrap().clone();
|
||||
let old_etags = req.headers.get::<headers::IfNoneMatch>();
|
||||
match capture.find("id") {
|
||||
Some(id) => {
|
||||
let info = app.get_metadata(String::from(id));
|
||||
match (info, old_etags) {
|
||||
(Ok(info_), Some(if_none_match)) => {
|
||||
if compare_etags(info_, if_none_match) {
|
||||
return Ok(Response::with(status::NotModified));
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
match app.get_thumbnail(id) {
|
||||
Ok((info, stream)) => Ok(Response::with((
|
||||
status::Ok,
|
||||
Header(headers::ContentType(
|
||||
info.file_type.parse::<iron::mime::Mime>().unwrap(),
|
||||
)),
|
||||
Header(headers::ETag(headers::EntityTag::new(false, info.hash))),
|
||||
BodyReader(stream),
|
||||
))),
|
||||
Err(_err) => Ok(Response::with(status::NotFound)),
|
||||
}
|
||||
}
|
||||
_ => Ok(Response::with(status::BadRequest)),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct PostHandler {
|
||||
pub app: Arc<RwLock<App>>,
|
||||
}
|
||||
|
||||
impl Handler for PostHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let mut app = self.app.write().unwrap();
|
||||
let m_token = req.extensions.get::<Authentication>();
|
||||
match m_token {
|
||||
Some(token) => {
|
||||
if token.check_authorizations(is_admin) {
|
||||
let params = req.get_ref::<Params>().unwrap();
|
||||
if let Value::File(f_info) = params.get("file").unwrap() {
|
||||
match app.add_file(
|
||||
&f_info.path,
|
||||
&f_info.filename.clone().map(|fname| PathBuf::from(fname)),
|
||||
) {
|
||||
Ok(_) => Ok(Response::with((
|
||||
status::MovedPermanently,
|
||||
Redirect(router::url_for(req, "index", HashMap::new())),
|
||||
))),
|
||||
Err(_) => Ok(Response::with(status::InternalServerError)),
|
||||
}
|
||||
} else {
|
||||
Ok(Response::with(status::BadRequest))
|
||||
}
|
||||
} else {
|
||||
Ok(Response::with(status::Forbidden))
|
||||
}
|
||||
}
|
||||
None => Ok(Response::with(status::Forbidden)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DeleteHandler {
|
||||
pub app: Arc<RwLock<App>>,
|
||||
}
|
||||
|
||||
impl Handler for DeleteHandler {
|
||||
fn handle(&self, req: &mut Request) -> IronResult<Response> {
|
||||
let mut app = self.app.write().unwrap();
|
||||
let capture = req.extensions.get::<Router>().unwrap().clone();
|
||||
let m_token = req.extensions.get::<Authentication>();
|
||||
match m_token {
|
||||
Some(token) => {
|
||||
if token.check_authorizations(is_admin) {
|
||||
match capture.find("id") {
|
||||
Some(id) => match app.delete_file(String::from(id)) {
|
||||
Ok(()) => Ok(Response::with((
|
||||
status::MovedPermanently,
|
||||
Redirect(router::url_for(req, "index", HashMap::new())),
|
||||
))),
|
||||
Err(_) => Ok(Response::with(status::InternalServerError)),
|
||||
},
|
||||
None => Ok(Response::with(status::BadRequest)),
|
||||
}
|
||||
} else {
|
||||
Ok(Response::with(status::Forbidden))
|
||||
}
|
||||
}
|
||||
None => Ok(Response::with(status::Forbidden)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn css(_: &mut Request) -> IronResult<Response> {
|
||||
let mut css: String = String::from("");
|
||||
File::open("templates/style.css")
|
||||
.unwrap()
|
||||
.read_to_string(&mut css)
|
||||
.unwrap();
|
||||
Ok(Response::with((
|
||||
status::Ok,
|
||||
Header(headers::ContentType(iron::mime::Mime(
|
||||
iron::mime::TopLevel::Text,
|
||||
iron::mime::SubLevel::Css,
|
||||
vec![],
|
||||
))),
|
||||
css,
|
||||
)))
|
||||
}
|
||||
|
||||
fn script(_: &mut Request) -> IronResult<Response> {
|
||||
let mut js: String = String::from("");
|
||||
File::open("templates/script.js")
|
||||
.unwrap()
|
||||
.read_to_string(&mut js)
|
||||
.unwrap();
|
||||
Ok(Response::with((
|
||||
status::Ok,
|
||||
Header(headers::ContentType(iron::mime::Mime(
|
||||
iron::mime::TopLevel::Text,
|
||||
iron::mime::SubLevel::Javascript,
|
||||
vec![],
|
||||
))),
|
||||
js,
|
||||
)))
|
||||
}
|
||||
*/
|
||||
|
||||
fn serve_file<F>(
|
||||
info: FileInfo,
|
||||
file: F,
|
||||
old_etags: Option<String>,
|
||||
) -> http::Result<http::Response<Vec<u8>>>
|
||||
where
|
||||
F: FnOnce() -> Result<Vec<u8>, ReadFileError>,
|
||||
{
|
||||
match old_etags {
|
||||
Some(old_etags) if old_etags != info.hash => warp::http::Response::builder()
|
||||
.header("content-type", info.file_type)
|
||||
.status(StatusCode::NOT_MODIFIED)
|
||||
.body(vec![]),
|
||||
_ => match file() {
|
||||
Ok(content) => warp::http::Response::builder()
|
||||
.header("content-type", info.file_type)
|
||||
.header("etag", info.hash)
|
||||
.status(StatusCode::OK)
|
||||
.body(content),
|
||||
Err(_) => warp::http::Response::builder()
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body(vec![]),
|
||||
},
|
||||
}
|
||||
}
|
||||
/*
|
||||
*/
|
||||
|
||||
/*
|
||||
async fn collect_content(
|
||||
mut part: Part,
|
||||
) -> Result<(Option<String>, Option<String>, Vec<u8>), String> {
|
||||
|
@ -261,7 +57,9 @@ async fn collect_content(
|
|||
content,
|
||||
))
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
async fn collect_multipart(
|
||||
mut stream: warp::filters::multipart::FormData,
|
||||
) -> Result<Vec<(Option<String>, Option<String>, Vec<u8>)>, warp::Error> {
|
||||
|
@ -276,10 +74,12 @@ async fn collect_multipart(
|
|||
|
||||
Ok(content)
|
||||
}
|
||||
*/
|
||||
|
||||
/*
|
||||
async fn handle_upload(
|
||||
form: warp::filters::multipart::FormData,
|
||||
app: Arc<RwLock<Store>>,
|
||||
app: App,
|
||||
) -> warp::http::Result<warp::http::Response<String>> {
|
||||
let files = collect_multipart(form).await;
|
||||
match files {
|
||||
|
@ -287,7 +87,7 @@ async fn handle_upload(
|
|||
for (_, filename, content) in files {
|
||||
match filename {
|
||||
Some(filename) => {
|
||||
app.write().unwrap().add_file(filename, content).unwrap();
|
||||
app.add_file(filename, content).unwrap();
|
||||
}
|
||||
None => {
|
||||
return warp::http::Response::builder()
|
||||
|
@ -310,150 +110,85 @@ async fn handle_upload(
|
|||
.status(StatusCode::SEE_OTHER)
|
||||
.body("".to_owned())
|
||||
}
|
||||
*/
|
||||
|
||||
fn with_app(app: App) -> impl Filter<Extract = (App,), Error = Infallible> + Clone {
|
||||
warp::any().map(move || app.clone())
|
||||
}
|
||||
|
||||
fn maybe_with_session() -> impl Filter<Extract = (Option<SessionToken>,), Error = Rejection> + Copy
|
||||
{
|
||||
warp::any()
|
||||
.and(warp::header::optional::<String>("cookie"))
|
||||
.map(|cookies| {
|
||||
println!("cookies retrieved: {:?}", cookies);
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
fn with_session() -> impl Filter<Extract = (SessionToken,), Error = Rejection> + Copy {
|
||||
warp::any()
|
||||
.and(warp::header::<String>("cookie"))
|
||||
.map(|token: String| SessionToken::from(token))
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
/*
|
||||
let auth_db_path = std::env::var("ORIZENTIC_DB").unwrap();
|
||||
let secret = Secret(Vec::from(
|
||||
std::env::var("ORIZENTIC_SECRET").unwrap().as_bytes(),
|
||||
));
|
||||
let auth_middleware = Authentication::new(secret, auth_db_path);
|
||||
|
||||
let mut router = Router::new();
|
||||
|
||||
router.post("/", files::PostHandler { app: app.clone() }, "upload-file");
|
||||
|
||||
router.delete(
|
||||
"/:id",
|
||||
files::DeleteHandler { app: app.clone() },
|
||||
"delete-file",
|
||||
);
|
||||
router.get("/css", css, "styles");
|
||||
router.get("/script", script, "script");
|
||||
|
||||
let mut chain = Chain::new(router);
|
||||
chain.link_before(auth_middleware);
|
||||
chain.link_before(RestForm {});
|
||||
|
||||
Iron::new(chain).http("0.0.0.0:3000").unwrap();
|
||||
*/
|
||||
|
||||
/*
|
||||
let root = warp::path!().and(warp::get()).map({
|
||||
|| {
|
||||
warp::http::Response::builder()
|
||||
.header("content-type", "text/html")
|
||||
.status(StatusCode::NOT_MODIFIED)
|
||||
.body(())
|
||||
}
|
||||
});
|
||||
let server = warp::serve(root);
|
||||
server
|
||||
.run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002))
|
||||
.await;
|
||||
*/
|
||||
|
||||
pretty_env_logger::init();
|
||||
|
||||
let app = Arc::new(RwLock::new(Store::new(PathBuf::from(
|
||||
&std::env::var("FILE_SHARE_DIR").unwrap(),
|
||||
))));
|
||||
let authdb = AuthDB::new(PathBuf::from(":memory:")).await.unwrap();
|
||||
let store = Store::new(PathBuf::from(&std::env::var("FILE_SHARE_DIR").unwrap()));
|
||||
|
||||
let app_filter = {
|
||||
let app = App::new(authdb, store);
|
||||
|
||||
/*
|
||||
let with_app = {
|
||||
let app = app.clone();
|
||||
warp::any().map(move || app.clone())
|
||||
};
|
||||
*/
|
||||
|
||||
let log = warp::log("file_service");
|
||||
let root = warp::path!()
|
||||
.and(warp::get())
|
||||
.and(with_app(app.clone()))
|
||||
.and(maybe_with_session())
|
||||
.then(handle_index);
|
||||
|
||||
let root = warp::path!().and(warp::get()).and(app_filter.clone()).map({
|
||||
move |app: Arc<RwLock<Store>>| {
|
||||
info!("root handler");
|
||||
let app = app.read().unwrap();
|
||||
match app.list_files() {
|
||||
Ok(ids) => {
|
||||
let files = ids
|
||||
.into_iter()
|
||||
.map(|id| app.get_file(&id))
|
||||
.collect::<Vec<Result<FileHandle, ReadFileError>>>();
|
||||
warp::http::Response::builder()
|
||||
.header("content-type", "text/html")
|
||||
.status(StatusCode::OK)
|
||||
.body(pages::index(files).to_html_string())
|
||||
}
|
||||
Err(_) => warp::http::Response::builder()
|
||||
.header("content-type", "text/html")
|
||||
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
||||
.body("".to_owned()),
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let post_upload_handler = warp::path!("upload")
|
||||
.and(warp::post())
|
||||
.and(warp::filters::multipart::form().max_length(1024 * 1024 * 32))
|
||||
.and(app_filter.clone())
|
||||
.then(
|
||||
|form: warp::filters::multipart::FormData, app: Arc<RwLock<Store>>| {
|
||||
handle_upload(form, app)
|
||||
},
|
||||
);
|
||||
|
||||
/*
|
||||
let post_delete_handler = warp::path!(String)
|
||||
let auth = warp::path!("auth")
|
||||
.and(warp::post())
|
||||
.and(with_app(app.clone()))
|
||||
.and(warp::filters::body::form())
|
||||
.map(|id: String, form: HashMap<String, String>| {
|
||||
info!("post_delete {}", id);
|
||||
info!("form: {:?}", form);
|
||||
warp::http::Response::builder()
|
||||
.header("location", "/")
|
||||
.status(StatusCode::SEE_OTHER)
|
||||
.body(vec![])
|
||||
});
|
||||
*/
|
||||
.then(handle_auth);
|
||||
|
||||
let upload_handler = warp::path!("upload")
|
||||
.and(warp::post())
|
||||
.and(with_app(app.clone()))
|
||||
.and(with_session())
|
||||
.then(handle_upload);
|
||||
|
||||
let thumbnail = warp::path!(String / "tn")
|
||||
.and(warp::get())
|
||||
.and(warp::header::optional::<String>("if-none-match"))
|
||||
.map({
|
||||
let app = app.clone();
|
||||
move |id: String, old_etags: Option<String>| match app
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_file(&FileId::from(id))
|
||||
{
|
||||
Ok(file) => serve_file(file.info.clone(), || file.thumbnail(), old_etags),
|
||||
Err(_err) => warp::http::Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(vec![]),
|
||||
}
|
||||
});
|
||||
.and(with_app(app.clone()))
|
||||
.then(move |id, old_etags, app: App| thumbnail(app, id, old_etags));
|
||||
|
||||
let file = warp::path!(String)
|
||||
.and(warp::get())
|
||||
.and(warp::header::optional::<String>("if-none-match"))
|
||||
.map({
|
||||
let app = app.clone();
|
||||
move |id: String, old_etags: Option<String>| match app
|
||||
.read()
|
||||
.unwrap()
|
||||
.get_file(&FileId::from(id))
|
||||
{
|
||||
Ok(file) => serve_file(file.info.clone(), || file.content(), old_etags),
|
||||
Err(_err) => warp::http::Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(vec![]),
|
||||
}
|
||||
});
|
||||
.and(with_app(app.clone()))
|
||||
.then(move |id, old_etags, app: App| file(app, id, old_etags));
|
||||
|
||||
let server = warp::serve(
|
||||
root.or(post_upload_handler)
|
||||
.or(thumbnail)
|
||||
.or(file)
|
||||
.with(log),
|
||||
root.or(auth).with(log), /*
|
||||
root.or(auth)
|
||||
.or(thumbnail)
|
||||
.or(file)
|
||||
.or(upload_handler)
|
||||
.with(log),
|
||||
*/
|
||||
);
|
||||
|
||||
server
|
||||
.run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002))
|
||||
.await;
|
||||
|
|
|
@ -4,7 +4,22 @@ use crate::{
|
|||
};
|
||||
use build_html::{self, Container, ContainerType, Html, HtmlContainer};
|
||||
|
||||
pub fn index(handles: Vec<Result<FileHandle, ReadFileError>>) -> build_html::HtmlPage {
|
||||
pub fn auth(_message: Option<String>) -> build_html::HtmlPage {
|
||||
build_html::HtmlPage::new()
|
||||
.with_title("Authentication")
|
||||
.with_html(
|
||||
Form::new()
|
||||
.with_path("/auth")
|
||||
.with_method("post")
|
||||
.with_container(
|
||||
Container::new(ContainerType::Div)
|
||||
.with_html(Input::new("token", "token").with_id("for-token-input"))
|
||||
.with_html(Label::new("for-token-input", "Authentication Token")),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn gallery(handles: Vec<Result<FileHandle, ReadFileError>>) -> build_html::HtmlPage {
|
||||
let mut page = build_html::HtmlPage::new()
|
||||
.with_title("Admin list of files")
|
||||
.with_header(1, "Admin list of files")
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::sqlite::SqlitePool;
|
||||
use std::collections::HashSet;
|
||||
use std::{ops::Deref, path::PathBuf};
|
||||
use std::{ops::Deref, path::PathBuf, sync::Arc};
|
||||
use thiserror::Error;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
mod filehandle;
|
||||
mod fileinfo;
|
||||
|
@ -57,6 +59,117 @@ pub enum ReadFileError {
|
|||
IOError(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum AuthError {
|
||||
#[error("database failed")]
|
||||
SqlError,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||
pub struct Username(String);
|
||||
|
||||
impl From<String> for Username {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for Username {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Username> for PathBuf {
|
||||
fn from(s: Username) -> Self {
|
||||
Self::from(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&Username> for PathBuf {
|
||||
fn from(s: &Username) -> Self {
|
||||
let Username(s) = s;
|
||||
Self::from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Username {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||
pub struct AuthToken(String);
|
||||
|
||||
impl From<String> for AuthToken {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for AuthToken {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AuthToken> for PathBuf {
|
||||
fn from(s: AuthToken) -> Self {
|
||||
Self::from(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&AuthToken> for PathBuf {
|
||||
fn from(s: &AuthToken) -> Self {
|
||||
let AuthToken(s) = s;
|
||||
Self::from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for AuthToken {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||
pub struct SessionToken(String);
|
||||
|
||||
impl From<String> for SessionToken {
|
||||
fn from(s: String) -> Self {
|
||||
Self(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&str> for SessionToken {
|
||||
fn from(s: &str) -> Self {
|
||||
Self(s.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl From<SessionToken> for PathBuf {
|
||||
fn from(s: SessionToken) -> Self {
|
||||
Self::from(&s)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&SessionToken> for PathBuf {
|
||||
fn from(s: &SessionToken) -> Self {
|
||||
let SessionToken(s) = s;
|
||||
Self::from(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SessionToken {
|
||||
type Target = String;
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Hash, PartialEq, Eq)]
|
||||
pub struct FileId(String);
|
||||
|
||||
|
@ -104,6 +217,69 @@ impl FileRoot for Context {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct App {
|
||||
authdb: Arc<RwLock<AuthDB>>,
|
||||
store: Arc<RwLock<Store>>,
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub fn new(authdb: AuthDB, store: Store) -> Self {
|
||||
Self {
|
||||
authdb: Arc::new(RwLock::new(authdb)),
|
||||
store: Arc::new(RwLock::new(store)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn auth_token(&self, token: AuthToken) -> Result<SessionToken, AuthError> {
|
||||
self.authdb.read().await.auth_token(token).await
|
||||
}
|
||||
|
||||
pub async fn auth_session(&self, token: SessionToken) -> Result<Username, AuthError> {
|
||||
self.authdb.read().await.auth_session(token).await
|
||||
}
|
||||
|
||||
pub async fn list_files(&self) -> Result<HashSet<FileId>, ReadFileError> {
|
||||
self.store.read().await.list_files()
|
||||
}
|
||||
|
||||
pub async fn get_file(&self, id: &FileId) -> Result<FileHandle, ReadFileError> {
|
||||
self.store.read().await.get_file(id)
|
||||
}
|
||||
|
||||
pub async fn add_file(
|
||||
&self,
|
||||
filename: String,
|
||||
content: Vec<u8>,
|
||||
) -> Result<FileHandle, WriteFileError> {
|
||||
self.store.write().await.add_file(filename, content)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct AuthDB {
|
||||
pool: SqlitePool,
|
||||
}
|
||||
|
||||
impl AuthDB {
|
||||
pub async fn new(path: PathBuf) -> Result<Self, sqlx::Error> {
|
||||
let pool = SqlitePool::connect(&format!("sqlite://{}", path.to_str().unwrap())).await?;
|
||||
Ok(Self { pool })
|
||||
}
|
||||
|
||||
async fn auth_token(&self, _token: AuthToken) -> Result<SessionToken, AuthError> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
async fn auth_session(&self, _token: SessionToken) -> Result<Username, AuthError> {
|
||||
/*
|
||||
let conn = self.pool.acquire().await.map_err(|_| AuthError::SqlError)?;
|
||||
conn.transaction(|tr| {})
|
||||
*/
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Store {
|
||||
files_root: PathBuf,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue