Compare commits

...

9 Commits

Author SHA1 Message Date
d74ca76e44 bigger changements
All checks were successful
build docker container automatically / build (push) Successful in 4m14s
2026-04-17 16:57:18 +02:00
031d0e39df test latest tag
All checks were successful
build docker container automatically / build (push) Successful in 3m17s
2026-04-17 16:48:15 +02:00
b739780c13 test latest tag
All checks were successful
build docker container automatically / build (push) Successful in 3m15s
2026-04-17 16:40:41 +02:00
ad8183e10b fix CD fr fr
All checks were successful
build docker container automatically / build (push) Successful in 3m19s
2026-04-17 16:19:46 +02:00
ec1323a1b4 fix CD fr this time (please)
Some checks failed
build docker container automatically / build (push) Failing after 2m1s
2026-04-17 16:14:01 +02:00
3854c09577 fix CD 2026-04-17 16:11:23 +02:00
fc3806c334 add CD, fix warnings 2026-04-17 16:09:42 +02:00
c29d5f5a9f fix typo in home, remove assets/tailwind.css 2026-04-17 11:45:36 +02:00
12ebb3f8d9 router and home page 2026-04-17 11:39:35 +02:00
16 changed files with 163 additions and 333 deletions

View File

@ -0,0 +1,35 @@
name: build docker container automatically
run-name: docker build
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: get commit hash
id: get_hash
run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Login to Docker Hub
uses: docker/login-action@v2
with:
registry: git.tmoron.fr
username: ${{ secrets.PUSH_USERNAME }}
password: ${{ secrets.PUSH_PASSWORD }}
- name: Build and push
uses: docker/build-push-action@v4
with:
context: "{{defaultContext}}:${{ input.path }}"
push: true
tags: git.tmoron.fr/${{ gitea.repository }}:latest

3
.gitignore vendored
View File

@ -10,3 +10,6 @@ postgres/
serveurhttp serveurhttp
test test
.env .env
public/
assets/tailwind.css

View File

@ -3,6 +3,8 @@ name = "httpserver"
version = "0.1.0" version = "0.1.0"
authors = ["tomoron <tomoron@student.42angouleme.fr>"] authors = ["tomoron <tomoron@student.42angouleme.fr>"]
edition = "2021" edition = "2021"
description = "a rewrite of my http server in rust"
repository = "https://git.tmoron.fr/tom/httpserver"
# 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

24
Dockerfile Normal file
View File

@ -0,0 +1,24 @@
FROM lewimbes/dioxus AS builder
RUN mkdir /build
WORKDIR /build
LABEL stage="builder"
RUN apt-get update && apt-get install -y \
libpq-dev \
default-libmysqlclient-dev \
sqlite3 libsqlite3-dev && \
rm -rf /var/lib/apt/lists/*
COPY . .
RUN dx build -r
RUN cp /build/target/dx/httpserver/release/web /app -r
WORKDIR /app
RUN rm -rf /build
ENV IP="0.0.0.0"
CMD ["./server"]

View File

@ -1,262 +0,0 @@
/*! tailwindcss v4.1.5 | MIT License | https://tailwindcss.com */
@layer properties;
@layer theme, base, components, utilities;
@layer theme {
:root, :host {
--font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji';
--font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New',
monospace;
--color-blue-800: oklch(42.4% 0.199 265.638);
--color-zinc-900: oklch(21% 0.006 285.885);
--color-white: #fff;
--spacing: 0.25rem;
--text-4xl: 2.25rem;
--text-4xl--line-height: calc(2.5 / 2.25);
--font-weight-bold: 700;
--default-font-family: var(--font-sans);
--default-mono-font-family: var(--font-mono);
}
}
@layer base {
*, ::after, ::before, ::backdrop, ::file-selector-button {
box-sizing: border-box;
margin: 0;
padding: 0;
border: 0 solid;
}
html, :host {
line-height: 1.5;
-webkit-text-size-adjust: 100%;
tab-size: 4;
font-family: var(--default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji');
font-feature-settings: var(--default-font-feature-settings, normal);
font-variation-settings: var(--default-font-variation-settings, normal);
-webkit-tap-highlight-color: transparent;
}
hr {
height: 0;
color: inherit;
border-top-width: 1px;
}
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
h1, h2, h3, h4, h5, h6 {
font-size: inherit;
font-weight: inherit;
}
a {
color: inherit;
-webkit-text-decoration: inherit;
text-decoration: inherit;
}
b, strong {
font-weight: bolder;
}
code, kbd, samp, pre {
font-family: var(--default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace);
font-feature-settings: var(--default-mono-font-feature-settings, normal);
font-variation-settings: var(--default-mono-font-variation-settings, normal);
font-size: 1em;
}
small {
font-size: 80%;
}
sub, sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
table {
text-indent: 0;
border-color: inherit;
border-collapse: collapse;
}
:-moz-focusring {
outline: auto;
}
progress {
vertical-align: baseline;
}
summary {
display: list-item;
}
ol, ul, menu {
list-style: none;
}
img, svg, video, canvas, audio, iframe, embed, object {
display: block;
vertical-align: middle;
}
img, video {
max-width: 100%;
height: auto;
}
button, input, select, optgroup, textarea, ::file-selector-button {
font: inherit;
font-feature-settings: inherit;
font-variation-settings: inherit;
letter-spacing: inherit;
color: inherit;
border-radius: 0;
background-color: transparent;
opacity: 1;
}
:where(select:is([multiple], [size])) optgroup {
font-weight: bolder;
}
:where(select:is([multiple], [size])) optgroup option {
padding-inline-start: 20px;
}
::file-selector-button {
margin-inline-end: 4px;
}
::placeholder {
opacity: 1;
}
@supports (not (-webkit-appearance: -apple-pay-button)) or (contain-intrinsic-size: 1px) {
::placeholder {
color: currentcolor;
@supports (color: color-mix(in lab, red, red)) {
color: color-mix(in oklab, currentcolor 50%, transparent);
}
}
}
textarea {
resize: vertical;
}
::-webkit-search-decoration {
-webkit-appearance: none;
}
::-webkit-date-and-time-value {
min-height: 1lh;
text-align: inherit;
}
::-webkit-datetime-edit {
display: inline-flex;
}
::-webkit-datetime-edit-fields-wrapper {
padding: 0;
}
::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field {
padding-block: 0;
}
:-moz-ui-invalid {
box-shadow: none;
}
button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button {
appearance: button;
}
::-webkit-inner-spin-button, ::-webkit-outer-spin-button {
height: auto;
}
[hidden]:where(:not([hidden='until-found'])) {
display: none !important;
}
}
@layer utilities {
.visible {
visibility: visible;
}
.static {
position: static;
}
.contents {
display: contents;
}
.flex {
display: flex;
}
.hidden {
display: none;
}
.table {
display: table;
}
.h-screen {
height: 100vh;
}
.w-screen {
width: 100vw;
}
.transform {
transform: var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,);
}
.flex-col {
flex-direction: column;
}
.bg-zinc-900 {
background-color: var(--color-zinc-900);
}
.p-1 {
padding: calc(var(--spacing) * 1);
}
.p-2 {
padding: calc(var(--spacing) * 2);
}
.px-2 {
padding-inline: calc(var(--spacing) * 2);
}
.text-4xl {
font-size: var(--text-4xl);
line-height: var(--tw-leading, var(--text-4xl--line-height));
}
.font-bold {
--tw-font-weight: var(--font-weight-bold);
font-weight: var(--font-weight-bold);
}
.text-blue-800 {
color: var(--color-blue-800);
}
.text-white {
color: var(--color-white);
}
.underline {
text-decoration-line: underline;
}
}
@property --tw-rotate-x {
syntax: "*";
inherits: false;
}
@property --tw-rotate-y {
syntax: "*";
inherits: false;
}
@property --tw-rotate-z {
syntax: "*";
inherits: false;
}
@property --tw-skew-x {
syntax: "*";
inherits: false;
}
@property --tw-skew-y {
syntax: "*";
inherits: false;
}
@property --tw-font-weight {
syntax: "*";
inherits: false;
}
@layer properties {
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
*, ::before, ::after, ::backdrop {
--tw-rotate-x: initial;
--tw-rotate-y: initial;
--tw-rotate-z: initial;
--tw-skew-x: initial;
--tw-skew-y: initial;
--tw-font-weight: initial;
}
}
}

View File

@ -1,2 +1,2 @@
mod component; //mod component;
pub use component::*; //pub use component::*;

View File

@ -12,9 +12,11 @@ use crate::config::ClientConfig;
use std::time::Duration; use std::time::Duration;
#[cfg(feature = "server")]
use std::fs; use std::fs;
use std::fs::File; #[cfg(feature = "server")]
use std::io::Write; use std::io::Write;
#[cfg(feature = "server")]
use futures::StreamExt; use futures::StreamExt;
@ -44,7 +46,7 @@ async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
let filename: String = loop { let filename: String = loop {
let cur = random_string::generate(20, "ABCDEFGHIJKLMNOPQRTSTUVWXYZ0123456789"); let cur = random_string::generate(20, "ABCDEFGHIJKLMNOPQRTSTUVWXYZ0123456789");
if (!fs::exists(s_config.upload_folder.clone() + &cur).expect("can't check if file exists")) { if !fs::exists(s_config.upload_folder.clone() + &cur).expect("can't check if file exists") {
break cur; break cur;
} }
}; };
@ -74,7 +76,7 @@ async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
break; break;
} }
if file.write(&bytes).is_err() { error == Some("failed write"); break; }; if file.write(&bytes).is_err() { error = Some("failed write"); break; };
}, },
Err(_) => { error = Some("unknown"); break; } Err(_) => { error = Some("unknown"); break; }
} }
@ -82,8 +84,8 @@ async fn upload_file(mut upload: FileStream) -> Result<String, HttpError> {
match error { match error {
Some(err)=> { Some(err)=> {
file.sync_data(); let _ = file.sync_data();
fs::remove_file(s_config.upload_folder.clone() + &filename); let _ = fs::remove_file(s_config.upload_folder.clone() + &filename);
HttpError::internal_server_error(err)? HttpError::internal_server_error(err)?
} }
None => { None => {
@ -137,7 +139,7 @@ pub fn build_table(files: Vec<(String, String, Option<Result<String, HttpError>>
rsx! { p { "Upload failed, reason : {msg}" } } rsx! { p { "Upload failed, reason : {msg}" } }
} }
} } } }
None => { rsx! { p { "Waiting for file to be uploaded" } } } None => { rsx! { p { "Uploading ..." } } }
} }
} }
} }

View File

@ -1,9 +1,6 @@
use std::fs::create_dir;
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus::fullstack::FileStream; use dioxus::fullstack::FileStream;
#[cfg(feature = "server")]
use httpserver::models::GetFile;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use diesel::prelude::*; use diesel::prelude::*;
#[cfg(feature = "server")] #[cfg(feature = "server")]
@ -15,6 +12,7 @@ use httpserver::schema;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::config::ServerConfig; use crate::config::ServerConfig;
#[cfg(feature = "server")]
use std::path::Path; use std::path::Path;
@ -39,10 +37,10 @@ async fn download_file(id: String) -> Result<FileStream, HttpError> {
use schema::file; use schema::file;
let s_config = ServerConfig::load(); let s_config = ServerConfig::load();
let result = DB.with(|pool| schema::file::table.select((file::filename)) let result = DB.with(|pool| schema::file::table.select(file::filename)
.filter(file::stored_filename.eq(id.clone())) .filter(file::stored_filename.eq(id.clone()))
.filter(file::deleted.eq(false)) .filter(file::deleted.eq(false))
.load::<(String)>(&mut pool.get().unwrap())); .load::<String>(&mut pool.get().unwrap()));
if let Err(_) = result { if let Err(_) = result {
return HttpError::internal_server_error("Database request failed"); return HttpError::internal_server_error("Database request failed");

View File

@ -1,20 +1,17 @@
use std::time::SystemTime;
use chrono::DateTime; use chrono::DateTime;
use chrono::Utc; use chrono::Utc;
use dioxus::fullstack::httperror;
use dioxus::prelude::*; use dioxus::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use httpserver::utils::byte_to_human_size; use httpserver::utils::byte_to_human_size;
#[cfg(feature = "server")]
use httpserver::models::GetFile;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use diesel::prelude::*; use diesel::prelude::*;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use httpserver::DB; use httpserver::DB;
#[cfg(feature = "server")]
use std::time::SystemTime;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use httpserver::schema; use httpserver::schema;
@ -62,10 +59,15 @@ fn show_file_info(file_id: String, file_info: &FetchedInfo) -> Element {
p { "Position in the deletion queue : ", p { "Position in the deletion queue : ",
match file_info.deletion_pos { match file_info.deletion_pos {
Some(pos) => pos.to_string(), Some(pos) => pos.to_string(),
None => "unknown".to_string() None => "files aren't deleted for a week after the time of upload".to_string()
} }
} }
a { class: "height-20px text-blue-800 underline", href: "/upload/{file_id}/dl", "click here to download the file"} a {
class: "height-20px text-blue-800 underline",
rel: "noopener noreferrer",
href: "/upload/{file_id}/dl",
"click here to download the file"
}
} }
} }
} }

View File

@ -1,7 +1,5 @@
use dioxus::prelude::*; use dioxus::prelude::*;
#[cfg(feature = "server")]
use httpserver::models::GetFile;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use diesel::prelude::*; use diesel::prelude::*;
#[cfg(feature = "server")] #[cfg(feature = "server")]
@ -9,17 +7,14 @@ use httpserver::DB;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use diesel::dsl; use diesel::dsl;
#[cfg(feature = "server")]
use httpserver::schema;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use crate::config::ServerConfig; use crate::config::ServerConfig;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "server")]
use bigdecimal::{BigDecimal, ToPrimitive}; use bigdecimal::{BigDecimal, ToPrimitive};
use httpserver::utils::byte_to_human_size;
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
struct UploadServerStats { struct UploadServerStats {
total_limit_soft: u64, total_limit_soft: u64,
@ -43,8 +38,8 @@ async fn get_upload_stats() -> Result<UploadServerStats, HttpError> {
_ => return HttpError::internal_server_error("wth ?") _ => return HttpError::internal_server_error("wth ?")
}; };
let total_files = match DB.with(|pool| file::table.select((dsl::count(file::id))) let total_files = match DB.with(|pool| file::table.select(dsl::count(file::id))
.first::<(i64)>(&mut pool.get().unwrap())) { .first::<i64>(&mut pool.get().unwrap())) {
Ok(val) => val, Ok(val) => val,
_ => return HttpError::internal_server_error("wth ?") _ => return HttpError::internal_server_error("wth ?")
}; };
@ -61,8 +56,12 @@ async fn get_upload_stats() -> Result<UploadServerStats, HttpError> {
} }
fn render_stats(stats : &UploadServerStats) -> Element { fn render_stats(stats : &UploadServerStats) -> Element {
let soft_percentage = stats.used_space as f32 / stats.total_limit_soft as f32; let mut soft_percentage = stats.used_space as f32 / stats.total_limit_soft as f32;
let hard_percentage = stats.used_space as f32 / stats.total_limit_hard as f32; let mut hard_percentage = stats.used_space as f32 / stats.total_limit_hard as f32;
soft_percentage = (soft_percentage * 1000f32).round() / 1000f32;
hard_percentage = (hard_percentage * 1000f32).round() / 1000f32;
rsx! { rsx! {
h1 {class: "text-4xl font-bold", "Server info"} h1 {class: "text-4xl font-bold", "Server info"}

View File

@ -12,23 +12,23 @@ impl ServerConfig {
pub fn load() -> Self { pub fn load() -> Self {
Self { Self {
upload_folder: "./test/".to_string(), upload_folder: "./test/".to_string(),
db_url: std::env::var("DATABASE_URL").expect("missing DATABASE_URL"), db_url: std::env::var("HTTPSERVER_DATABASE_URL").expect("missing HTTPSERVER_DATABASE_URL"),
upload_storage_limit_soft: 1024 * 1024 * 1024 * 200, upload_storage_limit_soft: 1024 * 1024 * 1024 * 200,
upload_storage_limit_hard: 1024 * 1024 * 1024 * 300, upload_storage_limit_hard: 1024 * 1024 * 1024 * 250,
} }
} }
} }
pub struct ClientConfig { pub struct ClientConfig {
pub upload_max_size: usize, pub upload_max_size: u64,
} }
impl ClientConfig { impl ClientConfig {
pub fn load() -> Self { pub fn load() -> Self {
Self { Self {
upload_max_size: 1024 * 1024 * 1024 * 1, //1GB for testing upload_max_size: 1024 * 1024 * 1024 * 50,
} }
} }
} }

View File

@ -1,7 +1,5 @@
#[cfg(feature = "server")] #[cfg(feature = "server")]
use diesel::pg::PgConnection; use diesel::pg::PgConnection;
#[cfg(feature = "server")]
use diesel::prelude::*;
pub mod utils; pub mod utils;
@ -18,17 +16,15 @@ pub mod schema;
#[cfg(feature = "server")] #[cfg(feature = "server")]
use diesel::r2d2::{ConnectionManager, Pool}; use diesel::r2d2::{ConnectionManager, Pool};
#[cfg(feature = "server")]
use diesel::result::Error;
#[cfg(feature = "server")] #[cfg(feature = "server")]
thread_local! { thread_local! {
pub static DB: diesel::r2d2::Pool<ConnectionManager<PgConnection>> = { pub static DB: Pool<ConnectionManager<PgConnection>> = {
let s_config = ServerConfig::load(); let s_config = ServerConfig::load();
let db_url = s_config.db_url.clone(); let db_url = s_config.db_url.clone();
let manager = ConnectionManager::<PgConnection>::new(&db_url); let manager = ConnectionManager::<PgConnection>::new(&db_url);
diesel::r2d2::Pool::builder() Pool::builder()
.build(manager) .build(manager)
.unwrap_or_else(|_| panic!("Error creating pool for {}", db_url)) .unwrap_or_else(|_| panic!("Error creating pool for {}", db_url))
}; };

View File

@ -1,27 +1,38 @@
use dioxus::prelude::*; use dioxus::prelude::*;
use crate::components::upload::{FileInfo, UploadFile, UploadStats};
use tracing::Level; use tracing::Level;
pub mod config; pub mod config;
use crate::components::toast::ToastProvider; use crate::components::toast::ToastProvider;
use crate::views::{Upload, UploadInfo, Home};
#[cfg(feature = "server")]
use crate::config::ServerConfig;
mod components; mod components;
mod views; mod views;
//#[derive(Debug, Clone, Routable, PartialEq)] #[derive(Debug, Clone, Routable, PartialEq)]
//#[rustfmt::skip] #[rustfmt::skip]
//enum Route { enum Route {
// #[route("/upload")] #[route("/")]
// Upload { }, Home { },
//} #[route("/upload/:file")]
UploadInfo { file: String },
#[route("/upload")]
Upload { },
}
const FAVICON: Asset = asset!("/assets/favicon.ico"); const FAVICON: Asset = asset!("/assets/favicon.ico");
const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css"); const TAILWIND_CSS: Asset = asset!("/assets/tailwind.css");
fn main() { fn main() {
#[cfg(feature = "server")]
let _ = ServerConfig::load();
dioxus::logger::init(Level::INFO).expect("failed to init logger"); dioxus::logger::init(Level::INFO).expect("failed to init logger");
dioxus::launch(App); dioxus::launch(App);
} }
@ -34,10 +45,7 @@ fn App() -> Element {
div { div {
class: "h-screen w-screen bg-zinc-900 text-white", class: "h-screen w-screen bg-zinc-900 text-white",
ToastProvider { ToastProvider {
UploadFile { } Router::<Route> {}
FileInfo {file: "ULI0GZWJQH9BGEZR1VZ1"}
UploadStats { }
// Router::<Route> {}
} }
} }

21
src/views/home.rs Normal file
View File

@ -0,0 +1,21 @@
use dioxus::prelude::*;
use crate::Route;
#[component]
pub fn Home() -> Element {
rsx! {
div { class : "flex flex-col text-3xl px-2",
Link {
to : Route::Upload { },
class: "height-20px text-blue-700 underline py-1",
"Upload"
}
a {
class: "height-20px text-blue-700 underline py-1",
href : "https://git.tmoron.fr/tom ",
"gitea"
}
p { class : "text-sm text-gray-600", "I know, you're impressed by my design skills." }
}
}
}

View File

@ -1,2 +1,5 @@
//mod upload; mod upload;
//pub use upload::Upload; pub use upload::{Upload, UploadInfo};
mod home;
pub use home::Home;

View File

@ -1,20 +1,19 @@
use dioxus::{ use dioxus::prelude::*;
fullstack::{FileStream},
prelude::*,
};
#[cfg(feature = "server")] use crate::components::upload::{ FileInfo, UploadFile, UploadStats };
use crate::config::ServerConfig;
#[cfg(feature = "server")]
use httpserver::models::{GetFile,NewFile};
#[cfg(feature = "server")]
use diesel::prelude::*;
#[cfg(feature = "server")]
use httpserver::DB;
use dioxus_primitives::toast::{ #[component]
ToastOptions, pub fn Upload() -> Element {
consume_toast rsx! {
}; UploadFile { }
UploadStats { }
}
}
#[component]
pub fn UploadInfo(file: String) -> Element {
rsx! {
FileInfo{ file : file }
}
}