Inherit dependencies from workspace manifest, and optimize some out (#3655)
* chore: inherit dependencies from workspace, optimize some deps out * Update bitflags from 2.9.0 to 2.9.1 * Fix temp directory leak in check_java_at_filepath * Fix build * Fix lint * chore(app-lib): refactor overkill `futures` executor usage to Tokio MPSC * chore: fix Clippy lint * tweak: optimize out dependency on OpenSSL source build Contrary to what I expected before, this was caused due to the Tauri updater plugin using a different TLS stack than everything else. * chore(labrinth): drop now unused dependency * Update zip because 2.6.1 got yanked * Downgrade weezl to 0.1.8 * Mention that p256 is also a blocker for rand 0.9 * chore: sidestep GitHub review requirements * chore: sidestep GitHub review requirements (2) * chore: sidestep GitHub review requirements (3) --------- Co-authored-by: Josiah Glosson <soujournme@gmail.com>
This commit is contained in:
parent
37cc81a36d
commit
f19643095e
798
Cargo.lock
generated
798
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
193
Cargo.toml
193
Cargo.toml
@ -1,25 +1,182 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = '2'
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
'./packages/app-lib',
|
"apps/app",
|
||||||
'./apps/app-playground',
|
"apps/app-playground",
|
||||||
'./apps/app',
|
"apps/daedalus_client",
|
||||||
'./apps/labrinth',
|
"apps/labrinth",
|
||||||
'./apps/daedalus_client',
|
"packages/app-lib",
|
||||||
'./packages/daedalus',
|
"packages/ariadne",
|
||||||
'./packages/ariadne',
|
"packages/daedalus",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Optimize for speed and reduce size on release builds
|
[workspace.dependencies]
|
||||||
[profile.release]
|
actix-cors = "0.7.1"
|
||||||
panic = "abort" # Strip expensive panic clean-up logic
|
actix-files = "0.6.6"
|
||||||
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
actix-http = "3.11.0"
|
||||||
lto = true # Enables link to optimizations
|
actix-multipart = "0.7.2"
|
||||||
opt-level = "s" # Optimize for binary size
|
actix-rt = "2.10.0"
|
||||||
strip = true # Remove debug symbols
|
actix-web = "4.11.0"
|
||||||
|
actix-web-prom = "0.10.0"
|
||||||
[profile.dev.package.sqlx-macros]
|
actix-ws = "0.3.0"
|
||||||
opt-level = 3
|
argon2 = { version = "0.5.3", features = ["std"] }
|
||||||
|
ariadne = { path = "packages/ariadne" }
|
||||||
|
async-compression = { version = "0.4.23", default-features = false }
|
||||||
|
async-recursion = "1.1.1"
|
||||||
|
async-stripe = { version = "0.41.0", default-features = false, features = [
|
||||||
|
"runtime-tokio-hyper-rustls",
|
||||||
|
] }
|
||||||
|
async-trait = "0.1.88"
|
||||||
|
async-tungstenite = { version = "0.29.1", default-features = false, features = [
|
||||||
|
"futures-03-sink",
|
||||||
|
] }
|
||||||
|
async-walkdir = "2.1.0"
|
||||||
|
async_zip = "0.0.17"
|
||||||
|
base64 = "0.22.1"
|
||||||
|
bitflags = "2.9.0"
|
||||||
|
bytes = "1.10.1"
|
||||||
|
censor = "0.3.0"
|
||||||
|
chrono = "0.4.41"
|
||||||
|
clap = "4.5.38"
|
||||||
|
clickhouse = "0.13.2"
|
||||||
|
color-thief = "0.2.2"
|
||||||
|
console-subscriber = "0.4.1"
|
||||||
|
daedalus = { path = "packages/daedalus" }
|
||||||
|
dashmap = "6.1.0"
|
||||||
|
deadpool-redis = "0.20.0"
|
||||||
|
dirs = "6.0.0"
|
||||||
|
discord-rich-presence = "0.2.5"
|
||||||
|
dotenv-build = "0.1.1"
|
||||||
|
dotenvy = "0.15.7"
|
||||||
|
dunce = "1.0.5"
|
||||||
|
either = "1.15.0"
|
||||||
|
enumset = "1.1.6"
|
||||||
|
flate2 = "1.1.1"
|
||||||
|
fs4 = { version = "0.13.1", default-features = false }
|
||||||
|
futures = { version = "0.3.31", default-features = false }
|
||||||
|
futures-util = "0.3.31"
|
||||||
|
hex = "0.4.3"
|
||||||
|
hickory-resolver = "0.25.2"
|
||||||
|
hmac = "0.12.1"
|
||||||
|
hyper-tls = "0.6.0"
|
||||||
|
hyper-util = "0.1.11"
|
||||||
|
iana-time-zone = "0.1.63"
|
||||||
|
image = { version = "0.25.6", default-features = false, features = ["rayon"] }
|
||||||
|
indexmap = "2.9.0"
|
||||||
|
indicatif = "0.17.11"
|
||||||
|
itertools = "0.14.0"
|
||||||
|
jemalloc_pprof = "0.7.0"
|
||||||
|
json-patch = { version = "4.0.0", default-features = false }
|
||||||
|
lettre = { version = "0.11.16", default-features = false, features = [
|
||||||
|
"builder",
|
||||||
|
"hostname",
|
||||||
|
"pool",
|
||||||
|
"ring",
|
||||||
|
"rustls",
|
||||||
|
"rustls-native-certs",
|
||||||
|
"smtp-transport",
|
||||||
|
] }
|
||||||
|
maxminddb = "0.26.0"
|
||||||
|
meilisearch-sdk = { version = "0.28.0", default-features = false }
|
||||||
|
murmur2 = "0.1.0"
|
||||||
|
native-dialog = "0.9.0"
|
||||||
|
notify = { version = "8.0.0", default-features = false }
|
||||||
|
notify-debouncer-mini = { version = "0.6.0", default-features = false }
|
||||||
|
p256 = "0.13.2"
|
||||||
|
paste = "1.0.15"
|
||||||
|
prometheus = "0.14.0"
|
||||||
|
quartz_nbt = "0.2.9"
|
||||||
|
quick-xml = "0.37.5"
|
||||||
|
rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9
|
||||||
|
rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9
|
||||||
|
redis = "=0.29.5" # Locked on 0.29 until deadpool-redis updates to 0.30
|
||||||
|
regex = "1.11.1"
|
||||||
|
reqwest = { version = "0.12.15", default-features = false }
|
||||||
|
rust-s3 = { version = "0.35.1", default-features = false, features = [
|
||||||
|
"fail-on-err",
|
||||||
|
"tags",
|
||||||
|
"tokio-rustls-tls",
|
||||||
|
] }
|
||||||
|
rust_decimal = { version = "1.37.1", features = [
|
||||||
|
"serde-with-float",
|
||||||
|
"serde-with-str",
|
||||||
|
] }
|
||||||
|
rust_iso3166 = "0.1.14"
|
||||||
|
rusty-money = "0.4.1"
|
||||||
|
sentry = { version = "0.38.1", default-features = false, features = [
|
||||||
|
"backtrace",
|
||||||
|
"contexts",
|
||||||
|
"debug-images",
|
||||||
|
"panic",
|
||||||
|
"reqwest",
|
||||||
|
"rustls",
|
||||||
|
] }
|
||||||
|
sentry-actix = "0.38.1"
|
||||||
|
serde = "1.0.219"
|
||||||
|
serde-xml-rs = "0.8.0" # Also an XML (de)serializer, consider dropping yaserde in favor of this
|
||||||
|
serde_bytes = "0.11.17"
|
||||||
|
serde_cbor = "0.11.2"
|
||||||
|
serde_ini = "0.2.0"
|
||||||
|
serde_json = "1.0.140"
|
||||||
|
serde_with = "3.12.0"
|
||||||
|
sha1 = "0.10.6"
|
||||||
|
sha1_smol = { version = "1.0.1", features = ["std"] }
|
||||||
|
sha2 = "0.10.9"
|
||||||
|
spdx = "0.10.8"
|
||||||
|
sqlx = { version = "0.8.5", default-features = false }
|
||||||
|
sysinfo = { version = "0.35.1", default-features = false }
|
||||||
|
tar = "0.4.44"
|
||||||
|
tauri = "2.5.1"
|
||||||
|
tauri-build = "2.2.0"
|
||||||
|
tauri-plugin-deep-link = "2.2.1"
|
||||||
|
tauri-plugin-dialog = "2.2.1"
|
||||||
|
tauri-plugin-opener = "2.2.6"
|
||||||
|
tauri-plugin-os = "2.2.1"
|
||||||
|
tauri-plugin-single-instance = "2.2.3"
|
||||||
|
tauri-plugin-updater = { version = "2.7.1", default-features = false, features = [
|
||||||
|
"rustls-tls",
|
||||||
|
] }
|
||||||
|
tauri-plugin-window-state = "2.2.2"
|
||||||
|
tempfile = "3.20.0"
|
||||||
|
theseus = { path = "packages/app-lib" }
|
||||||
|
thiserror = "2.0.12"
|
||||||
|
tikv-jemalloc-ctl = "0.6.0"
|
||||||
|
tikv-jemallocator = "0.6.0"
|
||||||
|
tokio = "1.45.0"
|
||||||
|
tokio-stream = "0.1.17"
|
||||||
|
tokio-util = "0.7.15"
|
||||||
|
totp-rs = "5.7.0"
|
||||||
|
tracing = "0.1.41"
|
||||||
|
tracing-actix-web = "0.7.18"
|
||||||
|
tracing-error = "0.2.1"
|
||||||
|
tracing-subscriber = "0.3.19"
|
||||||
|
url = "2.5.4"
|
||||||
|
urlencoding = "2.1.3"
|
||||||
|
uuid = "1.16.0"
|
||||||
|
validator = "0.20.0"
|
||||||
|
webp = { version = "0.3.0", default-features = false }
|
||||||
|
whoami = "1.6.0"
|
||||||
|
winreg = "0.55.0"
|
||||||
|
woothee = "0.13.0"
|
||||||
|
yaserde = "0.12.0"
|
||||||
|
zip = { version = "3.0.0", default-features = false, features = [
|
||||||
|
"bzip2",
|
||||||
|
"deflate",
|
||||||
|
"deflate64",
|
||||||
|
"zstd",
|
||||||
|
] }
|
||||||
|
zxcvbn = "3.1.0"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
wry = { git = "https://github.com/modrinth/wry", rev = "cafdaa9" }
|
wry = { git = "https://github.com/modrinth/wry", rev = "cafdaa9" }
|
||||||
|
|
||||||
|
# Optimize for speed and reduce size on release builds
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s" # Optimize for binary size
|
||||||
|
strip = true # Remove debug symbols
|
||||||
|
lto = true # Enables link to optimizations
|
||||||
|
panic = "abort" # Strip expensive panic clean-up logic
|
||||||
|
codegen-units = 1 # Compile crates one after another so the compiler can optimize better
|
||||||
|
|
||||||
|
[profile.dev.package.sqlx-macros]
|
||||||
|
opt-level = 3
|
||||||
|
@ -6,9 +6,6 @@ edition = "2024"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
theseus = { path = "../../packages/app-lib", features = ["cli"] }
|
theseus = { workspace = true, features = ["cli"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
|
||||||
webbrowser = "1.0.4"
|
enumset.workspace = true
|
||||||
enumset = "1.1"
|
|
||||||
|
|
||||||
tracing = "0.1.37"
|
|
||||||
|
@ -15,8 +15,7 @@ pub async fn authenticate_run() -> theseus::Result<Credentials> {
|
|||||||
println!("A browser window will now open, follow the login flow there.");
|
println!("A browser window will now open, follow the login flow there.");
|
||||||
let login = minecraft_auth::begin_login().await?;
|
let login = minecraft_auth::begin_login().await?;
|
||||||
|
|
||||||
println!("URL {}", login.redirect_uri.as_str());
|
println!("Open URL {} in a browser", login.redirect_uri.as_str());
|
||||||
webbrowser::open(login.redirect_uri.as_str())?;
|
|
||||||
|
|
||||||
println!("Please enter URL code: ");
|
println!("Please enter URL code: ");
|
||||||
let mut input = String::new();
|
let mut input = String::new();
|
||||||
|
@ -8,47 +8,45 @@ edition = "2024"
|
|||||||
build = "build.rs"
|
build = "build.rs"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
tauri-build = { version = "2.2.0", features = ["codegen"] }
|
tauri-build = { workspace = true, features = ["codegen"] }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
theseus = { path = "../../packages/app-lib", features = ["tauri"] }
|
theseus = { workspace = true, features = ["tauri"] }
|
||||||
|
|
||||||
serde_json = "1.0"
|
serde_json.workspace = true
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_with = "3.0.0"
|
serde_with.workspace = true
|
||||||
|
|
||||||
tauri = { version = "2.5.1", features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
|
tauri = { workspace = true, features = ["devtools", "macos-private-api", "protocol-asset", "unstable"] }
|
||||||
tauri-plugin-window-state = "2.2.0"
|
tauri-plugin-window-state.workspace = true
|
||||||
tauri-plugin-deep-link = "2.2.0"
|
tauri-plugin-deep-link.workspace = true
|
||||||
tauri-plugin-os = "2.2.0"
|
tauri-plugin-os.workspace = true
|
||||||
tauri-plugin-opener = "2.2.6"
|
tauri-plugin-opener.workspace = true
|
||||||
tauri-plugin-dialog = "2.2.0"
|
tauri-plugin-dialog.workspace = true
|
||||||
tauri-plugin-updater = { version = "2.3.0" }
|
tauri-plugin-updater.workspace = true
|
||||||
tauri-plugin-single-instance = { version = "2.2.0" }
|
tauri-plugin-single-instance.workspace = true
|
||||||
|
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { workspace = true, features = ["time"] }
|
||||||
thiserror = "2.0.12"
|
thiserror.workspace = true
|
||||||
daedalus = { path = "../../packages/daedalus" }
|
daedalus.workspace = true
|
||||||
chrono = "0.4.26"
|
chrono.workspace = true
|
||||||
either = "1.15"
|
either.workspace = true
|
||||||
|
|
||||||
url = "2.2"
|
url.workspace = true
|
||||||
urlencoding = "2.1"
|
urlencoding.workspace = true
|
||||||
uuid = { version = "1.1", features = ["serde", "v4"] }
|
uuid = { workspace = true, features = ["serde", "v4"] }
|
||||||
|
|
||||||
tracing = "0.1.37"
|
tracing.workspace = true
|
||||||
tracing-error = "0.2.0"
|
tracing-error.workspace = true
|
||||||
|
|
||||||
dashmap = "6.1.0"
|
dashmap.workspace = true
|
||||||
paste = "1.0.15"
|
paste.workspace = true
|
||||||
enumset = { version = "1.1", features = ["serde"] }
|
enumset = { workspace = true, features = ["serde"] }
|
||||||
|
|
||||||
opener = { version = "0.7.2", features = ["reveal", "dbus-vendored"] }
|
native-dialog.workspace = true
|
||||||
|
|
||||||
native-dialog = "0.9.0"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
tauri-plugin-updater = { version = "2.3.0", optional = true, features = ["native-tls-vendored", "zip"], default-features = false }
|
tauri-plugin-updater = { workspace = true, optional = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
# by default Tauri runs in production mode
|
# by default Tauri runs in production mode
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use tauri::Runtime;
|
||||||
|
use tauri_plugin_opener::OpenerExt;
|
||||||
use theseus::{
|
use theseus::{
|
||||||
handler,
|
handler,
|
||||||
prelude::{CommandPayload, DirectoryInfo},
|
prelude::{CommandPayload, DirectoryInfo},
|
||||||
@ -74,29 +76,29 @@ pub async fn should_disable_mouseover() -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn highlight_in_folder(path: PathBuf) {
|
pub fn highlight_in_folder<R: Runtime>(
|
||||||
let res = opener::reveal(path);
|
app: tauri::AppHandle<R>,
|
||||||
|
path: PathBuf,
|
||||||
if let Err(e) = res {
|
) {
|
||||||
|
if let Err(e) = app.opener().reveal_item_in_dir(path) {
|
||||||
tracing::error!("Failed to highlight file in folder: {}", e);
|
tracing::error!("Failed to highlight file in folder: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn open_path(path: PathBuf) {
|
pub fn open_path<R: Runtime>(app: tauri::AppHandle<R>, path: PathBuf) {
|
||||||
let res = opener::open(path);
|
if let Err(e) = app.opener().open_path(path.to_string_lossy(), None::<&str>)
|
||||||
|
{
|
||||||
if let Err(e) = res {
|
|
||||||
tracing::error!("Failed to open path: {}", e);
|
tracing::error!("Failed to open path: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
pub fn show_launcher_logs_folder() {
|
pub fn show_launcher_logs_folder<R: Runtime>(app: tauri::AppHandle<R>) {
|
||||||
let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default();
|
let path = DirectoryInfo::launcher_logs_dir().unwrap_or_default();
|
||||||
// failure to get folder just opens filesystem
|
// failure to get folder just opens filesystem
|
||||||
// (ie: if in debug mode only and launcher_logs never created)
|
// (ie: if in debug mode only and launcher_logs never created)
|
||||||
open_path(path);
|
open_path(app, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get opening command
|
// Get opening command
|
||||||
|
@ -7,25 +7,24 @@ edition = "2024"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
daedalus = { path = "../../packages/daedalus" }
|
daedalus.workspace = true
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { workspace = true, features = ["sync", "macros", "rt-multi-thread"] }
|
||||||
futures = "0.3.25"
|
futures.workspace = true
|
||||||
dotenvy = "0.15.6"
|
dotenvy.workspace = true
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json.workspace = true
|
||||||
serde-xml-rs = "0.6.0"
|
serde-xml-rs.workspace = true
|
||||||
lazy_static = "1.4.0"
|
thiserror.workspace = true
|
||||||
thiserror = "2.0"
|
reqwest = { workspace = true, features = ["stream", "json", "rustls-tls-native-roots"] }
|
||||||
reqwest = { version = "0.12.15", default-features = false, features = ["stream", "json", "rustls-tls-native-roots"] }
|
async_zip = { workspace = true, features = ["chrono", "tokio-fs", "deflate", "bzip2", "zstd", "deflate64"] }
|
||||||
async_zip = { version = "0.0.17", features = ["full"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
bytes.workspace = true
|
||||||
bytes = "1.6.0"
|
rust-s3.workspace = true
|
||||||
rust-s3 = { version = "0.35.1", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] }
|
dashmap.workspace = true
|
||||||
dashmap = "6.1.0"
|
sha1_smol.workspace = true
|
||||||
sha1_smol = { version = "1.0.0", features = ["std"] }
|
indexmap = { workspace = true, features = ["serde"] }
|
||||||
indexmap = { version = "2.2.6", features = ["serde"] }
|
itertools.workspace = true
|
||||||
itertools = "0.14.0"
|
tracing-error.workspace = true
|
||||||
tracing-error = "0.2.0"
|
|
||||||
|
|
||||||
tracing = "0.1"
|
tracing.workspace = true
|
||||||
tracing-subscriber = { version = "0.3", features = ["env-filter"] }
|
tracing-subscriber = { workspace = true, features = ["env-filter"] }
|
||||||
|
@ -3,59 +3,57 @@ use bytes::Bytes;
|
|||||||
use s3::creds::Credentials;
|
use s3::creds::Credentials;
|
||||||
use s3::{Bucket, Region};
|
use s3::{Bucket, Region};
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, LazyLock};
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
static BUCKET: LazyLock<Bucket> = LazyLock::new(|| {
|
||||||
static ref BUCKET: Bucket = {
|
let region = dotenvy::var("S3_REGION").unwrap();
|
||||||
let region = dotenvy::var("S3_REGION").unwrap();
|
let b = Bucket::new(
|
||||||
let b = Bucket::new(
|
&dotenvy::var("S3_BUCKET_NAME").unwrap(),
|
||||||
&dotenvy::var("S3_BUCKET_NAME").unwrap(),
|
if &*region == "r2" {
|
||||||
if &*region == "r2" {
|
Region::R2 {
|
||||||
Region::R2 {
|
account_id: dotenvy::var("S3_URL").unwrap(),
|
||||||
account_id: dotenvy::var("S3_URL").unwrap(),
|
}
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Region::Custom {
|
|
||||||
region: region.clone(),
|
|
||||||
endpoint: dotenvy::var("S3_URL").unwrap(),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Credentials::new(
|
|
||||||
Some(&*dotenvy::var("S3_ACCESS_TOKEN").unwrap()),
|
|
||||||
Some(&*dotenvy::var("S3_SECRET").unwrap()),
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
).unwrap(),
|
|
||||||
).unwrap();
|
|
||||||
|
|
||||||
if region == "path-style" {
|
|
||||||
*b.with_path_style()
|
|
||||||
} else {
|
} else {
|
||||||
*b
|
Region::Custom {
|
||||||
}
|
region: region.clone(),
|
||||||
};
|
endpoint: dotenvy::var("S3_URL").unwrap(),
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
Credentials::new(
|
||||||
|
Some(&*dotenvy::var("S3_ACCESS_TOKEN").unwrap()),
|
||||||
|
Some(&*dotenvy::var("S3_SECRET").unwrap()),
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
if region == "path-style" {
|
||||||
pub static ref REQWEST_CLIENT: reqwest::Client = {
|
*b.with_path_style()
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
} else {
|
||||||
if let Ok(header) = reqwest::header::HeaderValue::from_str(&format!(
|
*b
|
||||||
"modrinth/daedalus/{} (support@modrinth.com)",
|
}
|
||||||
env!("CARGO_PKG_VERSION")
|
});
|
||||||
)) {
|
|
||||||
headers.insert(reqwest::header::USER_AGENT, header);
|
|
||||||
}
|
|
||||||
|
|
||||||
reqwest::Client::builder()
|
pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
|
||||||
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
.timeout(std::time::Duration::from_secs(15))
|
if let Ok(header) = reqwest::header::HeaderValue::from_str(&format!(
|
||||||
.default_headers(headers)
|
"modrinth/daedalus/{} (support@modrinth.com)",
|
||||||
.build()
|
env!("CARGO_PKG_VERSION")
|
||||||
.unwrap()
|
)) {
|
||||||
};
|
headers.insert(reqwest::header::USER_AGENT, header);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
reqwest::Client::builder()
|
||||||
|
.tcp_keepalive(Some(std::time::Duration::from_secs(10)))
|
||||||
|
.timeout(std::time::Duration::from_secs(15))
|
||||||
|
.default_headers(headers)
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
#[tracing::instrument(skip(bytes, semaphore))]
|
#[tracing::instrument(skip(bytes, semaphore))]
|
||||||
pub async fn upload_file_to_bucket(
|
pub async fn upload_file_to_bucket(
|
||||||
|
@ -11,74 +11,72 @@ name = "labrinth"
|
|||||||
path = "src/main.rs"
|
path = "src/main.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-web = "4.10.2"
|
actix-web.workspace = true
|
||||||
actix-rt = "2.9.0"
|
actix-rt.workspace = true
|
||||||
actix-multipart = "0.7.2"
|
actix-multipart.workspace = true
|
||||||
actix-cors = "0.7.1"
|
actix-cors.workspace = true
|
||||||
actix-ws = "0.3.0"
|
actix-ws.workspace = true
|
||||||
actix-files = "0.6.5"
|
actix-files.workspace = true
|
||||||
prometheus = "0.13.4" # Locked on 0.13 until actix updates to 0.14
|
prometheus.workspace = true
|
||||||
actix-web-prom = { version = "0.9.0", features = ["process"] }
|
actix-web-prom = { workspace = true, features = ["process"] }
|
||||||
|
|
||||||
tracing = "0.1.41"
|
tracing.workspace = true
|
||||||
tracing-actix-web = "0.7.18"
|
tracing-actix-web.workspace = true
|
||||||
console-subscriber = "0.4.1"
|
console-subscriber.workspace = true
|
||||||
|
|
||||||
tokio = { version = "1.35.1", features = ["sync", "rt-multi-thread"] }
|
tokio = { workspace = true, features = ["sync", "rt-multi-thread"] }
|
||||||
tokio-stream = "0.1.14"
|
tokio-stream.workspace = true
|
||||||
|
|
||||||
futures = "0.3.30"
|
futures.workspace = true
|
||||||
futures-util = "0.3.30"
|
futures-util.workspace = true
|
||||||
async-trait = "0.1.70"
|
async-trait.workspace = true
|
||||||
dashmap = "6.1.0"
|
dashmap.workspace = true
|
||||||
lazy_static = "1.4.0"
|
|
||||||
|
|
||||||
meilisearch-sdk = "0.28.0"
|
meilisearch-sdk = { workspace = true, features = ["reqwest"] }
|
||||||
rust-s3 = { version = "0.35.1", default-features = false, features = ["fail-on-err", "tags", "tokio-rustls-tls"] }
|
rust-s3.workspace = true
|
||||||
reqwest = { version = "0.12.15", features = ["json", "multipart"] }
|
reqwest = { workspace = true, features = ["http2", "rustls-tls-webpki-roots", "json", "multipart"] }
|
||||||
hyper = { version = "1.6", features = ["full"] }
|
hyper-tls.workspace = true
|
||||||
hyper-tls = "0.6.0"
|
hyper-util.workspace = true
|
||||||
hyper-util = "0.1.11"
|
|
||||||
|
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json.workspace = true
|
||||||
serde_with = "3.0.0"
|
serde_with.workspace = true
|
||||||
chrono = { version = "0.4.26", features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
yaserde = "0.12.0"
|
yaserde = { workspace = true, features = ["derive"] }
|
||||||
yaserde_derive = "0.12.0"
|
|
||||||
|
|
||||||
rand = "0.8.5" # Locked on 0.8 until argon2 updates to 0.9
|
rand.workspace = true
|
||||||
rand_chacha = "0.3.1" # Locked on 0.3 until we can update rand to 0.9
|
rand_chacha.workspace = true
|
||||||
bytes = "1.4.0"
|
bytes.workspace = true
|
||||||
base64 = "0.22.1"
|
base64.workspace = true
|
||||||
sha1 = { version = "0.10.6", features = ["std"] }
|
sha1.workspace = true
|
||||||
sha2 = "0.10.9"
|
sha2.workspace = true
|
||||||
hmac = "0.12.1"
|
hmac.workspace = true
|
||||||
argon2 = { version = "0.5.0", features = ["std"] }
|
argon2.workspace = true
|
||||||
murmur2 = "0.1.0"
|
murmur2.workspace = true
|
||||||
bitflags = "2.4.0"
|
bitflags.workspace = true
|
||||||
hex = "0.4.3"
|
hex.workspace = true
|
||||||
zxcvbn = "3.1.0"
|
zxcvbn.workspace = true
|
||||||
totp-rs = { version = "5.0.2", features = ["gen_secret"] }
|
totp-rs = { workspace = true, features = ["gen_secret"] }
|
||||||
|
|
||||||
url = "2.4.0"
|
url.workspace = true
|
||||||
urlencoding = "2.1.2"
|
urlencoding.workspace = true
|
||||||
|
|
||||||
zip = "2.6.1"
|
zip.workspace = true
|
||||||
|
|
||||||
itertools = "0.14.0"
|
itertools.workspace = true
|
||||||
|
|
||||||
validator = { version = "0.20.0", features = ["derive"] }
|
validator = { workspace = true, features = ["derive"] }
|
||||||
regex = "1.10.2"
|
regex.workspace = true
|
||||||
censor = "0.3.0"
|
censor.workspace = true
|
||||||
spdx = { version = "0.10.3", features = ["text"] }
|
spdx = { workspace = true, features = ["text"] }
|
||||||
|
|
||||||
dotenvy = "0.15.7"
|
dotenvy.workspace = true
|
||||||
thiserror = "2.0.12"
|
thiserror.workspace = true
|
||||||
either = "1.13"
|
either.workspace = true
|
||||||
|
|
||||||
sqlx = { version = "0.8.2", features = [
|
sqlx = { workspace = true, features = [
|
||||||
"runtime-tokio-rustls",
|
"runtime-tokio",
|
||||||
|
"tls-rustls-ring",
|
||||||
"postgres",
|
"postgres",
|
||||||
"chrono",
|
"chrono",
|
||||||
"macros",
|
"macros",
|
||||||
@ -86,51 +84,49 @@ sqlx = { version = "0.8.2", features = [
|
|||||||
"rust_decimal",
|
"rust_decimal",
|
||||||
"json",
|
"json",
|
||||||
] }
|
] }
|
||||||
rust_decimal = { version = "1.33.1", features = [
|
rust_decimal = { workspace = true, features = [
|
||||||
"serde-with-float",
|
"serde-with-float",
|
||||||
"serde-with-str",
|
"serde-with-str",
|
||||||
] }
|
] }
|
||||||
redis = { version = "0.29.5", features = ["tokio-comp", "ahash", "r2d2"] } # Locked on 0.29 until deadpool-redis updates to 0.30
|
redis = { workspace = true, features = ["tokio-comp", "ahash", "r2d2"] } # Locked on 0.29 until deadpool-redis updates to 0.30
|
||||||
deadpool-redis = "0.20.0"
|
deadpool-redis.workspace = true
|
||||||
clickhouse = { version = "0.13.2", features = ["uuid", "time"] }
|
clickhouse = { workspace = true, features = ["uuid", "time"] }
|
||||||
uuid = { version = "1.2.2", features = ["v4", "fast-rng", "serde"] }
|
uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] }
|
||||||
|
|
||||||
maxminddb = "0.26.0"
|
maxminddb.workspace = true
|
||||||
flate2 = "1.0.25"
|
flate2.workspace = true
|
||||||
tar = "0.4.38"
|
tar.workspace = true
|
||||||
|
|
||||||
sentry = { version = "0.37.0", default-features = false, features = ["backtrace", "contexts", "debug-images", "panic", "rustls", "reqwest"] }
|
sentry.workspace = true
|
||||||
sentry-actix = "0.37.0"
|
sentry-actix.workspace = true
|
||||||
|
|
||||||
image = "0.25.6"
|
image = { workspace = true, features = ["avif", "bmp", "dds", "exr", "ff", "gif", "hdr", "ico", "jpeg", "png", "pnm", "qoi", "tga", "tiff", "webp"] }
|
||||||
color-thief = "0.2.2"
|
color-thief.workspace = true
|
||||||
webp = "0.3.0"
|
webp.workspace = true
|
||||||
|
|
||||||
woothee = "0.13.0"
|
woothee.workspace = true
|
||||||
|
|
||||||
lettre = "0.11.3"
|
lettre.workspace = true
|
||||||
|
|
||||||
derive-new = "0.7.0"
|
rust_iso3166.workspace = true
|
||||||
rust_iso3166 = "0.1.11"
|
|
||||||
|
|
||||||
async-stripe = { version = "0.41.0", features = ["runtime-tokio-hyper-rustls"] }
|
async-stripe = { workspace = true, features = ["billing", "checkout", "connect", "webhook-events"] }
|
||||||
rusty-money = "0.4.1"
|
rusty-money.workspace = true
|
||||||
json-patch = "4.0.0"
|
json-patch.workspace = true
|
||||||
|
|
||||||
ariadne = { path = "../../packages/ariadne" }
|
ariadne.workspace = true
|
||||||
|
|
||||||
clap = { version = "4.5", features = ["derive"] }
|
clap = { workspace = true, features = ["derive"] }
|
||||||
iana-time-zone = "0.1.61"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "linux")'.dependencies]
|
[target.'cfg(target_os = "linux")'.dependencies]
|
||||||
tikv-jemallocator = { version = "0.6.0", features = ["profiling", "unprefixed_malloc_on_supported_platforms"] }
|
tikv-jemallocator = { workspace = true, features = ["profiling", "unprefixed_malloc_on_supported_platforms"] }
|
||||||
tikv-jemalloc-ctl = { version = "0.6.0", features = ["stats"] }
|
tikv-jemalloc-ctl = { workspace = true, features = ["stats"] }
|
||||||
jemalloc_pprof = { version = "0.7.0", features = ["flamegraph"] }
|
jemalloc_pprof = { workspace = true, features = ["flamegraph"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
actix-http = "3.4.0"
|
actix-http.workspace = true
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
dotenv-build = "0.1.1"
|
dotenv-build.workspace = true
|
||||||
chrono = "0.4.38"
|
chrono.workspace = true
|
||||||
iana-time-zone = "0.1.60"
|
iana-time-zone.workspace = true
|
||||||
|
@ -111,10 +111,10 @@ pub async fn init_oauth(
|
|||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
OAuthError::redirect(e, &oauth_info.state, &redirect_uri)
|
OAuthError::redirect(e, &oauth_info.state, &redirect_uri)
|
||||||
})?;
|
})?;
|
||||||
let redirect_uris = OAuthRedirectUris::new(
|
let redirect_uris = OAuthRedirectUris {
|
||||||
oauth_info.redirect_uri.clone(),
|
original: oauth_info.redirect_uri.clone(),
|
||||||
redirect_uri.clone(),
|
validated: redirect_uri.clone(),
|
||||||
);
|
};
|
||||||
match existing_authorization {
|
match existing_authorization {
|
||||||
Some(existing_authorization)
|
Some(existing_authorization)
|
||||||
if existing_authorization.scopes.contains(requested_scopes) =>
|
if existing_authorization.scopes.contains(requested_scopes) =>
|
||||||
|
@ -3,7 +3,7 @@ use crate::auth::oauth::OAuthErrorType;
|
|||||||
use crate::database::models::OAuthClientId;
|
use crate::database::models::OAuthClientId;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
#[derive(derive_new::new, Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct OAuthRedirectUris {
|
pub struct OAuthRedirectUris {
|
||||||
pub original: Option<String>,
|
pub original: Option<String>,
|
||||||
pub validated: ValidatedRedirectUri,
|
pub validated: ValidatedRedirectUri,
|
||||||
|
@ -132,6 +132,8 @@ pub async fn payouts(
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod version_updater {
|
mod version_updater {
|
||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use crate::database::models::legacy_loader_fields::MinecraftGameVersion;
|
use crate::database::models::legacy_loader_fields::MinecraftGameVersion;
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
@ -197,36 +199,45 @@ mod version_updater {
|
|||||||
("3D Shareware v1.34", "3D-Shareware-v1.34"),
|
("3D Shareware v1.34", "3D-Shareware-v1.34"),
|
||||||
];
|
];
|
||||||
|
|
||||||
lazy_static::lazy_static! {
|
/// Mojank for some reason has versions released at the same DateTime. This hardcodes them to fix this,
|
||||||
/// Mojank for some reason has versions released at the same DateTime. This hardcodes them to fix this,
|
/// as most of our ordering logic is with DateTime
|
||||||
/// as most of our ordering logic is with DateTime
|
static HALL_OF_SHAME_2: LazyLock<[(&'static str, DateTime<Utc>); 4]> =
|
||||||
static ref HALL_OF_SHAME_2: [(&'static str, DateTime<Utc>); 4] = [
|
LazyLock::new(|| {
|
||||||
(
|
[
|
||||||
"1.4.5",
|
(
|
||||||
chrono::DateTime::parse_from_rfc3339("2012-12-19T22:00:00+00:00")
|
"1.4.5",
|
||||||
|
chrono::DateTime::parse_from_rfc3339(
|
||||||
|
"2012-12-19T22:00:00+00:00",
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"1.4.6",
|
"1.4.6",
|
||||||
chrono::DateTime::parse_from_rfc3339("2012-12-19T22:00:01+00:00")
|
chrono::DateTime::parse_from_rfc3339(
|
||||||
|
"2012-12-19T22:00:01+00:00",
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"1.6.3",
|
"1.6.3",
|
||||||
chrono::DateTime::parse_from_rfc3339("2013-09-13T10:54:41+00:00")
|
chrono::DateTime::parse_from_rfc3339(
|
||||||
|
"2013-09-13T10:54:41+00:00",
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"13w37b",
|
"13w37b",
|
||||||
chrono::DateTime::parse_from_rfc3339("2013-09-13T10:54:42+00:00")
|
chrono::DateTime::parse_from_rfc3339(
|
||||||
|
"2013-09-13T10:54:42+00:00",
|
||||||
|
)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.into(),
|
.into(),
|
||||||
),
|
),
|
||||||
];
|
]
|
||||||
}
|
});
|
||||||
|
|
||||||
for version in input.versions.into_iter() {
|
for version in input.versions.into_iter() {
|
||||||
let mut name = version.id;
|
let mut name = version.id;
|
||||||
|
@ -117,11 +117,10 @@ impl GalleryItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(derive_new::new)]
|
|
||||||
pub struct ModCategory {
|
pub struct ModCategory {
|
||||||
project_id: ProjectId,
|
pub project_id: ProjectId,
|
||||||
category_id: CategoryId,
|
pub category_id: CategoryId,
|
||||||
is_additional: bool,
|
pub is_additional: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ModCategory {
|
impl ModCategory {
|
||||||
@ -245,12 +244,18 @@ impl ProjectBuilder {
|
|||||||
let project_id = self.project_id;
|
let project_id = self.project_id;
|
||||||
let mod_categories = categories
|
let mod_categories = categories
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|c| ModCategory::new(project_id, c, false))
|
.map(|category_id| ModCategory {
|
||||||
.chain(
|
project_id,
|
||||||
additional_categories
|
category_id,
|
||||||
.into_iter()
|
is_additional: false,
|
||||||
.map(|c| ModCategory::new(project_id, c, true)),
|
})
|
||||||
)
|
.chain(additional_categories.into_iter().map(|category_id| {
|
||||||
|
ModCategory {
|
||||||
|
project_id,
|
||||||
|
category_id,
|
||||||
|
is_additional: true,
|
||||||
|
}
|
||||||
|
}))
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
ModCategory::insert_many(mod_categories, &mut *transaction).await?;
|
ModCategory::insert_many(mod_categories, &mut *transaction).await?;
|
||||||
|
|
||||||
|
@ -229,7 +229,10 @@ impl VersionBuilder {
|
|||||||
|
|
||||||
let loader_versions = loaders
|
let loader_versions = loaders
|
||||||
.iter()
|
.iter()
|
||||||
.map(|l| LoaderVersion::new(*l, version_id))
|
.map(|&loader_id| LoaderVersion {
|
||||||
|
loader_id,
|
||||||
|
version_id,
|
||||||
|
})
|
||||||
.collect_vec();
|
.collect_vec();
|
||||||
LoaderVersion::insert_many(loader_versions, transaction).await?;
|
LoaderVersion::insert_many(loader_versions, transaction).await?;
|
||||||
|
|
||||||
@ -239,7 +242,7 @@ impl VersionBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(derive_new::new, Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct LoaderVersion {
|
pub struct LoaderVersion {
|
||||||
pub loader_id: LoaderId,
|
pub loader_id: LoaderId,
|
||||||
pub version_id: VersionId,
|
pub version_id: VersionId,
|
||||||
|
@ -152,7 +152,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
.expect("Failed to register redis metrics");
|
.expect("Failed to register redis metrics");
|
||||||
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
labrinth::routes::debug::jemalloc_mmeory_stats(&prometheus.registry)
|
labrinth::routes::debug::jemalloc_memory_stats(&prometheus.registry)
|
||||||
.expect("Failed to register jemalloc metrics");
|
.expect("Failed to register jemalloc metrics");
|
||||||
|
|
||||||
let labrinth_config = labrinth::app_setup(
|
let labrinth_config = labrinth::app_setup(
|
||||||
|
@ -50,7 +50,7 @@ fn require_profiling_activated(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn jemalloc_mmeory_stats(
|
pub fn jemalloc_memory_stats(
|
||||||
registry: &Registry,
|
registry: &Registry,
|
||||||
) -> Result<(), prometheus::Error> {
|
) -> Result<(), prometheus::Error> {
|
||||||
let allocated_mem = IntGauge::new(
|
let allocated_mem = IntGauge::new(
|
||||||
|
@ -13,7 +13,7 @@ use crate::util::captcha::check_hcaptcha;
|
|||||||
use crate::util::env::parse_strings_from_var;
|
use crate::util::env::parse_strings_from_var;
|
||||||
use crate::util::ext::get_image_ext;
|
use crate::util::ext::get_image_ext;
|
||||||
use crate::util::img::upload_image_optimized;
|
use crate::util::img::upload_image_optimized;
|
||||||
use crate::util::validate::{RE_URL_SAFE, validation_errors_to_string};
|
use crate::util::validate::validation_errors_to_string;
|
||||||
use actix_web::web::{Data, Query, ServiceConfig, scope};
|
use actix_web::web::{Data, Query, ServiceConfig, scope};
|
||||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, post, web};
|
||||||
use argon2::password_hash::SaltString;
|
use argon2::password_hash::SaltString;
|
||||||
@ -1318,7 +1318,7 @@ pub async fn sign_up_sendy(email: &str) -> Result<(), AuthenticationError> {
|
|||||||
|
|
||||||
#[derive(Deserialize, Validate)]
|
#[derive(Deserialize, Validate)]
|
||||||
pub struct NewAccount {
|
pub struct NewAccount {
|
||||||
#[validate(length(min = 1, max = 39), regex(path = *RE_URL_SAFE))]
|
#[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_URL_SAFE))]
|
||||||
pub username: String,
|
pub username: String,
|
||||||
#[validate(length(min = 8, max = 256))]
|
#[validate(length(min = 8, max = 256))]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
@ -12,7 +12,7 @@ use crate::{auth::get_user_from_headers, database};
|
|||||||
use actix_web::{HttpRequest, HttpResponse, get, route, web};
|
use actix_web::{HttpRequest, HttpResponse, get, route, web};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use yaserde_derive::YaSerialize;
|
use yaserde::YaSerialize;
|
||||||
|
|
||||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||||
cfg.service(maven_metadata);
|
cfg.service(maven_metadata);
|
||||||
|
@ -9,8 +9,6 @@ use crate::models::v2::user::LegacyUser;
|
|||||||
use crate::queue::session::AuthQueue;
|
use crate::queue::session::AuthQueue;
|
||||||
use crate::routes::{ApiError, v2_reroute, v3};
|
use crate::routes::{ApiError, v2_reroute, v3};
|
||||||
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
use actix_web::{HttpRequest, HttpResponse, delete, get, patch, web};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -135,20 +133,16 @@ pub async fn projects_list(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref RE_URL_SAFE: Regex = Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Validate)]
|
#[derive(Serialize, Deserialize, Validate)]
|
||||||
pub struct EditUser {
|
pub struct EditUser {
|
||||||
#[validate(length(min = 1, max = 39), regex(path = *RE_URL_SAFE))]
|
#[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_URL_SAFE))]
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
#[serde(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
skip_serializing_if = "Option::is_none",
|
skip_serializing_if = "Option::is_none",
|
||||||
with = "::serde_with::rust::double_option"
|
with = "::serde_with::rust::double_option"
|
||||||
)]
|
)]
|
||||||
#[validate(length(min = 1, max = 64), regex(path = *RE_URL_SAFE))]
|
#[validate(length(min = 1, max = 64), regex(path = *crate::util::validate::RE_URL_SAFE))]
|
||||||
pub name: Option<Option<String>>,
|
pub name: Option<Option<String>>,
|
||||||
#[serde(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
|
@ -906,11 +906,11 @@ pub async fn edit_project_categories(
|
|||||||
categories: &Vec<String>,
|
categories: &Vec<String>,
|
||||||
perms: &ProjectPermissions,
|
perms: &ProjectPermissions,
|
||||||
project_id: db_ids::ProjectId,
|
project_id: db_ids::ProjectId,
|
||||||
additional: bool,
|
is_additional: bool,
|
||||||
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
transaction: &mut sqlx::Transaction<'_, sqlx::Postgres>,
|
||||||
) -> Result<(), ApiError> {
|
) -> Result<(), ApiError> {
|
||||||
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
|
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||||
let additional_str = if additional { "additional " } else { "" };
|
let additional_str = if is_additional { "additional " } else { "" };
|
||||||
return Err(ApiError::CustomAuthentication(format!(
|
return Err(ApiError::CustomAuthentication(format!(
|
||||||
"You do not have the permissions to edit the {additional_str}categories of this project!"
|
"You do not have the permissions to edit the {additional_str}categories of this project!"
|
||||||
)));
|
)));
|
||||||
@ -928,7 +928,11 @@ pub async fn edit_project_categories(
|
|||||||
|
|
||||||
let mcategories = category_ids
|
let mcategories = category_ids
|
||||||
.values()
|
.values()
|
||||||
.map(|x| ModCategory::new(project_id, *x, additional))
|
.map(|&category_id| ModCategory {
|
||||||
|
project_id,
|
||||||
|
category_id,
|
||||||
|
is_additional,
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
mod_categories.extend(mcategories);
|
mod_categories.extend(mcategories);
|
||||||
}
|
}
|
||||||
@ -1081,7 +1085,6 @@ pub async fn dependency_list(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(derive_new::new)]
|
|
||||||
pub struct CategoryChanges<'a> {
|
pub struct CategoryChanges<'a> {
|
||||||
pub categories: &'a Option<Vec<String>>,
|
pub categories: &'a Option<Vec<String>>,
|
||||||
pub add_categories: &'a Option<Vec<String>>,
|
pub add_categories: &'a Option<Vec<String>>,
|
||||||
@ -1241,11 +1244,11 @@ pub async fn projects_edit(
|
|||||||
&categories,
|
&categories,
|
||||||
&project.categories,
|
&project.categories,
|
||||||
project.inner.id as db_ids::ProjectId,
|
project.inner.id as db_ids::ProjectId,
|
||||||
CategoryChanges::new(
|
CategoryChanges {
|
||||||
&bulk_edit_project.categories,
|
categories: &bulk_edit_project.categories,
|
||||||
&bulk_edit_project.add_categories,
|
add_categories: &bulk_edit_project.add_categories,
|
||||||
&bulk_edit_project.remove_categories,
|
remove_categories: &bulk_edit_project.remove_categories,
|
||||||
),
|
},
|
||||||
3,
|
3,
|
||||||
false,
|
false,
|
||||||
&mut transaction,
|
&mut transaction,
|
||||||
@ -1256,11 +1259,12 @@ pub async fn projects_edit(
|
|||||||
&categories,
|
&categories,
|
||||||
&project.additional_categories,
|
&project.additional_categories,
|
||||||
project.inner.id as db_ids::ProjectId,
|
project.inner.id as db_ids::ProjectId,
|
||||||
CategoryChanges::new(
|
CategoryChanges {
|
||||||
&bulk_edit_project.additional_categories,
|
categories: &bulk_edit_project.additional_categories,
|
||||||
&bulk_edit_project.add_additional_categories,
|
add_categories: &bulk_edit_project.add_additional_categories,
|
||||||
&bulk_edit_project.remove_additional_categories,
|
remove_categories: &bulk_edit_project
|
||||||
),
|
.remove_additional_categories,
|
||||||
|
},
|
||||||
256,
|
256,
|
||||||
true,
|
true,
|
||||||
&mut transaction,
|
&mut transaction,
|
||||||
@ -1383,11 +1387,11 @@ pub async fn bulk_edit_project_categories(
|
|||||||
))
|
))
|
||||||
})?
|
})?
|
||||||
.id;
|
.id;
|
||||||
mod_categories.push(ModCategory::new(
|
mod_categories.push(ModCategory {
|
||||||
project_id,
|
project_id,
|
||||||
category_id,
|
category_id,
|
||||||
is_additional,
|
is_additional,
|
||||||
));
|
});
|
||||||
}
|
}
|
||||||
ModCategory::insert_many(mod_categories, &mut *transaction).await?;
|
ModCategory::insert_many(mod_categories, &mut *transaction).await?;
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
use std::{collections::HashMap, sync::Arc};
|
use std::{collections::HashMap, sync::Arc};
|
||||||
|
|
||||||
use actix_web::{HttpRequest, HttpResponse, web};
|
use actix_web::{HttpRequest, HttpResponse, web};
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::PgPool;
|
use sqlx::PgPool;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
@ -358,13 +356,9 @@ pub async fn orgs_list(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref RE_URL_SAFE: Regex = Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Validate)]
|
#[derive(Serialize, Deserialize, Validate)]
|
||||||
pub struct EditUser {
|
pub struct EditUser {
|
||||||
#[validate(length(min = 1, max = 39), regex(path = *RE_URL_SAFE))]
|
#[validate(length(min = 1, max = 39), regex(path = *crate::util::validate::RE_URL_SAFE))]
|
||||||
pub username: Option<String>,
|
pub username: Option<String>,
|
||||||
#[serde(
|
#[serde(
|
||||||
default,
|
default,
|
||||||
|
@ -287,10 +287,10 @@ pub async fn version_edit_helper(
|
|||||||
ApiError::Validation(validation_errors_to_string(err, None))
|
ApiError::Validation(validation_errors_to_string(err, None))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
let version_id = info.0;
|
let version_id = info.0.into();
|
||||||
let id = version_id.into();
|
|
||||||
|
|
||||||
let result = database::models::Version::get(id, &**pool, &redis).await?;
|
let result =
|
||||||
|
database::models::Version::get(version_id, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(version_item) = result {
|
if let Some(version_item) = result {
|
||||||
let team_member =
|
let team_member =
|
||||||
@ -345,7 +345,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
name.trim(),
|
name.trim(),
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -359,7 +359,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
number,
|
number,
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -373,7 +373,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
version_type.as_str(),
|
version_type.as_str(),
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -384,7 +384,7 @@ pub async fn version_edit_helper(
|
|||||||
"
|
"
|
||||||
DELETE FROM dependencies WHERE dependent_id = $1
|
DELETE FROM dependencies WHERE dependent_id = $1
|
||||||
",
|
",
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -448,7 +448,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE version_id = $1
|
WHERE version_id = $1
|
||||||
AND field_id = ANY($2)
|
AND field_id = ANY($2)
|
||||||
",
|
",
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
&loader_field_ids
|
&loader_field_ids
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
@ -476,7 +476,7 @@ pub async fn version_edit_helper(
|
|||||||
.remove(&loader_field.id)
|
.remove(&loader_field.id)
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
let vf: VersionField = VersionField::check_parse(
|
let vf: VersionField = VersionField::check_parse(
|
||||||
version_id.into(),
|
version_id,
|
||||||
loader_field.clone(),
|
loader_field.clone(),
|
||||||
vf_value.clone(),
|
vf_value.clone(),
|
||||||
enum_variants,
|
enum_variants,
|
||||||
@ -493,7 +493,7 @@ pub async fn version_edit_helper(
|
|||||||
"
|
"
|
||||||
DELETE FROM loaders_versions WHERE version_id = $1
|
DELETE FROM loaders_versions WHERE version_id = $1
|
||||||
",
|
",
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -513,7 +513,10 @@ pub async fn version_edit_helper(
|
|||||||
.to_string(),
|
.to_string(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
loader_versions.push(LoaderVersion::new(loader_id, id));
|
loader_versions.push(LoaderVersion {
|
||||||
|
loader_id,
|
||||||
|
version_id,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
LoaderVersion::insert_many(loader_versions, &mut transaction)
|
LoaderVersion::insert_many(loader_versions, &mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -535,7 +538,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
featured,
|
featured,
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -549,7 +552,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
body,
|
body,
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -569,7 +572,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
*downloads as i32,
|
*downloads as i32,
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -604,7 +607,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
status.as_str(),
|
status.as_str(),
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
@ -652,7 +655,7 @@ pub async fn version_edit_helper(
|
|||||||
WHERE (id = $2)
|
WHERE (id = $2)
|
||||||
",
|
",
|
||||||
ordering.to_owned() as Option<i32>,
|
ordering.to_owned() as Option<i32>,
|
||||||
id as database::models::ids::VersionId,
|
version_id as database::models::ids::VersionId,
|
||||||
)
|
)
|
||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
|
use std::sync::LazyLock;
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use validator::{ValidationErrors, ValidationErrorsKind};
|
use validator::{ValidationErrors, ValidationErrorsKind};
|
||||||
|
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
|
|
||||||
lazy_static! {
|
pub static RE_URL_SAFE: LazyLock<Regex> =
|
||||||
pub static ref RE_URL_SAFE: Regex =
|
LazyLock::new(|| Regex::new(r"^[a-zA-Z0-9_-]*$").unwrap());
|
||||||
Regex::new(r#"^[a-zA-Z0-9!@$()`.+,_"-]*$"#).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: In order to ensure readability, only the first error is printed, this may need to be expanded on in the future!
|
//TODO: In order to ensure readability, only the first error is printed, this may need to be expanded on in the future!
|
||||||
pub fn validation_errors_to_string(
|
pub fn validation_errors_to_string(
|
||||||
|
@ -5,75 +5,72 @@ authors = ["Jai A <jaiagr+gpg@pm.me>"]
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
bytes = "1"
|
bytes.workspace = true
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json.workspace = true
|
||||||
serde_ini = "0.2.0"
|
serde_ini.workspace = true
|
||||||
sha1_smol = { version = "1.0.0", features = ["std"] }
|
sha1_smol.workspace = true
|
||||||
sha2 = "0.10.9"
|
sha2.workspace = true
|
||||||
url = { version = "2.2", features = ["serde"] }
|
url = { workspace = true, features = ["serde"] }
|
||||||
uuid = { version = "1.1", features = ["serde", "v4"] }
|
uuid = { workspace = true, features = ["serde", "v4"] }
|
||||||
zip = "2.6.1"
|
zip.workspace = true
|
||||||
async_zip = { version = "0.0.17", features = ["chrono", "tokio-fs", "deflate", "bzip2", "zstd", "deflate64"] }
|
async_zip = { workspace = true, features = ["chrono", "tokio-fs", "deflate", "bzip2", "zstd", "deflate64"] }
|
||||||
flate2 = "1.1.1"
|
flate2.workspace = true
|
||||||
tempfile = "3.5.0"
|
tempfile.workspace = true
|
||||||
dashmap = { version = "6.1.0", features = ["serde"] }
|
dashmap = { workspace = true, features = ["serde"] }
|
||||||
quick-xml = { version = "0.37", features = ["async-tokio"] }
|
quick-xml = { workspace = true, features = ["async-tokio"] }
|
||||||
enumset = "1.1"
|
enumset.workspace = true
|
||||||
|
|
||||||
chrono = { version = "0.4.19", features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
daedalus = { path = "../../packages/daedalus" }
|
daedalus.workspace = true
|
||||||
dirs = "6.0.0"
|
dirs.workspace = true
|
||||||
|
|
||||||
regex = "1.5"
|
regex.workspace = true
|
||||||
sys-info = "0.9.0"
|
sysinfo = { workspace = true, features = ["system", "disk"] }
|
||||||
sysinfo = "0.35.0"
|
thiserror.workspace = true
|
||||||
thiserror = "2.0.12"
|
either.workspace = true
|
||||||
either = "1.13"
|
|
||||||
|
|
||||||
tracing = "0.1.37"
|
tracing.workspace = true
|
||||||
tracing-subscriber = { version = "0.3.18", features = ["chrono", "env-filter"] }
|
tracing-subscriber = { workspace = true, features = ["chrono", "env-filter"] }
|
||||||
tracing-error = "0.2.0"
|
tracing-error.workspace = true
|
||||||
|
|
||||||
paste = { version = "1.0" }
|
paste.workspace = true
|
||||||
|
|
||||||
tauri = { version = "2.5.1", optional = true }
|
tauri = { workspace = true, optional = true }
|
||||||
indicatif = { version = "0.17.3", optional = true }
|
indicatif = { workspace = true, optional = true }
|
||||||
|
|
||||||
async-tungstenite = { version = "0.29.1", features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
|
async-tungstenite = { workspace = true, features = ["tokio-runtime", "tokio-rustls-webpki-roots"] }
|
||||||
futures = "0.3"
|
futures = { workspace = true, features = ["async-await", "alloc"] }
|
||||||
reqwest = { version = "0.12.15", features = ["json", "stream", "deflate", "gzip", "brotli", "rustls-tls", "charset", "http2", "macos-system-configuration"], default-features = false }
|
reqwest = { workspace = true, features = ["json", "stream", "deflate", "gzip", "brotli", "rustls-tls-webpki-roots", "charset", "http2", "macos-system-configuration"] }
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { workspace = true, features = ["time", "io-util", "net", "sync", "fs", "macros", "process"] }
|
||||||
tokio-util = "0.7"
|
tokio-util = { workspace = true, features = ["compat"] }
|
||||||
async-recursion = "1.0.4"
|
async-recursion.workspace = true
|
||||||
fs4 = { version = "0.13", features = ["tokio"] }
|
fs4 = { workspace = true, features = ["tokio"] }
|
||||||
async-walkdir = "2.1"
|
async-walkdir.workspace = true
|
||||||
async-compression = { version = "0.4", default-features = false, features = ["tokio", "gzip"] }
|
async-compression = { workspace = true, features = ["tokio", "gzip"] }
|
||||||
|
|
||||||
notify = { version = "8.0.0", default-features = false }
|
notify.workspace = true
|
||||||
notify-debouncer-mini = { version = "0.6.0", default-features = false }
|
notify-debouncer-mini.workspace = true
|
||||||
|
|
||||||
lazy_static = "1.4.0"
|
dunce.workspace = true
|
||||||
dunce = "1.0.3"
|
|
||||||
|
|
||||||
whoami = "1.4.0"
|
whoami.workspace = true
|
||||||
|
|
||||||
discord-rich-presence = "0.2.4"
|
discord-rich-presence.workspace = true
|
||||||
|
|
||||||
p256 = { version = "0.13.2", features = ["ecdsa"] }
|
p256 = { workspace = true, features = ["ecdsa"] }
|
||||||
rand = "0.8"
|
rand.workspace = true
|
||||||
byteorder = "1.5.0"
|
base64.workspace = true
|
||||||
base64 = "0.22.1"
|
|
||||||
|
|
||||||
sqlx = { version = "0.8.2", features = [ "runtime-tokio", "sqlite", "macros" ] }
|
sqlx = { workspace = true, features = ["runtime-tokio", "sqlite", "macros", "migrate", "json"] }
|
||||||
|
|
||||||
quartz_nbt = { version = "0.2", features = ["serde"] }
|
quartz_nbt = { workspace = true, features = ["serde"] }
|
||||||
hickory-resolver = "0.25"
|
hickory-resolver.workspace = true
|
||||||
|
|
||||||
ariadne = { path = "../ariadne" }
|
ariadne.workspace = true
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winreg = "0.55.0"
|
winreg.workspace = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
tauri = ["dep:tauri"]
|
tauri = ["dep:tauri"]
|
||||||
|
@ -6,6 +6,7 @@ use dashmap::DashMap;
|
|||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use sysinfo::{MemoryRefreshKind, RefreshKind};
|
||||||
|
|
||||||
use crate::util::io;
|
use crate::util::io;
|
||||||
use crate::util::jre::extract_java_majorminor_version;
|
use crate::util::jre::extract_java_majorminor_version;
|
||||||
@ -175,11 +176,10 @@ pub async fn test_jre(
|
|||||||
|
|
||||||
// Gets maximum memory in KiB.
|
// Gets maximum memory in KiB.
|
||||||
pub async fn get_max_memory() -> crate::Result<u64> {
|
pub async fn get_max_memory() -> crate::Result<u64> {
|
||||||
Ok(sys_info::mem_info()
|
Ok(sysinfo::System::new_with_specifics(
|
||||||
.map_err(|_| {
|
RefreshKind::nothing()
|
||||||
crate::Error::from(crate::ErrorKind::LauncherError(
|
.with_memory(MemoryRefreshKind::nothing().with_ram()),
|
||||||
"Unable to get computer memory".to_string(),
|
)
|
||||||
))
|
.total_memory()
|
||||||
})?
|
/ 1024)
|
||||||
.total)
|
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ use either::Either;
|
|||||||
use enumset::{EnumSet, EnumSetType};
|
use enumset::{EnumSet, EnumSetType};
|
||||||
use fs4::tokio::AsyncFileExt;
|
use fs4::tokio::AsyncFileExt;
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use quartz_nbt::{NbtCompound, NbtTag};
|
use quartz_nbt::{NbtCompound, NbtTag};
|
||||||
use regex::{Regex, RegexBuilder};
|
use regex::{Regex, RegexBuilder};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@ -25,6 +24,7 @@ use std::cmp::Reverse;
|
|||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::LazyLock;
|
||||||
use tokio::io::AsyncWriteExt;
|
use tokio::io::AsyncWriteExt;
|
||||||
use tokio_util::compat::FuturesAsyncWriteCompatExt;
|
use tokio_util::compat::FuturesAsyncWriteCompatExt;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
@ -548,17 +548,19 @@ pub async fn backup_world(instance: &Path, world: &str) -> Result<u64> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn find_available_name(dir: &Path, file_name: &str, extension: &str) -> String {
|
fn find_available_name(dir: &Path, file_name: &str, extension: &str) -> String {
|
||||||
lazy_static! {
|
static RESERVED_WINDOWS_FILENAMES: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
static ref RESERVED_WINDOWS_FILENAMES: Regex = RegexBuilder::new(r#"^.*\.|(?:COM|CLOCK\$|CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\..*)?$"#)
|
RegexBuilder::new(r#"^.*\.|(?:COM|CLOCK\$|CON|PRN|AUX|NUL|COM[1-9]|LPT[1-9])(?:\..*)?$"#)
|
||||||
.case_insensitive(true)
|
.case_insensitive(true)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap()
|
||||||
static ref COPY_COUNTER_PATTERN: Regex = RegexBuilder::new(r#"^(?<name>.*) \((?<count>\d*)\)$"#)
|
});
|
||||||
|
static COPY_COUNTER_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
|
RegexBuilder::new(r#"^(?<name>.*) \((?<count>\d*)\)$"#)
|
||||||
.case_insensitive(true)
|
.case_insensitive(true)
|
||||||
.unicode(true)
|
.unicode(true)
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap()
|
||||||
}
|
});
|
||||||
|
|
||||||
let mut file_name = file_name.replace(
|
let mut file_name = file_name.replace(
|
||||||
[
|
[
|
||||||
|
@ -5,30 +5,27 @@ use crate::state::{
|
|||||||
DirectoryInfo, ProfileInstallStage, ProjectType, attached_world_data,
|
DirectoryInfo, ProfileInstallStage, ProjectType, attached_world_data,
|
||||||
};
|
};
|
||||||
use crate::worlds::WorldType;
|
use crate::worlds::WorldType;
|
||||||
use futures::{SinkExt, StreamExt, channel::mpsc::channel};
|
|
||||||
use notify::{RecommendedWatcher, RecursiveMode};
|
use notify::{RecommendedWatcher, RecursiveMode};
|
||||||
use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer};
|
use notify_debouncer_mini::{DebounceEventResult, Debouncer, new_debouncer};
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tokio::sync::RwLock;
|
use tokio::sync::{RwLock, mpsc::channel};
|
||||||
|
|
||||||
pub type FileWatcher = RwLock<Debouncer<RecommendedWatcher>>;
|
pub type FileWatcher = RwLock<Debouncer<RecommendedWatcher>>;
|
||||||
|
|
||||||
pub async fn init_watcher() -> crate::Result<FileWatcher> {
|
pub async fn init_watcher() -> crate::Result<FileWatcher> {
|
||||||
let (mut tx, mut rx) = channel(1);
|
let (tx, mut rx) = channel(1);
|
||||||
|
|
||||||
let file_watcher = new_debouncer(
|
let file_watcher = new_debouncer(
|
||||||
Duration::from_secs_f32(1.0),
|
Duration::from_secs_f32(1.0),
|
||||||
move |res: DebounceEventResult| {
|
move |res: DebounceEventResult| {
|
||||||
futures::executor::block_on(async {
|
tx.blocking_send(res).ok();
|
||||||
tx.send(res).await.unwrap();
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
let span = tracing::span!(tracing::Level::INFO, "init_watcher");
|
let span = tracing::span!(tracing::Level::INFO, "init_watcher");
|
||||||
tracing::info!(parent: &span, "Initting watcher");
|
tracing::info!(parent: &span, "Initting watcher");
|
||||||
while let Some(res) = rx.next().await {
|
while let Some(res) = rx.recv().await {
|
||||||
let _span = span.enter();
|
let _span = span.enter();
|
||||||
|
|
||||||
match res {
|
match res {
|
||||||
|
@ -2,7 +2,6 @@ use crate::ErrorKind;
|
|||||||
use crate::util::fetch::REQWEST_CLIENT;
|
use crate::util::fetch::REQWEST_CLIENT;
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
|
use base64::prelude::{BASE64_STANDARD, BASE64_URL_SAFE_NO_PAD};
|
||||||
use byteorder::BigEndian;
|
|
||||||
use chrono::{DateTime, Duration, TimeZone, Utc};
|
use chrono::{DateTime, Duration, TimeZone, Utc};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use futures::TryStreamExt;
|
use futures::TryStreamExt;
|
||||||
@ -62,12 +61,6 @@ pub enum MinecraftAuthenticationError {
|
|||||||
#[source]
|
#[source]
|
||||||
source: reqwest::Error,
|
source: reqwest::Error,
|
||||||
},
|
},
|
||||||
#[error("Error creating signed request buffer {step:?}: {source}")]
|
|
||||||
ConstructingSignedRequest {
|
|
||||||
step: MinecraftAuthStep,
|
|
||||||
#[source]
|
|
||||||
source: std::io::Error,
|
|
||||||
},
|
|
||||||
#[error("Error reading XBOX Session ID header")]
|
#[error("Error reading XBOX Session ID header")]
|
||||||
NoSessionId,
|
NoSessionId,
|
||||||
#[error("Error reading user hash")]
|
#[error("Error reading user hash")]
|
||||||
@ -1087,56 +1080,25 @@ async fn send_signed_request<T: DeserializeOwned>(
|
|||||||
let time: u128 =
|
let time: u128 =
|
||||||
{ ((current_date.timestamp() as u128) + 11644473600) * 10000000 };
|
{ ((current_date.timestamp() as u128) + 11644473600) * 10000000 };
|
||||||
|
|
||||||
use byteorder::WriteBytesExt;
|
|
||||||
let mut buffer = Vec::new();
|
let mut buffer = Vec::new();
|
||||||
buffer.write_u32::<BigEndian>(1).map_err(|source| {
|
buffer.extend_from_slice(&1_u32.to_be_bytes()[..]);
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
|
buffer.push(0_u8);
|
||||||
})?;
|
buffer.extend_from_slice(&(time as u64).to_be_bytes()[..]);
|
||||||
buffer.write_u8(0).map_err(|source| {
|
buffer.push(0_u8);
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
|
|
||||||
})?;
|
|
||||||
buffer
|
|
||||||
.write_u64::<BigEndian>(time as u64)
|
|
||||||
.map_err(|source| {
|
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest {
|
|
||||||
source,
|
|
||||||
step,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
buffer.write_u8(0).map_err(|source| {
|
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
|
|
||||||
})?;
|
|
||||||
buffer.extend_from_slice("POST".as_bytes());
|
buffer.extend_from_slice("POST".as_bytes());
|
||||||
buffer.write_u8(0).map_err(|source| {
|
buffer.push(0_u8);
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
|
|
||||||
})?;
|
|
||||||
buffer.extend_from_slice(url_path.as_bytes());
|
buffer.extend_from_slice(url_path.as_bytes());
|
||||||
buffer.write_u8(0).map_err(|source| {
|
buffer.push(0_u8);
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
|
|
||||||
})?;
|
|
||||||
buffer.extend_from_slice(&auth);
|
buffer.extend_from_slice(&auth);
|
||||||
buffer.write_u8(0).map_err(|source| {
|
buffer.push(0_u8);
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
|
|
||||||
})?;
|
|
||||||
buffer.extend_from_slice(&body);
|
buffer.extend_from_slice(&body);
|
||||||
buffer.write_u8(0).map_err(|source| {
|
buffer.push(0_u8);
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let ecdsa_sig: Signature = key.key.sign(&buffer);
|
let ecdsa_sig: Signature = key.key.sign(&buffer);
|
||||||
|
|
||||||
let mut sig_buffer = Vec::new();
|
let mut sig_buffer = Vec::new();
|
||||||
sig_buffer.write_i32::<BigEndian>(1).map_err(|source| {
|
sig_buffer.extend_from_slice(&1_i32.to_be_bytes()[..]);
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest { source, step }
|
sig_buffer.extend_from_slice(&(time as u64).to_be_bytes()[..]);
|
||||||
})?;
|
|
||||||
sig_buffer
|
|
||||||
.write_u64::<BigEndian>(time as u64)
|
|
||||||
.map_err(|source| {
|
|
||||||
MinecraftAuthenticationError::ConstructingSignedRequest {
|
|
||||||
source,
|
|
||||||
step,
|
|
||||||
}
|
|
||||||
})?;
|
|
||||||
sig_buffer.extend_from_slice(&ecdsa_sig.r().to_bytes());
|
sig_buffer.extend_from_slice(&ecdsa_sig.r().to_bytes());
|
||||||
sig_buffer.extend_from_slice(&ecdsa_sig.s().to_bytes());
|
sig_buffer.extend_from_slice(&ecdsa_sig.s().to_bytes());
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ use crate::util::fetch::{FetchSemaphore, IoSemaphore, write_cached_icon};
|
|||||||
use crate::util::io::{self};
|
use crate::util::io::{self};
|
||||||
use chrono::{DateTime, TimeDelta, TimeZone, Utc};
|
use chrono::{DateTime, TimeDelta, TimeZone, Utc};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
@ -17,6 +16,7 @@ use std::collections::HashSet;
|
|||||||
use std::convert::TryFrom;
|
use std::convert::TryFrom;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::sync::LazyLock;
|
||||||
use tokio::fs::DirEntry;
|
use tokio::fs::DirEntry;
|
||||||
use tokio::io::{AsyncBufReadExt, AsyncRead};
|
use tokio::io::{AsyncBufReadExt, AsyncRead};
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
@ -837,9 +837,9 @@ impl Profile {
|
|||||||
state: &crate::State,
|
state: &crate::State,
|
||||||
join_entry: &mut JoinLogEntry,
|
join_entry: &mut JoinLogEntry,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
lazy_static! {
|
static LOG_LINE_REGEX: LazyLock<Regex> = LazyLock::new(|| {
|
||||||
static ref LOG_LINE_REGEX: Regex = Regex::new(r"^\[[0-9]{2}(?::[0-9]{2}){2}] \[.+?/[A-Z]+?]: Connecting to (.+?), ([1-9][0-9]{0,4})$").unwrap();
|
Regex::new(r"^\[[0-9]{2}(?::[0-9]{2}){2}] \[.+?/[A-Z]+?]: Connecting to (.+?), ([1-9][0-9]{0,4})$").unwrap()
|
||||||
}
|
});
|
||||||
let reader = tokio::io::BufReader::new(reader);
|
let reader = tokio::io::BufReader::new(reader);
|
||||||
let mut lines = reader.lines();
|
let mut lines = reader.lines();
|
||||||
while let Some(log_line) = lines.next_line().await? {
|
while let Some(log_line) = lines.next_line().await? {
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
//! Functions for fetching infromation from the Internet
|
//! Functions for fetching information from the Internet
|
||||||
use super::io::{self, IOError};
|
use super::io::{self, IOError};
|
||||||
use crate::config::{MODRINTH_API_URL, MODRINTH_API_URL_V3};
|
use crate::config::{MODRINTH_API_URL, MODRINTH_API_URL_V3};
|
||||||
use crate::event::LoadingBarId;
|
use crate::event::LoadingBarId;
|
||||||
use crate::event::emit::emit_loading;
|
use crate::event::emit::emit_loading;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use serde::de::DeserializeOwned;
|
use serde::de::DeserializeOwned;
|
||||||
use std::ffi::OsStr;
|
use std::ffi::OsStr;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
use std::sync::LazyLock;
|
||||||
use std::time::{self};
|
use std::time::{self};
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
use tokio::{fs::File, io::AsyncWriteExt};
|
use tokio::{fs::File, io::AsyncWriteExt};
|
||||||
@ -18,22 +18,20 @@ pub struct IoSemaphore(pub Semaphore);
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct FetchSemaphore(pub Semaphore);
|
pub struct FetchSemaphore(pub Semaphore);
|
||||||
|
|
||||||
lazy_static! {
|
pub static REQWEST_CLIENT: LazyLock<reqwest::Client> = LazyLock::new(|| {
|
||||||
pub static ref REQWEST_CLIENT: reqwest::Client = {
|
let mut headers = reqwest::header::HeaderMap::new();
|
||||||
let mut headers = reqwest::header::HeaderMap::new();
|
let header = reqwest::header::HeaderValue::from_str(&format!(
|
||||||
let header = reqwest::header::HeaderValue::from_str(&format!(
|
"modrinth/theseus/{} (support@modrinth.com)",
|
||||||
"modrinth/theseus/{} (support@modrinth.com)",
|
env!("CARGO_PKG_VERSION")
|
||||||
env!("CARGO_PKG_VERSION")
|
))
|
||||||
))
|
.unwrap();
|
||||||
.unwrap();
|
headers.insert(reqwest::header::USER_AGENT, header);
|
||||||
headers.insert(reqwest::header::USER_AGENT, header);
|
reqwest::Client::builder()
|
||||||
reqwest::Client::builder()
|
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
||||||
.tcp_keepalive(Some(time::Duration::from_secs(10)))
|
.default_headers(headers)
|
||||||
.default_headers(headers)
|
.build()
|
||||||
.build()
|
.expect("Reqwest Client Building Failed")
|
||||||
.expect("Reqwest Client Building Failed")
|
});
|
||||||
};
|
|
||||||
}
|
|
||||||
const FETCH_ATTEMPTS: usize = 3;
|
const FETCH_ATTEMPTS: usize = 3;
|
||||||
|
|
||||||
#[tracing::instrument(skip(semaphore))]
|
#[tracing::instrument(skip(semaphore))]
|
||||||
|
@ -276,11 +276,10 @@ pub async fn check_java_at_filepath(path: &Path) -> Option<JavaVersion> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let bytes = include_bytes!("../../library/JavaInfo.class");
|
let bytes = include_bytes!("../../library/JavaInfo.class");
|
||||||
let tempdir: PathBuf = tempfile::tempdir().ok()?.into_path();
|
let Ok(tempdir) = tempfile::tempdir() else {
|
||||||
if !tempdir.exists() {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
};
|
||||||
let file_path = tempdir.join("JavaInfo.class");
|
let file_path = tempdir.path().join("JavaInfo.class");
|
||||||
io::write(&file_path, bytes).await.ok()?;
|
io::write(&file_path, bytes).await.ok()?;
|
||||||
|
|
||||||
let output = Command::new(&java)
|
let output = Command::new(&java)
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
//! Platform-related code
|
//! Platform-related code
|
||||||
use daedalus::minecraft::{Os, OsRule};
|
use daedalus::minecraft::{Os, OsRule};
|
||||||
use regex::Regex;
|
|
||||||
|
|
||||||
// OS detection
|
// OS detection
|
||||||
pub trait OsExt {
|
pub trait OsExt {
|
||||||
@ -92,12 +91,16 @@ pub fn os_rule(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(version) = &rule.version {
|
// `rule.version` is ignored because it's not usually seen on real recent
|
||||||
if let Ok(regex) = Regex::new(version.as_str()) {
|
// Minecraft version manifests, its alleged regex syntax is undefined and is
|
||||||
rule_match &=
|
// likely to not match `Regex`'s, and the way to get the value to match it
|
||||||
regex.is_match(&sys_info::os_release().unwrap_or_default());
|
// against is allegedly calling `System.getProperty("os.version")`, which
|
||||||
}
|
// on Windows the OpenJDK implements by fetching the kernel32.dll version,
|
||||||
}
|
// an approach that no public Rust library implements. Moreover, launchers
|
||||||
|
// such as PrismLauncher also ignore this field. Code references:
|
||||||
|
// - https://github.com/openjdk/jdk/blob/948ade8e7003a41683600428c8e3155c7ed798db/src/java.base/windows/native/libjava/java_props_md.c#L556
|
||||||
|
// - https://github.com/PrismLauncher/PrismLauncher/blob/1c20faccf88999474af70db098a4c10e7a03af33/launcher/minecraft/Rule.h#L77
|
||||||
|
// - https://github.com/FillZpp/sys-info-rs/blob/60ecf1470a5b7c90242f429934a3bacb6023ec4d/c/windows.c#L23-L38
|
||||||
|
|
||||||
rule_match
|
rule_match
|
||||||
}
|
}
|
||||||
|
@ -4,13 +4,12 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json.workspace = true
|
||||||
thiserror = "2.0.12"
|
thiserror.workspace = true
|
||||||
uuid = { version = "1.2.2", features = ["v4", "fast-rng", "serde"] }
|
uuid = { workspace = true, features = ["v4", "fast-rng", "serde"] }
|
||||||
serde_bytes = "0.11"
|
serde_bytes.workspace = true
|
||||||
rand = "0.8.5"
|
rand.workspace = true
|
||||||
either = "1.13"
|
either.workspace = true
|
||||||
chrono = { version = "0.4.26", features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
serde_cbor = "0.11"
|
serde_cbor.workspace = true
|
||||||
lazy_static = "1.5"
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
use lazy_static::lazy_static;
|
use std::{collections::HashMap, sync::LazyLock};
|
||||||
use std::collections::HashMap;
|
|
||||||
|
|
||||||
lazy_static! {
|
static SPECIAL_PARENTS: LazyLock<HashMap<&'static str, &'static str>> =
|
||||||
static ref SPECIAL_PARENTS: HashMap<&'static str, &'static str> = {
|
LazyLock::new(|| {
|
||||||
let mut m = HashMap::new();
|
let mut m = HashMap::new();
|
||||||
m.insert("15w14a", "1.8.3");
|
m.insert("15w14a", "1.8.3");
|
||||||
m.insert("1.RV-Pre1", "1.9.2");
|
m.insert("1.RV-Pre1", "1.9.2");
|
||||||
@ -12,8 +11,7 @@ lazy_static! {
|
|||||||
m.insert("23w13a_or_b", "23w13a");
|
m.insert("23w13a_or_b", "23w13a");
|
||||||
m.insert("24w14potato", "24w12a");
|
m.insert("24w14potato", "24w12a");
|
||||||
m
|
m
|
||||||
};
|
});
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_feature_supported_in(
|
pub fn is_feature_supported_in(
|
||||||
version: &str,
|
version: &str,
|
||||||
|
@ -14,7 +14,7 @@ readme = "README.md"
|
|||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { workspace = true, features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json.workspace = true
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
thiserror = "2.0"
|
thiserror.workspace = true
|
||||||
|
Loading…
x
Reference in New Issue
Block a user