new(all): Initial import.
This commit is contained in:
parent
e6201719ce
commit
cba43cefde
6 changed files with 58 additions and 101 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -2349,6 +2349,7 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"axum 0.7.9",
|
"axum 0.7.9",
|
||||||
|
"chrono",
|
||||||
"futures-util",
|
"futures-util",
|
||||||
"gb-core",
|
"gb-core",
|
||||||
"gb-messaging",
|
"gb-messaging",
|
||||||
|
@ -2357,6 +2358,7 @@ dependencies = [
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
"tokio-test",
|
"tokio-test",
|
||||||
"tower 0.4.13",
|
"tower 0.4.13",
|
||||||
"tower-http 0.5.2",
|
"tower-http 0.5.2",
|
||||||
|
|
|
@ -18,7 +18,9 @@ serde_json.workspace = true
|
||||||
uuid.workspace = true
|
uuid.workspace = true
|
||||||
tracing.workspace = true
|
tracing.workspace = true
|
||||||
async-trait.workspace = true
|
async-trait.workspace = true
|
||||||
futures-util = "0.3"
|
futures-util = { version = "0.3", features = ["sink"] }
|
||||||
|
chrono = { workspace = true, features = ["serde"] }
|
||||||
|
tokio-stream = "0.1.17"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
rstest.workspace = true
|
rstest.workspace = true
|
||||||
|
|
|
@ -2,7 +2,7 @@ use axum::{
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Router,
|
Router,
|
||||||
extract::{
|
extract::{
|
||||||
ws::{WebSocket, Message as WsMessage},
|
ws::WebSocket,
|
||||||
Path, State, WebSocketUpgrade,
|
Path, State, WebSocketUpgrade,
|
||||||
},
|
},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
|
@ -10,15 +10,13 @@ use axum::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use gb_core::{Result, Error, models::*};
|
use gb_core::{Result, Error, models::*};
|
||||||
use gb_messaging::{MessageProcessor, models::MessageEnvelope}; // Update this line
|
use gb_messaging::{MessageProcessor, models::MessageEnvelope};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
use chrono;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
|
|
||||||
|
|
||||||
use tracing::{instrument, error};
|
use tracing::{instrument, error};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use futures_util::SinkExt;
|
|
||||||
|
|
||||||
pub struct ApiState {
|
pub struct ApiState {
|
||||||
pub message_processor: Mutex<MessageProcessor>,
|
pub message_processor: Mutex<MessageProcessor>,
|
||||||
|
@ -42,36 +40,36 @@ pub fn create_router(message_processor: MessageProcessor) -> Router {
|
||||||
|
|
||||||
async fn handle_ws_connection(
|
async fn handle_ws_connection(
|
||||||
ws: WebSocket,
|
ws: WebSocket,
|
||||||
State(_state): State<Arc<ApiState>>,
|
state: Arc<ApiState>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<()> {
|
||||||
let (mut sender, mut receiver) = ws.split();
|
let (_sender, mut receiver) = ws.split();
|
||||||
// ... rest of the implementation
|
|
||||||
|
while let Some(Ok(msg)) = receiver.next().await {
|
||||||
|
if let Ok(text) = msg.to_text() {
|
||||||
|
if let Ok(envelope) = serde_json::from_str::<MessageEnvelope>(text) {
|
||||||
|
let mut processor = state.message_processor.lock().await;
|
||||||
|
if let Err(e) = processor.process_message(&envelope).await {
|
||||||
|
error!("Failed to process message: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
#[instrument(skip(state, ws))]
|
#[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(|socket| async move {
|
||||||
let (mut sender, mut receiver) = socket.split();
|
let _ = handle_ws_connection(socket, state).await;
|
||||||
|
|
||||||
while let Some(Ok(msg)) = receiver.next().await {
|
|
||||||
if let Ok(text) = msg.to_text() {
|
|
||||||
if let Ok(envelope) = serde_json::from_str::<MessageEnvelope>(text) {
|
|
||||||
let mut processor = state.message_processor.lock().await;
|
|
||||||
if let Err(e) = processor.sender().send(envelope).await {
|
|
||||||
error!("Failed to process WebSocket message: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
#[instrument(skip(state, message))]
|
#[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>,
|
||||||
|
@ -83,8 +81,8 @@ async fn send_message(
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut processor = state.message_processor.lock().await;
|
let mut processor = state.message_processor.lock().await;
|
||||||
processor.sender().send(envelope.clone()).await
|
processor.process_message(&envelope).await
|
||||||
.map_err(|e| Error::internal(format!("Failed to send message: {}", e)))?;
|
.map_err(|e| Error::internal(format!("Failed to process message: {}", e)))?;
|
||||||
|
|
||||||
Ok(Json(MessageId(envelope.id)))
|
Ok(Json(MessageId(envelope.id)))
|
||||||
}
|
}
|
||||||
|
@ -92,14 +90,14 @@ async fn send_message(
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
#[instrument(skip(state))]
|
#[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]
|
#[axum::debug_handler]
|
||||||
#[instrument(skip(state, config))]
|
#[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>,
|
||||||
|
@ -110,7 +108,7 @@ async fn create_room(
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
#[instrument(skip(state))]
|
#[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!()
|
||||||
|
@ -119,7 +117,7 @@ async fn get_room(
|
||||||
#[axum::debug_handler]
|
#[axum::debug_handler]
|
||||||
#[instrument(skip(state))]
|
#[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>> {
|
||||||
|
@ -136,7 +134,6 @@ mod tests {
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_health_check() {
|
async fn test_health_check() {
|
||||||
let app = create_router(MessageProcessor::new(100));
|
let app = create_router(MessageProcessor::new(100));
|
||||||
|
|
||||||
let response = app
|
let response = app
|
||||||
.oneshot(
|
.oneshot(
|
||||||
axum::http::Request::builder()
|
axum::http::Request::builder()
|
||||||
|
@ -146,39 +143,6 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_send_message() {
|
|
||||||
let app = create_router(MessageProcessor::new(100));
|
|
||||||
|
|
||||||
let message = Message {
|
|
||||||
id: Uuid::new_v4(),
|
|
||||||
customer_id: Uuid::new_v4(),
|
|
||||||
instance_id: Uuid::new_v4(),
|
|
||||||
conversation_id: Uuid::new_v4(),
|
|
||||||
sender_id: Uuid::new_v4(),
|
|
||||||
kind: "test".to_string(),
|
|
||||||
content: "test message".to_string(),
|
|
||||||
metadata: serde_json::Value::Object(serde_json::Map::new()),
|
|
||||||
created_at: chrono::Utc::now(),
|
|
||||||
shard_key: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let response = app
|
|
||||||
.oneshot(
|
|
||||||
axum::http::Request::builder()
|
|
||||||
.method("POST")
|
|
||||||
.uri("/messages")
|
|
||||||
.header("content-type", "application/json")
|
|
||||||
.body(Body::from(serde_json::to_string(&message).unwrap()))
|
|
||||||
.unwrap(),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
assert_eq!(response.status(), StatusCode::OK);
|
assert_eq!(response.status(), StatusCode::OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,3 +52,5 @@ headers = "0.3"
|
||||||
rstest = "0.18"
|
rstest = "0.18"
|
||||||
tokio-test = "0.4"
|
tokio-test = "0.4"
|
||||||
mockall = "0.12"
|
mockall = "0.12"
|
||||||
|
axum-extra = { version = "0.7" }
|
||||||
|
sqlx = { version = "0.7", features = ["runtime-tokio-native-tls", "postgres", "uuid", "chrono", "json"] }
|
||||||
|
|
|
@ -1,46 +1,33 @@
|
||||||
use axum::{
|
use axum::{
|
||||||
async_trait,
|
http::Request,
|
||||||
extract::FromRequestParts,
|
response::Response,
|
||||||
http::{request::Parts, StatusCode},
|
middleware::Next,
|
||||||
response::{IntoResponse, Response},
|
|
||||||
RequestPartsExt,
|
|
||||||
};
|
};
|
||||||
use axum_extra::headers::{authorization::Bearer, Authorization};
|
|
||||||
use axum_extra::TypedHeader;
|
use axum_extra::TypedHeader;
|
||||||
use crate::{models::User, AuthError};
|
use axum_extra::headers::{Authorization, authorization::Bearer};
|
||||||
|
use gb_core::User;
|
||||||
|
use jsonwebtoken::{decode, DecodingKey, Validation};
|
||||||
|
|
||||||
impl IntoResponse for AuthError {
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
fn into_response(self) -> Response {
|
struct Claims {
|
||||||
let (status, error_message) = match self {
|
sub: String,
|
||||||
AuthError::InvalidToken => (StatusCode::UNAUTHORIZED, "Invalid token"),
|
exp: i64,
|
||||||
AuthError::MissingToken => (StatusCode::UNAUTHORIZED, "Missing token"),
|
|
||||||
AuthError::TokenExpired => (StatusCode::UNAUTHORIZED, "Token expired"),
|
|
||||||
AuthError::InvalidCredentials => (StatusCode::UNAUTHORIZED, "Invalid credentials"),
|
|
||||||
AuthError::AuthenticationFailed => (StatusCode::UNAUTHORIZED, "Authentication failed"),
|
|
||||||
AuthError::Database(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Database error"),
|
|
||||||
AuthError::Cache(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Cache error"),
|
|
||||||
AuthError::Internal(_) => (StatusCode::INTERNAL_SERVER_ERROR, "Internal server error"),
|
|
||||||
};
|
|
||||||
|
|
||||||
(status, error_message).into_response()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
pub async fn auth_middleware<B>(
|
||||||
impl<S> FromRequestParts<S> for User
|
TypedHeader(auth): TypedHeader<Authorization<Bearer>>,
|
||||||
where
|
request: Request<B>,
|
||||||
S: Send + Sync,
|
next: Next<B>,
|
||||||
{
|
) -> Result<Response, AuthError> {
|
||||||
type Rejection = AuthError;
|
let token = auth.token();
|
||||||
|
let key = DecodingKey::from_secret(b"secret");
|
||||||
|
let validation = Validation::default();
|
||||||
|
|
||||||
async fn from_request_parts(parts: &mut Parts, _state: &S) -> Result<Self, Self::Rejection> {
|
match decode::<Claims>(token, &key, &validation) {
|
||||||
let TypedHeader(Authorization(bearer)) = parts
|
Ok(_claims) => {
|
||||||
.extract::<TypedHeader<Authorization<Bearer>>>()
|
let response = next.run(request).await;
|
||||||
.await
|
Ok(response)
|
||||||
.map_err(|_| AuthError::MissingToken)?;
|
}
|
||||||
|
Err(_) => Err(AuthError::InvalidToken),
|
||||||
let token = bearer.token();
|
|
||||||
|
|
||||||
todo!("Implement token validation")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue