You've already forked AstralRinth
forked from didirus/AstralRinth
Merge commit 'df1499047ccc8f39d756d5beba60651237aca1c0' into beta
This commit is contained in:
+4
-3
@@ -4,15 +4,16 @@ root = true
|
|||||||
[*]
|
[*]
|
||||||
charset = utf-8
|
charset = utf-8
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 4
|
||||||
end_of_line = lf
|
end_of_line = lf
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
max_line_length = 100
|
max_line_length = 100
|
||||||
|
|
||||||
[*.md]
|
[*.md]
|
||||||
|
indent_size = 2
|
||||||
max_line_length = off
|
max_line_length = off
|
||||||
trim_trailing_whitespace = false
|
trim_trailing_whitespace = false
|
||||||
|
|
||||||
[*.{rs,java,kts}]
|
[*.{json,yml,yaml,ts,vue,scss,css,html,js,cjs,mjs,gltf,prettierrc}]
|
||||||
indent_size = 4
|
indent_size = 2
|
||||||
|
|||||||
Generated
+182
-195
File diff suppressed because it is too large
Load Diff
+36
-36
@@ -25,31 +25,31 @@ actix-ws = "0.3.0"
|
|||||||
argon2 = { version = "0.5.3", features = ["std"] }
|
argon2 = { version = "0.5.3", features = ["std"] }
|
||||||
ariadne = { path = "packages/ariadne" }
|
ariadne = { path = "packages/ariadne" }
|
||||||
async_zip = "0.0.17"
|
async_zip = "0.0.17"
|
||||||
async-compression = { version = "0.4.25", default-features = false }
|
async-compression = { version = "0.4.27", default-features = false }
|
||||||
async-recursion = "1.1.1"
|
async-recursion = "1.1.1"
|
||||||
async-stripe = { version = "0.41.0", default-features = false, features = [
|
async-stripe = { version = "0.41.0", default-features = false, features = [
|
||||||
"runtime-tokio-hyper-rustls",
|
"runtime-tokio-hyper-rustls",
|
||||||
] }
|
] }
|
||||||
async-trait = "0.1.88"
|
async-trait = "0.1.88"
|
||||||
async-tungstenite = { version = "0.29.1", default-features = false, features = [
|
async-tungstenite = { version = "0.30.0", default-features = false, features = [
|
||||||
"futures-03-sink",
|
"futures-03-sink",
|
||||||
] }
|
] }
|
||||||
async-walkdir = "2.1.0"
|
async-walkdir = "2.1.0"
|
||||||
base64 = "0.22.1"
|
base64 = "0.22.1"
|
||||||
bitflags = "2.9.1"
|
bitflags = "2.9.1"
|
||||||
bytemuck = "1.23.0"
|
bytemuck = "1.23.1"
|
||||||
bytes = "1.10.1"
|
bytes = "1.10.1"
|
||||||
censor = "0.3.0"
|
censor = "0.3.0"
|
||||||
chardetng = "0.1.17"
|
chardetng = "0.1.17"
|
||||||
chrono = "0.4.41"
|
chrono = "0.4.41"
|
||||||
clap = "4.5.40"
|
clap = "4.5.43"
|
||||||
clickhouse = "0.13.3"
|
clickhouse = "0.13.3"
|
||||||
color-thief = "0.2.2"
|
color-thief = "0.2.2"
|
||||||
console-subscriber = "0.4.1"
|
console-subscriber = "0.4.1"
|
||||||
daedalus = { path = "packages/daedalus" }
|
daedalus = { path = "packages/daedalus" }
|
||||||
dashmap = "6.1.0"
|
dashmap = "6.1.0"
|
||||||
data-url = "0.3.1"
|
data-url = "0.3.1"
|
||||||
deadpool-redis = "0.21.1"
|
deadpool-redis = "0.22.0"
|
||||||
dirs = "6.0.0"
|
dirs = "6.0.0"
|
||||||
discord-rich-presence = "0.2.5"
|
discord-rich-presence = "0.2.5"
|
||||||
dotenv-build = "0.1.1"
|
dotenv-build = "0.1.1"
|
||||||
@@ -57,7 +57,7 @@ dotenvy = "0.15.7"
|
|||||||
dunce = "1.0.5"
|
dunce = "1.0.5"
|
||||||
either = "1.15.0"
|
either = "1.15.0"
|
||||||
encoding_rs = "0.8.35"
|
encoding_rs = "0.8.35"
|
||||||
enumset = "1.1.6"
|
enumset = "1.1.7"
|
||||||
flate2 = "1.1.2"
|
flate2 = "1.1.2"
|
||||||
fs4 = { version = "0.13.1", default-features = false }
|
fs4 = { version = "0.13.1", default-features = false }
|
||||||
futures = { version = "0.3.31", default-features = false }
|
futures = { version = "0.3.31", default-features = false }
|
||||||
@@ -74,15 +74,15 @@ hyper-rustls = { version = "0.27.7", default-features = false, features = [
|
|||||||
"ring",
|
"ring",
|
||||||
"tls12",
|
"tls12",
|
||||||
] }
|
] }
|
||||||
hyper-util = "0.1.14"
|
hyper-util = "0.1.16"
|
||||||
iana-time-zone = "0.1.63"
|
iana-time-zone = "0.1.63"
|
||||||
image = { version = "0.25.6", default-features = false, features = ["rayon"] }
|
image = { version = "0.25.6", default-features = false, features = ["rayon"] }
|
||||||
indexmap = "2.9.0"
|
indexmap = "2.10.0"
|
||||||
indicatif = "0.17.11"
|
indicatif = "0.18.0"
|
||||||
itertools = "0.14.0"
|
itertools = "0.14.0"
|
||||||
jemalloc_pprof = "0.7.0"
|
jemalloc_pprof = "0.8.1"
|
||||||
json-patch = { version = "4.0.0", default-features = false }
|
json-patch = { version = "4.0.0", default-features = false }
|
||||||
lettre = { version = "0.11.17", default-features = false, features = [
|
lettre = { version = "0.11.18", default-features = false, features = [
|
||||||
"builder",
|
"builder",
|
||||||
"hostname",
|
"hostname",
|
||||||
"pool",
|
"pool",
|
||||||
@@ -92,24 +92,24 @@ lettre = { version = "0.11.17", default-features = false, features = [
|
|||||||
"smtp-transport",
|
"smtp-transport",
|
||||||
] }
|
] }
|
||||||
maxminddb = "0.26.0"
|
maxminddb = "0.26.0"
|
||||||
meilisearch-sdk = { version = "0.28.0", default-features = false }
|
meilisearch-sdk = { version = "0.29.1", default-features = false }
|
||||||
murmur2 = "0.1.0"
|
murmur2 = "0.1.0"
|
||||||
native-dialog = "0.9.0"
|
native-dialog = "0.9.0"
|
||||||
notify = { version = "8.0.0", default-features = false }
|
notify = { version = "8.2.0", default-features = false }
|
||||||
notify-debouncer-mini = { version = "0.6.0", default-features = false }
|
notify-debouncer-mini = { version = "0.7.0", default-features = false }
|
||||||
p256 = "0.13.2"
|
p256 = "0.13.2"
|
||||||
paste = "1.0.15"
|
paste = "1.0.15"
|
||||||
phf = { version = "0.12.1", features = ["macros"] }
|
phf = { version = "0.12.1", features = ["macros"] }
|
||||||
png = "0.17.16"
|
png = "0.17.16"
|
||||||
prometheus = "0.14.0"
|
prometheus = "0.14.0"
|
||||||
quartz_nbt = "0.2.9"
|
quartz_nbt = "0.2.9"
|
||||||
quick-xml = "0.37.5"
|
quick-xml = "0.38.1"
|
||||||
rand = "=0.8.5" # Locked on 0.8 until argon2 and p256 update to 0.9
|
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
|
rand_chacha = "=0.3.1" # Locked on 0.3 until we can update rand to 0.9
|
||||||
redis = "=0.31.0" # Locked on 0.31 until deadpool-redis updates to 0.32
|
redis = "0.32.4"
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
reqwest = { version = "0.12.20", default-features = false }
|
reqwest = { version = "0.12.22", default-features = false }
|
||||||
rgb = "0.8.50"
|
rgb = "0.8.52"
|
||||||
rust_decimal = { version = "1.37.2", features = [
|
rust_decimal = { version = "1.37.2", features = [
|
||||||
"serde-with-float",
|
"serde-with-float",
|
||||||
"serde-with-str",
|
"serde-with-str",
|
||||||
@@ -121,7 +121,7 @@ rust-s3 = { version = "0.35.1", default-features = false, features = [
|
|||||||
"tokio-rustls-tls",
|
"tokio-rustls-tls",
|
||||||
] }
|
] }
|
||||||
rusty-money = "0.4.1"
|
rusty-money = "0.4.1"
|
||||||
sentry = { version = "0.41.0", default-features = false, features = [
|
sentry = { version = "0.42.0", default-features = false, features = [
|
||||||
"backtrace",
|
"backtrace",
|
||||||
"contexts",
|
"contexts",
|
||||||
"debug-images",
|
"debug-images",
|
||||||
@@ -129,45 +129,45 @@ sentry = { version = "0.41.0", default-features = false, features = [
|
|||||||
"reqwest",
|
"reqwest",
|
||||||
"rustls",
|
"rustls",
|
||||||
] }
|
] }
|
||||||
sentry-actix = "0.41.0"
|
sentry-actix = "0.42.0"
|
||||||
serde = "1.0.219"
|
serde = "1.0.219"
|
||||||
serde_bytes = "0.11.17"
|
serde_bytes = "0.11.17"
|
||||||
serde_cbor = "0.11.2"
|
serde_cbor = "0.11.2"
|
||||||
serde_ini = "0.2.0"
|
serde_ini = "0.2.0"
|
||||||
serde_json = "1.0.140"
|
serde_json = "1.0.142"
|
||||||
serde_with = "3.13.0"
|
serde_with = "3.14.0"
|
||||||
serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde in favor of this
|
serde-xml-rs = "0.8.1" # Also an XML (de)serializer, consider dropping yaserde in favor of this
|
||||||
sha1 = "0.10.6"
|
sha1 = "0.10.6"
|
||||||
sha1_smol = { version = "1.0.1", features = ["std"] }
|
sha1_smol = { version = "1.0.1", features = ["std"] }
|
||||||
sha2 = "0.10.9"
|
sha2 = "0.10.9"
|
||||||
spdx = "0.10.8"
|
spdx = "0.10.9"
|
||||||
sqlx = { version = "0.8.6", default-features = false }
|
sqlx = { version = "0.8.6", default-features = false }
|
||||||
sysinfo = { version = "0.35.2", default-features = false }
|
sysinfo = { version = "0.36.1", default-features = false }
|
||||||
tar = "0.4.44"
|
tar = "0.4.44"
|
||||||
tauri = "2.6.1"
|
tauri = "2.7.0"
|
||||||
tauri-build = "2.3.0"
|
tauri-build = "2.3.1"
|
||||||
tauri-plugin-deep-link = "2.4.0"
|
tauri-plugin-deep-link = "2.4.1"
|
||||||
tauri-plugin-dialog = "2.3.0"
|
tauri-plugin-dialog = "2.3.2"
|
||||||
tauri-plugin-http = "2.5.0"
|
tauri-plugin-http = "2.5.1"
|
||||||
tauri-plugin-opener = "2.4.0"
|
tauri-plugin-opener = "2.4.0"
|
||||||
tauri-plugin-os = "2.3.0"
|
tauri-plugin-os = "2.3.0"
|
||||||
tauri-plugin-single-instance = "2.3.0"
|
tauri-plugin-single-instance = "2.3.2"
|
||||||
tauri-plugin-updater = { version = "2.9.0", default-features = false, features = [
|
tauri-plugin-updater = { version = "2.9.0", default-features = false, features = [
|
||||||
"rustls-tls",
|
"rustls-tls",
|
||||||
"zip",
|
"zip",
|
||||||
] }
|
] }
|
||||||
tauri-plugin-window-state = "2.3.0"
|
tauri-plugin-window-state = "2.4.0"
|
||||||
tempfile = "3.20.0"
|
tempfile = "3.20.0"
|
||||||
theseus = { path = "packages/app-lib" }
|
theseus = { path = "packages/app-lib" }
|
||||||
thiserror = "2.0.12"
|
thiserror = "2.0.12"
|
||||||
tikv-jemalloc-ctl = "0.6.0"
|
tikv-jemalloc-ctl = "0.6.0"
|
||||||
tikv-jemallocator = "0.6.0"
|
tikv-jemallocator = "0.6.0"
|
||||||
tokio = "1.45.1"
|
tokio = "1.47.1"
|
||||||
tokio-stream = "0.1.17"
|
tokio-stream = "0.1.17"
|
||||||
tokio-util = "0.7.15"
|
tokio-util = "0.7.16"
|
||||||
totp-rs = "5.7.0"
|
totp-rs = "5.7.0"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
tracing-actix-web = "0.7.18"
|
tracing-actix-web = "0.7.19"
|
||||||
tracing-error = "0.2.1"
|
tracing-error = "0.2.1"
|
||||||
tracing-subscriber = "0.3.19"
|
tracing-subscriber = "0.3.19"
|
||||||
url = "2.5.4"
|
url = "2.5.4"
|
||||||
@@ -179,7 +179,7 @@ whoami = "1.6.0"
|
|||||||
winreg = "0.55.0"
|
winreg = "0.55.0"
|
||||||
woothee = "0.13.0"
|
woothee = "0.13.0"
|
||||||
yaserde = "0.12.0"
|
yaserde = "0.12.0"
|
||||||
zip = { version = "4.2.0", default-features = false, features = [
|
zip = { version = "4.3.0", default-features = false, features = [
|
||||||
"bzip2",
|
"bzip2",
|
||||||
"deflate",
|
"deflate",
|
||||||
"deflate64",
|
"deflate64",
|
||||||
@@ -226,7 +226,7 @@ wildcard_dependencies = "warn"
|
|||||||
warnings = "deny"
|
warnings = "deny"
|
||||||
|
|
||||||
[patch.crates-io]
|
[patch.crates-io]
|
||||||
wry = { git = "https://github.com/modrinth/wry", rev = "21db186" }
|
wry = { git = "https://github.com/modrinth/wry", rev = "f2ce0b0" }
|
||||||
|
|
||||||
# Optimize for speed and reduce size on release builds
|
# Optimize for speed and reduce size on release builds
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
+65
-39
@@ -1,41 +1,67 @@
|
|||||||
!macro NSIS_HOOK_POSTINSTALL
|
; https://nsis.sourceforge.io/ShellExecWait
|
||||||
SetShellVarContext current
|
!macro ShellExecWait verb app param workdir show exitoutvar ;only app and show must be != "", every thing else is optional
|
||||||
|
#define SEE_MASK_NOCLOSEPROCESS 0x40
|
||||||
IfFileExists "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe" file_found file_not_found
|
System::Store S
|
||||||
file_found:
|
!if "${NSIS_PTR_SIZE}" > 4
|
||||||
Delete "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
!define /ReDef /math SYSSIZEOF_SHELLEXECUTEINFO 14 * ${NSIS_PTR_SIZE}
|
||||||
|
!else ifndef SYSSIZEOF_SHELLEXECUTEINFO
|
||||||
Delete "$LOCALAPPDATA${PRODUCTNAME}\uninstall.exe"
|
!define SYSSIZEOF_SHELLEXECUTEINFO 60
|
||||||
RMDir "$LOCALAPPDATA${PRODUCTNAME}"
|
!endif
|
||||||
|
System::Call '*(&i${SYSSIZEOF_SHELLEXECUTEINFO})i.r0'
|
||||||
!insertmacro DeleteAppUserModelId
|
System::Call '*$0(i ${SYSSIZEOF_SHELLEXECUTEINFO},i 0x40,p $hwndparent,t "${verb}",t $\'${app}$\',t $\'${param}$\',t "${workdir}",i ${show})p.r0'
|
||||||
|
System::Call 'shell32::ShellExecuteEx(t)(pr0)i.r1 ?e' ; (t) to trigger A/W selection
|
||||||
; Remove start menu shortcut
|
${If} $1 <> 0
|
||||||
!insertmacro MUI_STARTMENU_GETFOLDER Application $AppStartMenuFolder
|
System::Call '*$0(is,i,p,p,p,p,p,p,p,p,p,p,p,p,p.r1)' ;stack value not really used, just a fancy pop ;)
|
||||||
!insertmacro IsShortcutTarget "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
System::Call 'kernel32::WaitForSingleObject(pr1,i-1)'
|
||||||
Pop $0
|
System::Call 'kernel32::GetExitCodeProcess(pr1,*i.s)'
|
||||||
${If} $0 = 1
|
System::Call 'kernel32::CloseHandle(pr1)'
|
||||||
!insertmacro UnpinShortcut "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk"
|
${EndIf}
|
||||||
Delete "$SMPROGRAMS$AppStartMenuFolder${PRODUCTNAME}.lnk"
|
System::Free $0
|
||||||
RMDir "$SMPROGRAMS$AppStartMenuFolder"
|
!if "${exitoutvar}" == ""
|
||||||
|
pop $0
|
||||||
|
!endif
|
||||||
|
System::Store L
|
||||||
|
!if "${exitoutvar}" != ""
|
||||||
|
pop ${exitoutvar}
|
||||||
|
!endif
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
; --------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Var /GLOBAL OldInstallDir
|
||||||
|
|
||||||
|
!macro NSIS_HOOK_PREINSTALL
|
||||||
|
SetShellVarContext all
|
||||||
|
${If} ${FileExists} "$SMPROGRAMS\${PRODUCTNAME}.lnk"
|
||||||
|
UserInfo::GetAccountType
|
||||||
|
Pop $0
|
||||||
|
${If} $0 != "Admin"
|
||||||
|
MessageBox MB_ICONINFORMATION|MB_OK "An old installation of the Modrinth App was detected that requires administrator permission to update from. You will be prompted with an admin prompt shortly."
|
||||||
|
${EndIf}
|
||||||
|
|
||||||
|
ReadRegStr $4 SHCTX "${MANUPRODUCTKEY}" ""
|
||||||
|
ReadRegStr $R1 SHCTX "${UNINSTKEY}" "UninstallString"
|
||||||
|
|
||||||
|
ReadRegStr $OldInstallDir SHCTX "${UNINSTKEY}" "InstallLocation"
|
||||||
|
StrCpy $OldInstallDir $OldInstallDir "" 1
|
||||||
|
StrCpy $OldInstallDir $OldInstallDir -1 ""
|
||||||
|
|
||||||
|
DetailPrint "Executing $R1"
|
||||||
|
!insertmacro ShellExecWait "runas" '$R1' '/P _?=$4' "" ${SW_SHOW} $3
|
||||||
|
${If} $3 <> 0
|
||||||
|
SetErrorLevel $3
|
||||||
|
MessageBox MB_ICONEXCLAMATION|MB_OK "Failed to uninstall old global installation"
|
||||||
|
Abort
|
||||||
|
${EndIf}
|
||||||
|
${EndIf}
|
||||||
|
SetShellVarContext current
|
||||||
|
!macroend
|
||||||
|
|
||||||
|
!macro NSIS_HOOK_POSTINSTALL
|
||||||
|
!insertmacro IsShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$OldInstallDir\${MAINBINARYNAME}.exe"
|
||||||
|
Pop $0
|
||||||
|
${If} $0 = 1
|
||||||
|
!insertmacro SetShortcutTarget "$DESKTOP\${PRODUCTNAME}.lnk" "$INSTDIR\${MAINBINARYNAME}.exe"
|
||||||
|
Return
|
||||||
${EndIf}
|
${EndIf}
|
||||||
!insertmacro IsShortcutTarget "$SMPROGRAMS${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
|
||||||
Pop $0
|
|
||||||
${If} $0 = 1
|
|
||||||
!insertmacro UnpinShortcut "$SMPROGRAMS${PRODUCTNAME}.lnk"
|
|
||||||
Delete "$SMPROGRAMS${PRODUCTNAME}.lnk"
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
!insertmacro IsShortcutTarget "$DESKTOP${PRODUCTNAME}.lnk" "$LOCALAPPDATA${PRODUCTNAME}\theseus_gui.exe"
|
|
||||||
Pop $0
|
|
||||||
${If} $0 = 1
|
|
||||||
!insertmacro UnpinShortcut "$DESKTOP${PRODUCTNAME}.lnk"
|
|
||||||
Delete "$DESKTOP${PRODUCTNAME}.lnk"
|
|
||||||
${EndIf}
|
|
||||||
|
|
||||||
DeleteRegKey HKCU "${UNINSTKEY}"
|
|
||||||
|
|
||||||
goto end_of_test ;<== important for not continuing on the else branch
|
|
||||||
file_not_found:
|
|
||||||
end_of_test:
|
|
||||||
!macroend
|
!macroend
|
||||||
|
|||||||
@@ -122,17 +122,14 @@ pub async fn login<R: Runtime>(
|
|||||||
.url()?
|
.url()?
|
||||||
.as_str()
|
.as_str()
|
||||||
.starts_with("https://login.live.com/oauth20_desktop.srf")
|
.starts_with("https://login.live.com/oauth20_desktop.srf")
|
||||||
{
|
&& let Some((_, code)) =
|
||||||
if let Some((_, code)) =
|
|
||||||
window.url()?.query_pairs().find(|x| x.0 == "code")
|
window.url()?.query_pairs().find(|x| x.0 == "code")
|
||||||
{
|
{
|
||||||
window.close()?;
|
window.close()?;
|
||||||
let val =
|
let val = minecraft_auth::finish_login(&code.clone(), flow).await?;
|
||||||
minecraft_auth::finish_login(&code.clone(), flow).await?;
|
|
||||||
|
|
||||||
return Ok(Some(val));
|
return Ok(Some(val));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
tokio::time::sleep(std::time::Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,12 +103,12 @@ pub async fn should_disable_mouseover() -> bool {
|
|||||||
// We try to match version to 12.2 or higher. If unrecognizable to pattern or lower, we default to the css with disabled mouseover for safety
|
// We try to match version to 12.2 or higher. If unrecognizable to pattern or lower, we default to the css with disabled mouseover for safety
|
||||||
if let tauri_plugin_os::Version::Semantic(major, minor, _) =
|
if let tauri_plugin_os::Version::Semantic(major, minor, _) =
|
||||||
tauri_plugin_os::version()
|
tauri_plugin_os::version()
|
||||||
|
&& major >= 12
|
||||||
|
&& minor >= 3
|
||||||
{
|
{
|
||||||
if major >= 12 && minor >= 3 {
|
|
||||||
// Mac os version is 12.3 or higher, we allow mouseover
|
// Mac os version is 12.3 or higher, we allow mouseover
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
// Not macos, we allow mouseover
|
// Not macos, we allow mouseover
|
||||||
|
|||||||
@@ -243,11 +243,11 @@ fn main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
#[cfg(not(target_os = "linux"))]
|
#[cfg(not(target_os = "linux"))]
|
||||||
if let Some(window) = app.get_window("main") {
|
if let Some(window) = app.get_window("main")
|
||||||
if let Err(e) = window.set_shadow(true) {
|
&& let Err(e) = window.set_shadow(true)
|
||||||
|
{
|
||||||
tracing::warn!("Failed to set window shadow: {e}");
|
tracing::warn!("Failed to set window shadow: {e}");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
"icon": ["icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"],
|
||||||
"windows": {
|
"windows": {
|
||||||
"nsis": {
|
"nsis": {
|
||||||
"installMode": "perMachine",
|
"installMode": "currentUser",
|
||||||
"installerHooks": "./nsis/hooks.nsi"
|
"installerHooks": "./nsis/hooks.nsi"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM rust:1.88.0 AS build
|
FROM rust:1.89.0 AS build
|
||||||
|
|
||||||
WORKDIR /usr/src/daedalus
|
WORKDIR /usr/src/daedalus
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -506,17 +506,16 @@ async fn fetch(
|
|||||||
|
|
||||||
return Ok(lib);
|
return Ok(lib);
|
||||||
}
|
}
|
||||||
} else if let Some(url) = &lib.url {
|
} else if let Some(url) = &lib.url
|
||||||
if !url.is_empty() {
|
&& !url.is_empty()
|
||||||
|
{
|
||||||
insert_mirrored_artifact(
|
insert_mirrored_artifact(
|
||||||
&lib.name,
|
&lib.name,
|
||||||
None,
|
None,
|
||||||
vec![
|
vec![
|
||||||
url.clone(),
|
url.clone(),
|
||||||
"https://libraries.minecraft.net/"
|
"https://libraries.minecraft.net/".to_string(),
|
||||||
.to_string(),
|
"https://maven.creeperhost.net/".to_string(),
|
||||||
"https://maven.creeperhost.net/"
|
|
||||||
.to_string(),
|
|
||||||
maven_url.to_string(),
|
maven_url.to_string(),
|
||||||
],
|
],
|
||||||
false,
|
false,
|
||||||
@@ -527,7 +526,6 @@ async fn fetch(
|
|||||||
|
|
||||||
return Ok(lib);
|
return Ok(lib);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Other libraries are generally available in the "maven" directory of the installer. If they are
|
// Other libraries are generally available in the "maven" directory of the installer. If they are
|
||||||
// not present here, they will be generated by Forge processors.
|
// not present here, they will be generated by Forge processors.
|
||||||
|
|||||||
@@ -93,9 +93,9 @@ async fn main() -> Result<()> {
|
|||||||
.ok()
|
.ok()
|
||||||
.and_then(|x| x.parse::<bool>().ok())
|
.and_then(|x| x.parse::<bool>().ok())
|
||||||
.unwrap_or(false)
|
.unwrap_or(false)
|
||||||
|
&& let Ok(token) = dotenvy::var("CLOUDFLARE_TOKEN")
|
||||||
|
&& let Ok(zone_id) = dotenvy::var("CLOUDFLARE_ZONE_ID")
|
||||||
{
|
{
|
||||||
if let Ok(token) = dotenvy::var("CLOUDFLARE_TOKEN") {
|
|
||||||
if let Ok(zone_id) = dotenvy::var("CLOUDFLARE_ZONE_ID") {
|
|
||||||
let cache_clears = upload_files
|
let cache_clears = upload_files
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| format_url(&x.0))
|
.map(|x| format_url(&x.0))
|
||||||
@@ -130,8 +130,6 @@ async fn main() -> Result<()> {
|
|||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -167,20 +167,18 @@ pub async fn download_file(
|
|||||||
let bytes = x.bytes().await;
|
let bytes = x.bytes().await;
|
||||||
|
|
||||||
if let Ok(bytes) = bytes {
|
if let Ok(bytes) = bytes {
|
||||||
if let Some(sha1) = sha1 {
|
if let Some(sha1) = sha1
|
||||||
if &*sha1_async(bytes.clone()).await? != sha1 {
|
&& &*sha1_async(bytes.clone()).await? != sha1
|
||||||
|
{
|
||||||
if attempt <= 3 {
|
if attempt <= 3 {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return Err(
|
return Err(crate::ErrorKind::ChecksumFailure {
|
||||||
crate::ErrorKind::ChecksumFailure {
|
|
||||||
hash: sha1.to_string(),
|
hash: sha1.to_string(),
|
||||||
url: url.to_string(),
|
url: url.to_string(),
|
||||||
tries: attempt,
|
tries: attempt,
|
||||||
}
|
}
|
||||||
.into(),
|
.into());
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,59 +3,43 @@ title: Labrinth (API)
|
|||||||
description: Guide for contributing to Modrinth's backend
|
description: Guide for contributing to Modrinth's backend
|
||||||
---
|
---
|
||||||
|
|
||||||
This project is part of our [monorepo](https://github.com/modrinth/code). You can find it in the `apps/labrinth` directory.
|
This project is part of our [monorepo](https://github.com/modrinth/code). You can find it in the `apps/labrinth` directory. The instructions below assume that you have switched your working directory to the `apps/labrinth` subdirectory.
|
||||||
|
|
||||||
[labrinth] is the Rust-based backend serving Modrinth's API with the help of the [Actix](https://actix.rs) framework. To get started with a labrinth instance, install docker, docker-compose (which comes with Docker), and [Rust]. The initial startup can be done simply with the command `docker-compose up`, or with `docker compose up` (Compose V2 and later). That will deploy a PostgreSQL database on port 5432 and a MeiliSearch instance on port 7700. To run the API itself, you'll need to use the `cargo run` command, this will deploy the API on port 8000.
|
[labrinth] is the Rust-based backend serving Modrinth's API with the help of the [Actix](https://actix.rs) framework. To get started with a labrinth instance, install docker, docker-compose (which comes with Docker), and [Rust]. The initial startup can be done simply with the command `docker-compose up`, or with `docker compose up` (Compose V2 and later). That will deploy a PostgreSQL database on port 5432, a MeiliSearch instance on port 7700, and a [Mailpit](https://mailpit.axllent.org/) SMTP server on port 1025, with a web UI to inspect sent emails on port 8025. To run the API itself, you'll need to use the `cargo run` command, this will deploy the API on port 8000.
|
||||||
|
|
||||||
To get a basic configuration, copy the `.env.local` file to `.env`. Now, you'll have to install the sqlx CLI, which can be done with cargo:
|
To get a basic configuration, copy the `.env.local` file to `.env`. Now, you'll have to install the sqlx CLI, which can be done with cargo:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
cargo install --git https://github.com/launchbadge/sqlx sqlx-cli --no-default-features --features postgres,rustls
|
cargo install sqlx-cli --no-default-features --features mysql,sqlite,postgres,rustls,completions
|
||||||
```
|
```
|
||||||
|
|
||||||
From there, you can create the database and perform all database migrations with one simple command:
|
From there, you can create the database and set up its schema with one simple command:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
sqlx database setup
|
cargo sqlx database setup
|
||||||
```
|
```
|
||||||
|
|
||||||
To enable labrinth to create a project, you need to add two things.
|
To enable labrinth to create projects and serve useful metadata to the frontend build scripts, you'll need to seed the database with several key entities:
|
||||||
|
|
||||||
1. An entry in the `loaders` table.
|
1. Categories, in the `categories` table.
|
||||||
2. An entry in the `loaders_project_types` table.
|
2. Loaders and their fields, in the `loaders`, `loader_fields`, `loader_field_enums`, `loader_field_enum_values`, and `loader_fields_loaders` tables.
|
||||||
|
3. Project types and their allowed loaders and games, in the `project_types`, `loaders_project_types`, and `loaders_project_types_games` tables.
|
||||||
|
4. Optionally, to moderate projects from the frontend, an admin user, in the `users` table.
|
||||||
|
|
||||||
A minimal setup can be done from the command line with [psql](https://www.postgresql.org/docs/current/app-psql.html):
|
The most convenient way to do this seeding is with the [psql](https://www.postgresql.org/docs/current/app-psql.html) command line tool and the pre-existing seed data fixture. This fixture was generated by dumping the official staging environment database at a specific point in time, and defines an admin user with email `admin@modrinth.invalid` and password `admin`:
|
||||||
|
|
||||||
```bash
|
```sh
|
||||||
psql --host=localhost --port=5432 -U <username, default is labrinth> -W
|
source .env
|
||||||
|
psql "$DATABASE_URL" < fixtures/labrinth-seed-data-202508052143.sql
|
||||||
```
|
```
|
||||||
|
|
||||||
The default password for the database is `labrinth`. Once you've connected, run
|
You can find more example SQL statements for seeding the database in the `tests/files/dummy_data.sql` file.
|
||||||
|
|
||||||
```sql
|
|
||||||
INSERT INTO loaders VALUES (0, 'placeholder_loader');
|
|
||||||
INSERT INTO loaders_project_types VALUES (0, 1); -- modloader id, supported type id
|
|
||||||
INSERT INTO categories VALUES (0, 'placeholder_category', 1); -- category id, category, project type id
|
|
||||||
```
|
|
||||||
|
|
||||||
This will initialize your database with a modloader called 'placeholder_loader', with id 0, and marked as supporting mods only. It will also create a category called 'placeholder_category' that is marked as supporting mods only
|
|
||||||
If you would like 'placeholder_loader' to be marked as supporting modpacks too, run
|
|
||||||
|
|
||||||
```sql
|
|
||||||
INSERT INTO loaders_project_types VALUES (0, 2); -- modloader id, supported type id
|
|
||||||
```
|
|
||||||
|
|
||||||
If you would like 'placeholder_category' to be marked as supporting modpacks too, run
|
|
||||||
|
|
||||||
```sql
|
|
||||||
INSERT INTO categories VALUES (0, 'placeholder_category', 2); -- modloader id, supported type id
|
|
||||||
```
|
|
||||||
|
|
||||||
You can find more example SQL statements for seeding the database in the `apps/labrinth/tests/files/dummy_data.sql` file.
|
|
||||||
|
|
||||||
The majority of configuration is done at runtime using [dotenvy](https://crates.io/crates/dotenvy) and the `.env` file. Each of the variables and what they do can be found in the dropdown below. Additionally, there are three command line options that can be used to specify to MeiliSearch what you want to do.
|
The majority of configuration is done at runtime using [dotenvy](https://crates.io/crates/dotenvy) and the `.env` file. Each of the variables and what they do can be found in the dropdown below. Additionally, there are three command line options that can be used to specify to MeiliSearch what you want to do.
|
||||||
|
|
||||||
During development, you might notice that changes made directly to entities in the PostgreSQL database do not seem to take effect. This is often because the Redis cache still holds outdated data. To ensure your updates are reflected, clear the cache by e.g. running `redis-cli FLUSHALL`, which will force Labrinth to fetch the latest data from the database the next time it is needed.
|
During development, you might notice that changes made directly to entities in the PostgreSQL database do not seem to take effect. This is often because the Redis cache still holds outdated data. To ensure your updates are reflected, clear the cache by e.g. running `redis-cli FLUSHALL`, which will force labrinth to fetch the latest data from the database the next time it is needed.
|
||||||
|
|
||||||
|
You can also start labrinth and its backing services at once using `docker compose --profile with-labrinth up`, which will build and start labrinth through its Docker image as if it was yet another service container. To have that container be automatically rebuilt during development as changes to the source code are made, add the `--watch` flag, which enables [Compose Watch](https://docs.docker.com/compose/how-tos/file-watch/). Keep in mind, however, that Compose Watch is bound to be slower than other similar solutions that work outside of a container, particularly on Windows or macOS, where Docker runs in a virtual machine.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>.env variables & command line options</summary>
|
<summary>.env variables & command line options</summary>
|
||||||
@@ -68,7 +52,7 @@ During development, you might notice that changes made directly to entities in t
|
|||||||
`CDN_URL`: The publicly accessible base URL for files uploaded to the CDN
|
`CDN_URL`: The publicly accessible base URL for files uploaded to the CDN
|
||||||
`MODERATION_DISCORD_WEBHOOK`: The URL for a Discord webhook where projects pending approval will be sent
|
`MODERATION_DISCORD_WEBHOOK`: The URL for a Discord webhook where projects pending approval will be sent
|
||||||
`CLOUDFLARE_INTEGRATION`: Whether labrinth should integrate with Cloudflare's spam protection
|
`CLOUDFLARE_INTEGRATION`: Whether labrinth should integrate with Cloudflare's spam protection
|
||||||
`DATABASE_URL`: The URL for the PostgreSQL database
|
`DATABASE_URL`: The URL for the PostgreSQL database, including its username, password, host, port, and database name
|
||||||
`DATABASE_MIN_CONNECTIONS`: The minimum number of concurrent connections allowed to the database at the same time
|
`DATABASE_MIN_CONNECTIONS`: The minimum number of concurrent connections allowed to the database at the same time
|
||||||
`DATABASE_MAX_CONNECTIONS`: The maximum number of concurrent connections allowed to the database at the same time
|
`DATABASE_MAX_CONNECTIONS`: The maximum number of concurrent connections allowed to the database at the same time
|
||||||
`MEILISEARCH_ADDR`: The URL for the MeiliSearch instance used for search
|
`MEILISEARCH_ADDR`: The URL for the MeiliSearch instance used for search
|
||||||
@@ -109,14 +93,13 @@ The OAuth configuration options are fairly self-explanatory. For help setting up
|
|||||||
|
|
||||||
If you're prepared to contribute by submitting a pull request, ensure you have met the following criteria:
|
If you're prepared to contribute by submitting a pull request, ensure you have met the following criteria:
|
||||||
|
|
||||||
- `cargo fmt` has been run.
|
- `cargo fmt --all` has been run.
|
||||||
- `cargo clippy` has been run.
|
- `cargo clippy --all-targets` has been run.
|
||||||
- `cargo check` has been run.
|
|
||||||
- `cargo sqlx prepare` has been run.
|
- `cargo sqlx prepare` has been run.
|
||||||
|
|
||||||
> Note: If you encounter issues with `sqlx` saying 'no queries found' after running `cargo sqlx prepare`, you may need to ensure the installed version of `sqlx-cli` matches the current version of `sqlx` used [in labrinth](https://github.com/modrinth/labrinth/blob/master/Cargo.toml).
|
> Note: If you encounter issues with `sqlx` saying 'no queries found' after running `cargo sqlx prepare`, you may need to ensure the installed version of `sqlx-cli` matches the current version of `sqlx` used [in labrinth](https://github.com/modrinth/labrinth/blob/master/Cargo.toml).
|
||||||
|
|
||||||
[Discord]: https://discord.modrinth.com
|
[Discord]: https://discord.modrinth.com
|
||||||
[GitHub]: https://github.com/modrinth
|
[GitHub]: https://github.com/modrinth
|
||||||
[labrinth]: https://github.com/modrinth/labrinth
|
[labrinth]: https://github.com/modrinth/code/tree/main/apps/labrinth
|
||||||
[Rust]: https://www.rust-lang.org/tools/install
|
[Rust]: https://www.rust-lang.org/tools/install
|
||||||
|
|||||||
@@ -50,13 +50,35 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else-if="generatedMessage">
|
<div v-else-if="generatedMessage">
|
||||||
<div>
|
<div>
|
||||||
|
<ButtonStyled>
|
||||||
|
<button class="mb-2" @click="useSimpleEditor = !useSimpleEditor">
|
||||||
|
<template v-if="!useSimpleEditor">
|
||||||
|
<ToggleLeftIcon aria-hidden="true" />
|
||||||
|
Use simple mode
|
||||||
|
</template>
|
||||||
|
<template v-else>
|
||||||
|
<ToggleRightIcon aria-hidden="true" />
|
||||||
|
Use advanced mode
|
||||||
|
</template>
|
||||||
|
</button>
|
||||||
|
</ButtonStyled>
|
||||||
<MarkdownEditor
|
<MarkdownEditor
|
||||||
|
v-if="!useSimpleEditor"
|
||||||
v-model="message"
|
v-model="message"
|
||||||
:max-height="400"
|
:max-height="400"
|
||||||
placeholder="No message generated."
|
placeholder="No message generated."
|
||||||
:disabled="false"
|
:disabled="false"
|
||||||
:heading-buttons="false"
|
:heading-buttons="false"
|
||||||
/>
|
/>
|
||||||
|
<textarea
|
||||||
|
v-else
|
||||||
|
v-model="message"
|
||||||
|
type="text"
|
||||||
|
class="bg-bg-input h-[400px] w-full rounded-lg border border-solid border-divider px-3 py-2 font-mono text-base"
|
||||||
|
placeholder="No message generated."
|
||||||
|
autocomplete="off"
|
||||||
|
@input="persistState"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="isModpackPermissionsStage">
|
<div v-else-if="isModpackPermissionsStage">
|
||||||
@@ -324,6 +346,8 @@ import {
|
|||||||
CheckIcon,
|
CheckIcon,
|
||||||
KeyboardIcon,
|
KeyboardIcon,
|
||||||
EyeOffIcon,
|
EyeOffIcon,
|
||||||
|
ToggleLeftIcon,
|
||||||
|
ToggleRightIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import {
|
import {
|
||||||
checklist,
|
checklist,
|
||||||
@@ -368,7 +392,6 @@ import {
|
|||||||
type Stage,
|
type Stage,
|
||||||
finalPermissionMessages,
|
finalPermissionMessages,
|
||||||
} from "@modrinth/moderation";
|
} from "@modrinth/moderation";
|
||||||
import * as prettier from "prettier";
|
|
||||||
import ModpackPermissionsFlow from "./ModpackPermissionsFlow.vue";
|
import ModpackPermissionsFlow from "./ModpackPermissionsFlow.vue";
|
||||||
import KeybindsModal from "./ChecklistKeybindsModal.vue";
|
import KeybindsModal from "./ChecklistKeybindsModal.vue";
|
||||||
import { useModerationStore } from "~/store/moderation.ts";
|
import { useModerationStore } from "~/store/moderation.ts";
|
||||||
@@ -392,6 +415,7 @@ const isModpackPermissionsStage = computed(() => {
|
|||||||
return currentStageObj.value.id === "modpack-permissions";
|
return currentStageObj.value.id === "modpack-permissions";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const useSimpleEditor = ref(false);
|
||||||
const message = ref("");
|
const message = ref("");
|
||||||
const generatedMessage = ref(false);
|
const generatedMessage = ref(false);
|
||||||
const loadingMessage = ref(false);
|
const loadingMessage = ref(false);
|
||||||
@@ -1118,19 +1142,7 @@ async function generateMessage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
|
||||||
const formattedMessage = await prettier.format(fullMessage, {
|
|
||||||
parser: "markdown",
|
|
||||||
printWidth: 80,
|
|
||||||
proseWrap: "always",
|
|
||||||
tabWidth: 2,
|
|
||||||
useTabs: false,
|
|
||||||
});
|
|
||||||
message.value = formattedMessage;
|
|
||||||
} catch (formattingError) {
|
|
||||||
console.warn("Failed to format markdown, using original:", formattingError);
|
|
||||||
message.value = fullMessage;
|
message.value = fullMessage;
|
||||||
}
|
|
||||||
|
|
||||||
generatedMessage.value = true;
|
generatedMessage.value = true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -100,7 +100,6 @@ import {
|
|||||||
ScaleIcon,
|
ScaleIcon,
|
||||||
} from "@modrinth/assets";
|
} from "@modrinth/assets";
|
||||||
import { defineMessages, useVIntl } from "@vintl/vintl";
|
import { defineMessages, useVIntl } from "@vintl/vintl";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
|
||||||
import ConfettiExplosion from "vue-confetti-explosion";
|
import ConfettiExplosion from "vue-confetti-explosion";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import ModerationQueueCard from "~/components/ui/moderation/ModerationQueueCard.vue";
|
import ModerationQueueCard from "~/components/ui/moderation/ModerationQueueCard.vue";
|
||||||
@@ -215,7 +214,7 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentFilterType = useLocalStorage("moderation-current-filter-type", () => "All projects");
|
const currentFilterType = ref("All projects");
|
||||||
const filterTypes: readonly string[] = readonly([
|
const filterTypes: readonly string[] = readonly([
|
||||||
"All projects",
|
"All projects",
|
||||||
"Modpacks",
|
"Modpacks",
|
||||||
@@ -226,7 +225,7 @@ const filterTypes: readonly string[] = readonly([
|
|||||||
"Shaders",
|
"Shaders",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const currentSortType = useLocalStorage("moderation-current-sort-type", () => "Oldest");
|
const currentSortType = ref("Oldest");
|
||||||
const sortTypes: readonly string[] = readonly(["Oldest", "Newest"]);
|
const sortTypes: readonly string[] = readonly(["Oldest", "Newest"]);
|
||||||
|
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
@@ -287,8 +286,10 @@ const typeFiltered = computed(() => {
|
|||||||
const projectType = filterMap[currentFilterType.value];
|
const projectType = filterMap[currentFilterType.value];
|
||||||
if (!projectType) return baseFiltered.value;
|
if (!projectType) return baseFiltered.value;
|
||||||
|
|
||||||
return baseFiltered.value.filter((queueItem) =>
|
return baseFiltered.value.filter(
|
||||||
queueItem.project.project_types.includes(projectType),
|
(queueItem) =>
|
||||||
|
queueItem.project.project_types.length > 0 &&
|
||||||
|
queueItem.project.project_types[0] === projectType,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,6 @@
|
|||||||
import { DropdownSelect, Button, Pagination } from "@modrinth/ui";
|
import { DropdownSelect, Button, Pagination } from "@modrinth/ui";
|
||||||
import { XIcon, SearchIcon, SortAscIcon, SortDescIcon, FilterIcon } from "@modrinth/assets";
|
import { XIcon, SearchIcon, SortAscIcon, SortDescIcon, FilterIcon } from "@modrinth/assets";
|
||||||
import { defineMessages, useVIntl } from "@vintl/vintl";
|
import { defineMessages, useVIntl } from "@vintl/vintl";
|
||||||
import { useLocalStorage } from "@vueuse/core";
|
|
||||||
import type { Report } from "@modrinth/utils";
|
import type { Report } from "@modrinth/utils";
|
||||||
import Fuse from "fuse.js";
|
import Fuse from "fuse.js";
|
||||||
import type { ExtendedReport } from "@modrinth/moderation";
|
import type { ExtendedReport } from "@modrinth/moderation";
|
||||||
@@ -170,10 +169,10 @@ watch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentFilterType = useLocalStorage("moderation-reports-filter-type", () => "All");
|
const currentFilterType = ref("All");
|
||||||
const filterTypes: readonly string[] = readonly(["All", "Unread", "Read"]);
|
const filterTypes: readonly string[] = readonly(["All", "Unread", "Read"]);
|
||||||
|
|
||||||
const currentSortType = useLocalStorage("moderation-reports-sort-type", () => "Oldest");
|
const currentSortType = ref("Oldest");
|
||||||
const sortTypes: readonly string[] = readonly(["Oldest", "Newest"]);
|
const sortTypes: readonly string[] = readonly(["Oldest", "Newest"]);
|
||||||
|
|
||||||
const currentPage = ref(1);
|
const currentPage = ref(1);
|
||||||
|
|||||||
@@ -0,0 +1,129 @@
|
|||||||
|
DEBUG=true
|
||||||
|
RUST_LOG=info,sqlx::query=warn
|
||||||
|
SENTRY_DSN=none
|
||||||
|
|
||||||
|
SITE_URL=http://localhost:3000
|
||||||
|
# This CDN URL matches the local storage backend set below, which uses MOCK_FILE_PATH
|
||||||
|
CDN_URL=file:///tmp/modrinth
|
||||||
|
LABRINTH_ADMIN_KEY=feedbeef
|
||||||
|
RATE_LIMIT_IGNORE_KEY=feedbeef
|
||||||
|
|
||||||
|
DATABASE_URL=postgresql://labrinth:labrinth@labrinth-postgres/labrinth
|
||||||
|
DATABASE_MIN_CONNECTIONS=0
|
||||||
|
DATABASE_MAX_CONNECTIONS=16
|
||||||
|
|
||||||
|
MEILISEARCH_ADDR=http://labrinth-meilisearch:7700
|
||||||
|
MEILISEARCH_KEY=modrinth
|
||||||
|
|
||||||
|
REDIS_URL=redis://labrinth-redis
|
||||||
|
REDIS_MAX_CONNECTIONS=10000
|
||||||
|
|
||||||
|
BIND_ADDR=0.0.0.0:8000
|
||||||
|
SELF_ADDR=http://labrinth:8000
|
||||||
|
|
||||||
|
MODERATION_SLACK_WEBHOOK=
|
||||||
|
PUBLIC_DISCORD_WEBHOOK=
|
||||||
|
CLOUDFLARE_INTEGRATION=false
|
||||||
|
|
||||||
|
STORAGE_BACKEND=local
|
||||||
|
MOCK_FILE_PATH=/tmp/modrinth
|
||||||
|
|
||||||
|
S3_PUBLIC_BUCKET_NAME=none
|
||||||
|
S3_PUBLIC_USES_PATH_STYLE_BUCKET=false
|
||||||
|
S3_PUBLIC_REGION=none
|
||||||
|
S3_PUBLIC_URL=none
|
||||||
|
S3_PUBLIC_ACCESS_TOKEN=none
|
||||||
|
S3_PUBLIC_SECRET=none
|
||||||
|
|
||||||
|
S3_PRIVATE_BUCKET_NAME=none
|
||||||
|
S3_PRIVATE_USES_PATH_STYLE_BUCKET=false
|
||||||
|
S3_PRIVATE_REGION=none
|
||||||
|
S3_PRIVATE_URL=none
|
||||||
|
S3_PRIVATE_ACCESS_TOKEN=none
|
||||||
|
S3_PRIVATE_SECRET=none
|
||||||
|
|
||||||
|
# 1 hour
|
||||||
|
LOCAL_INDEX_INTERVAL=3600
|
||||||
|
# 30 minutes
|
||||||
|
VERSION_INDEX_INTERVAL=1800
|
||||||
|
|
||||||
|
RATE_LIMIT_IGNORE_IPS='["127.0.0.1"]'
|
||||||
|
|
||||||
|
WHITELISTED_MODPACK_DOMAINS='["cdn.modrinth.com", "github.com", "raw.githubusercontent.com"]'
|
||||||
|
|
||||||
|
ALLOWED_CALLBACK_URLS='["localhost", ".modrinth.com", "127.0.0.1"]'
|
||||||
|
|
||||||
|
GITHUB_CLIENT_ID=none
|
||||||
|
GITHUB_CLIENT_SECRET=none
|
||||||
|
|
||||||
|
GITLAB_CLIENT_ID=none
|
||||||
|
GITLAB_CLIENT_SECRET=none
|
||||||
|
|
||||||
|
DISCORD_CLIENT_ID=none
|
||||||
|
DISCORD_CLIENT_SECRET=none
|
||||||
|
|
||||||
|
MICROSOFT_CLIENT_ID=none
|
||||||
|
MICROSOFT_CLIENT_SECRET=none
|
||||||
|
|
||||||
|
GOOGLE_CLIENT_ID=none
|
||||||
|
GOOGLE_CLIENT_SECRET=none
|
||||||
|
|
||||||
|
PAYPAL_API_URL=https://api-m.sandbox.paypal.com/v1/
|
||||||
|
PAYPAL_WEBHOOK_ID=none
|
||||||
|
PAYPAL_CLIENT_ID=none
|
||||||
|
PAYPAL_CLIENT_SECRET=none
|
||||||
|
PAYPAL_NVP_USERNAME=none
|
||||||
|
PAYPAL_NVP_PASSWORD=none
|
||||||
|
PAYPAL_NVP_SIGNATURE=none
|
||||||
|
|
||||||
|
STEAM_API_KEY=none
|
||||||
|
|
||||||
|
TREMENDOUS_API_URL=https://testflight.tremendous.com/api/v2/
|
||||||
|
TREMENDOUS_API_KEY=none
|
||||||
|
TREMENDOUS_PRIVATE_KEY=none
|
||||||
|
TREMENDOUS_CAMPAIGN_ID=none
|
||||||
|
|
||||||
|
HCAPTCHA_SECRET=none
|
||||||
|
|
||||||
|
SMTP_FROM_NAME=Modrinth
|
||||||
|
SMTP_FROM_ADDRESS=no-reply@mail.modrinth.com
|
||||||
|
SMTP_USERNAME=
|
||||||
|
SMTP_PASSWORD=
|
||||||
|
SMTP_HOST=labrinth-mail
|
||||||
|
SMTP_PORT=1025
|
||||||
|
SMTP_TLS=none
|
||||||
|
|
||||||
|
SITE_VERIFY_EMAIL_PATH=auth/verify-email
|
||||||
|
SITE_RESET_PASSWORD_PATH=auth/reset-password
|
||||||
|
SITE_BILLING_PATH=none
|
||||||
|
|
||||||
|
SENDY_URL=none
|
||||||
|
SENDY_LIST_ID=none
|
||||||
|
SENDY_API_KEY=none
|
||||||
|
|
||||||
|
ANALYTICS_ALLOWED_ORIGINS='["http://127.0.0.1:3000", "http://localhost:3000", "https://modrinth.com", "https://www.modrinth.com", "*"]'
|
||||||
|
|
||||||
|
CLICKHOUSE_REPLICATED=false
|
||||||
|
CLICKHOUSE_URL=http://labrinth-clickhouse:8123
|
||||||
|
CLICKHOUSE_USER=default
|
||||||
|
CLICKHOUSE_PASSWORD=default
|
||||||
|
CLICKHOUSE_DATABASE=staging_ariadne
|
||||||
|
|
||||||
|
MAXMIND_LICENSE_KEY=none
|
||||||
|
|
||||||
|
FLAME_ANVIL_URL=none
|
||||||
|
|
||||||
|
STRIPE_API_KEY=none
|
||||||
|
STRIPE_WEBHOOK_SECRET=none
|
||||||
|
|
||||||
|
ADITUDE_API_KEY=none
|
||||||
|
|
||||||
|
PYRO_API_KEY=none
|
||||||
|
|
||||||
|
BREX_API_URL=https://platform.brexapis.com/v2/
|
||||||
|
BREX_API_KEY=none
|
||||||
|
|
||||||
|
DELPHI_URL=none
|
||||||
|
DELPHI_SLACK_WEBHOOK=none
|
||||||
|
|
||||||
|
ARCHON_URL=none
|
||||||
@@ -87,11 +87,11 @@ HCAPTCHA_SECRET=none
|
|||||||
|
|
||||||
SMTP_FROM_NAME=Modrinth
|
SMTP_FROM_NAME=Modrinth
|
||||||
SMTP_FROM_ADDRESS=no-reply@mail.modrinth.com
|
SMTP_FROM_ADDRESS=no-reply@mail.modrinth.com
|
||||||
SMTP_USERNAME=none
|
SMTP_USERNAME=
|
||||||
SMTP_PASSWORD=none
|
SMTP_PASSWORD=
|
||||||
SMTP_HOST=none
|
SMTP_HOST=localhost
|
||||||
SMTP_PORT=465
|
SMTP_PORT=1025
|
||||||
SMTP_TLS=tls
|
SMTP_TLS=none
|
||||||
|
|
||||||
SITE_VERIFY_EMAIL_PATH=auth/verify-email
|
SITE_VERIFY_EMAIL_PATH=auth/verify-email
|
||||||
SITE_RESET_PASSWORD_PATH=auth/reset-password
|
SITE_RESET_PASSWORD_PATH=auth/reset-password
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# syntax=docker/dockerfile:1
|
# syntax=docker/dockerfile:1
|
||||||
|
|
||||||
FROM rust:1.88.0 AS build
|
FROM rust:1.89.0 AS build
|
||||||
|
|
||||||
WORKDIR /usr/src/labrinth
|
WORKDIR /usr/src/labrinth
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ fn main() {
|
|||||||
let git_hash = String::from_utf8(output.stdout)
|
let git_hash = String::from_utf8(output.stdout)
|
||||||
.expect("valid UTF-8 output from `git` invocation");
|
.expect("valid UTF-8 output from `git` invocation");
|
||||||
|
|
||||||
println!("cargo::rerun-if-changed=.git/HEAD");
|
println!("cargo::rerun-if-changed=../../.git/HEAD");
|
||||||
println!("cargo::rustc-env=GIT_HASH={}", git_hash.trim());
|
println!("cargo::rustc-env=GIT_HASH={}", git_hash.trim());
|
||||||
|
|
||||||
let timedate_fmt = Local::now().format("%F @ %I:%M %p");
|
let timedate_fmt = Local::now().format("%F @ %I:%M %p");
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -5,7 +5,7 @@
|
|||||||
"lint": "cargo fmt --check && cargo clippy --all-targets",
|
"lint": "cargo fmt --check && cargo clippy --all-targets",
|
||||||
"fix": "cargo clippy --all-targets --fix --allow-dirty && cargo fmt",
|
"fix": "cargo clippy --all-targets --fix --allow-dirty && cargo fmt",
|
||||||
"dev": "cargo run",
|
"dev": "cargo run",
|
||||||
"//": "Labrinth integration tests require a lot of disk space, so in the standard GitHub Actions",
|
"//": "labrinth integration tests require a lot of disk space, so in the standard GitHub Actions",
|
||||||
"//": "runners we must remove useless development tools from the base image, which frees up ~20 GiB.",
|
"//": "runners we must remove useless development tools from the base image, which frees up ~20 GiB.",
|
||||||
"//": "The command commented out below can be used in CI to debug what is taking up space:",
|
"//": "The command commented out below can be used in CI to debug what is taking up space:",
|
||||||
"//": "sudo du -xh --max-depth=4 / | sort -rh | curl -X POST --data-urlencode content@/dev/fd/0 https://api.mclo.gs/1/log",
|
"//": "sudo du -xh --max-depth=4 / | sort -rh | curl -X POST --data-urlencode content@/dev/fd/0 https://api.mclo.gs/1/log",
|
||||||
|
|||||||
@@ -322,13 +322,12 @@ pub async fn is_visible_collection(
|
|||||||
} else {
|
} else {
|
||||||
!collection_data.status.is_hidden()
|
!collection_data.status.is_hidden()
|
||||||
}) && !collection_data.projects.is_empty();
|
}) && !collection_data.projects.is_empty();
|
||||||
if let Some(user) = &user_option {
|
if let Some(user) = &user_option
|
||||||
if !authorized
|
&& !authorized
|
||||||
&& (user.role.is_mod() || user.id == collection_data.user_id.into())
|
&& (user.role.is_mod() || user.id == collection_data.user_id.into())
|
||||||
{
|
{
|
||||||
authorized = true;
|
authorized = true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(authorized)
|
Ok(authorized)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,12 +355,12 @@ pub async fn filter_visible_collections(
|
|||||||
|
|
||||||
for collection in check_collections {
|
for collection in check_collections {
|
||||||
// Collections are simple- if we are the owner or a mod, we can see it
|
// Collections are simple- if we are the owner or a mod, we can see it
|
||||||
if let Some(user) = user_option {
|
if let Some(user) = user_option
|
||||||
if user.role.is_mod() || user.id == collection.user_id.into() {
|
&& (user.role.is_mod() || user.id == collection.user_id.into())
|
||||||
|
{
|
||||||
return_collections.push(collection.into());
|
return_collections.push(collection.into());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(return_collections)
|
Ok(return_collections)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,8 @@ pub fn send_email_raw(
|
|||||||
let password = dotenvy::var("SMTP_PASSWORD")?;
|
let password = dotenvy::var("SMTP_PASSWORD")?;
|
||||||
let host = dotenvy::var("SMTP_HOST")?;
|
let host = dotenvy::var("SMTP_HOST")?;
|
||||||
let port = dotenvy::var("SMTP_PORT")?.parse::<u16>().unwrap_or(465);
|
let port = dotenvy::var("SMTP_PORT")?.parse::<u16>().unwrap_or(465);
|
||||||
let creds = Credentials::new(username, password);
|
let creds =
|
||||||
|
(!username.is_empty()).then(|| Credentials::new(username, password));
|
||||||
let tls_setting = match dotenvy::var("SMTP_TLS")?.as_str() {
|
let tls_setting = match dotenvy::var("SMTP_TLS")?.as_str() {
|
||||||
"none" => Tls::None,
|
"none" => Tls::None,
|
||||||
"opportunistic_start_tls" => {
|
"opportunistic_start_tls" => {
|
||||||
@@ -55,13 +56,12 @@ pub fn send_email_raw(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let mailer = SmtpTransport::relay(&host)?
|
let mut mailer = SmtpTransport::relay(&host)?.port(port).tls(tls_setting);
|
||||||
.port(port)
|
if let Some(creds) = creds {
|
||||||
.tls(tls_setting)
|
mailer = mailer.credentials(creds);
|
||||||
.credentials(creds)
|
}
|
||||||
.build();
|
|
||||||
|
|
||||||
mailer.send(&email)?;
|
mailer.build().send(&email)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,11 +95,11 @@ impl DBFlow {
|
|||||||
redis: &RedisPool,
|
redis: &RedisPool,
|
||||||
) -> Result<Option<DBFlow>, DatabaseError> {
|
) -> Result<Option<DBFlow>, DatabaseError> {
|
||||||
let flow = Self::get(id, redis).await?;
|
let flow = Self::get(id, redis).await?;
|
||||||
if let Some(flow) = flow.as_ref() {
|
if let Some(flow) = flow.as_ref()
|
||||||
if predicate(flow) {
|
&& predicate(flow)
|
||||||
|
{
|
||||||
Self::remove(id, redis).await?;
|
Self::remove(id, redis).await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Ok(flow)
|
Ok(flow)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -801,18 +801,19 @@ impl VersionField {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if let Some(count) = countable {
|
if let Some(count) = countable {
|
||||||
if let Some(min) = loader_field.min_val {
|
if let Some(min) = loader_field.min_val
|
||||||
if count < min {
|
&& count < min
|
||||||
|
{
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Provided value '{v}' for {field_name} is less than the minimum of {min}",
|
"Provided value '{v}' for {field_name} is less than the minimum of {min}",
|
||||||
v = serde_json::to_string(&value).unwrap_or_default(),
|
v = serde_json::to_string(&value).unwrap_or_default(),
|
||||||
field_name = loader_field.field,
|
field_name = loader_field.field,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(max) = loader_field.max_val {
|
if let Some(max) = loader_field.max_val
|
||||||
if count > max {
|
&& count > max
|
||||||
|
{
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Provided value '{v}' for {field_name} is greater than the maximum of {max}",
|
"Provided value '{v}' for {field_name} is greater than the maximum of {max}",
|
||||||
v = serde_json::to_string(&value).unwrap_or_default(),
|
v = serde_json::to_string(&value).unwrap_or_default(),
|
||||||
@@ -820,7 +821,6 @@ impl VersionField {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(VersionField {
|
Ok(VersionField {
|
||||||
version_id,
|
version_id,
|
||||||
|
|||||||
@@ -483,8 +483,9 @@ impl DBTeamMember {
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(accepted) = new_accepted {
|
if let Some(accepted) = new_accepted
|
||||||
if accepted {
|
&& accepted
|
||||||
|
{
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
"
|
"
|
||||||
UPDATE team_members
|
UPDATE team_members
|
||||||
@@ -497,7 +498,6 @@ impl DBTeamMember {
|
|||||||
.execute(&mut **transaction)
|
.execute(&mut **transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(payouts_split) = new_payouts_split {
|
if let Some(payouts_split) = new_payouts_split {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
|
|||||||
@@ -353,11 +353,11 @@ impl RedisPool {
|
|||||||
};
|
};
|
||||||
|
|
||||||
for (idx, key) in fetch_ids.into_iter().enumerate() {
|
for (idx, key) in fetch_ids.into_iter().enumerate() {
|
||||||
if let Some(locked) = results.get(idx) {
|
if let Some(locked) = results.get(idx)
|
||||||
if locked.is_none() {
|
&& locked.is_none()
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some((key, raw_key)) = ids.remove(&key) {
|
if let Some((key, raw_key)) = ids.remove(&key) {
|
||||||
if let Some(val) = expired_values.remove(&key) {
|
if let Some(val) = expired_values.remove(&key) {
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ pub fn app_setup(
|
|||||||
enable_background_tasks: bool,
|
enable_background_tasks: bool,
|
||||||
) -> LabrinthConfig {
|
) -> LabrinthConfig {
|
||||||
info!(
|
info!(
|
||||||
"Starting Labrinth on {}",
|
"Starting labrinth on {}",
|
||||||
dotenvy::var("BIND_ADDR").unwrap()
|
dotenvy::var("BIND_ADDR").unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ async fn main() -> std::io::Result<()> {
|
|||||||
|
|
||||||
if args.run_background_task.is_none() {
|
if args.run_background_task.is_none() {
|
||||||
info!(
|
info!(
|
||||||
"Starting Labrinth on {}",
|
"Starting labrinth on {}",
|
||||||
dotenvy::var("BIND_ADDR").unwrap()
|
dotenvy::var("BIND_ADDR").unwrap()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -334,19 +334,15 @@ impl From<Version> for LegacyVersion {
|
|||||||
// the v2 loaders are whatever the corresponding loader fields are
|
// the v2 loaders are whatever the corresponding loader fields are
|
||||||
let mut loaders =
|
let mut loaders =
|
||||||
data.loaders.into_iter().map(|l| l.0).collect::<Vec<_>>();
|
data.loaders.into_iter().map(|l| l.0).collect::<Vec<_>>();
|
||||||
if loaders.contains(&"mrpack".to_string()) {
|
if loaders.contains(&"mrpack".to_string())
|
||||||
if let Some((_, mrpack_loaders)) = data
|
&& let Some((_, mrpack_loaders)) = data
|
||||||
.fields
|
.fields
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|(key, _)| key == "mrpack_loaders")
|
.find(|(key, _)| key == "mrpack_loaders")
|
||||||
{
|
&& let Ok(mrpack_loaders) = serde_json::from_value(mrpack_loaders)
|
||||||
if let Ok(mrpack_loaders) =
|
|
||||||
serde_json::from_value(mrpack_loaders)
|
|
||||||
{
|
{
|
||||||
loaders = mrpack_loaders;
|
loaders = mrpack_loaders;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
let loaders = loaders.into_iter().map(Loader).collect::<Vec<_>>();
|
let loaders = loaders.into_iter().map(Loader).collect::<Vec<_>>();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ impl LegacyResultSearchProject {
|
|||||||
pub fn from(result_search_project: ResultSearchProject) -> Self {
|
pub fn from(result_search_project: ResultSearchProject) -> Self {
|
||||||
let mut categories = result_search_project.categories;
|
let mut categories = result_search_project.categories;
|
||||||
categories.extend(result_search_project.loaders.clone());
|
categories.extend(result_search_project.loaders.clone());
|
||||||
if categories.contains(&"mrpack".to_string()) {
|
if categories.contains(&"mrpack".to_string())
|
||||||
if let Some(mrpack_loaders) = result_search_project
|
&& let Some(mrpack_loaders) = result_search_project
|
||||||
.project_loader_fields
|
.project_loader_fields
|
||||||
.get("mrpack_loaders")
|
.get("mrpack_loaders")
|
||||||
{
|
{
|
||||||
@@ -56,11 +56,10 @@ impl LegacyResultSearchProject {
|
|||||||
);
|
);
|
||||||
categories.retain(|c| c != "mrpack");
|
categories.retain(|c| c != "mrpack");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
let mut display_categories = result_search_project.display_categories;
|
let mut display_categories = result_search_project.display_categories;
|
||||||
display_categories.extend(result_search_project.loaders);
|
display_categories.extend(result_search_project.loaders);
|
||||||
if display_categories.contains(&"mrpack".to_string()) {
|
if display_categories.contains(&"mrpack".to_string())
|
||||||
if let Some(mrpack_loaders) = result_search_project
|
&& let Some(mrpack_loaders) = result_search_project
|
||||||
.project_loader_fields
|
.project_loader_fields
|
||||||
.get("mrpack_loaders")
|
.get("mrpack_loaders")
|
||||||
{
|
{
|
||||||
@@ -72,7 +71,6 @@ impl LegacyResultSearchProject {
|
|||||||
);
|
);
|
||||||
display_categories.retain(|c| c != "mrpack");
|
display_categories.retain(|c| c != "mrpack");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Sort then remove duplicates
|
// Sort then remove duplicates
|
||||||
categories.sort();
|
categories.sort();
|
||||||
|
|||||||
@@ -166,12 +166,12 @@ impl From<ProjectQueryResult> for Project {
|
|||||||
Ok(spdx_expr) => {
|
Ok(spdx_expr) => {
|
||||||
let mut vec: Vec<&str> = Vec::new();
|
let mut vec: Vec<&str> = Vec::new();
|
||||||
for node in spdx_expr.iter() {
|
for node in spdx_expr.iter() {
|
||||||
if let spdx::expression::ExprNode::Req(req) = node {
|
if let spdx::expression::ExprNode::Req(req) = node
|
||||||
if let Some(id) = req.req.license.id() {
|
&& let Some(id) = req.req.license.id()
|
||||||
|
{
|
||||||
vec.push(id.full_name);
|
vec.push(id.full_name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
// spdx crate returns AND/OR operations in postfix order
|
// spdx crate returns AND/OR operations in postfix order
|
||||||
// and it would be a lot more effort to make it actually in order
|
// and it would be a lot more effort to make it actually in order
|
||||||
// so let's just ignore that and make them comma-separated
|
// so let's just ignore that and make them comma-separated
|
||||||
|
|||||||
@@ -51,17 +51,17 @@ impl ProjectPermissions {
|
|||||||
return Some(ProjectPermissions::all());
|
return Some(ProjectPermissions::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(member) = project_team_member {
|
if let Some(member) = project_team_member
|
||||||
if member.accepted {
|
&& member.accepted
|
||||||
|
{
|
||||||
return Some(member.permissions);
|
return Some(member.permissions);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(member) = organization_team_member {
|
if let Some(member) = organization_team_member
|
||||||
if member.accepted {
|
&& member.accepted
|
||||||
|
{
|
||||||
return Some(member.permissions);
|
return Some(member.permissions);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if role.is_mod() {
|
if role.is_mod() {
|
||||||
Some(
|
Some(
|
||||||
@@ -107,11 +107,11 @@ impl OrganizationPermissions {
|
|||||||
return Some(OrganizationPermissions::all());
|
return Some(OrganizationPermissions::all());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(member) = team_member {
|
if let Some(member) = team_member
|
||||||
if member.accepted {
|
&& member.accepted
|
||||||
|
{
|
||||||
return member.organization_permissions;
|
return member.organization_permissions;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if role.is_mod() {
|
if role.is_mod() {
|
||||||
return Some(
|
return Some(
|
||||||
OrganizationPermissions::EDIT_DETAILS
|
OrganizationPermissions::EDIT_DETAILS
|
||||||
|
|||||||
@@ -45,20 +45,18 @@ impl MaxMindIndexer {
|
|||||||
|
|
||||||
if let Ok(entries) = archive.entries() {
|
if let Ok(entries) = archive.entries() {
|
||||||
for mut file in entries.flatten() {
|
for mut file in entries.flatten() {
|
||||||
if let Ok(path) = file.header().path() {
|
if let Ok(path) = file.header().path()
|
||||||
if path.extension().and_then(|x| x.to_str()) == Some("mmdb")
|
&& path.extension().and_then(|x| x.to_str()) == Some("mmdb")
|
||||||
{
|
{
|
||||||
let mut buf = Vec::new();
|
let mut buf = Vec::new();
|
||||||
file.read_to_end(&mut buf).unwrap();
|
file.read_to_end(&mut buf).unwrap();
|
||||||
|
|
||||||
let reader =
|
let reader = maxminddb::Reader::from_source(buf).unwrap();
|
||||||
maxminddb::Reader::from_source(buf).unwrap();
|
|
||||||
|
|
||||||
return Ok(Some(reader));
|
return Ok(Some(reader));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if should_panic {
|
if should_panic {
|
||||||
panic!(
|
panic!(
|
||||||
|
|||||||
@@ -371,8 +371,8 @@ impl AutomatedModerationQueue {
|
|||||||
for file in
|
for file in
|
||||||
files.iter().filter(|x| x.version_id == version.id.into())
|
files.iter().filter(|x| x.version_id == version.id.into())
|
||||||
{
|
{
|
||||||
if let Some(hash) = file.hashes.get("sha1") {
|
if let Some(hash) = file.hashes.get("sha1")
|
||||||
if let Some((index, (sha1, _, file_name, _))) = hashes
|
&& let Some((index, (sha1, _, file_name, _))) = hashes
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.find(|(_, (value, _, _, _))| value == hash)
|
.find(|(_, (value, _, _, _))| value == hash)
|
||||||
@@ -384,7 +384,6 @@ impl AutomatedModerationQueue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// All files are on Modrinth, so we don't send any messages
|
// All files are on Modrinth, so we don't send any messages
|
||||||
if hashes.is_empty() {
|
if hashes.is_empty() {
|
||||||
@@ -420,13 +419,12 @@ impl AutomatedModerationQueue {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
for row in rows {
|
for row in rows {
|
||||||
if let Some(sha1) = row.sha1 {
|
if let Some(sha1) = row.sha1
|
||||||
if let Some((index, (sha1, _, file_name, _))) = hashes.iter().enumerate().find(|(_, (value, _, _, _))| value == &sha1) {
|
&& let Some((index, (sha1, _, file_name, _))) = hashes.iter().enumerate().find(|(_, (value, _, _, _))| value == &sha1) {
|
||||||
final_hashes.insert(sha1.clone(), IdentifiedFile { file_name: file_name.clone(), status: ApprovalType::from_string(&row.status).unwrap_or(ApprovalType::Unidentified) });
|
final_hashes.insert(sha1.clone(), IdentifiedFile { file_name: file_name.clone(), status: ApprovalType::from_string(&row.status).unwrap_or(ApprovalType::Unidentified) });
|
||||||
hashes.remove(index);
|
hashes.remove(index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if hashes.is_empty() {
|
if hashes.is_empty() {
|
||||||
let metadata = MissingMetadata {
|
let metadata = MissingMetadata {
|
||||||
@@ -499,8 +497,8 @@ impl AutomatedModerationQueue {
|
|||||||
let mut insert_ids = Vec::new();
|
let mut insert_ids = Vec::new();
|
||||||
|
|
||||||
for row in rows {
|
for row in rows {
|
||||||
if let Some((curse_index, (hash, _flame_id))) = flame_files.iter().enumerate().find(|(_, x)| Some(x.1 as i32) == row.flame_project_id) {
|
if let Some((curse_index, (hash, _flame_id))) = flame_files.iter().enumerate().find(|(_, x)| Some(x.1 as i32) == row.flame_project_id)
|
||||||
if let Some((index, (sha1, _, file_name, _))) = hashes.iter().enumerate().find(|(_, (value, _, _, _))| value == hash) {
|
&& let Some((index, (sha1, _, file_name, _))) = hashes.iter().enumerate().find(|(_, (value, _, _, _))| value == hash) {
|
||||||
final_hashes.insert(sha1.clone(), IdentifiedFile {
|
final_hashes.insert(sha1.clone(), IdentifiedFile {
|
||||||
file_name: file_name.clone(),
|
file_name: file_name.clone(),
|
||||||
status: ApprovalType::from_string(&row.status).unwrap_or(ApprovalType::Unidentified),
|
status: ApprovalType::from_string(&row.status).unwrap_or(ApprovalType::Unidentified),
|
||||||
@@ -513,7 +511,6 @@ impl AutomatedModerationQueue {
|
|||||||
flame_files.remove(curse_index);
|
flame_files.remove(curse_index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !insert_ids.is_empty() && !insert_hashes.is_empty() {
|
if !insert_ids.is_empty() && !insert_hashes.is_empty() {
|
||||||
sqlx::query!(
|
sqlx::query!(
|
||||||
@@ -581,8 +578,8 @@ impl AutomatedModerationQueue {
|
|||||||
for (sha1, _pack_file, file_name, _mumur2) in hashes {
|
for (sha1, _pack_file, file_name, _mumur2) in hashes {
|
||||||
let flame_file = flame_files.iter().find(|x| x.0 == sha1);
|
let flame_file = flame_files.iter().find(|x| x.0 == sha1);
|
||||||
|
|
||||||
if let Some((_, flame_project_id)) = flame_file {
|
if let Some((_, flame_project_id)) = flame_file
|
||||||
if let Some(project) = flame_projects.iter().find(|x| &x.id == flame_project_id) {
|
&& let Some(project) = flame_projects.iter().find(|x| &x.id == flame_project_id) {
|
||||||
missing_metadata.flame_files.insert(sha1, MissingMetadataFlame {
|
missing_metadata.flame_files.insert(sha1, MissingMetadataFlame {
|
||||||
title: project.name.clone(),
|
title: project.name.clone(),
|
||||||
file_name,
|
file_name,
|
||||||
@@ -592,7 +589,6 @@ impl AutomatedModerationQueue {
|
|||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
missing_metadata.unknown_files.insert(sha1, file_name);
|
missing_metadata.unknown_files.insert(sha1, file_name);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -257,17 +257,17 @@ impl PayoutsQueue {
|
|||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
if !status.is_success() {
|
if !status.is_success()
|
||||||
if let Some(obj) = value.as_object() {
|
&& let Some(obj) = value.as_object()
|
||||||
|
{
|
||||||
if let Some(array) = obj.get("errors") {
|
if let Some(array) = obj.get("errors") {
|
||||||
#[derive(Deserialize)]
|
#[derive(Deserialize)]
|
||||||
struct TremendousError {
|
struct TremendousError {
|
||||||
message: String,
|
message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
let err = serde_json::from_value::<TremendousError>(
|
let err =
|
||||||
array.clone(),
|
serde_json::from_value::<TremendousError>(array.clone())
|
||||||
)
|
|
||||||
.map_err(|_| {
|
.map_err(|_| {
|
||||||
ApiError::Payments(
|
ApiError::Payments(
|
||||||
"could not retrieve Tremendous error json body"
|
"could not retrieve Tremendous error json body"
|
||||||
@@ -282,7 +282,6 @@ impl PayoutsQueue {
|
|||||||
"could not retrieve Tremendous error body".to_string(),
|
"could not retrieve Tremendous error body".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(serde_json::from_value(value)?)
|
Ok(serde_json::from_value(value)?)
|
||||||
}
|
}
|
||||||
@@ -449,11 +448,11 @@ impl PayoutsQueue {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// we do not support interval gift cards with non US based currencies since we cannot do currency conversions properly
|
// we do not support interval gift cards with non US based currencies since we cannot do currency conversions properly
|
||||||
if let PayoutInterval::Fixed { .. } = method.interval {
|
if let PayoutInterval::Fixed { .. } = method.interval
|
||||||
if !product.currency_codes.contains(&"USD".to_string()) {
|
&& !product.currency_codes.contains(&"USD".to_string())
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
methods.push(method);
|
methods.push(method);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,10 +55,10 @@ pub fn jemalloc_memory_stats(
|
|||||||
) -> Result<(), prometheus::Error> {
|
) -> Result<(), prometheus::Error> {
|
||||||
let allocated_mem = IntGauge::new(
|
let allocated_mem = IntGauge::new(
|
||||||
"labrinth_memory_allocated",
|
"labrinth_memory_allocated",
|
||||||
"Labrinth allocated memory",
|
"labrinth allocated memory",
|
||||||
)?;
|
)?;
|
||||||
let resident_mem =
|
let resident_mem =
|
||||||
IntGauge::new("labrinth_resident_memory", "Labrinth resident memory")?;
|
IntGauge::new("labrinth_resident_memory", "labrinth resident memory")?;
|
||||||
|
|
||||||
registry.register(Box::new(allocated_mem.clone()))?;
|
registry.register(Box::new(allocated_mem.clone()))?;
|
||||||
registry.register(Box::new(resident_mem.clone()))?;
|
registry.register(Box::new(resident_mem.clone()))?;
|
||||||
|
|||||||
@@ -286,8 +286,9 @@ pub async fn refund_charge(
|
|||||||
.upsert(&mut transaction)
|
.upsert(&mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if body.0.unprovision.unwrap_or(false) {
|
if body.0.unprovision.unwrap_or(false)
|
||||||
if let Some(subscription_id) = charge.subscription_id {
|
&& let Some(subscription_id) = charge.subscription_id
|
||||||
|
{
|
||||||
let open_charge =
|
let open_charge =
|
||||||
DBCharge::get_open_subscription(subscription_id, &**pool)
|
DBCharge::get_open_subscription(subscription_id, &**pool)
|
||||||
.await?;
|
.await?;
|
||||||
@@ -298,7 +299,6 @@ pub async fn refund_charge(
|
|||||||
open_charge.upsert(&mut transaction).await?;
|
open_charge.upsert(&mut transaction).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
}
|
}
|
||||||
@@ -392,19 +392,18 @@ pub async fn edit_subscription(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(interval) = &edit_subscription.interval {
|
if let Some(interval) = &edit_subscription.interval
|
||||||
if let Price::Recurring { intervals } = ¤t_price.prices {
|
&& let Price::Recurring { intervals } = ¤t_price.prices
|
||||||
|
{
|
||||||
if let Some(price) = intervals.get(interval) {
|
if let Some(price) = intervals.get(interval) {
|
||||||
open_charge.subscription_interval = Some(*interval);
|
open_charge.subscription_interval = Some(*interval);
|
||||||
open_charge.amount = *price as i64;
|
open_charge.amount = *price as i64;
|
||||||
} else {
|
} else {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Interval is not valid for this subscription!"
|
"Interval is not valid for this subscription!".to_string(),
|
||||||
.to_string(),
|
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let intent = if let Some(product_id) = &edit_subscription.product {
|
let intent = if let Some(product_id) = &edit_subscription.product {
|
||||||
let product_price =
|
let product_price =
|
||||||
@@ -1225,8 +1224,9 @@ pub async fn initiate_payment(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Price::Recurring { .. } = price_item.prices {
|
if let Price::Recurring { .. } = price_item.prices
|
||||||
if product.unitary {
|
&& product.unitary
|
||||||
|
{
|
||||||
let user_subscriptions =
|
let user_subscriptions =
|
||||||
user_subscription_item::DBUserSubscription::get_all_user(
|
user_subscription_item::DBUserSubscription::get_all_user(
|
||||||
user.id.into(),
|
user.id.into(),
|
||||||
@@ -1234,13 +1234,11 @@ pub async fn initiate_payment(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let user_products =
|
let user_products = product_item::DBProductPrice::get_many(
|
||||||
product_item::DBProductPrice::get_many(
|
|
||||||
&user_subscriptions
|
&user_subscriptions
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|x| {
|
.filter(|x| {
|
||||||
x.status
|
x.status == SubscriptionStatus::Provisioned
|
||||||
== SubscriptionStatus::Provisioned
|
|
||||||
})
|
})
|
||||||
.map(|x| x.price_id)
|
.map(|x| x.price_id)
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
@@ -1258,7 +1256,6 @@ pub async fn initiate_payment(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
(
|
(
|
||||||
price as i64,
|
price as i64,
|
||||||
@@ -2004,8 +2001,7 @@ pub async fn stripe_webhook(
|
|||||||
EventType::PaymentMethodAttached => {
|
EventType::PaymentMethodAttached => {
|
||||||
if let EventObject::PaymentMethod(payment_method) =
|
if let EventObject::PaymentMethod(payment_method) =
|
||||||
event.data.object
|
event.data.object
|
||||||
{
|
&& let Some(customer_id) =
|
||||||
if let Some(customer_id) =
|
|
||||||
payment_method.customer.map(|x| x.id())
|
payment_method.customer.map(|x| x.id())
|
||||||
{
|
{
|
||||||
let customer = stripe::Customer::retrieve(
|
let customer = stripe::Customer::retrieve(
|
||||||
@@ -2038,7 +2034,6 @@ pub async fn stripe_webhook(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -79,14 +79,13 @@ impl TempUser {
|
|||||||
file_host: &Arc<dyn FileHost + Send + Sync>,
|
file_host: &Arc<dyn FileHost + Send + Sync>,
|
||||||
redis: &RedisPool,
|
redis: &RedisPool,
|
||||||
) -> Result<crate::database::models::DBUserId, AuthenticationError> {
|
) -> Result<crate::database::models::DBUserId, AuthenticationError> {
|
||||||
if let Some(email) = &self.email {
|
if let Some(email) = &self.email
|
||||||
if crate::database::models::DBUser::get_by_email(email, client)
|
&& crate::database::models::DBUser::get_by_email(email, client)
|
||||||
.await?
|
.await?
|
||||||
.is_some()
|
.is_some()
|
||||||
{
|
{
|
||||||
return Err(AuthenticationError::DuplicateUser);
|
return Err(AuthenticationError::DuplicateUser);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let user_id =
|
let user_id =
|
||||||
crate::database::models::generate_user_id(transaction).await?;
|
crate::database::models::generate_user_id(transaction).await?;
|
||||||
@@ -1269,8 +1268,9 @@ pub async fn delete_auth_provider(
|
|||||||
.update_user_id(user.id.into(), None, &mut transaction)
|
.update_user_id(user.id.into(), None, &mut transaction)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if delete_provider.provider != AuthProvider::PayPal {
|
if delete_provider.provider != AuthProvider::PayPal
|
||||||
if let Some(email) = user.email {
|
&& let Some(email) = user.email
|
||||||
|
{
|
||||||
send_email(
|
send_email(
|
||||||
email,
|
email,
|
||||||
"Authentication method removed",
|
"Authentication method removed",
|
||||||
@@ -1282,7 +1282,6 @@ pub async fn delete_auth_provider(
|
|||||||
None,
|
None,
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
crate::database::models::DBUser::clear_caches(
|
crate::database::models::DBUser::clear_caches(
|
||||||
|
|||||||
@@ -189,8 +189,8 @@ pub async fn get_project_meta(
|
|||||||
.iter()
|
.iter()
|
||||||
.find(|x| Some(x.1.id as i32) == row.flame_project_id)
|
.find(|x| Some(x.1.id as i32) == row.flame_project_id)
|
||||||
.map(|x| x.0.clone())
|
.map(|x| x.0.clone())
|
||||||
|
&& let Some(val) = merged.flame_files.remove(&sha1)
|
||||||
{
|
{
|
||||||
if let Some(val) = merged.flame_files.remove(&sha1) {
|
|
||||||
merged.identified.insert(
|
merged.identified.insert(
|
||||||
sha1,
|
sha1,
|
||||||
IdentifiedFile {
|
IdentifiedFile {
|
||||||
@@ -201,7 +201,6 @@ pub async fn get_project_meta(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(merged))
|
Ok(HttpResponse::Ok().json(merged))
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -185,8 +185,9 @@ pub async fn edit_pat(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(pat) = pat {
|
if let Some(pat) = pat
|
||||||
if pat.user_id == user.id.into() {
|
&& pat.user_id == user.id.into()
|
||||||
|
{
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
if let Some(scopes) = &info.scopes {
|
if let Some(scopes) = &info.scopes {
|
||||||
@@ -248,7 +249,6 @@ pub async fn edit_pat(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
}
|
}
|
||||||
@@ -276,8 +276,9 @@ pub async fn delete_pat(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(pat) = pat {
|
if let Some(pat) = pat
|
||||||
if pat.user_id == user.id.into() {
|
&& pat.user_id == user.id.into()
|
||||||
|
{
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
database::models::pat_item::DBPersonalAccessToken::remove(
|
database::models::pat_item::DBPersonalAccessToken::remove(
|
||||||
pat.id,
|
pat.id,
|
||||||
@@ -291,7 +292,6 @@ pub async fn delete_pat(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().finish())
|
Ok(HttpResponse::NoContent().finish())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,8 +185,9 @@ pub async fn delete(
|
|||||||
|
|
||||||
let session = DBSession::get(info.into_inner().0, &**pool, &redis).await?;
|
let session = DBSession::get(info.into_inner().0, &**pool, &redis).await?;
|
||||||
|
|
||||||
if let Some(session) = session {
|
if let Some(session) = session
|
||||||
if session.user_id == current_user.id.into() {
|
&& session.user_id == current_user.id.into()
|
||||||
|
{
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
DBSession::remove(session.id, &mut transaction).await?;
|
DBSession::remove(session.id, &mut transaction).await?;
|
||||||
transaction.commit().await?;
|
transaction.commit().await?;
|
||||||
@@ -200,7 +201,6 @@ pub async fn delete(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -401,8 +401,8 @@ async fn broadcast_to_known_local_friends(
|
|||||||
friend.user_id
|
friend.user_id
|
||||||
};
|
};
|
||||||
|
|
||||||
if friend.accepted {
|
if friend.accepted
|
||||||
if let Some(socket_ids) =
|
&& let Some(socket_ids) =
|
||||||
sockets.sockets_by_user_id.get(&friend_id.into())
|
sockets.sockets_by_user_id.get(&friend_id.into())
|
||||||
{
|
{
|
||||||
for socket_id in socket_ids.iter() {
|
for socket_id in socket_ids.iter() {
|
||||||
@@ -412,7 +412,6 @@ async fn broadcast_to_known_local_friends(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -512,6 +512,7 @@ pub async fn project_edit(
|
|||||||
moderation_message_body: v2_new_project.moderation_message_body,
|
moderation_message_body: v2_new_project.moderation_message_body,
|
||||||
monetization_status: v2_new_project.monetization_status,
|
monetization_status: v2_new_project.monetization_status,
|
||||||
side_types_migration_review_status: None, // Not to be exposed in v2
|
side_types_migration_review_status: None, // Not to be exposed in v2
|
||||||
|
loader_fields: HashMap::new(), // Loader fields are not a thing in v2
|
||||||
};
|
};
|
||||||
|
|
||||||
// This returns 204 or failure so we don't need to do anything with it
|
// This returns 204 or failure so we don't need to do anything with it
|
||||||
|
|||||||
@@ -387,9 +387,10 @@ pub async fn revenue_get(
|
|||||||
.map(|x| (x.to_string(), HashMap::new()))
|
.map(|x| (x.to_string(), HashMap::new()))
|
||||||
.collect::<HashMap<_, _>>();
|
.collect::<HashMap<_, _>>();
|
||||||
for value in payouts_values {
|
for value in payouts_values {
|
||||||
if let Some(mod_id) = value.mod_id {
|
if let Some(mod_id) = value.mod_id
|
||||||
if let Some(amount) = value.amount_sum {
|
&& let Some(amount) = value.amount_sum
|
||||||
if let Some(interval_start) = value.interval_start {
|
&& let Some(interval_start) = value.interval_start
|
||||||
|
{
|
||||||
let id_string = to_base62(mod_id as u64);
|
let id_string = to_base62(mod_id as u64);
|
||||||
if !hm.contains_key(&id_string) {
|
if !hm.contains_key(&id_string) {
|
||||||
hm.insert(id_string.clone(), HashMap::new());
|
hm.insert(id_string.clone(), HashMap::new());
|
||||||
@@ -399,8 +400,6 @@ pub async fn revenue_get(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(hm))
|
Ok(HttpResponse::Ok().json(hm))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,11 +192,11 @@ pub async fn collection_get(
|
|||||||
.map(|x| x.1)
|
.map(|x| x.1)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
if let Some(data) = collection_data {
|
if let Some(data) = collection_data
|
||||||
if is_visible_collection(&data, &user_option, false).await? {
|
&& is_visible_collection(&data, &user_option, false).await?
|
||||||
|
{
|
||||||
return Ok(HttpResponse::Ok().json(Collection::from(data)));
|
return Ok(HttpResponse::Ok().json(Collection::from(data)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -536,11 +536,9 @@ pub async fn create_payout(
|
|||||||
Some(true),
|
Some(true),
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
|
&& let Some(data) = res.items.first()
|
||||||
{
|
{
|
||||||
if let Some(data) = res.items.first() {
|
payout_item.platform_id = Some(data.payout_item_id.clone());
|
||||||
payout_item.platform_id =
|
|
||||||
Some(data.payout_item_id.clone());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ use crate::database::redis::RedisPool;
|
|||||||
use crate::database::{self, models as db_models};
|
use crate::database::{self, models as db_models};
|
||||||
use crate::file_hosting::{FileHost, FileHostPublicity};
|
use crate::file_hosting::{FileHost, FileHostPublicity};
|
||||||
use crate::models;
|
use crate::models;
|
||||||
use crate::models::ids::ProjectId;
|
use crate::models::ids::{ProjectId, VersionId};
|
||||||
use crate::models::images::ImageContext;
|
use crate::models::images::ImageContext;
|
||||||
use crate::models::notifications::NotificationBody;
|
use crate::models::notifications::NotificationBody;
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
@@ -182,11 +182,11 @@ pub async fn project_get(
|
|||||||
.map(|x| x.1)
|
.map(|x| x.1)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
if let Some(data) = project_data {
|
if let Some(data) = project_data
|
||||||
if is_visible_project(&data.inner, &user_option, &pool, false).await? {
|
&& is_visible_project(&data.inner, &user_option, &pool, false).await?
|
||||||
|
{
|
||||||
return Ok(HttpResponse::Ok().json(Project::from(data)));
|
return Ok(HttpResponse::Ok().json(Project::from(data)));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,6 +250,8 @@ pub struct EditProject {
|
|||||||
pub monetization_status: Option<MonetizationStatus>,
|
pub monetization_status: Option<MonetizationStatus>,
|
||||||
pub side_types_migration_review_status:
|
pub side_types_migration_review_status:
|
||||||
Option<SideTypesMigrationReviewStatus>,
|
Option<SideTypesMigrationReviewStatus>,
|
||||||
|
#[serde(flatten)]
|
||||||
|
pub loader_fields: HashMap<String, serde_json::Value>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@@ -403,8 +405,10 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.is_searchable() && !project_item.inner.webhook_sent {
|
if status.is_searchable()
|
||||||
if let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK") {
|
&& !project_item.inner.webhook_sent
|
||||||
|
&& let Ok(webhook_url) = dotenvy::var("PUBLIC_DISCORD_WEBHOOK")
|
||||||
|
{
|
||||||
crate::util::webhook::send_discord_webhook(
|
crate::util::webhook::send_discord_webhook(
|
||||||
project_item.inner.id.into(),
|
project_item.inner.id.into(),
|
||||||
&pool,
|
&pool,
|
||||||
@@ -426,10 +430,10 @@ pub async fn project_edit(
|
|||||||
.execute(&mut *transaction)
|
.execute(&mut *transaction)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if user.role.is_mod() {
|
if user.role.is_mod()
|
||||||
if let Ok(webhook_url) = dotenvy::var("MODERATION_SLACK_WEBHOOK") {
|
&& let Ok(webhook_url) = dotenvy::var("MODERATION_SLACK_WEBHOOK")
|
||||||
|
{
|
||||||
crate::util::webhook::send_slack_webhook(
|
crate::util::webhook::send_slack_webhook(
|
||||||
project_item.inner.id.into(),
|
project_item.inner.id.into(),
|
||||||
&pool,
|
&pool,
|
||||||
@@ -450,7 +454,6 @@ pub async fn project_edit(
|
|||||||
.await
|
.await
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if team_member.is_none_or(|x| !x.accepted) {
|
if team_member.is_none_or(|x| !x.accepted) {
|
||||||
let notified_members = sqlx::query!(
|
let notified_members = sqlx::query!(
|
||||||
@@ -692,8 +695,9 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(links) = &new_project.link_urls {
|
if let Some(links) = &new_project.link_urls
|
||||||
if !links.is_empty() {
|
&& !links.is_empty()
|
||||||
|
{
|
||||||
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
|
if !perms.contains(ProjectPermissions::EDIT_DETAILS) {
|
||||||
return Err(ApiError::CustomAuthentication(
|
return Err(ApiError::CustomAuthentication(
|
||||||
"You do not have the permissions to edit the links of this project!"
|
"You do not have the permissions to edit the links of this project!"
|
||||||
@@ -718,8 +722,7 @@ pub async fn project_edit(
|
|||||||
|
|
||||||
for (platform, url) in links {
|
for (platform, url) in links {
|
||||||
if let Some(url) = url {
|
if let Some(url) = url {
|
||||||
let platform_id =
|
let platform_id = db_models::categories::LinkPlatform::get_id(
|
||||||
db_models::categories::LinkPlatform::get_id(
|
|
||||||
platform,
|
platform,
|
||||||
&mut *transaction,
|
&mut *transaction,
|
||||||
)
|
)
|
||||||
@@ -744,7 +747,6 @@ pub async fn project_edit(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if let Some(moderation_message) = &new_project.moderation_message {
|
if let Some(moderation_message) = &new_project.moderation_message {
|
||||||
if !user.role.is_mod()
|
if !user.role.is_mod()
|
||||||
&& (!project_item.inner.status.is_approved()
|
&& (!project_item.inner.status.is_approved()
|
||||||
@@ -870,6 +872,29 @@ pub async fn project_edit(
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !new_project.loader_fields.is_empty() {
|
||||||
|
for version in db_models::DBVersion::get_many(
|
||||||
|
&project_item.versions,
|
||||||
|
&**pool,
|
||||||
|
&redis,
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
super::versions::version_edit_helper(
|
||||||
|
req.clone(),
|
||||||
|
(VersionId::from(version.inner.id),),
|
||||||
|
pool.clone(),
|
||||||
|
redis.clone(),
|
||||||
|
super::versions::EditVersion {
|
||||||
|
fields: new_project.loader_fields.clone(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
session_queue.clone(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// check new description and body for links to associated images
|
// check new description and body for links to associated images
|
||||||
// if they no longer exist in the description or body, delete them
|
// if they no longer exist in the description or body, delete them
|
||||||
let checkable_strings: Vec<&str> =
|
let checkable_strings: Vec<&str> =
|
||||||
@@ -2430,7 +2455,7 @@ pub async fn project_get_organization(
|
|||||||
organization,
|
organization,
|
||||||
team_members,
|
team_members,
|
||||||
);
|
);
|
||||||
return Ok(HttpResponse::Ok().json(organization));
|
Ok(HttpResponse::Ok().json(organization))
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -767,13 +767,14 @@ pub async fn edit_team_member(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(new_permissions) = edit_member.permissions {
|
if let Some(new_permissions) = edit_member.permissions
|
||||||
if !permissions.contains(new_permissions) {
|
&& !permissions.contains(new_permissions)
|
||||||
|
{
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"The new permissions have permissions that you don't have".to_string(),
|
"The new permissions have permissions that you don't have"
|
||||||
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if edit_member.organization_permissions.is_some() {
|
if edit_member.organization_permissions.is_some() {
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
@@ -800,14 +801,13 @@ pub async fn edit_team_member(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(new_permissions) = edit_member.organization_permissions
|
if let Some(new_permissions) = edit_member.organization_permissions
|
||||||
|
&& !organization_permissions.contains(new_permissions)
|
||||||
{
|
{
|
||||||
if !organization_permissions.contains(new_permissions) {
|
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"The new organization permissions have permissions that you don't have"
|
"The new organization permissions have permissions that you don't have"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if edit_member.permissions.is_some()
|
if edit_member.permissions.is_some()
|
||||||
&& !organization_permissions.contains(
|
&& !organization_permissions.contains(
|
||||||
@@ -822,14 +822,14 @@ pub async fn edit_team_member(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(payouts_split) = edit_member.payouts_split {
|
if let Some(payouts_split) = edit_member.payouts_split
|
||||||
if payouts_split < Decimal::ZERO || payouts_split > Decimal::from(5000)
|
&& (payouts_split < Decimal::ZERO
|
||||||
|
|| payouts_split > Decimal::from(5000))
|
||||||
{
|
{
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"Payouts split must be between 0 and 5000!".to_string(),
|
"Payouts split must be between 0 and 5000!".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
DBTeamMember::edit_team_member(
|
DBTeamMember::edit_team_member(
|
||||||
id,
|
id,
|
||||||
@@ -883,15 +883,15 @@ pub async fn transfer_ownership(
|
|||||||
DBTeam::get_association(id.into(), &**pool).await?;
|
DBTeam::get_association(id.into(), &**pool).await?;
|
||||||
if let Some(TeamAssociationId::Project(pid)) = team_association_id {
|
if let Some(TeamAssociationId::Project(pid)) = team_association_id {
|
||||||
let result = DBProject::get_id(pid, &**pool, &redis).await?;
|
let result = DBProject::get_id(pid, &**pool, &redis).await?;
|
||||||
if let Some(project_item) = result {
|
if let Some(project_item) = result
|
||||||
if project_item.inner.organization_id.is_some() {
|
&& project_item.inner.organization_id.is_some()
|
||||||
|
{
|
||||||
return Err(ApiError::InvalidInput(
|
return Err(ApiError::InvalidInput(
|
||||||
"You cannot transfer ownership of a project team that is owend by an organization"
|
"You cannot transfer ownership of a project team that is owend by an organization"
|
||||||
.to_string(),
|
.to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !current_user.role.is_admin() {
|
if !current_user.role.is_admin() {
|
||||||
let member = DBTeamMember::get_from_user_id(
|
let member = DBTeamMember::get_from_user_id(
|
||||||
|
|||||||
@@ -289,8 +289,9 @@ pub async fn thread_get(
|
|||||||
.await?
|
.await?
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
if let Some(mut data) = thread_data {
|
if let Some(mut data) = thread_data
|
||||||
if is_authorized_thread(&data, &user, &pool).await? {
|
&& is_authorized_thread(&data, &user, &pool).await?
|
||||||
|
{
|
||||||
let authors = &mut data.members;
|
let authors = &mut data.members;
|
||||||
|
|
||||||
authors.append(
|
authors.append(
|
||||||
@@ -307,18 +308,14 @@ pub async fn thread_get(
|
|||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let users: Vec<User> = database::models::DBUser::get_many_ids(
|
let users: Vec<User> =
|
||||||
authors, &**pool, &redis,
|
database::models::DBUser::get_many_ids(authors, &**pool, &redis)
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(From::from)
|
.map(From::from)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
return Ok(
|
return Ok(HttpResponse::Ok().json(Thread::from(data, users, &user)));
|
||||||
HttpResponse::Ok().json(Thread::from(data, users, &user))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
}
|
}
|
||||||
@@ -454,8 +451,8 @@ pub async fn thread_send_message(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(project) = project {
|
if let Some(project) = project
|
||||||
if project.inner.status != ProjectStatus::Processing
|
&& project.inner.status != ProjectStatus::Processing
|
||||||
&& user.role.is_mod()
|
&& user.role.is_mod()
|
||||||
{
|
{
|
||||||
let members =
|
let members =
|
||||||
@@ -481,7 +478,6 @@ pub async fn thread_send_message(
|
|||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} else if let Some(report_id) = thread.report_id {
|
} else if let Some(report_id) = thread.report_id {
|
||||||
let report = database::models::report_item::DBReport::get(
|
let report = database::models::report_item::DBReport::get(
|
||||||
report_id, &**pool,
|
report_id, &**pool,
|
||||||
|
|||||||
@@ -522,11 +522,11 @@ async fn version_create_inner(
|
|||||||
.fetch_optional(pool)
|
.fetch_optional(pool)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(project_status) = project_status {
|
if let Some(project_status) = project_status
|
||||||
if project_status.status == ProjectStatus::Processing.as_str() {
|
&& project_status.status == ProjectStatus::Processing.as_str()
|
||||||
|
{
|
||||||
moderation_queue.projects.insert(project_id.into());
|
moderation_queue.projects.insert(project_id.into());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
}
|
}
|
||||||
@@ -871,8 +871,8 @@ pub async fn upload_file(
|
|||||||
ref format,
|
ref format,
|
||||||
ref files,
|
ref files,
|
||||||
} = validation_result
|
} = validation_result
|
||||||
|
&& dependencies.is_empty()
|
||||||
{
|
{
|
||||||
if dependencies.is_empty() {
|
|
||||||
let hashes: Vec<Vec<u8>> = format
|
let hashes: Vec<Vec<u8>> = format
|
||||||
.files
|
.files
|
||||||
.iter()
|
.iter()
|
||||||
@@ -933,7 +933,6 @@ pub async fn upload_file(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let data = data.freeze();
|
let data = data.freeze();
|
||||||
let primary = (validation_result.is_passed()
|
let primary = (validation_result.is_passed()
|
||||||
@@ -974,11 +973,11 @@ pub async fn upload_file(
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
if let ValidationResult::Warning(msg) = validation_result {
|
if let ValidationResult::Warning(msg) = validation_result
|
||||||
if primary {
|
&& primary
|
||||||
|
{
|
||||||
return Err(CreateError::InvalidInput(msg.to_string()));
|
return Err(CreateError::InvalidInput(msg.to_string()));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let url = format!("{cdn_url}/{file_path_encode}");
|
let url = format!("{cdn_url}/{file_path_encode}");
|
||||||
|
|
||||||
|
|||||||
@@ -148,8 +148,7 @@ pub async fn get_update_from_hash(
|
|||||||
&redis,
|
&redis,
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
&& let Some(project) = database::models::DBProject::get_id(
|
||||||
if let Some(project) = database::models::DBProject::get_id(
|
|
||||||
file.project_id,
|
file.project_id,
|
||||||
&**pool,
|
&**pool,
|
||||||
&redis,
|
&redis,
|
||||||
@@ -175,14 +174,10 @@ pub async fn get_update_from_hash(
|
|||||||
}
|
}
|
||||||
if let Some(loader_fields) = &update_data.loader_fields {
|
if let Some(loader_fields) = &update_data.loader_fields {
|
||||||
for (key, values) in loader_fields {
|
for (key, values) in loader_fields {
|
||||||
bool &= if let Some(x_vf) = x
|
bool &= if let Some(x_vf) =
|
||||||
.version_fields
|
x.version_fields.iter().find(|y| y.field_name == *key)
|
||||||
.iter()
|
|
||||||
.find(|y| y.field_name == *key)
|
|
||||||
{
|
{
|
||||||
values
|
values.iter().any(|v| x_vf.value.contains_json_value(v))
|
||||||
.iter()
|
|
||||||
.any(|v| x_vf.value.contains_json_value(v))
|
|
||||||
} else {
|
} else {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
@@ -193,20 +188,15 @@ pub async fn get_update_from_hash(
|
|||||||
.sorted();
|
.sorted();
|
||||||
|
|
||||||
if let Some(first) = versions.next_back() {
|
if let Some(first) = versions.next_back() {
|
||||||
if !is_visible_version(
|
if !is_visible_version(&first.inner, &user_option, &pool, &redis)
|
||||||
&first.inner,
|
|
||||||
&user_option,
|
|
||||||
&pool,
|
|
||||||
&redis,
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
return Err(ApiError::NotFound);
|
return Err(ApiError::NotFound);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(HttpResponse::Ok()
|
return Ok(
|
||||||
.json(models::projects::Version::from(first)));
|
HttpResponse::Ok().json(models::projects::Version::from(first))
|
||||||
}
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
@@ -398,15 +388,14 @@ pub async fn update_files(
|
|||||||
if let Some(version) = versions
|
if let Some(version) = versions
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.inner.project_id == file.project_id)
|
.find(|x| x.inner.project_id == file.project_id)
|
||||||
|
&& let Some(hash) = file.hashes.get(&algorithm)
|
||||||
{
|
{
|
||||||
if let Some(hash) = file.hashes.get(&algorithm) {
|
|
||||||
response.insert(
|
response.insert(
|
||||||
hash.clone(),
|
hash.clone(),
|
||||||
models::projects::Version::from(version.clone()),
|
models::projects::Version::from(version.clone()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
}
|
}
|
||||||
@@ -484,8 +473,8 @@ pub async fn update_individual_files(
|
|||||||
|
|
||||||
for project in projects {
|
for project in projects {
|
||||||
for file in files.iter().filter(|x| x.project_id == project.inner.id) {
|
for file in files.iter().filter(|x| x.project_id == project.inner.id) {
|
||||||
if let Some(hash) = file.hashes.get(&algorithm) {
|
if let Some(hash) = file.hashes.get(&algorithm)
|
||||||
if let Some(query_file) =
|
&& let Some(query_file) =
|
||||||
update_data.hashes.iter().find(|x| &x.hash == hash)
|
update_data.hashes.iter().find(|x| &x.hash == hash)
|
||||||
{
|
{
|
||||||
let version = all_versions
|
let version = all_versions
|
||||||
@@ -494,23 +483,17 @@ pub async fn update_individual_files(
|
|||||||
.filter(|x| {
|
.filter(|x| {
|
||||||
let mut bool = true;
|
let mut bool = true;
|
||||||
|
|
||||||
if let Some(version_types) =
|
if let Some(version_types) = &query_file.version_types {
|
||||||
&query_file.version_types
|
bool &= version_types
|
||||||
{
|
.iter()
|
||||||
bool &= version_types.iter().any(|y| {
|
.any(|y| y.as_str() == x.inner.version_type);
|
||||||
y.as_str() == x.inner.version_type
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if let Some(loaders) = &query_file.loaders {
|
if let Some(loaders) = &query_file.loaders {
|
||||||
bool &= x
|
bool &=
|
||||||
.loaders
|
x.loaders.iter().any(|y| loaders.contains(y));
|
||||||
.iter()
|
|
||||||
.any(|y| loaders.contains(y));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(loader_fields) =
|
if let Some(loader_fields) = &query_file.loader_fields {
|
||||||
&query_file.loader_fields
|
|
||||||
{
|
|
||||||
for (key, values) in loader_fields {
|
for (key, values) in loader_fields {
|
||||||
bool &= if let Some(x_vf) = x
|
bool &= if let Some(x_vf) = x
|
||||||
.version_fields
|
.version_fields
|
||||||
@@ -530,8 +513,8 @@ pub async fn update_individual_files(
|
|||||||
.sorted()
|
.sorted()
|
||||||
.next_back();
|
.next_back();
|
||||||
|
|
||||||
if let Some(version) = version {
|
if let Some(version) = version
|
||||||
if is_visible_version(
|
&& is_visible_version(
|
||||||
&version.inner,
|
&version.inner,
|
||||||
&user_option,
|
&user_option,
|
||||||
&pool,
|
&pool,
|
||||||
@@ -541,16 +524,12 @@ pub async fn update_individual_files(
|
|||||||
{
|
{
|
||||||
response.insert(
|
response.insert(
|
||||||
hash.clone(),
|
hash.clone(),
|
||||||
models::projects::Version::from(
|
models::projects::Version::from(version.clone()),
|
||||||
version.clone(),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(HttpResponse::Ok().json(response))
|
Ok(HttpResponse::Ok().json(response))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,15 +106,14 @@ pub async fn version_project_get_helper(
|
|||||||
|| x.inner.version_number == id.1
|
|| x.inner.version_number == id.1
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(version) = version {
|
if let Some(version) = version
|
||||||
if is_visible_version(&version.inner, &user_option, &pool, &redis)
|
&& is_visible_version(&version.inner, &user_option, &pool, &redis)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
return Ok(HttpResponse::Ok()
|
return Ok(HttpResponse::Ok()
|
||||||
.json(models::projects::Version::from(version)));
|
.json(models::projects::Version::from(version)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
}
|
}
|
||||||
@@ -190,13 +189,13 @@ pub async fn version_get_helper(
|
|||||||
.map(|x| x.1)
|
.map(|x| x.1)
|
||||||
.ok();
|
.ok();
|
||||||
|
|
||||||
if let Some(data) = version_data {
|
if let Some(data) = version_data
|
||||||
if is_visible_version(&data.inner, &user_option, &pool, &redis).await? {
|
&& is_visible_version(&data.inner, &user_option, &pool, &redis).await?
|
||||||
|
{
|
||||||
return Ok(
|
return Ok(
|
||||||
HttpResponse::Ok().json(models::projects::Version::from(data))
|
HttpResponse::Ok().json(models::projects::Version::from(data))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Err(ApiError::NotFound)
|
Err(ApiError::NotFound)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,15 +15,13 @@ pub async fn get_user_status(
|
|||||||
return Some(friend_status);
|
return Some(friend_status);
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Ok(mut conn) = redis.pool.get().await {
|
if let Ok(mut conn) = redis.pool.get().await
|
||||||
if let Ok(mut statuses) =
|
&& let Ok(mut statuses) =
|
||||||
conn.sscan::<_, String>(get_field_name(user)).await
|
conn.sscan::<_, String>(get_field_name(user)).await
|
||||||
|
&& let Some(status_json) = statuses.next_item().await
|
||||||
{
|
{
|
||||||
if let Some(status_json) = statuses.next_item().await {
|
|
||||||
return serde_json::from_str::<UserStatus>(&status_json).ok();
|
return serde_json::from_str::<UserStatus>(&status_json).ok();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,13 +138,12 @@ fn process_image(
|
|||||||
let (orig_width, orig_height) = img.dimensions();
|
let (orig_width, orig_height) = img.dimensions();
|
||||||
let aspect_ratio = orig_width as f32 / orig_height as f32;
|
let aspect_ratio = orig_width as f32 / orig_height as f32;
|
||||||
|
|
||||||
if let Some(target_width) = target_width {
|
if let Some(target_width) = target_width
|
||||||
if img.width() > target_width {
|
&& img.width() > target_width
|
||||||
let new_height =
|
{
|
||||||
(target_width as f32 / aspect_ratio).round() as u32;
|
let new_height = (target_width as f32 / aspect_ratio).round() as u32;
|
||||||
img = img.resize(target_width, new_height, FilterType::Lanczos3);
|
img = img.resize(target_width, new_height, FilterType::Lanczos3);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(min_aspect_ratio) = min_aspect_ratio {
|
if let Some(min_aspect_ratio) = min_aspect_ratio {
|
||||||
// Crop if necessary
|
// Crop if necessary
|
||||||
|
|||||||
@@ -133,13 +133,12 @@ pub async fn rate_limit_middleware(
|
|||||||
.expect("Rate limiter not configured properly")
|
.expect("Rate limiter not configured properly")
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
if let Some(key) = req.headers().get("x-ratelimit-key") {
|
if let Some(key) = req.headers().get("x-ratelimit-key")
|
||||||
if key.to_str().ok()
|
&& key.to_str().ok()
|
||||||
== dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok().as_deref()
|
== dotenvy::var("RATE_LIMIT_IGNORE_KEY").ok().as_deref()
|
||||||
{
|
{
|
||||||
return Ok(next.call(req).await?.map_into_left_body());
|
return Ok(next.call(req).await?.map_into_left_body());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let conn_info = req.connection_info().clone();
|
let conn_info = req.connection_info().clone();
|
||||||
let ip = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
let ip = if parse_var("CLOUDFLARE_INTEGRATION").unwrap_or(false) {
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ pub fn validation_errors_to_string(
|
|||||||
|
|
||||||
let key_option = map.keys().next();
|
let key_option = map.keys().next();
|
||||||
|
|
||||||
if let Some(field) = key_option {
|
if let Some(field) = key_option
|
||||||
if let Some(error) = map.get(field) {
|
&& let Some(error) = map.get(field)
|
||||||
|
{
|
||||||
return match error {
|
return match error {
|
||||||
ValidationErrorsKind::Struct(errors) => {
|
ValidationErrorsKind::Struct(errors) => {
|
||||||
validation_errors_to_string(
|
validation_errors_to_string(
|
||||||
@@ -54,7 +55,8 @@ pub fn validation_errors_to_string(
|
|||||||
&mut output,
|
&mut output,
|
||||||
"Field {field} failed validation with error: {}",
|
"Field {field} failed validation with error: {}",
|
||||||
error.code
|
error.code
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,7 +64,6 @@ pub fn validation_errors_to_string(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
String::new()
|
String::new()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,8 +238,9 @@ pub async fn send_slack_webhook(
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(icon_url) = metadata.project_icon_url {
|
if let Some(icon_url) = metadata.project_icon_url
|
||||||
if let Some(project_block) = project_block.as_object_mut() {
|
&& let Some(project_block) = project_block.as_object_mut()
|
||||||
|
{
|
||||||
project_block.insert(
|
project_block.insert(
|
||||||
"accessory".to_string(),
|
"accessory".to_string(),
|
||||||
serde_json::json!({
|
serde_json::json!({
|
||||||
@@ -249,7 +250,6 @@ pub async fn send_slack_webhook(
|
|||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
blocks.push(project_block);
|
blocks.push(project_block);
|
||||||
|
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ pub async fn add_dummy_data(api: &ApiV3, db: TemporaryDatabase) -> DummyData {
|
|||||||
let pool = &db.pool.clone();
|
let pool = &db.pool.clone();
|
||||||
|
|
||||||
pool.execute(
|
pool.execute(
|
||||||
include_str!("../files/dummy_data.sql")
|
include_str!("../fixtures/dummy_data.sql")
|
||||||
.replace("$1", &Scopes::all().bits().to_string())
|
.replace("$1", &Scopes::all().bits().to_string())
|
||||||
.as_str(),
|
.as_str(),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -222,11 +222,11 @@ impl<'a, A: Api> PermissionsTest<'a, A> {
|
|||||||
resp.status().as_u16()
|
resp.status().as_u16()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if resp.status() == StatusCode::OK {
|
if resp.status() == StatusCode::OK
|
||||||
if let Some(failure_json_check) = &self.failure_json_check {
|
&& let Some(failure_json_check) = &self.failure_json_check
|
||||||
|
{
|
||||||
failure_json_check(&test::read_body_json(resp).await);
|
failure_json_check(&test::read_body_json(resp).await);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Failure test- logged in on a non-team user
|
// Failure test- logged in on a non-team user
|
||||||
let resp = req_gen(PermissionsTestContext {
|
let resp = req_gen(PermissionsTestContext {
|
||||||
@@ -246,11 +246,11 @@ impl<'a, A: Api> PermissionsTest<'a, A> {
|
|||||||
resp.status().as_u16()
|
resp.status().as_u16()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if resp.status() == StatusCode::OK {
|
if resp.status() == StatusCode::OK
|
||||||
if let Some(failure_json_check) = &self.failure_json_check {
|
&& let Some(failure_json_check) = &self.failure_json_check
|
||||||
|
{
|
||||||
failure_json_check(&test::read_body_json(resp).await);
|
failure_json_check(&test::read_body_json(resp).await);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Failure test- logged in with EVERY non-relevant permission
|
// Failure test- logged in with EVERY non-relevant permission
|
||||||
let resp: ServiceResponse = req_gen(PermissionsTestContext {
|
let resp: ServiceResponse = req_gen(PermissionsTestContext {
|
||||||
@@ -270,11 +270,11 @@ impl<'a, A: Api> PermissionsTest<'a, A> {
|
|||||||
resp.status().as_u16()
|
resp.status().as_u16()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if resp.status() == StatusCode::OK {
|
if resp.status() == StatusCode::OK
|
||||||
if let Some(failure_json_check) = &self.failure_json_check {
|
&& let Some(failure_json_check) = &self.failure_json_check
|
||||||
|
{
|
||||||
failure_json_check(&test::read_body_json(resp).await);
|
failure_json_check(&test::read_body_json(resp).await);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Patch user's permissions to success permissions
|
// Patch user's permissions to success permissions
|
||||||
modify_user_team_permissions(
|
modify_user_team_permissions(
|
||||||
@@ -300,11 +300,11 @@ impl<'a, A: Api> PermissionsTest<'a, A> {
|
|||||||
resp.status().as_u16()
|
resp.status().as_u16()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if resp.status() == StatusCode::OK {
|
if resp.status() == StatusCode::OK
|
||||||
if let Some(success_json_check) = &self.success_json_check {
|
&& let Some(success_json_check) = &self.success_json_check
|
||||||
|
{
|
||||||
success_json_check(&test::read_body_json(resp).await);
|
success_json_check(&test::read_body_json(resp).await);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// If the remove_user flag is set, remove the user from the project
|
// If the remove_user flag is set, remove the user from the project
|
||||||
// Relevant for existing projects/users
|
// Relevant for existing projects/users
|
||||||
|
|||||||
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
allow-dbg-in-tests = true
|
allow-dbg-in-tests = true
|
||||||
msrv = "1.88.0"
|
msrv = "1.89.0"
|
||||||
|
|||||||
+47
-1
@@ -1,6 +1,8 @@
|
|||||||
|
name: labrinth
|
||||||
services:
|
services:
|
||||||
postgres_db:
|
postgres_db:
|
||||||
image: postgres:alpine
|
image: postgres:alpine
|
||||||
|
container_name: labrinth-postgres
|
||||||
volumes:
|
volumes:
|
||||||
- db-data:/var/lib/postgresql/data
|
- db-data:/var/lib/postgresql/data
|
||||||
ports:
|
ports:
|
||||||
@@ -16,6 +18,7 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
meilisearch:
|
meilisearch:
|
||||||
image: getmeili/meilisearch:v1.12.0
|
image: getmeili/meilisearch:v1.12.0
|
||||||
|
container_name: labrinth-meilisearch
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
ports:
|
ports:
|
||||||
- '7700:7700'
|
- '7700:7700'
|
||||||
@@ -31,6 +34,7 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
redis:
|
redis:
|
||||||
image: redis:alpine
|
image: redis:alpine
|
||||||
|
container_name: labrinth-redis
|
||||||
restart: on-failure
|
restart: on-failure
|
||||||
ports:
|
ports:
|
||||||
- '6379:6379'
|
- '6379:6379'
|
||||||
@@ -43,17 +47,59 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
clickhouse:
|
clickhouse:
|
||||||
image: clickhouse/clickhouse-server
|
image: clickhouse/clickhouse-server
|
||||||
|
container_name: labrinth-clickhouse
|
||||||
ports:
|
ports:
|
||||||
- '8123:8123'
|
- '8123:8123'
|
||||||
environment:
|
environment:
|
||||||
CLICKHOUSE_USER: default
|
CLICKHOUSE_USER: default
|
||||||
CLICKHOUSE_PASSWORD: default
|
CLICKHOUSE_PASSWORD: default
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ['CMD', 'clickhouse-client', '--query', 'SELECT 1']
|
test: ['CMD-SHELL', 'clickhouse-client --query "SELECT 1"']
|
||||||
interval: 3s
|
interval: 3s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 3
|
||||||
|
mail:
|
||||||
|
image: axllent/mailpit:v1.27
|
||||||
|
container_name: labrinth-mail
|
||||||
|
ports:
|
||||||
|
- '1025:1025'
|
||||||
|
- '8025:8025'
|
||||||
|
environment:
|
||||||
|
MP_ENABLE_SPAMASSASSIN: postmark
|
||||||
|
healthcheck:
|
||||||
|
test: ['CMD', 'wget', '-q', '-O/dev/null', 'http://localhost:8025/api/v1/info']
|
||||||
|
interval: 3s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
labrinth:
|
||||||
|
profiles:
|
||||||
|
- with-labrinth
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: ./apps/labrinth/Dockerfile
|
||||||
|
container_name: labrinth
|
||||||
|
ports:
|
||||||
|
- '8000:8000'
|
||||||
|
env_file: ./apps/labrinth/.env.docker-compose
|
||||||
|
volumes:
|
||||||
|
- labrinth-cdn-data:/tmp/modrinth
|
||||||
|
depends_on:
|
||||||
|
postgres_db:
|
||||||
|
condition: service_healthy
|
||||||
|
meilisearch:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_healthy
|
||||||
|
clickhouse:
|
||||||
|
condition: service_healthy
|
||||||
|
mail:
|
||||||
|
condition: service_healthy
|
||||||
|
develop:
|
||||||
|
watch:
|
||||||
|
- path: ./apps/labrinth
|
||||||
|
action: rebuild
|
||||||
volumes:
|
volumes:
|
||||||
meilisearch-data:
|
meilisearch-data:
|
||||||
db-data:
|
db-data:
|
||||||
redis-data:
|
redis-data:
|
||||||
|
labrinth-cdn-data:
|
||||||
|
|||||||
@@ -50,11 +50,11 @@ pub async fn parse_command(
|
|||||||
// We assume anything else is a filepath to an .mrpack file
|
// We assume anything else is a filepath to an .mrpack file
|
||||||
let path = PathBuf::from(command_string);
|
let path = PathBuf::from(command_string);
|
||||||
let path = io::canonicalize(path)?;
|
let path = io::canonicalize(path)?;
|
||||||
if let Some(ext) = path.extension() {
|
if let Some(ext) = path.extension()
|
||||||
if ext == "mrpack" {
|
&& ext == "mrpack"
|
||||||
|
{
|
||||||
return Ok(CommandPayload::RunMRPack { path });
|
return Ok(CommandPayload::RunMRPack { path });
|
||||||
}
|
}
|
||||||
}
|
|
||||||
emit_warning(&format!(
|
emit_warning(&format!(
|
||||||
"Invalid command, unrecognized filetype: {}",
|
"Invalid command, unrecognized filetype: {}",
|
||||||
path.display()
|
path.display()
|
||||||
|
|||||||
@@ -106,15 +106,15 @@ pub async fn auto_install_java(java_version: u32) -> crate::Result<PathBuf> {
|
|||||||
})?;
|
})?;
|
||||||
|
|
||||||
// removes the old installation of java
|
// removes the old installation of java
|
||||||
if let Some(file) = archive.file_names().next() {
|
if let Some(file) = archive.file_names().next()
|
||||||
if let Some(dir) = file.split('/').next() {
|
&& let Some(dir) = file.split('/').next()
|
||||||
|
{
|
||||||
let path = path.join(dir);
|
let path = path.join(dir);
|
||||||
|
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
io::remove_dir_all(path).await?;
|
io::remove_dir_all(path).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
emit_loading(&loading_bar, 0.0, Some("Extracting java"))?;
|
emit_loading(&loading_bar, 0.0, Some("Extracting java"))?;
|
||||||
archive.extract(&path).map_err(|_| {
|
archive.extract(&path).map_err(|_| {
|
||||||
|
|||||||
@@ -72,13 +72,13 @@ pub async fn remove_user(uuid: uuid::Uuid) -> crate::Result<()> {
|
|||||||
if let Some((uuid, user)) = users.remove(&uuid) {
|
if let Some((uuid, user)) = users.remove(&uuid) {
|
||||||
Credentials::remove(uuid, &state.pool).await?;
|
Credentials::remove(uuid, &state.pool).await?;
|
||||||
|
|
||||||
if user.active {
|
if user.active
|
||||||
if let Some((_, mut user)) = users.into_iter().next() {
|
&& let Some((_, mut user)) = users.into_iter().next()
|
||||||
|
{
|
||||||
user.active = true;
|
user.active = true;
|
||||||
user.upsert(&state.pool).await?;
|
user.upsert(&state.pool).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -221,15 +221,15 @@ async fn import_atlauncher_unmanaged(
|
|||||||
.unwrap_or_else(|| backup_name.to_string());
|
.unwrap_or_else(|| backup_name.to_string());
|
||||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||||
|
|
||||||
if let Some(ref project_id) = description.project_id {
|
if let Some(ref project_id) = description.project_id
|
||||||
if let Some(ref version_id) = description.version_id {
|
&& let Some(ref version_id) = description.version_id
|
||||||
|
{
|
||||||
prof.linked_data = Some(LinkedData {
|
prof.linked_data = Some(LinkedData {
|
||||||
project_id: project_id.clone(),
|
project_id: project_id.clone(),
|
||||||
version_id: version_id.clone(),
|
version_id: version_id.clone(),
|
||||||
locked: true,
|
locked: true,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
prof.icon_path = description
|
prof.icon_path = description
|
||||||
.icon
|
.icon
|
||||||
|
|||||||
@@ -383,8 +383,9 @@ pub async fn set_profile_information(
|
|||||||
.unwrap_or_else(|| backup_name.to_string());
|
.unwrap_or_else(|| backup_name.to_string());
|
||||||
prof.install_stage = ProfileInstallStage::PackInstalling;
|
prof.install_stage = ProfileInstallStage::PackInstalling;
|
||||||
|
|
||||||
if let Some(ref project_id) = description.project_id {
|
if let Some(ref project_id) = description.project_id
|
||||||
if let Some(ref version_id) = description.version_id {
|
&& let Some(ref version_id) = description.version_id
|
||||||
|
{
|
||||||
prof.linked_data = Some(LinkedData {
|
prof.linked_data = Some(LinkedData {
|
||||||
project_id: project_id.clone(),
|
project_id: project_id.clone(),
|
||||||
version_id: version_id.clone(),
|
version_id: version_id.clone(),
|
||||||
@@ -395,7 +396,6 @@ pub async fn set_profile_information(
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
prof.icon_path = description
|
prof.icon_path = description
|
||||||
.icon
|
.icon
|
||||||
|
|||||||
@@ -149,14 +149,13 @@ pub async fn install_zipped_mrpack_files(
|
|||||||
let profile_path = profile_path.clone();
|
let profile_path = profile_path.clone();
|
||||||
async move {
|
async move {
|
||||||
//TODO: Future update: prompt user for optional files in a modpack
|
//TODO: Future update: prompt user for optional files in a modpack
|
||||||
if let Some(env) = project.env {
|
if let Some(env) = project.env
|
||||||
if env
|
&& env
|
||||||
.get(&EnvType::Client)
|
.get(&EnvType::Client)
|
||||||
.is_some_and(|x| x == &SideType::Unsupported)
|
.is_some_and(|x| x == &SideType::Unsupported)
|
||||||
{
|
{
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let file = fetch_mirrors(
|
let file = fetch_mirrors(
|
||||||
&project
|
&project
|
||||||
@@ -375,15 +374,15 @@ pub async fn remove_all_related_files(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
{
|
{
|
||||||
if let Some(metadata) = &project.metadata {
|
if let Some(metadata) = &project.metadata
|
||||||
if to_remove.contains(&metadata.project_id) {
|
&& to_remove.contains(&metadata.project_id)
|
||||||
|
{
|
||||||
let path = profile_full_path.join(file_path);
|
let path = profile_full_path.join(file_path);
|
||||||
if path.exists() {
|
if path.exists() {
|
||||||
io::remove_file(&path).await?;
|
io::remove_file(&path).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Iterate over all Modrinth project file paths in the json, and remove them
|
// Iterate over all Modrinth project file paths in the json, and remove them
|
||||||
// (There should be few, but this removes any files the .mrpack intended as Modrinth projects but were unrecognized)
|
// (There should be few, but this removes any files the .mrpack intended as Modrinth projects but were unrecognized)
|
||||||
|
|||||||
@@ -337,8 +337,8 @@ pub async fn update_project(
|
|||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
.remove(project_path)
|
.remove(project_path)
|
||||||
|
&& let Some(update_version) = &file.update_version_id
|
||||||
{
|
{
|
||||||
if let Some(update_version) = &file.update_version_id {
|
|
||||||
let path = Profile::add_project_version(
|
let path = Profile::add_project_version(
|
||||||
profile_path,
|
profile_path,
|
||||||
update_version,
|
update_version,
|
||||||
@@ -353,13 +353,11 @@ pub async fn update_project(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !skip_send_event.unwrap_or(false) {
|
if !skip_send_event.unwrap_or(false) {
|
||||||
emit_profile(profile_path, ProfilePayloadType::Edited)
|
emit_profile(profile_path, ProfilePayloadType::Edited).await?;
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(path);
|
return Ok(path);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Err(crate::ErrorKind::InputError(
|
Err(crate::ErrorKind::InputError(
|
||||||
"This project cannot be updated!".to_string(),
|
"This project cannot be updated!".to_string(),
|
||||||
@@ -479,11 +477,11 @@ pub async fn export_mrpack(
|
|||||||
let included_export_candidates = included_export_candidates
|
let included_export_candidates = included_export_candidates
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|x| {
|
.filter(|x| {
|
||||||
if let Some(f) = PathBuf::from(x).file_name() {
|
if let Some(f) = PathBuf::from(x).file_name()
|
||||||
if f.to_string_lossy().starts_with(".DS_Store") {
|
&& f.to_string_lossy().starts_with(".DS_Store")
|
||||||
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
true
|
true
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
@@ -765,7 +763,7 @@ pub async fn try_update_playtime(path: &str) -> crate::Result<()> {
|
|||||||
let updated_recent_playtime = profile.recent_time_played;
|
let updated_recent_playtime = profile.recent_time_played;
|
||||||
|
|
||||||
let res = if updated_recent_playtime > 0 {
|
let res = if updated_recent_playtime > 0 {
|
||||||
// Create update struct to send to Labrinth
|
// Create update struct to send to labrinth
|
||||||
let modrinth_pack_version_id =
|
let modrinth_pack_version_id =
|
||||||
profile.linked_data.as_ref().map(|l| l.version_id.clone());
|
profile.linked_data.as_ref().map(|l| l.version_id.clone());
|
||||||
let playtime_update_json = json!({
|
let playtime_update_json = json!({
|
||||||
|
|||||||
@@ -187,6 +187,7 @@ pub enum LoadingBarType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
pub struct LoadingPayload {
|
pub struct LoadingPayload {
|
||||||
pub event: LoadingBarType,
|
pub event: LoadingBarType,
|
||||||
pub loader_uuid: Uuid,
|
pub loader_uuid: Uuid,
|
||||||
@@ -195,11 +196,7 @@ pub struct LoadingPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
pub struct OfflinePayload {
|
#[cfg(feature = "tauri")]
|
||||||
pub offline: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
|
||||||
pub struct WarningPayload {
|
pub struct WarningPayload {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
@@ -223,12 +220,14 @@ pub enum CommandPayload {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
pub struct ProcessPayload {
|
pub struct ProcessPayload {
|
||||||
pub profile_path_id: String,
|
pub profile_path_id: String,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub event: ProcessPayloadType,
|
pub event: ProcessPayloadType,
|
||||||
pub message: String,
|
pub message: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone, Debug)]
|
#[derive(Serialize, Clone, Debug)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum ProcessPayloadType {
|
pub enum ProcessPayloadType {
|
||||||
@@ -237,11 +236,13 @@ pub enum ProcessPayloadType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
|
#[cfg(feature = "tauri")]
|
||||||
pub struct ProfilePayload {
|
pub struct ProfilePayload {
|
||||||
pub profile_path_id: String,
|
pub profile_path_id: String,
|
||||||
#[serde(flatten)]
|
#[serde(flatten)]
|
||||||
pub event: ProfilePayloadType,
|
pub event: ProfilePayloadType,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
#[derive(Serialize, Clone)]
|
||||||
#[serde(tag = "event", rename_all = "snake_case")]
|
#[serde(tag = "event", rename_all = "snake_case")]
|
||||||
pub enum ProfilePayloadType {
|
pub enum ProfilePayloadType {
|
||||||
@@ -260,6 +261,16 @@ pub enum ProfilePayloadType {
|
|||||||
Removed,
|
Removed,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Clone)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
#[serde(tag = "event")]
|
||||||
|
pub enum FriendPayload {
|
||||||
|
FriendRequest { from: UserId },
|
||||||
|
UserOffline { id: UserId },
|
||||||
|
StatusUpdate { user_status: UserStatus },
|
||||||
|
StatusSync,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum EventError {
|
pub enum EventError {
|
||||||
#[error("Event state was not properly initialized")]
|
#[error("Event state was not properly initialized")]
|
||||||
@@ -272,13 +283,3 @@ pub enum EventError {
|
|||||||
#[error("Tauri error: {0}")]
|
#[error("Tauri error: {0}")]
|
||||||
TauriError(#[from] tauri::Error),
|
TauriError(#[from] tauri::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Clone)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
#[serde(tag = "event")]
|
|
||||||
pub enum FriendPayload {
|
|
||||||
FriendRequest { from: UserId },
|
|
||||||
UserOffline { id: UserId },
|
|
||||||
StatusUpdate { user_status: UserStatus },
|
|
||||||
StatusSync,
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -32,16 +32,16 @@ pub fn get_class_paths(
|
|||||||
let mut cps = libraries
|
let mut cps = libraries
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|library| {
|
.filter_map(|library| {
|
||||||
if let Some(rules) = &library.rules {
|
if let Some(rules) = &library.rules
|
||||||
if !parse_rules(
|
&& !parse_rules(
|
||||||
rules,
|
rules,
|
||||||
java_arch,
|
java_arch,
|
||||||
&QuickPlayType::None,
|
&QuickPlayType::None,
|
||||||
minecraft_updated,
|
minecraft_updated,
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !library.include_in_classpath {
|
if !library.include_in_classpath {
|
||||||
return None;
|
return None;
|
||||||
@@ -504,12 +504,12 @@ pub async fn get_processor_main_class(
|
|||||||
let mut line = line.map_err(IOError::from)?;
|
let mut line = line.map_err(IOError::from)?;
|
||||||
line.retain(|c| !c.is_whitespace());
|
line.retain(|c| !c.is_whitespace());
|
||||||
|
|
||||||
if line.starts_with("Main-Class:") {
|
if line.starts_with("Main-Class:")
|
||||||
if let Some(class) = line.split(':').nth(1) {
|
&& let Some(class) = line.split(':').nth(1)
|
||||||
|
{
|
||||||
return Ok(Some(class.to_string()));
|
return Ok(Some(class.to_string()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok::<Option<String>, crate::Error>(None)
|
Ok::<Option<String>, crate::Error>(None)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -290,12 +290,11 @@ pub async fn download_libraries(
|
|||||||
loading_try_for_each_concurrent(
|
loading_try_for_each_concurrent(
|
||||||
stream::iter(libraries.iter())
|
stream::iter(libraries.iter())
|
||||||
.map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move {
|
.map(Ok::<&Library, crate::Error>), None, loading_bar,loading_amount,num_files, None,|library| async move {
|
||||||
if let Some(rules) = &library.rules {
|
if let Some(rules) = &library.rules
|
||||||
if !parse_rules(rules, java_arch, &QuickPlayType::None, minecraft_updated) {
|
&& !parse_rules(rules, java_arch, &QuickPlayType::None, minecraft_updated) {
|
||||||
tracing::trace!("Skipped library {}", &library.name);
|
tracing::trace!("Skipped library {}", &library.name);
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if !library.downloadable {
|
if !library.downloadable {
|
||||||
tracing::trace!("Skipped non-downloadable library {}", &library.name);
|
tracing::trace!("Skipped non-downloadable library {}", &library.name);
|
||||||
@@ -311,15 +310,14 @@ pub async fn download_libraries(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(d::minecraft::LibraryDownloads { artifact: Some(ref artifact), ..}) = library.downloads {
|
if let Some(d::minecraft::LibraryDownloads { artifact: Some(ref artifact), ..}) = library.downloads
|
||||||
if !artifact.url.is_empty(){
|
&& !artifact.url.is_empty(){
|
||||||
let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore, &st.pool)
|
let bytes = fetch(&artifact.url, Some(&artifact.sha1), &st.fetch_semaphore, &st.pool)
|
||||||
.await?;
|
.await?;
|
||||||
write(&path, &bytes, &st.io_semaphore).await?;
|
write(&path, &bytes, &st.io_semaphore).await?;
|
||||||
tracing::trace!("Fetched library {} to path {:?}", &library.name, &path);
|
tracing::trace!("Fetched library {} to path {:?}", &library.name, &path);
|
||||||
return Ok::<_, crate::Error>(());
|
return Ok::<_, crate::Error>(());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let url = [
|
let url = [
|
||||||
library
|
library
|
||||||
|
|||||||
@@ -344,11 +344,11 @@ pub async fn install_minecraft(
|
|||||||
|
|
||||||
// Forge processors (90-100)
|
// Forge processors (90-100)
|
||||||
for (index, processor) in processors.iter().enumerate() {
|
for (index, processor) in processors.iter().enumerate() {
|
||||||
if let Some(sides) = &processor.sides {
|
if let Some(sides) = &processor.sides
|
||||||
if !sides.contains(&String::from("client")) {
|
&& !sides.contains(&String::from("client"))
|
||||||
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let cp = {
|
let cp = {
|
||||||
let mut cp = processor.classpath.clone();
|
let mut cp = processor.classpath.clone();
|
||||||
|
|||||||
@@ -391,11 +391,11 @@ impl DirectoryInfo {
|
|||||||
return Err(e);
|
return Err(e);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if let Some(disk_usage) = get_disk_usage(&move_dir)? {
|
if let Some(disk_usage) = get_disk_usage(&move_dir)?
|
||||||
if total_size > disk_usage {
|
&& total_size > disk_usage
|
||||||
|
{
|
||||||
return Err(crate::ErrorKind::DirectoryMoveError(format!("Not enough space to move directory to {}: only {} bytes available", app_dir.display(), disk_usage)).into());
|
return Err(crate::ErrorKind::DirectoryMoveError(format!("Not enough space to move directory to {}: only {} bytes available", app_dir.display(), disk_usage)).into());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let loader_bar_id = Arc::new(&loader_bar_id);
|
let loader_bar_id = Arc::new(&loader_bar_id);
|
||||||
futures::future::try_join_all(paths.iter().map(|x| {
|
futures::future::try_join_all(paths.iter().map(|x| {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ use ariadne::networking::message::{
|
|||||||
ClientToServerMessage, ServerToClientMessage,
|
ClientToServerMessage, ServerToClientMessage,
|
||||||
};
|
};
|
||||||
use ariadne::users::UserStatus;
|
use ariadne::users::UserStatus;
|
||||||
use async_tungstenite::WebSocketStream;
|
use async_tungstenite::WebSocketSender;
|
||||||
use async_tungstenite::tokio::{ConnectStream, connect_async};
|
use async_tungstenite::tokio::{ConnectStream, connect_async};
|
||||||
use async_tungstenite::tungstenite::Message;
|
use async_tungstenite::tungstenite::Message;
|
||||||
use async_tungstenite::tungstenite::client::IntoClientRequest;
|
use async_tungstenite::tungstenite::client::IntoClientRequest;
|
||||||
@@ -17,7 +17,6 @@ use bytes::Bytes;
|
|||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use either::Either;
|
use either::Either;
|
||||||
use futures::stream::SplitSink;
|
|
||||||
use futures::{SinkExt, StreamExt};
|
use futures::{SinkExt, StreamExt};
|
||||||
use reqwest::Method;
|
use reqwest::Method;
|
||||||
use reqwest::header::HeaderValue;
|
use reqwest::header::HeaderValue;
|
||||||
@@ -32,7 +31,7 @@ use tokio::sync::{Mutex, RwLock};
|
|||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
pub(super) type WriteSocket =
|
pub(super) type WriteSocket =
|
||||||
Arc<RwLock<Option<SplitSink<WebSocketStream<ConnectStream>, Message>>>>;
|
Arc<RwLock<Option<WebSocketSender<ConnectStream>>>>;
|
||||||
pub(super) type TunnelSockets = Arc<DashMap<Uuid, Arc<InternalTunnelSocket>>>;
|
pub(super) type TunnelSockets = Arc<DashMap<Uuid, Arc<InternalTunnelSocket>>>;
|
||||||
|
|
||||||
pub struct FriendsSocket {
|
pub struct FriendsSocket {
|
||||||
@@ -180,27 +179,24 @@ impl FriendsSocket {
|
|||||||
ServerToClientMessage::FriendSocketStoppedListening { .. } => {}, // TODO
|
ServerToClientMessage::FriendSocketStoppedListening { .. } => {}, // TODO
|
||||||
|
|
||||||
ServerToClientMessage::SocketConnected { to_socket, new_socket } => {
|
ServerToClientMessage::SocketConnected { to_socket, new_socket } => {
|
||||||
if let Some(connected_to) = sockets.get(&to_socket) {
|
if let Some(connected_to) = sockets.get(&to_socket)
|
||||||
if let InternalTunnelSocket::Listening(local_addr) = *connected_to.value().clone() {
|
&& let InternalTunnelSocket::Listening(local_addr) = *connected_to.value().clone()
|
||||||
if let Ok(new_stream) = TcpStream::connect(local_addr).await {
|
&& let Ok(new_stream) = TcpStream::connect(local_addr).await {
|
||||||
let (read, write) = new_stream.into_split();
|
let (read, write) = new_stream.into_split();
|
||||||
sockets.insert(new_socket, Arc::new(InternalTunnelSocket::Connected(Mutex::new(write))));
|
sockets.insert(new_socket, Arc::new(InternalTunnelSocket::Connected(Mutex::new(write))));
|
||||||
Self::socket_read_loop(write_handle.clone(), read, new_socket);
|
Self::socket_read_loop(write_handle.clone(), read, new_socket);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
let _ = Self::send_message(&write_handle, ClientToServerMessage::SocketClose { socket: new_socket }).await;
|
let _ = Self::send_message(&write_handle, ClientToServerMessage::SocketClose { socket: new_socket }).await;
|
||||||
},
|
},
|
||||||
ServerToClientMessage::SocketClosed { socket } => {
|
ServerToClientMessage::SocketClosed { socket } => {
|
||||||
sockets.remove_if(&socket, |_, x| matches!(*x.clone(), InternalTunnelSocket::Connected(_)));
|
sockets.remove_if(&socket, |_, x| matches!(*x.clone(), InternalTunnelSocket::Connected(_)));
|
||||||
},
|
},
|
||||||
ServerToClientMessage::SocketData { socket, data } => {
|
ServerToClientMessage::SocketData { socket, data } => {
|
||||||
if let Some(mut socket) = sockets.get_mut(&socket) {
|
if let Some(mut socket) = sockets.get_mut(&socket)
|
||||||
if let InternalTunnelSocket::Connected(ref stream) = *socket.value_mut().clone() {
|
&& let InternalTunnelSocket::Connected(ref stream) = *socket.value_mut().clone() {
|
||||||
let _ = stream.lock().await.write_all(&data).await;
|
let _ = stream.lock().await.write_all(&data).await;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -100,8 +100,8 @@ pub async fn init_watcher() -> crate::Result<FileWatcher> {
|
|||||||
let profile_path_str = profile_path_str.clone();
|
let profile_path_str = profile_path_str.clone();
|
||||||
let world = world.clone();
|
let world = world.clone();
|
||||||
tokio::spawn(async move {
|
tokio::spawn(async move {
|
||||||
if let Ok(state) = State::get().await {
|
if let Ok(state) = State::get().await
|
||||||
if let Err(e) = attached_world_data::AttachedWorldData::remove_for_world(
|
&& let Err(e) = attached_world_data::AttachedWorldData::remove_for_world(
|
||||||
&profile_path_str,
|
&profile_path_str,
|
||||||
WorldType::Singleplayer,
|
WorldType::Singleplayer,
|
||||||
&world,
|
&world,
|
||||||
@@ -109,7 +109,6 @@ pub async fn init_watcher() -> crate::Result<FileWatcher> {
|
|||||||
).await {
|
).await {
|
||||||
tracing::warn!("Failed to remove AttachedWorldData for '{world}': {e}")
|
tracing::warn!("Failed to remove AttachedWorldData for '{world}': {e}")
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
Some(ProfilePayloadType::WorldUpdated { world })
|
Some(ProfilePayloadType::WorldUpdated { world })
|
||||||
@@ -150,8 +149,9 @@ pub(crate) async fn watch_profiles_init(
|
|||||||
) {
|
) {
|
||||||
if let Ok(profiles_dir) = std::fs::read_dir(dirs.profiles_dir()) {
|
if let Ok(profiles_dir) = std::fs::read_dir(dirs.profiles_dir()) {
|
||||||
for profile_dir in profiles_dir {
|
for profile_dir in profiles_dir {
|
||||||
if let Ok(file_name) = profile_dir.map(|x| x.file_name()) {
|
if let Ok(file_name) = profile_dir.map(|x| x.file_name())
|
||||||
if let Some(file_name) = file_name.to_str() {
|
&& let Some(file_name) = file_name.to_str()
|
||||||
|
{
|
||||||
if file_name.starts_with(".DS_Store") {
|
if file_name.starts_with(".DS_Store") {
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -161,7 +161,6 @@ pub(crate) async fn watch_profiles_init(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) async fn watch_profile(
|
pub(crate) async fn watch_profile(
|
||||||
profile_path: &str,
|
profile_path: &str,
|
||||||
|
|||||||
@@ -76,11 +76,10 @@ where
|
|||||||
.loaded_config_dir
|
.loaded_config_dir
|
||||||
.clone()
|
.clone()
|
||||||
.and_then(|x| x.to_str().map(|x| x.to_string()))
|
.and_then(|x| x.to_str().map(|x| x.to_string()))
|
||||||
|
&& path != old_launcher_root_str
|
||||||
{
|
{
|
||||||
if path != old_launcher_root_str {
|
|
||||||
settings.custom_dir = Some(path);
|
settings.custom_dir = Some(path);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
settings.prev_custom_dir = Some(old_launcher_root_str.clone());
|
settings.prev_custom_dir = Some(old_launcher_root_str.clone());
|
||||||
|
|
||||||
@@ -137,19 +136,17 @@ where
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(device_token) = minecraft_auth.token {
|
if let Some(device_token) = minecraft_auth.token
|
||||||
if let Ok(private_key) =
|
&& let Ok(private_key) =
|
||||||
SigningKey::from_pkcs8_pem(&device_token.private_key)
|
SigningKey::from_pkcs8_pem(&device_token.private_key)
|
||||||
|
&& let Ok(uuid) = Uuid::parse_str(&device_token.id)
|
||||||
{
|
{
|
||||||
if let Ok(uuid) = Uuid::parse_str(&device_token.id) {
|
|
||||||
DeviceTokenPair {
|
DeviceTokenPair {
|
||||||
token: DeviceToken {
|
token: DeviceToken {
|
||||||
issue_instant: device_token.token.issue_instant,
|
issue_instant: device_token.token.issue_instant,
|
||||||
not_after: device_token.token.not_after,
|
not_after: device_token.token.not_after,
|
||||||
token: device_token.token.token,
|
token: device_token.token.token,
|
||||||
display_claims: device_token
|
display_claims: device_token.token.display_claims,
|
||||||
.token
|
|
||||||
.display_claims,
|
|
||||||
},
|
},
|
||||||
key: DeviceTokenKey {
|
key: DeviceTokenKey {
|
||||||
id: uuid,
|
id: uuid,
|
||||||
@@ -162,8 +159,6 @@ where
|
|||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut cached_entries = vec![];
|
let mut cached_entries = vec![];
|
||||||
|
|
||||||
@@ -208,13 +203,12 @@ where
|
|||||||
update_version,
|
update_version,
|
||||||
..
|
..
|
||||||
} = project.metadata
|
} = project.metadata
|
||||||
{
|
&& let Some(file) = version
|
||||||
if let Some(file) = version
|
|
||||||
.files
|
.files
|
||||||
.iter()
|
.iter()
|
||||||
.find(|x| x.hashes.get("sha512") == Some(&sha512))
|
.find(|x| x.hashes.get("sha512") == Some(&sha512))
|
||||||
|
&& let Some(sha1) = file.hashes.get("sha1")
|
||||||
{
|
{
|
||||||
if let Some(sha1) = file.hashes.get("sha1") {
|
|
||||||
if let Ok(metadata) = full_path.metadata() {
|
if let Ok(metadata) = full_path.metadata() {
|
||||||
let file_name = format!(
|
let file_name = format!(
|
||||||
"{}/{}",
|
"{}/{}",
|
||||||
@@ -228,24 +222,24 @@ where
|
|||||||
path: file_name,
|
path: file_name,
|
||||||
size: metadata.len(),
|
size: metadata.len(),
|
||||||
hash: sha1.clone(),
|
hash: sha1.clone(),
|
||||||
project_type: ProjectType::get_from_parent_folder(&full_path),
|
project_type:
|
||||||
|
ProjectType::get_from_parent_folder(
|
||||||
|
&full_path,
|
||||||
|
),
|
||||||
},
|
},
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
cached_entries.push(CacheValue::File(
|
cached_entries.push(CacheValue::File(CachedFile {
|
||||||
CachedFile {
|
|
||||||
hash: sha1.clone(),
|
hash: sha1.clone(),
|
||||||
project_id: version.project_id.clone(),
|
project_id: version.project_id.clone(),
|
||||||
version_id: version.id.clone(),
|
version_id: version.id.clone(),
|
||||||
},
|
}));
|
||||||
));
|
|
||||||
|
|
||||||
if let Some(update_version) = update_version {
|
if let Some(update_version) = update_version {
|
||||||
let mod_loader: ModLoader =
|
let mod_loader: ModLoader =
|
||||||
profile.metadata.loader.into();
|
profile.metadata.loader.into();
|
||||||
cached_entries.push(
|
cached_entries.push(CacheValue::FileUpdate(
|
||||||
CacheValue::FileUpdate(
|
|
||||||
CachedFileUpdate {
|
CachedFileUpdate {
|
||||||
hash: sha1.clone(),
|
hash: sha1.clone(),
|
||||||
game_version: profile
|
game_version: profile
|
||||||
@@ -253,15 +247,13 @@ where
|
|||||||
.game_version
|
.game_version
|
||||||
.clone(),
|
.clone(),
|
||||||
loaders: vec![
|
loaders: vec![
|
||||||
mod_loader
|
mod_loader.as_str().to_string(),
|
||||||
.as_str()
|
|
||||||
.to_string(),
|
|
||||||
],
|
],
|
||||||
update_version_id:
|
update_version_id: update_version
|
||||||
update_version.id.clone(),
|
.id
|
||||||
|
.clone(),
|
||||||
},
|
},
|
||||||
),
|
));
|
||||||
);
|
|
||||||
|
|
||||||
cached_entries.push(CacheValue::Version(
|
cached_entries.push(CacheValue::Version(
|
||||||
(*update_version).into(),
|
(*update_version).into(),
|
||||||
@@ -281,9 +273,8 @@ where
|
|||||||
badges: 0,
|
badges: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
cached_entries.push(CacheValue::User(
|
cached_entries
|
||||||
user.clone(),
|
.push(CacheValue::User(user.clone()));
|
||||||
));
|
|
||||||
|
|
||||||
TeamMember {
|
TeamMember {
|
||||||
team_id: x.team_id,
|
team_id: x.team_id,
|
||||||
@@ -297,11 +288,8 @@ where
|
|||||||
|
|
||||||
cached_entries.push(CacheValue::Team(members));
|
cached_entries.push(CacheValue::Team(members));
|
||||||
|
|
||||||
cached_entries.push(CacheValue::Version(
|
cached_entries
|
||||||
(*version).into(),
|
.push(CacheValue::Version((*version).into()));
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,17 +321,16 @@ where
|
|||||||
.map(|x| x.id),
|
.map(|x| x.id),
|
||||||
groups: profile.metadata.groups,
|
groups: profile.metadata.groups,
|
||||||
linked_data: profile.metadata.linked_data.and_then(|x| {
|
linked_data: profile.metadata.linked_data.and_then(|x| {
|
||||||
if let Some(project_id) = x.project_id {
|
if let Some(project_id) = x.project_id
|
||||||
if let Some(version_id) = x.version_id {
|
&& let Some(version_id) = x.version_id
|
||||||
if let Some(locked) = x.locked {
|
&& let Some(locked) = x.locked
|
||||||
|
{
|
||||||
return Some(LinkedData {
|
return Some(LinkedData {
|
||||||
project_id,
|
project_id,
|
||||||
version_id,
|
version_id,
|
||||||
locked,
|
locked,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
None
|
None
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -477,11 +477,10 @@ impl Credentials {
|
|||||||
..
|
..
|
||||||
},
|
},
|
||||||
) = *err.raw
|
) = *err.raw
|
||||||
|
&& (source.is_connect() || source.is_timeout())
|
||||||
{
|
{
|
||||||
if source.is_connect() || source.is_timeout() {
|
|
||||||
return Ok(Some(creds));
|
return Ok(Some(creds));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Err(err)
|
Err(err)
|
||||||
}
|
}
|
||||||
@@ -729,10 +728,9 @@ impl DeviceTokenPair {
|
|||||||
.fetch_optional(exec)
|
.fetch_optional(exec)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
if let Some(x) = res {
|
if let Some(x) = res
|
||||||
if let Ok(uuid) = Uuid::parse_str(&x.uuid) {
|
&& let Ok(uuid) = Uuid::parse_str(&x.uuid)
|
||||||
if let Ok(private_key) =
|
&& let Ok(private_key) = SigningKey::from_pkcs8_pem(&x.private_key)
|
||||||
SigningKey::from_pkcs8_pem(&x.private_key)
|
|
||||||
{
|
{
|
||||||
return Ok(Some(Self {
|
return Ok(Some(Self {
|
||||||
token: DeviceToken {
|
token: DeviceToken {
|
||||||
@@ -745,9 +743,7 @@ impl DeviceTokenPair {
|
|||||||
.single()
|
.single()
|
||||||
.unwrap_or_else(Utc::now),
|
.unwrap_or_else(Utc::now),
|
||||||
token: x.token,
|
token: x.token,
|
||||||
display_claims: serde_json::from_value(
|
display_claims: serde_json::from_value(x.display_claims)
|
||||||
x.display_claims,
|
|
||||||
)
|
|
||||||
.unwrap_or_default(),
|
.unwrap_or_default(),
|
||||||
},
|
},
|
||||||
key: DeviceTokenKey {
|
key: DeviceTokenKey {
|
||||||
@@ -758,8 +754,6 @@ impl DeviceTokenPair {
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
@@ -813,11 +807,7 @@ const MICROSOFT_CLIENT_ID: &str = "00000000402b5328";
|
|||||||
const AUTH_REPLY_URL: &str = "https://login.live.com/oauth20_desktop.srf";
|
const AUTH_REPLY_URL: &str = "https://login.live.com/oauth20_desktop.srf";
|
||||||
const REQUESTED_SCOPE: &str = "service::user.auth.xboxlive.com::MBI_SSL";
|
const REQUESTED_SCOPE: &str = "service::user.auth.xboxlive.com::MBI_SSL";
|
||||||
|
|
||||||
/* [AR] Fix
|
pub struct RequestWithDate<T> {
|
||||||
* Weird visibility issue that didn't reproduce before
|
|
||||||
* Had to make DeviceToken and RequestWithDate pub(crate) to fix compilation error
|
|
||||||
*/
|
|
||||||
pub(crate) struct RequestWithDate<T> {
|
|
||||||
pub date: DateTime<Utc>,
|
pub date: DateTime<Utc>,
|
||||||
pub value: T,
|
pub value: T,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,7 +198,7 @@ pub async fn finish_login_flow(
|
|||||||
semaphore: &FetchSemaphore,
|
semaphore: &FetchSemaphore,
|
||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite>,
|
||||||
) -> crate::Result<ModrinthCredentials> {
|
) -> crate::Result<ModrinthCredentials> {
|
||||||
// The authorization code actually is the access token, since Labrinth doesn't
|
// The authorization code actually is the access token, since labrinth doesn't
|
||||||
// issue separate authorization codes. Therefore, this is equivalent to an
|
// issue separate authorization codes. Therefore, this is equivalent to an
|
||||||
// implicit OAuth grant flow, and no additional exchanging or finalization is
|
// implicit OAuth grant flow, and no additional exchanging or finalization is
|
||||||
// needed. TODO not do this for the reasons outlined at
|
// needed. TODO not do this for the reasons outlined at
|
||||||
|
|||||||
@@ -360,8 +360,8 @@ impl Process {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Write the throwable if present
|
// Write the throwable if present
|
||||||
if !current_content.is_empty() {
|
if !current_content.is_empty()
|
||||||
if let Err(e) =
|
&& let Err(e) =
|
||||||
Process::append_to_log_file(
|
Process::append_to_log_file(
|
||||||
&log_path,
|
&log_path,
|
||||||
¤t_content,
|
¤t_content,
|
||||||
@@ -374,7 +374,6 @@ impl Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
b"log4j:Event" => {
|
b"log4j:Event" => {
|
||||||
in_event = false;
|
in_event = false;
|
||||||
// If no throwable was present, write the log entry at the end of the event
|
// If no throwable was present, write the log entry at the end of the event
|
||||||
@@ -429,8 +428,7 @@ impl Process {
|
|||||||
|
|
||||||
if let Some(timestamp) =
|
if let Some(timestamp) =
|
||||||
current_event.timestamp.as_deref()
|
current_event.timestamp.as_deref()
|
||||||
{
|
&& let Err(e) = Self::maybe_handle_server_join_logging(
|
||||||
if let Err(e) = Self::maybe_handle_server_join_logging(
|
|
||||||
profile_path,
|
profile_path,
|
||||||
timestamp,
|
timestamp,
|
||||||
message
|
message
|
||||||
@@ -439,43 +437,36 @@ impl Process {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Event::Text(mut e)) => {
|
Ok(Event::Text(mut e)) => {
|
||||||
if in_message || in_throwable {
|
if in_message || in_throwable {
|
||||||
if let Ok(text) = e.unescape() {
|
if let Ok(text) = e.xml_content() {
|
||||||
current_content.push_str(&text);
|
current_content.push_str(&text);
|
||||||
}
|
}
|
||||||
} else if !in_event
|
} else if !in_event
|
||||||
&& !e.inplace_trim_end()
|
&& !e.inplace_trim_end()
|
||||||
&& !e.inplace_trim_start()
|
&& !e.inplace_trim_start()
|
||||||
{
|
&& let Ok(text) = e.xml_content()
|
||||||
if let Ok(text) = e.unescape() {
|
&& let Err(e) = Process::append_to_log_file(
|
||||||
if let Err(e) = Process::append_to_log_file(
|
|
||||||
&log_path,
|
&log_path,
|
||||||
&format!("{text}\n"),
|
&format!("{text}\n"),
|
||||||
) {
|
)
|
||||||
|
{
|
||||||
tracing::error!(
|
tracing::error!(
|
||||||
"Failed to write to log file: {}",
|
"Failed to write to log file: {}",
|
||||||
e
|
e
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Event::CData(e)) => {
|
Ok(Event::CData(e)) => {
|
||||||
if in_message || in_throwable {
|
if (in_message || in_throwable)
|
||||||
if let Ok(text) = e
|
&& let Ok(text) = e.xml_content()
|
||||||
.escape()
|
|
||||||
.map_err(|x| x.into())
|
|
||||||
.and_then(|x| x.unescape())
|
|
||||||
{
|
{
|
||||||
current_content.push_str(&text);
|
current_content.push_str(&text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -720,16 +711,13 @@ impl Process {
|
|||||||
let logs_folder = state.directories.profile_logs_dir(&profile_path);
|
let logs_folder = state.directories.profile_logs_dir(&profile_path);
|
||||||
let log_path = logs_folder.join(LAUNCHER_LOG_PATH);
|
let log_path = logs_folder.join(LAUNCHER_LOG_PATH);
|
||||||
|
|
||||||
if log_path.exists() {
|
if log_path.exists()
|
||||||
if let Err(e) = Process::append_to_log_file(
|
&& let Err(e) = Process::append_to_log_file(
|
||||||
&log_path,
|
&log_path,
|
||||||
&format!("\n# Process exited with status: {mc_exit_status}\n"),
|
&format!("\n# Process exited with status: {mc_exit_status}\n"),
|
||||||
) {
|
)
|
||||||
tracing::warn!(
|
{
|
||||||
"Failed to write exit status to log file: {}",
|
tracing::warn!("Failed to write exit status to log file: {}", e);
|
||||||
e
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let _ = state.discord_rpc.clear_to_default(true).await;
|
let _ = state.discord_rpc.clear_to_default(true).await;
|
||||||
|
|||||||
@@ -595,8 +595,8 @@ impl Profile {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(self, semaphore, icon))]
|
#[tracing::instrument(skip(self, semaphore, icon))]
|
||||||
pub async fn set_icon<'a>(
|
pub async fn set_icon(
|
||||||
&'a mut self,
|
&mut self,
|
||||||
cache_dir: &Path,
|
cache_dir: &Path,
|
||||||
semaphore: &IoSemaphore,
|
semaphore: &IoSemaphore,
|
||||||
icon: bytes::Bytes,
|
icon: bytes::Bytes,
|
||||||
@@ -629,8 +629,8 @@ impl Profile {
|
|||||||
{
|
{
|
||||||
let subdirectory =
|
let subdirectory =
|
||||||
subdirectory.map_err(io::IOError::from)?.path();
|
subdirectory.map_err(io::IOError::from)?.path();
|
||||||
if subdirectory.is_file() {
|
if subdirectory.is_file()
|
||||||
if let Some(file_name) = subdirectory
|
&& let Some(file_name) = subdirectory
|
||||||
.file_name()
|
.file_name()
|
||||||
.and_then(|x| x.to_str())
|
.and_then(|x| x.to_str())
|
||||||
{
|
{
|
||||||
@@ -647,7 +647,6 @@ impl Profile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if profile.install_stage == ProfileInstallStage::MinecraftInstalling
|
if profile.install_stage == ProfileInstallStage::MinecraftInstalling
|
||||||
{
|
{
|
||||||
@@ -901,8 +900,8 @@ impl Profile {
|
|||||||
{
|
{
|
||||||
let subdirectory =
|
let subdirectory =
|
||||||
subdirectory.map_err(io::IOError::from)?.path();
|
subdirectory.map_err(io::IOError::from)?.path();
|
||||||
if subdirectory.is_file() {
|
if subdirectory.is_file()
|
||||||
if let Some(file_name) =
|
&& let Some(file_name) =
|
||||||
subdirectory.file_name().and_then(|x| x.to_str())
|
subdirectory.file_name().and_then(|x| x.to_str())
|
||||||
{
|
{
|
||||||
let file_size = subdirectory
|
let file_size = subdirectory
|
||||||
@@ -928,7 +927,6 @@ impl Profile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
let file_hashes = CachedEntry::get_file_hash_many(
|
let file_hashes = CachedEntry::get_file_hash_many(
|
||||||
&keys.iter().map(|s| &*s.cache_key).collect::<Vec<_>>(),
|
&keys.iter().map(|s| &*s.cache_key).collect::<Vec<_>>(),
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(skip(bytes, semaphore))]
|
#[tracing::instrument(skip(bytes, semaphore))]
|
||||||
pub async fn write<'a>(
|
pub async fn write(
|
||||||
path: &Path,
|
path: &Path,
|
||||||
bytes: &[u8],
|
bytes: &[u8],
|
||||||
semaphore: &IoSemaphore,
|
semaphore: &IoSemaphore,
|
||||||
|
|||||||
@@ -191,13 +191,13 @@ async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
|
|||||||
let mut jre_paths = HashSet::new();
|
let mut jre_paths = HashSet::new();
|
||||||
let base_path = state.directories.java_versions_dir();
|
let base_path = state.directories.java_versions_dir();
|
||||||
|
|
||||||
if base_path.is_dir() {
|
if base_path.is_dir()
|
||||||
if let Ok(dir) = std::fs::read_dir(base_path) {
|
&& let Ok(dir) = std::fs::read_dir(base_path)
|
||||||
|
{
|
||||||
for entry in dir.flatten() {
|
for entry in dir.flatten() {
|
||||||
let file_path = entry.path().join("bin");
|
let file_path = entry.path().join("bin");
|
||||||
|
|
||||||
if let Ok(contents) =
|
if let Ok(contents) = std::fs::read_to_string(file_path.clone())
|
||||||
std::fs::read_to_string(file_path.clone())
|
|
||||||
{
|
{
|
||||||
let entry = entry.path().join(contents);
|
let entry = entry.path().join(contents);
|
||||||
jre_paths.insert(entry);
|
jre_paths.insert(entry);
|
||||||
@@ -210,7 +210,6 @@ async fn get_all_autoinstalled_jre_path() -> Result<HashSet<PathBuf>, JREError>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Ok(jre_paths)
|
Ok(jre_paths)
|
||||||
})
|
})
|
||||||
@@ -300,8 +299,9 @@ pub async fn check_java_at_filepath(path: &Path) -> crate::Result<JavaVersion> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Extract version info from it
|
// Extract version info from it
|
||||||
if let Some(arch) = java_arch {
|
if let Some(arch) = java_arch
|
||||||
if let Some(version) = java_version {
|
&& let Some(version) = java_version
|
||||||
|
{
|
||||||
if let Ok(version) = extract_java_version(version) {
|
if let Ok(version) = extract_java_version(version) {
|
||||||
let path = java.to_string_lossy().to_string();
|
let path = java.to_string_lossy().to_string();
|
||||||
return Ok(JavaVersion {
|
return Ok(JavaVersion {
|
||||||
@@ -314,7 +314,6 @@ pub async fn check_java_at_filepath(path: &Path) -> crate::Result<JavaVersion> {
|
|||||||
|
|
||||||
return Err(JREError::InvalidJREVersion(version.to_owned()).into());
|
return Err(JREError::InvalidJREVersion(version.to_owned()).into());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Err(JREError::FailedJavaCheck(java).into())
|
Err(JREError::FailedJavaCheck(java).into())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,13 +33,12 @@ pub fn is_feature_supported_in(
|
|||||||
if part_version == part_first_release {
|
if part_version == part_first_release {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Ok(part_version) = part_version.parse::<u32>() {
|
if let Ok(part_version) = part_version.parse::<u32>()
|
||||||
if let Ok(part_first_release) = part_first_release.parse::<u32>() {
|
&& let Ok(part_first_release) = part_first_release.parse::<u32>()
|
||||||
if part_version > part_first_release {
|
&& part_version > part_first_release
|
||||||
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -169,6 +169,8 @@ import _TerminalSquareIcon from './icons/terminal-square.svg?component'
|
|||||||
import _TestIcon from './icons/test.svg?component'
|
import _TestIcon from './icons/test.svg?component'
|
||||||
import _TextQuoteIcon from './icons/text-quote.svg?component'
|
import _TextQuoteIcon from './icons/text-quote.svg?component'
|
||||||
import _TimerIcon from './icons/timer.svg?component'
|
import _TimerIcon from './icons/timer.svg?component'
|
||||||
|
import _ToggleLeftIcon from './icons/toggle-left.svg?component'
|
||||||
|
import _ToggleRightIcon from './icons/toggle-right.svg?component'
|
||||||
import _TransferIcon from './icons/transfer.svg?component'
|
import _TransferIcon from './icons/transfer.svg?component'
|
||||||
import _TrashIcon from './icons/trash.svg?component'
|
import _TrashIcon from './icons/trash.svg?component'
|
||||||
import _TriangleAlertIcon from './icons/triangle-alert.svg?component'
|
import _TriangleAlertIcon from './icons/triangle-alert.svg?component'
|
||||||
@@ -362,6 +364,8 @@ export const TerminalSquareIcon = _TerminalSquareIcon
|
|||||||
export const TestIcon = _TestIcon
|
export const TestIcon = _TestIcon
|
||||||
export const TextQuoteIcon = _TextQuoteIcon
|
export const TextQuoteIcon = _TextQuoteIcon
|
||||||
export const TimerIcon = _TimerIcon
|
export const TimerIcon = _TimerIcon
|
||||||
|
export const ToggleLeftIcon = _ToggleLeftIcon
|
||||||
|
export const ToggleRightIcon = _ToggleRightIcon
|
||||||
export const TransferIcon = _TransferIcon
|
export const TransferIcon = _TransferIcon
|
||||||
export const TrashIcon = _TrashIcon
|
export const TrashIcon = _TrashIcon
|
||||||
export const TriangleAlertIcon = _TriangleAlertIcon
|
export const TriangleAlertIcon = _TriangleAlertIcon
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-toggle-left-icon lucide-toggle-left"><circle cx="9" cy="12" r="3"/><rect width="20" height="14" x="2" y="5" rx="7"/></svg>
|
||||||
|
After Width: | Height: | Size: 324 B |
@@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-toggle-right-icon lucide-toggle-right"><circle cx="15" cy="12" r="3"/><rect width="20" height="14" x="2" y="5" rx="7"/></svg>
|
||||||
|
After Width: | Height: | Size: 327 B |
Generated
+7179
-6266
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -1,2 +1,2 @@
|
|||||||
[toolchain]
|
[toolchain]
|
||||||
channel = "1.88.0"
|
channel = "1.89.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user