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/
|
||||
|
||||
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_governor",
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
"tracing-subscriber",
|
||||
"utoipa",
|
||||
"utoipa-swagger-ui",
|
||||
@@ -577,6 +578,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
@@ -3588,6 +3598,12 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "symlink"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.117"
|
||||
@@ -3900,6 +3916,19 @@ dependencies = [
|
||||
"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]]
|
||||
name = "tracing-attributes"
|
||||
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-swagger-ui = { version = "9.0.2", features = ["axum"] }
|
||||
image = "0.25.10"
|
||||
tracing-appender = "0.2.5"
|
||||
|
||||
[dev-dependencies]
|
||||
axum-test = "20.0.0"
|
||||
|
||||
@@ -44,12 +44,20 @@ const BODY_LIMIT: usize = 1000 * 1000 * 1000 * 5;
|
||||
|
||||
#[tokio::main]
|
||||
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()
|
||||
.with(
|
||||
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||
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())
|
||||
.init();
|
||||
|
||||
@@ -70,31 +78,12 @@ async fn main() -> Result<()> {
|
||||
.layer(DefaultBodyLimit::max(BODY_LIMIT));
|
||||
|
||||
let user_repository = UserRepository::new(pool);
|
||||
let mut routes_auth = routes_auth(user_repository.clone());
|
||||
|
||||
if std::env::var("ENVIRONMENT").unwrap_or_default() != "test" {
|
||||
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 = routes_auth.layer(GovernorLayer::new(governor_conf_auth));
|
||||
}
|
||||
let routes_auth = routes_auth(user_repository.clone());
|
||||
let routes_auth = if std::env::var("ENVIRONMENT").unwrap_or_default() == "test" {
|
||||
routes_auth
|
||||
} else {
|
||||
apply_governor(routes_auth)
|
||||
};
|
||||
|
||||
let mut openapi = AuthApi::openapi();
|
||||
openapi.merge(FileApi::openapi());
|
||||
@@ -133,3 +122,27 @@ async fn main() -> Result<()> {
|
||||
|
||||
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 futures_util::{Stream, StreamExt};
|
||||
use opendal::{Operator, layers::LoggingLayer, services};
|
||||
use opendal::{FuturesBytesStream, Operator, layers::LoggingLayer, services};
|
||||
use serde::Serialize;
|
||||
use sqlx::{PgPool, prelude::FromRow};
|
||||
|
||||
@@ -80,22 +80,14 @@ impl FileRepository {
|
||||
Ok(record)
|
||||
}
|
||||
|
||||
pub async fn download_file(
|
||||
&self,
|
||||
file_id: i64,
|
||||
user_id: i64,
|
||||
) -> Result<impl Stream<Item = std::io::Result<Bytes>> + use<>> {
|
||||
pub async fn download_file(&self, file_id: i64, user_id: i64) -> Result<FuturesBytesStream> {
|
||||
let record = self.get_file(file_id, user_id).await?;
|
||||
let reader = self.op.reader(&record.storage_key).await?;
|
||||
let stream = reader.into_bytes_stream(0..).await?;
|
||||
Ok(stream)
|
||||
}
|
||||
|
||||
pub async fn thumbnail(
|
||||
&self,
|
||||
file_id: i64,
|
||||
user_id: i64,
|
||||
) -> Result<impl Stream<Item = std::io::Result<Bytes>> + use<>> {
|
||||
pub async fn thumbnail(&self, file_id: i64, user_id: i64) -> Result<FuturesBytesStream> {
|
||||
let record = self.get_file(file_id, user_id).await?;
|
||||
let path = format!("thumbnail/{}", &record.storage_key);
|
||||
let reader = self.op.reader(&path).await?;
|
||||
@@ -109,7 +101,7 @@ impl FileRepository {
|
||||
user_id: i64,
|
||||
from: 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 reader = self.op.reader(&record.storage_key).await?;
|
||||
let stream = reader.into_bytes_stream(from..to).await?;
|
||||
|
||||
Reference in New Issue
Block a user