Some checks failed
BotServer CI / build (push) Failing after 1m34s
Split 20+ files over 1000 lines into focused subdirectories for better maintainability and code organization. All changes maintain backward compatibility through re-export wrappers. Major splits: - attendance/llm_assist.rs (2074→7 modules) - basic/keywords/face_api.rs → face_api/ (7 modules) - basic/keywords/file_operations.rs → file_ops/ (8 modules) - basic/keywords/hear_talk.rs → hearing/ (6 modules) - channels/wechat.rs → wechat/ (10 modules) - channels/youtube.rs → youtube/ (5 modules) - contacts/mod.rs → contacts_api/ (6 modules) - core/bootstrap/mod.rs → bootstrap/ (5 modules) - core/shared/admin.rs → admin_*.rs (5 modules) - designer/canvas.rs → canvas_api/ (6 modules) - designer/mod.rs → designer_api/ (6 modules) - docs/handlers.rs → handlers_api/ (11 modules) - drive/mod.rs → drive_handlers.rs, drive_types.rs - learn/mod.rs → types.rs - main.rs → main_module/ (7 modules) - meet/webinar.rs → webinar_api/ (8 modules) - paper/mod.rs → (10 modules) - security/auth.rs → auth_api/ (7 modules) - security/passkey.rs → (4 modules) - sources/mod.rs → sources_api/ (5 modules) - tasks/mod.rs → task_api/ (5 modules) Stats: 38,040 deletions, 1,315 additions across 318 files Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
389 lines
12 KiB
Rust
389 lines
12 KiB
Rust
use crate::core::shared::state::AppState;
|
|
use crate::core::middleware::AuthenticatedUser;
|
|
use super::types::*;
|
|
use axum::{
|
|
extract::{Path, State},
|
|
http::StatusCode,
|
|
response::IntoResponse,
|
|
Json,
|
|
};
|
|
use diesel::prelude::*;
|
|
use diesel::sql_types::{Bool, Text, Varchar};
|
|
use diesel::sql_types::Uuid as DieselUuid;
|
|
use log::warn;
|
|
use std::sync::Arc;
|
|
use uuid::Uuid;
|
|
|
|
fn strip_html_tags(html: &str) -> String {
|
|
let text = html
|
|
.replace(" ", " ")
|
|
.replace("&", "&")
|
|
.replace("<", "<")
|
|
.replace(">", ">")
|
|
.replace(""", "\"")
|
|
.replace("'", "'");
|
|
|
|
let text = text
|
|
.replace("<br>", "\n")
|
|
.replace("<br/>", "\n")
|
|
.replace("<br />", "\n")
|
|
.replace("</p>", "\n")
|
|
.replace("</div>", "\n")
|
|
.replace("</li>", "\n");
|
|
|
|
let mut result = String::with_capacity(text.len());
|
|
let mut in_tag = false;
|
|
|
|
for c in text.chars() {
|
|
match c {
|
|
'<' => in_tag = true,
|
|
'>' => in_tag = false,
|
|
_ if !in_tag => result.push(c),
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
let mut cleaned = String::new();
|
|
let mut prev_newline = false;
|
|
for c in result.chars() {
|
|
if c == '\n' {
|
|
if !prev_newline {
|
|
cleaned.push(c);
|
|
}
|
|
prev_newline = true;
|
|
} else {
|
|
cleaned.push(c);
|
|
prev_newline = false;
|
|
}
|
|
}
|
|
|
|
cleaned.trim().to_string()
|
|
}
|
|
|
|
pub async fn list_signatures(
|
|
State(state): State<Arc<AppState>>,
|
|
user: AuthenticatedUser,
|
|
) -> impl IntoResponse {
|
|
let mut conn = match state.conn.get() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
return Json(serde_json::json!({
|
|
"error": format!("Database connection error: {}", e),
|
|
"signatures": []
|
|
}));
|
|
}
|
|
};
|
|
|
|
let user_id = user.user_id;
|
|
let result: Result<Vec<EmailSignatureRow>, _> = diesel::sql_query(
|
|
"SELECT id, user_id, bot_id, name, content_html, content_plain, is_default, is_active, created_at, updated_at
|
|
FROM email_signatures
|
|
WHERE user_id = $1 AND is_active = true
|
|
ORDER BY is_default DESC, name ASC"
|
|
)
|
|
.bind::<DieselUuid, _>(user_id)
|
|
.load(&mut conn);
|
|
|
|
match result {
|
|
Ok(signatures) => Json(serde_json::json!({
|
|
"signatures": signatures
|
|
})),
|
|
Err(e) => {
|
|
warn!("Failed to list signatures: {}", e);
|
|
Json(serde_json::json!({
|
|
"signatures": [{
|
|
"id": "default",
|
|
"name": "Default Signature",
|
|
"content_html": "<p>Best regards,<br>The Team</p>",
|
|
"content_plain": "Best regards,\nThe Team",
|
|
"is_default": true
|
|
}]
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn get_default_signature(
|
|
State(state): State<Arc<AppState>>,
|
|
user: AuthenticatedUser,
|
|
) -> impl IntoResponse {
|
|
let mut conn = match state.conn.get() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
return Json(serde_json::json!({
|
|
"id": "default",
|
|
"name": "Default Signature",
|
|
"content_html": "<p>Best regards,<br>The Team</p>",
|
|
"content_plain": "Best regards,\nThe Team",
|
|
"is_default": true,
|
|
"_error": format!("Database connection error: {}", e)
|
|
}));
|
|
}
|
|
};
|
|
|
|
let user_id = user.user_id;
|
|
let result: Result<EmailSignatureRow, _> = diesel::sql_query(
|
|
"SELECT id, user_id, bot_id, name, content_html, content_plain, is_default, is_active, created_at, updated_at
|
|
FROM email_signatures
|
|
WHERE user_id = $1 AND is_default = true AND is_active = true
|
|
LIMIT 1"
|
|
)
|
|
.bind::<DieselUuid, _>(user_id)
|
|
.get_result(&mut conn);
|
|
|
|
match result {
|
|
Ok(signature) => Json(serde_json::json!({
|
|
"id": signature.id,
|
|
"name": signature.name,
|
|
"content_html": signature.content_html,
|
|
"content_plain": signature.content_plain,
|
|
"is_default": signature.is_default
|
|
})),
|
|
Err(_) => {
|
|
Json(serde_json::json!({
|
|
"id": "default",
|
|
"name": "Default Signature",
|
|
"content_html": "<p>Best regards,<br>The Team</p>",
|
|
"content_plain": "Best regards,\nThe Team",
|
|
"is_default": true
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn get_signature(
|
|
State(state): State<Arc<AppState>>,
|
|
Path(id): Path<String>,
|
|
user: AuthenticatedUser,
|
|
) -> impl IntoResponse {
|
|
let signature_id = match Uuid::parse_str(&id) {
|
|
Ok(id) => id,
|
|
Err(_) => {
|
|
return (StatusCode::BAD_REQUEST, Json(serde_json::json!({
|
|
"error": "Invalid signature ID"
|
|
}))).into_response();
|
|
}
|
|
};
|
|
|
|
let mut conn = match state.conn.get() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
|
|
"error": format!("Database connection error: {}", e)
|
|
}))).into_response();
|
|
}
|
|
};
|
|
|
|
let user_id = user.user_id;
|
|
let result: Result<EmailSignatureRow, _> = diesel::sql_query(
|
|
"SELECT id, user_id, bot_id, name, content_html, content_plain, is_default, is_active, created_at, updated_at
|
|
FROM email_signatures
|
|
WHERE id = $1 AND user_id = $2"
|
|
)
|
|
.bind::<DieselUuid, _>(signature_id)
|
|
.bind::<DieselUuid, _>(user_id)
|
|
.get_result(&mut conn);
|
|
|
|
match result {
|
|
Ok(signature) => Json(serde_json::json!(signature)).into_response(),
|
|
Err(_) => (StatusCode::NOT_FOUND, Json(serde_json::json!({
|
|
"error": "Signature not found"
|
|
}))).into_response()
|
|
}
|
|
}
|
|
|
|
pub async fn create_signature(
|
|
State(state): State<Arc<AppState>>,
|
|
user: AuthenticatedUser,
|
|
Json(payload): Json<CreateSignatureRequest>,
|
|
) -> impl IntoResponse {
|
|
let mut conn = match state.conn.get() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": format!("Database connection error: {}", e)
|
|
}))).into_response();
|
|
}
|
|
};
|
|
|
|
let new_id = Uuid::new_v4();
|
|
let user_id = user.user_id;
|
|
let content_plain = payload.content_plain.unwrap_or_else(|| {
|
|
strip_html_tags(&payload.content_html)
|
|
});
|
|
|
|
if payload.is_default {
|
|
let _ = diesel::sql_query(
|
|
"UPDATE email_signatures SET is_default = false WHERE user_id = $1 AND is_default = true"
|
|
)
|
|
.bind::<DieselUuid, _>(user_id)
|
|
.execute(&mut conn);
|
|
}
|
|
|
|
let result = diesel::sql_query(
|
|
"INSERT INTO email_signatures (id, user_id, name, content_html, content_plain, is_default, is_active, created_at, updated_at)
|
|
VALUES ($1, $2, $3, $4, $5, $6, true, NOW(), NOW())
|
|
RETURNING id"
|
|
)
|
|
.bind::<DieselUuid, _>(new_id)
|
|
.bind::<DieselUuid, _>(user_id)
|
|
.bind::<Varchar, _>(&payload.name)
|
|
.bind::<Text, _>(&payload.content_html)
|
|
.bind::<Text, _>(&content_plain)
|
|
.bind::<Bool, _>(payload.is_default)
|
|
.execute(&mut conn);
|
|
|
|
match result {
|
|
Ok(_) => Json(serde_json::json!({
|
|
"success": true,
|
|
"id": new_id,
|
|
"name": payload.name
|
|
})).into_response(),
|
|
Err(e) => {
|
|
warn!("Failed to create signature: {}", e);
|
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": format!("Failed to create signature: {}", e)
|
|
}))).into_response()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn update_signature(
|
|
State(state): State<Arc<AppState>>,
|
|
Path(id): Path<String>,
|
|
user: AuthenticatedUser,
|
|
Json(payload): Json<UpdateSignatureRequest>,
|
|
) -> impl IntoResponse {
|
|
let signature_id = match Uuid::parse_str(&id) {
|
|
Ok(id) => id,
|
|
Err(_) => {
|
|
return (StatusCode::BAD_REQUEST, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": "Invalid signature ID"
|
|
}))).into_response();
|
|
}
|
|
};
|
|
|
|
let mut conn = match state.conn.get() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": format!("Database connection error: {}", e)
|
|
}))).into_response();
|
|
}
|
|
};
|
|
|
|
let user_id = user.user_id;
|
|
|
|
let mut updates = vec!["updated_at = NOW()".to_string()];
|
|
if payload.name.is_some() {
|
|
updates.push("name = $3".to_string());
|
|
}
|
|
if payload.content_html.is_some() {
|
|
updates.push("content_html = $4".to_string());
|
|
}
|
|
if payload.content_plain.is_some() {
|
|
updates.push("content_plain = $5".to_string());
|
|
}
|
|
if let Some(is_default) = payload.is_default {
|
|
if is_default {
|
|
let _ = diesel::sql_query(
|
|
"UPDATE email_signatures SET is_default = false WHERE user_id = $1 AND is_default = true AND id != $2"
|
|
)
|
|
.bind::<DieselUuid, _>(user_id)
|
|
.bind::<DieselUuid, _>(signature_id)
|
|
.execute(&mut conn);
|
|
}
|
|
updates.push("is_default = $6".to_string());
|
|
}
|
|
if payload.is_active.is_some() {
|
|
updates.push("is_active = $7".to_string());
|
|
}
|
|
|
|
let result = diesel::sql_query(format!(
|
|
"UPDATE email_signatures SET {} WHERE id = $1 AND user_id = $2",
|
|
updates.join(", ")
|
|
))
|
|
.bind::<DieselUuid, _>(signature_id)
|
|
.bind::<DieselUuid, _>(user_id)
|
|
.bind::<Varchar, _>(payload.name.unwrap_or_default())
|
|
.bind::<Text, _>(payload.content_html.unwrap_or_default())
|
|
.bind::<Text, _>(payload.content_plain.unwrap_or_default())
|
|
.bind::<Bool, _>(payload.is_default.unwrap_or(false))
|
|
.bind::<Bool, _>(payload.is_active.unwrap_or(true))
|
|
.execute(&mut conn);
|
|
|
|
match result {
|
|
Ok(rows) if rows > 0 => Json(serde_json::json!({
|
|
"success": true,
|
|
"id": id
|
|
})).into_response(),
|
|
Ok(_) => (StatusCode::NOT_FOUND, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": "Signature not found"
|
|
}))).into_response(),
|
|
Err(e) => {
|
|
warn!("Failed to update signature: {}", e);
|
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": format!("Failed to update signature: {}", e)
|
|
}))).into_response()
|
|
}
|
|
}
|
|
}
|
|
|
|
pub async fn delete_signature(
|
|
State(state): State<Arc<AppState>>,
|
|
Path(id): Path<String>,
|
|
user: AuthenticatedUser,
|
|
) -> impl IntoResponse {
|
|
let signature_id = match Uuid::parse_str(&id) {
|
|
Ok(id) => id,
|
|
Err(_) => {
|
|
return (StatusCode::BAD_REQUEST, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": "Invalid signature ID"
|
|
}))).into_response();
|
|
}
|
|
};
|
|
|
|
let mut conn = match state.conn.get() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
return (StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": format!("Database connection error: {}", e)
|
|
}))).into_response();
|
|
}
|
|
};
|
|
|
|
let user_id = user.user_id;
|
|
|
|
let result = diesel::sql_query(
|
|
"UPDATE email_signatures SET is_active = false, updated_at = NOW() WHERE id = $1 AND user_id = $2"
|
|
)
|
|
.bind::<DieselUuid, _>(signature_id)
|
|
.bind::<DieselUuid, _>(user_id)
|
|
.execute(&mut conn);
|
|
|
|
match result {
|
|
Ok(rows) if rows > 0 => Json(serde_json::json!({
|
|
"success": true,
|
|
"id": id
|
|
})).into_response(),
|
|
Ok(_) => (StatusCode::NOT_FOUND, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": "Signature not found"
|
|
}))).into_response(),
|
|
Err(e) => {
|
|
warn!("Failed to delete signature: {}", e);
|
|
(StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({
|
|
"success": false,
|
|
"error": format!("Failed to delete signature: {}", e)
|
|
}))).into_response()
|
|
}
|
|
}
|
|
}
|