file responses
All checks were successful
ci/woodpecker/push/test Pipeline was successful

This commit is contained in:
JMARyA 2025-03-04 18:47:55 +01:00
parent 4688968a32
commit 0c17944215
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
7 changed files with 128 additions and 71 deletions

1
Cargo.lock generated
View file

@ -147,6 +147,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
name = "based"
version = "0.1.0"
dependencies = [
"async-stream",
"bcrypt",
"chrono",
"dashmap",

View file

@ -25,6 +25,7 @@ rand = "0.8.5"
data-encoding = "2.6.0"
bcrypt = "0.16.0"
dashmap = "6.1.0"
async-stream = "0.3.6"
[build-dependencies]
reqwest = { version = "0.11", features = ["blocking"] }

View file

@ -52,15 +52,12 @@ pub trait AssetRoutes {
impl AssetRoutes for rocket::Rocket<Build> {
fn mount_assets(self) -> Self {
self.mount(
"/",
routes![
crate::asset::htmx_script_route,
crate::asset::flowbite_css,
crate::asset::flowbite_js,
crate::asset::material_css,
crate::asset::material_font
],
)
self.mount("/", routes![
crate::asset::htmx_script_route,
crate::asset::flowbite_css,
crate::asset::flowbite_js,
crate::asset::material_css,
crate::asset::material_font
])
}
}

View file

@ -1,16 +1,75 @@
use crate::gen_random;
use rocket::Request;
use rocket::Response;
use rocket::http::Header;
use rocket::http::Status;
use rocket::response::Responder;
use std::io::Cursor;
use crate::gen_random;
use std::io::Read;
use std::os::unix::fs::FileExt;
use std::os::unix::fs::MetadataExt;
// TODO: Implement file based response
pub struct Data {
file: Option<String>,
raw: Option<Vec<u8>>,
}
impl Data {
pub fn raw(data: Vec<u8>) -> Self {
Self {
file: None,
raw: Some(data),
}
}
pub fn file(path: &str) -> Self {
Self {
file: Some(path.to_string()),
raw: None,
}
}
pub fn len(&self) -> usize {
if let Some(raw) = &self.raw {
raw.len()
} else {
std::fs::File::open(self.file.as_ref().unwrap())
.unwrap()
.metadata()
.unwrap()
.size() as usize
}
}
pub fn index(&self, start: usize, end: usize) -> Vec<u8> {
if let Some(raw) = &self.raw {
raw[start..=end].to_vec()
} else {
let file = std::fs::File::open(self.file.as_ref().unwrap()).unwrap();
let mut buf: Vec<_> = Vec::with_capacity((end - start) as usize);
file.read_exact_at(&mut buf, start as u64).unwrap();
buf
}
}
pub fn full(self) -> Vec<u8> {
if let Some(raw) = self.raw {
raw
} else {
let mut buf = Vec::with_capacity(self.len());
std::fs::File::open(self.file.unwrap())
.unwrap()
.read_to_end(&mut buf)
.unwrap();
return buf;
}
}
}
pub struct DataResponse {
data: Vec<u8>,
data: Data,
content_type: String,
// TODO : Implement better cache control
cache_duration: Option<u64>,
@ -18,9 +77,18 @@ pub struct DataResponse {
impl DataResponse {
#[must_use]
pub const fn new(data: Vec<u8>, content_type: String, cache_duration: Option<u64>) -> Self {
pub fn new(data: Vec<u8>, content_type: String, cache_duration: Option<u64>) -> Self {
Self {
data,
data: Data::raw(data),
content_type,
cache_duration,
}
}
#[must_use]
pub fn new_file(path: &str, content_type: String, cache_duration: Option<u64>) -> Self {
Self {
data: Data::file(path),
content_type,
cache_duration,
}
@ -35,7 +103,7 @@ impl<'r> Responder<'r, 'static> for DataResponse {
let ranges = range.split(",").collect::<Vec<_>>();
if ranges.len() == 1 {
if let Some((start, end)) = parse_range_header(range, self.data.len()) {
let sliced_data = &self.data[start..=end];
let sliced_data = self.data.index(start, end);
return Ok(Response::build()
.header(Header::new(
"Content-Range",
@ -53,7 +121,7 @@ impl<'r> Responder<'r, 'static> for DataResponse {
for range in ranges {
if let Some((start, end)) = parse_range_header(range, self.data.len()) {
let sliced_data = &self.data[start..=end];
let sliced_data = self.data.index(start, end);
let mut body: Vec<u8> = Vec::new();
@ -71,7 +139,7 @@ impl<'r> Responder<'r, 'static> for DataResponse {
format!("Content-Type: {}\r\n\r\n", self.content_type.clone())
.as_bytes(),
);
body.extend_from_slice(sliced_data);
body.extend_from_slice(&sliced_data);
body.extend_from_slice("\r\n".as_bytes());
multipart_body.extend_from_slice(&body);
@ -102,7 +170,7 @@ impl<'r> Responder<'r, 'static> for DataResponse {
.header(cache_control_header)
.header(Header::new("Accept-Ranges", "bytes"))
.header(Header::new("Content-Type", self.content_type))
.streamed_body(Cursor::new(self.data))
.streamed_body(Cursor::new(self.data.full()))
.finalize())
}
}

View file

@ -29,32 +29,29 @@ pub fn Modal<T: UIWidget + 'static, E: UIWidget + 'static, F: FnOnce(String) ->
) -> (String, PreEscaped<String>) {
let id = uuid::Uuid::new_v4().to_string();
(
format!("modal-{id}"),
html! {
div id=(format!("modal-{id}")) tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full" {
div class="relative p-4 w-full max-w-2xl max-h-full" {
(format!("modal-{id}"), html! {
div id=(format!("modal-{id}")) tabindex="-1" aria-hidden="true" class="hidden overflow-y-auto overflow-x-hidden fixed top-0 right-0 left-0 z-50 justify-center items-center w-full md:inset-0 h-[calc(100%-1rem)] max-h-full" {
div class="relative p-4 w-full max-w-2xl max-h-full" {
div class="relative bg-white rounded-lg shadow dark:bg-gray-700" {
div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600" {
h3 class="text-xl font-semibold text-gray-900 dark:text-white" { (title) }
button type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-hide=(format!("modal-{id}")) {
svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14" {
path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" {};
};
span class="sr-only" { "Close modal" };
}
};
div class="p-4 md:p-5 space-y-4" {
(body)
};
div class="flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600" {
(footer(format!("modal-{id}")))
};
div class="relative bg-white rounded-lg shadow dark:bg-gray-700" {
div class="flex items-center justify-between p-4 md:p-5 border-b rounded-t dark:border-gray-600" {
h3 class="text-xl font-semibold text-gray-900 dark:text-white" { (title) }
button type="button" class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white" data-modal-hide=(format!("modal-{id}")) {
svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 14 14" {
path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6" {};
};
span class="sr-only" { "Close modal" };
}
};
}};
},
)
div class="p-4 md:p-5 space-y-4" {
(body)
};
div class="flex items-center p-4 md:p-5 border-t border-gray-200 rounded-b dark:border-gray-600" {
(footer(format!("modal-{id}")))
};
};
}};
})
}

View file

@ -287,13 +287,10 @@ pub fn BottomNavigationTile<T: UIWidget + 'static>(
) -> ClassicWidget<LinkWidget> {
Classic(
"inline-flex flex-col items-center justify-center px-5 hover:bg-gray-50 dark:hover:bg-gray-800 group",
Link(
reference,
html! {
(icon.map(|x| x.render()).unwrap_or_default());
span class="text-sm text-gray-500 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-500" { (text) };
},
),
Link(reference, html! {
(icon.map(|x| x.render()).unwrap_or_default());
span class="text-sm text-gray-500 dark:text-gray-400 group-hover:text-blue-600 dark:group-hover:text-blue-500" { (text) };
}),
)
}

View file

@ -274,25 +274,21 @@ impl GridElement {
}
pub fn span(mut self, value: GridElementValue) -> Self {
self.1.push(format!(
"{}-span-{}",
self.2,
match value {
GridElementValue::_1 => "1",
GridElementValue::_2 => "2",
GridElementValue::_3 => "3",
GridElementValue::_4 => "4",
GridElementValue::_5 => "5",
GridElementValue::_6 => "6",
GridElementValue::_7 => "7",
GridElementValue::_8 => "8",
GridElementValue::_9 => "9",
GridElementValue::_10 => "10",
GridElementValue::_11 => "11",
GridElementValue::_12 => "12",
GridElementValue::Auto => "full",
}
));
self.1.push(format!("{}-span-{}", self.2, match value {
GridElementValue::_1 => "1",
GridElementValue::_2 => "2",
GridElementValue::_3 => "3",
GridElementValue::_4 => "4",
GridElementValue::_5 => "5",
GridElementValue::_6 => "6",
GridElementValue::_7 => "7",
GridElementValue::_8 => "8",
GridElementValue::_9 => "9",
GridElementValue::_10 => "10",
GridElementValue::_11 => "11",
GridElementValue::_12 => "12",
GridElementValue::Auto => "full",
}));
self
}