use db::conn::sea_orm::*; use db::issue::issue_reactions as reaction_entity; use time::OffsetDateTime; use uuid::Uuid; use crate::error::{IssueError, IssueResult}; use crate::features::IssueService; use crate::features::issue::usecase::load_issue; use super::super::{CreateReactionOption, ReactionView}; use super::helpers::{ensure_issue_visible, require_authenticated}; pub async fn create_issue_reaction( svc: &IssueService, actor: Uuid, channel_id: Uuid, issue_id: Uuid, input: CreateReactionOption, ) -> IssueResult { require_authenticated(actor)?; validate_emoji(svc, &input.emoji)?; ensure_issue_visible(svc, channel_id, issue_id).await?; let db = svc.db.writer(); let id = Uuid::new_v4(); let active: reaction_entity::ActiveModel = reaction_entity::ActiveModel::builder() .set_id(id) .set_issue_id(Some(issue_id)) .set_comment_id(None) .set_user_id(actor) .set_emoji(input.emoji) .set_created_at(OffsetDateTime::now_utc()) .into(); match reaction_entity::Entity::insert(active) .on_conflict_do_nothing() .exec(&db) .await? { TryInsertResult::Inserted(_) | TryInsertResult::Empty => {} TryInsertResult::Conflicted => { return Err(IssueError::Conflict("reaction already exists".into())); } } let saved = reaction_entity::Entity::find_by_id(id) .one(&db) .await? .ok_or_else(|| IssueError::Internal("reaction not found after insert".into()))?; Ok(ReactionView::from(saved)) } pub async fn create_comment_reaction( svc: &IssueService, actor: Uuid, channel_id: Uuid, issue_id: Uuid, comment_id: Uuid, input: CreateReactionOption, ) -> IssueResult { require_authenticated(actor)?; validate_emoji(svc, &input.emoji)?; load_issue(svc, channel_id, issue_id).await?; crate::features::comment::usecase::require_authenticated(actor)?; let db = svc.db.reader(); let _c = db::issue::comment::Entity::find_by_id(comment_id) .filter(db::issue::comment::COLUMN.issue_id.eq(issue_id)) .one(&db) .await? .ok_or_else(|| IssueError::NotFound(format!("comment {comment_id}")))?; let db = svc.db.writer(); let id = Uuid::new_v4(); let active: reaction_entity::ActiveModel = reaction_entity::ActiveModel::builder() .set_id(id) .set_issue_id(None) .set_comment_id(Some(comment_id)) .set_user_id(actor) .set_emoji(input.emoji) .set_created_at(OffsetDateTime::now_utc()) .into(); match reaction_entity::Entity::insert(active) .on_conflict_do_nothing() .exec(&db) .await? { TryInsertResult::Inserted(_) | TryInsertResult::Empty => {} TryInsertResult::Conflicted => { return Err(IssueError::Conflict("reaction already exists".into())); } } let saved = reaction_entity::Entity::find_by_id(id) .one(&db) .await? .ok_or_else(|| IssueError::Internal("reaction not found after insert".into()))?; Ok(ReactionView::from(saved)) } fn validate_emoji(svc: &IssueService, emoji: &str) -> IssueResult<()> { if emoji.trim().is_empty() || emoji.len() > svc.config.max_reaction_emoji_len { return Err(IssueError::BadRequest("invalid emoji".into())); } Ok(()) }