2025-03-29 00:26:56 -04:00
|
|
|
use axum::{
|
|
|
|
Extension, Json,
|
|
|
|
extract::Path,
|
|
|
|
response::{IntoResponse, Redirect},
|
|
|
|
};
|
2025-03-24 19:55:08 -04:00
|
|
|
use axum_extra::extract::CookieJar;
|
2025-03-31 15:39:49 -04:00
|
|
|
use tetratto_core::model::{
|
|
|
|
ApiReturn, Error,
|
|
|
|
auth::Notification,
|
|
|
|
communities::{Community, CommunityMembership},
|
|
|
|
communities_permissions::CommunityPermission,
|
|
|
|
};
|
2025-03-24 19:55:08 -04:00
|
|
|
|
|
|
|
use crate::{
|
2025-04-24 16:57:25 -04:00
|
|
|
get_user_from_token,
|
2025-03-24 19:55:08 -04:00
|
|
|
routes::api::v1::{
|
2025-04-24 16:57:25 -04:00
|
|
|
CreateCommunity, UpdateCommunityContext, UpdateCommunityJoinAccess, UpdateCommunityOwner,
|
2025-04-01 15:03:56 -04:00
|
|
|
UpdateCommunityReadAccess, UpdateCommunityTitle, UpdateCommunityWriteAccess,
|
|
|
|
UpdateMembershipRole,
|
2025-03-24 19:55:08 -04:00
|
|
|
},
|
2025-04-24 16:57:25 -04:00
|
|
|
State,
|
2025-03-24 19:55:08 -04:00
|
|
|
};
|
|
|
|
|
2025-03-29 00:26:56 -04:00
|
|
|
pub async fn redirect_from_id(
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<String>,
|
|
|
|
) -> impl IntoResponse {
|
2025-03-31 15:39:49 -04:00
|
|
|
match (data.read().await)
|
|
|
|
.0
|
2025-03-29 00:26:56 -04:00
|
|
|
.get_community_by_id(match id.parse::<usize>() {
|
|
|
|
Ok(id) => id,
|
|
|
|
Err(_) => return Redirect::to("/"),
|
|
|
|
})
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(c) => Redirect::to(&format!("/community/{}", c.title)),
|
|
|
|
Err(_) => Redirect::to("/"),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-24 19:55:08 -04:00
|
|
|
pub async fn create_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
2025-03-27 18:10:47 -04:00
|
|
|
Json(req): Json<CreateCommunity>,
|
2025-03-24 19:55:08 -04:00
|
|
|
) -> 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
|
2025-03-27 18:10:47 -04:00
|
|
|
.create_community(Community::new(req.title, user.id))
|
2025-03-24 19:55:08 -04:00
|
|
|
.await
|
|
|
|
{
|
2025-03-29 00:26:56 -04:00
|
|
|
Ok(id) => Json(ApiReturn {
|
2025-03-24 19:55:08 -04:00
|
|
|
ok: true,
|
2025-03-27 18:10:47 -04:00
|
|
|
message: "Community created".to_string(),
|
2025-03-29 00:26:56 -04:00
|
|
|
payload: Some(id.to_string()),
|
2025-03-24 19:55:08 -04:00
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-24 19:55:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn delete_request(
|
|
|
|
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()),
|
|
|
|
};
|
|
|
|
|
2025-05-09 22:59:19 -04:00
|
|
|
match data.delete_community(id, &user).await {
|
2025-03-24 19:55:08 -04:00
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
2025-03-27 18:10:47 -04:00
|
|
|
message: "Community deleted".to_string(),
|
2025-03-24 19:55:08 -04:00
|
|
|
payload: (),
|
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-24 19:55:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_title_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
2025-03-29 00:26:56 -04:00
|
|
|
Json(req): Json<UpdateCommunityTitle>,
|
2025-03-24 19:55:08 -04:00
|
|
|
) -> 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()),
|
|
|
|
};
|
|
|
|
|
2025-04-12 10:15:47 -04:00
|
|
|
match data.update_community_title(id, user, &req.title).await {
|
2025-03-24 19:55:08 -04:00
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
2025-03-27 18:10:47 -04:00
|
|
|
message: "Community updated".to_string(),
|
2025-03-24 19:55:08 -04:00
|
|
|
payload: (),
|
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-24 19:55:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-27 18:10:47 -04:00
|
|
|
pub async fn update_context_request(
|
2025-03-24 19:55:08 -04:00
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
2025-03-27 18:10:47 -04:00
|
|
|
Json(req): Json<UpdateCommunityContext>,
|
2025-03-24 19:55:08 -04:00
|
|
|
) -> 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()),
|
|
|
|
};
|
|
|
|
|
2025-05-16 16:09:21 -04:00
|
|
|
match data.update_community_context(id, &user, req.context).await {
|
2025-03-24 19:55:08 -04:00
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
2025-03-27 18:10:47 -04:00
|
|
|
message: "Community updated".to_string(),
|
2025-03-24 19:55:08 -04:00
|
|
|
payload: (),
|
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-24 19:55:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_read_access_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
2025-03-29 00:26:56 -04:00
|
|
|
Json(req): Json<UpdateCommunityReadAccess>,
|
2025-03-24 19:55:08 -04:00
|
|
|
) -> 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()),
|
|
|
|
};
|
|
|
|
|
2025-03-27 18:10:47 -04:00
|
|
|
match data
|
2025-05-16 16:09:21 -04:00
|
|
|
.update_community_read_access(id, &user, req.access)
|
2025-03-27 18:10:47 -04:00
|
|
|
.await
|
|
|
|
{
|
2025-03-24 19:55:08 -04:00
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
2025-03-27 18:10:47 -04:00
|
|
|
message: "Community updated".to_string(),
|
2025-03-24 19:55:08 -04:00
|
|
|
payload: (),
|
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
2025-03-24 19:55:08 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub async fn update_write_access_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
2025-03-29 00:26:56 -04:00
|
|
|
Json(req): Json<UpdateCommunityWriteAccess>,
|
2025-03-24 19:55:08 -04:00
|
|
|
) -> 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()),
|
|
|
|
};
|
|
|
|
|
2025-03-27 18:10:47 -04:00
|
|
|
match data
|
2025-05-16 16:09:21 -04:00
|
|
|
.update_community_write_access(id, &user, req.access)
|
2025-03-27 18:10:47 -04:00
|
|
|
.await
|
|
|
|
{
|
2025-03-24 19:55:08 -04:00
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
2025-03-27 18:10:47 -04:00
|
|
|
message: "Community updated".to_string(),
|
2025-03-24 19:55:08 -04:00
|
|
|
payload: (),
|
|
|
|
}),
|
2025-03-31 15:39:49 -04:00
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-01 15:03:56 -04:00
|
|
|
pub async fn update_join_access_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
Json(req): Json<UpdateCommunityJoinAccess>,
|
|
|
|
) -> 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
|
2025-05-16 16:09:21 -04:00
|
|
|
.update_community_join_access(id, &user, req.access)
|
2025-04-01 15:03:56 -04:00
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "Community updated".to_string(),
|
|
|
|
payload: (),
|
|
|
|
}),
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-04-24 16:57:25 -04:00
|
|
|
pub async fn update_owner_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
Json(req): Json<UpdateCommunityOwner>,
|
|
|
|
) -> 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
|
|
|
|
.update_community_owner(
|
|
|
|
id,
|
|
|
|
user,
|
|
|
|
match req.user.parse::<usize>() {
|
|
|
|
Ok(x) => x,
|
|
|
|
Err(e) => return Json(Error::MiscError(e.to_string()).into()),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
Ok(_) => Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: "Community updated".to_string(),
|
|
|
|
payload: (),
|
|
|
|
}),
|
|
|
|
Err(e) => Json(e.into()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-03-31 15:39:49 -04:00
|
|
|
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
|
|
|
|
{
|
2025-04-01 15:03:56 -04:00
|
|
|
Ok(m) => Json(ApiReturn {
|
2025-03-31 15:39:49 -04:00
|
|
|
ok: true,
|
2025-04-01 15:03:56 -04:00
|
|
|
message: m,
|
2025-03-31 15:39:49 -04:00
|
|
|
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()),
|
|
|
|
};
|
|
|
|
|
2025-05-11 14:27:55 -04:00
|
|
|
match data.delete_membership(membership.id, &user).await {
|
2025-03-31 15:39:49 -04:00
|
|
|
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,
|
2025-03-24 19:55:08 -04:00
|
|
|
Err(e) => return Json(e.into()),
|
2025-03-31 15:39:49 -04:00
|
|
|
};
|
|
|
|
|
|
|
|
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)
|
2025-04-01 13:26:33 -04:00
|
|
|
if req.role.check_banned() {
|
2025-03-31 15:39:49 -04:00
|
|
|
// 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());
|
|
|
|
}
|
2025-04-01 13:26:33 -04:00
|
|
|
} else if membership.role.check_banned() {
|
2025-03-31 15:39:49 -04:00
|
|
|
// 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());
|
|
|
|
};
|
|
|
|
|
2025-04-01 15:03:56 -04:00
|
|
|
if let Err(e) = data.incr_community_member_count(community.id).await {
|
|
|
|
return Json(e.into());
|
|
|
|
}
|
|
|
|
} else if req.role.check(CommunityPermission::REQUESTED) {
|
|
|
|
// user was demoted to a request again
|
|
|
|
if let Err(e) = data.decr_community_member_count(community.id).await {
|
|
|
|
return Json(e.into());
|
|
|
|
}
|
|
|
|
} else if membership.role.check(CommunityPermission::REQUESTED) {
|
|
|
|
// user was accepted to community
|
|
|
|
if let Err(e) = data
|
|
|
|
.create_notification(Notification::new(
|
|
|
|
"You have been accepted into a community you requested to join!"
|
|
|
|
.to_string(),
|
|
|
|
format!(
|
|
|
|
"You have been accepted into [{}](/community/{}).",
|
|
|
|
community.title, community.title
|
|
|
|
),
|
|
|
|
membership.owner,
|
|
|
|
))
|
|
|
|
.await
|
|
|
|
{
|
|
|
|
return Json(e.into());
|
|
|
|
};
|
|
|
|
|
2025-03-31 15:39:49 -04:00
|
|
|
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()),
|
2025-03-24 19:55:08 -04:00
|
|
|
}
|
|
|
|
}
|
2025-06-08 15:34:29 -04:00
|
|
|
|
|
|
|
pub async fn supports_titles_request(
|
|
|
|
jar: CookieJar,
|
|
|
|
Extension(data): Extension<State>,
|
|
|
|
Path(id): Path<usize>,
|
|
|
|
) -> impl IntoResponse {
|
|
|
|
let data = &(data.read().await).0;
|
|
|
|
if get_user_from_token!(jar, data).is_none() {
|
|
|
|
return Json(Error::NotAllowed.into());
|
|
|
|
}
|
|
|
|
|
|
|
|
let community = match data.get_community_by_id(id).await {
|
|
|
|
Ok(c) => c,
|
|
|
|
Err(e) => return Json(e.into()),
|
|
|
|
};
|
|
|
|
|
|
|
|
Json(ApiReturn {
|
|
|
|
ok: true,
|
|
|
|
message: if community.context.enable_titles {
|
|
|
|
"yes".to_string()
|
|
|
|
} else {
|
|
|
|
"no".to_string()
|
|
|
|
},
|
|
|
|
payload: (),
|
|
|
|
})
|
|
|
|
}
|