feat: 0.2.27 - 抽离数据库基础设施模块

- 新增 ias-db crate,集中管理 PostgreSQL 连接池和 SQLx 迁移
- 迁移 ias-service/migrations 到 ias-db/migrations
- ias-main 改用 ias_db::connect_db(),ias-service 只接 PgPool
- 新增操作日志 HTTP 查询接口 (GET /v1/logs, GET /v1/logs/{id})
- 新增操作日志 Web 前端页面,支持按级别/模块/关键词筛选
- 消息队列关键节点增加操作日志写入
- ias-mail 新增 mark_remote_message_seen,IMAP 已读同步
- 工作流配置加载改为文件级容错 (load_configs_with_warnings)
- ias-context 工具调用说明更新为 query_api_docs + call_http_api
- 新增高德地图地理编码 HTTP 能力配置

模块版本: ias-db 0.1.0, ias-ai 0.1.14, ias-context 0.1.6, ias-http 0.1.15, ias-mail 0.1.16, ias-service 0.1.25, ias-main 0.2.27
This commit is contained in:
2026-07-08 17:44:21 +08:00
parent 7d5140c8d2
commit 7deae286ca
83 changed files with 1440 additions and 107 deletions
+34 -4
View File
@@ -5,7 +5,9 @@ use axum::{Extension, Json, Router};
use serde_json::{Value, json};
use sqlx::PgPool;
use crate::model::MailListQuery;
use crate::config::MailPollerConfig;
use crate::model::{MailListQuery, MailMessage};
use crate::poller::mark_remote_message_seen;
use crate::repository::MailRepository;
type ApiError = (StatusCode, Json<Value>);
@@ -60,12 +62,11 @@ async fn get_message(
.map_err(api_internal_error)?
.ok_or_else(|| api_not_found("邮件记录不存在"))?;
// 标记已读
if !message.seen {
match MailRepository::mark_as_seen(&state.db, id).await {
match mark_message_seen(&state.db, &message).await {
Ok(true) => message.seen = true,
Ok(false) => {}
Err(error) => eprintln!("标记邮件已读失败 id={id}: {error}"),
Err(error) => eprintln!("同步邮件已读失败 id={id}: {error:#}"),
}
}
@@ -77,6 +78,35 @@ async fn get_message(
// ── 辅助函数 ─────────────────────────────────────────────
async fn mark_message_seen(pool: &PgPool, message: &MailMessage) -> anyhow::Result<bool> {
let mut config = mail_config_for_message(pool, message)
.await?
.ok_or_else(|| anyhow::anyhow!("没有可用于同步已读状态的邮件账户配置"))?;
config.mailbox = message.mailbox.clone();
mark_remote_message_seen(&config, &message.uid).await?;
MailRepository::mark_as_seen(pool, message.id).await
}
async fn mail_config_for_message(
pool: &PgPool,
message: &MailMessage,
) -> anyhow::Result<Option<MailPollerConfig>> {
if let Some(account_id) = message.mail_account_id {
let account = MailRepository::find_account(pool, account_id)
.await?
.ok_or_else(|| anyhow::anyhow!("邮件账户不存在: {account_id}"))?;
return Ok(Some(MailPollerConfig::from_account(account)));
}
let Some(config) = MailPollerConfig::from_env()? else {
return Ok(None);
};
if config.source_key == message.source_key {
return Ok(Some(config));
}
Ok(None)
}
fn api_not_found(message: impl Into<String>) -> ApiError {
(
StatusCode::NOT_FOUND,