static serve refactor
This commit is contained in:
parent
028026bfdc
commit
2026a8f457
16 changed files with 192 additions and 173 deletions
|
@ -80,7 +80,7 @@ body {
|
|||
}
|
||||
|
||||
.icons {
|
||||
background-image: url('/images/icons.png');
|
||||
background-image: url('/image/icons.png');
|
||||
display: inline-block;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
|
|
|
@ -33,11 +33,11 @@ function remove(id) {
|
|||
}
|
||||
|
||||
function pfp(id) {
|
||||
return `<img src="/cdn/avatar?user_id=${id}">`
|
||||
return `<img src="/image/avatar?user_id=${id}">`
|
||||
}
|
||||
|
||||
function banner(id) {
|
||||
return `<img src="/cdn/banner?user_id=${id}" onerror="this.remove()" >`
|
||||
return `<img src="/image/banner?user_id=${id}" onerror="this.remove()" >`
|
||||
}
|
||||
|
||||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',
|
||||
|
|
|
@ -5,7 +5,8 @@ use serde::Deserialize;
|
|||
use tower_cookies::{Cookie, Cookies};
|
||||
|
||||
use crate::{
|
||||
admin, database,
|
||||
database,
|
||||
public::admin,
|
||||
types::{
|
||||
extract::{AdminUser, Check, CheckResult, Json},
|
||||
http::ResponseCode,
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
use axum::{extract::Query, response::Response, routing::get, Router, http::StatusCode};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::types::http::ResponseCode;
|
||||
|
||||
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AvatarRequest {
|
||||
user_id: u64,
|
||||
}
|
||||
|
||||
async fn avatar(params: Option<Query<AvatarRequest>>) -> Response {
|
||||
|
||||
let Some(params) = params else {
|
||||
return ResponseCode::BadRequest.text("Missing query paramaters");
|
||||
};
|
||||
|
||||
let custom = format!("/image/custom/avatar/{}.png", params.user_id);
|
||||
let default = format!("/image/default/{}.png", params.user_id % 25);
|
||||
|
||||
let file = ResponseCode::Success.file(&custom).await;
|
||||
if file.status() != StatusCode::OK {
|
||||
return ResponseCode::Success.file(&default).await
|
||||
}
|
||||
file
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct BannerRequest {
|
||||
user_id: u64,
|
||||
}
|
||||
|
||||
async fn banner(params: Option<Query<BannerRequest>>) -> Response {
|
||||
|
||||
let Some(params) = params else {
|
||||
return ResponseCode::BadRequest.text("Missing query paramaters");
|
||||
};
|
||||
|
||||
let custom = format!("/image/custom/banner/{}.png", params.user_id);
|
||||
|
||||
let file = ResponseCode::Success.file(&custom).await;
|
||||
if file.status() != StatusCode::OK {
|
||||
return ResponseCode::NotFound.text("User does not have a custom banner")
|
||||
}
|
||||
file
|
||||
}
|
||||
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.route("/avatar", get(avatar))
|
||||
.route("/banner", get(banner))
|
||||
}
|
|
@ -1,14 +1,17 @@
|
|||
use crate::types::extract::RouterURI;
|
||||
use axum::{Extension, Router, BoxError, error_handling::HandleErrorLayer};
|
||||
use axum::{error_handling::HandleErrorLayer, BoxError, Extension, Router};
|
||||
use tower::ServiceBuilder;
|
||||
use tower_governor::{governor::GovernorConfigBuilder, GovernorLayer, errors::display_error, key_extractor::SmartIpKeyExtractor};
|
||||
use tower_governor::{
|
||||
errors::display_error, governor::GovernorConfigBuilder, key_extractor::SmartIpKeyExtractor,
|
||||
GovernorLayer,
|
||||
};
|
||||
|
||||
pub mod admin;
|
||||
pub mod auth;
|
||||
pub mod pages;
|
||||
pub mod posts;
|
||||
pub mod users;
|
||||
pub mod image;
|
||||
mod admin;
|
||||
mod auth;
|
||||
mod posts;
|
||||
mod users;
|
||||
|
||||
pub use auth::RegistrationRequet;
|
||||
|
||||
pub fn router() -> Router {
|
||||
let governor_conf = Box::new(
|
||||
|
|
|
@ -3,7 +3,11 @@ use crate::types::{
|
|||
http::ResponseCode,
|
||||
user::User,
|
||||
};
|
||||
use axum::{response::Response, routing::{post, put}, Router};
|
||||
use axum::{
|
||||
response::Response,
|
||||
routing::{post, put},
|
||||
Router,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
|
@ -64,7 +68,6 @@ async fn load_self(AuthorizedUser(user): AuthorizedUser) -> Response {
|
|||
}
|
||||
|
||||
async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response {
|
||||
|
||||
let path = format!("./public/image/custom/avatar/{}.png", user.user_id);
|
||||
|
||||
if img.save(path).is_err() {
|
||||
|
@ -75,7 +78,6 @@ async fn avatar(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response
|
|||
}
|
||||
|
||||
async fn banner(AuthorizedUser(user): AuthorizedUser, Png(img): Png) -> Response {
|
||||
|
||||
let path = format!("./public/image/custom/banner/{}.png", user.user_id);
|
||||
|
||||
if img.save(path).is_err() {
|
||||
|
|
|
@ -2,7 +2,7 @@ use rusqlite::{OptionalExtension, Row};
|
|||
use std::time::{Duration, SystemTime, UNIX_EPOCH};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::{api::auth::RegistrationRequet, database, types::user::User};
|
||||
use crate::{api::RegistrationRequet, database, types::user::User};
|
||||
|
||||
pub fn init() -> Result<(), rusqlite::Error> {
|
||||
let sql = "
|
||||
|
|
39
src/main.rs
39
src/main.rs
|
@ -1,38 +1,25 @@
|
|||
use axum::{
|
||||
body::HttpBody,
|
||||
http::{Request, StatusCode},
|
||||
extract::DefaultBodyLimit,
|
||||
http::Request,
|
||||
middleware::{self, Next},
|
||||
response::Response,
|
||||
RequestExt, Router, extract::DefaultBodyLimit,
|
||||
RequestExt, Router,
|
||||
};
|
||||
use std::{net::SocketAddr, process::exit, fs};
|
||||
use public::console;
|
||||
use std::{fs, net::SocketAddr, process::exit};
|
||||
use tower_cookies::CookieManagerLayer;
|
||||
use tracing::{error, info, metadata::LevelFilter};
|
||||
use tracing_subscriber::{
|
||||
filter::filter_fn, prelude::__tracing_subscriber_SubscriberExt, util::SubscriberInitExt, Layer,
|
||||
};
|
||||
use types::{http::ResponseCode, extract::RequestIp};
|
||||
use types::extract::RequestIp;
|
||||
|
||||
use crate::api::{pages, image};
|
||||
|
||||
mod admin;
|
||||
mod api;
|
||||
mod console;
|
||||
mod database;
|
||||
mod public;
|
||||
mod types;
|
||||
|
||||
async fn serve<B>(req: Request<B>, next: Next<B>) -> Response
|
||||
where
|
||||
B: Send + Sync + 'static + HttpBody,
|
||||
{
|
||||
let uri = req.uri();
|
||||
let file = ResponseCode::Success.file(&uri.to_string()).await;
|
||||
if file.status() != StatusCode::OK {
|
||||
return next.run(req).await;
|
||||
}
|
||||
file
|
||||
}
|
||||
|
||||
async fn log<B>(mut req: Request<B>, next: Next<B>) -> Response
|
||||
where
|
||||
B: Send + Sync + 'static + HttpBody,
|
||||
|
@ -47,7 +34,7 @@ where
|
|||
}
|
||||
|
||||
async fn not_found() -> Response {
|
||||
ResponseCode::NotFound.file("/404.html").await
|
||||
public::serve("/404.html").await
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -69,16 +56,16 @@ async fn main() {
|
|||
};
|
||||
|
||||
fs::create_dir_all("./public/image/custom").expect("Coudn't make custom data directory");
|
||||
fs::create_dir_all("./public/image/custom/avatar").expect("Coudn't make custom avatar directory");
|
||||
fs::create_dir_all("./public/image/custom/banner").expect("Coudn't make custom banner directory");
|
||||
fs::create_dir_all("./public/image/custom/avatar")
|
||||
.expect("Coudn't make custom avatar directory");
|
||||
fs::create_dir_all("./public/image/custom/banner")
|
||||
.expect("Coudn't make custom banner directory");
|
||||
|
||||
let app = Router::new()
|
||||
.fallback(not_found)
|
||||
.layer(middleware::from_fn(log))
|
||||
.layer(middleware::from_fn(serve))
|
||||
.nest("/", pages::router())
|
||||
.nest("/", public::router())
|
||||
.nest("/api", api::router())
|
||||
.nest("/cdn", image::router())
|
||||
.layer(CookieManagerLayer::new())
|
||||
.layer(DefaultBodyLimit::max(512_000));
|
||||
|
||||
|
|
69
src/public/file.rs
Normal file
69
src/public/file.rs
Normal file
|
@ -0,0 +1,69 @@
|
|||
use axum::{
|
||||
extract::{Path, Query},
|
||||
http::StatusCode,
|
||||
response::Response,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::types::http::ResponseCode;
|
||||
|
||||
use super::console;
|
||||
|
||||
pub async fn js(Path(path): Path<String>) -> Response {
|
||||
let path = format!("/js/{}", path);
|
||||
super::serve(&path).await
|
||||
}
|
||||
|
||||
pub async fn css(Path(path): Path<String>) -> Response {
|
||||
let path = format!("/css/{}", path);
|
||||
super::serve(&path).await
|
||||
}
|
||||
|
||||
pub async fn fonts(Path(path): Path<String>) -> Response {
|
||||
let path = format!("/fonts/{}", path);
|
||||
super::serve(&path).await
|
||||
}
|
||||
|
||||
pub async fn image(Path(path): Path<String>) -> Response {
|
||||
let path = format!("/image/{}", path);
|
||||
super::serve(&path).await
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct AvatarRequest {
|
||||
user_id: u64,
|
||||
}
|
||||
|
||||
pub async fn avatar(params: Option<Query<AvatarRequest>>) -> Response {
|
||||
let Some(params) = params else {
|
||||
return ResponseCode::BadRequest.text("Missing query paramaters");
|
||||
};
|
||||
|
||||
let custom = format!("/image/custom/avatar/{}.png", params.user_id);
|
||||
let default = format!("/image/default/{}.png", params.user_id % 25);
|
||||
|
||||
let file = super::serve(&custom).await;
|
||||
if file.status() != StatusCode::OK {
|
||||
return super::serve(&default).await;
|
||||
}
|
||||
file
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct BannerRequest {
|
||||
user_id: u64,
|
||||
}
|
||||
|
||||
pub async fn banner(params: Option<Query<BannerRequest>>) -> Response {
|
||||
let Some(params) = params else {
|
||||
return ResponseCode::BadRequest.text("Missing query paramaters");
|
||||
};
|
||||
|
||||
let custom = format!("/image/custom/banner/{}.png", params.user_id);
|
||||
|
||||
let file = super::serve(&custom).await;
|
||||
if file.status() != StatusCode::OK {
|
||||
return ResponseCode::NotFound.text("User does not have a custom banner");
|
||||
}
|
||||
file
|
||||
}
|
47
src/public/mod.rs
Normal file
47
src/public/mod.rs
Normal file
|
@ -0,0 +1,47 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
http::{Request, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeFile;
|
||||
|
||||
use crate::types::http::ResponseCode;
|
||||
|
||||
pub mod admin;
|
||||
pub mod console;
|
||||
pub mod file;
|
||||
pub mod pages;
|
||||
|
||||
pub fn router() -> Router {
|
||||
Router::new()
|
||||
.nest("/", pages::router())
|
||||
.route("/js/*path", get(file::js))
|
||||
.route("/css/*path", get(file::css))
|
||||
.route("/fonts/*path", get(file::fonts))
|
||||
.route("/image/*path", get(file::image))
|
||||
.route("/image/avatar", get(file::avatar))
|
||||
.route("/image/banner", get(file::banner))
|
||||
}
|
||||
|
||||
pub async fn serve(path: &str) -> Response {
|
||||
if !path.chars().any(|c| c == '.') {
|
||||
return ResponseCode::BadRequest.text("Invalid file path");
|
||||
}
|
||||
|
||||
let path = format!("public{path}");
|
||||
let file = ServeFile::new(path);
|
||||
|
||||
let Ok(res) = file.oneshot(Request::new(Body::empty())).await else {
|
||||
tracing::error!("Error while fetching file");
|
||||
return ResponseCode::InternalServerError.text("Error while fetching file");
|
||||
};
|
||||
|
||||
if res.status() != StatusCode::OK {
|
||||
return ResponseCode::NotFound.text("File not found");
|
||||
}
|
||||
|
||||
res.into_response()
|
||||
}
|
|
@ -5,7 +5,7 @@ use axum::{
|
|||
};
|
||||
|
||||
use crate::{
|
||||
console,
|
||||
public::console,
|
||||
types::{
|
||||
extract::{AuthorizedUser, Log},
|
||||
http::ResponseCode,
|
||||
|
@ -24,32 +24,20 @@ async fn login(user: Option<AuthorizedUser>, _: Log) -> Response {
|
|||
if user.is_some() {
|
||||
Redirect::to("/home").into_response()
|
||||
} else {
|
||||
ResponseCode::Success.file("/login.html").await
|
||||
super::serve("/login.html").await
|
||||
}
|
||||
}
|
||||
|
||||
async fn home(user: Option<AuthorizedUser>, _: Log) -> Response {
|
||||
if user.is_none() {
|
||||
Redirect::to("/login").into_response()
|
||||
} else {
|
||||
ResponseCode::Success.file("/home.html").await
|
||||
}
|
||||
async fn home(_: Log) -> Response {
|
||||
super::serve("/home.html").await
|
||||
}
|
||||
|
||||
async fn people(user: Option<AuthorizedUser>, _: Log) -> Response {
|
||||
if user.is_none() {
|
||||
Redirect::to("/login").into_response()
|
||||
} else {
|
||||
ResponseCode::Success.file("/people.html").await
|
||||
}
|
||||
async fn people(_: Log) -> Response {
|
||||
super::serve("/people.html").await
|
||||
}
|
||||
|
||||
async fn profile(user: Option<AuthorizedUser>, _: Log) -> Response {
|
||||
if user.is_none() {
|
||||
Redirect::to("/login").into_response()
|
||||
} else {
|
||||
ResponseCode::Success.file("/profile.html").await
|
||||
}
|
||||
async fn profile(_: Log) -> Response {
|
||||
super::serve("/profile.html").await
|
||||
}
|
||||
|
||||
async fn console() -> Response {
|
||||
|
@ -57,7 +45,7 @@ async fn console() -> Response {
|
|||
}
|
||||
|
||||
async fn admin() -> Response {
|
||||
ResponseCode::Success.file("/admin.html").await
|
||||
super::serve("/admin.html").await
|
||||
}
|
||||
|
||||
async fn wordpress(_: Log) -> Response {
|
|
@ -1,20 +1,24 @@
|
|||
use std::{io::{Read, Cursor}, net::{IpAddr, SocketAddr}};
|
||||
use std::{
|
||||
io::{Cursor, Read},
|
||||
net::{IpAddr, SocketAddr},
|
||||
};
|
||||
|
||||
use axum::{
|
||||
async_trait,
|
||||
body::HttpBody,
|
||||
extract::{FromRequest, FromRequestParts, ConnectInfo},
|
||||
extract::{ConnectInfo, FromRequest, FromRequestParts},
|
||||
http::{request::Parts, Request},
|
||||
response::Response,
|
||||
BoxError, RequestExt,
|
||||
};
|
||||
use bytes::Bytes;
|
||||
use image::{io::Reader, ImageFormat, DynamicImage};
|
||||
use image::{io::Reader, DynamicImage, ImageFormat};
|
||||
use serde::de::DeserializeOwned;
|
||||
use tower_cookies::Cookies;
|
||||
|
||||
use crate::{
|
||||
admin, console,
|
||||
public::admin,
|
||||
public::console,
|
||||
types::{
|
||||
http::{ResponseCode, Result},
|
||||
session::Session,
|
||||
|
@ -32,41 +36,43 @@ where
|
|||
type Rejection = Response;
|
||||
|
||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self> {
|
||||
|
||||
let headers = &parts.headers;
|
||||
|
||||
let forwardedfor = headers.get("x-forwarded-for")
|
||||
let forwardedfor = headers
|
||||
.get("x-forwarded-for")
|
||||
.and_then(|h| h.to_str().ok())
|
||||
.and_then(|h|
|
||||
.and_then(|h| {
|
||||
h.split(',')
|
||||
.rev()
|
||||
.find_map(|s| s.trim().parse::<IpAddr>().ok())
|
||||
);
|
||||
});
|
||||
|
||||
if let Some(forwardedfor) = forwardedfor {
|
||||
return Ok(RequestIp(forwardedfor))
|
||||
return Ok(RequestIp(forwardedfor));
|
||||
}
|
||||
|
||||
let realip = headers.get("x-real-ip")
|
||||
let realip = headers
|
||||
.get("x-real-ip")
|
||||
.and_then(|hv| hv.to_str().ok())
|
||||
.and_then(|s| s.parse::<IpAddr>().ok());
|
||||
|
||||
if let Some(realip) = realip {
|
||||
return Ok(RequestIp(realip))
|
||||
return Ok(RequestIp(realip));
|
||||
}
|
||||
|
||||
let realip = headers.get("x-real-ip")
|
||||
let realip = headers
|
||||
.get("x-real-ip")
|
||||
.and_then(|hv| hv.to_str().ok())
|
||||
.and_then(|s| s.parse::<IpAddr>().ok());
|
||||
|
||||
if let Some(realip) = realip {
|
||||
return Ok(RequestIp(realip))
|
||||
return Ok(RequestIp(realip));
|
||||
}
|
||||
|
||||
let info = parts.extensions.get::<ConnectInfo<SocketAddr>>();
|
||||
|
||||
if let Some(info) = info {
|
||||
return Ok(RequestIp(info.0.ip()))
|
||||
return Ok(RequestIp(info.0.ip()));
|
||||
}
|
||||
|
||||
Err(ResponseCode::Forbidden.text("You have no ip"))
|
||||
|
@ -163,7 +169,6 @@ where
|
|||
type Rejection = Response;
|
||||
|
||||
async fn from_request(req: Request<B>, state: &S) -> Result<Self> {
|
||||
|
||||
let bytes = match read_body(req, state).await {
|
||||
Ok(body) => body,
|
||||
Err(err) => return Err(err),
|
||||
|
@ -238,7 +243,6 @@ where
|
|||
B::Error: Into<BoxError>,
|
||||
S: Send + Sync,
|
||||
{
|
||||
|
||||
let Ok(RequestIp(ip)) = req.extract_parts::<RequestIp>().await else {
|
||||
tracing::error!("Failed to read client ip");
|
||||
return Err(ResponseCode::InternalServerError.text("Failed to read client ip"));
|
||||
|
@ -255,14 +259,7 @@ where
|
|||
return Err(ResponseCode::BadRequest.text("Request can be at most 512kb"));
|
||||
};
|
||||
|
||||
console::log(
|
||||
ip,
|
||||
method,
|
||||
uri,
|
||||
Some(path.to_string()),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
console::log(ip, method, uri, Some(path.to_string()), None).await;
|
||||
|
||||
Ok(bytes.bytes().flatten().collect())
|
||||
}
|
||||
|
|
|
@ -1,11 +1,8 @@
|
|||
use axum::{
|
||||
body::Body,
|
||||
headers::HeaderName,
|
||||
http::{HeaderValue, Request, StatusCode},
|
||||
http::{HeaderValue, StatusCode},
|
||||
response::{IntoResponse, Response},
|
||||
};
|
||||
use tower::ServiceExt;
|
||||
use tower_http::services::ServeFile;
|
||||
use tracing::instrument;
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -58,24 +55,6 @@ impl ResponseCode {
|
|||
);
|
||||
res
|
||||
}
|
||||
|
||||
#[instrument()]
|
||||
pub async fn file(self, path: &str) -> Response {
|
||||
if !path.chars().any(|c| c == '.') {
|
||||
return Self::BadRequest.text("Folders cannot be served");
|
||||
}
|
||||
let path = format!("public{path}");
|
||||
let svc = ServeFile::new(path);
|
||||
let Ok(mut res) = svc.oneshot(Request::new(Body::empty())).await else {
|
||||
tracing::error!("Error while fetching file");
|
||||
return Self::InternalServerError.text("Error while fetching file");
|
||||
};
|
||||
if res.status() != StatusCode::OK {
|
||||
return Self::NotFound.text("File not found");
|
||||
}
|
||||
*res.status_mut() = self.code();
|
||||
res.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, Response>;
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::api::auth::RegistrationRequet;
|
||||
use crate::api::RegistrationRequet;
|
||||
use crate::database;
|
||||
use crate::types::http::{ResponseCode, Result};
|
||||
|
||||
|
|
Loading…
Reference in a new issue