2025-03-25 22:52:47 -04:00
|
|
|
use crate::{
|
2025-06-13 17:47:00 -04:00
|
|
|
check_user_blocked_or_private, get_user_from_token,
|
2025-03-25 22:52:47 -04:00
|
|
|
model::{ApiReturn, Error},
|
2025-06-13 17:47:00 -04:00
|
|
|
routes::pages::PaginatedQuery,
|
|
|
|
State,
|
|
|
|
};
|
|
|
|
use axum::{
|
|
|
|
extract::{Path, Query},
|
|
|
|
response::IntoResponse,
|
|
|
|
Extension, Json,
|
2025-03-25 22:52:47 -04:00
|
|
|
};
|
|
|
|
use axum_extra::extract::CookieJar;
|
2025-06-13 17:47:00 -04:00
|
|
|
use tetratto_core::model::{
|
2025-06-27 03:45:50 -04:00
|
|
|
auth::{AchievementName, FollowResult, IpBlock, Notification, UserBlock, UserFollow},
|
2025-06-13 17:47:00 -04:00
|
|
|
oauth,
|
|
|
|
};
|
2025-03-25 22:52:47 -04:00
|
|
|
|
|
|
|
/// Toggle following on the given user.
|
|
|
|
pub async fn follow_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
let data = &(data.read().await).0;
|
2025-06-13 17:47:00 -04:00
|
|
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageFollowing) {
|
2025-03-25 22:52:47 -04:00
|
|
|
Some(ua) => ua,
|
|
|
|
None => return Json(Error::NotAllowed.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Ok(userfollow) = data.get_userfollow_by_initiator_receiver(user.id, id).await {
|
|
|
|
// delete
|
2025-04-17 21:48:45 -04:00
|
|
|
match data.delete_userfollow(userfollow.id, &user, false).await {
|
2025-03-25 22:52:47 -04:00
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "User unfollowed".to_string(),
|
|
|
|
payload: (),
|
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-25 22:52:47 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// create
|
2025-04-14 17:21:52 -04:00
|
|
|
match data
|
|
|
|
.create_userfollow(UserFollow::new(user.id, id), false)
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(r) => {
|
|
|
|
if r == FollowResult::Followed {
|
|
|
|
if let Err(e) = data
|
|
|
|
.create_notification(Notification::new(
|
|
|
|
"Somebody has followed you!".to_string(),
|
|
|
|
format!(
|
|
|
|
"You have been followed by [@{}](/api/v1/auth/user/find/{}).",
|
|
|
|
user.username, user.id
|
|
|
|
),
|
|
|
|
id,
|
|
|
|
))
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
return Json(e.into());
|
|
|
|
};
|
2025-03-31 20:02:09 -04:00
|
|
|
|
2025-06-27 03:45:50 -04:00
|
|
|
if let Err(e) = data
|
|
|
|
.add_achievement(&user, AchievementName::FollowUser.into())
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
return Json(e.into());
|
|
|
|
}
|
|
|
|
|
2025-04-14 17:21:52 -04:00
|
|
|
Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "User followed".to_string(),
|
|
|
|
payload: (),
|
|
|
|
})
|
|
|
|
} else {
|
|
|
|
Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "Asked to follow user".to_string(),
|
|
|
|
payload: (),
|
|
|
|
})
|
|
|
|
}
|
2025-03-31 20:02:09 -04:00
|
|
|
}
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-25 22:52:47 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-14 17:21:52 -04:00
|
|
|
pub async fn cancel_follow_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
let data = &(data.read().await).0;
|
2025-06-13 17:47:00 -04:00
|
|
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageFollowing) {
|
2025-04-14 17:21:52 -04:00
|
|
|
Some(ua) => ua,
|
|
|
|
None => return Json(Error::NotAllowed.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
match data.delete_request(user.id, id, &user, true).await {
|
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "Follow request deleted".to_string(),
|
|
|
|
payload: (),
|
|
|
|
}),
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn accept_follow_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
let data = &(data.read().await).0;
|
2025-06-13 17:47:00 -04:00
|
|
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageFollowers) {
|
2025-04-14 17:21:52 -04:00
|
|
|
Some(ua) => ua,
|
|
|
|
None => return Json(Error::NotAllowed.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
// delete the request
|
|
|
|
if let Err(e) = data.delete_request(id, user.id, &user, true).await {
|
|
|
|
return Json(e.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
// create follow
|
|
|
|
match data
|
|
|
|
.create_userfollow(UserFollow::new(id, user.id), true)
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(_) => {
|
|
|
|
if let Err(e) = data
|
|
|
|
.create_notification(Notification::new(
|
|
|
|
"Somebody has accepted your follow request!".to_string(),
|
|
|
|
format!(
|
|
|
|
"You are now following [@{}](/api/v1/auth/user/find/{}).",
|
|
|
|
user.username, user.id
|
|
|
|
),
|
|
|
|
id,
|
|
|
|
))
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
return Json(e.into());
|
|
|
|
};
|
|
|
|
|
|
|
|
Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "User follow request accepted".to_string(),
|
|
|
|
payload: (),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-25 22:52:47 -04:00
|
|
|
/// Toggle blocking on the given user.
|
|
|
|
pub async fn block_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
let data = &(data.read().await).0;
|
2025-06-13 17:47:00 -04:00
|
|
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserManageBlocks) {
|
2025-03-25 22:52:47 -04:00
|
|
|
Some(ua) => ua,
|
|
|
|
None => return Json(Error::NotAllowed.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Ok(userblock) = data.get_userblock_by_initiator_receiver(user.id, id).await {
|
|
|
|
// delete
|
|
|
|
match data.delete_userblock(userblock.id, user).await {
|
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "User unblocked".to_string(),
|
|
|
|
payload: (),
|
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-25 22:52:47 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// create
|
|
|
|
match data.create_userblock(UserBlock::new(user.id, id)).await {
|
|
|
|
Ok(_) => {
|
|
|
|
if let Ok(userfollow) = data.get_userfollow_by_initiator_receiver(user.id, id).await
|
|
|
|
{
|
|
|
|
// automatically unfollow
|
2025-04-17 21:48:45 -04:00
|
|
|
match data.delete_userfollow(userfollow.id, &user, false).await {
|
2025-03-31 20:02:09 -04:00
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "User blocked".to_string(),
|
|
|
|
payload: (),
|
|
|
|
}),
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
} else if let Ok(userfollow) =
|
|
|
|
data.get_userfollow_by_receiver_initiator(user.id, id).await
|
|
|
|
{
|
|
|
|
// automatically unfollow
|
2025-04-17 21:48:45 -04:00
|
|
|
match data.delete_userfollow(userfollow.id, &user, false).await {
|
2025-03-25 22:52:47 -04:00
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
2025-03-31 20:02:09 -04:00
|
|
|
message: "User blocked".to_string(),
|
2025-03-25 22:52:47 -04:00
|
|
|
payload: (),
|
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-25 22:52:47 -04:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// not following user, don't do anything else
|
|
|
|
Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "User blocked".to_string(),
|
|
|
|
payload: (),
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-25 22:52:47 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-04-19 18:59:55 -04:00
|
|
|
|
|
|
|
/// Toggle IP blocking on the given IP.
|
|
|
|
pub async fn ip_block_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Path(ip): Path<String>,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
let data = &(data.read().await).0;
|
2025-06-13 17:47:00 -04:00
|
|
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserCreateIpBlock) {
|
2025-04-19 18:59:55 -04:00
|
|
|
Some(ua) => ua,
|
|
|
|
None => return Json(Error::NotAllowed.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
if let Ok(ipblock) = data.get_ipblock_by_initiator_receiver(user.id, &ip).await {
|
|
|
|
// delete
|
|
|
|
match data.delete_ipblock(ipblock.id, user).await {
|
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "IP unblocked".to_string(),
|
|
|
|
payload: (),
|
|
|
|
}),
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// create
|
|
|
|
match data.create_ipblock(IpBlock::new(user.id, ip)).await {
|
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "IP blocked".to_string(),
|
|
|
|
payload: (),
|
|
|
|
}),
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2025-06-13 17:47:00 -04:00
|
|
|
|
|
|
|
/// Get the followers of the given user.
|
|
|
|
pub async fn followers_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Query(props): Query<PaginatedQuery>,
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
let data = &(data.read().await).0;
|
|
|
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadProfiles) {
|
|
|
|
Some(ua) => ua,
|
|
|
|
None => return Json(Error::NotAllowed.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
let other_user = match data.get_user_by_id(id).await {
|
|
|
|
Ok(ua) => ua,
|
|
|
|
Err(e) => return Json(e.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
check_user_blocked_or_private!(Some(&user), other_user, data, @api);
|
|
|
|
match data.get_userfollows_by_receiver(id, 12, props.page).await {
|
|
|
|
Ok(f) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "Success".to_string(),
|
|
|
|
payload: match data.fill_userfollows_with_initiator(f).await {
|
|
|
|
Ok(f) => Some(data.userfollows_user_filter(&f)),
|
|
|
|
Err(e) => return Json(e.into()),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Get the following of the given user.
|
|
|
|
pub async fn following_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Query(props): Query<PaginatedQuery>,
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
let data = &(data.read().await).0;
|
|
|
|
let user = match get_user_from_token!(jar, data, oauth::AppScope::UserReadProfiles) {
|
|
|
|
Some(ua) => ua,
|
|
|
|
None => return Json(Error::NotAllowed.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
let other_user = match data.get_user_by_id(id).await {
|
|
|
|
Ok(ua) => ua,
|
|
|
|
Err(e) => return Json(e.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
check_user_blocked_or_private!(Some(&user), other_user, data, @api);
|
|
|
|
match data.get_userfollows_by_initiator(id, 12, props.page).await {
|
|
|
|
Ok(f) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "Success".to_string(),
|
|
|
|
payload: match data.fill_userfollows_with_receiver(f).await {
|
|
|
|
Ok(f) => Some(data.userfollows_user_filter(&f)),
|
|
|
|
Err(e) => return Json(e.into()),
|
|
|
|
},
|
|
|
|
}),
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
}
|