You've already forked AstralRinth
forked from didirus/AstralRinth
Friends system for app (#2958)
* Friends system for app * Fix impl issues * move friends to in-memory store
This commit is contained in:
242
apps/labrinth/src/routes/v3/friends.rs
Normal file
242
apps/labrinth/src/routes/v3/friends.rs
Normal file
@@ -0,0 +1,242 @@
|
||||
use crate::auth::get_user_from_headers;
|
||||
use crate::database::models::UserId;
|
||||
use crate::database::redis::RedisPool;
|
||||
use crate::models::pats::Scopes;
|
||||
use crate::models::users::UserFriend;
|
||||
use crate::queue::session::AuthQueue;
|
||||
use crate::queue::socket::ActiveSockets;
|
||||
use crate::routes::internal::statuses::{close_socket, ServerToClientMessage};
|
||||
use crate::routes::ApiError;
|
||||
use actix_web::{delete, get, post, web, HttpRequest, HttpResponse};
|
||||
use chrono::Utc;
|
||||
use sqlx::PgPool;
|
||||
|
||||
pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
cfg.service(add_friend);
|
||||
cfg.service(remove_friend);
|
||||
cfg.service(friends);
|
||||
}
|
||||
|
||||
#[post("friend/{id}")]
|
||||
pub async fn add_friend(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
db: web::Data<ActiveSockets>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::USER_WRITE]),
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let friend =
|
||||
crate::database::models::User::get(&string, &**pool, &redis).await?;
|
||||
|
||||
if let Some(friend) = friend {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
if let Some(friend) =
|
||||
crate::database::models::friend_item::FriendItem::get_friend(
|
||||
user.id.into(),
|
||||
friend.id,
|
||||
&**pool,
|
||||
)
|
||||
.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::FriendItem::update_friend(
|
||||
friend.user_id,
|
||||
friend.friend_id,
|
||||
true,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
|
||||
async fn send_friend_status(
|
||||
user_id: UserId,
|
||||
friend_id: UserId,
|
||||
pool: &PgPool,
|
||||
redis: &RedisPool,
|
||||
sockets: &ActiveSockets,
|
||||
) -> Result<(), ApiError> {
|
||||
if let Some(pair) = sockets.auth_sockets.get(&user_id.into()) {
|
||||
let (friend_status, _) = pair.value();
|
||||
if let Some(mut socket) =
|
||||
sockets.auth_sockets.get_mut(&friend_id.into())
|
||||
{
|
||||
let (_, socket) = socket.value_mut();
|
||||
|
||||
if socket
|
||||
.text(serde_json::to_string(
|
||||
&ServerToClientMessage::StatusUpdate {
|
||||
status: friend_status.clone(),
|
||||
},
|
||||
)?)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
close_socket(
|
||||
friend_id.into(),
|
||||
pool,
|
||||
redis,
|
||||
sockets,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
send_friend_status(
|
||||
friend.user_id,
|
||||
friend.friend_id,
|
||||
&pool,
|
||||
&redis,
|
||||
&db,
|
||||
)
|
||||
.await?;
|
||||
send_friend_status(
|
||||
friend.friend_id,
|
||||
friend.user_id,
|
||||
&pool,
|
||||
&redis,
|
||||
&db,
|
||||
)
|
||||
.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::FriendItem {
|
||||
user_id: user.id.into(),
|
||||
friend_id: friend.id,
|
||||
created: Utc::now(),
|
||||
accepted: false,
|
||||
}
|
||||
.insert(&mut transaction)
|
||||
.await?;
|
||||
|
||||
if let Some(mut socket) = db.auth_sockets.get_mut(&friend.id.into())
|
||||
{
|
||||
let (_, socket) = socket.value_mut();
|
||||
|
||||
if socket
|
||||
.text(serde_json::to_string(
|
||||
&ServerToClientMessage::FriendRequest { from: user.id },
|
||||
)?)
|
||||
.await
|
||||
.is_err()
|
||||
{
|
||||
close_socket(user.id, &pool, &redis, &db).await?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[delete("friend/{id}")]
|
||||
pub async fn remove_friend(
|
||||
req: HttpRequest,
|
||||
info: web::Path<(String,)>,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::USER_WRITE]),
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let string = info.into_inner().0;
|
||||
let friend =
|
||||
crate::database::models::User::get(&string, &**pool, &redis).await?;
|
||||
|
||||
if let Some(friend) = friend {
|
||||
let mut transaction = pool.begin().await?;
|
||||
|
||||
crate::database::models::friend_item::FriendItem::remove(
|
||||
user.id.into(),
|
||||
friend.id,
|
||||
&mut transaction,
|
||||
)
|
||||
.await?;
|
||||
|
||||
transaction.commit().await?;
|
||||
|
||||
Ok(HttpResponse::NoContent().body(""))
|
||||
} else {
|
||||
Err(ApiError::NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
#[get("friends")]
|
||||
pub async fn friends(
|
||||
req: HttpRequest,
|
||||
pool: web::Data<PgPool>,
|
||||
redis: web::Data<RedisPool>,
|
||||
session_queue: web::Data<AuthQueue>,
|
||||
) -> Result<HttpResponse, ApiError> {
|
||||
let user = get_user_from_headers(
|
||||
&req,
|
||||
&**pool,
|
||||
&redis,
|
||||
&session_queue,
|
||||
Some(&[Scopes::USER_READ]),
|
||||
)
|
||||
.await?
|
||||
.1;
|
||||
|
||||
let friends =
|
||||
crate::database::models::friend_item::FriendItem::get_user_friends(
|
||||
user.id.into(),
|
||||
None,
|
||||
&**pool,
|
||||
)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(UserFriend::from)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(HttpResponse::Ok().json(friends))
|
||||
}
|
||||
@@ -5,6 +5,7 @@ use serde_json::json;
|
||||
|
||||
pub mod analytics_get;
|
||||
pub mod collections;
|
||||
pub mod friends;
|
||||
pub mod images;
|
||||
pub mod notifications;
|
||||
pub mod organizations;
|
||||
@@ -42,7 +43,8 @@ pub fn config(cfg: &mut web::ServiceConfig) {
|
||||
.configure(users::config)
|
||||
.configure(version_file::config)
|
||||
.configure(payouts::config)
|
||||
.configure(versions::config),
|
||||
.configure(versions::config)
|
||||
.configure(friends::config),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -300,6 +300,7 @@ pub struct EditUser {
|
||||
pub badges: Option<Badges>,
|
||||
#[validate(length(max = 160))]
|
||||
pub venmo_handle: Option<String>,
|
||||
pub allow_friend_requests: Option<bool>,
|
||||
}
|
||||
|
||||
pub async fn user_edit(
|
||||
@@ -438,6 +439,20 @@ pub async fn user_edit(
|
||||
.await?;
|
||||
}
|
||||
|
||||
if let Some(allow_friend_requests) = &user.allow_friend_requests {
|
||||
sqlx::query!(
|
||||
"
|
||||
UPDATE users
|
||||
SET allow_friend_requests = $1
|
||||
WHERE (id = $2)
|
||||
",
|
||||
allow_friend_requests,
|
||||
id as crate::database::models::ids::UserId,
|
||||
)
|
||||
.execute(&mut *transaction)
|
||||
.await?;
|
||||
}
|
||||
|
||||
transaction.commit().await?;
|
||||
User::clear_caches(&[(id, Some(actual_user.username))], &redis)
|
||||
.await?;
|
||||
|
||||
Reference in New Issue
Block a user