初步集成消息通道,支持Telegram + Lark机器人
This commit is contained in:
31
src-tauri/src/db/entities/chat_channel.rs
Normal file
31
src-tauri/src/db/entities/chat_channel.rs
Normal file
@@ -0,0 +1,31 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "chat_channel")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub channel_type: String,
|
||||
pub enabled: bool,
|
||||
pub config_json: String,
|
||||
pub event_filter_json: Option<String>,
|
||||
pub daily_report_enabled: bool,
|
||||
pub daily_report_time: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
pub updated_at: DateTimeUtc,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::chat_channel_message_log::Entity")]
|
||||
MessageLogs,
|
||||
}
|
||||
|
||||
impl Related<super::chat_channel_message_log::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::MessageLogs.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
33
src-tauri/src/db/entities/chat_channel_message_log.rs
Normal file
33
src-tauri/src/db/entities/chat_channel_message_log.rs
Normal file
@@ -0,0 +1,33 @@
|
||||
use sea_orm::entity::prelude::*;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
|
||||
#[sea_orm(table_name = "chat_channel_message_log")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub channel_id: i32,
|
||||
pub direction: String,
|
||||
pub message_type: String,
|
||||
pub content_preview: String,
|
||||
pub status: String,
|
||||
pub error_detail: Option<String>,
|
||||
pub created_at: DateTimeUtc,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::chat_channel::Entity",
|
||||
from = "Column::ChannelId",
|
||||
to = "super::chat_channel::Column::Id"
|
||||
)]
|
||||
ChatChannel,
|
||||
}
|
||||
|
||||
impl Related<super::chat_channel::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::ChatChannel.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod agent_setting;
|
||||
pub mod app_metadata;
|
||||
pub mod chat_channel;
|
||||
pub mod chat_channel_message_log;
|
||||
pub mod conversation;
|
||||
pub mod folder;
|
||||
pub mod folder_command;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
pub use super::agent_setting::Entity as AgentSetting;
|
||||
pub use super::app_metadata::Entity as AppMetadata;
|
||||
pub use super::chat_channel::Entity as ChatChannel;
|
||||
pub use super::chat_channel_message_log::Entity as ChatChannelMessageLog;
|
||||
pub use super::conversation::Entity as Conversation;
|
||||
pub use super::folder::Entity as Folder;
|
||||
pub use super::folder_command::Entity as FolderCommand;
|
||||
|
||||
179
src-tauri/src/db/migration/m20260330_000001_chat_channel.rs
Normal file
179
src-tauri/src/db/migration/m20260330_000001_chat_channel.rs
Normal file
@@ -0,0 +1,179 @@
|
||||
use sea_orm_migration::prelude::*;
|
||||
|
||||
#[derive(DeriveMigrationName)]
|
||||
pub struct Migration;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
// ─── chat_channel ───
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ChatChannel::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ChatChannel::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(ChatChannel::Name).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(ChatChannel::ChannelType)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannel::Enabled)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(true),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannel::ConfigJson)
|
||||
.text()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(ChatChannel::EventFilterJson).text().null())
|
||||
.col(
|
||||
ColumnDef::new(ChatChannel::DailyReportEnabled)
|
||||
.boolean()
|
||||
.not_null()
|
||||
.default(false),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannel::DailyReportTime)
|
||||
.string()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannel::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannel::UpdatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
// ─── chat_channel_message_log ───
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(ChatChannelMessageLog::Table)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(ChatChannelMessageLog::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannelMessageLog::ChannelId)
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannelMessageLog::Direction)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannelMessageLog::MessageType)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannelMessageLog::ContentPreview)
|
||||
.text()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannelMessageLog::Status)
|
||||
.string()
|
||||
.not_null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannelMessageLog::ErrorDetail)
|
||||
.text()
|
||||
.null(),
|
||||
)
|
||||
.col(
|
||||
ColumnDef::new(ChatChannelMessageLog::CreatedAt)
|
||||
.timestamp_with_time_zone()
|
||||
.not_null(),
|
||||
)
|
||||
.foreign_key(
|
||||
ForeignKey::create()
|
||||
.name("fk_ccml_channel_id")
|
||||
.from(
|
||||
ChatChannelMessageLog::Table,
|
||||
ChatChannelMessageLog::ChannelId,
|
||||
)
|
||||
.to(ChatChannel::Table, ChatChannel::Id)
|
||||
.on_delete(ForeignKeyAction::Cascade),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
manager
|
||||
.create_index(
|
||||
Index::create()
|
||||
.name("idx_ccml_channel_created")
|
||||
.table(ChatChannelMessageLog::Table)
|
||||
.col(ChatChannelMessageLog::ChannelId)
|
||||
.col(ChatChannelMessageLog::CreatedAt)
|
||||
.to_owned(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(ChatChannelMessageLog::Table)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(ChatChannel::Table).to_owned())
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ChatChannel {
|
||||
Table,
|
||||
Id,
|
||||
Name,
|
||||
ChannelType,
|
||||
Enabled,
|
||||
ConfigJson,
|
||||
EventFilterJson,
|
||||
DailyReportEnabled,
|
||||
DailyReportTime,
|
||||
CreatedAt,
|
||||
UpdatedAt,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum ChatChannelMessageLog {
|
||||
Table,
|
||||
Id,
|
||||
ChannelId,
|
||||
Direction,
|
||||
MessageType,
|
||||
ContentPreview,
|
||||
Status,
|
||||
ErrorDetail,
|
||||
CreatedAt,
|
||||
}
|
||||
@@ -5,6 +5,7 @@ mod m20260219_000001_folder_command;
|
||||
mod m20260221_000001_folder_is_open;
|
||||
mod m20260226_000001_agent_setting;
|
||||
mod m20260227_000001_folder_parent_branch;
|
||||
mod m20260330_000001_chat_channel;
|
||||
pub struct Migrator;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -16,6 +17,7 @@ impl MigratorTrait for Migrator {
|
||||
Box::new(m20260221_000001_folder_is_open::Migration),
|
||||
Box::new(m20260226_000001_agent_setting::Migration),
|
||||
Box::new(m20260227_000001_folder_parent_branch::Migration),
|
||||
Box::new(m20260330_000001_chat_channel::Migration),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
70
src-tauri/src/db/service/chat_channel_message_log_service.rs
Normal file
70
src-tauri/src/db/service/chat_channel_message_log_service.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use chrono::Utc;
|
||||
use sea_orm::prelude::DateTimeUtc;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, DatabaseConnection, EntityTrait,
|
||||
QueryFilter, QueryOrder, Set,
|
||||
};
|
||||
|
||||
use crate::db::entities::chat_channel_message_log;
|
||||
use crate::db::error::DbError;
|
||||
|
||||
pub async fn create_log(
|
||||
conn: &DatabaseConnection,
|
||||
channel_id: i32,
|
||||
direction: &str,
|
||||
message_type: &str,
|
||||
content_preview: &str,
|
||||
status: &str,
|
||||
error_detail: Option<String>,
|
||||
) -> Result<(), DbError> {
|
||||
let active = chat_channel_message_log::ActiveModel {
|
||||
id: NotSet,
|
||||
channel_id: Set(channel_id),
|
||||
direction: Set(direction.to_string()),
|
||||
message_type: Set(message_type.to_string()),
|
||||
content_preview: Set(truncate_preview(content_preview)),
|
||||
status: Set(status.to_string()),
|
||||
error_detail: Set(error_detail),
|
||||
created_at: Set(Utc::now()),
|
||||
};
|
||||
active.insert(conn).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn list_by_channel(
|
||||
conn: &DatabaseConnection,
|
||||
channel_id: i32,
|
||||
limit: u64,
|
||||
offset: u64,
|
||||
) -> Result<Vec<chat_channel_message_log::Model>, DbError> {
|
||||
use sea_orm::PaginatorTrait;
|
||||
Ok(chat_channel_message_log::Entity::find()
|
||||
.filter(chat_channel_message_log::Column::ChannelId.eq(channel_id))
|
||||
.order_by_desc(chat_channel_message_log::Column::CreatedAt)
|
||||
.paginate(conn, limit)
|
||||
.fetch_page(offset / limit)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn cleanup_old_logs(
|
||||
conn: &DatabaseConnection,
|
||||
older_than: DateTimeUtc,
|
||||
) -> Result<u64, DbError> {
|
||||
let result = chat_channel_message_log::Entity::delete_many()
|
||||
.filter(chat_channel_message_log::Column::CreatedAt.lt(older_than))
|
||||
.exec(conn)
|
||||
.await?;
|
||||
Ok(result.rows_affected)
|
||||
}
|
||||
|
||||
fn truncate_preview(s: &str) -> String {
|
||||
if s.len() <= 200 {
|
||||
s.to_string()
|
||||
} else {
|
||||
let mut end = 200;
|
||||
while !s.is_char_boundary(end) && end > 0 {
|
||||
end -= 1;
|
||||
}
|
||||
format!("{}...", &s[..end])
|
||||
}
|
||||
}
|
||||
98
src-tauri/src/db/service/chat_channel_service.rs
Normal file
98
src-tauri/src/db/service/chat_channel_service.rs
Normal file
@@ -0,0 +1,98 @@
|
||||
use chrono::Utc;
|
||||
use sea_orm::{
|
||||
ActiveModelTrait, ActiveValue::NotSet, ColumnTrait, DatabaseConnection, EntityTrait,
|
||||
IntoActiveModel, QueryFilter, QueryOrder, Set,
|
||||
};
|
||||
|
||||
use crate::db::entities::chat_channel;
|
||||
use crate::db::error::DbError;
|
||||
|
||||
pub async fn create(
|
||||
conn: &DatabaseConnection,
|
||||
name: String,
|
||||
channel_type: String,
|
||||
config_json: String,
|
||||
enabled: bool,
|
||||
daily_report_enabled: bool,
|
||||
daily_report_time: Option<String>,
|
||||
) -> Result<chat_channel::Model, DbError> {
|
||||
let now = Utc::now();
|
||||
let active = chat_channel::ActiveModel {
|
||||
id: NotSet,
|
||||
name: Set(name),
|
||||
channel_type: Set(channel_type),
|
||||
enabled: Set(enabled),
|
||||
config_json: Set(config_json),
|
||||
event_filter_json: Set(None),
|
||||
daily_report_enabled: Set(daily_report_enabled),
|
||||
daily_report_time: Set(daily_report_time),
|
||||
created_at: Set(now),
|
||||
updated_at: Set(now),
|
||||
};
|
||||
Ok(active.insert(conn).await?)
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
conn: &DatabaseConnection,
|
||||
id: i32,
|
||||
name: Option<String>,
|
||||
enabled: Option<bool>,
|
||||
config_json: Option<String>,
|
||||
event_filter_json: Option<Option<String>>,
|
||||
daily_report_enabled: Option<bool>,
|
||||
daily_report_time: Option<Option<String>>,
|
||||
) -> Result<chat_channel::Model, DbError> {
|
||||
let model = chat_channel::Entity::find_by_id(id)
|
||||
.one(conn)
|
||||
.await?
|
||||
.ok_or_else(|| DbError::Migration(format!("chat channel not found: {id}")))?;
|
||||
|
||||
let mut active = model.into_active_model();
|
||||
if let Some(v) = name {
|
||||
active.name = Set(v);
|
||||
}
|
||||
if let Some(v) = enabled {
|
||||
active.enabled = Set(v);
|
||||
}
|
||||
if let Some(v) = config_json {
|
||||
active.config_json = Set(v);
|
||||
}
|
||||
if let Some(v) = event_filter_json {
|
||||
active.event_filter_json = Set(v);
|
||||
}
|
||||
if let Some(v) = daily_report_enabled {
|
||||
active.daily_report_enabled = Set(v);
|
||||
}
|
||||
if let Some(v) = daily_report_time {
|
||||
active.daily_report_time = Set(v);
|
||||
}
|
||||
active.updated_at = Set(Utc::now());
|
||||
Ok(active.update(conn).await?)
|
||||
}
|
||||
|
||||
pub async fn delete(conn: &DatabaseConnection, id: i32) -> Result<(), DbError> {
|
||||
chat_channel::Entity::delete_by_id(id).exec(conn).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn get_by_id(
|
||||
conn: &DatabaseConnection,
|
||||
id: i32,
|
||||
) -> Result<Option<chat_channel::Model>, DbError> {
|
||||
Ok(chat_channel::Entity::find_by_id(id).one(conn).await?)
|
||||
}
|
||||
|
||||
pub async fn list_all(conn: &DatabaseConnection) -> Result<Vec<chat_channel::Model>, DbError> {
|
||||
Ok(chat_channel::Entity::find()
|
||||
.order_by_asc(chat_channel::Column::Id)
|
||||
.all(conn)
|
||||
.await?)
|
||||
}
|
||||
|
||||
pub async fn list_enabled(conn: &DatabaseConnection) -> Result<Vec<chat_channel::Model>, DbError> {
|
||||
Ok(chat_channel::Entity::find()
|
||||
.filter(chat_channel::Column::Enabled.eq(true))
|
||||
.order_by_asc(chat_channel::Column::Id)
|
||||
.all(conn)
|
||||
.await?)
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
pub mod agent_setting_service;
|
||||
pub mod app_metadata_service;
|
||||
pub mod chat_channel_message_log_service;
|
||||
pub mod chat_channel_service;
|
||||
pub mod conversation_service;
|
||||
pub mod folder_command_service;
|
||||
pub mod folder_service;
|
||||
|
||||
Reference in New Issue
Block a user