You've already forked AstralRinth
forked from didirus/AstralRinth
Small friends fixes (#4270)
* Ensure that fetch errors are properly propagated * Handle user not found errors better in add_friend * Cargo fmt * Introduce new LabrinthError returnable by fetch_advanced * Allow enter key to send a friend request
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -9026,6 +9026,7 @@ dependencies = [
|
|||||||
"daedalus",
|
"daedalus",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"data-url",
|
"data-url",
|
||||||
|
"derive_more 2.0.1",
|
||||||
"dirs",
|
"dirs",
|
||||||
"discord-rich-presence",
|
"discord-rich-presence",
|
||||||
"dotenvy",
|
"dotenvy",
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ 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.22.0"
|
deadpool-redis = "0.22.0"
|
||||||
|
derive_more = "2.0.1"
|
||||||
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"
|
||||||
|
|||||||
@@ -250,7 +250,13 @@ onUnmounted(() => {
|
|||||||
<div class="mb-4">
|
<div class="mb-4">
|
||||||
<h2 class="m-0 text-lg font-extrabold text-contrast">Username</h2>
|
<h2 class="m-0 text-lg font-extrabold text-contrast">Username</h2>
|
||||||
<p class="m-0 mt-1 leading-tight">You can add friends with their Modrinth username.</p>
|
<p class="m-0 mt-1 leading-tight">You can add friends with their Modrinth username.</p>
|
||||||
<input v-model="username" class="mt-2 w-full" type="text" placeholder="Enter username..." />
|
<input
|
||||||
|
v-model="username"
|
||||||
|
class="mt-2 w-full"
|
||||||
|
type="text"
|
||||||
|
placeholder="Enter username..."
|
||||||
|
@keyup.enter="addFriendFromModal"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ButtonStyled color="brand">
|
<ButtonStyled color="brand">
|
||||||
<button :disabled="username.length === 0" @click="addFriendFromModal">
|
<button :disabled="username.length === 0" @click="addFriendFromModal">
|
||||||
|
|||||||
@@ -189,7 +189,7 @@ impl Role {
|
|||||||
pub struct UserFriend {
|
pub struct UserFriend {
|
||||||
// The user who accepted the friend request
|
// The user who accepted the friend request
|
||||||
pub id: UserId,
|
pub id: UserId,
|
||||||
/// THe user who sent the friend request
|
/// The user who sent the friend request
|
||||||
pub friend_id: UserId,
|
pub friend_id: UserId,
|
||||||
pub accepted: bool,
|
pub accepted: bool,
|
||||||
pub created: DateTime<Utc>,
|
pub created: DateTime<Utc>,
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::auth::get_user_from_headers;
|
use crate::auth::get_user_from_headers;
|
||||||
use crate::database::models::DBUserId;
|
use crate::database::models::friend_item::DBFriend;
|
||||||
|
use crate::database::models::{DBUser, DBUserId};
|
||||||
use crate::database::redis::RedisPool;
|
use crate::database::redis::RedisPool;
|
||||||
use crate::models::pats::Scopes;
|
use crate::models::pats::Scopes;
|
||||||
use crate::models::users::UserFriend;
|
use crate::models::users::UserFriend;
|
||||||
@@ -42,102 +43,94 @@ pub async fn add_friend(
|
|||||||
.1;
|
.1;
|
||||||
|
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let friend =
|
let Some(friend) = DBUser::get(&string, &**pool, &redis).await? else {
|
||||||
crate::database::models::DBUser::get(&string, &**pool, &redis).await?;
|
return Err(ApiError::NotFound);
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(friend) = friend {
|
let mut transaction = pool.begin().await?;
|
||||||
let mut transaction = pool.begin().await?;
|
|
||||||
|
|
||||||
if let Some(friend) =
|
if let Some(friend) =
|
||||||
crate::database::models::friend_item::DBFriend::get_friend(
|
DBFriend::get_friend(user.id.into(), friend.id, &**pool).await?
|
||||||
user.id.into(),
|
{
|
||||||
friend.id,
|
if friend.accepted {
|
||||||
&**pool,
|
return Err(ApiError::InvalidInput(
|
||||||
)
|
"You are already friends with this user!".to_string(),
|
||||||
.await?
|
));
|
||||||
{
|
|
||||||
if friend.accepted {
|
|
||||||
return Err(ApiError::InvalidInput(
|
|
||||||
"You are already friends with this user!".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !friend.accepted && user.id != friend.friend_id.into() {
|
|
||||||
return Err(ApiError::InvalidInput(
|
|
||||||
"You cannot accept your own friend request!".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::database::models::friend_item::DBFriend::update_friend(
|
|
||||||
friend.user_id,
|
|
||||||
friend.friend_id,
|
|
||||||
true,
|
|
||||||
&mut transaction,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
async fn send_friend_status(
|
|
||||||
user_id: DBUserId,
|
|
||||||
friend_id: DBUserId,
|
|
||||||
sockets: &ActiveSockets,
|
|
||||||
redis: &RedisPool,
|
|
||||||
) -> Result<(), ApiError> {
|
|
||||||
if let Some(friend_status) =
|
|
||||||
get_user_status(user_id.into(), sockets, redis).await
|
|
||||||
{
|
|
||||||
broadcast_friends_message(
|
|
||||||
redis,
|
|
||||||
RedisFriendsMessage::DirectStatusUpdate {
|
|
||||||
to_user: friend_id.into(),
|
|
||||||
status: friend_status,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
send_friend_status(friend.user_id, friend.friend_id, &db, &redis)
|
|
||||||
.await?;
|
|
||||||
send_friend_status(friend.friend_id, friend.user_id, &db, &redis)
|
|
||||||
.await?;
|
|
||||||
} else {
|
|
||||||
if friend.id == user.id.into() {
|
|
||||||
return Err(ApiError::InvalidInput(
|
|
||||||
"You cannot add yourself as a friend!".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
if !friend.allow_friend_requests {
|
|
||||||
return Err(ApiError::InvalidInput(
|
|
||||||
"Friend requests are disabled for this user!".to_string(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::database::models::friend_item::DBFriend {
|
|
||||||
user_id: user.id.into(),
|
|
||||||
friend_id: friend.id,
|
|
||||||
created: Utc::now(),
|
|
||||||
accepted: false,
|
|
||||||
}
|
|
||||||
.insert(&mut transaction)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
send_message_to_user(
|
|
||||||
&db,
|
|
||||||
friend.id.into(),
|
|
||||||
&ServerToClientMessage::FriendRequest { from: user.id },
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction.commit().await?;
|
if !friend.accepted && user.id != friend.friend_id.into() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"You cannot accept your own friend request!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
Ok(HttpResponse::NoContent().body(""))
|
DBFriend::update_friend(
|
||||||
|
friend.user_id,
|
||||||
|
friend.friend_id,
|
||||||
|
true,
|
||||||
|
&mut transaction,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
async fn send_friend_status(
|
||||||
|
user_id: DBUserId,
|
||||||
|
friend_id: DBUserId,
|
||||||
|
sockets: &ActiveSockets,
|
||||||
|
redis: &RedisPool,
|
||||||
|
) -> Result<(), ApiError> {
|
||||||
|
if let Some(friend_status) =
|
||||||
|
get_user_status(user_id.into(), sockets, redis).await
|
||||||
|
{
|
||||||
|
broadcast_friends_message(
|
||||||
|
redis,
|
||||||
|
RedisFriendsMessage::DirectStatusUpdate {
|
||||||
|
to_user: friend_id.into(),
|
||||||
|
status: friend_status,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
send_friend_status(friend.user_id, friend.friend_id, &db, &redis)
|
||||||
|
.await?;
|
||||||
|
send_friend_status(friend.friend_id, friend.user_id, &db, &redis)
|
||||||
|
.await?;
|
||||||
} else {
|
} else {
|
||||||
Err(ApiError::NotFound)
|
if friend.id == user.id.into() {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"You cannot add yourself as a friend!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !friend.allow_friend_requests {
|
||||||
|
return Err(ApiError::InvalidInput(
|
||||||
|
"Friend requests are disabled for this user!".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
DBFriend {
|
||||||
|
user_id: user.id.into(),
|
||||||
|
friend_id: friend.id,
|
||||||
|
created: Utc::now(),
|
||||||
|
accepted: false,
|
||||||
|
}
|
||||||
|
.insert(&mut transaction)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
send_message_to_user(
|
||||||
|
&db,
|
||||||
|
friend.id.into(),
|
||||||
|
&ServerToClientMessage::FriendRequest { from: user.id },
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
transaction.commit().await?;
|
||||||
|
|
||||||
|
Ok(HttpResponse::NoContent().body(""))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[delete("friend/{id}")]
|
#[delete("friend/{id}")]
|
||||||
@@ -160,18 +153,12 @@ pub async fn remove_friend(
|
|||||||
.1;
|
.1;
|
||||||
|
|
||||||
let string = info.into_inner().0;
|
let string = info.into_inner().0;
|
||||||
let friend =
|
let friend = DBUser::get(&string, &**pool, &redis).await?;
|
||||||
crate::database::models::DBUser::get(&string, &**pool, &redis).await?;
|
|
||||||
|
|
||||||
if let Some(friend) = friend {
|
if let Some(friend) = friend {
|
||||||
let mut transaction = pool.begin().await?;
|
let mut transaction = pool.begin().await?;
|
||||||
|
|
||||||
crate::database::models::friend_item::DBFriend::remove(
|
DBFriend::remove(user.id.into(), friend.id, &mut transaction).await?;
|
||||||
user.id.into(),
|
|
||||||
friend.id,
|
|
||||||
&mut transaction,
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
send_message_to_user(
|
send_message_to_user(
|
||||||
&db,
|
&db,
|
||||||
@@ -205,12 +192,7 @@ pub async fn friends(
|
|||||||
.await?
|
.await?
|
||||||
.1;
|
.1;
|
||||||
|
|
||||||
let friends =
|
let friends = DBFriend::get_user_friends(user.id.into(), None, &**pool)
|
||||||
crate::database::models::friend_item::DBFriend::get_user_friends(
|
|
||||||
user.id.into(),
|
|
||||||
None,
|
|
||||||
&**pool,
|
|
||||||
)
|
|
||||||
.await?
|
.await?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(UserFriend::from)
|
.map(UserFriend::from)
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ bytemuck.workspace = true
|
|||||||
rgb.workspace = true
|
rgb.workspace = true
|
||||||
phf.workspace = true
|
phf.workspace = true
|
||||||
itertools.workspace = true
|
itertools.workspace = true
|
||||||
|
derive_more = { workspace = true, features = ["display"] }
|
||||||
|
|
||||||
chrono = { workspace = true, features = ["serde"] }
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
daedalus.workspace = true
|
daedalus.workspace = true
|
||||||
|
|||||||
@@ -3,8 +3,17 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use crate::{profile, util};
|
use crate::{profile, util};
|
||||||
use data_url::DataUrlError;
|
use data_url::DataUrlError;
|
||||||
|
use derive_more::Display;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use tracing_error::InstrumentError;
|
use tracing_error::InstrumentError;
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize, Debug, Display)]
|
||||||
|
#[display("{description}")]
|
||||||
|
pub struct LabrinthError {
|
||||||
|
pub error: String,
|
||||||
|
pub description: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(thiserror::Error, Debug)]
|
#[derive(thiserror::Error, Debug)]
|
||||||
pub enum ErrorKind {
|
pub enum ErrorKind {
|
||||||
#[error("Filesystem error: {0}")]
|
#[error("Filesystem error: {0}")]
|
||||||
@@ -56,6 +65,9 @@ pub enum ErrorKind {
|
|||||||
#[error("Error fetching URL: {0}")]
|
#[error("Error fetching URL: {0}")]
|
||||||
FetchError(#[from] reqwest::Error),
|
FetchError(#[from] reqwest::Error),
|
||||||
|
|
||||||
|
#[error("{0}")]
|
||||||
|
LabrinthError(LabrinthError),
|
||||||
|
|
||||||
#[error("Websocket error: {0}")]
|
#[error("Websocket error: {0}")]
|
||||||
WSError(#[from] async_tungstenite::tungstenite::Error),
|
WSError(#[from] async_tungstenite::tungstenite::Error),
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::ErrorKind;
|
||||||
use crate::data::ModrinthCredentials;
|
use crate::data::ModrinthCredentials;
|
||||||
use crate::event::FriendPayload;
|
use crate::event::FriendPayload;
|
||||||
use crate::event::emit::emit_friend;
|
use crate::event::emit::emit_friend;
|
||||||
@@ -322,7 +323,7 @@ impl FriendsSocket {
|
|||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy,
|
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy,
|
||||||
semaphore: &FetchSemaphore,
|
semaphore: &FetchSemaphore,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
fetch_advanced(
|
let result = fetch_advanced(
|
||||||
Method::POST,
|
Method::POST,
|
||||||
&format!("{}friend/{user_id}", env!("MODRINTH_API_URL_V3")),
|
&format!("{}friend/{user_id}", env!("MODRINTH_API_URL_V3")),
|
||||||
None,
|
None,
|
||||||
@@ -332,7 +333,18 @@ impl FriendsSocket {
|
|||||||
semaphore,
|
semaphore,
|
||||||
exec,
|
exec,
|
||||||
)
|
)
|
||||||
.await?;
|
.await;
|
||||||
|
|
||||||
|
if let Err(ref e) = result
|
||||||
|
&& let ErrorKind::LabrinthError(e) = &*e.raw
|
||||||
|
&& e.error == "not_found"
|
||||||
|
{
|
||||||
|
return Err(ErrorKind::OtherError(format!(
|
||||||
|
"No user found with username \"{user_id}\""
|
||||||
|
))
|
||||||
|
.into());
|
||||||
|
}
|
||||||
|
result?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
//! Functions for fetching information from the Internet
|
//! Functions for fetching information from the Internet
|
||||||
use super::io::{self, IOError};
|
use super::io::{self, IOError};
|
||||||
|
use crate::ErrorKind;
|
||||||
use crate::event::LoadingBarId;
|
use crate::event::LoadingBarId;
|
||||||
use crate::event::emit::emit_loading;
|
use crate::event::emit::emit_loading;
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
@@ -108,32 +109,31 @@ pub async fn fetch_advanced(
|
|||||||
|
|
||||||
let result = req.send().await;
|
let result = req.send().await;
|
||||||
match result {
|
match result {
|
||||||
Ok(x) => {
|
Ok(resp) => {
|
||||||
if x.status().is_server_error() {
|
if resp.status().is_server_error() && attempt <= FETCH_ATTEMPTS
|
||||||
if attempt <= FETCH_ATTEMPTS {
|
{
|
||||||
continue;
|
continue;
|
||||||
} else {
|
}
|
||||||
return Err(crate::Error::from(
|
if resp.status().is_client_error()
|
||||||
crate::ErrorKind::OtherError(
|
|| resp.status().is_server_error()
|
||||||
"Server error when fetching content"
|
{
|
||||||
.to_string(),
|
let backup_error = resp.error_for_status_ref().unwrap_err();
|
||||||
),
|
if let Ok(error) = resp.json().await {
|
||||||
));
|
return Err(ErrorKind::LabrinthError(error).into());
|
||||||
}
|
}
|
||||||
|
return Err(backup_error.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
let bytes = if let Some((bar, total)) = &loading_bar {
|
let bytes = if let Some((bar, total)) = &loading_bar {
|
||||||
let length = x.content_length();
|
let length = resp.content_length();
|
||||||
if let Some(total_size) = length {
|
if let Some(total_size) = length {
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
let mut stream = x.bytes_stream();
|
let mut stream = resp.bytes_stream();
|
||||||
let mut bytes = Vec::new();
|
let mut bytes = Vec::new();
|
||||||
while let Some(item) = stream.next().await {
|
while let Some(item) = stream.next().await {
|
||||||
let chunk = item.or(Err(
|
let chunk = item.or(Err(ErrorKind::NoValueFor(
|
||||||
crate::error::ErrorKind::NoValueFor(
|
"fetch bytes".to_string(),
|
||||||
"fetch bytes".to_string(),
|
)))?;
|
||||||
),
|
|
||||||
))?;
|
|
||||||
bytes.append(&mut chunk.to_vec());
|
bytes.append(&mut chunk.to_vec());
|
||||||
emit_loading(
|
emit_loading(
|
||||||
bar,
|
bar,
|
||||||
@@ -145,10 +145,10 @@ pub async fn fetch_advanced(
|
|||||||
|
|
||||||
Ok(bytes::Bytes::from(bytes))
|
Ok(bytes::Bytes::from(bytes))
|
||||||
} else {
|
} else {
|
||||||
x.bytes().await
|
resp.bytes().await
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
x.bytes().await
|
resp.bytes().await
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Ok(bytes) = bytes {
|
if let Ok(bytes) = bytes {
|
||||||
@@ -158,7 +158,7 @@ pub async fn fetch_advanced(
|
|||||||
if attempt <= FETCH_ATTEMPTS {
|
if attempt <= FETCH_ATTEMPTS {
|
||||||
continue;
|
continue;
|
||||||
} else {
|
} else {
|
||||||
return Err(crate::ErrorKind::HashError(
|
return Err(ErrorKind::HashError(
|
||||||
sha1.to_string(),
|
sha1.to_string(),
|
||||||
hash,
|
hash,
|
||||||
)
|
)
|
||||||
@@ -194,10 +194,9 @@ pub async fn fetch_mirrors(
|
|||||||
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy,
|
exec: impl sqlx::Executor<'_, Database = sqlx::Sqlite> + Copy,
|
||||||
) -> crate::Result<Bytes> {
|
) -> crate::Result<Bytes> {
|
||||||
if mirrors.is_empty() {
|
if mirrors.is_empty() {
|
||||||
return Err(crate::ErrorKind::InputError(
|
return Err(
|
||||||
"No mirrors provided!".to_string(),
|
ErrorKind::InputError("No mirrors provided!".to_string()).into()
|
||||||
)
|
);
|
||||||
.into());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (index, mirror) in mirrors.iter().enumerate() {
|
for (index, mirror) in mirrors.iter().enumerate() {
|
||||||
@@ -276,8 +275,8 @@ pub async fn write(
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn copy(
|
pub async fn copy(
|
||||||
src: impl AsRef<std::path::Path>,
|
src: impl AsRef<Path>,
|
||||||
dest: impl AsRef<std::path::Path>,
|
dest: impl AsRef<Path>,
|
||||||
semaphore: &IoSemaphore,
|
semaphore: &IoSemaphore,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
let src: &Path = src.as_ref();
|
let src: &Path = src.as_ref();
|
||||||
|
|||||||
Reference in New Issue
Block a user