feat: add initial structures and routing logic

This commit is contained in:
koalp 2021-05-20 18:26:13 +02:00
parent 3883e0f46b
commit 0736f3851b
Signed by: koalp
GPG Key ID: 35B21047DEB09A81
13 changed files with 1765 additions and 0 deletions

1
.env Normal file
View File

@ -0,0 +1 @@
FEUILLE_DATA_DIR=./tmp.data

View File

@ -0,0 +1,19 @@
---
name: "🐛 Bug report"
about: "For reporting bugs"
title: ""
labels:
- "type::bug"
- "status::review_needed"
---
**Description**
*write a concise bug description*
**Steps to reproduce**
1.
2.
**Expected behavior**
*describe what you expected to happen*

View File

@ -0,0 +1,15 @@
---
name: "🗣 Discussion"
about: "For discussion about the software, when you want to discuss about several conception possibilities"
title: ""
labels:
- "type::discussion"
- "status::review_needed"
---
*describe the problem *
## Propositions
*(optionnal) explain the different implementation that you would propose*

View File

@ -0,0 +1,14 @@
---
name: "💡 Feature request"
about: "For requesting a new feature, with an implementation plan"
title: ""
labels:
- "type::enhancement"
- "status::review_needed"
---
*(if applicable) describe what problem or frustration you have currently*
*describe what you would like to be able to do, or what solution you would like*
*(optional) additional context, comments

View File

@ -0,0 +1,15 @@
---
name: "❓ Ask a question"
about: "If you have a question about the usage of the libraries or the tool"
title: ""
labels:
- "type::question"
- "status::review_needed"
---
*ask your question*
*describe what you have you read so far to try to answer this question ?*
*(optional) would you think the is a in documentation ?*

View File

@ -0,0 +1,11 @@
---
name: "🚧 Refactor"
about: "For refactoring propositions"
title: ""
labels:
- "type::refactor"
- "status::review_needed"
---
*explain why and what you want to refactor*

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/target

1479
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

23
Cargo.toml Normal file
View File

@ -0,0 +1,23 @@
[package]
name = "feuille"
version = "0.1.0"
authors = ["koalp <koalp@alpaga.dev>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.40"
dotenv = "0.15.0"
url = "2.2.1"
thiserror = "1.0.24"
[dependencies.rocket]
git = "https://github.com/SergioBenitez/Rocket"
rev = "3a7559edcec7c443e68e22e038aaa2d90ef27c23"
version = "0.5.0-dev"
[dependencies.rocket_contrib]
git = "https://github.com/SergioBenitez/Rocket"
rev = "3a7559edcec7c443e68e22e038aaa2d90ef27c23"
version = "0.5.0-dev"

32
README.md Normal file
View File

@ -0,0 +1,32 @@
A static website hosting service for use in CD.
It aim at providing a service similar to github or gitlab pages for self-hosted
software forges and CI/CD services.
# TODO
## Phase I
- [ ] upload /subdomain/<subdomain>/upload -> untar
- [ ] serve for any subdomain
## Phase II
- [ ] store subdomains linked to each upload (which database ? sled ?)
- [ ] serve for specific subdomain (rewrite sub.pages.domain.net to pages.domain.net/sub ?)
## Phase III
- [ ] config (ex: single page app, custom 404, et c)
- config in static file, or in api ? (or both ?)
- [ ] simple login (and prepare fail2ban)
Yeah ! A minimal working service !
## Phase IV
Highly hypothetical
- [ ] upload from web interface
- [ ] ability to set login backend and api OIDC auth
- [ ] subdomain reservation mechanism (usecase: forge reserve subdomains for
users/orgs)

19
src/api.rs Normal file
View File

@ -0,0 +1,19 @@
use rocket::{
data::TempFile,
post,
routes,
Route,
};
pub fn routes() -> Vec<Route> {
routes![subdomain]
}
#[post(
"/subdomain/<subdomain>/upload",
format = "application/x-gtar",
data = "<file>"
)]
pub fn subdomain(subdomain: String, mut file: TempFile<'_>) {
println!("upload to subdomain {}", subdomain);
}

26
src/main.rs Normal file
View File

@ -0,0 +1,26 @@
use std::env;
use std::sync::{Arc, Mutex};
use dotenv::dotenv;
use rocket::{get, launch, routes};
use url::Host;
mod subdomains;
use subdomains::Hosts;
mod api;
#[launch]
fn rocket() -> _ {
dotenv().ok();
env::var("FEUILLE_DATA_DIR").expect("You have to set the FEUILLE_DATA_DIR configuration");
// TODO: replace by builder
let subdomain_hosts =
Hosts::new(Host::parse("example.com").expect("invalid host")).add_subdomain("test");
rocket::build()
.mount("/", api::routes())
.mount("/", subdomains::routes())
.manage(subdomain_hosts)
//.mount("/", StaticFiles::from("tmp.data/"))
}

110
src/subdomains.rs Normal file
View File

@ -0,0 +1,110 @@
use std::path::PathBuf;
use std::sync::{self, Arc, Mutex};
use std::convert::TryInto;
use thiserror::Error;
use url::Host;
use rocket::{get, Route, routes, State};
use rocket::outcome::try_outcome;
use rocket::request::{FromRequest, Request, Outcome};
pub fn routes() -> Vec<Route> {
routes![static_website]
}
#[derive(PartialEq, Clone, Debug, Error)]
pub enum Error {
#[error("Can't lock an underling mutex")]
Lock,
#[error("Malformed subdomain, did you put '.' in it ?")]
MalformedSubdomain,
#[error("IP host are not supported")]
IPHost,
}
type Result<T, E = Error> = core::result::Result<T, E>;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Website {
path: PathBuf,
host: Host,
}
impl Website {
/// Creates a website from a given subdomain and a base_host
///
/// # Errors
///
/// If your subdomain contains dots, an Error::MalformedUrl is returned
/// Error::IPHost is returned if the given host is an IP
fn from_subdomain<S: AsRef<str>>(subdomain: S, base_host: &Host) -> Result<Self> {
// TODO: implement the path creation
let path = "".into();
if subdomain.as_ref().contains(".") {
return Err(Error::MalformedSubdomain);
}
let host = match base_host {
Host::Domain(base_host) => format!("{}.{}", subdomain.as_ref(), base_host.to_string()),
_ => return Err(Error::IPHost),
};
let host = Host::parse(&host).map_err(|_| Error::MalformedSubdomain)?;
Ok(Self {
path,
host,
})
}
}
#[derive(Debug, Clone)]
pub(crate) struct Hosts {
hosts: Arc<Mutex<Vec<Website>>>,
base_host: Host,
}
impl Hosts {
pub fn new(base_host: Host) -> Self {
Self{
hosts: Arc::new(Mutex::new(vec![])),
base_host,
}
}
pub fn contains_host(&self, host: &Host) -> Result<bool>{
match self.hosts.lock() {
Ok(hosts) => Ok(hosts.iter().any(|website| &website.host == host)),
Err(_) => Err(Error::Lock),
}
}
pub fn add_subdomain<S: AsRef<str>>(&self, subdomain: S) -> Result<()> {
match &mut self.hosts.lock() {
Ok(hosts) => {
hosts.push(Website::from_subdomain(subdomain, &self.base_host)?);
Ok(())
},
Err(_) => Err(Error::Lock),
}
}
}
pub struct Header<'a>(pub &'a str);
#[rocket::async_trait]
impl<'a> FromRequest<'a> for Header<'a> {
type Error = ();
async fn from_request(req: &'a Request<'_>) -> Outcome<Self, Self::Error> {
let subdomain_hosts = try_outcome!(req.guard::<State<Hosts>>().await);
match (req.headers().get_one("Host"), &subdomain_hosts) {
// TODO: those unwrap should return Outcome Errors
(Some(h), sh) if sh.contains_host(&Host::parse(h).unwrap()).unwrap() => Outcome::Success(Header(h)),
(_, _) => return Outcome::Forward(()),
}
}
}
#[get("/<path..>")]
fn static_website(path: PathBuf, subdomain: Header) {
println!("go go go to the subdomain {} and path {:?} !", subdomain.0, path);
}