botserver/src/email/signatures.rs
Rodrigo Rodriguez 5ea171d126
Some checks failed
BotServer CI / build (push) Failing after 1m34s
Refactor: Split large files into modular subdirectories
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>
2026-02-12 21:09:30 +00:00

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("&nbsp;", " ")
.replace("&amp;", "&")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&quot;", "\"")
.replace("&#39;", "'");
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()
}
}
}