MR App 0.9.5 - Big bugfix update (#3585)

* Add launcher_feature_version to Profile

* Misc fixes

- Add typing to theme and settings stuff
- Push instance route on creation from installing a modpack
- Fixed servers not reloading properly when first added

* Make old instances scan the logs folder for joined servers on launcher startup

* Create AttachedWorldData

* Change AttachedWorldData interface

* Rename WorldType::World to WorldType::Singleplayer

* Implement world display status system

* Fix Minecraft font

* Fix set_world_display_status Tauri error

* Add 'Play instance' option

* Add option to disable worlds showing in Home

* Fixes

- Fix available server filter only showing if there are some available
- Fixed server and singleplayer filters sometimes showing when there are only servers or singleplayer worlds
- Fixed new worlds not being automatically added when detected
- Rephrased Jump back into worlds option description

* Fixed sometimes more than 6 items showing up in Jump back in

* Fix servers.dat issue with instances you haven't played before

* Fix too large of bulk requests being made, limit max to 800 #3430

* Add hiding from home page, add types to Mods.vue

* Make recent worlds go into grid when display is huge

* Fix lint

* Remove redundant media query

* Fix protocol version on home page, and home page being blocked by pinging servers

* Clippy fix

* More Clippy fixes

* Fix Prettier lints

* Undo `from_string` changes

---------

Co-authored-by: Josiah Glosson <soujournme@gmail.com>
Co-authored-by: Alejandro González <me@alegon.dev>
This commit is contained in:
Prospector
2025-05-01 16:13:13 -07:00
committed by GitHub
parent 4a2605bc1e
commit 3dad6b317f
123 changed files with 1622 additions and 744 deletions

View File

@@ -95,7 +95,7 @@ impl actix_web::ResponseError for OAuthError {
);
if let Some(state) = self.state.as_ref() {
redirect_uri = format!("{}&state={}", redirect_uri, state);
redirect_uri = format!("{redirect_uri}&state={state}");
}
HttpResponse::Ok()

View File

@@ -414,7 +414,7 @@ fn generate_access_token() -> String {
.take(60)
.map(char::from)
.collect::<String>();
format!("mro_{}", random)
format!("mro_{random}")
}
async fn init_oauth_code_flow(

View File

@@ -32,7 +32,7 @@ impl Display for ErrorPage {
let html = include_str!("error.html")
.replace("{{ code }}", &self.code.to_string())
.replace("{{ message }}", &self.message);
write!(f, "{}", html)?;
write!(f, "{html}")?;
Ok(())
}

View File

@@ -103,8 +103,7 @@ impl MinecraftGameVersion {
}
_ => {
return Err(DatabaseError::SchemaError(format!(
"Game version requires field value to be an enum: {:?}",
version_field
"Game version requires field value to be an enum: {version_field:?}"
)));
}
};

View File

@@ -1080,8 +1080,7 @@ impl VersionFieldValue {
let field_name = field_type.to_str();
let did_not_exist_error = |field_name: &str, desired_field: &str| {
DatabaseError::SchemaError(format!(
"Field name {} for field {} in does not exist",
desired_field, field_name
"Field name {desired_field} for field {field_name} in does not exist"
))
};
@@ -1103,8 +1102,7 @@ impl VersionFieldValue {
.collect::<Vec<_>>();
if field_id.len() > 1 {
return Err(DatabaseError::SchemaError(format!(
"Multiple field ids for field {}",
field_name
"Multiple field ids for field {field_name}"
)));
}

View File

@@ -912,7 +912,7 @@ impl Version {
file.hashes.iter().map(|(algo, hash)| {
(
VERSION_FILES_NAMESPACE,
Some(format!("{}_{}", algo, hash)),
Some(format!("{algo}_{hash}")),
)
})
},

View File

@@ -80,10 +80,9 @@ impl From<DBNotification> for Notification {
} => (
"A project you follow has been updated!".to_string(),
format!(
"The project {} has released a new version: {}",
project_id, version_id
"The project {project_id} has released a new version: {version_id}"
),
format!("/project/{}/version/{}", project_id, version_id),
format!("/project/{project_id}/version/{version_id}"),
vec![],
),
NotificationBody::TeamInvite {
@@ -93,8 +92,8 @@ impl From<DBNotification> for Notification {
..
} => (
"You have been invited to join a team!".to_string(),
format!("An invite has been sent for you to be {} of a team", role),
format!("/project/{}", project_id),
format!("An invite has been sent for you to be {role} of a team"),
format!("/project/{project_id}"),
vec![
NotificationAction {
name: "Accept".to_string(),
@@ -117,10 +116,9 @@ impl From<DBNotification> for Notification {
} => (
"You have been invited to join an organization!".to_string(),
format!(
"An invite has been sent for you to be {} of an organization",
role
"An invite has been sent for you to be {role} of an organization"
),
format!("/organization/{}", organization_id),
format!("/organization/{organization_id}"),
vec![
NotificationAction {
name: "Accept".to_string(),
@@ -149,7 +147,7 @@ impl From<DBNotification> for Notification {
old_status.as_friendly_str(),
new_status.as_friendly_str()
),
format!("/project/{}", project_id),
format!("/project/{project_id}"),
vec![],
),
NotificationBody::ModeratorMessage {
@@ -160,9 +158,9 @@ impl From<DBNotification> for Notification {
"A moderator has sent you a message!".to_string(),
"Click on the link to read more.".to_string(),
if let Some(project_id) = project_id {
format!("/project/{}", project_id)
format!("/project/{project_id}")
} else if let Some(report_id) = report_id {
format!("/project/{}", report_id)
format!("/project/{report_id}")
} else {
"#".to_string()
},

View File

@@ -222,8 +222,7 @@ pub async fn delphi_result_ingest(
for (issue, trace) in &body.issues {
for (path, code) in trace {
header.push_str(&format!(
"\n issue {issue} found at file {}: \n ```\n{}\n```",
path, code
"\n issue {issue} found at file {path}: \n ```\n{code}\n```"
));
}
}
@@ -242,10 +241,8 @@ pub async fn delphi_result_ingest(
for (issue, trace) in &body.issues {
for path in trace.keys() {
thread_header.push_str(&format!(
"\n\n- issue {issue} found at file {}",
path
));
thread_header
.push_str(&format!("\n\n- issue {issue} found at file {path}"));
}
if trace.is_empty() {

View File

@@ -247,7 +247,7 @@ impl AuthProvider {
state: String,
) -> Result<String, AuthenticationError> {
let self_addr = dotenvy::var("SELF_ADDR")?;
let raw_redirect_uri = format!("{}/v2/auth/callback", self_addr);
let raw_redirect_uri = format!("{self_addr}/v2/auth/callback");
let redirect_uri = urlencoding::encode(&raw_redirect_uri);
Ok(match self {
@@ -255,30 +255,24 @@ impl AuthProvider {
let client_id = dotenvy::var("GITHUB_CLIENT_ID")?;
format!(
"https://github.com/login/oauth/authorize?client_id={}&prompt=select_account&state={}&scope=read%3Auser%20user%3Aemail&redirect_uri={}",
client_id,
state,
redirect_uri,
"https://github.com/login/oauth/authorize?client_id={client_id}&prompt=select_account&state={state}&scope=read%3Auser%20user%3Aemail&redirect_uri={redirect_uri}",
)
}
AuthProvider::Discord => {
let client_id = dotenvy::var("DISCORD_CLIENT_ID")?;
format!("https://discord.com/api/oauth2/authorize?client_id={}&state={}&response_type=code&scope=identify%20email&redirect_uri={}", client_id, state, redirect_uri)
format!("https://discord.com/api/oauth2/authorize?client_id={client_id}&state={state}&response_type=code&scope=identify%20email&redirect_uri={redirect_uri}")
}
AuthProvider::Microsoft => {
let client_id = dotenvy::var("MICROSOFT_CLIENT_ID")?;
format!("https://login.live.com/oauth20_authorize.srf?client_id={}&response_type=code&scope=user.read&state={}&prompt=select_account&redirect_uri={}", client_id, state, redirect_uri)
format!("https://login.live.com/oauth20_authorize.srf?client_id={client_id}&response_type=code&scope=user.read&state={state}&prompt=select_account&redirect_uri={redirect_uri}")
}
AuthProvider::GitLab => {
let client_id = dotenvy::var("GITLAB_CLIENT_ID")?;
format!(
"https://gitlab.com/oauth/authorize?client_id={}&state={}&scope=read_user+profile+email&response_type=code&redirect_uri={}",
client_id,
state,
redirect_uri,
"https://gitlab.com/oauth/authorize?client_id={client_id}&state={state}&scope=read_user+profile+email&response_type=code&redirect_uri={redirect_uri}",
)
}
AuthProvider::Google => {
@@ -342,8 +336,7 @@ impl AuthProvider {
let client_secret = dotenvy::var("GITHUB_CLIENT_SECRET")?;
let url = format!(
"https://github.com/login/oauth/access_token?client_id={}&client_secret={}&code={}&redirect_uri={}",
client_id, client_secret, code, redirect_uri
"https://github.com/login/oauth/access_token?client_id={client_id}&client_secret={client_secret}&code={code}&redirect_uri={redirect_uri}"
);
let token: AccessToken = reqwest::Client::new()
@@ -482,9 +475,8 @@ impl AuthProvider {
form.insert("openid.mode".to_string(), "check_authentication");
for val in signed.split(',') {
if let Some(arr_val) = query.get(&format!("openid.{}", val))
{
form.insert(format!("openid.{}", val), &**arr_val);
if let Some(arr_val) = query.get(&format!("openid.{val}")) {
form.insert(format!("openid.{val}"), &**arr_val);
}
}
@@ -621,8 +613,7 @@ impl AuthProvider {
email: discord_user.email,
avatar_url: discord_user.avatar.map(|x| {
format!(
"https://cdn.discordapp.com/avatars/{}/{}.webp",
id, x
"https://cdn.discordapp.com/avatars/{id}/{x}.webp"
)
}),
bio: None,
@@ -741,9 +732,7 @@ impl AuthProvider {
let response: String = reqwest::get(
&format!(
"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={}&steamids={}",
api_key,
token
"https://api.steampowered.com/ISteamUser/GetPlayerSummaries/v0002/?key={api_key}&steamids={token}"
)
)
.await?
@@ -1367,7 +1356,7 @@ pub async fn create_account_with_password(
if let Some(feedback) =
score.feedback().clone().and_then(|x| x.warning())
{
format!("Password too weak: {}", feedback)
format!("Password too weak: {feedback}")
} else {
"Specified password is too weak! Please improve its strength."
.to_string()
@@ -2030,7 +2019,7 @@ pub async fn change_password(
if let Some(feedback) =
score.feedback().clone().and_then(|x| x.warning())
{
format!("Password too weak: {}", feedback)
format!("Password too weak: {feedback}")
} else {
"Specified password is too weak! Please improve its strength.".to_string()
},
@@ -2085,8 +2074,8 @@ pub async fn change_password(
send_email(
email,
&format!("Password {}", changed),
&format!("Your password has been {} on your account.", changed),
&format!("Password {changed}"),
&format!("Your password has been {changed} on your account."),
"If you did not make this change, please contact us immediately through our support channels on Discord or via email (support@modrinth.com).",
None,
)?;

View File

@@ -113,7 +113,7 @@ pub async fn create_pat(
.take(60)
.map(char::from)
.collect::<String>();
let token = format!("mrp_{}", token);
let token = format!("mrp_{token}");
let name = info.name.clone();
database::models::pat_item::PersonalAccessToken {

View File

@@ -116,7 +116,7 @@ pub async fn forge_updates(
for game_version in &game_versions {
response
.promos
.entry(format!("{}-recommended", game_version))
.entry(format!("{game_version}-recommended"))
.or_insert_with(|| version.version_number.clone());
}
}
@@ -124,7 +124,7 @@ pub async fn forge_updates(
for game_version in &game_versions {
response
.promos
.entry(format!("{}-latest", game_version))
.entry(format!("{game_version}-latest"))
.or_insert_with(|| version.version_number.clone());
}
}

View File

@@ -144,7 +144,7 @@ where
match (
"Content-Type",
format!("multipart/form-data; boundary={}", boundary).as_str(),
format!("multipart/form-data; boundary={boundary}").as_str(),
)
.try_into_pair()
{
@@ -153,8 +153,7 @@ where
}
Err(err) => {
CreateError::InvalidInput(format!(
"Error inserting test header: {:?}.",
err
"Error inserting test header: {err:?}."
));
}
};

View File

@@ -424,7 +424,7 @@ pub async fn collection_icon_edit(
let collection_id: CollectionId = collection_item.id.into();
let upload_result = crate::util::img::upload_image_optimized(
&format!("data/{}", collection_id),
&format!("data/{collection_id}"),
bytes.freeze(),
&ext.ext,
Some(96),

View File

@@ -393,7 +393,7 @@ pub async fn oauth_client_icon_edit(
)
.await?;
let upload_result = upload_image_optimized(
&format!("data/{}", client_id),
&format!("data/{client_id}"),
bytes.freeze(),
&ext.ext,
Some(96),

View File

@@ -1096,7 +1096,7 @@ pub async fn organization_icon_edit(
let organization_id: OrganizationId = organization_item.id.into();
let upload_result = crate::util::img::upload_image_optimized(
&format!("data/{}", organization_id),
&format!("data/{organization_id}"),
bytes.freeze(),
&ext.ext,
Some(96),

View File

@@ -676,8 +676,7 @@ pub async fn cancel_payout(
.make_paypal_request::<(), ()>(
Method::POST,
&format!(
"payments/payouts-item/{}/cancel",
platform_id
"payments/payouts-item/{platform_id}/cancel"
),
None,
None,
@@ -689,7 +688,7 @@ pub async fn cancel_payout(
payouts
.make_tremendous_request::<(), ()>(
Method::POST,
&format!("rewards/{}/cancel", platform_id),
&format!("rewards/{platform_id}/cancel"),
None,
)
.await?;

View File

@@ -810,8 +810,7 @@ async fn project_create_inner(
|| image.context.inner_id().is_some()
{
return Err(CreateError::InvalidInput(format!(
"Image {} is not unused and in the 'project' context",
image_id
"Image {image_id} is not unused and in the 'project' context"
)));
}
@@ -830,8 +829,7 @@ async fn project_create_inner(
image_item::Image::clear_cache(image.id.into(), redis).await?;
} else {
return Err(CreateError::InvalidInput(format!(
"Image {} does not exist",
image_id
"Image {image_id} does not exist"
)));
}
}

View File

@@ -710,10 +710,8 @@ pub async fn project_edit(
));
}
let ids_to_delete = links
.iter()
.map(|(name, _)| name.clone())
.collect::<Vec<String>>();
let ids_to_delete =
links.keys().cloned().collect::<Vec<String>>();
// Deletes all links from hashmap- either will be deleted or be replaced
sqlx::query!(
"
@@ -1270,10 +1268,7 @@ pub async fn projects_edit(
.await?;
if let Some(links) = &bulk_edit_project.link_urls {
let ids_to_delete = links
.iter()
.map(|(name, _)| name.clone())
.collect::<Vec<String>>();
let ids_to_delete = links.keys().cloned().collect::<Vec<String>>();
// Deletes all links from hashmap- either will be deleted or be replaced
sqlx::query!(
"
@@ -1482,7 +1477,7 @@ pub async fn project_icon_edit(
let project_id: ProjectId = project_item.inner.id.into();
let upload_result = upload_image_optimized(
&format!("data/{}", project_id),
&format!("data/{project_id}"),
bytes.freeze(),
&ext.ext,
Some(96),
@@ -1700,7 +1695,7 @@ pub async fn add_gallery_item(
let id: ProjectId = project_item.inner.id.into();
let upload_result = upload_image_optimized(
&format!("data/{}/images", id),
&format!("data/{id}/images"),
bytes.freeze(),
&ext.ext,
Some(350),

View File

@@ -178,8 +178,7 @@ pub async fn report_create(
|| image.context.inner_id().is_some()
{
return Err(ApiError::InvalidInput(format!(
"Image {} is not unused and in the 'report' context",
image_id
"Image {image_id} is not unused and in the 'report' context"
)));
}
@@ -198,8 +197,7 @@ pub async fn report_create(
image_item::Image::clear_cache(image.id.into(), &redis).await?;
} else {
return Err(ApiError::InvalidInput(format!(
"Image {} could not be found",
image_id
"Image {image_id} could not be found"
)));
}
}

View File

@@ -527,8 +527,7 @@ pub async fn thread_send_message(
) || image.context.inner_id().is_some()
{
return Err(ApiError::InvalidInput(format!(
"Image {} is not unused and in the 'thread_message' context",
image_id
"Image {image_id} is not unused and in the 'thread_message' context"
)));
}
@@ -548,8 +547,7 @@ pub async fn thread_send_message(
.await?;
} else {
return Err(ApiError::InvalidInput(format!(
"Image {} does not exist",
image_id
"Image {image_id} does not exist"
)));
}
}

View File

@@ -595,7 +595,7 @@ pub async fn user_icon_edit(
let user_id: UserId = actual_user.id.into();
let upload_result = crate::util::img::upload_image_optimized(
&format!("data/{}", user_id),
&format!("data/{user_id}"),
bytes.freeze(),
&ext.ext,
Some(96),

View File

@@ -486,8 +486,7 @@ async fn version_create_inner(
|| image.context.inner_id().is_some()
{
return Err(CreateError::InvalidInput(format!(
"Image {} is not unused and in the 'version' context",
image_id
"Image {image_id} is not unused and in the 'version' context"
)));
}
@@ -506,8 +505,7 @@ async fn version_create_inner(
image_item::Image::clear_cache(image.id.into(), redis).await?;
} else {
return Err(CreateError::InvalidInput(format!(
"Image {} does not exist",
image_id
"Image {image_id} does not exist"
)));
}
}
@@ -810,7 +808,7 @@ pub async fn upload_file(
) -> Result<(), CreateError> {
let (file_name, file_extension) = get_name_ext(content_disposition)?;
if other_file_names.contains(&format!("{}.{}", file_name, file_extension)) {
if other_file_names.contains(&format!("{file_name}.{file_extension}")) {
return Err(CreateError::InvalidInput(
"Duplicate files are not allowed to be uploaded to Modrinth!"
.to_string(),

View File

@@ -67,5 +67,5 @@ pub async fn push_back_user_expiry(
}
fn get_field_name(user: UserId) -> String {
format!("user_status:{}", user)
format!("user_status:{user}")
}

View File

@@ -34,7 +34,7 @@ impl AppendsMultipart for TestRequest {
let (boundary, payload) = generate_multipart(data);
self.append_header((
"Content-Type",
format!("multipart/form-data; boundary={}", boundary),
format!("multipart/form-data; boundary={boundary}"),
))
.set_payload(payload)
}
@@ -62,17 +62,12 @@ pub fn generate_multipart(
if let Some(filename) = &segment.filename {
payload.extend_from_slice(
format!("; filename=\"{filename}\"", filename = filename)
.as_bytes(),
format!("; filename=\"{filename}\"").as_bytes(),
);
}
if let Some(content_type) = &segment.content_type {
payload.extend_from_slice(
format!(
"\r\nContent-Type: {content_type}",
content_type = content_type
)
.as_bytes(),
format!("\r\nContent-Type: {content_type}").as_bytes(),
);
}
payload.extend_from_slice(b"\r\n\r\n");
@@ -87,9 +82,7 @@ pub fn generate_multipart(
}
payload.extend_from_slice(b"\r\n");
}
payload.extend_from_slice(
format!("--{boundary}--\r\n", boundary = boundary).as_bytes(),
);
payload.extend_from_slice(format!("--{boundary}--\r\n").as_bytes());
(boundary, Bytes::from(payload))
}

View File

@@ -51,8 +51,7 @@ pub async fn upload_image_optimized(
let content_type = crate::util::ext::get_image_content_type(file_extension)
.ok_or_else(|| {
ApiError::InvalidInput(format!(
"Invalid format for image: {}",
file_extension
"Invalid format for image: {file_extension}"
))
})?;
@@ -91,7 +90,7 @@ pub async fn upload_image_optimized(
let upload_data = file_host
.upload_file(
content_type,
&format!("{}/{}.{}", upload_folder, hash, file_extension),
&format!("{upload_folder}/{hash}.{file_extension}"),
bytes,
)
.await?;