a06673b
use db::conn::sea_orm::*;
use db::space::space_bans as ban_entity;
use db::space::spaces as space_entity;
use time::OffsetDateTime;
use uuid::Uuid;
use crate::error::{SpaceError, SpaceResult};
use crate::features::SpaceService;
use super::types::{CreateBanCmd, SpaceBanPage, SpaceBanView, UpdateBanCmd};
async fn owner_space(svc: &SpaceService, caller: Uuid, space_id: Uuid) -> SpaceResult<space_entity::Model> {
let db = svc.db.reader();
let space = space_entity::Entity::find_by_id(space_id)
.one(&db)
.await?
.ok_or_else(|| SpaceError::NotFound(format!("space {space_id}")))?;
if space.owner_id != caller {
return Err(SpaceError::Forbidden("only the owner can manage bans".to_string()));
}
Ok(space)
}
fn parse_ts(s: Option<&str>) -> SpaceResult<Option<OffsetDateTime>> {
match s {
None | Some("") => Ok(None),
Some(v) => OffsetDateTime::parse(v, &time::format_description::well_known::Rfc3339)
.map(Some)
.map_err(|_| SpaceError::BadRequest("invalid timestamp".to_string())),
}
}
pub async fn ban_user(svc: &SpaceService, caller: Uuid, space_id: Uuid, cmd: CreateBanCmd) -> SpaceResult<SpaceBanView> {
owner_space(svc, caller, space_id).await?;
let expires_at = parse_ts(cmd.expires_at.as_deref())?;
let db = svc.db.writer();
let now = OffsetDateTime::now_utc();
let existing = ban_entity::Entity::find_by_id((space_id, cmd.user_id)).one(&db).await?;
if existing.is_some() {
return Err(SpaceError::Conflict(format!("user {} is already banned in space {space_id}", cmd.user_id)));
}
let active: ban_entity::ActiveModel = ban_entity::ActiveModel::builder()
.set_space_id(space_id)
.set_user_id(cmd.user_id)
.set_reason(cmd.reason)
.set_banned_by(caller)
.set_created_at(now)
.set_updated_at(now)
.set_expires_at(expires_at)
.set_allow_agent_join(cmd.allow_agent_join.unwrap_or(false))
.set_allow_speak(cmd.allow_speak.unwrap_or(false))
.set_allow_push_repo(cmd.allow_push_repo.unwrap_or(false))
.set_allow_comment_any(cmd.allow_comment_any.unwrap_or(false))
.into();
let saved = active.insert(&db).await?;
Ok(saved.into())
}
pub async fn get_ban(svc: &SpaceService, caller: Uuid, space_id: Uuid, user_id: Uuid) -> SpaceResult<SpaceBanView> {
owner_space(svc, caller, space_id).await?;
let db = svc.db.reader();
let ban = ban_entity::Entity::find_by_id((space_id, user_id))
.one(&db)
.await?
.ok_or_else(|| SpaceError::NotFound(format!("ban {user_id} in space {space_id}")))?;
Ok(ban.into())
}
pub async fn update_ban(svc: &SpaceService, caller: Uuid, space_id: Uuid, user_id: Uuid, cmd: UpdateBanCmd) -> SpaceResult<SpaceBanView> {
owner_space(svc, caller, space_id).await?;
let db = svc.db.writer();
let txn = db.begin().await?;
let ban = ban_entity::Entity::find_by_id((space_id, user_id))
.one(&txn)
.await?
.ok_or_else(|| SpaceError::NotFound(format!("ban {user_id} in space {space_id}")))?;
let mut active: ban_entity::ActiveModel = ban.into();
if let Some(reason) = cmd.reason {
active.reason = Set(reason);
}
if let Some(expires_at) = cmd.expires_at {
active.expires_at = Set(parse_ts(expires_at.as_deref())?);
}
if let Some(v) = cmd.allow_agent_join {
active.allow_agent_join = Set(v);
}
if let Some(v) = cmd.allow_speak {
active.allow_speak = Set(v);
}
if let Some(v) = cmd.allow_push_repo {
active.allow_push_repo = Set(v);
}
if let Some(v) = cmd.allow_comment_any {
active.allow_comment_any = Set(v);
}
active.updated_at = Set(OffsetDateTime::now_utc());
let saved = active.update(&txn).await?;
txn.commit().await?;
Ok(saved.into())
}
pub async fn unban_user(svc: &SpaceService, caller: Uuid, space_id: Uuid, user_id: Uuid) -> SpaceResult<()> {
owner_space(svc, caller, space_id).await?;
let db = svc.db.writer();
let txn = db.begin().await?;
let deleted = ban_entity::Entity::delete_by_id((space_id, user_id)).exec(&txn).await?;
if deleted.rows_affected == 0 {
return Err(SpaceError::NotFound(format!("ban {user_id} in space {space_id}")));
}
txn.commit().await?;
Ok(())
}
pub async fn list_bans(svc: &SpaceService, caller: Uuid, space_id: Uuid, limit: u64, offset: u64) -> SpaceResult<SpaceBanPage> {
owner_space(svc, caller, space_id).await?;
let db = svc.db.reader();
let limit = std::cmp::max(std::cmp::min(limit, svc.config.max_pagination), 1);
let items = ban_entity::Entity::find()
.filter(ban_entity::Column::SpaceId.eq(space_id))
.order_by_desc(ban_entity::Column::CreatedAt)
.limit(Some(limit))
.offset(Some(offset))
.all(&db)
.await?;
let total = ban_entity::Entity::find()
.filter(ban_entity::Column::SpaceId.eq(space_id))
.count(&db)
.await?;
Ok(SpaceBanPage {
items: items.into_iter().map(SpaceBanView::from).collect(),
total: total as i64,
})
}