new(all): Initial import.

This commit is contained in:
Rodrigo Rodriguez 2024-12-24 10:09:14 -03:00
parent cba43cefde
commit 7f384469a9
4 changed files with 41 additions and 119 deletions

View file

@ -8,11 +8,9 @@ use axum::{
response::IntoResponse, response::IntoResponse,
Json, Json,
}; };
use gb_core::{Result, Error, models::*}; use gb_core::{Result, Error, models::*};
use gb_messaging::{MessageProcessor, models::MessageEnvelope}; use gb_messaging::{MessageProcessor, models::MessageEnvelope};
use std::sync::Arc; use std::{sync::Arc, collections::HashMap};
use chrono;
use tokio::sync::Mutex; use tokio::sync::Mutex;
use tracing::{instrument, error}; use tracing::{instrument, error};
use uuid::Uuid; use uuid::Uuid;
@ -48,7 +46,7 @@ async fn handle_ws_connection(
if let Ok(text) = msg.to_text() { if let Ok(text) = msg.to_text() {
if let Ok(envelope) = serde_json::from_str::<MessageEnvelope>(text) { if let Ok(envelope) = serde_json::from_str::<MessageEnvelope>(text) {
let mut processor = state.message_processor.lock().await; let mut processor = state.message_processor.lock().await;
if let Err(e) = processor.process_message(&envelope).await { if let Err(e) = processor.process_messages(vec![envelope]).await {
error!("Failed to process message: {}", e); error!("Failed to process message: {}", e);
} }
} }
@ -57,19 +55,17 @@ async fn handle_ws_connection(
Ok(()) Ok(())
} }
#[axum::debug_handler] #[instrument(skip_all)]
#[instrument(skip(state))]
async fn websocket_handler( async fn websocket_handler(
State(state): State<Arc<ApiState>>, State(state): State<Arc<ApiState>>,
ws: WebSocketUpgrade, ws: WebSocketUpgrade,
) -> impl IntoResponse { ) -> impl IntoResponse {
ws.on_upgrade(|socket| async move { ws.on_upgrade(move |socket| async move {
let _ = handle_ws_connection(socket, state).await; let _ = handle_ws_connection(socket, state).await;
}) })
} }
#[axum::debug_handler] #[instrument(skip_all)]
#[instrument(skip(state))]
async fn send_message( async fn send_message(
State(state): State<Arc<ApiState>>, State(state): State<Arc<ApiState>>,
Json(message): Json<Message>, Json(message): Json<Message>,
@ -77,27 +73,25 @@ async fn send_message(
let envelope = MessageEnvelope { let envelope = MessageEnvelope {
id: Uuid::new_v4(), id: Uuid::new_v4(),
message, message,
metadata: std::collections::HashMap::new(), metadata: HashMap::new(),
}; };
let mut processor = state.message_processor.lock().await; let mut processor = state.message_processor.lock().await;
processor.process_message(&envelope).await processor.process_messages(vec![envelope.clone()]).await
.map_err(|e| Error::internal(format!("Failed to process message: {}", e)))?; .map_err(|e| Error::internal(format!("Failed to process message: {}", e)))?;
Ok(Json(MessageId(envelope.id))) Ok(Json(MessageId(envelope.id)))
} }
#[axum::debug_handler] #[instrument(skip_all)]
#[instrument(skip(state))]
async fn get_message( async fn get_message(
State(_state): State<Arc<ApiState>>, State(_state): State<Arc<ApiState>>,
Path(id): Path<Uuid>, Path(_id): Path<Uuid>,
) -> Result<Json<Message>> { ) -> Result<Json<Message>> {
todo!() todo!()
} }
#[axum::debug_handler] #[instrument(skip_all)]
#[instrument(skip(state))]
async fn create_room( async fn create_room(
State(_state): State<Arc<ApiState>>, State(_state): State<Arc<ApiState>>,
Json(_config): Json<RoomConfig>, Json(_config): Json<RoomConfig>,
@ -105,44 +99,19 @@ async fn create_room(
todo!() todo!()
} }
#[axum::debug_handler] #[instrument(skip_all)]
#[instrument(skip(state))]
async fn get_room( async fn get_room(
State(_state): State<Arc<ApiState>>, State(_state): State<Arc<ApiState>>,
Path(id): Path<Uuid>, Path(_id): Path<Uuid>,
) -> Result<Json<Room>> { ) -> Result<Json<Room>> {
todo!() todo!()
} }
#[axum::debug_handler] #[instrument(skip_all)]
#[instrument(skip(state))]
async fn join_room( async fn join_room(
State(_state): State<Arc<ApiState>>, State(_state): State<Arc<ApiState>>,
Path(id): Path<Uuid>, Path(_id): Path<Uuid>,
Json(user_id): Json<Uuid>, Json(_user_id): Json<Uuid>,
) -> Result<Json<Connection>> { ) -> Result<Json<Connection>> {
todo!() todo!()
} }
#[cfg(test)]
mod tests {
use super::*;
use axum::http::StatusCode;
use axum::body::Body;
use tower::ServiceExt;
#[tokio::test]
async fn test_health_check() {
let app = create_router(MessageProcessor::new(100));
let response = app
.oneshot(
axum::http::Request::builder()
.uri("/health")
.body(Body::empty())
.unwrap(),
)
.await
.unwrap();
assert_eq!(response.status(), StatusCode::OK);
}
}

View file

@ -1,12 +1,14 @@
use axum::{ use axum::{
http::Request, http::{Request, Response},
response::Response,
middleware::Next, middleware::Next,
body::Body,
}; };
use axum_extra::TypedHeader; use axum_extra::TypedHeader;
use axum_extra::headers::{Authorization, authorization::Bearer}; use axum_extra::headers::{Authorization, authorization::Bearer};
use gb_core::User; use gb_core::User;
use jsonwebtoken::{decode, DecodingKey, Validation}; use jsonwebtoken::{decode, DecodingKey, Validation};
use serde::{Serialize, Deserialize};
use crate::AuthError;
#[derive(Debug, Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize)]
struct Claims { struct Claims {
@ -14,10 +16,10 @@ struct Claims {
exp: i64, exp: i64,
} }
pub async fn auth_middleware<B>( pub async fn auth_middleware(
TypedHeader(auth): TypedHeader<Authorization<Bearer>>, TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
request: Request<B>, request: Request<Body>,
next: Next<B>, next: Next,
) -> Result<Response, AuthError> { ) -> Result<Response, AuthError> {
let token = auth.token(); let token = auth.token();
let key = DecodingKey::from_secret(b"secret"); let key = DecodingKey::from_secret(b"secret");

View file

@ -1,16 +1,10 @@
use std::time::Duration;
use chromiumoxide::browser::{Browser, BrowserConfig}; use chromiumoxide::browser::{Browser, BrowserConfig};
use chromiumoxide::cdp::browser_protocol; use chromiumoxide::element::Element;
use chromiumoxide::page::Page; use chromiumoxide::page::Page;
use futures_util::StreamExt; use futures_util::StreamExt;
use gb_core::{Error, Result}; use gb_core::{Error, Result};
use tracing::instrument; use tracing::instrument;
#[derive(Debug)]
pub struct Element {
inner: chromiumoxide::element::Element,
}
pub struct WebAutomation { pub struct WebAutomation {
browser: Browser, browser: Browser,
} }
@ -18,10 +12,14 @@ pub struct WebAutomation {
impl WebAutomation { impl WebAutomation {
#[instrument] #[instrument]
pub async fn new() -> Result<Self> { pub async fn new() -> Result<Self> {
let (browser, mut handler) = Browser::launch(BrowserConfig::default()) let config = BrowserConfig::builder()
.build()
.map_err(|e| Error::internal(e.to_string()))?;
let (browser, mut handler) = Browser::launch(config)
.await .await
.map_err(|e| Error::internal(e.to_string()))?; .map_err(|e| Error::internal(e.to_string()))?;
tokio::spawn(async move { tokio::spawn(async move {
while let Some(h) = handler.next().await { while let Some(h) = handler.next().await {
if let Err(e) = h { if let Err(e) = h {
@ -35,36 +33,34 @@ impl WebAutomation {
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn new_page(&self) -> Result<Page> { pub async fn new_page(&self) -> Result<Page> {
let params = browser_protocol::page::CreateTarget::new(); let params = chromiumoxide::cdp::browser_protocol::target::CreateTarget::new()
let page = self.browser.new_page(params) .url("about:blank");
self.browser.new_page(params)
.await .await
.map_err(|e| Error::internal(e.to_string()))?; .map_err(|e| Error::internal(e.to_string()))
Ok(page)
} }
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn navigate(&self, page: &Page, url: &str) -> Result<()> { pub async fn navigate(&self, page: &Page, url: &str) -> Result<()> {
page.goto(url) page.goto(url)
.await .await
.map_err(|e| Error::internal(e.to_string()))?; .map_err(|e| Error::internal(e.to_string()))
Ok(())
} }
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn get_element(&self, page: &Page, selector: &str) -> Result<Element> { pub async fn get_element(&self, page: &Page, selector: &str) -> Result<Element> {
let element = page.find_element(selector) page.find_element(selector)
.await .await
.map_err(|e| Error::internal(e.to_string()))?; .map_err(|e| Error::internal(e.to_string()))
Ok(Element { inner: element })
} }
#[instrument(skip(self))] #[instrument(skip(self))]
pub async fn screenshot(&self, page: &Page, _path: &str) -> Result<Vec<u8>> { pub async fn screenshot(&self, page: &Page, _path: &str) -> Result<Vec<u8>> {
let screenshot_params = browser_protocol::page::CaptureScreenshot::new(); let params = chromiumoxide::cdp::browser_protocol::page::CaptureScreenshot::new();
let data = page.screenshot(screenshot_params) page.screenshot(params)
.await .await
.map_err(|e| Error::internal(e.to_string()))?; .map_err(|e| Error::internal(e.to_string()))
Ok(data)
} }
#[instrument(skip(self))] #[instrument(skip(self))]
@ -74,49 +70,4 @@ impl WebAutomation {
.map_err(|e| Error::internal(e.to_string()))?; .map_err(|e| Error::internal(e.to_string()))?;
Ok(()) Ok(())
} }
#[instrument(skip(self))]
pub async fn wait_for_network_idle(&self, page: &Page) -> Result<()> {
page.evaluate("() => new Promise(resolve => setTimeout(resolve, 1000))")
.await
.map_err(|e| Error::internal(e.to_string()))?;
Ok(())
}
} }
#[cfg(test)]
mod tests {
use super::*;
use rstest::*;
#[fixture]
async fn automation() -> WebAutomation {
WebAutomation::new().await.unwrap()
}
#[rstest]
#[tokio::test]
async fn test_navigation(automation: WebAutomation) -> Result<()> {
let page = automation.new_page().await?;
automation.navigate(&page, "https://example.com").await?;
Ok(())
}
#[rstest]
#[tokio::test]
async fn test_element_interaction(automation: WebAutomation) -> Result<()> {
let page = automation.new_page().await?;
automation.navigate(&page, "https://example.com").await?;
let element = automation.get_element(&page, "h1").await?;
Ok(())
}
#[rstest]
#[tokio::test]
async fn test_screenshot(automation: WebAutomation) -> Result<()> {
let page = automation.new_page().await?;
automation.navigate(&page, "https://example.com").await?;
let screenshot = automation.screenshot(&page, "test.png").await?;
Ok(())
}
}

View file

@ -130,7 +130,7 @@ impl ImageProcessor {
let mut api = Tesseract::new(None, Some("eng")) let mut api = Tesseract::new(None, Some("eng"))
.map_err(|e| Error::internal(format!("Failed to initialize Tesseract: {}", e)))?; .map_err(|e| Error::internal(format!("Failed to initialize Tesseract: {}", e)))?;
api.set_image(temp_file.path()) api.set_image(temp_file.path().to_str().unwrap())
.map_err(|e| Error::internal(format!("Failed to set image: {}", e)))?; .map_err(|e| Error::internal(format!("Failed to set image: {}", e)))?;
api.recognize() api.recognize()