Skip to content
Clone
main
165 lines 5.3 KB Rust
use models::issue::issue_pin;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, Set};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use utoipa::{IntoParams, ToSchema};
use uuid::Uuid;
use crate::errors::IssueError;
use crate::services::service::IssueService;
use crate::share::authz;
use crate::share::lookup::{board_context, lookup_board, lookup_issue, space_context};
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct PinDto {
pub id: Uuid,
pub board_id: Uuid,
pub issue_id: Option<Uuid>,
pub pin_order: i32,
pub created_at: OffsetDateTime,
}
impl From<issue_pin::Model> for PinDto {
fn from(m: issue_pin::Model) -> Self {
Self {
id: m.id,
board_id: m.collection_id,
issue_id: m.issue_id,
pin_order: m.pin_order,
created_at: m.created_at,
}
}
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct CreatePinCmd {
pub issue_id: Uuid,
pub pin_order: i32,
}
#[derive(Debug, Clone, Deserialize, IntoParams, ToSchema)]
pub struct PinPath {
pub space_name: String,
pub board_id: Uuid,
pub pin_id: Uuid,
}
#[derive(Debug, Clone, Deserialize, ToSchema)]
pub struct UpdatePinCmd {
pub pin_order: i32,
}
impl IssueService {
#[tracing::instrument(name = "issues.update_pin", skip_all, fields(caller = %caller, space = %space_name, board_id = %board_id, pin_id = %pin_id))]
pub async fn update_pin(
&self,
caller: i64,
space_name: &str,
board_id: Uuid,
pin_id: Uuid,
cmd: UpdatePinCmd,
) -> Result<PinDto, IssueError> {
let space = space_context(self, caller, space_name).await?;
authz::require_board_manage(self, &space, caller).await?;
let row = issue_pin::Entity::find_by_id(pin_id)
.filter(issue_pin::Column::CollectionId.eq(board_id))
.one(self.database.write())
.await?
.ok_or(IssueError::NotFound)?;
if cmd.pin_order < 0 {
return Err(IssueError::BadRequest("pin_order must be >= 0".to_string()));
}
let mut am: issue_pin::ActiveModel = row.into();
am.pin_order = Set(cmd.pin_order);
let updated = am.update(self.database.write()).await?;
Ok(PinDto::from(updated))
}
}
impl IssueService {
#[tracing::instrument(name = "issues.create_pin", skip_all, fields(caller = %caller, space = %space_name, board_id = %board_id))]
pub async fn create_pin(
&self,
caller: i64,
space_name: &str,
board_id: Uuid,
cmd: CreatePinCmd,
) -> Result<PinDto, IssueError> {
let space = space_context(self, caller, space_name).await?;
authz::require_board_manage(self, &space, caller).await?;
let board = lookup_board(self, &space, board_id, caller).await?;
let _ = lookup_issue(self, &board, cmd.issue_id).await?;
if cmd.pin_order < 0 {
return Err(IssueError::BadRequest(
"pin_order must be non-negative".to_string(),
));
}
let existing = issue_pin::Entity::find()
.filter(issue_pin::Column::CollectionId.eq(board.id))
.filter(issue_pin::Column::IssueId.eq(Some(cmd.issue_id)))
.one(self.database.read())
.await?;
if existing.is_some() {
return Err(IssueError::Conflict("issue already pinned".to_string()));
}
let now = OffsetDateTime::now_utc();
let row = issue_pin::ActiveModel {
id: Set(Uuid::now_v7()),
collection_id: Set(board.id),
issue_id: Set(Some(cmd.issue_id)),
project_id: Set(None),
pin_order: Set(cmd.pin_order),
created_at: Set(now),
};
let inserted = row.insert(self.database.write()).await?;
Ok(PinDto::from(inserted))
}
pub async fn list_pins(
&self,
caller: i64,
space_name: &str,
board_id: Uuid,
) -> Result<Vec<PinDto>, IssueError> {
let (_, board) = board_context(self, caller, space_name, board_id).await?;
let rows = issue_pin::Entity::find()
.filter(issue_pin::Column::CollectionId.eq(board.id))
.order_by_asc(issue_pin::Column::PinOrder)
.order_by_asc(issue_pin::Column::CreatedAt)
.all(self.database.read())
.await?;
Ok(rows.into_iter().map(PinDto::from).collect())
}
#[tracing::instrument(name = "issues.delete_pin", skip_all, fields(caller = %caller, space = %space_name, board_id = %board_id, pin_id = %pin_id))]
pub async fn delete_pin(
&self,
caller: i64,
space_name: &str,
board_id: Uuid,
pin_id: Uuid,
) -> Result<(), IssueError> {
let space = space_context(self, caller, space_name).await?;
authz::require_board_manage(self, &space, caller).await?;
let board = lookup_board(self, &space, board_id, caller).await?;
let p = issue_pin::Entity::find_by_id(pin_id)
.filter(issue_pin::Column::CollectionId.eq(board.id))
.one(self.database.read())
.await?
.ok_or(IssueError::NotFound)?;
issue_pin::Entity::delete_by_id(p.id)
.exec(self.database.write())
.await?;
Ok(())
}
}