Skip to content
This repository was archived by the owner on Oct 19, 2026. It is now read-only.

Commit 24e0317

Browse files
authored
feat: add bynamic sidebar db and api router (#1698)
* feat: add bynamic sidebar db and api router * feat: fix review problems * feat: fmt
1 parent e8898f7 commit 24e0317

15 files changed

Lines changed: 446 additions & 3 deletions

File tree

‎ceres/src/model/dynamic_sidebar.rs‎

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
use callisto::dynamic_sidebar;
2+
use serde::{Deserialize, Serialize};
3+
use utoipa::ToSchema;
4+
5+
#[derive(Serialize, Deserialize, ToSchema)]
6+
pub struct SidebarRes {
7+
pub id: i32,
8+
pub public_id: String,
9+
pub label: String,
10+
pub href: String,
11+
pub visible: bool,
12+
pub order_index: i32,
13+
}
14+
15+
impl From<dynamic_sidebar::Model> for SidebarRes {
16+
fn from(value: dynamic_sidebar::Model) -> Self {
17+
Self {
18+
id: value.id,
19+
public_id: value.public_id,
20+
label: value.label,
21+
href: value.href,
22+
visible: value.visible,
23+
order_index: value.order_index,
24+
}
25+
}
26+
}
27+
28+
pub type SidebarMenuListRes = Vec<SidebarRes>;
29+
30+
#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)]
31+
pub struct CreateSidebarPayload {
32+
pub public_id: String,
33+
pub label: String,
34+
pub href: String,
35+
pub visible: bool,
36+
pub order_index: i32,
37+
}
38+
39+
#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)]
40+
pub struct UpdateSidebarPayload {
41+
pub public_id: Option<String>,
42+
pub label: Option<String>,
43+
pub href: Option<String>,
44+
pub visible: Option<bool>,
45+
pub order_index: Option<i32>,
46+
}

‎ceres/src/model/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ pub mod buck;
33
pub mod change_list;
44
pub mod commit;
55
pub mod conversation;
6+
pub mod dynamic_sidebar;
67
pub mod git;
78
pub mod gpg;
89
pub mod issue;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.19
2+
3+
use sea_orm::entity::prelude::*;
4+
use serde::{Deserialize, Serialize};
5+
6+
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
7+
#[sea_orm(table_name = "dynamic_sidebar")]
8+
pub struct Model {
9+
#[sea_orm(primary_key)]
10+
pub id: i32,
11+
#[sea_orm(unique)]
12+
pub public_id: String,
13+
pub label: String,
14+
pub href: String,
15+
pub visible: bool,
16+
#[sea_orm(unique)]
17+
pub order_index: i32,
18+
}
19+
20+
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
21+
pub enum Relation {}
22+
23+
impl ActiveModelBehavior for ActiveModel {}

‎jupiter/callisto/src/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ pub mod buck_session_file;
88
pub mod builds;
99
pub mod check_result;
1010
pub mod commit_auths;
11+
pub mod dynamic_sidebar;
1112
pub mod entity_ext;
1213
pub mod git_blob;
1314
pub mod git_commit;

‎jupiter/callisto/src/prelude.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ pub use super::buck_session_file::Entity as BuckSessionFile;
66
pub use super::builds::Entity as Builds;
77
pub use super::check_result::Entity as CheckResult;
88
pub use super::commit_auths::Entity as CommitAuths;
9+
pub use super::dynamic_sidebar::Entity as DynamicSidebar;
910
pub use super::git_blob::Entity as GitBlob;
1011
pub use super::git_commit::Entity as GitCommit;
1112
pub use super::git_issue::Entity as GitIssue;
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
use sea_orm::DatabaseBackend;
2+
use sea_orm_migration::prelude::*;
3+
4+
#[derive(DeriveMigrationName)]
5+
pub struct Migration;
6+
7+
#[async_trait::async_trait]
8+
impl MigrationTrait for Migration {
9+
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
10+
manager
11+
.create_table(
12+
Table::create()
13+
.table(DynamicSidebar::Table)
14+
.if_not_exists()
15+
.col(
16+
ColumnDef::new(DynamicSidebar::Id)
17+
.integer()
18+
.not_null()
19+
.auto_increment()
20+
.primary_key(),
21+
)
22+
.col(ColumnDef::new(DynamicSidebar::PublicId).string().not_null())
23+
.col(ColumnDef::new(DynamicSidebar::Label).string().not_null())
24+
.col(ColumnDef::new(DynamicSidebar::Href).string().not_null())
25+
.col(ColumnDef::new(DynamicSidebar::Visible).boolean().not_null())
26+
.col(
27+
ColumnDef::new(DynamicSidebar::OrderIndex)
28+
.integer()
29+
.not_null(),
30+
)
31+
.to_owned(),
32+
)
33+
.await?;
34+
35+
manager
36+
.create_index(
37+
Index::create()
38+
.unique()
39+
.name("idx_dynamic_sidebar_public_id_unique")
40+
.table(DynamicSidebar::Table)
41+
.col(DynamicSidebar::PublicId)
42+
.to_owned(),
43+
)
44+
.await?;
45+
46+
manager
47+
.create_index(
48+
Index::create()
49+
.unique()
50+
.name("idx_dynamic_sidebar_order_index")
51+
.table(DynamicSidebar::Table)
52+
.col(DynamicSidebar::OrderIndex)
53+
.to_owned(),
54+
)
55+
.await?;
56+
57+
let backend = manager.get_database_backend();
58+
59+
match backend {
60+
DatabaseBackend::Postgres => {
61+
let sql = r#"
62+
INSERT INTO dynamic_sidebar (public_id, label, href, visible, order_index) VALUES
63+
('home', 'Home', '/posts', true, 0),
64+
('inbox', 'Inbox', '/inbox', true, 1),
65+
('chat', 'Chat', '/chat', true, 2),
66+
('notes', 'Docs', '/notes', true, 3),
67+
('calls', 'Calls', '/calls', true, 4),
68+
('drafts', 'Drafts', '/drafts', true, 5),
69+
('code', 'Code', '/code', true, 6),
70+
('tags', 'Tags', '/code/tags', true, 7),
71+
('cl', 'Change List', '/cl', true, 8),
72+
('mq', 'Merge Queue', '/queue/main', true, 9),
73+
('issue', 'Issue', '/issue', true, 10),
74+
('rust', 'Rust', '/rust', true, 11);
75+
"#;
76+
77+
manager.get_connection().execute_unprepared(sql).await?;
78+
}
79+
DatabaseBackend::Sqlite | DatabaseBackend::MySql => {}
80+
}
81+
82+
Ok(())
83+
}
84+
85+
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
86+
manager
87+
.drop_table(Table::drop().table(DynamicSidebar::Table).to_owned())
88+
.await
89+
}
90+
}
91+
92+
#[derive(DeriveIden)]
93+
enum DynamicSidebar {
94+
Table,
95+
Id,
96+
PublicId,
97+
Label,
98+
Href,
99+
Visible,
100+
OrderIndex,
101+
}

‎jupiter/src/migration/mod.rs‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ mod m20251117_181240_add_system_required_field_for_reviewer;
7272
mod m20251119_145041_add_draft_status;
7373
mod m20251125_135032_add_draft_conv_type;
7474
mod m20251128_000001_create_buck_session;
75+
mod m20251203_013745_add_dynamic_sidebar;
7576

7677
/// Creates a primary key column definition with big integer type.
7778
///
@@ -132,6 +133,7 @@ impl MigratorTrait for Migrator {
132133
Box::new(m20251119_145041_add_draft_status::Migration),
133134
Box::new(m20251125_135032_add_draft_conv_type::Migration),
134135
Box::new(m20251128_000001_create_buck_session::Migration),
136+
Box::new(m20251203_013745_add_dynamic_sidebar::Migration),
135137
]
136138
}
137139
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
use std::ops::Deref;
2+
3+
use crate::storage::base_storage::{BaseStorage, StorageConnector};
4+
use callisto::dynamic_sidebar;
5+
use common::errors::MegaError;
6+
use sea_orm::{
7+
ActiveModelTrait,
8+
ActiveValue::{NotSet, Set},
9+
EntityTrait, QueryOrder,
10+
};
11+
12+
#[derive(Clone)]
13+
pub struct DynamicSidebarStorage {
14+
pub base: BaseStorage,
15+
}
16+
17+
impl Deref for DynamicSidebarStorage {
18+
type Target = BaseStorage;
19+
fn deref(&self) -> &Self::Target {
20+
&self.base
21+
}
22+
}
23+
24+
impl DynamicSidebarStorage {
25+
pub async fn get_sidebar_by_id(
26+
&self,
27+
id: i32,
28+
) -> Result<Option<dynamic_sidebar::Model>, MegaError> {
29+
let model = dynamic_sidebar::Entity::find_by_id(id)
30+
.one(self.get_connection())
31+
.await?;
32+
33+
Ok(model)
34+
}
35+
36+
pub async fn get_sidebars(&self) -> Result<Vec<dynamic_sidebar::Model>, MegaError> {
37+
let res = dynamic_sidebar::Entity::find()
38+
.order_by_asc(dynamic_sidebar::Column::OrderIndex)
39+
.all(self.get_connection())
40+
.await?;
41+
Ok(res)
42+
}
43+
44+
pub async fn new_sidebar(
45+
&self,
46+
public_id: String,
47+
label: String,
48+
href: String,
49+
visible: bool,
50+
order_index: i32,
51+
) -> Result<dynamic_sidebar::Model, MegaError> {
52+
let active_model = dynamic_sidebar::ActiveModel {
53+
id: NotSet,
54+
public_id: Set(public_id),
55+
label: Set(label),
56+
href: Set(href),
57+
visible: Set(visible),
58+
order_index: Set(order_index),
59+
};
60+
61+
let res = active_model.insert(self.get_connection()).await?;
62+
Ok(res)
63+
}
64+
65+
pub async fn update_sidebar(
66+
&self,
67+
id: i32,
68+
public_id: Option<String>,
69+
label: Option<String>,
70+
href: Option<String>,
71+
visible: Option<bool>,
72+
order_index: Option<i32>,
73+
) -> Result<dynamic_sidebar::Model, MegaError> {
74+
let model = dynamic_sidebar::Entity::find_by_id(id)
75+
.one(self.get_connection())
76+
.await?
77+
.ok_or_else(|| MegaError::Other(format!("Sidebar with id `{id}` not found")))?;
78+
79+
let mut active_model: dynamic_sidebar::ActiveModel = model.into();
80+
81+
if let Some(public_id) = public_id {
82+
active_model.public_id = Set(public_id);
83+
}
84+
if let Some(label) = label {
85+
active_model.label = Set(label);
86+
}
87+
if let Some(href) = href {
88+
active_model.href = Set(href);
89+
}
90+
if let Some(visible) = visible {
91+
active_model.visible = Set(visible);
92+
}
93+
if let Some(order_index) = order_index {
94+
active_model.order_index = Set(order_index);
95+
}
96+
97+
let updated_model = active_model.update(self.get_connection()).await?;
98+
99+
Ok(updated_model)
100+
}
101+
102+
pub async fn delete_sidebar(&self, id: i32) -> Result<dynamic_sidebar::Model, MegaError> {
103+
let model = dynamic_sidebar::Entity::find_by_id(id)
104+
.one(self.get_connection())
105+
.await?
106+
.ok_or_else(|| MegaError::Other(format!("Sidebar with id `{id}` not found")))?;
107+
108+
let delete_result = dynamic_sidebar::Entity::delete_by_id(id)
109+
.exec(self.get_connection())
110+
.await
111+
.map_err(|e| MegaError::Other(format!("Failed to delete sidebar: {e}")))?;
112+
113+
if delete_result.rows_affected == 0 {
114+
return Err(MegaError::Other(format!(
115+
"Sidebar with id `{id}` was not deleted"
116+
)));
117+
}
118+
119+
Ok(model)
120+
}
121+
}

‎jupiter/src/storage/mod.rs‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ pub mod cl_reviewer_storage;
44
pub mod cl_storage;
55
pub mod commit_binding_storage;
66
pub mod conversation_storage;
7+
pub mod dynamic_sidebar_storage;
78
pub mod git_db_storage;
89
pub mod gpg_storage;
910
pub mod init;
@@ -27,6 +28,7 @@ use crate::service::cl_service::CLService;
2728
use crate::service::issue_service::IssueService;
2829
use crate::service::merge_queue_service::MergeQueueService;
2930
use crate::storage::conversation_storage::ConversationStorage;
31+
use crate::storage::dynamic_sidebar_storage::DynamicSidebarStorage;
3032
use crate::storage::init::database_connection;
3133
use crate::storage::{
3234
buck_storage::BuckStorage, cl_storage::ClStorage, commit_binding_storage::CommitBindingStorage,
@@ -59,6 +61,7 @@ pub struct AppService {
5961
pub reviewer_storage: ClReviewerStorage,
6062
pub merge_queue_storage: MergeQueueStorage,
6163
pub buck_storage: BuckStorage,
64+
pub dynamic_sidebar_storage: DynamicSidebarStorage,
6265
}
6366

6467
impl AppService {
@@ -82,6 +85,7 @@ impl AppService {
8285
reviewer_storage: ClReviewerStorage { base: mock.clone() },
8386
merge_queue_storage: MergeQueueStorage::new(mock.clone()),
8487
buck_storage: BuckStorage { base: mock.clone() },
88+
dynamic_sidebar_storage: DynamicSidebarStorage { base: mock.clone() },
8589
})
8690
}
8791
}
@@ -117,6 +121,7 @@ impl Storage {
117121
let reviewer_storage = ClReviewerStorage { base: base.clone() };
118122
let merge_queue_storage = MergeQueueStorage::new(base.clone());
119123
let buck_storage = BuckStorage { base: base.clone() };
124+
let dynamic_sidebar_storage = DynamicSidebarStorage { base: base.clone() };
120125

121126
let app_service = AppService {
122127
mono_storage: mono_storage.clone(),
@@ -136,6 +141,7 @@ impl Storage {
136141
reviewer_storage,
137142
merge_queue_storage: merge_queue_storage.clone(),
138143
buck_storage,
144+
dynamic_sidebar_storage,
139145
};
140146
let merge_queue_service = MergeQueueService::new(base.clone());
141147

@@ -220,6 +226,10 @@ impl Storage {
220226
self.app_service.buck_storage.clone()
221227
}
222228

229+
pub fn dynamic_sidebar_storage(&self) -> DynamicSidebarStorage {
230+
self.app_service.dynamic_sidebar_storage.clone()
231+
}
232+
223233
pub fn mock() -> Self {
224234
// During test time, we don't need a AppContext,
225235
// Put config in a leaked static variable thus the weak reference will always be valid.

0 commit comments

Comments
 (0)