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

Commit 3c7dae0

Browse files
authored
Feat/dynamic sidebar api refactor (#1707)
* feat: add sync_sidebar router * feat: change ID default to NoSet * chore: address code review feedback
1 parent 842fb26 commit 3c7dae0

5 files changed

Lines changed: 126 additions & 4 deletions

File tree

‎ceres/src/model/dynamic_sidebar.rs‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,26 @@ pub struct UpdateSidebarPayload {
4444
pub visible: Option<bool>,
4545
pub order_index: Option<i32>,
4646
}
47+
48+
#[derive(Clone, Debug, ToSchema, Serialize, Deserialize)]
49+
pub struct SidebarSyncPayload {
50+
pub id: Option<i32>,
51+
pub public_id: String,
52+
pub label: String,
53+
pub href: String,
54+
pub visible: bool,
55+
pub order_index: i32,
56+
}
57+
58+
impl From<SidebarSyncPayload> for jupiter::model::sidebar_dto::SidebarSyncDto {
59+
fn from(value: SidebarSyncPayload) -> Self {
60+
Self {
61+
id: value.id,
62+
public_id: value.public_id,
63+
label: value.label,
64+
href: value.href,
65+
visible: value.visible,
66+
order_index: value.order_index,
67+
}
68+
}
69+
}

‎jupiter/src/model/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ pub mod common;
33
pub mod conv_dto;
44
pub mod issue_dto;
55
pub mod merge_queue_dto;
6+
pub mod sidebar_dto;

‎jupiter/src/model/sidebar_dto.rs‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#[derive(Debug, Clone)]
2+
pub struct SidebarSyncDto {
3+
pub id: Option<i32>,
4+
pub public_id: String,
5+
pub label: String,
6+
pub href: String,
7+
pub visible: bool,
8+
pub order_index: i32,
9+
}

‎jupiter/src/storage/dynamic_sidebar_storage.rs‎

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,15 @@
11
use std::ops::Deref;
22

3-
use crate::storage::base_storage::{BaseStorage, StorageConnector};
3+
use crate::{
4+
model::sidebar_dto::SidebarSyncDto,
5+
storage::base_storage::{BaseStorage, StorageConnector},
6+
};
47
use callisto::dynamic_sidebar;
58
use common::errors::MegaError;
69
use sea_orm::{
710
ActiveModelTrait,
811
ActiveValue::{NotSet, Set},
9-
EntityTrait, QueryOrder,
12+
EntityTrait, QueryOrder, TransactionTrait,
1013
};
1114

1215
#[derive(Clone)]
@@ -118,4 +121,57 @@ impl DynamicSidebarStorage {
118121

119122
Ok(model)
120123
}
124+
125+
pub async fn sync_sidebar(
126+
&self,
127+
items: Vec<SidebarSyncDto>,
128+
) -> Result<Vec<dynamic_sidebar::Model>, MegaError> {
129+
if items.is_empty() {
130+
return Ok(Vec::new());
131+
}
132+
133+
// Begin a transaction
134+
let txn = self.get_connection().begin().await?;
135+
136+
let mut res_models = Vec::with_capacity(items.len());
137+
138+
for item in items {
139+
if let Some(id) = item.id {
140+
// Update existing menu item
141+
if let Some(model) = dynamic_sidebar::Entity::find_by_id(id).one(&txn).await? {
142+
let mut active_model: dynamic_sidebar::ActiveModel = model.into();
143+
144+
active_model.public_id = Set(item.public_id);
145+
active_model.label = Set(item.label);
146+
active_model.href = Set(item.href);
147+
active_model.visible = Set(item.visible);
148+
active_model.order_index = Set(item.order_index);
149+
150+
let updated = active_model.update(&txn).await?;
151+
res_models.push(updated);
152+
} else {
153+
return Err(MegaError::Other(format!(
154+
"Sidebar with id `{id}` not found"
155+
)));
156+
}
157+
} else {
158+
// Insert new menu item
159+
let active_model = dynamic_sidebar::ActiveModel {
160+
id: NotSet,
161+
public_id: Set(item.public_id),
162+
label: Set(item.label),
163+
href: Set(item.href),
164+
visible: Set(item.visible),
165+
order_index: Set(item.order_index),
166+
};
167+
let inserted = active_model.insert(&txn).await?;
168+
res_models.push(inserted);
169+
}
170+
}
171+
172+
// Commit the transaction
173+
txn.commit().await?;
174+
175+
Ok(res_models)
176+
}
121177
}

‎mono/src/api/router/dynamic_sidebar_router.rs‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ use axum::{
33
extract::{Path, State},
44
};
55
use ceres::model::dynamic_sidebar::{
6-
CreateSidebarPayload, SidebarMenuListRes, SidebarRes, UpdateSidebarPayload,
6+
CreateSidebarPayload, SidebarMenuListRes, SidebarRes, SidebarSyncPayload, UpdateSidebarPayload,
77
};
88
use common::model::CommonResult;
99
use utoipa_axum::{router::OpenApiRouter, routes};
@@ -20,7 +20,8 @@ pub fn routers() -> OpenApiRouter<MonoApiServiceState> {
2020
.routes(routes!(sidebar_menu_list))
2121
.routes(routes!(new_sidebar))
2222
.routes(routes!(update_sidebar_by_id))
23-
.routes(routes!(delete_sidebar_by_id)),
23+
.routes(routes!(delete_sidebar_by_id))
24+
.routes(routes!(sync_sidebar)),
2425
)
2526
}
2627

@@ -106,6 +107,38 @@ async fn update_sidebar_by_id(
106107
Ok(Json(CommonResult::success(Some(res.into()))))
107108
}
108109

110+
/// Sync sidebar menus
111+
#[utoipa::path(
112+
post,
113+
path = "/sync",
114+
request_body = Vec<SidebarSyncPayload>,
115+
responses(
116+
(status = 200, body = CommonResult<Vec<SidebarRes>>, content_type = "application/json")
117+
),
118+
tag = SIDEBAR_TAG,
119+
description = "Sync sidebar menus. \
120+
Each `public_id` and `order_index` must be unique across all sidebar items. \
121+
The operation will fail if: \
122+
- A new item has a `public_id` that already exists \
123+
- An update tries to set a `public_id` to one that's already in use by another item \
124+
- Multiple items in the payload have the same `order_index` \
125+
- An update tries to set an `order_index` that's already in use \
126+
The transaction will be rolled back if any of these constraints are violated."
127+
)]
128+
async fn sync_sidebar(
129+
state: State<MonoApiServiceState>,
130+
Json(payloads): Json<Vec<SidebarSyncPayload>>,
131+
) -> Result<Json<CommonResult<Vec<SidebarRes>>>, ApiError> {
132+
let res = state
133+
.dynamic_sidebar_stg()
134+
.sync_sidebar(payloads.into_iter().map(|item| item.into()).collect())
135+
.await?;
136+
137+
Ok(Json(CommonResult::success(Some(
138+
res.into_iter().map(|item| item.into()).collect(),
139+
))))
140+
}
141+
109142
/// Delete sidebar menu
110143
#[utoipa::path(
111144
delete,

0 commit comments

Comments
 (0)