diff --git a/crates/app/src/public/html/communities/list.html b/crates/app/src/public/html/communities/list.html index e5cb525..ee15f87 100644 --- a/crates/app/src/public/html/communities/list.html +++ b/crates/app/src/public/html/communities/list.html @@ -68,6 +68,11 @@ async function create_community_from_form(e) { e.preventDefault(); await trigger("atto::debounce", ["communities::create"]); + + if (e.target.title.value.includes(" ")) { + return alert("Cannot contain spaces!"); + } + fetch("/api/v1/communities", { method: "POST", headers: { diff --git a/crates/app/src/public/html/communities/settings.html b/crates/app/src/public/html/communities/settings.html index 7a4a537..4fc28bd 100644 --- a/crates/app/src/public/html/communities/settings.html +++ b/crates/app/src/public/html/communities/settings.html @@ -317,6 +317,33 @@ }); }; + globalThis.transfer_ownership = async (uid) => { + if ( + !(await trigger("atto::confirm", [ + "Are you sure you would like to do this?\n\nThis action is PERMANENT!", + ])) + ) { + return; + } + + fetch(`/api/v1/communities/{{ community.id }}/transfer_ownership`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + user: uid, + }), + }) + .then((res) => res.json()) + .then((res) => { + trigger("atto::toast", [ + res.ok ? "success" : "error", + res.message, + ]); + }); + }; + globalThis.select_user_from_form = (e) => { e.preventDefault(); fetch( @@ -359,6 +386,7 @@ ${res.payload.role !== 33 ? `` : ``} ${res.payload.role !== 65 ? `` : ``} +
diff --git a/crates/app/src/routes/api/v1/communities/communities.rs b/crates/app/src/routes/api/v1/communities/communities.rs index ba4eb89..10bf691 100644 --- a/crates/app/src/routes/api/v1/communities/communities.rs +++ b/crates/app/src/routes/api/v1/communities/communities.rs @@ -12,12 +12,13 @@ use tetratto_core::model::{ }; use crate::{ - State, get_user_from_token, + get_user_from_token, routes::api::v1::{ - CreateCommunity, UpdateCommunityContext, UpdateCommunityJoinAccess, + CreateCommunity, UpdateCommunityContext, UpdateCommunityJoinAccess, UpdateCommunityOwner, UpdateCommunityReadAccess, UpdateCommunityTitle, UpdateCommunityWriteAccess, UpdateMembershipRole, }, + State, }; pub async fn redirect_from_id( @@ -201,6 +202,38 @@ pub async fn update_join_access_request( } } +pub async fn update_owner_request( + jar: CookieJar, + Extension(data): Extension, + Path(id): Path, + Json(req): Json, +) -> 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::() { + 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()), + } +} + pub async fn get_membership( jar: CookieJar, Extension(data): Extension, diff --git a/crates/app/src/routes/api/v1/mod.rs b/crates/app/src/routes/api/v1/mod.rs index 84e49e2..c0135f4 100644 --- a/crates/app/src/routes/api/v1/mod.rs +++ b/crates/app/src/routes/api/v1/mod.rs @@ -64,6 +64,10 @@ pub fn routes() -> Router { "/communities/{id}/access/join", post(communities::communities::update_join_access_request), ) + .route( + "/communities/{id}/transfer_ownership", + post(communities::communities::update_owner_request), + ) .route( "/communities/{id}/upload/avatar", post(communities::images::upload_avatar_request), @@ -342,6 +346,11 @@ pub struct UpdateMembershipRole { pub role: CommunityPermission, } +#[derive(Deserialize)] +pub struct UpdateCommunityOwner { + pub user: String, +} + #[derive(Deserialize)] pub struct UpdateUserRole { pub role: FinePermission, diff --git a/crates/core/src/database/communities.rs b/crates/core/src/database/communities.rs index 33accc3..2f0e6ff 100644 --- a/crates/core/src/database/communities.rs +++ b/crates/core/src/database/communities.rs @@ -397,6 +397,68 @@ impl DataManager { Ok(()) } + pub async fn update_community_owner( + &self, + id: usize, + user: User, + new_owner: usize, + ) -> Result<()> { + let y = self.get_community_by_id(id).await?; + + if user.id != y.owner { + if !user.permissions.check(FinePermission::MANAGE_COMMUNITIES) { + return Err(Error::NotAllowed); + } else { + self.create_audit_log_entry(crate::model::moderation::AuditLogEntry::new( + user.id, + format!("invoked `update_community_owner` with x value `{id}`"), + )) + .await? + } + } + + let new_owner_membership = self + .get_membership_by_owner_community(new_owner, y.id) + .await?; + let current_owner_membership = self + .get_membership_by_owner_community(y.owner, y.id) + .await?; + + // ... + let conn = match self.connect().await { + Ok(c) => c, + Err(e) => return Err(Error::DatabaseConnection(e.to_string())), + }; + + let res = execute!( + &conn, + "UPDATE communities SET owner = $1 WHERE id = $2", + params![&(new_owner as i64), &(id as i64)] + ); + + if let Err(e) = res { + return Err(Error::DatabaseError(e.to_string())); + } + + self.cache_clear_community(&y).await; + + // update memberships + self.update_membership_role( + new_owner_membership.id, + CommunityPermission::DEFAULT | CommunityPermission::ADMINISTRATOR, + ) + .await?; + + self.update_membership_role( + current_owner_membership.id, + CommunityPermission::DEFAULT | CommunityPermission::MEMBER, + ) + .await?; + + // return + Ok(()) + } + auto_method!(update_community_context(CommunityContext)@get_community_by_id_no_void:MANAGE_COMMUNITIES -> "UPDATE communities SET context = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community); auto_method!(update_community_read_access(CommunityReadAccess)@get_community_by_id_no_void:MANAGE_COMMUNITIES -> "UPDATE communities SET read_access = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community); auto_method!(update_community_write_access(CommunityWriteAccess)@get_community_by_id_no_void:MANAGE_COMMUNITIES -> "UPDATE communities SET write_access = $1 WHERE id = $2" --serde --cache-key-tmpl=cache_clear_community);