diff --git a/aries/src/service/api/nostr_router.rs b/aries/src/service/api/nostr_router.rs index 106c8f3ef..8a89ce41c 100644 --- a/aries/src/service/api/nostr_router.rs +++ b/aries/src/service/api/nostr_router.rs @@ -53,7 +53,7 @@ async fn receive( } }; //save - let storage = state.storage.services.relay_storage.clone(); + let storage = state.storage.relay_storage(); if storage .get_nostr_event_by_id(&ztm_nostr_event.id) .await @@ -89,7 +89,7 @@ async fn receive( filters: filters_json.clone(), id: Uuid::new_v4().to_string(), }; - let storage = state.storage.services.relay_storage.clone(); + let storage = state.storage.relay_storage(); let req_list: Vec = storage .get_all_nostr_req_by_subscription_id(&subscription_id.to_string()) .await @@ -113,7 +113,7 @@ pub async fn event_list( Query(_query): Query>, state: State, ) -> Result>, (StatusCode, String)> { - let storage = state.storage.services.relay_storage.clone(); + let storage = state.storage.relay_storage(); let event_list: Vec = storage .get_all_nostr_event() .await @@ -128,7 +128,7 @@ pub async fn req_list( Query(_query): Query>, state: State, ) -> Result>, (StatusCode, String)> { - let storage = state.storage.services.relay_storage.clone(); + let storage = state.storage.relay_storage(); let req_list: Vec = storage .get_all_nostr_req() .await diff --git a/ceres/src/api_service/import_api_service.rs b/ceres/src/api_service/import_api_service.rs index 9f3b0bc3d..cd88b9571 100644 --- a/ceres/src/api_service/import_api_service.rs +++ b/ceres/src/api_service/import_api_service.rs @@ -47,7 +47,7 @@ impl ApiHandler for ImportApiService { } async fn get_root_commit(&self) -> Commit { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); let refs = storage .get_default_ref(self.repo.repo_id) .await @@ -62,7 +62,7 @@ impl ApiHandler for ImportApiService { } async fn get_root_tree(&self) -> Tree { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); let refs = storage .get_default_ref(self.repo.repo_id) .await @@ -84,8 +84,7 @@ impl ApiHandler for ImportApiService { async fn get_tree_by_hash(&self, hash: &str) -> Tree { self.storage - .services - .git_db_storage + .git_db_storage() .get_tree_by_hash(self.repo.repo_id, hash) .await .unwrap() @@ -94,7 +93,7 @@ impl ApiHandler for ImportApiService { } async fn get_commit_by_hash(&self, hash: &str) -> Option { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); let commit = storage .get_commit_by_hash(self.repo.repo_id, hash) .await @@ -103,7 +102,7 @@ impl ApiHandler for ImportApiService { } async fn get_tree_relate_commit(&self, t_hash: &str) -> Commit { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); let tree_info = storage .get_tree_by_hash(self.repo.repo_id, t_hash) .await @@ -118,7 +117,7 @@ impl ApiHandler for ImportApiService { } async fn get_commits_by_hashes(&self, c_hashes: Vec) -> Result, GitError> { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); let commits = storage .get_commits_by_hashes(self.repo.repo_id, &c_hashes) .await diff --git a/ceres/src/api_service/mod.rs b/ceres/src/api_service/mod.rs index c2a8d7283..0cbcba807 100644 --- a/ceres/src/api_service/mod.rs +++ b/ceres/src/api_service/mod.rs @@ -39,11 +39,7 @@ pub trait ApiHandler: Send + Sync { async fn get_raw_blob_by_hash(&self, hash: &str) -> Result, MegaError> { let context = self.get_context(); - context - .services - .raw_db_storage - .get_raw_blob_by_hash(hash) - .await + context.raw_db_storage().get_raw_blob_by_hash(hash).await } fn strip_relative(&self, path: &Path) -> Result; diff --git a/ceres/src/api_service/mono_api_service.rs b/ceres/src/api_service/mono_api_service.rs index c3d744358..9e2361098 100644 --- a/ceres/src/api_service/mono_api_service.rs +++ b/ceres/src/api_service/mono_api_service.rs @@ -43,7 +43,7 @@ impl ApiHandler for MonoApiService { /// /// Returns `Ok(())` on success, or a `GitError` on failure. async fn create_monorepo_file(&self, file_info: CreateFileInfo) -> Result<(), GitError> { - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let path = PathBuf::from(file_info.path); let mut save_trees = vec![]; @@ -124,7 +124,7 @@ impl ApiHandler for MonoApiService { } async fn get_root_tree(&self) -> Tree { - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let refs = storage.get_ref("/").await.unwrap().unwrap(); storage @@ -137,8 +137,7 @@ impl ApiHandler for MonoApiService { async fn get_tree_by_hash(&self, hash: &str) -> Tree { self.storage - .services - .mono_storage + .mono_storage() .get_tree_by_hash(hash) .await .unwrap() @@ -147,15 +146,14 @@ impl ApiHandler for MonoApiService { } async fn get_commit_by_hash(&self, hash: &str) -> Option { - let storage = self.storage.services.mono_storage.clone(); - match storage.get_commit_by_hash(hash).await { + match self.storage.mono_storage().get_commit_by_hash(hash).await { Ok(Some(commit)) => Some(commit.into()), _ => None, } } async fn get_tree_relate_commit(&self, t_hash: &str) -> Commit { - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let tree_info = storage.get_tree_by_hash(t_hash).await.unwrap().unwrap(); storage .get_commit_by_hash(&tree_info.commit_id) @@ -166,8 +164,12 @@ impl ApiHandler for MonoApiService { } async fn get_commits_by_hashes(&self, c_hashes: Vec) -> Result, GitError> { - let storage = self.storage.services.mono_storage.clone(); - let commits = storage.get_commits_by_hashes(&c_hashes).await.unwrap(); + let commits = self + .storage + .mono_storage() + .get_commits_by_hashes(&c_hashes) + .await + .unwrap(); Ok(commits.into_iter().map(|x| x.into()).collect()) } @@ -179,7 +181,7 @@ impl ApiHandler for MonoApiService { Some(tree) => { let mut item_to_commit = HashMap::new(); - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let tree_hashes = tree .tree_items .iter() @@ -231,7 +233,7 @@ impl ApiHandler for MonoApiService { impl MonoApiService { pub async fn merge_mr(&self, username: &str, mr: mega_mr::Model) -> Result<(), MegaError> { - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let refs = storage.get_ref(&mr.path).await.unwrap().unwrap(); if mr.from_hash == refs.ref_commit_hash { @@ -258,7 +260,7 @@ impl MonoApiService { } // add conversation self.storage - .issue_storage() + .conversation_storage() .add_conversation(&mr.link, username, None, ConvTypeEnum::Merged) .await .unwrap(); @@ -280,7 +282,7 @@ impl MonoApiService { mut tree_vec: Vec, commit: Commit, ) -> Result { - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let mut save_trees = Vec::new(); let mut p_commit_id = String::new(); @@ -451,7 +453,7 @@ impl MonoApiService { commit_hash: &str, ) -> Result, MegaError> { let mut res = vec![]; - let mono_storage = self.storage.services.mono_storage.clone(); + let mono_storage = self.storage.mono_storage(); let commit = mono_storage.get_commit_by_hash(commit_hash).await?; if let Some(commit) = commit { let tree = mono_storage.get_tree_by_hash(&commit.tree).await?; diff --git a/ceres/src/pack/import_repo.rs b/ceres/src/pack/import_repo.rs index 5749de99c..1bb20e41e 100644 --- a/ceres/src/pack/import_repo.rs +++ b/ceres/src/pack/import_repo.rs @@ -48,8 +48,7 @@ impl PackHandler for ImportRepo { async fn head_hash(&self) -> (String, Vec) { let result = self .storage - .services - .git_db_storage + .git_db_storage() .get_ref(self.repo.repo_id) .await .unwrap(); @@ -62,7 +61,7 @@ impl PackHandler for ImportRepo { &self, mut receiver: UnboundedReceiver, ) -> Result, GitError> { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); let mut entry_list = vec![]; let semaphore = Arc::new(Semaphore::new(8)); let mut join_tasks = vec![]; @@ -105,8 +104,8 @@ impl PackHandler for ImportRepo { let (entry_tx, entry_rx) = mpsc::channel(pack_config.channel_message_size); let (stream_tx, stream_rx) = mpsc::channel(pack_config.channel_message_size); - let storage = self.storage.services.git_db_storage.clone(); - let raw_storage = self.storage.services.raw_db_storage.clone(); + let storage = self.storage.git_db_storage(); + let raw_storage = self.storage.raw_db_storage(); let total = storage.get_obj_count_by_repo_id(self.repo.repo_id).await; let encoder = PackEncoder::new(total, 0, stream_tx); encoder.encode_async(entry_rx).await.unwrap(); @@ -193,7 +192,7 @@ impl PackHandler for ImportRepo { ) -> Result>, GitError> { let mut want_clone = want.clone(); let pack_config = &self.storage.config().pack; - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); let obj_num = AtomicUsize::new(0); let mut exist_objs = HashSet::new(); @@ -286,8 +285,7 @@ impl PackHandler for ImportRepo { async fn get_trees_by_hashes(&self, hashes: Vec) -> Result, MegaError> { Ok(self .storage - .services - .git_db_storage + .git_db_storage() .get_trees_by_hashes(self.repo.repo_id, hashes) .await .unwrap() @@ -301,14 +299,13 @@ impl PackHandler for ImportRepo { hashes: Vec, ) -> Result, MegaError> { self.storage - .services - .raw_db_storage + .raw_db_storage() .get_raw_blobs_by_hashes(hashes) .await } async fn update_refs(&self, _: Option, refs: &RefCommand) -> Result<(), GitError> { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); match refs.command_type { CommandType::Create => { storage @@ -332,8 +329,7 @@ impl PackHandler for ImportRepo { async fn check_commit_exist(&self, hash: &str) -> bool { self.storage - .services - .git_db_storage + .git_db_storage() .get_commit_by_hash(self.repo.repo_id, hash) .await .unwrap() @@ -341,7 +337,7 @@ impl PackHandler for ImportRepo { } async fn check_default_branch(&self) -> bool { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); storage .default_branch_exist(self.repo.repo_id) .await @@ -366,14 +362,13 @@ impl ImportRepo { let mono_api_service = MonoApiService { storage: self.storage.clone(), }; - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let save_trees = mono_api_service.search_and_create_tree(&path).await?; let mut root_ref = storage.get_ref("/").await.unwrap().unwrap(); let latest_commit: Commit = self .storage - .services - .git_db_storage + .git_db_storage() .get_commit_by_hash(self.repo.repo_id, &commit_id) .await .unwrap() diff --git a/ceres/src/pack/monorepo.rs b/ceres/src/pack/monorepo.rs index 977820336..fce9c00d4 100644 --- a/ceres/src/pack/monorepo.rs +++ b/ceres/src/pack/monorepo.rs @@ -42,7 +42,7 @@ pub struct MonoRepo { #[async_trait] impl PackHandler for MonoRepo { async fn head_hash(&self) -> (String, Vec) { - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let result = storage.get_refs(self.path.to_str().unwrap()).await.unwrap(); @@ -126,7 +126,7 @@ impl PackHandler for MonoRepo { &self, mut receiver: UnboundedReceiver, ) -> Result, GitError> { - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let mut entry_list = Vec::new(); let mut join_tasks = vec![]; let mut current_commit_id = String::new(); @@ -176,7 +176,7 @@ impl PackHandler for MonoRepo { ) -> Result>, GitError> { let mut want_clone = want.clone(); let pack_config = &self.storage.config().pack; - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); let obj_num = AtomicUsize::new(0); let mut exist_objs = HashSet::new(); @@ -262,8 +262,7 @@ impl PackHandler for MonoRepo { async fn get_trees_by_hashes(&self, hashes: Vec) -> Result, MegaError> { Ok(self .storage - .services - .mono_storage + .mono_storage() .get_trees_by_hashes(hashes) .await .unwrap() @@ -277,14 +276,13 @@ impl PackHandler for MonoRepo { hashes: Vec, ) -> Result, MegaError> { self.storage - .services - .raw_db_storage + .raw_db_storage() .get_raw_blobs_by_hashes(hashes) .await } async fn update_refs(&self, commit: Option, refs: &RefCommand) -> Result<(), GitError> { - let storage = self.storage.services.mono_storage.clone(); + let storage = self.storage.mono_storage(); if let Some(c) = commit { let mr_link = self.handle_mr(&c.format_message()).await.unwrap(); @@ -311,8 +309,7 @@ impl PackHandler for MonoRepo { async fn check_commit_exist(&self, hash: &str) -> bool { self.storage - .services - .mono_storage + .mono_storage() .get_commit_by_hash(hash) .await .unwrap() @@ -352,10 +349,10 @@ impl MonoRepo { async fn handle_existing_mr(&self, mr: mega_mr::Model) -> Result<(), MegaError> { let mr_stg = self.storage.mr_storage(); - let issue_stg = self.storage.issue_storage(); + let comment_stg = self.storage.conversation_storage(); if mr.from_hash == self.from_hash { if mr.to_hash != self.to_hash { - issue_stg + comment_stg .add_conversation( &mr.link, "", @@ -372,7 +369,7 @@ impl MonoRepo { tracing::info!("repeat commit with mr: {}, do nothing", mr.id); } } else { - issue_stg + comment_stg .add_conversation( &mr.link, "", diff --git a/ceres/src/protocol/mod.rs b/ceres/src/protocol/mod.rs index c9fe1a250..67a749866 100644 --- a/ceres/src/protocol/mod.rs +++ b/ceres/src/protocol/mod.rs @@ -150,7 +150,7 @@ impl SmartProtocol { pub async fn pack_handler(&self) -> Result, ProtocolError> { let import_dir = self.storage.config().monorepo.import_dir.clone(); if self.path.starts_with(import_dir.clone()) { - let storage = self.storage.services.git_db_storage.clone(); + let storage = self.storage.git_db_storage(); let path_str = self.path.to_str().unwrap(); let model = storage.find_git_repo_exact_match(path_str).await.unwrap(); let repo = if let Some(repo) = model { diff --git a/context/src/lib.rs b/context/src/lib.rs index 1fd225db8..09334c052 100644 --- a/context/src/lib.rs +++ b/context/src/lib.rs @@ -36,11 +36,7 @@ impl AppContext { #[cfg(feature = "p2p")] let client = gemini::p2p::client::P2PClient::new(storage.clone(), vault.clone()); - storage - .services - .mono_storage - .init_monorepo(&config.monorepo) - .await; + storage.mono_storage().init_monorepo(&config.monorepo).await; Self { storage, diff --git a/gemini/src/p2p/client.rs b/gemini/src/p2p/client.rs index a5b6b8595..7d85ad1ff 100644 --- a/gemini/src/p2p/client.rs +++ b/gemini/src/p2p/client.rs @@ -251,7 +251,7 @@ impl P2PClient { } pub async fn repo_share(&self, path: String) -> Result { - let db = self.storage.services.git_db_storage.clone(); + let db = self.storage.git_db_storage(); let client = self.wrapped_client(); let repo: git_repo::Model = @@ -322,7 +322,7 @@ impl P2PClient { } async fn request_git_clone(&self, path: String, to_peer_id: String) -> Result<()> { - let db = self.storage.services.git_db_storage.clone(); + let db = self.storage.git_db_storage(); let model = match db.find_git_repo_exact_match(path.as_str()).await { Ok(model) => model, Err(e) => bail!(e), @@ -458,7 +458,7 @@ impl P2PClient { } async fn response_git_clone(&self, path: String, to_peer_id: String) -> Result<()> { - let db = self.storage.services.git_db_storage.clone(); + let db = self.storage.git_db_storage(); let repo: Repo = match get_git_model_by_path(self.storage.clone(), path.clone()).await { None => { diff --git a/gemini/src/util.rs b/gemini/src/util.rs index 0a874aa96..84f5b2255 100644 --- a/gemini/src/util.rs +++ b/gemini/src/util.rs @@ -80,8 +80,7 @@ pub fn get_repo_path(mut path: String) -> String { pub async fn get_git_model_by_path(storage: Storage, path: String) -> Option { let git_model = storage - .services - .git_db_storage + .git_db_storage() .find_git_repo_exact_match(get_repo_path(path).as_str()) .await; diff --git a/jupiter/README.md b/jupiter/README.md index 80a886ae3..b93ce150f 100644 --- a/jupiter/README.md +++ b/jupiter/README.md @@ -7,7 +7,7 @@ cd mega/jupiter/src -sea-orm-cli migrate generate +sea-orm-cli migrate generate "your_migration_name" ``` 2. Generate entity files diff --git a/jupiter/callisto/Cargo.toml b/jupiter/callisto/Cargo.toml index cc7ee7557..25b5ab251 100644 --- a/jupiter/callisto/Cargo.toml +++ b/jupiter/callisto/Cargo.toml @@ -12,3 +12,6 @@ path = "src/mod.rs" serde = { workspace = true, features = ["derive"] } sea-orm = { workspace = true, features = ["sqlx-postgres", "sqlx-mysql", "sqlx-sqlite"] } utoipa = { workspace = true, features = ["axum_extras"] } +chrono = { workspace = true } +idgenerator = { workspace = true } +rand = { workspace = true } diff --git a/jupiter/callisto/src/entity_ext/mega_conversation.rs b/jupiter/callisto/src/entity_ext/mega_conversation.rs index 4da288880..c8d2cd0f3 100644 --- a/jupiter/callisto/src/entity_ext/mega_conversation.rs +++ b/jupiter/callisto/src/entity_ext/mega_conversation.rs @@ -1,6 +1,10 @@ use sea_orm::entity::prelude::*; -use crate::{mega_conversation::Column, mega_conversation::Entity}; +use crate::{ + entity_ext::generate_id, + mega_conversation::{self, Column, Entity}, + sea_orm_active_enums::ConvTypeEnum, +}; #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { @@ -34,3 +38,23 @@ impl Related for Entity { Relation::MegaMr.def() } } + +impl mega_conversation::Model { + pub fn new( + link: &str, + conv_type: ConvTypeEnum, + comment: Option, + username: &str, + ) -> Self { + let now = chrono::Utc::now().naive_utc(); + Self { + id: generate_id(), + link: link.to_owned(), + conv_type, + comment, + created_at: now, + updated_at: now, + username: username.to_owned(), + } + } +} diff --git a/jupiter/callisto/src/entity_ext/mod.rs b/jupiter/callisto/src/entity_ext/mod.rs index 95b4ac5bd..81173f3ac 100644 --- a/jupiter/callisto/src/entity_ext/mod.rs +++ b/jupiter/callisto/src/entity_ext/mod.rs @@ -4,3 +4,39 @@ pub mod label; pub mod mega_conversation; pub mod mega_issue; pub mod mega_mr; +pub mod reactions; + +use idgenerator::IdInstance; +use rand::Rng; + +pub fn generate_id() -> i64 { + // Call `next_id` to generate a new unique id. + IdInstance::next_id() +} + +pub fn generate_public_id() -> String { + let rng = rand::rng(); + let str: String = rng + .sample_iter(rand::distr::Alphanumeric) + .take(12) + .map(char::from) + .collect(); + str.to_lowercase() +} + +#[cfg(test)] +mod test { + use crate::entity_ext::generate_public_id; + + #[test] + fn test_link_generate() { + let link = generate_public_id(); + println!("public id: {:?}", link); + assert!( + link.chars().count() == 12 + && link + .chars() + .all(|c| c.is_alphanumeric() || c.is_lowercase()) + ) + } +} diff --git a/jupiter/callisto/src/entity_ext/reactions.rs b/jupiter/callisto/src/entity_ext/reactions.rs new file mode 100644 index 000000000..eea19f57c --- /dev/null +++ b/jupiter/callisto/src/entity_ext/reactions.rs @@ -0,0 +1,25 @@ +use crate::entity_ext::{generate_id, generate_public_id}; +use crate::reactions; + +impl reactions::Model { + pub fn new( + content: Option, + subject_id: i64, + subject_type: &str, + username: &str, + ) -> Self { + let now = chrono::Utc::now().naive_utc(); + Self { + id: generate_id(), + created_at: now, + updated_at: now, + public_id: generate_public_id(), + content, + subject_id, + subject_type: subject_type.to_owned(), + organization_membership_id: None, + username: username.to_owned(), + discarded_at: None, + } + } +} diff --git a/jupiter/callisto/src/mod.rs b/jupiter/callisto/src/mod.rs index 66ad72715..b916452fb 100644 --- a/jupiter/callisto/src/mod.rs +++ b/jupiter/callisto/src/mod.rs @@ -29,6 +29,7 @@ pub mod mega_tag; pub mod mega_tree; pub mod mq_storage; pub mod raw_blob; +pub mod reactions; pub mod relay_lfs_info; pub mod relay_node; pub mod relay_nostr_event; diff --git a/jupiter/callisto/src/prelude.rs b/jupiter/callisto/src/prelude.rs index 6e3c76103..fc364466e 100644 --- a/jupiter/callisto/src/prelude.rs +++ b/jupiter/callisto/src/prelude.rs @@ -26,6 +26,7 @@ pub use super::mega_tag::Entity as MegaTag; pub use super::mega_tree::Entity as MegaTree; pub use super::mq_storage::Entity as MqStorage; pub use super::raw_blob::Entity as RawBlob; +pub use super::reactions::Entity as Reactions; pub use super::relay_lfs_info::Entity as RelayLfsInfo; pub use super::relay_node::Entity as RelayNode; pub use super::relay_nostr_event::Entity as RelayNostrEvent; diff --git a/jupiter/callisto/src/reactions.rs b/jupiter/callisto/src/reactions.rs new file mode 100644 index 000000000..970c06e4b --- /dev/null +++ b/jupiter/callisto/src/reactions.rs @@ -0,0 +1,25 @@ +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.12 + +use sea_orm::entity::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)] +#[sea_orm(table_name = "reactions")] +pub struct Model { + pub created_at: DateTime, + pub updated_at: DateTime, + #[sea_orm(primary_key, auto_increment = false)] + pub id: i64, + pub public_id: String, + pub content: Option, + pub subject_id: i64, + pub username: String, + pub subject_type: String, + pub organization_membership_id: Option, + pub discarded_at: Option, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] +pub enum Relation {} + +impl ActiveModelBehavior for ActiveModel {} diff --git a/jupiter/src/lib.rs b/jupiter/src/lib.rs index d6100852f..24a87f2ee 100644 --- a/jupiter/src/lib.rs +++ b/jupiter/src/lib.rs @@ -1,5 +1,7 @@ pub mod lfs_storage; pub mod migration; +pub mod model; +pub mod service; pub mod storage; pub mod utils; diff --git a/jupiter/src/migration/m20250710_073119_create_reactions.rs b/jupiter/src/migration/m20250710_073119_create_reactions.rs new file mode 100644 index 000000000..05321e238 --- /dev/null +++ b/jupiter/src/migration/m20250710_073119_create_reactions.rs @@ -0,0 +1,44 @@ +use sea_orm_migration::{prelude::*, schema::*}; + +use crate::migration::pk_bigint; + +#[derive(DeriveMigrationName)] +pub struct Migration; + +#[async_trait::async_trait] +impl MigrationTrait for Migration { + async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> { + manager + .create_table( + table_auto(Reactions::Table) + .col(pk_bigint(Reactions::Id)) + .col(string(Reactions::PublicId)) + .col(string_null(Reactions::Content)) + .col(big_integer(Reactions::SubjectId)) + .col(string(Reactions::SubjectType)) + .col(big_integer_null(Reactions::OrganizationMembershipId)) + .col(string(Reactions::Username)) + .col(date_time_null(Reactions::DiscardedAt)) + .to_owned(), + ) + .await?; + Ok(()) + } + + async fn down(&self, _: &SchemaManager) -> Result<(), DbErr> { + Ok(()) + } +} + +#[derive(DeriveIden)] +enum Reactions { + Table, + Id, + PublicId, + Content, + SubjectId, + SubjectType, + OrganizationMembershipId, + Username, + DiscardedAt, +} diff --git a/jupiter/src/migration/mod.rs b/jupiter/src/migration/mod.rs index 2f33fce97..1237c4716 100644 --- a/jupiter/src/migration/mod.rs +++ b/jupiter/src/migration/mod.rs @@ -43,6 +43,7 @@ mod m20250613_033821_alter_user_id; mod m20250618_065050_add_label; mod m20250628_025312_add_username_in_conversation; mod m20250702_072055_add_item_assignees; +mod m20250710_073119_create_reactions; /// Creates a primary key column definition with big integer type. /// @@ -74,6 +75,7 @@ impl MigratorTrait for Migrator { Box::new(m20250618_065050_add_label::Migration), Box::new(m20250628_025312_add_username_in_conversation::Migration), Box::new(m20250702_072055_add_item_assignees::Migration), + Box::new(m20250710_073119_create_reactions::Migration), ] } } diff --git a/jupiter/src/storage/stg_common/model.rs b/jupiter/src/model/common.rs similarity index 92% rename from jupiter/src/storage/stg_common/model.rs rename to jupiter/src/model/common.rs index 9737b1c9d..29c30a75a 100644 --- a/jupiter/src/storage/stg_common/model.rs +++ b/jupiter/src/model/common.rs @@ -23,7 +23,5 @@ pub struct ListParams { pub struct LabelAssigneeParams { pub item_id: i64, - pub link: String, - pub username: String, pub item_type: String, } diff --git a/jupiter/src/model/issue_dto.rs b/jupiter/src/model/issue_dto.rs new file mode 100644 index 000000000..d26ac250d --- /dev/null +++ b/jupiter/src/model/issue_dto.rs @@ -0,0 +1,13 @@ +use callisto::{item_assignees, label, mega_conversation, mega_issue, reactions}; + +pub struct IssueDetails { + pub issue: mega_issue::Model, + pub conversations: Vec, + pub labels: Vec, + pub assignees: Vec, +} + +pub struct ConvWithReactions { + pub conversation: mega_conversation::Model, + pub reactions: Vec, +} diff --git a/jupiter/src/model/mod.rs b/jupiter/src/model/mod.rs new file mode 100644 index 000000000..fbf5f8b3a --- /dev/null +++ b/jupiter/src/model/mod.rs @@ -0,0 +1,2 @@ +pub mod common; +pub mod issue_dto; diff --git a/jupiter/src/service/issue_service.rs b/jupiter/src/service/issue_service.rs new file mode 100644 index 000000000..354e5b4e0 --- /dev/null +++ b/jupiter/src/service/issue_service.rs @@ -0,0 +1,55 @@ +use crate::{ + model::issue_dto::IssueDetails, + service::IssueService, + storage::{ + base_storage::{BaseStorage, StorageConnector}, + conversation_storage::ConversationStorage, + issue_storage::IssueStorage, + }, +}; + +use common::errors::MegaError; + +impl IssueService { + pub fn new(issue_storage: IssueStorage, conversation_storage: ConversationStorage) -> Self { + Self { + issue_storage, + conversation_storage, + } + } + + pub fn mock() -> Self { + let mock = BaseStorage::mock(); + Self { + issue_storage: IssueStorage { base: mock.clone() }, + conversation_storage: ConversationStorage { base: mock.clone() }, + } + } + + pub async fn get_issue_details(&self, link: &str) -> Result { + let (issue, labels) = self + .issue_storage + .get_issue_labels(link) + .await? + .ok_or_else(|| MegaError::with_message("Issue not found"))?; + + let conversations = self + .conversation_storage + .get_comments_with_reactions(link) + .await?; + + let (_, assignees) = self + .issue_storage + .get_issue_assignees(link) + .await? + .unwrap_or((issue.clone(), vec![])); + + let res = IssueDetails { + issue, + labels, + conversations, + assignees, + }; + Ok(res) + } +} diff --git a/jupiter/src/service/mod.rs b/jupiter/src/service/mod.rs new file mode 100644 index 000000000..04d9b7fe6 --- /dev/null +++ b/jupiter/src/service/mod.rs @@ -0,0 +1,9 @@ +use crate::storage::{conversation_storage::ConversationStorage, issue_storage::IssueStorage}; + +pub mod issue_service; + +#[derive(Clone)] +pub struct IssueService { + pub issue_storage: IssueStorage, + pub conversation_storage: ConversationStorage, +} diff --git a/jupiter/src/storage/conversation_storage.rs b/jupiter/src/storage/conversation_storage.rs new file mode 100644 index 000000000..784911345 --- /dev/null +++ b/jupiter/src/storage/conversation_storage.rs @@ -0,0 +1,86 @@ +use std::collections::HashMap; +use std::ops::Deref; + +use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter}; + +use callisto::sea_orm_active_enums::ConvTypeEnum; +use callisto::{mega_conversation, reactions}; +use common::errors::MegaError; + +use crate::model::issue_dto::ConvWithReactions; +use crate::storage::base_storage::{BaseStorage, StorageConnector}; + +#[derive(Clone)] +pub struct ConversationStorage { + pub base: BaseStorage, +} + +impl Deref for ConversationStorage { + type Target = BaseStorage; + fn deref(&self) -> &Self::Target { + &self.base + } +} + +impl ConversationStorage { + pub async fn add_conversation( + &self, + link: &str, + username: &str, + comment: Option, + conv_type: ConvTypeEnum, + ) -> Result { + let conversation = mega_conversation::Model::new(link, conv_type, comment, username); + let conversation = conversation.into_active_model(); + let res = conversation.insert(self.get_connection()).await.unwrap(); + Ok(res.id) + } + + pub async fn add_reactions( + &self, + content: Option, + subject_id: i64, + subject_type: &str, + username: &str, + ) -> Result { + let reactions = reactions::Model::new(content, subject_id, subject_type, username); + let a_model = reactions.into_active_model(); + let res = a_model.insert(self.get_connection()).await?; + Ok(res) + } + + pub async fn get_comments_with_reactions( + &self, + link: &str, + ) -> Result, MegaError> { + let conversations = mega_conversation::Entity::find() + .filter(mega_conversation::Column::Link.eq(link)) + .all(self.get_connection()) + .await?; + + let conv_ids = conversations.iter().map(|c| c.id).collect::>(); + + let reactions = reactions::Entity::find() + .filter(reactions::Column::SubjectId.is_in(conv_ids.clone())) + .all(self.get_connection()) + .await?; + + let mut conv_map = HashMap::new(); + for conversation in conversations { + let related = reactions + .iter() + .filter(|r| r.subject_id == conversation.id) + .cloned() + .collect(); + conv_map.insert( + conversation.id, + ConvWithReactions { + conversation, + reactions: related, + }, + ); + } + + Ok(conv_map.into_values().collect()) + } +} diff --git a/jupiter/src/storage/issue_storage.rs b/jupiter/src/storage/issue_storage.rs index 97414f6d6..c0c4c52b4 100644 --- a/jupiter/src/storage/issue_storage.rs +++ b/jupiter/src/storage/issue_storage.rs @@ -6,15 +6,14 @@ use sea_orm::{ PaginatorTrait, QueryFilter, QuerySelect, RelationTrait, Set, TransactionTrait, }; -use callisto::sea_orm_active_enums::ConvTypeEnum; use callisto::{item_assignees, item_labels, label, mega_conversation, mega_issue}; use common::errors::MegaError; use common::model::Pagination; use common::utils::{generate_id, generate_link}; +use crate::model::common::{ItemDetails, LabelAssigneeParams, ListParams}; use crate::storage::base_storage::{BaseStorage, StorageConnector}; use crate::storage::stg_common::combine_item_list; -use crate::storage::stg_common::model::{ItemDetails, LabelAssigneeParams, ListParams}; use crate::storage::stg_common::query_build::{apply_sort, filter_by_assignees, filter_by_labels}; #[derive(Clone)] @@ -109,6 +108,31 @@ impl IssueStorage { Ok(model) } + pub async fn get_issue_labels( + &self, + link: &str, + ) -> Result)>, MegaError> { + let labels: Vec<(mega_issue::Model, Vec)> = mega_issue::Entity::find() + .filter(mega_issue::Column::Link.eq(link)) + .find_with_related(label::Entity) + .all(self.get_connection()) + .await?; + Ok(labels.first().cloned()) + } + + pub async fn get_issue_assignees( + &self, + link: &str, + ) -> Result)>, MegaError> { + let assignees: Vec<(mega_issue::Model, Vec)> = + mega_issue::Entity::find() + .filter(mega_issue::Column::Link.eq(link)) + .find_with_related(item_assignees::Entity) + .all(self.get_connection()) + .await?; + Ok(assignees.first().cloned()) + } + pub async fn get_issue_by_id(&self, id: i64) -> Result, MegaError> { let model = mega_issue::Entity::find_by_id(id) .one(self.get_connection()) @@ -176,27 +200,6 @@ impl IssueStorage { Ok(()) } - pub async fn add_conversation( - &self, - link: &str, - username: &str, - comment: Option, - conv_type: ConvTypeEnum, - ) -> Result { - let conversation = mega_conversation::Model { - id: generate_id(), - link: link.to_owned(), - conv_type, - comment, - created_at: chrono::Utc::now().naive_utc(), - updated_at: chrono::Utc::now().naive_utc(), - username: username.to_owned(), - }; - let conversation = conversation.into_active_model(); - let res = conversation.insert(self.get_connection()).await.unwrap(); - Ok(res.id) - } - pub async fn new_label( &self, name: &str, @@ -260,12 +263,7 @@ impl IssueStorage { ) -> Result<(), MegaError> { let txn = self.get_connection().begin().await?; - let LabelAssigneeParams { - item_id, - link, - username, - item_type, - } = params; + let LabelAssigneeParams { item_id, item_type } = params; if !to_remove.is_empty() { item_labels::Entity::delete_many() @@ -273,14 +271,6 @@ impl IssueStorage { .filter(item_labels::Column::LabelId.is_in(to_remove.clone())) .exec(&txn) .await?; - - self.add_conversation( - &link, - &username, - Some(format!("{username} removed {to_remove:?}")), - ConvTypeEnum::Label, - ) - .await?; } if !to_add.is_empty() { @@ -301,13 +291,6 @@ impl IssueStorage { item_labels::Entity::insert_many(new_item_labels) .exec(&txn) .await?; - self.add_conversation( - &link, - &username, - Some(format!("{username} added {to_add:?}")), - ConvTypeEnum::Label, - ) - .await?; } txn.commit().await?; @@ -323,12 +306,7 @@ impl IssueStorage { ) -> Result<(), MegaError> { let txn = self.get_connection().begin().await?; - let LabelAssigneeParams { - item_id, - link, - username, - item_type, - } = params; + let LabelAssigneeParams { item_id, item_type } = params; if !to_remove.is_empty() { item_assignees::Entity::delete_many() @@ -336,14 +314,6 @@ impl IssueStorage { .filter(item_assignees::Column::AssignneeId.is_in(to_remove.clone())) .exec(&txn) .await?; - - self.add_conversation( - &link, - &username, - Some(format!("{username} unassigned {to_remove:?}")), - ConvTypeEnum::Assignee, - ) - .await?; } if !to_add.is_empty() { @@ -364,13 +334,6 @@ impl IssueStorage { item_assignees::Entity::insert_many(new_item) .exec(&txn) .await?; - self.add_conversation( - &link, - &username, - Some(format!("{username} assigned {to_add:?}")), - ConvTypeEnum::Assignee, - ) - .await?; } txn.commit().await?; diff --git a/jupiter/src/storage/mod.rs b/jupiter/src/storage/mod.rs index 6f7c83a61..0bed22f54 100644 --- a/jupiter/src/storage/mod.rs +++ b/jupiter/src/storage/mod.rs @@ -1,4 +1,5 @@ pub mod base_storage; +pub mod conversation_storage; pub mod git_db_storage; pub mod init; pub mod issue_storage; @@ -16,6 +17,8 @@ use std::sync::{Arc, LazyLock, Weak}; use common::config::Config; use crate::lfs_storage::{self, local_storage::LocalStorage, LfsFileStorage}; +use crate::service::IssueService; +use crate::storage::conversation_storage::ConversationStorage; use crate::storage::init::database_connection; use crate::storage::{ git_db_storage::GitDbStorage, issue_storage::IssueStorage, lfs_db_storage::LfsDbStorage, @@ -26,7 +29,7 @@ use crate::storage::{ use crate::storage::base_storage::{BaseStorage, StorageConnector}; #[derive(Clone)] -pub struct Service { +pub struct AppService { pub mono_storage: MonoStorage, pub git_db_storage: GitDbStorage, pub raw_db_storage: RawDbStorage, @@ -36,28 +39,11 @@ pub struct Service { pub vault_storage: VaultStorage, pub mr_storage: MrStorage, pub issue_storage: IssueStorage, + pub conversation_storage: ConversationStorage, pub lfs_file_storage: Arc, } -impl Service { - async fn new(config: &Config) -> Self { - let connection = Arc::new(database_connection(&config.database).await); - let base = BaseStorage::new(connection.clone()); - - Self { - mono_storage: MonoStorage { base: base.clone() }, - git_db_storage: GitDbStorage { base: base.clone() }, - raw_db_storage: RawDbStorage { base: base.clone() }, - lfs_db_storage: LfsDbStorage { base: base.clone() }, - relay_storage: RelayStorage { base: base.clone() }, - user_storage: UserStorage { base: base.clone() }, - mr_storage: MrStorage { base: base.clone() }, - issue_storage: IssueStorage { base: base.clone() }, - vault_storage: VaultStorage { base: base.clone() }, - lfs_file_storage: lfs_storage::init(config.lfs.clone(), connection.clone()).await, - } - } - +impl AppService { fn mock() -> Arc { let mock = BaseStorage::mock(); Arc::new(Self { @@ -71,21 +57,52 @@ impl Service { lfs_file_storage: Arc::new(LocalStorage::mock()), mr_storage: MrStorage { base: mock.clone() }, issue_storage: IssueStorage { base: mock.clone() }, + conversation_storage: ConversationStorage { base: mock.clone() }, }) } } #[derive(Clone)] pub struct Storage { - pub services: Arc, + pub(crate) app_service: Arc, + pub issue_service: IssueService, pub config: Weak, } impl Storage { pub async fn new(config: Arc) -> Self { + let connection = Arc::new(database_connection(&config.database).await); + let base = BaseStorage::new(connection.clone()); + + let mono_storage = MonoStorage { base: base.clone() }; + let git_db_storage = GitDbStorage { base: base.clone() }; + let raw_db_storage = RawDbStorage { base: base.clone() }; + let lfs_db_storage = LfsDbStorage { base: base.clone() }; + let relay_storage = RelayStorage { base: base.clone() }; + let user_storage = UserStorage { base: base.clone() }; + let mr_storage = MrStorage { base: base.clone() }; + let issue_storage = IssueStorage { base: base.clone() }; + let vault_storage = VaultStorage { base: base.clone() }; + let conversation_storage = ConversationStorage { base: base.clone() }; + let lfs_file_storage = lfs_storage::init(config.lfs.clone(), connection.clone()).await; + + let app_service = AppService { + mono_storage, + git_db_storage, + raw_db_storage, + lfs_db_storage, + relay_storage, + user_storage, + vault_storage, + mr_storage, + issue_storage: issue_storage.clone(), + conversation_storage: conversation_storage.clone(), + lfs_file_storage, + }; Storage { - services: Service::new(&config).await.into(), + app_service: app_service.into(), config: Arc::downgrade(&config), + issue_service: IssueService::new(issue_storage, conversation_storage), } } @@ -94,43 +111,47 @@ impl Storage { } pub fn mono_storage(&self) -> MonoStorage { - self.services.mono_storage.clone() + self.app_service.mono_storage.clone() } pub fn git_db_storage(&self) -> GitDbStorage { - self.services.git_db_storage.clone() + self.app_service.git_db_storage.clone() } pub fn raw_db_storage(&self) -> RawDbStorage { - self.services.raw_db_storage.clone() + self.app_service.raw_db_storage.clone() } pub fn lfs_db_storage(&self) -> LfsDbStorage { - self.services.lfs_db_storage.clone() + self.app_service.lfs_db_storage.clone() } pub fn relay_storage(&self) -> RelayStorage { - self.services.relay_storage.clone() + self.app_service.relay_storage.clone() } pub fn user_storage(&self) -> UserStorage { - self.services.user_storage.clone() + self.app_service.user_storage.clone() } pub fn vault_storage(&self) -> VaultStorage { - self.services.vault_storage.clone() + self.app_service.vault_storage.clone() } pub fn mr_storage(&self) -> MrStorage { - self.services.mr_storage.clone() + self.app_service.mr_storage.clone() } pub fn issue_storage(&self) -> IssueStorage { - self.services.issue_storage.clone() + self.app_service.issue_storage.clone() + } + + pub fn conversation_storage(&self) -> ConversationStorage { + self.app_service.conversation_storage.clone() } pub fn lfs_file_storage(&self) -> Arc { - self.services.lfs_file_storage.clone() + self.app_service.lfs_file_storage.clone() } pub fn mock() -> Self { @@ -139,7 +160,8 @@ impl Storage { static CONFIG: LazyLock> = LazyLock::new(|| Config::mock().into()); Storage { - services: Service::mock(), + app_service: AppService::mock(), + issue_service: IssueService::mock(), config: Arc::downgrade(&*CONFIG), } } diff --git a/jupiter/src/storage/mr_storage.rs b/jupiter/src/storage/mr_storage.rs index e10cf6bf2..269ad3ce4 100644 --- a/jupiter/src/storage/mr_storage.rs +++ b/jupiter/src/storage/mr_storage.rs @@ -12,9 +12,9 @@ use callisto::{item_assignees, label, mega_conversation, mega_mr}; use common::errors::MegaError; use common::utils::generate_id; +use crate::model::common::{ItemDetails, ListParams}; use crate::storage::base_storage::{BaseStorage, StorageConnector}; use crate::storage::stg_common::combine_item_list; -use crate::storage::stg_common::model::{ItemDetails, ListParams}; use crate::storage::stg_common::query_build::{apply_sort, filter_by_assignees, filter_by_labels}; #[derive(Clone)] diff --git a/jupiter/src/storage/stg_common/item.rs b/jupiter/src/storage/stg_common/item.rs index 2ff6ab062..4a51a7c61 100644 --- a/jupiter/src/storage/stg_common/item.rs +++ b/jupiter/src/storage/stg_common/item.rs @@ -1,6 +1,6 @@ use callisto::{mega_issue, mega_mr}; -use crate::storage::stg_common::model::ItemKind; +use crate::model::common::ItemKind; pub trait ItemEntity { type Model; diff --git a/jupiter/src/storage/stg_common/mod.rs b/jupiter/src/storage/stg_common/mod.rs index b31a48179..54fa3a24d 100644 --- a/jupiter/src/storage/stg_common/mod.rs +++ b/jupiter/src/storage/stg_common/mod.rs @@ -4,10 +4,9 @@ use indexmap::IndexMap; use callisto::{item_assignees, label, mega_conversation, sea_orm_active_enums::ConvTypeEnum}; -use crate::storage::stg_common::{item::ItemEntity, model::ItemDetails}; +use crate::{model::common::ItemDetails, storage::stg_common::item::ItemEntity}; pub mod item; -pub mod model; pub mod query_build; /// Combine labels, assignees, and conversations into a unified list of `ItemDetails`. diff --git a/jupiter/src/tests.rs b/jupiter/src/tests.rs index 95bdac711..11f8f5ad6 100644 --- a/jupiter/src/tests.rs +++ b/jupiter/src/tests.rs @@ -8,13 +8,15 @@ use common::config::Config; use crate::lfs_storage::local_storage::LocalStorage; use crate::migration::apply_migrations; +use crate::service::IssueService; use crate::storage::base_storage::{BaseStorage, StorageConnector}; use crate::storage::{ - git_db_storage::GitDbStorage, issue_storage::IssueStorage, lfs_db_storage::LfsDbStorage, - mono_storage::MonoStorage, mr_storage::MrStorage, raw_db_storage::RawDbStorage, - relay_storage::RelayStorage, user_storage::UserStorage, vault_storage::VaultStorage, + conversation_storage::ConversationStorage, git_db_storage::GitDbStorage, + issue_storage::IssueStorage, lfs_db_storage::LfsDbStorage, mono_storage::MonoStorage, + mr_storage::MrStorage, raw_db_storage::RawDbStorage, relay_storage::RelayStorage, + user_storage::UserStorage, vault_storage::VaultStorage, }; -use crate::storage::{Service, Storage}; +use crate::storage::{AppService, Storage}; pub async fn test_db_connection(temp_dir: impl AsRef) -> DatabaseConnection { let db_url = format!("sqlite://{}/test.db", temp_dir.as_ref().to_string_lossy()); @@ -38,11 +40,10 @@ pub async fn test_storage(temp_dir: impl AsRef) -> Storage { static CONFIG: LazyLock> = LazyLock::new(|| Config::mock().into()); let connection = test_db_connection(temp_dir).await; let connection = Arc::new(connection); - // let lfs_db_storage = LfsDbStorage::new(connection.clone()).await; let config = CONFIG.clone(); let base = BaseStorage::new(connection.clone()); - let svc = Service { + let svc = AppService { mono_storage: MonoStorage { base: base.clone() }, git_db_storage: GitDbStorage { base: base.clone() }, raw_db_storage: RawDbStorage { base: base.clone() }, @@ -52,13 +53,15 @@ pub async fn test_storage(temp_dir: impl AsRef) -> Storage { mr_storage: MrStorage { base: base.clone() }, issue_storage: IssueStorage { base: base.clone() }, vault_storage: VaultStorage { base: base.clone() }, + conversation_storage: ConversationStorage { base: base.clone() }, lfs_file_storage: Arc::new(LocalStorage::mock()), // fix it when you really use it. }; apply_migrations(&connection, true).await.unwrap(); Storage { - services: Arc::new(svc), + app_service: Arc::new(svc), + issue_service: IssueService::mock(), config: Arc::downgrade(&config), } } diff --git a/mono/src/api/api_common/label_assignee.rs b/mono/src/api/api_common/label_assignee.rs index 36f812931..ac4c3c00c 100644 --- a/mono/src/api/api_common/label_assignee.rs +++ b/mono/src/api/api_common/label_assignee.rs @@ -2,15 +2,14 @@ use std::collections::HashSet; use axum::{extract::State, Json}; +use callisto::sea_orm_active_enums::ConvTypeEnum; use common::model::CommonResult; -use jupiter::storage::stg_common::model::LabelAssigneeParams; +use jupiter::model::common::LabelAssigneeParams; use crate::api::error::ApiError; +use crate::api::label::LabelUpdatePayload; use crate::api::MonoApiServiceState; -use crate::api::{ - api_common::model::{AssigneeUpdatePayload, LabelUpdatePayload}, - oauth::model::LoginUser, -}; +use crate::api::{api_common::model::AssigneeUpdatePayload, oauth::model::LoginUser}; pub async fn label_update( user: LoginUser, @@ -37,16 +36,35 @@ pub async fn label_update( let to_add: Vec = new_ids.difference(&old_ids).copied().collect(); let to_remove: Vec = old_ids.difference(&new_ids).copied().collect(); - let params = LabelAssigneeParams { - item_id, - link, - username: user.username, - item_type, - }; + let params = LabelAssigneeParams { item_id, item_type }; issue_storage - .modify_labels(to_add, to_remove, params) + .modify_labels(to_add.clone(), to_remove.clone(), params) .await?; + let username = user.username; + if !to_remove.is_empty() { + state + .conv_stg() + .add_conversation( + &link, + &username, + Some(format!("{username} removed {to_remove:?}")), + ConvTypeEnum::Label, + ) + .await?; + } + + if !to_add.is_empty() { + state + .conv_stg() + .add_conversation( + &link, + &username, + Some(format!("{username} added {to_add:?}")), + ConvTypeEnum::Label, + ) + .await?; + } Ok(Json(CommonResult::success(None))) } @@ -75,15 +93,34 @@ pub async fn assignees_update( let to_add: Vec = new_ids.difference(&old_ids).cloned().collect(); let to_remove: Vec = old_ids.difference(&new_ids).cloned().collect(); - let params = LabelAssigneeParams { - item_id, - link, - username: user.username, - item_type, - }; + let params = LabelAssigneeParams { item_id, item_type }; issue_storage - .modify_assignees(to_add, to_remove, params) + .modify_assignees(to_add.clone(), to_remove.clone(), params) .await?; + let username = user.username; + if !to_remove.is_empty() { + state + .conv_stg() + .add_conversation( + &link, + &username, + Some(format!("{username} unassigned {to_remove:?}")), + ConvTypeEnum::Assignee, + ) + .await?; + } + + if !to_add.is_empty() { + state + .conv_stg() + .add_conversation( + &link, + &username, + Some(format!("{username} assigned {to_add:?}")), + ConvTypeEnum::Assignee, + ) + .await?; + } Ok(Json(CommonResult::success(None))) } diff --git a/mono/src/api/api_common/model.rs b/mono/src/api/api_common/model.rs index 3306abc05..3a115dd74 100644 --- a/mono/src/api/api_common/model.rs +++ b/mono/src/api/api_common/model.rs @@ -1,14 +1,7 @@ use serde::Deserialize; use utoipa::ToSchema; -use jupiter::storage::stg_common::model::ListParams; - -#[derive(Deserialize, ToSchema)] -pub struct LabelUpdatePayload { - pub label_ids: Vec, - pub item_id: i64, - pub link: String, -} +use jupiter::model::common::ListParams; #[derive(Deserialize, ToSchema)] pub struct AssigneeUpdatePayload { diff --git a/mono/src/api/api_router.rs b/mono/src/api/api_router.rs index 65ed7ccb5..7a4248edd 100644 --- a/mono/src/api/api_router.rs +++ b/mono/src/api/api_router.rs @@ -20,7 +20,8 @@ use common::model::CommonResult; use utoipa_axum::{router::OpenApiRouter, routes}; use crate::api::{ - issue::issue_router, label::label_router, mr::mr_router, user::user_router, MonoApiServiceState, + conversation::conv_router, issue::issue_router, label::label_router, mr::mr_router, + user::user_router, MonoApiServiceState, }; use crate::{api::error::ApiError, server::https_server::GIT_TAG}; @@ -41,6 +42,7 @@ pub fn routers() -> OpenApiRouter { .merge(user_router::routers()) .merge(issue_router::routers()) .merge(label_router::routers()) + .merge(conv_router::routers()) } /// Get blob file as string @@ -340,8 +342,7 @@ async fn path_can_be_cloned( let res = if path.starts_with(&import_dir) { state .storage - .services - .git_db_storage + .git_db_storage() .find_git_repo_exact_match(path.to_str().unwrap()) .await .unwrap() diff --git a/mono/src/api/conversation/conv_router.rs b/mono/src/api/conversation/conv_router.rs new file mode 100644 index 000000000..c1d40722c --- /dev/null +++ b/mono/src/api/conversation/conv_router.rs @@ -0,0 +1,50 @@ +use axum::{ + extract::{Path, State}, + Json, +}; +use utoipa_axum::{router::OpenApiRouter, routes}; + +use common::model::CommonResult; + +use crate::api::conversation::ReactionRequest; +use crate::api::oauth::model::LoginUser; +use crate::api::MonoApiServiceState; +use crate::{api::error::ApiError, server::https_server::CONV_TAG}; + +pub fn routers() -> OpenApiRouter { + OpenApiRouter::new().nest( + "/conversation", + OpenApiRouter::new().routes(routes!(comment_reactions)), + ) +} + +/// Add comment reactions with emoji +#[utoipa::path( + post, + params( + ("comment_id", description = "comment conversation id"), + ), + path = "/comments/{comment_id}/reactions", + request_body = ReactionRequest, + responses( + (status = 200, body = CommonResult, content_type = "application/json") + ), + tag = CONV_TAG +)] +async fn comment_reactions( + user: LoginUser, + Path(comment_id): Path, + state: State, + Json(payload): Json, +) -> Result>, ApiError> { + state + .conv_stg() + .add_reactions( + Some(payload.content), + comment_id, + &payload.comment_type, + &user.username, + ) + .await?; + Ok(Json(CommonResult::success(None))) +} diff --git a/mono/src/api/conversation/mod.rs b/mono/src/api/conversation/mod.rs new file mode 100644 index 000000000..b7f176ab0 --- /dev/null +++ b/mono/src/api/conversation/mod.rs @@ -0,0 +1,57 @@ +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +use callisto::{reactions, sea_orm_active_enums::ConvTypeEnum}; +use jupiter::model::issue_dto::ConvWithReactions; + +pub mod conv_router; + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct ConversationItem { + pub id: i64, + pub username: String, + pub conv_type: ConvTypeEnum, + pub comment: Option, + pub created_at: i64, + pub updated_at: i64, + pub grouped_reactions: Vec, +} + +impl From for ConversationItem { + fn from(value: ConvWithReactions) -> Self { + Self { + id: value.conversation.id, + username: value.conversation.username, + conv_type: value.conversation.conv_type, + comment: value.conversation.comment, + created_at: value.conversation.created_at.and_utc().timestamp(), + updated_at: value.conversation.updated_at.and_utc().timestamp(), + grouped_reactions: vec![], + } + } +} + +#[derive(Serialize, Deserialize, ToSchema)] +pub struct ReactionItem { + pub viewer_reaction_id: String, + pub emoji: String, + pub tooltip: String, + pub reactions_count: usize, + pub custom_content: String, +} +impl From for ConversationItem { + fn from(_: reactions::Model) -> Self { + todo!() + } +} + +#[derive(Deserialize, ToSchema)] +pub struct SaveCommentRequest { + pub content: String, +} + +#[derive(Deserialize, ToSchema)] +pub struct ReactionRequest { + pub content: String, + pub comment_type: String, +} diff --git a/mono/src/api/issue/issue_router.rs b/mono/src/api/issue/issue_router.rs index c144032e2..040c7257b 100644 --- a/mono/src/api/issue/issue_router.rs +++ b/mono/src/api/issue/issue_router.rs @@ -6,17 +6,17 @@ use utoipa_axum::{router::OpenApiRouter, routes}; use callisto::sea_orm_active_enums::ConvTypeEnum; use common::model::{CommonPage, CommonResult, PageParams}; +use jupiter::service::IssueService; use crate::api::{ - api_common::model::{LabelUpdatePayload, ListPayload}, - mr::SaveCommentRequest, + api_common::model::ListPayload, conversation::SaveCommentRequest, label::LabelUpdatePayload, }; use crate::api::{ api_common::{self, model::AssigneeUpdatePayload}, MonoApiServiceState, }; use crate::api::{ - issue::{IssueDetail, ItemRes, NewIssue}, + issue::{IssueDetailRes, ItemRes, NewIssue}, oauth::model::LoginUser, }; use crate::{api::error::ApiError, server::https_server::ISSUE_TAG}; @@ -76,17 +76,10 @@ async fn fetch_issue_list( async fn issue_detail( Path(link): Path, state: State, -) -> Result>, ApiError> { - let res = state.issue_stg().get_issue(&link).await?; - let res = if let Some(model) = res { - let mut detail: IssueDetail = model.into(); - let conversations = state.issue_stg().get_conversations(&link).await.unwrap(); - detail.conversations = conversations.into_iter().map(|x| x.into()).collect(); - CommonResult::success(Some(detail)) - } else { - CommonResult::success(None) - }; - Ok(Json(res)) +) -> Result>, ApiError> { + let issue_service: IssueService = state.storage.issue_service.clone(); + let issue_details = issue_service.get_issue_details(&link).await?; + Ok(Json(CommonResult::success(Some(issue_details.into())))) } /// New Issue @@ -104,9 +97,13 @@ async fn new_issue( state: State, Json(json): Json, ) -> Result>, ApiError> { - let stg = state.issue_stg().clone(); - let res = stg.save_issue(&user.username, &json.title).await.unwrap(); - let _ = stg + let res = state + .issue_stg() + .save_issue(&user.username, &json.title) + .await + .unwrap(); + let _ = state + .conv_stg() .add_conversation( &res.link, &user.username, @@ -136,7 +133,7 @@ async fn close_issue( ) -> Result>, ApiError> { state.issue_stg().close_issue(&link).await?; state - .issue_stg() + .conv_stg() .add_conversation( &link, &user.username, @@ -166,7 +163,7 @@ async fn reopen_issue( ) -> Result>, ApiError> { state.issue_stg().reopen_issue(&link).await?; state - .issue_stg() + .conv_stg() .add_conversation( &link, &user.username, @@ -197,7 +194,7 @@ async fn save_comment( Json(payload): Json, ) -> Result>, ApiError> { state - .issue_stg() + .conv_stg() .add_conversation( &link, &user.username, diff --git a/mono/src/api/issue/mod.rs b/mono/src/api/issue/mod.rs index 2d8ff3754..a764a7175 100644 --- a/mono/src/api/issue/mod.rs +++ b/mono/src/api/issue/mod.rs @@ -1,9 +1,11 @@ -use callisto::mega_issue; -use jupiter::storage::stg_common::model::{ItemDetails, ItemKind}; +use jupiter::{ + model::common::{ItemDetails, ItemKind}, + model::issue_dto::IssueDetails, +}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::api::{label::LabelItem, mr::MegaConversation}; +use crate::api::{conversation::ConversationItem, label::LabelItem}; pub mod issue_router; @@ -65,24 +67,32 @@ pub struct NewIssue { } #[derive(Serialize, Deserialize)] -pub struct IssueDetail { +pub struct IssueDetailRes { pub id: i64, pub link: String, pub title: String, pub status: String, pub open_timestamp: i64, - pub conversations: Vec, + pub conversations: Vec, + pub labels: Vec, + pub assignees: Vec, } -impl From for IssueDetail { - fn from(value: mega_issue::Model) -> Self { +impl From for IssueDetailRes { + fn from(value: IssueDetails) -> Self { Self { - id: value.id, - link: value.link, - title: value.title, - status: value.status.to_string(), - open_timestamp: value.created_at.and_utc().timestamp(), - conversations: vec![], + id: value.issue.id, + link: value.issue.link, + title: value.issue.title, + status: value.issue.status.to_string(), + open_timestamp: value.issue.created_at.and_utc().timestamp(), + conversations: value.conversations.into_iter().map(|x| x.into()).collect(), + labels: value.labels.into_iter().map(|x| x.into()).collect(), + assignees: value + .assignees + .into_iter() + .map(|x| x.assignnee_id) + .collect(), } } } diff --git a/mono/src/api/label/mod.rs b/mono/src/api/label/mod.rs index c19bb3b5b..68ecb6b97 100644 --- a/mono/src/api/label/mod.rs +++ b/mono/src/api/label/mod.rs @@ -29,3 +29,10 @@ pub struct NewLabel { pub color: String, pub description: String, } + +#[derive(Deserialize, ToSchema)] +pub struct LabelUpdatePayload { + pub label_ids: Vec, + pub item_id: i64, + pub link: String, +} diff --git a/mono/src/api/mod.rs b/mono/src/api/mod.rs index 4aec1951c..1361da924 100644 --- a/mono/src/api/mod.rs +++ b/mono/src/api/mod.rs @@ -16,15 +16,16 @@ use ceres::{ protocol::repo::Repo, }; use common::errors::ProtocolError; -use jupiter::{ - storage::Storage, - storage::{issue_storage::IssueStorage, mr_storage::MrStorage, user_storage::UserStorage}, +use jupiter::storage::{ + conversation_storage::ConversationStorage, issue_storage::IssueStorage, mr_storage::MrStorage, + user_storage::UserStorage, Storage, }; use crate::api::oauth::campsite_store::CampsiteApiStore; pub mod api_common; pub mod api_router; +pub mod conversation; pub mod error; pub mod issue; pub mod label; @@ -103,13 +104,16 @@ impl MonoApiServiceState { self.storage.user_storage() } + fn conv_stg(&self) -> ConversationStorage { + self.storage.conversation_storage() + } + async fn api_handler(&self, path: &Path) -> Result, ProtocolError> { let import_dir = self.storage.config().monorepo.import_dir.clone(); if path.starts_with(&import_dir) && path != import_dir { if let Some(model) = self .storage - .services - .git_db_storage + .git_db_storage() .find_git_repo_like_path(path.to_str().unwrap()) .await .unwrap() diff --git a/mono/src/api/mr/mod.rs b/mono/src/api/mr/mod.rs index 2f37f523f..ed3d2544f 100644 --- a/mono/src/api/mr/mod.rs +++ b/mono/src/api/mr/mod.rs @@ -2,13 +2,13 @@ use std::str::FromStr; use ceres::model::mr::MrDiffFile; use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; +use uuid::Uuid; use callisto::{ mega_conversation, mega_mr, sea_orm_active_enums::{ConvTypeEnum, MergeStatusEnum}, }; -use utoipa::ToSchema; -use uuid::Uuid; pub mod mr_router; @@ -105,11 +105,6 @@ impl MuiTreeNode { } } -#[derive(Deserialize, ToSchema)] -pub struct SaveCommentRequest { - pub content: String, -} - #[derive(Serialize, ToSchema)] pub struct MrFilesRes { pub path: String, diff --git a/mono/src/api/mr/mr_router.rs b/mono/src/api/mr/mr_router.rs index cfe417d97..845b2de7c 100644 --- a/mono/src/api/mr/mr_router.rs +++ b/mono/src/api/mr/mr_router.rs @@ -16,10 +16,12 @@ use saturn::ActionEnum; use crate::api::{ api_common::{ self, - model::{AssigneeUpdatePayload, LabelUpdatePayload, ListPayload}, + model::{AssigneeUpdatePayload, ListPayload}, }, + conversation::SaveCommentRequest, issue::ItemRes, - mr::{FilesChangedList, MRDetail, MrFilesRes, MuiTreeNode, SaveCommentRequest}, + label::LabelUpdatePayload, + mr::{FilesChangedList, MRDetail, MrFilesRes, MuiTreeNode}, oauth::model::LoginUser, }; use crate::api::{util, MonoApiServiceState}; @@ -76,7 +78,7 @@ async fn reopen_mr( let link = model.link.clone(); state.mr_stg().reopen_mr(model).await?; state - .issue_stg() + .conv_stg() .add_conversation( &link, &user.username, @@ -121,7 +123,7 @@ async fn close_mr( let link = model.link.clone(); state.mr_stg().close_mr(model).await?; state - .issue_stg() + .conv_stg() .add_conversation( &link, &user.username, @@ -311,7 +313,7 @@ async fn save_comment( let res = state.mr_stg().get_mr(&link).await?; let model = res.ok_or(MegaError::with_message("Not Found"))?; state - .issue_stg() + .conv_stg() .add_conversation( &model.link, &user.username, diff --git a/mono/src/server/https_server.rs b/mono/src/server/https_server.rs index b19ffb2b2..3980b15c2 100644 --- a/mono/src/server/https_server.rs +++ b/mono/src/server/https_server.rs @@ -204,6 +204,7 @@ pub const GIT_TAG: &str = "git"; pub const MR_TAG: &str = "merge_request"; pub const ISSUE_TAG: &str = "issue"; pub const LABEL_TAG: &str = "label"; +pub const CONV_TAG: &str = "conversation"; #[derive(OpenApi)] #[openapi( tags( diff --git a/monobean/src/core/mega_core.rs b/monobean/src/core/mega_core.rs index 0a0bb5f98..4d4cbb6a8 100644 --- a/monobean/src/core/mega_core.rs +++ b/monobean/src/core/mega_core.rs @@ -297,9 +297,7 @@ impl MegaCore { if path.as_ref().starts_with(&import_dir) && path.as_ref() != import_dir { if let Some(model) = ctx .storage - .services - .as_ref() - .git_db_storage + .git_db_storage() .find_git_repo_like_path(path.as_ref().to_string_lossy().as_ref()) .await .unwrap()