Compare commits
4 Commits
affe9bcd14
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 824cd2ad53 | |||
| 40d57d7c77 | |||
| da9cd3e03b | |||
| eb95eed752 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -24,3 +24,4 @@ node_modules/
|
|||||||
|
|
||||||
docs/
|
docs/
|
||||||
|
|
||||||
|
logs/
|
||||||
16
backend/.sqlx/query-ac8099bc9fa42a4152f2230903f9e9b51edfc01f03fd18cc2fe0ab3a956f3175.json
generated
Normal file
16
backend/.sqlx/query-ac8099bc9fa42a4152f2230903f9e9b51edfc01f03fd18cc2fe0ab3a956f3175.json
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"db_name": "PostgreSQL",
|
||||||
|
"query": "\n UPDATE file_records\n SET thumbnail_storage_path = $1\n WHERE id = $2\n AND user_id = $3\n ",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Left": [
|
||||||
|
"Text",
|
||||||
|
"Int8",
|
||||||
|
"Int8"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "ac8099bc9fa42a4152f2230903f9e9b51edfc01f03fd18cc2fe0ab3a956f3175"
|
||||||
|
}
|
||||||
29
backend/Cargo.lock
generated
29
backend/Cargo.lock
generated
@@ -307,6 +307,7 @@ dependencies = [
|
|||||||
"tower-http",
|
"tower-http",
|
||||||
"tower_governor",
|
"tower_governor",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"utoipa",
|
"utoipa",
|
||||||
"utoipa-swagger-ui",
|
"utoipa-swagger-ui",
|
||||||
@@ -577,6 +578,15 @@ dependencies = [
|
|||||||
"cfg-if",
|
"cfg-if",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "crossbeam-channel"
|
||||||
|
version = "0.5.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-utils",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "crossbeam-deque"
|
name = "crossbeam-deque"
|
||||||
version = "0.8.6"
|
version = "0.8.6"
|
||||||
@@ -3588,6 +3598,12 @@ version = "2.6.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "symlink"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.117"
|
version = "2.0.117"
|
||||||
@@ -3900,6 +3916,19 @@ dependencies = [
|
|||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-appender"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"symlink",
|
||||||
|
"thiserror 2.0.18",
|
||||||
|
"time",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.31"
|
version = "0.1.31"
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ tower_governor = { version = "0.8.0", default-features = false, features = ["axu
|
|||||||
utoipa = { version = "5.5.0", features = ["axum_extras", "chrono"] }
|
utoipa = { version = "5.5.0", features = ["axum_extras", "chrono"] }
|
||||||
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
utoipa-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||||
image = "0.25.10"
|
image = "0.25.10"
|
||||||
|
tracing-appender = "0.2.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
axum-test = "20.0.0"
|
axum-test = "20.0.0"
|
||||||
|
|||||||
@@ -44,12 +44,20 @@ const BODY_LIMIT: usize = 1000 * 1000 * 1000 * 5;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
|
let file_appender = tracing_appender::rolling::daily("logs", "loft.log");
|
||||||
|
let (non_blocking, _guard) = tracing_appender::non_blocking(file_appender);
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(
|
.with(
|
||||||
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||||
format!("{}=info,tower_http=debug", env!("CARGO_CRATE_NAME")).into()
|
format!("{}=info,tower_http=debug", env!("CARGO_CRATE_NAME")).into()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.with(
|
||||||
|
tracing_subscriber::fmt::layer()
|
||||||
|
.with_ansi(false)
|
||||||
|
.with_writer(non_blocking),
|
||||||
|
)
|
||||||
.with(tracing_subscriber::fmt::layer())
|
.with(tracing_subscriber::fmt::layer())
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
@@ -70,31 +78,12 @@ async fn main() -> Result<()> {
|
|||||||
.layer(DefaultBodyLimit::max(BODY_LIMIT));
|
.layer(DefaultBodyLimit::max(BODY_LIMIT));
|
||||||
|
|
||||||
let user_repository = UserRepository::new(pool);
|
let user_repository = UserRepository::new(pool);
|
||||||
let mut routes_auth = routes_auth(user_repository.clone());
|
let routes_auth = routes_auth(user_repository.clone());
|
||||||
|
let routes_auth = if std::env::var("ENVIRONMENT").unwrap_or_default() == "test" {
|
||||||
if std::env::var("ENVIRONMENT").unwrap_or_default() != "test" {
|
routes_auth
|
||||||
let governor_conf_auth = GovernorConfigBuilder::default()
|
} else {
|
||||||
.per_second(4)
|
apply_governor(routes_auth)
|
||||||
.burst_size(2)
|
};
|
||||||
.key_extractor(SmartIpKeyExtractor)
|
|
||||||
.finish()
|
|
||||||
.expect("failed to initialize rate limiter configurations");
|
|
||||||
let governor_auth_limiter = governor_conf_auth.limiter().clone();
|
|
||||||
let interval = Duration::from_secs(60);
|
|
||||||
|
|
||||||
std::thread::spawn(move || {
|
|
||||||
loop {
|
|
||||||
std::thread::sleep(interval);
|
|
||||||
let len = governor_auth_limiter.len();
|
|
||||||
if len > 0 {
|
|
||||||
info!("rate limiting auth storage size: {len}");
|
|
||||||
}
|
|
||||||
governor_auth_limiter.retain_recent();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
routes_auth = routes_auth.layer(GovernorLayer::new(governor_conf_auth));
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut openapi = AuthApi::openapi();
|
let mut openapi = AuthApi::openapi();
|
||||||
openapi.merge(FileApi::openapi());
|
openapi.merge(FileApi::openapi());
|
||||||
@@ -133,3 +122,27 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_governor(routes_auth: Router) -> Router {
|
||||||
|
let governor_conf_auth = GovernorConfigBuilder::default()
|
||||||
|
.per_second(4)
|
||||||
|
.burst_size(2)
|
||||||
|
.key_extractor(SmartIpKeyExtractor)
|
||||||
|
.finish()
|
||||||
|
.expect("failed to initialize rate limiter configurations");
|
||||||
|
let governor_auth_limiter = governor_conf_auth.limiter().clone();
|
||||||
|
let interval = Duration::from_secs(60);
|
||||||
|
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
loop {
|
||||||
|
std::thread::sleep(interval);
|
||||||
|
let len = governor_auth_limiter.len();
|
||||||
|
if len > 0 {
|
||||||
|
info!("rate limiting auth storage size: {len}");
|
||||||
|
}
|
||||||
|
governor_auth_limiter.retain_recent();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
routes_auth.layer(GovernorLayer::new(governor_conf_auth))
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use axum::{body::Bytes, extract::multipart::MultipartError};
|
use axum::{body::Bytes, extract::multipart::MultipartError};
|
||||||
use futures_util::{Stream, StreamExt};
|
use futures_util::{Stream, StreamExt};
|
||||||
use opendal::{Operator, layers::LoggingLayer, services};
|
use opendal::{FuturesBytesStream, Operator, layers::LoggingLayer, services};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use sqlx::{PgPool, prelude::FromRow};
|
use sqlx::{PgPool, prelude::FromRow};
|
||||||
|
|
||||||
@@ -80,22 +80,14 @@ impl FileRepository {
|
|||||||
Ok(record)
|
Ok(record)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn download_file(
|
pub async fn download_file(&self, file_id: i64, user_id: i64) -> Result<FuturesBytesStream> {
|
||||||
&self,
|
|
||||||
file_id: i64,
|
|
||||||
user_id: i64,
|
|
||||||
) -> Result<impl Stream<Item = std::io::Result<Bytes>> + use<>> {
|
|
||||||
let record = self.get_file(file_id, user_id).await?;
|
let record = self.get_file(file_id, user_id).await?;
|
||||||
let reader = self.op.reader(&record.storage_key).await?;
|
let reader = self.op.reader(&record.storage_key).await?;
|
||||||
let stream = reader.into_bytes_stream(0..).await?;
|
let stream = reader.into_bytes_stream(0..).await?;
|
||||||
Ok(stream)
|
Ok(stream)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn thumbnail(
|
pub async fn thumbnail(&self, file_id: i64, user_id: i64) -> Result<FuturesBytesStream> {
|
||||||
&self,
|
|
||||||
file_id: i64,
|
|
||||||
user_id: i64,
|
|
||||||
) -> Result<impl Stream<Item = std::io::Result<Bytes>> + use<>> {
|
|
||||||
let record = self.get_file(file_id, user_id).await?;
|
let record = self.get_file(file_id, user_id).await?;
|
||||||
let path = format!("thumbnail/{}", &record.storage_key);
|
let path = format!("thumbnail/{}", &record.storage_key);
|
||||||
let reader = self.op.reader(&path).await?;
|
let reader = self.op.reader(&path).await?;
|
||||||
@@ -109,7 +101,7 @@ impl FileRepository {
|
|||||||
user_id: i64,
|
user_id: i64,
|
||||||
from: u64,
|
from: u64,
|
||||||
to: u64,
|
to: u64,
|
||||||
) -> Result<impl Stream<Item = std::io::Result<Bytes>> + use<>> {
|
) -> Result<FuturesBytesStream> {
|
||||||
let record = self.get_file(file_id, user_id).await?;
|
let record = self.get_file(file_id, user_id).await?;
|
||||||
let reader = self.op.reader(&record.storage_key).await?;
|
let reader = self.op.reader(&record.storage_key).await?;
|
||||||
let stream = reader.into_bytes_stream(from..to).await?;
|
let stream = reader.into_bytes_stream(from..to).await?;
|
||||||
|
|||||||
Reference in New Issue
Block a user