Files
AstralRinth/apps/labrinth/src/routes/v3/friends.rs
Josiah Glosson 650ab71a83 Commonized networking (#3310)
* Fix not being able to connect to local friends socket

* Start basic work on tunneling protocol and move some code into a common crate

* Commonize message serialization logic

* Serialize Base62Ids as u64 when human-readability is not required

* Move ActiveSockets tuple into struct

* Make CI run when rust-common is updated

CI is currently broken for labrinth, however

* Fix theseus-release.yml to reference itself correctly

* Implement Labrinth side of tunneling

* Implement non-friend part of theseus tunneling

* Implement client-side except for socket loop

* Implement the socket loop

Doesn't work though. Debugging time!

* Fix config.rs

* Fix deadlock in labrinth socket handling

* Update dockerfile

* switch to workspace prepare at root level

* Wait for connection before tunneling in playground

* Move rust-common into labrinth

* Remove rust-common references from Actions

* Revert "Update dockerfile"

This reverts commit 3caad59bb474ce425d0b8928d7cee7ae1a5011bd.

* Fix Docker build

* Rebuild Theseus if common code changes

* Allow multiple connections from the same user

* Fix test building

* Move FriendSocketListening and FriendSocketStoppedListening to non-panicking TODO for now

* Make message_serialization macro take varargs for binary messages

* Improve syntax of message_serialization macro

* Remove the ability to connect to a virtual socket, and disable the ability to listen on one

* Allow the app to compile without running labrinth

* Clippy fix

* Update Rust and Clippy fix again

---------

Co-authored-by: Jai A <jaiagr+gpg@pm.me>
2025-02-28 10:52:47 -08:00

213 lines
5.8 KiB
Rust

use crate::auth::get_user_from_headers;
use crate::common::networking::message::ServerToClientMessage;
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::send_message_to_user;
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,
sockets: &ActiveSockets,
) -> Result<(), ApiError> {
if let Some(friend_status) = sockets.get_status(user_id.into())
{
send_message_to_user(
sockets,
friend_id.into(),
&ServerToClientMessage::StatusUpdate {
status: friend_status.clone(),
},
)
.await?;
}
Ok(())
}
send_friend_status(friend.user_id, friend.friend_id, &db).await?;
send_friend_status(friend.friend_id, friend.user_id, &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?;
send_message_to_user(
&db,
friend.id.into(),
&ServerToClientMessage::FriendRequest { from: user.id },
)
.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>,
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?;
crate::database::models::friend_item::FriendItem::remove(
user.id.into(),
friend.id,
&mut transaction,
)
.await?;
send_message_to_user(
&db,
friend.id.into(),
&ServerToClientMessage::FriendRequestRejected { from: user.id },
)
.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))
}