add: ability to join/leave/be banned from communities

This commit is contained in:
trisua 2025-03-31 15:39:49 -04:00
parent f3c2157dfc
commit 619184d02e
28 changed files with 618 additions and 197 deletions

View file

@ -51,13 +51,11 @@ pub async fn avatar_request(
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match {
if req.selector_type == AvatarSelectorType::Id {
data.get_user_by_id(selector.parse::<usize>().unwrap())
.await
} else {
data.get_user_by_username(&selector).await
}
let user = match if req.selector_type == AvatarSelectorType::Id {
data.get_user_by_id(selector.parse::<usize>().unwrap())
.await
} else {
data.get_user_by_username(&selector).await
} {
Ok(ua) => ua,
Err(_) => {

View file

@ -18,7 +18,7 @@ pub async fn redirect_from_id(
Extension(data): Extension<State>,
Path(id): Path<String>,
) -> impl IntoResponse {
match (&(data.read().await).0)
match (data.read().await).0
.get_user_by_id(match id.parse::<usize>() {
Ok(id) => id,
Err(_) => return Redirect::to("/"),
@ -43,10 +43,8 @@ pub async fn update_profile_settings_request(
None => return Json(Error::NotAllowed.into()),
};
if user.id != id {
if !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
if user.id != id && !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
match data.update_user_settings(id, req).await {
@ -72,10 +70,8 @@ pub async fn update_profile_password_request(
None => return Json(Error::NotAllowed.into()),
};
if user.id != id {
if !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
if user.id != id && !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
match data
@ -103,10 +99,8 @@ pub async fn update_profile_username_request(
None => return Json(Error::NotAllowed.into()),
};
if user.id != id {
if !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
if user.id != id && !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
if data.get_user_by_username(&req.to).await.is_ok() {
@ -136,10 +130,8 @@ pub async fn update_profile_tokens_request(
None => return Json(Error::NotAllowed.into()),
};
if user.id != id {
if !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
if user.id != id && !user.permissions.check(FinePermission::MANAGE_USERS) {
return Json(Error::NotAllowed.into());
}
match data.update_user_tokens(id, req).await {

View file

@ -26,7 +26,7 @@ pub async fn follow_request(
message: "User unfollowed".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
} else {
// create
@ -36,7 +36,7 @@ pub async fn follow_request(
message: "User followed".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
}
@ -61,7 +61,7 @@ pub async fn block_request(
message: "User unblocked".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
} else {
// create
@ -76,7 +76,7 @@ pub async fn block_request(
message: "User unfollowed".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
} else {
// not following user, don't do anything else
@ -87,7 +87,7 @@ pub async fn block_request(
})
}
}
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
}

View file

@ -4,13 +4,18 @@ use axum::{
response::{IntoResponse, Redirect},
};
use axum_extra::extract::CookieJar;
use tetratto_core::model::{ApiReturn, Error, communities::Community};
use tetratto_core::model::{
ApiReturn, Error,
auth::Notification,
communities::{Community, CommunityMembership},
communities_permissions::CommunityPermission,
};
use crate::{
State, get_user_from_token,
routes::api::v1::{
CreateCommunity, UpdateCommunityContext, UpdateCommunityReadAccess, UpdateCommunityTitle,
UpdateCommunityWriteAccess,
UpdateCommunityWriteAccess, UpdateMembershipRole,
},
};
@ -18,7 +23,8 @@ pub async fn redirect_from_id(
Extension(data): Extension<State>,
Path(id): Path<String>,
) -> impl IntoResponse {
match (&(data.read().await).0)
match (data.read().await)
.0
.get_community_by_id(match id.parse::<usize>() {
Ok(id) => id,
Err(_) => return Redirect::to("/"),
@ -50,7 +56,7 @@ pub async fn create_request(
message: "Community created".to_string(),
payload: Some(id.to_string()),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -71,7 +77,7 @@ pub async fn delete_request(
message: "Community deleted".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -93,7 +99,7 @@ pub async fn update_title_request(
message: "Community updated".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -115,7 +121,7 @@ pub async fn update_context_request(
message: "Community updated".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -140,7 +146,7 @@ pub async fn update_read_access_request(
message: "Community updated".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -165,6 +171,176 @@ pub async fn update_write_access_request(
message: "Community updated".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
pub async fn get_membership(
jar: CookieJar,
Extension(data): Extension<State>,
Path((cid, uid)): Path<(usize, usize)>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
let community = match data.get_community_by_id(cid).await {
Ok(c) => c,
Err(e) => return Json(e.into()),
};
if user.id != community.owner {
// only the owner can select community memberships
return Json(Error::NotAllowed.into());
}
match data.get_membership_by_owner_community(uid, cid).await {
Ok(m) => Json(ApiReturn {
ok: true,
message: "Membership exists".to_string(),
payload: Some(m),
}),
Err(e) => Json(e.into()),
}
}
pub async fn create_membership(
jar: CookieJar,
Extension(data): Extension<State>,
Path(id): Path<usize>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
match data
.create_membership(CommunityMembership::new(
user.id,
id,
CommunityPermission::default(),
))
.await
{
Ok(_) => Json(ApiReturn {
ok: true,
message: "Community joined".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn delete_membership(
jar: CookieJar,
Extension(data): Extension<State>,
Path((cid, uid)): Path<(usize, usize)>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
let membership = match data.get_membership_by_owner_community(uid, cid).await {
Ok(c) => c,
Err(e) => return Json(e.into()),
};
match data.delete_membership(membership.id, user).await {
Ok(_) => Json(ApiReturn {
ok: true,
message: "Membership deleted".to_string(),
payload: (),
}),
Err(e) => Json(e.into()),
}
}
pub async fn update_membership_role(
jar: CookieJar,
Extension(data): Extension<State>,
Path((cid, uid)): Path<(usize, usize)>,
Json(req): Json<UpdateMembershipRole>,
) -> impl IntoResponse {
let data = &(data.read().await).0;
let user = match get_user_from_token!(jar, data) {
Some(ua) => ua,
None => return Json(Error::NotAllowed.into()),
};
let membership = match data.get_membership_by_owner_community(uid, cid).await {
Ok(c) => c,
Err(e) => return Json(e.into()),
};
let community = match data.get_community_by_id(membership.community).await {
Ok(c) => c,
Err(e) => return Json(e.into()),
};
if membership.owner == community.owner {
return Json(Error::MiscError("Cannot update community owner's role".to_string()).into());
}
if user.id != community.owner {
return Json(Error::NotAllowed.into());
}
match data.update_membership_role(membership.id, req.role).await {
Ok(_) => {
// check if the user was just banned/unbanned (and send notifs)
if (req.role & CommunityPermission::BANNED) == CommunityPermission::BANNED {
// user was banned
if let Err(e) = data
.create_notification(Notification::new(
"You have been banned from a community.".to_string(),
format!(
"You have been banned from [{}](/community/{}).",
community.title, community.title
),
membership.owner,
))
.await
{
return Json(e.into());
};
if let Err(e) = data.decr_community_member_count(community.id).await {
// banned members do not count towards member count
return Json(e.into());
}
} else if (membership.role & CommunityPermission::BANNED) == CommunityPermission::BANNED
{
// user was unbanned
if let Err(e) = data
.create_notification(Notification::new(
"You have been unbanned from a community.".to_string(),
format!(
"You have been unbanned from [{}](/community/{}).",
community.title, community.title
),
membership.owner,
))
.await
{
return Json(e.into());
};
if let Err(e) = data.incr_community_member_count(community.id).await {
return Json(e.into());
}
}
Json(ApiReturn {
ok: true,
message: "Membership updated".to_string(),
payload: (),
})
}
Err(e) => Json(e.into()),
}
}

View file

@ -120,13 +120,10 @@ pub async fn upload_avatar_request(
Err(e) => return Json(e.into()),
};
if auth_user.id != community.owner {
if !auth_user
if auth_user.id != community.owner && !auth_user
.permissions
.check(FinePermission::MANAGE_COMMUNITIES)
{
return Json(Error::NotAllowed.into());
}
.check(FinePermission::MANAGE_COMMUNITIES) {
return Json(Error::NotAllowed.into());
}
let path = pathd!(
@ -176,13 +173,10 @@ pub async fn upload_banner_request(
Err(e) => return Json(e.into()),
};
if auth_user.id != community.owner {
if !auth_user
if auth_user.id != community.owner && !auth_user
.permissions
.check(FinePermission::MANAGE_COMMUNITIES)
{
return Json(Error::NotAllowed.into());
}
.check(FinePermission::MANAGE_COMMUNITIES) {
return Json(Error::NotAllowed.into());
}
let path = pathd!(

View file

@ -42,7 +42,7 @@ pub async fn create_request(
message: "Post created".to_string(),
payload: Some(id.to_string()),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -63,7 +63,7 @@ pub async fn delete_request(
message: "Post deleted".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -85,7 +85,7 @@ pub async fn update_content_request(
message: "Post updated".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -107,6 +107,6 @@ pub async fn update_context_request(
message: "Post updated".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}

View file

@ -10,6 +10,7 @@ use axum::{
use serde::Deserialize;
use tetratto_core::model::{
communities::{CommunityContext, CommunityReadAccess, CommunityWriteAccess, PostContext},
communities_permissions::CommunityPermission,
reactions::AssetType,
};
@ -139,6 +140,23 @@ pub fn routes() -> Router {
"/notifications/{id}/read_status",
post(notifications::update_read_status_request),
)
// community memberships
.route(
"/communities/{id}/join",
post(communities::communities::create_membership),
)
.route(
"/communities/{cid}/memberships/{uid}",
get(communities::communities::get_membership),
)
.route(
"/communities/{cid}/memberships/{uid}",
delete(communities::communities::delete_membership),
)
.route(
"/communities/{cid}/memberships/{uid}/role",
post(communities::communities::update_membership_role),
)
}
#[derive(Deserialize)]
@ -217,3 +235,8 @@ pub struct UpdateUserIsVerified {
pub struct UpdateNotificationRead {
pub read: bool,
}
#[derive(Deserialize)]
pub struct UpdateMembershipRole {
pub role: CommunityPermission,
}

View file

@ -23,7 +23,7 @@ pub async fn delete_request(
message: "Notification deleted".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -43,7 +43,7 @@ pub async fn delete_all_request(
message: "Notifications deleted".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -65,6 +65,6 @@ pub async fn update_read_status_request(
message: "Notification updated".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}

View file

@ -21,7 +21,7 @@ pub async fn get_request(
message: "Reaction exists".to_string(),
payload: Some(r),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}
@ -98,6 +98,6 @@ pub async fn delete_request(
message: "Reaction deleted".to_string(),
payload: (),
}),
Err(e) => return Json(e.into()),
Err(e) => Json(e.into()),
}
}