update logging system and send websocket message on owner creation via api

This commit is contained in:
Ninjdai 2025-08-01 11:14:04 +02:00
parent 3e1c744db1
commit c016fc915b
7 changed files with 97 additions and 53 deletions

View file

@ -1,16 +1,16 @@
use std::{net::SocketAddr, sync::Arc, time::Duration};
use std::{net::SocketAddr, sync::Arc};
use axum::{extract::State, http::HeaderMap, routing::get};
use reqwest::{header::USER_AGENT};
use sea_orm::{ConnectionTrait, Database, DatabaseConnection, EntityTrait, PaginatorTrait, Schema};
use tokio::{sync::broadcast::{self, Sender}, task, time};
use tokio::{sync::broadcast::{self, Sender}};
use utoipa::openapi::{ContactBuilder, InfoBuilder, LicenseBuilder};
use utoipa_axum::router::OpenApiRouter;
use utoipa_redoc::{Redoc, Servable};
use utoipa_swagger_ui::{Config, SwaggerUi};
use utoipa_axum::routes;
use crate::{entities::{owner, prelude::BookInstance}, utils::events::Event};
use crate::{entities::prelude::BookInstance, utils::events::Event};
pub mod entities;
pub mod utils;
@ -38,41 +38,26 @@ async fn index(
#[tokio::main]
async fn main() {
pretty_env_logger::init();
let db: Arc<DatabaseConnection> = Arc::new(Database::connect(String::from("sqlite://./alexandria.db?mode=rwc")).await.unwrap());
let builder = db.get_database_backend();
let schema = Schema::new(builder);
if let Err(err) = db.execute(builder.build(schema.create_table_from_entity(crate::entities::prelude::Book).if_not_exists())).await {
println!("Error while creating book table: {err:?}");
log::error!(target: "database", "Error while creating book table: {err:?}");
return;
}
if let Err(err) = db.execute(builder.build(schema.create_table_from_entity(crate::entities::prelude::BookInstance).if_not_exists())).await {
println!("Error while creating book_instance table: {err:?}");
log::error!(target: "database", "Error while creating book_instance table: {err:?}");
return;
}
if let Err(err) = db.execute(builder.build(schema.create_table_from_entity(crate::entities::prelude::Owner).if_not_exists())).await {
println!("Error while creating owner table: {err:?}");
log::error!(target: "database", "Error while creating owner table: {err:?}");
return;
}
let (event_bus, _) = broadcast::channel(16);
let ntx = event_bus.clone();
let _forever = task::spawn(async move {
let mut interval = time::interval(Duration::from_secs(5));
let mut id = 1;
loop {
interval.tick().await;
let _ = ntx.send(Event::WebsocketBroadcast(utils::events::WebsocketMessage::NewOwner(Arc::new(owner::Model {
id,
first_name: "Avril".to_string(),
last_name: "Papillon".to_string(),
contact: "avril.papillon@proton.me".to_string()
}))));
id += 1;
}
});
let mut default_headers = HeaderMap::new();
default_headers.append(USER_AGENT, "Alexandria/1.0 (unionetudianteauvergne@gmail.com)".parse().unwrap());
@ -118,7 +103,7 @@ async fn main() {
let router = router.merge(swagger);
let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap();
println!("Running on http://{}", listener.local_addr().unwrap());
log::info!("Running on http://{}", listener.local_addr().unwrap());
axum::serve(
listener,
router.into_make_service_with_connect_info::<SocketAddr>()

View file

@ -73,7 +73,7 @@ pub async fn create_book_instance(
let b = book_instance.save(state.db_conn.as_ref()).await;
match b {
Err(e) => {
println!("Error while inserting new book instance: {:#?}", e);
log::error!(target: "api", "Error while inserting new book instance: {:#?}", e);
(StatusCode::BAD_REQUEST, Json(None))
},
Ok(res) => (StatusCode::OK, Json(Some(res.try_into_model().expect("All fields should be set once the book instance is saved"))))

View file

@ -6,7 +6,7 @@ use sea_orm::{ActiveModelTrait, ActiveValue::{NotSet, Set}, EntityTrait, TryInto
use serde::{Deserialize, Serialize};
use utoipa::IntoParams;
use crate::{entities::{owner, prelude::Owner}, AppState};
use crate::{entities::{owner, prelude::Owner}, utils::events::{Event, WebsocketMessage}, AppState};
#[derive(IntoParams)]
@ -62,19 +62,23 @@ pub async fn create_owner(
Json(instance_payload): Json<OwnerCreateParams>,
) -> (StatusCode, Json<Option<owner::Model>>) {
let book_instance = owner::ActiveModel {
let owner = owner::ActiveModel {
first_name: Set(instance_payload.first_name),
last_name: Set(instance_payload.last_name),
contact: Set(instance_payload.contact),
id: NotSet
};
let b = book_instance.save(state.db_conn.as_ref()).await;
match b {
let o = owner.insert(state.db_conn.as_ref()).await;
match o {
Err(e) => {
println!("Error while inserting new owner: {:#?}", e);
log::error!(target: "api", "Error while inserting new owner from api: {:#?}", e);
(StatusCode::BAD_REQUEST, Json(None))
},
Ok(res) => (StatusCode::OK, Json(Some(res.try_into_model().expect("All fields should be set once the book instance is saved"))))
Ok(res) => {
let model = res.try_into_model().expect("All fields should be set once the owner is saved");
let _ = state.event_bus.send(Event::WebsocketBroadcast(WebsocketMessage::NewOwner(Arc::new(model.clone()))));
(StatusCode::OK, Json(Some(model)))
}
}
}

View file

@ -9,7 +9,6 @@ use axum::{
},
response::IntoResponse
};
use serde_json::json;
use crate::{utils::events, AppState};
@ -21,9 +20,7 @@ pub async fn ws_handler(
ConnectInfo(addr): ConnectInfo<SocketAddr>,
State(state): State<Arc<AppState>>
) -> impl IntoResponse {
println!("`{addr} connected.");
// finalize the upgrade process by returning upgrade callback.
// we can customize the callback by sending additional info such as address.
log::debug!(target: "websocket", "{addr} connected.");
ws.on_upgrade(move |socket| handle_socket(socket, addr, state))
}
@ -34,9 +31,9 @@ async fn handle_socket(mut socket: WebSocket, who: SocketAddr, state: Arc<AppSta
.await
.is_ok()
{
println!("WS >>> Pinged {who}...");
log::debug!(target: "websocket", "Pinged {who}...");
} else {
println!("WS >>> Could not send ping {who}!");
log::debug!(target: "websocket", "Could not send ping to {who}!");
return;
}
@ -46,7 +43,7 @@ async fn handle_socket(mut socket: WebSocket, who: SocketAddr, state: Arc<AppSta
return;
}
} else {
println!("WS >>> Client {who} abruptly disconnected");
log::debug!(target: "websocket", "Client {who} abruptly disconnected");
return;
}
}
@ -78,15 +75,15 @@ async fn handle_socket(mut socket: WebSocket, who: SocketAddr, state: Arc<AppSta
tokio::select! {
rv_a = (&mut send_task) => {
match rv_a {
Ok(()) => println!("WS >>> Sender connection with {who} gracefully shut down"),
Err(a) => println!("WS >>> Error sending messages {a:?}")
Ok(()) => log::debug!(target: "websocket", "Sender connection with {who} gracefully shut down"),
Err(a) => log::debug!(target: "websocket", "Error sending messages {a:?}")
}
recv_task.abort();
},
rv_b = (&mut recv_task) => {
match rv_b {
Ok(()) => println!("WS >>> Receiver connection with {who} gracefully shut down"),
Err(b) => println!("WS >>> Error receiving messages {b:?}")
Ok(()) => log::debug!(target: "websocket", "Receiver connection with {who} gracefully shut down"),
Err(b) => log::debug!(target: "websocket", "Error receiving messages {b:?}")
}
send_task.abort();
}
@ -96,28 +93,27 @@ async fn handle_socket(mut socket: WebSocket, who: SocketAddr, state: Arc<AppSta
fn process_message(msg: Message, who: SocketAddr) -> ControlFlow<(), ()> {
match msg {
Message::Text(t) => {
println!("WS >>> {who} sent str: {t:?}");
log::debug!(target: "websocket", "{who} sent str: {t:?}");
}
Message::Binary(d) => {
println!("WS >>> {who} sent {} bytes: {d:?}", d.len());
log::debug!(target: "websocket", "{who} sent {} bytes: {d:?}", d.len());
}
Message::Close(c) => {
if let Some(cf) = c {
println!(
"WS >>> {who} sent close with code {} and reason `{}`",
log::debug!(target: "websocket",
"{who} sent close with code {} and reason `{}`",
cf.code, cf.reason
);
} else {
println!("WS >>> {who} somehow sent close message without CloseFrame");
log::debug!(target: "websocket", "{who} somehow sent close message without CloseFrame");
}
return ControlFlow::Break(());
}
Message::Pong(v) => {
println!("WS >>> {who} sent pong with {v:?}");
Message::Pong(_v) => {
//log::debug!(target: "websocket", "{who} sent pong with {v:?}");
}
Message::Ping(v) => {
println!("WS >>> {who} sent ping with {v:?}");
Message::Ping(_v) => {
//log::debug!(target: "websocket", "{who} sent ping with {v:?}");
}
}
ControlFlow::Continue(())

View file

@ -30,7 +30,7 @@ pub async fn fetch_book_by_ean(web_client: &reqwest::Client, ean: &String) -> Op
match body.status() {
StatusCode::OK => {
let res = body.text().await.unwrap();
println!("Res: {res:#?}");
log::trace!(target: "api", "OpenLibrary book fetch result: {res:#?}");
let v: Value = serde_json::from_str(&res).unwrap();
Some(FetchedBook {
ean: ean.to_string(),