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

Commit dd79f1f

Browse files
authored
feat: Bot Installation (#2019)
* feat: add bot install router * fix: reslove cr problems
1 parent 0878b9b commit dd79f1f

10 files changed

Lines changed: 375 additions & 7 deletions

File tree

‎ceres/src/model/bots.rs‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
use callisto::{
2+
bot_installations,
3+
sea_orm_active_enums::{InstallationBotStatusEnum, InstallationTargetTypeEnum},
4+
};
5+
use serde::{Deserialize, Serialize};
6+
use utoipa::ToSchema;
7+
8+
#[derive(Serialize, Deserialize, ToSchema)]
9+
pub struct BotRes {
10+
pub id: i64,
11+
pub bot_id: i64,
12+
pub target_type: InstallationTargetType,
13+
pub target_id: i64,
14+
pub status: InstallationBotStatus,
15+
pub installed_by: i64,
16+
}
17+
18+
#[derive(Serialize, Deserialize, ToSchema)]
19+
pub struct InstallBotReq {
20+
pub target_type: InstallationTargetType,
21+
pub target_id: i64,
22+
pub installed_by: i64,
23+
}
24+
25+
#[derive(Serialize, Deserialize, ToSchema)]
26+
pub struct ChangeInstallationStatus {
27+
pub target_type: InstallationTargetType,
28+
pub status: InstallationBotStatus,
29+
}
30+
31+
#[derive(Serialize, Deserialize, ToSchema)]
32+
pub enum InstallationBotStatus {
33+
Disabled,
34+
Enabled,
35+
}
36+
37+
#[derive(Serialize, Deserialize, ToSchema)]
38+
pub enum InstallationTargetType {
39+
Organization,
40+
Repository,
41+
}
42+
43+
impl From<bot_installations::Model> for BotRes {
44+
fn from(value: bot_installations::Model) -> Self {
45+
Self {
46+
id: value.id,
47+
bot_id: value.bot_id,
48+
target_type: value.target_type.into(),
49+
target_id: value.target_id,
50+
status: value.status.into(),
51+
installed_by: value.installed_by,
52+
}
53+
}
54+
}
55+
56+
impl From<InstallationBotStatusEnum> for InstallationBotStatus {
57+
fn from(value: InstallationBotStatusEnum) -> Self {
58+
match value {
59+
InstallationBotStatusEnum::Disabled => InstallationBotStatus::Disabled,
60+
InstallationBotStatusEnum::Enabled => InstallationBotStatus::Enabled,
61+
}
62+
}
63+
}
64+
65+
impl From<InstallationTargetTypeEnum> for InstallationTargetType {
66+
fn from(value: InstallationTargetTypeEnum) -> Self {
67+
match value {
68+
InstallationTargetTypeEnum::Organization => InstallationTargetType::Organization,
69+
InstallationTargetTypeEnum::Repository => InstallationTargetType::Repository,
70+
}
71+
}
72+
}
73+
74+
impl From<InstallationBotStatus> for InstallationBotStatusEnum {
75+
fn from(value: InstallationBotStatus) -> Self {
76+
match value {
77+
InstallationBotStatus::Disabled => InstallationBotStatusEnum::Disabled,
78+
InstallationBotStatus::Enabled => InstallationBotStatusEnum::Enabled,
79+
}
80+
}
81+
}
82+
83+
impl From<InstallationTargetType> for InstallationTargetTypeEnum {
84+
fn from(value: InstallationTargetType) -> Self {
85+
match value {
86+
InstallationTargetType::Organization => InstallationTargetTypeEnum::Organization,
87+
InstallationTargetType::Repository => InstallationTargetTypeEnum::Repository,
88+
}
89+
}
90+
}

‎ceres/src/model/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod blame;
2+
pub mod bots;
23
pub mod buck;
34
pub mod change_list;
45
pub mod cl;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use chrono::Utc;
2+
3+
use crate::{
4+
bot_installations,
5+
entity_ext::generate_id,
6+
sea_orm_active_enums::{InstallationBotStatusEnum, InstallationTargetTypeEnum},
7+
};
8+
9+
impl bot_installations::Model {
10+
pub fn new(
11+
bot_id: i64,
12+
target_type: InstallationTargetTypeEnum,
13+
target_id: i64,
14+
status: InstallationBotStatusEnum,
15+
installed_by: i64,
16+
) -> Self {
17+
let now = Utc::now().into();
18+
19+
Self {
20+
id: generate_id(),
21+
bot_id,
22+
target_type,
23+
target_id,
24+
status,
25+
installed_by,
26+
installed_at: now,
27+
}
28+
}
29+
}

‎jupiter/callisto/src/entity_ext/mod.rs‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod bot_installations;
12
pub mod bot_keys;
23
pub mod bots;
34
pub mod buck_session;

‎jupiter/src/storage/bots_storage.rs‎

Lines changed: 102 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,20 @@
11
use std::ops::Deref;
22

33
use callisto::{
4-
bot_keys, bots,
5-
sea_orm_active_enums::{BotStatusEnum, PermissionScopeEnum},
4+
bot_installations, bot_keys, bots,
5+
sea_orm_active_enums::{
6+
BotStatusEnum, InstallationBotStatusEnum, InstallationTargetTypeEnum, PermissionScopeEnum,
7+
},
68
};
79
use common::errors::MegaError;
810
use rsa::{
911
RsaPrivateKey,
1012
pkcs8::{EncodePrivateKey, EncodePublicKey},
1113
rand_core::OsRng,
1214
};
13-
use sea_orm::{ActiveModelTrait, IntoActiveModel};
15+
use sea_orm::{
16+
ActiveModelTrait, ActiveValue::Set, ColumnTrait, EntityTrait, IntoActiveModel, QueryFilter,
17+
};
1418

1519
use crate::storage::base_storage::{BaseStorage, StorageConnector};
1620

@@ -73,6 +77,101 @@ impl BotsStorage {
7377
Ok((bot, private_pem))
7478
}
7579

80+
pub async fn get_installed_bot_by_id(
81+
&self,
82+
bot_id: i64,
83+
) -> Result<Vec<bot_installations::Model>, MegaError> {
84+
let installations = bot_installations::Entity::find()
85+
.filter(bot_installations::Column::BotId.eq(bot_id))
86+
.all(self.get_connection())
87+
.await?;
88+
89+
Ok(installations)
90+
}
91+
92+
/// Install a bot to a target (organization or repository)
93+
pub async fn install_bot(
94+
&self,
95+
bot_id: i64,
96+
target_type: InstallationTargetTypeEnum,
97+
target_id: i64,
98+
installed_by: i64,
99+
) -> Result<bot_installations::Model, MegaError> {
100+
// Check if already installed
101+
if (bot_installations::Entity::find()
102+
.filter(bot_installations::Column::BotId.eq(bot_id))
103+
.filter(bot_installations::Column::TargetType.eq(target_type.clone()))
104+
.filter(bot_installations::Column::TargetId.eq(target_id))
105+
.one(self.get_connection())
106+
.await?)
107+
.is_some()
108+
{
109+
return Err(MegaError::Other(
110+
"Bot already installed on this target".into(),
111+
));
112+
}
113+
114+
// Insert installation record
115+
let model = bot_installations::Model::new(
116+
bot_id,
117+
target_type,
118+
target_id,
119+
InstallationBotStatusEnum::Enabled,
120+
installed_by,
121+
);
122+
123+
let active_model = model.into_active_model();
124+
125+
let res = active_model.insert(self.get_connection()).await?;
126+
Ok(res)
127+
}
128+
129+
/// Uninstall a bot from a target
130+
pub async fn uninstall_bot(
131+
&self,
132+
bot_id: i64,
133+
target_type: InstallationTargetTypeEnum,
134+
target_id: i64,
135+
) -> Result<(), MegaError> {
136+
let installation = bot_installations::Entity::find()
137+
.filter(bot_installations::Column::BotId.eq(bot_id))
138+
.filter(bot_installations::Column::TargetType.eq(target_type.clone()))
139+
.filter(bot_installations::Column::TargetId.eq(target_id))
140+
.one(self.get_connection())
141+
.await?
142+
.ok_or_else(|| MegaError::Other("Bot installation not found".into()))?
143+
.into_active_model();
144+
145+
installation
146+
.into_active_model()
147+
.delete(self.get_connection())
148+
.await?;
149+
150+
Ok(())
151+
}
152+
153+
/// Change the status of an installed bot (Enabled / Disabled)
154+
pub async fn change_installed_bot_status(
155+
&self,
156+
bot_id: i64,
157+
target_type: InstallationTargetTypeEnum,
158+
target_id: i64,
159+
status: InstallationBotStatusEnum,
160+
) -> Result<bot_installations::Model, MegaError> {
161+
let mut installation = bot_installations::Entity::find()
162+
.filter(bot_installations::Column::BotId.eq(bot_id))
163+
.filter(bot_installations::Column::TargetType.eq(target_type.clone()))
164+
.filter(bot_installations::Column::TargetId.eq(target_id))
165+
.one(self.get_connection())
166+
.await?
167+
.ok_or_else(|| MegaError::Other("Bot installation not found".into()))?
168+
.into_active_model();
169+
170+
installation.status = Set(status);
171+
let res = installation.update(self.get_connection()).await?;
172+
Ok(res)
173+
}
174+
76175
pub async fn new_bot_model(
77176
&self,
78177
name: &str,

‎jupiter/src/storage/mod.rs‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,10 @@ impl Storage {
432432
self.app_service.webhook_storage.clone()
433433
}
434434

435+
pub fn bots_storage(&self) -> BotsStorage {
436+
self.app_service.bots_storage.clone()
437+
}
438+
435439
pub fn mock() -> Self {
436440
// During test time, we don't need a AppContext,
437441
// Put config in a leaked static variable thus the weak reference will always be valid.

‎mono/src/api/api_router.rs‎

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,10 @@ use crate::{
1616
error::ApiError,
1717
notes::note_router,
1818
router::{
19-
admin_router, buck_router, build_trigger_router, cl_router, code_review_router,
20-
commit_router, conv_router, dynamic_sidebar_router, gpg_router, group_router,
21-
issue_router, label_router, merge_queue_router, permission_router, preview_router,
22-
repo_router, reviewer_router, tag_router, user_router, webhook_router,
19+
admin_router, bot_router, buck_router, build_trigger_router, cl_router,
20+
code_review_router, commit_router, conv_router, dynamic_sidebar_router, gpg_router,
21+
group_router, issue_router, label_router, merge_queue_router, permission_router,
22+
preview_router, repo_router, reviewer_router, tag_router, user_router, webhook_router,
2323
},
2424
},
2525
server::http_server::SYSTEM_COMMON,
@@ -51,6 +51,7 @@ pub fn routers() -> OpenApiRouter<MonoApiServiceState> {
5151
.merge(code_review_router::routers())
5252
.merge(build_trigger_router::routers())
5353
.merge(webhook_router::routers())
54+
.merge(bot_router::routers())
5455
}
5556

5657
/// Health Check

0 commit comments

Comments
 (0)