Skip to content

Commit 2cb0021

Browse files
committed
done rsync, base roting
1 parent 0c39412 commit 2cb0021

File tree

14 files changed

+586
-3
lines changed

14 files changed

+586
-3
lines changed

.env

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
STORE_PATH=""
2+
CACHE_PATH=""

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,7 @@ debug
33
**/dist
44
**/.DS_Store
55
**/Cargo.lock
6+
.store/*
7+
.ref/*
8+
!.store/.gitkeep
9+
!.ref/.gitkeep

.ref/.gitkeep

Whitespace-only changes.

.store/.gitkeep

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
it store directory

Cargo.toml

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,21 @@ license = "MIT" #"Apache-2.0"
1111
ohkami = { version = "0.24.2", features = ["openapi", "rt_tokio"] }
1212
tokio = { version = "1.48.0", features = ["full"] }
1313
tracing = "^0.1"
14-
tracing-subscriber = "^0.3"
14+
tracing-subscriber = { version = "^0.3", features = ["env-filter"] }
1515
serde = { version = "^1", features = ["derive"] }
1616
serde_json = { version = "1" }
1717
thiserror = "^2"
18+
tee_morphosis = "1.3.0"
19+
image = { version = "0.25.8", default-features = false }
20+
reqwest = "0.12.24"
21+
dashmap = { version = "6.1.0", features = ["rayon", "serde"] }
22+
regex = "1.12.2"
23+
rayon = "1.11.0"
24+
dotenvy = "0.15.7"
25+
futures = "0.3.31"
26+
27+
[dev-dependencies]
28+
tokio = { version = "1.48.0", features = ["full"] }
1829

1930
[profile.dev]
2031
opt-level = 0

src/app.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,24 @@
1919
//! - Сбор статистики, кто, зачем
2020
//! - Мб рейтлимит
2121
//! - Бан лист (:))
22+
23+
pub mod logger;
24+
pub mod png;
25+
pub mod skin;
26+
27+
use ohkami::{Ohkami, Route, claw::status};
28+
29+
use crate::app::skin::skin_router;
30+
async fn health_check() -> status::NoContent {
31+
status::NoContent
32+
}
33+
34+
pub async fn app() {
35+
Ohkami::new((
36+
"/skin".By(skin_router()),
37+
"/uvs".GET(health_check),
38+
"/health".GET(health_check),
39+
))
40+
.howl("localhost:3000")
41+
.await
42+
}

src/app/logger.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
use ohkami::{FangAction, Request, Response};
2+
3+
#[derive(Clone)]
4+
pub struct LogRequest;
5+
impl FangAction for LogRequest {
6+
async fn fore<'a>(
7+
&'a self,
8+
req: &'a mut Request,
9+
) -> Result<(), Response> {
10+
tracing::debug!("\nGot request: {req:#?}");
11+
Ok(())
12+
}
13+
}

src/app/png.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
use std::borrow::Cow;
2+
3+
use ohkami::{claw::content::IntoContent, openapi};
4+
5+
/// Represents a PNG image content.
6+
pub struct Png(pub Vec<u8>);
7+
8+
impl IntoContent for Png {
9+
const CONTENT_TYPE: &'static str = "image/png";
10+
11+
fn into_content(self) -> Result<std::borrow::Cow<'static, [u8]>, impl std::fmt::Display> {
12+
Result::<_, std::convert::Infallible>::Ok(Cow::Owned(self.0))
13+
}
14+
15+
fn openapi_responsebody() -> impl Into<openapi::schema::SchemaRef> {
16+
openapi::schema::SchemaRef::Reference("png")
17+
}
18+
}

src/app/skin.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use std::path::PathBuf;
2+
3+
use image::ImageFormat;
4+
use ohkami::{
5+
IntoResponse, Ohkami, Query, Route,
6+
claw::status::Created,
7+
openapi::{self, Schema},
8+
serde::Deserialize,
9+
};
10+
use tee_morphosis::tee::{Tee, hsl::ddnet_color_to_hsl, parts::TeePart, skin::TEE_SKIN_LAYOUT};
11+
use tokio::{fs, task::spawn_blocking};
12+
use tracing::info;
13+
14+
use crate::{
15+
app::{logger::LogRequest, png::Png},
16+
error::Error,
17+
};
18+
19+
pub fn skin_router() -> Ohkami {
20+
Ohkami::new((LogRequest, openapi::Tag("skin"), "/".GET(skin_handler)))
21+
}
22+
23+
#[derive(Debug, Deserialize, Schema)]
24+
pub struct SkinQuery<'req> {
25+
pub body: Option<u32>,
26+
pub feet: Option<u32>,
27+
pub name: Option<&'req str>,
28+
}
29+
30+
async fn skin_handler(Query(query): Query<SkinQuery<'_>>) -> Result<impl IntoResponse, Error> {
31+
let path = PathBuf::from("./.ref")
32+
.join(query.name.ok_or(Error::QueryNameNotFound)?)
33+
.with_extension("png");
34+
info!(path=%path.display());
35+
36+
let uv = fs::read(path.clone()).await.map_err(Error::Io)?;
37+
38+
let tee = spawn_blocking(move || {
39+
Tee::new(uv.into(), ImageFormat::Png).map(|mut tee| {
40+
if let Some(value) = query.body {
41+
tee.apply_hsv_to_parts(
42+
ddnet_color_to_hsl(value),
43+
&[TeePart::Body, TeePart::BodyShadow],
44+
);
45+
}
46+
if let Some(value) = query.feet {
47+
tee.apply_hsv_to_parts(
48+
ddnet_color_to_hsl(value),
49+
&[TeePart::Feet, TeePart::FeetShadow],
50+
);
51+
}
52+
tee.compose_default(TEE_SKIN_LAYOUT)
53+
})
54+
})
55+
.await???;
56+
Ok(Created(Png(tee.into())))
57+
}

src/error.rs

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
use std::path::PathBuf;
2+
3+
use ohkami::{IntoResponse, Response};
4+
use reqwest::header::ToStrError;
5+
use thiserror::Error;
6+
use tokio::{io, task::JoinError};
7+
use tracing::error;
8+
9+
#[derive(Debug, Error)]
10+
pub enum Error {
11+
#[error("I/O error")]
12+
Io(#[from] io::Error),
13+
#[error("Fail to join task")]
14+
TaskJoin(#[from] JoinError),
15+
#[error("Tee error")]
16+
Tee(#[from] tee_morphosis::error::TeeError),
17+
#[error("Query expected name, but got none")]
18+
QueryNameNotFound,
19+
#[error("Reqwest error")]
20+
Reqwest(#[from] reqwest::Error),
21+
#[error("Reqwest header convertation error")]
22+
ToStrError(#[from] ToStrError),
23+
#[error("Fail to save: {name}, path {path:#?}, {error}")]
24+
SaveFailed {
25+
path: PathBuf,
26+
name: String,
27+
error: String,
28+
},
29+
#[error("Fail to download: {name}, {error}")]
30+
DownloadFailed { name: String, error: String },
31+
}
32+
33+
impl IntoResponse for Error {
34+
fn into_response(self) -> Response {
35+
match self {
36+
Error::Io(e) => {
37+
error!(error=%e,"I/O error");
38+
Response::InternalServerError().with_text("Skin not found")
39+
}
40+
Error::Tee(e) => {
41+
error!(error=%e,"Tee error");
42+
Response::InternalServerError().with_text("Fail to render uv")
43+
}
44+
Error::QueryNameNotFound => {
45+
error!("Query expected name, but got none");
46+
Response::BadRequest().with_text("Query expected name, but got none")
47+
}
48+
Error::Reqwest(e) => {
49+
error!(error=%e,"Reqwest error");
50+
Response::InternalServerError()
51+
}
52+
Error::ToStrError(e) => {
53+
error!(error=%e,"Reqwest ToStr error");
54+
Response::InternalServerError()
55+
}
56+
Error::SaveFailed {
57+
path,
58+
name,
59+
error,
60+
} => todo!(),
61+
Error::DownloadFailed {
62+
name,
63+
error,
64+
} => todo!(),
65+
Error::TaskJoin(join_error) => todo!(),
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)