main
use models::issue::{issue_item, issues};
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
use space::Space;
use uuid::Uuid;
use crate::errors::IssueError;
use crate::services::service::IssueService;
pub async fn lookup_space_by_name(
service: &IssueService,
space_name: &str,
) -> Result<Space, IssueError> {
Ok(service.access.by_slug(space_name).await?)
}
pub async fn lookup_board(
service: &IssueService,
space: &Space,
board_id: Uuid,
_caller: i64,
) -> Result<issues::Model, IssueError> {
let board = issues::Entity::find_by_id(board_id)
.one(service.database.read())
.await?
.ok_or(IssueError::NotFound)?;
if board.space_id != space.id() {
return Err(IssueError::NotFound);
}
Ok(board)
}
pub async fn lookup_issue(
service: &IssueService,
board: &issues::Model,
issue_id: Uuid,
) -> Result<issue_item::Model, IssueError> {
issue_item::Entity::find_by_id(issue_id)
.filter(issue_item::Column::CollectionId.eq(board.id))
.one(service.database.read())
.await?
.ok_or(IssueError::NotFound)
}
pub async fn lookup_issue_by_ref(
service: &IssueService,
board: &issues::Model,
issue_ref: &str,
) -> Result<issue_item::Model, IssueError> {
let base = issue_item::Entity::find().filter(issue_item::Column::CollectionId.eq(board.id));
let item = if let Ok(id) = Uuid::parse_str(issue_ref) {
base.filter(issue_item::Column::Id.eq(id))
.one(service.database.read())
.await?
} else if let Ok(number) = issue_ref.parse::<i32>() {
base.filter(issue_item::Column::AutoincrementId.eq(number))
.one(service.database.read())
.await?
} else if let Some(number) = {
let __inline = |prefix: Option<&str>, issue_ref: &str| -> Option<i32> {
let prefix = prefix?;
let (head, tail) = issue_ref.rsplit_once('-')?;
if !head.eq_ignore_ascii_case(prefix) {
return None;
}
tail.parse::<i32>().ok()
};
__inline(board.prefix.as_deref(), issue_ref)
} {
// `CAD-12` keeps pasted links readable; only this board's own prefix
// resolves, so a foreign key never silently points at another board.
base.filter(issue_item::Column::AutoincrementId.eq(number))
.one(service.database.read())
.await?
} else {
None
};
item.ok_or(IssueError::NotFound)
}
pub async fn require_member(
_service: &IssueService,
space: &Space,
caller: i64,
) -> Result<(), IssueError> {
Ok(space::require(space, caller, space::Capability::Read).await?)
}
pub async fn space_context(
service: &IssueService,
caller: i64,
space_name: &str,
) -> Result<Space, IssueError> {
let space = lookup_space_by_name(service, space_name).await?;
require_member(service, &space, caller).await?;
Ok(space)
}
pub async fn board_context(
service: &IssueService,
caller: i64,
space_name: &str,
board_id: Uuid,
) -> Result<(Space, issues::Model), IssueError> {
let space = space_context(service, caller, space_name).await?;
let board = lookup_board(service, &space, board_id, caller).await?;
Ok((space, board))
}
pub async fn issue_context(
service: &IssueService,
caller: i64,
space_name: &str,
board_id: Uuid,
issue_id: Uuid,
) -> Result<(Space, issues::Model, issue_item::Model), IssueError> {
let (space, board) = board_context(service, caller, space_name, board_id).await?;
let item = lookup_issue(service, &board, issue_id).await?;
Ok((space, board, item))
}
pub async fn require_issue_access(
service: &IssueService,
caller: i64,
space_name: &str,
board_id: Uuid,
issue_id: Uuid,
) -> Result<(), IssueError> {
issue_context(service, caller, space_name, board_id, issue_id).await?;
Ok(())
}
/// Same access rules as [`issue_context`], resolved from the issue id alone —
/// used by routes that carry no space/board in their path (attachment reads).
pub async fn issue_context_by_id(
service: &IssueService,
caller: i64,
issue_id: Uuid,
) -> Result<(Space, issues::Model, issue_item::Model), IssueError> {
let item = issue_item::Entity::find_by_id(issue_id)
.one(service.database.read())
.await?
.ok_or(IssueError::NotFound)?;
let board = issues::Entity::find_by_id(item.collection_id)
.one(service.database.read())
.await?
.ok_or(IssueError::NotFound)?;
let space = service
.access
.by_id(board.space_id)
.await
.map_err(|_| IssueError::NotFound)?;
require_member(service, &space, caller).await?;
let board = lookup_board(service, &space, board.id, caller).await?;
Ok((space, board, item))
}