use models::repo::{repo_relation_topic, repo_topic, repos}; use sea_orm::{ ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set, }; use serde::{Deserialize, Serialize}; use time::OffsetDateTime; use utoipa::ToSchema; use uuid::Uuid; use crate::context::facade::GitContext; use crate::service::GitService; use crate::share::errors::GitError; #[derive(Debug, Clone, Serialize, ToSchema)] pub struct TopicDto { pub id: Uuid, pub name: String, pub created_at: OffsetDateTime, pub updated_at: OffsetDateTime, } impl From for TopicDto { fn from(m: repo_topic::Model) -> Self { Self { id: m.id, name: m.name, created_at: m.created_at, updated_at: m.updated_at, } } } #[derive(Debug, Clone, Deserialize, ToSchema)] pub struct CreateTopicCmd { pub name: String, } #[derive(Debug, Clone, Serialize, ToSchema)] pub struct TopicRepoDto { pub repo_id: Uuid, pub repo_name: String, pub space_name: String, pub is_private: bool, } impl GitService { pub async fn create_topic(&self, name: String) -> Result { let trimmed = name.trim().to_string(); if trimmed.is_empty() { return Err(GitError::InvalidArgument( "topic name must not be empty".to_string(), )); } let now = OffsetDateTime::now_utc(); let existing = repo_topic::Entity::find() .filter(repo_topic::Column::Name.eq(&trimmed)) .one(self.db_read()) .await?; if let Some(row) = existing { return Ok(row.into()); } let am = repo_topic::ActiveModel { id: Set(Uuid::now_v7()), name: Set(trimmed), created_at: Set(now), updated_at: Set(now), }; let saved = am.insert(self.db_write()).await?; Ok(saved.into()) } pub async fn get_topic(&self, name: &str) -> Result { let row = repo_topic::Entity::find() .filter(repo_topic::Column::Name.eq(name)) .one(self.db_read()) .await? .ok_or(GitError::NotFound)?; Ok(row.into()) } pub async fn list_topics(&self, limit: u64, offset: u64) -> Result, GitError> { let rows = repo_topic::Entity::find() .order_by_asc(repo_topic::Column::Name) .limit(limit) .offset(offset) .all(self.db_read()) .await?; Ok(rows.into_iter().map(Into::into).collect()) } pub async fn add_topic_to_repo( &self, ctx: &GitContext, topic_name: &str, ) -> Result { let now = OffsetDateTime::now_utc(); let topic = match repo_topic::Entity::find() .filter(repo_topic::Column::Name.eq(topic_name)) .one(self.db_read()) .await? { Some(t) => t, None => { let am = repo_topic::ActiveModel { id: Set(Uuid::now_v7()), name: Set(topic_name.to_string()), created_at: Set(now), updated_at: Set(now), }; am.insert(self.db_write()).await? } }; let existing = repo_relation_topic::Entity::find() .filter(repo_relation_topic::Column::RepoId.eq(ctx.model.id)) .filter(repo_relation_topic::Column::TopicId.eq(topic.id)) .one(self.db_read()) .await?; if existing.is_none() { let am = repo_relation_topic::ActiveModel { id: Set(Uuid::now_v7()), repo_id: Set(ctx.model.id), topic_id: Set(topic.id), created_at: Set(now), }; am.insert(self.db_write()).await?; } Ok(topic.into()) } pub async fn remove_topic_from_repo( &self, ctx: &GitContext, topic_name: &str, ) -> Result<(), GitError> { let topic = repo_topic::Entity::find() .filter(repo_topic::Column::Name.eq(topic_name)) .one(self.db_read()) .await? .ok_or(GitError::NotFound)?; repo_relation_topic::Entity::delete_many() .filter(repo_relation_topic::Column::RepoId.eq(ctx.model.id)) .filter(repo_relation_topic::Column::TopicId.eq(topic.id)) .exec(self.db_write()) .await?; Ok(()) } pub async fn list_repo_topics(&self, ctx: &GitContext) -> Result, GitError> { let relations = repo_relation_topic::Entity::find() .filter(repo_relation_topic::Column::RepoId.eq(ctx.model.id)) .all(self.db_read()) .await?; if relations.is_empty() { return Ok(Vec::new()); } let topic_ids: Vec = relations.iter().map(|r| r.topic_id).collect(); let topics = repo_topic::Entity::find() .filter(repo_topic::Column::Id.is_in(topic_ids)) .all(self.db_read()) .await?; Ok(topics.into_iter().map(Into::into).collect()) } pub async fn list_repos_by_topic( &self, topic_name: &str, limit: u64, offset: u64, ) -> Result, GitError> { let topic = repo_topic::Entity::find() .filter(repo_topic::Column::Name.eq(topic_name)) .one(self.db_read()) .await? .ok_or(GitError::NotFound)?; let relations = repo_relation_topic::Entity::find() .filter(repo_relation_topic::Column::TopicId.eq(topic.id)) .limit(limit) .offset(offset) .all(self.db_read()) .await?; if relations.is_empty() { return Ok(Vec::new()); } let repo_ids: Vec = relations.iter().map(|r| r.repo_id).collect(); let rows = repos::Entity::find() .filter(repos::Column::Id.is_in(repo_ids)) .all(self.db_read()) .await?; Ok(rows .into_iter() .map(|r| TopicRepoDto { repo_id: r.id, repo_name: r.name, space_name: String::new(), is_private: r.is_private, }) .collect()) } }