botserver/src/basic/keywords/file_ops/basic_io.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

186 lines
6.9 KiB
Rust

/*****************************************************************************\
| █████ █████ ██ █ █████ █████ ████ ██ ████ █████ █████ ███ ® |
| ██ █ ███ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
| ██ ███ ████ █ ██ █ ████ █████ ██████ ██ ████ █ █ █ ██ |
| ██ ██ █ █ ██ █ █ ██ ██ ██ ██ ██ ██ █ ██ ██ █ █ |
| █████ █████ █ ███ █████ ██ ██ ██ ██ █████ ████ █████ █ ███ |
| |
| General Bots Copyright (c) pragmatismo.com.br. All rights reserved. |
| Licensed under the AGPL-3.0. |
| |
| According to our dual licensing model, this program can be used either |
| under the terms of the GNU Affero General Public License, version 3, |
| or under a proprietary license. |
| |
| The texts of the GNU Affero General Public License with an additional |
| permission and of our proprietary license can be found at and |
| in the LICENSE file you have received along with this program. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY, without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU Affero General Public License for more details. |
| |
| "General Bots" is a registered trademark of pragmatismo.com.br. |
| The licensing of the program under the AGPLv3 does not imply a |
| trademark license. Therefore any rights, title and interest in |
| our trademarks remain entirely with us. |
| |
\*****************************************************************************/
use crate::core::shared::models::schema::bots::dsl::*;
use crate::core::shared::models::UserSession;
use crate::core::shared::state::AppState;
use diesel::prelude::*;
use log::{error, trace};
use std::error::Error;
pub async fn execute_read(
state: &AppState,
user: &UserSession,
path: &str,
) -> Result<String, Box<dyn Error + Send + Sync>> {
let client = state.drive.as_ref().ok_or("S3 client not configured")?;
let bot_name: String = {
let mut db_conn = state.conn.get().map_err(|e| format!("DB error: {e}"))?;
bots.filter(id.eq(&user.bot_id))
.select(name)
.first(&mut *db_conn)
.map_err(|e| {
error!("Failed to query bot name: {e}");
e
})?
};
let bucket_name = format!("{bot_name}.gbai");
let key = format!("{bot_name}.gbdrive/{path}");
let response = client
.get_object()
.bucket(&bucket_name)
.key(&key)
.send()
.await
.map_err(|e| format!("S3 get failed: {e}"))?;
let data = response.body.collect().await?.into_bytes();
let content =
String::from_utf8(data.to_vec()).map_err(|_| "File content is not valid UTF-8")?;
trace!("READ successful: {} bytes", content.len());
Ok(content)
}
pub async fn execute_write(
state: &AppState,
user: &UserSession,
path: &str,
content: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let client = state.drive.as_ref().ok_or("S3 client not configured")?;
let bot_name: String = {
let mut db_conn = state.conn.get().map_err(|e| format!("DB error: {e}"))?;
bots.filter(id.eq(&user.bot_id))
.select(name)
.first(&mut *db_conn)
.map_err(|e| {
error!("Failed to query bot name: {e}");
e
})?
};
let bucket_name = format!("{bot_name}.gbai");
let key = format!("{bot_name}.gbdrive/{path}");
client
.put_object()
.bucket(&bucket_name)
.key(&key)
.body(content.as_bytes().to_vec().into())
.send()
.await
.map_err(|e| format!("S3 put failed: {e}"))?;
trace!("WRITE successful: {} bytes to {path}", content.len());
Ok(())
}
pub async fn execute_delete_file(
state: &AppState,
user: &UserSession,
path: &str,
) -> Result<(), Box<dyn Error + Send + Sync>> {
let client = state.drive.as_ref().ok_or("S3 client not configured")?;
let bot_name: String = {
let mut db_conn = state.conn.get().map_err(|e| format!("DB error: {e}"))?;
bots.filter(id.eq(&user.bot_id))
.select(name)
.first(&mut *db_conn)
.map_err(|e| {
error!("Failed to query bot name: {e}");
e
})?
};
let bucket_name = format!("{bot_name}.gbai");
let key = format!("{bot_name}.gbdrive/{path}");
client
.delete_object()
.bucket(&bucket_name)
.key(&key)
.send()
.await
.map_err(|e| format!("S3 delete failed: {e}"))?;
trace!("DELETE_FILE successful: {path}");
Ok(())
}
pub async fn execute_list(
state: &AppState,
user: &UserSession,
path: &str,
) -> Result<Vec<String>, Box<dyn Error + Send + Sync>> {
let client = state.drive.as_ref().ok_or("S3 client not configured")?;
let bot_name: String = {
let mut db_conn = state.conn.get().map_err(|e| format!("DB error: {e}"))?;
bots.filter(id.eq(&user.bot_id))
.select(name)
.first(&mut *db_conn)
.map_err(|e| {
error!("Failed to query bot name: {e}");
e
})?
};
let bucket_name = format!("{bot_name}.gbai");
let prefix = format!("{bot_name}.gbdrive/{path}");
let response = client
.list_objects_v2()
.bucket(&bucket_name)
.prefix(&prefix)
.send()
.await
.map_err(|e| format!("S3 list failed: {e}"))?;
let files: Vec<String> = response
.contents()
.iter()
.filter_map(|obj| {
obj.key().map(|k| {
k.strip_prefix(&format!("{bot_name}.gbdrive/"))
.unwrap_or(k)
.to_string()
})
})
.collect();
trace!("LIST successful: {} files", files.len());
Ok(files)
}