use api::pagination::{Page, PageQuery}; use api::users::UserDto; use models::pull_request::{pull_request_event_line, pull_requests}; use sea_orm::ActiveValue::Set; use sea_orm::{ ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, PaginatorTrait, QueryFilter, QueryOrder, QuerySelect, }; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use uuid::Uuid; use crate::errors::PullError; use crate::services::service::PullService; pub const EVENT_CREATED: &str = "created"; pub const EVENT_UPDATED: &str = "updated"; pub const EVENT_CLOSED: &str = "closed"; pub const EVENT_REOPENED: &str = "reopened"; pub const EVENT_MERGED: &str = "merged"; pub const EVENT_COMMENTED: &str = "commented"; pub const EVENT_REVIEW_SUBMITTED: &str = "review_submitted"; pub const EVENT_REVIEW_DISMISSED: &str = "review_dismissed"; pub const EVENT_REVIEW_REQUESTED: &str = "review_requested"; pub const EVENT_REVIEW_REQUEST_CANCELLED: &str = "review_request_cancelled"; pub const EVENT_HEAD_UPDATED: &str = "head_updated"; pub const EVENT_LABELED: &str = "labeled"; pub const EVENT_UNLABELED: &str = "unlabeled"; pub const EVENT_ASSIGNED: &str = "assigned"; pub const EVENT_UNASSIGNED: &str = "unassigned"; pub const EVENT_LOCKED: &str = "locked"; pub const EVENT_UNLOCKED: &str = "unlocked"; pub const EVENT_AUTO_MERGE_ENABLED: &str = "auto_merge_enabled"; pub const EVENT_AUTO_MERGE_DISABLED: &str = "auto_merge_disabled"; #[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)] pub struct PullEventDto { pub id: Uuid, pub pull_request_id: Uuid, pub event_type: String, pub actor: Option, pub commit_sha: Option, pub content: Option, pub created_at: OffsetDateTime, } pub type EventPage = Page; pub async fn write_event( db: &impl ConnectionTrait, pull: &pull_requests::Model, actor: Option, event_type: &str, commit_sha: Option, content: Option, ) -> Result<(), sea_orm::DbErr> { let actor = match actor { Some(actor) => actor, None => pull.author_id, }; let payload = serde_json::json!({ "pull_request_id": pull.id, "user_id": actor, "event_type": event_type, "commit_sha": &commit_sha, "content": &content, "pull_request": { "id": pull.id, "repo_id": pull.repo_id, "issue_id": pull.issue_id, "number": pull.autoincrement_id, "author_id": pull.author_id, "title": &pull.title, "body": &pull.body, "head_branch": &pull.head_branch, "head_sha": &pull.head_sha, "head_label": &pull.head_label, "base_branch": &pull.base_branch, "base_sha": &pull.base_sha, "is_draft": pull.is_draft, "merge_method": pull.merge_method, "has_merged": pull.has_merged, "merged_commit_sha": &pull.merged_commit_sha, "squash_commit_sha": &pull.squash_commit_sha, "auto_merge": pull.auto_merge, "additions": pull.additions, "deletions": pull.deletions, "files_changed": pull.files_changed, "merger_id": pull.merger_id, "merged_at": pull.merged_at, "is_locked": pull.is_locked, "created_at": pull.created_at, "updated_at": pull.updated_at, "closed_at": pull.closed_at, }, }); let row = pull_request_event_line::ActiveModel { id: Set(Uuid::now_v7()), pull_request_id: Set(pull.id), user_id: Set(actor), event_type: Set(event_type.to_string()), commit_sha: Set(commit_sha), content: Set(content), created_at: Set(OffsetDateTime::now_utc()), }; row.insert(db).await?; let Some(kind) = event_kind(event_type) else { return Ok(()); }; let labels = queue::Labels::new() .with("pull_id", pull.id) .with("user_id", actor); events::emit(db, kind, labels, payload).await.map(|_| ()) } fn event_kind(event_type: &str) -> Option { Some(match event_type { EVENT_CREATED => events::EventKind::PullPullCreate, EVENT_UPDATED => events::EventKind::PullPullUpdate, EVENT_CLOSED => events::EventKind::PullPullClose, EVENT_REOPENED => events::EventKind::PullPullReopen, EVENT_MERGED => events::EventKind::PullPullMerge, EVENT_COMMENTED => events::EventKind::PullCommentCreate, EVENT_REVIEW_SUBMITTED => events::EventKind::PullReviewCreate, EVENT_REVIEW_DISMISSED => events::EventKind::PullReviewDelete, EVENT_REVIEW_REQUESTED => events::EventKind::PullReviewRequestCreate, EVENT_REVIEW_REQUEST_CANCELLED => events::EventKind::PullReviewRequestDelete, EVENT_HEAD_UPDATED => events::EventKind::PullHeadUpdate, EVENT_LABELED => events::EventKind::PullLabelCreate, EVENT_UNLABELED => events::EventKind::PullLabelDelete, EVENT_ASSIGNED => events::EventKind::PullAssigneeCreate, EVENT_UNASSIGNED => events::EventKind::PullAssigneeDelete, EVENT_LOCKED => events::EventKind::PullLockCreate, EVENT_UNLOCKED => events::EventKind::PullLockDelete, EVENT_AUTO_MERGE_ENABLED => events::EventKind::PullAutoMergeCreate, EVENT_AUTO_MERGE_DISABLED => events::EventKind::PullAutoMergeDelete, _ => return None, }) } impl PullService { pub async fn record_event( &self, pull: &pull_requests::Model, actor: Option, event_type: &str, commit_sha: Option, content: Option, ) -> Result<(), PullError> { self.record_event_with( self.db_write(), pull, actor, event_type, commit_sha, content, ) .await } pub(crate) async fn record_event_with( &self, conn: &impl ConnectionTrait, pull: &pull_requests::Model, actor: Option, event_type: &str, commit_sha: Option, content: Option, ) -> Result<(), PullError> { write_event(conn, pull, actor, event_type, commit_sha, content).await?; Ok(()) } pub async fn list_events( &self, pull_id: Uuid, page: PageQuery, ) -> Result { let (limit, offset) = page.bounds()?; let total = pull_request_event_line::Entity::find() .filter(pull_request_event_line::Column::PullRequestId.eq(pull_id)) .count(self.db_read()) .await?; let rows = pull_request_event_line::Entity::find() .filter(pull_request_event_line::Column::PullRequestId.eq(pull_id)) .order_by_asc(pull_request_event_line::Column::CreatedAt) .limit(limit) .offset(offset) .all(self.db_read()) .await?; let actors = self .actors .briefs(&{ let rows = &rows; let mut ids: Vec = rows.iter().map(|row| row.user_id).collect(); ids.sort(); ids.dedup(); ids }) .await?; let items = rows .into_iter() .map(|row| PullEventDto { id: row.id, pull_request_id: row.pull_request_id, event_type: row.event_type, commit_sha: row.commit_sha, content: row.content, created_at: row.created_at, actor: actors.get(&row.user_id).cloned(), }) .collect(); Ok(Page { items, total, limit, offset, }) } }