new(all): Initial import.
This commit is contained in:
parent
cba43cefde
commit
7f384469a9
4 changed files with 41 additions and 119 deletions
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -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");
|
||||||
|
|
|
@ -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(())
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
||||||
|
|
Loading…
Add table
Reference in a new issue