use db::conn::sea_orm::*; use db::issue::issue as issue_entity; use db::issue::issues as issues_entity; use time::OffsetDateTime; use uuid::Uuid; use crate::error::{IssueError, IssueResult}; use crate::features::IssueService; use super::super::{CreateIssueOption, IssueView}; use super::helpers::require_authenticated; pub async fn create_issue( svc: &IssueService, actor: Uuid, channel_id: Uuid, input: CreateIssueOption, ) -> IssueResult { require_authenticated(actor)?; if input.title.trim().is_empty() || input.title.len() > svc.config.max_title_len { return Err(IssueError::BadRequest(format!( "title length must be in 1..={}", svc.config.max_title_len ))); } if input.body.len() > svc.config.max_body_len { return Err(IssueError::BadRequest(format!( "body too long (max {})", svc.config.max_body_len ))); } let db = svc.db.writer(); let txn = db.begin().await?; // 1) Resolve / create default issues board for this channel (atomic within txn) let collection: issues_entity::Model = match issues_entity::Entity::find() .filter(issues_entity::COLUMN.channel_id.eq(channel_id)) .order_by_asc(issues_entity::COLUMN.created_at) .lock_exclusive() .one(&txn) .await? { Some(c) => c, None => { let now = OffsetDateTime::now_utc(); let new_collection_id = Uuid::new_v4(); let active: issues_entity::ActiveModel = issues_entity::ActiveModel::builder() .set_id(new_collection_id) .set_channel_id(channel_id) .set_name(svc.config.default_collection_name.clone()) .set_icon(None) .set_description(None) .set_created_at(now) .set_updated_at(None) .into(); active.insert(&txn).await? } }; // 2) SELECT … FOR UPDATE on this collection's issue rows to compute next id let max_id: Option = issue_entity::Entity::find() .filter(issue_entity::COLUMN.collection_id.eq(collection.id)) .lock_exclusive() .select_only() .column_as(issue_entity::COLUMN.autoincrement_id.max(), "max") .into_tuple::>() .one(&txn) .await? .flatten(); let next_id = max_id.map(|n| n + 1).unwrap_or(1); // 3) Insert new issue let now = OffsetDateTime::now_utc(); let issue_id = Uuid::new_v4(); let active: issue_entity::ActiveModel = issue_entity::ActiveModel::builder() .set_id(issue_id) .set_collection_id(collection.id) .set_autoincrement_id(next_id) .set_author_id(actor) .set_title(input.title.trim().to_string()) .set_description(input.description) .set_body(input.body) .set_milestone_id(input.milestone_id) .set_created_at(now) .set_updated_at(None) .set_closed_at(None) .into(); let saved = active.insert(&txn).await?; txn.commit().await?; Ok(IssueView::from(saved)) }