Swap from iron to warp and start rebuilding the app

This commit is contained in:
Savanni D'Gerinel 2023-09-20 23:06:34 -04:00
parent 75bfae02c4
commit a02e335492
12 changed files with 320 additions and 104 deletions

View File

@ -7,8 +7,10 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
build_html = { version = "2" }
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
hex-string = "0.1.0" hex-string = "0.1.0"
http = { version = "0.2" }
image = "0.23.5" image = "0.23.5"
iron = "0.6.1" iron = "0.6.1"
logger = "*" logger = "*"
@ -22,4 +24,6 @@ serde_json = "*"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
sha2 = "0.8.2" sha2 = "0.8.2"
thiserror = "1.0.20" thiserror = "1.0.20"
uuid = { version = "0.4", features = ["serde", "v4"] } tokio = { version = "1", features = [ "full" ] }
uuid = { version = "0.4", features = [ "serde", "v4" ] }
warp = { version = "0.3" }

View File

@ -0,0 +1 @@
{"id":"rawr.png","size":23777,"created":"2020-06-04T16:04:10.085680927Z","file_type":"image/png","hash":"b6cd35e113b95d62f53d9cbd27ccefef47d3e324aef01a2db6c0c6d3a43c89ee"}

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

135
file-service/src/html.rs Normal file
View File

@ -0,0 +1,135 @@
use build_html::{self, Html, HtmlContainer};
#[derive(Clone, Debug)]
pub struct Form {
method: String,
encoding: Option<String>,
elements: String,
}
impl Form {
pub fn new() -> Self {
Self {
method: "get".to_owned(),
encoding: None,
elements: "".to_owned(),
}
}
pub fn with_method(mut self, method: &str) -> Self {
self.method = method.to_owned();
self
}
pub fn with_encoding(mut self, encoding: &str) -> Self {
self.encoding = Some(encoding.to_owned());
self
}
}
impl Html for Form {
fn to_html_string(&self) -> String {
let encoding = match self.encoding {
Some(ref encoding) => format!("encoding={encoding}", encoding = encoding),
None => format!(""),
};
format!(
"<form method={method} {encoding}>\n{elements}\n</form>\n",
method = self.method,
encoding = encoding,
elements = self.elements.to_html_string()
)
}
}
impl HtmlContainer for Form {
fn add_html<H: Html>(&mut self, html: H) {
self.elements.push_str(&html.to_html_string());
}
}
#[derive(Clone, Debug)]
pub struct Input {
ty: String,
name: String,
id: String,
value: Option<String>,
}
impl Html for Input {
fn to_html_string(&self) -> String {
format!(
"<input type=\"{ty}\" name=\"{name}\" id=\"{id}\">{value}</input>\n",
ty = self.ty,
name = self.name,
id = self.id,
value = self.value.clone().unwrap_or("".to_owned()),
)
}
}
impl Input {
pub fn new(ty: &str, name: &str, id: &str) -> Self {
Self {
ty: ty.to_owned(),
name: name.to_owned(),
id: id.to_owned(),
value: None,
}
}
pub fn with_value(mut self, val: &str) -> Self {
self.value = Some(val.to_owned());
self
}
}
#[derive(Clone, Debug)]
pub struct Label {
target: String,
text: String,
}
impl Label {
pub fn new(target: &str, text: &str) -> Self {
Self {
target: target.to_owned(),
text: text.to_owned(),
}
}
}
impl Html for Label {
fn to_html_string(&self) -> String {
format!(
"<label for=\"{target}\">{text}</label>",
target = self.target,
text = self.text
)
}
}
#[derive(Clone, Debug)]
pub struct Button {
name: String,
text: String,
}
impl Button {
pub fn new(name: &str, text: &str) -> Self {
Self {
name: name.to_owned(),
text: text.to_owned(),
}
}
}
impl Html for Button {
fn to_html_string(&self) -> String {
format!(
"<button name={name}>{text}</button>",
name = self.name,
text = self.text
)
}
}

View File

@ -1,31 +0,0 @@
use std::path::PathBuf;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("not implemented")]
NotImplemented,
#[error("file not found: `{0}`")]
FileNotFound(PathBuf),
#[error("file is not an image: `{0}`")]
NotAnImage(PathBuf),
#[error("path is not a file: `{0}`")]
NotAFile(PathBuf),
#[error("Image loading error")]
ImageError(#[from] image::ImageError),
#[error("IO error")]
IOError(#[from] std::io::Error),
#[error("JSON error")]
JSONError(#[from] serde_json::error::Error),
#[error("UTF8 Error")]
UTF8Error(#[from] std::str::Utf8Error),
}
pub type Result<A> = std::result::Result<A, Error>;

View File

@ -1,9 +1,38 @@
use super::error::{Error, Result};
use super::fileinfo::FileInfo; use super::fileinfo::FileInfo;
use super::thumbnail::Thumbnail; use super::thumbnail::Thumbnail;
use std::fs::{copy, read_dir, remove_file}; use std::fs::{copy, read_dir, remove_file};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use thiserror::Error;
#[derive(Error, Debug)]
pub enum FileError {
#[error("not implemented")]
NotImplemented,
#[error("file not found: `{0}`")]
FileNotFound(PathBuf),
#[error("file is not an image: `{0}`")]
NotAnImage(PathBuf),
#[error("path is not a file: `{0}`")]
NotAFile(PathBuf),
#[error("Image loading error")]
ImageError(#[from] image::ImageError),
#[error("IO error")]
IOError(#[from] std::io::Error),
#[error("JSON error")]
JSONError(#[from] serde_json::error::Error),
#[error("UTF8 Error")]
UTF8Error(#[from] std::str::Utf8Error),
}
/// One file in the database, complete with the path of the file and information about the
/// thumbnail of the file.
#[derive(Debug)] #[derive(Debug)]
pub struct File { pub struct File {
info: FileInfo, info: FileInfo,
@ -17,7 +46,7 @@ impl File {
root: &Path, root: &Path,
temp_path: &PathBuf, temp_path: &PathBuf,
filename: &Option<PathBuf>, filename: &Option<PathBuf>,
) -> Result<File> { ) -> Result<Self, FileError> {
let mut dest_path = PathBuf::from(root); let mut dest_path = PathBuf::from(root);
dest_path.push(id); dest_path.push(id);
match filename { match filename {
@ -33,27 +62,27 @@ impl File {
copy(temp_path, dest_path.clone())?; copy(temp_path, dest_path.clone())?;
let info = FileInfo::from_path(&dest_path)?; let info = FileInfo::from_path(&dest_path)?;
let tn = Thumbnail::from_path(&dest_path)?; let tn = Thumbnail::from_path(&dest_path)?;
Ok(File { Ok(Self {
info, info,
tn, tn,
root: PathBuf::from(root), root: PathBuf::from(root),
}) })
} }
pub fn open(id: &str, root: &Path) -> Result<File> { pub fn open(id: &str, root: &Path) -> Result<Self, FileError> {
let mut file_path = PathBuf::from(root); let mut file_path = PathBuf::from(root);
file_path.push(id.clone()); file_path.push(id.clone());
if !file_path.exists() { if !file_path.exists() {
return Err(Error::FileNotFound(file_path)); return Err(FileError::FileNotFound(file_path));
} }
if !file_path.is_file() { if !file_path.is_file() {
return Err(Error::NotAFile(file_path)); return Err(FileError::NotAFile(file_path));
} }
let info = match FileInfo::open(id, root) { let info = match FileInfo::open(id, root) {
Ok(i) => Ok(i), Ok(i) => Ok(i),
Err(Error::FileNotFound(_)) => { Err(FileError::FileNotFound(_)) => {
let info = FileInfo::from_path(&file_path)?; let info = FileInfo::from_path(&file_path)?;
info.save(&root)?; info.save(&root)?;
Ok(info) Ok(info)
@ -63,14 +92,14 @@ impl File {
let tn = Thumbnail::open(id, root)?; let tn = Thumbnail::open(id, root)?;
Ok(File { Ok(Self {
info, info,
tn, tn,
root: PathBuf::from(root), root: PathBuf::from(root),
}) })
} }
pub fn list(root: &Path) -> Vec<Result<File>> { pub fn list(root: &Path) -> Vec<Result<Self, FileError>> {
let dir_iter = read_dir(&root).unwrap(); let dir_iter = read_dir(&root).unwrap();
dir_iter dir_iter
.filter(|entry| { .filter(|entry| {
@ -81,7 +110,7 @@ impl File {
.map(|entry| { .map(|entry| {
let entry_ = entry.unwrap(); let entry_ = entry.unwrap();
let id = entry_.file_name().into_string().unwrap(); let id = entry_.file_name().into_string().unwrap();
File::open(&id, root) Self::open(&id, root)
}) })
.collect() .collect()
} }
@ -94,13 +123,13 @@ impl File {
self.tn.clone() self.tn.clone()
} }
pub fn stream(&self) -> Result<std::fs::File> { pub fn stream(&self) -> Result<std::fs::File, FileError> {
let mut path = self.root.clone(); let mut path = self.root.clone();
path.push(self.info.id.clone()); path.push(self.info.id.clone());
std::fs::File::open(path).map_err(Error::from) std::fs::File::open(path).map_err(FileError::from)
} }
pub fn delete(&self) -> Result<()> { pub fn delete(&self) -> Result<(), FileError> {
let mut path = self.root.clone(); let mut path = self.root.clone();
path.push(self.info.id.clone()); path.push(self.info.id.clone());
remove_file(path)?; remove_file(path)?;

View File

@ -7,8 +7,7 @@ use std::fs::remove_file;
use std::io::{Read, Write}; use std::io::{Read, Write};
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use super::error::{Error, Result}; use crate::{append_extension, FileError};
use super::utils::append_extension;
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct FileInfo { pub struct FileInfo {
@ -23,45 +22,45 @@ pub struct FileInfo {
} }
impl FileInfo { impl FileInfo {
pub fn save(&self, root: &Path) -> Result<()> { pub fn save(&self, root: &Path) -> Result<(), FileError> {
let ser = serde_json::to_string(self).unwrap(); let ser = serde_json::to_string(self).unwrap();
std::fs::File::create(FileInfo::metadata_path(&self.id, root)) std::fs::File::create(FileInfo::metadata_path(&self.id, root))
.and_then(|mut stream| stream.write(ser.as_bytes()).map(|_| (()))) .and_then(|mut stream| stream.write(ser.as_bytes()).map(|_| (())))
.map_err(Error::from) .map_err(FileError::from)
} }
pub fn open(id: &str, root: &Path) -> Result<FileInfo> { pub fn open(id: &str, root: &Path) -> Result<FileInfo, FileError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
let md_path = FileInfo::metadata_path(id, root); let md_path = FileInfo::metadata_path(id, root);
std::fs::File::open(md_path.clone()) std::fs::File::open(md_path.clone())
.and_then(|mut stream| stream.read_to_end(&mut buf)) .and_then(|mut stream| stream.read_to_end(&mut buf))
.map_err(move |err| match err.kind() { .map_err(move |err| match err.kind() {
std::io::ErrorKind::NotFound => Error::FileNotFound(md_path), std::io::ErrorKind::NotFound => FileError::FileNotFound(md_path),
_ => Error::IOError(err), _ => FileError::IOError(err),
})?; })?;
let str_repr = std::str::from_utf8(&buf)?; let str_repr = std::str::from_utf8(&buf)?;
serde_json::from_str(&str_repr).map_err(Error::from) serde_json::from_str(&str_repr).map_err(FileError::from)
} }
pub fn from_path(path: &Path) -> Result<FileInfo> { pub fn from_path(path: &Path) -> Result<FileInfo, FileError> {
match (path.is_file(), path.is_dir()) { match (path.is_file(), path.is_dir()) {
(false, false) => Err(Error::FileNotFound(PathBuf::from(path))), (false, false) => Err(FileError::FileNotFound(PathBuf::from(path))),
(false, true) => Err(Error::NotAFile(PathBuf::from(path))), (false, true) => Err(FileError::NotAFile(PathBuf::from(path))),
(true, _) => Ok(()), (true, _) => Ok(()),
}?; }?;
let metadata = path.metadata().map_err(Error::IOError)?; let metadata = path.metadata().map_err(FileError::IOError)?;
let id = path let id = path
.file_name() .file_name()
.map(|s| String::from(s.to_string_lossy())) .map(|s| String::from(s.to_string_lossy()))
.ok_or(Error::NotAFile(PathBuf::from(path)))?; .ok_or(FileError::NotAFile(PathBuf::from(path)))?;
let created = metadata let created = metadata
.created() .created()
.map(|m| DateTime::from(m)) .map(|m| DateTime::from(m))
.map_err(|err| Error::IOError(err))?; .map_err(|err| FileError::IOError(err))?;
let file_type = String::from( let file_type = String::from(
mime_guess::from_path(path) mime_guess::from_path(path)
.first_or_octet_stream() .first_or_octet_stream()
@ -71,18 +70,18 @@ impl FileInfo {
Ok(FileInfo { Ok(FileInfo {
id, id,
size: metadata.len(), size: metadata.len(),
created: created, created,
file_type, file_type,
hash: hash.as_string(), hash: hash.as_string(),
root: PathBuf::from(path.parent().unwrap()), root: PathBuf::from(path.parent().unwrap()),
}) })
} }
fn hash_file(path: &Path) -> Result<HexString> { fn hash_file(path: &Path) -> Result<HexString, FileError> {
let mut buf = Vec::new(); let mut buf = Vec::new();
let mut file = std::fs::File::open(path).map_err(Error::from)?; let mut file = std::fs::File::open(path).map_err(FileError::from)?;
file.read_to_end(&mut buf).map_err(Error::from)?; file.read_to_end(&mut buf).map_err(FileError::from)?;
let mut vec = Vec::new(); let mut vec = Vec::new();
vec.extend_from_slice(Sha256::digest(&buf).as_slice()); vec.extend_from_slice(Sha256::digest(&buf).as_slice());
Ok(HexString::from_bytes(&vec)) Ok(HexString::from_bytes(&vec))
@ -95,9 +94,9 @@ impl FileInfo {
append_extension(&path, "json") append_extension(&path, "json")
} }
pub fn delete(&self) -> Result<()> { pub fn delete(&self) -> Result<(), FileError> {
let path = FileInfo::metadata_path(&self.id, &self.root); let path = FileInfo::metadata_path(&self.id, &self.root);
remove_file(path).map_err(Error::from) remove_file(path).map_err(FileError::from)
} }
} }

View File

@ -1,14 +1,12 @@
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use uuid::Uuid; use uuid::Uuid;
mod error;
mod file; mod file;
mod fileinfo; mod fileinfo;
mod thumbnail; mod thumbnail;
mod utils; pub mod utils;
pub use error::{Error, Result}; pub use file::{File, FileError};
pub use file::File;
pub use fileinfo::FileInfo; pub use fileinfo::FileInfo;
pub use thumbnail::Thumbnail; pub use thumbnail::Thumbnail;
@ -23,32 +21,36 @@ impl App {
} }
} }
pub fn list_files(&self) -> Vec<Result<File>> { pub fn list_files(&self) -> Vec<Result<File, FileError>> {
File::list(&self.files_root) File::list(&self.files_root)
} }
pub fn add_file(&mut self, temp_path: &PathBuf, filename: &Option<PathBuf>) -> Result<File> { pub fn add_file(
&mut self,
temp_path: &PathBuf,
filename: &Option<PathBuf>,
) -> Result<File, FileError> {
let id = Uuid::new_v4().hyphenated().to_string(); let id = Uuid::new_v4().hyphenated().to_string();
File::new(&id, &self.files_root, temp_path, filename) File::new(&id, &self.files_root, temp_path, filename)
} }
pub fn delete_file(&mut self, id: String) -> Result<()> { pub fn delete_file(&mut self, id: String) -> Result<(), FileError> {
let f = File::open(&id, &self.files_root)?; let f = File::open(&id, &self.files_root)?;
f.delete() f.delete()
} }
pub fn get_metadata(&self, id: String) -> Result<FileInfo> { pub fn get_metadata(&self, id: String) -> Result<FileInfo, FileError> {
FileInfo::open(&id, &self.files_root) FileInfo::open(&id, &self.files_root)
} }
pub fn get_file(&self, id: String) -> Result<(FileInfo, std::fs::File)> { pub fn get_file(&self, id: String) -> Result<(FileInfo, std::fs::File), FileError> {
let f = File::open(&id, &self.files_root)?; let f = File::open(&id, &self.files_root)?;
let info = f.info(); let info = f.info();
let stream = f.stream()?; let stream = f.stream()?;
Ok((info, stream)) Ok((info, stream))
} }
pub fn get_thumbnail(&self, id: &str) -> Result<(FileInfo, std::fs::File)> { pub fn get_thumbnail(&self, id: &str) -> Result<(FileInfo, std::fs::File), FileError> {
let f = File::open(id, &self.files_root)?; let f = File::open(id, &self.files_root)?;
let stream = f.thumbnail().stream()?; let stream = f.thumbnail().stream()?;
Ok((f.info(), stream)) Ok((f.info(), stream))

View File

@ -2,7 +2,7 @@ use image::imageops::FilterType;
use std::fs::remove_file; use std::fs::remove_file;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use super::error::{Error, Result}; use crate::FileError;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Thumbnail { pub struct Thumbnail {
@ -11,7 +11,7 @@ pub struct Thumbnail {
} }
impl Thumbnail { impl Thumbnail {
pub fn open(id: &str, root: &Path) -> Result<Thumbnail> { pub fn open(id: &str, root: &Path) -> Result<Thumbnail, FileError> {
let mut source_path = PathBuf::from(root); let mut source_path = PathBuf::from(root);
source_path.push(id); source_path.push(id);
@ -30,15 +30,15 @@ impl Thumbnail {
Ok(self_) Ok(self_)
} }
pub fn from_path(path: &Path) -> Result<Thumbnail> { pub fn from_path(path: &Path) -> Result<Thumbnail, FileError> {
let id = path let id = path
.file_name() .file_name()
.map(|s| String::from(s.to_string_lossy())) .map(|s| String::from(s.to_string_lossy()))
.ok_or(Error::NotAnImage(PathBuf::from(path)))?; .ok_or(FileError::NotAnImage(PathBuf::from(path)))?;
let root = path let root = path
.parent() .parent()
.ok_or(Error::FileNotFound(PathBuf::from(path)))?; .ok_or(FileError::FileNotFound(PathBuf::from(path)))?;
Thumbnail::open(&id, root) Thumbnail::open(&id, root)
} }
@ -50,20 +50,20 @@ impl Thumbnail {
path path
} }
pub fn stream(&self) -> Result<std::fs::File> { pub fn stream(&self) -> Result<std::fs::File, FileError> {
let thumbnail_path = Thumbnail::thumbnail_path(&self.id, &self.root); let thumbnail_path = Thumbnail::thumbnail_path(&self.id, &self.root);
std::fs::File::open(thumbnail_path.clone()).map_err(|err| { std::fs::File::open(thumbnail_path.clone()).map_err(|err| {
if err.kind() == std::io::ErrorKind::NotFound { if err.kind() == std::io::ErrorKind::NotFound {
Error::FileNotFound(thumbnail_path) FileError::FileNotFound(thumbnail_path)
} else { } else {
Error::from(err) FileError::from(err)
} }
}) })
} }
pub fn delete(&self) -> Result<()> { pub fn delete(&self) -> Result<(), FileError> {
let path = Thumbnail::thumbnail_path(&self.id, &self.root); let path = Thumbnail::thumbnail_path(&self.id, &self.root);
remove_file(path).map_err(Error::from) remove_file(path).map_err(FileError::from)
} }
} }

View File

@ -1,28 +1,31 @@
/*
use iron::headers; use iron::headers;
use iron::middleware::Handler; use iron::middleware::Handler;
use iron::modifiers::{Header, Redirect}; use iron::modifiers::{Header, Redirect};
use iron::prelude::*; use iron::prelude::*;
use iron::response::BodyReader; use iron::response::BodyReader;
use iron::status; use iron::status;
use mustache::{compile_path, Template}; */
use orizentic::{Permissions, ResourceName, Secret}; use http::status::StatusCode;
use params::{Params, Value}; // use mustache::{compile_path, Template};
use router::Router; // use orizentic::{Permissions, ResourceName, Secret};
use serde::Serialize; use build_html::Html;
use std::collections::HashMap; use std::{
use std::fs::File; net::{IpAddr, Ipv4Addr, SocketAddr},
use std::io::Read; path::Path,
use std::path::Path; sync::{Arc, RwLock},
use std::path::PathBuf; };
use std::sync::{Arc, RwLock}; use warp::Filter;
mod cookies; mod cookies;
mod html;
mod lib; mod lib;
mod middleware; mod middleware;
mod pages;
use lib::{App, FileInfo}; use lib::{utils::append_extension, App, File, FileError, FileInfo};
use middleware::{Authentication, RestForm};
/*
fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool { fn is_admin(resource: &ResourceName, permissions: &Permissions) -> bool {
let Permissions(perms) = permissions; let Permissions(perms) = permissions;
ResourceName(String::from( ResourceName(String::from(
@ -280,18 +283,17 @@ fn script(_: &mut Request) -> IronResult<Response> {
js, js,
))) )))
} }
*/
fn main() { #[tokio::main]
pub async fn main() {
/*
let auth_db_path = std::env::var("ORIZENTIC_DB").unwrap(); let auth_db_path = std::env::var("ORIZENTIC_DB").unwrap();
let secret = Secret(Vec::from( let secret = Secret(Vec::from(
std::env::var("ORIZENTIC_SECRET").unwrap().as_bytes(), std::env::var("ORIZENTIC_SECRET").unwrap().as_bytes(),
)); ));
let auth_middleware = Authentication::new(secret, auth_db_path); let auth_middleware = Authentication::new(secret, auth_db_path);
let app = Arc::new(RwLock::new(App::new(Path::new(
&std::env::var("FILE_SHARE_DIR").unwrap(),
))));
let mut router = Router::new(); let mut router = Router::new();
router.get( router.get(
"/", "/",
@ -338,4 +340,39 @@ fn main() {
chain.link_before(RestForm {}); chain.link_before(RestForm {});
Iron::new(chain).http("0.0.0.0:3000").unwrap(); 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;
*/
let app = Arc::new(RwLock::new(App::new(Path::new(
&std::env::var("FILE_SHARE_DIR").unwrap(),
))));
let root = warp::path!().map({
let app = app.clone();
move || {
warp::http::Response::builder()
.header("content-type", "text/html")
.status(StatusCode::OK)
.body(pages::index(app.read().unwrap().list_files()).to_html_string())
}
});
let server = warp::serve(root);
server
.run(SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 8002))
.await;
} }

View File

@ -26,7 +26,7 @@ impl Authentication {
&self, &self,
token_str: String, token_str: String,
) -> Result<orizentic::VerifiedToken, orizentic::Error> { ) -> Result<orizentic::VerifiedToken, orizentic::Error> {
self.auth.decode_and_validate_text(&token_str) self.auth.decode_and_validate_text(token_str)
} }
} }

40
file-service/src/pages.rs Normal file
View File

@ -0,0 +1,40 @@
use crate::{html::*, File, FileError};
use build_html::{self, Container, ContainerType, HtmlContainer};
pub fn index(files: Vec<Result<File, FileError>>) -> build_html::HtmlPage {
let mut page = build_html::HtmlPage::new()
.with_title("Admin list of files")
.with_header(1, "Admin list of files")
.with_html(
Form::new()
.with_method("post")
.with_encoding("multipart/form-data")
.with_container(
Container::new(ContainerType::Div)
.with_html(Input::new("file", "file", "file-selector-input"))
.with_html(Label::new("for-selector-input", "Select a file")),
)
.with_html(Button::new("upload", "Upload file")),
);
for file in files {
let mut container =
Container::new(ContainerType::Div).with_attributes(vec![("class", "file")]);
match file {
Ok(file) => {
let tn = Container::new(ContainerType::Div)
.with_attributes(vec![("class", "thumbnail")])
.with_link(
format!("/file/{}", file.info().id),
"<p> paragraph within the link </p>".to_owned(),
);
container.add_html(tn);
}
Err(err) => {
container.add_paragraph(format!("{:?}", err));
}
}
page.add_container(container);
}
page
}