diff options
author | FxQnLr <[email protected]> | 2024-03-04 21:37:55 +0100 |
---|---|---|
committer | FxQnLr <[email protected]> | 2024-03-04 21:37:55 +0100 |
commit | 0a058ba2064d323451462a79c71580dea7d8ec8c (patch) | |
tree | 3e0a5a8c2d48aac8b1ff1720455db47a0c50386a /src | |
parent | 0920c86de3523785b5f4ac67e2090f0736f9fcb2 (diff) | |
download | webol-0a058ba2064d323451462a79c71580dea7d8ec8c.tar webol-0a058ba2064d323451462a79c71580dea7d8ec8c.tar.gz webol-0a058ba2064d323451462a79c71580dea7d8ec8c.zip |
Closes #19. Added OpenApi through `utoipa`
Diffstat (limited to 'src')
-rw-r--r-- | src/db.rs | 11 | ||||
-rw-r--r-- | src/error.rs | 3 | ||||
-rw-r--r-- | src/main.rs | 89 | ||||
-rw-r--r-- | src/routes.rs | 2 | ||||
-rw-r--r-- | src/routes/device.rs | 91 | ||||
-rw-r--r-- | src/routes/start.rs | 16 |
6 files changed, 172 insertions, 40 deletions
@@ -1,6 +1,7 @@ | |||
1 | use serde::Serialize; | 1 | use serde::Serialize; |
2 | use sqlx::{PgPool, postgres::PgPoolOptions, types::{ipnetwork::IpNetwork, mac_address::MacAddress}}; | 2 | use sqlx::{PgPool, postgres::PgPoolOptions, types::{ipnetwork::IpNetwork, mac_address::MacAddress}}; |
3 | use tracing::{debug, info}; | 3 | use tracing::{debug, info}; |
4 | use utoipa::ToSchema; | ||
4 | 5 | ||
5 | #[derive(Serialize, Debug)] | 6 | #[derive(Serialize, Debug)] |
6 | pub struct Device { | 7 | pub struct Device { |
@@ -11,6 +12,16 @@ pub struct Device { | |||
11 | pub times: Option<Vec<i64>> | 12 | pub times: Option<Vec<i64>> |
12 | } | 13 | } |
13 | 14 | ||
15 | #[derive(ToSchema)] | ||
16 | #[schema(as = Device)] | ||
17 | pub struct DeviceSchema { | ||
18 | pub id: String, | ||
19 | pub mac: String, | ||
20 | pub broadcast_addr: String, | ||
21 | pub ip: String, | ||
22 | pub times: Option<Vec<i64>> | ||
23 | } | ||
24 | |||
14 | pub async fn init_db_pool(db_url: &str) -> PgPool { | 25 | pub async fn init_db_pool(db_url: &str) -> PgPool { |
15 | debug!("attempt to connect dbPool to '{}'", db_url); | 26 | debug!("attempt to connect dbPool to '{}'", db_url); |
16 | 27 | ||
diff --git a/src/error.rs b/src/error.rs index 513b51b..006fcdb 100644 --- a/src/error.rs +++ b/src/error.rs | |||
@@ -5,10 +5,11 @@ use axum::response::{IntoResponse, Response}; | |||
5 | use axum::Json; | 5 | use axum::Json; |
6 | use mac_address::MacParseError; | 6 | use mac_address::MacParseError; |
7 | use serde_json::json; | 7 | use serde_json::json; |
8 | use utoipa::ToSchema; | ||
8 | use std::io; | 9 | use std::io; |
9 | use tracing::error; | 10 | use tracing::error; |
10 | 11 | ||
11 | #[derive(Debug, thiserror::Error)] | 12 | #[derive(Debug, thiserror::Error, ToSchema)] |
12 | pub enum Error { | 13 | pub enum Error { |
13 | #[error("db: {source}")] | 14 | #[error("db: {source}")] |
14 | Db { | 15 | Db { |
diff --git a/src/main.rs b/src/main.rs index d17984f..8978e58 100644 --- a/src/main.rs +++ b/src/main.rs | |||
@@ -1,21 +1,29 @@ | |||
1 | use crate::config::Config; | 1 | use crate::{ |
2 | use crate::db::init_db_pool; | 2 | config::Config, |
3 | use crate::routes::device; | 3 | db::init_db_pool, |
4 | use crate::routes::start::start; | 4 | routes::{device, start, status}, |
5 | use crate::routes::status::status; | 5 | services::ping::{BroadcastCommand, StatusMap}, |
6 | use crate::services::ping::StatusMap; | 6 | }; |
7 | use axum::middleware::from_fn_with_state; | 7 | use axum::{ |
8 | use axum::routing::{get, put}; | 8 | middleware::from_fn_with_state, |
9 | use axum::{routing::post, Router}; | 9 | routing::{get, post}, |
10 | Router, | ||
11 | }; | ||
10 | use dashmap::DashMap; | 12 | use dashmap::DashMap; |
11 | use services::ping::BroadcastCommand; | ||
12 | use sqlx::PgPool; | 13 | use sqlx::PgPool; |
13 | use std::env; | 14 | use std::{env, sync::Arc}; |
14 | use std::sync::Arc; | ||
15 | use tokio::sync::broadcast::{channel, Sender}; | 15 | use tokio::sync::broadcast::{channel, Sender}; |
16 | use tracing::{info, level_filters::LevelFilter}; | 16 | use tracing::{info, level_filters::LevelFilter}; |
17 | use tracing_subscriber::fmt::time::UtcTime; | 17 | use tracing_subscriber::{ |
18 | use tracing_subscriber::{fmt, prelude::*, EnvFilter}; | 18 | fmt::{self, time::UtcTime}, |
19 | prelude::*, | ||
20 | EnvFilter, | ||
21 | }; | ||
22 | use utoipa::{ | ||
23 | openapi::security::{ApiKey, ApiKeyValue, SecurityScheme}, | ||
24 | Modify, OpenApi, | ||
25 | }; | ||
26 | use utoipa_swagger_ui::SwaggerUi; | ||
19 | 27 | ||
20 | mod config; | 28 | mod config; |
21 | mod db; | 29 | mod db; |
@@ -25,7 +33,47 @@ mod routes; | |||
25 | mod services; | 33 | mod services; |
26 | mod wol; | 34 | mod wol; |
27 | 35 | ||
36 | #[derive(OpenApi)] | ||
37 | #[openapi( | ||
38 | paths( | ||
39 | start::start, | ||
40 | device::get, | ||
41 | device::get_path, | ||
42 | device::post, | ||
43 | device::put, | ||
44 | ), | ||
45 | components( | ||
46 | schemas( | ||
47 | start::Payload, | ||
48 | start::Response, | ||
49 | device::PutDevicePayload, | ||
50 | device::GetDevicePayload, | ||
51 | device::PostDevicePayload, | ||
52 | db::DeviceSchema, | ||
53 | ) | ||
54 | ), | ||
55 | modifiers(&SecurityAddon), | ||
56 | tags( | ||
57 | (name = "Webol", description = "Webol API") | ||
58 | ) | ||
59 | )] | ||
60 | struct ApiDoc; | ||
61 | |||
62 | struct SecurityAddon; | ||
63 | |||
64 | impl Modify for SecurityAddon { | ||
65 | fn modify(&self, openapi: &mut utoipa::openapi::OpenApi) { | ||
66 | if let Some(components) = openapi.components.as_mut() { | ||
67 | components.add_security_scheme( | ||
68 | "api_key", | ||
69 | SecurityScheme::ApiKey(ApiKey::Header(ApiKeyValue::new("Authorization"))), | ||
70 | ); | ||
71 | } | ||
72 | } | ||
73 | } | ||
74 | |||
28 | #[tokio::main] | 75 | #[tokio::main] |
76 | #[allow(deprecated)] | ||
29 | async fn main() -> color_eyre::eyre::Result<()> { | 77 | async fn main() -> color_eyre::eyre::Result<()> { |
30 | color_eyre::install()?; | 78 | color_eyre::install()?; |
31 | 79 | ||
@@ -67,12 +115,15 @@ async fn main() -> color_eyre::eyre::Result<()> { | |||
67 | }; | 115 | }; |
68 | 116 | ||
69 | let app = Router::new() | 117 | let app = Router::new() |
70 | .route("/start", post(start)) | 118 | .route("/start", post(start::start)) |
71 | .route("/device", get(device::get)) | 119 | .route( |
72 | .route("/device", put(device::put)) | 120 | "/device", |
73 | .route("/device", post(device::post)) | 121 | post(device::post).get(device::get).put(device::put), |
74 | .route("/status", get(status)) | 122 | ) |
123 | .route("/device/:id", get(device::get_path)) | ||
124 | .route("/status", get(status::status)) | ||
75 | .route_layer(from_fn_with_state(shared_state.clone(), extractors::auth)) | 125 | .route_layer(from_fn_with_state(shared_state.clone(), extractors::auth)) |
126 | .merge(SwaggerUi::new("/swagger-ui").url("/api-docs/openapi.json", ApiDoc::openapi())) | ||
76 | .with_state(Arc::new(shared_state)); | 127 | .with_state(Arc::new(shared_state)); |
77 | 128 | ||
78 | let addr = config.serveraddr; | 129 | let addr = config.serveraddr; |
diff --git a/src/routes.rs b/src/routes.rs index d5ab0d6..a72f27b 100644 --- a/src/routes.rs +++ b/src/routes.rs | |||
@@ -1,3 +1,3 @@ | |||
1 | pub mod start; | 1 | pub mod start; |
2 | pub mod device; | 2 | pub mod device; |
3 | pub mod status; \ No newline at end of file | 3 | pub mod status; |
diff --git a/src/routes/device.rs b/src/routes/device.rs index d39d98e..d01d9f0 100644 --- a/src/routes/device.rs +++ b/src/routes/device.rs | |||
@@ -1,14 +1,25 @@ | |||
1 | use crate::db::Device; | 1 | use crate::db::Device; |
2 | use crate::error::Error; | 2 | use crate::error::Error; |
3 | use axum::extract::State; | 3 | use axum::extract::{Path, State}; |
4 | use axum::Json; | 4 | use axum::Json; |
5 | use mac_address::MacAddress; | 5 | use mac_address::MacAddress; |
6 | use serde::{Deserialize, Serialize}; | 6 | use serde::Deserialize; |
7 | use serde_json::{json, Value}; | 7 | use serde_json::{json, Value}; |
8 | use sqlx::types::ipnetwork::IpNetwork; | 8 | use sqlx::types::ipnetwork::IpNetwork; |
9 | use std::{sync::Arc, str::FromStr}; | 9 | use std::{str::FromStr, sync::Arc}; |
10 | use tracing::{debug, info}; | 10 | use tracing::{debug, info}; |
11 | use utoipa::ToSchema; | ||
11 | 12 | ||
13 | #[utoipa::path( | ||
14 | get, | ||
15 | path = "/device", | ||
16 | request_body = GetDevicePayload, | ||
17 | responses( | ||
18 | (status = 200, description = "Get `Device` information", body = [Device]) | ||
19 | ), | ||
20 | security(("api_key" = [])) | ||
21 | )] | ||
22 | #[deprecated] | ||
12 | pub async fn get( | 23 | pub async fn get( |
13 | State(state): State<Arc<crate::AppState>>, | 24 | State(state): State<Arc<crate::AppState>>, |
14 | Json(payload): Json<GetDevicePayload>, | 25 | Json(payload): Json<GetDevicePayload>, |
@@ -31,11 +42,53 @@ pub async fn get( | |||
31 | Ok(Json(json!(device))) | 42 | Ok(Json(json!(device))) |
32 | } | 43 | } |
33 | 44 | ||
34 | #[derive(Deserialize)] | 45 | #[utoipa::path( |
46 | get, | ||
47 | path = "/device/{id}", | ||
48 | responses( | ||
49 | (status = 200, description = "Get `Device` information", body = [Device]) | ||
50 | ), | ||
51 | params( | ||
52 | ("id" = String, Path, description = "Device id") | ||
53 | ), | ||
54 | security(("api_key" = [])) | ||
55 | )] | ||
56 | pub async fn get_path( | ||
57 | State(state): State<Arc<crate::AppState>>, | ||
58 | Path(path): Path<String>, | ||
59 | ) -> Result<Json<Value>, Error> { | ||
60 | info!("get device from path {}", path); | ||
61 | let device = sqlx::query_as!( | ||
62 | Device, | ||
63 | r#" | ||
64 | SELECT id, mac, broadcast_addr, ip, times | ||
65 | FROM devices | ||
66 | WHERE id = $1; | ||
67 | "#, | ||
68 | path | ||
69 | ) | ||
70 | .fetch_one(&state.db) | ||
71 | .await?; | ||
72 | |||
73 | debug!("got device {:?}", device); | ||
74 | |||
75 | Ok(Json(json!(device))) | ||
76 | } | ||
77 | |||
78 | #[derive(Deserialize, ToSchema)] | ||
35 | pub struct GetDevicePayload { | 79 | pub struct GetDevicePayload { |
36 | id: String, | 80 | id: String, |
37 | } | 81 | } |
38 | 82 | ||
83 | #[utoipa::path( | ||
84 | put, | ||
85 | path = "/device", | ||
86 | request_body = PutDevicePayload, | ||
87 | responses( | ||
88 | (status = 200, description = "List matching todos by query", body = [DeviceSchema]) | ||
89 | ), | ||
90 | security(("api_key" = [])) | ||
91 | )] | ||
39 | pub async fn put( | 92 | pub async fn put( |
40 | State(state): State<Arc<crate::AppState>>, | 93 | State(state): State<Arc<crate::AppState>>, |
41 | Json(payload): Json<PutDevicePayload>, | 94 | Json(payload): Json<PutDevicePayload>, |
@@ -44,26 +97,28 @@ pub async fn put( | |||
44 | "add device {} ({}, {}, {})", | 97 | "add device {} ({}, {}, {})", |
45 | payload.id, payload.mac, payload.broadcast_addr, payload.ip | 98 | payload.id, payload.mac, payload.broadcast_addr, payload.ip |
46 | ); | 99 | ); |
47 | 100 | ||
48 | let ip = IpNetwork::from_str(&payload.ip)?; | 101 | let ip = IpNetwork::from_str(&payload.ip)?; |
49 | let mac = MacAddress::from_str(&payload.mac)?; | 102 | let mac = MacAddress::from_str(&payload.mac)?; |
50 | sqlx::query!( | 103 | let device = sqlx::query_as!( |
104 | Device, | ||
51 | r#" | 105 | r#" |
52 | INSERT INTO devices (id, mac, broadcast_addr, ip) | 106 | INSERT INTO devices (id, mac, broadcast_addr, ip) |
53 | VALUES ($1, $2, $3, $4); | 107 | VALUES ($1, $2, $3, $4) |
108 | RETURNING id, mac, broadcast_addr, ip, times; | ||
54 | "#, | 109 | "#, |
55 | payload.id, | 110 | payload.id, |
56 | mac, | 111 | mac, |
57 | payload.broadcast_addr, | 112 | payload.broadcast_addr, |
58 | ip | 113 | ip |
59 | ) | 114 | ) |
60 | .execute(&state.db) | 115 | .fetch_one(&state.db) |
61 | .await?; | 116 | .await?; |
62 | 117 | ||
63 | Ok(Json(json!(PutDeviceResponse { success: true }))) | 118 | Ok(Json(json!(device))) |
64 | } | 119 | } |
65 | 120 | ||
66 | #[derive(Deserialize)] | 121 | #[derive(Deserialize, ToSchema)] |
67 | pub struct PutDevicePayload { | 122 | pub struct PutDevicePayload { |
68 | id: String, | 123 | id: String, |
69 | mac: String, | 124 | mac: String, |
@@ -71,11 +126,15 @@ pub struct PutDevicePayload { | |||
71 | ip: String, | 126 | ip: String, |
72 | } | 127 | } |
73 | 128 | ||
74 | #[derive(Serialize)] | 129 | #[utoipa::path( |
75 | pub struct PutDeviceResponse { | 130 | post, |
76 | success: bool, | 131 | path = "/device", |
77 | } | 132 | request_body = PostDevicePayload, |
78 | 133 | responses( | |
134 | (status = 200, description = "List matching todos by query", body = [DeviceSchema]) | ||
135 | ), | ||
136 | security(("api_key" = [])) | ||
137 | )] | ||
79 | pub async fn post( | 138 | pub async fn post( |
80 | State(state): State<Arc<crate::AppState>>, | 139 | State(state): State<Arc<crate::AppState>>, |
81 | Json(payload): Json<PostDevicePayload>, | 140 | Json(payload): Json<PostDevicePayload>, |
@@ -104,7 +163,7 @@ pub async fn post( | |||
104 | Ok(Json(json!(device))) | 163 | Ok(Json(json!(device))) |
105 | } | 164 | } |
106 | 165 | ||
107 | #[derive(Deserialize)] | 166 | #[derive(Deserialize, ToSchema)] |
108 | pub struct PostDevicePayload { | 167 | pub struct PostDevicePayload { |
109 | id: String, | 168 | id: String, |
110 | mac: String, | 169 | mac: String, |
diff --git a/src/routes/start.rs b/src/routes/start.rs index d4c0802..ef6e8f2 100644 --- a/src/routes/start.rs +++ b/src/routes/start.rs | |||
@@ -6,10 +6,20 @@ use axum::extract::State; | |||
6 | use axum::Json; | 6 | use axum::Json; |
7 | use serde::{Deserialize, Serialize}; | 7 | use serde::{Deserialize, Serialize}; |
8 | use serde_json::{json, Value}; | 8 | use serde_json::{json, Value}; |
9 | use utoipa::ToSchema; | ||
9 | use std::sync::Arc; | 10 | use std::sync::Arc; |
10 | use tracing::{debug, info}; | 11 | use tracing::{debug, info}; |
11 | use uuid::Uuid; | 12 | use uuid::Uuid; |
12 | 13 | ||
14 | #[utoipa::path( | ||
15 | post, | ||
16 | path = "/start", | ||
17 | request_body = Payload, | ||
18 | responses( | ||
19 | (status = 200, description = "List matching todos by query", body = [Response]) | ||
20 | ), | ||
21 | security(("api_key" = [])) | ||
22 | )] | ||
13 | pub async fn start( | 23 | pub async fn start( |
14 | State(state): State<Arc<crate::AppState>>, | 24 | State(state): State<Arc<crate::AppState>>, |
15 | Json(payload): Json<Payload>, | 25 | Json(payload): Json<Payload>, |
@@ -88,14 +98,14 @@ fn setup_ping(state: Arc<crate::AppState>, device: Device) -> String { | |||
88 | uuid_ret | 98 | uuid_ret |
89 | } | 99 | } |
90 | 100 | ||
91 | #[derive(Deserialize)] | 101 | #[derive(Deserialize, ToSchema)] |
92 | pub struct Payload { | 102 | pub struct Payload { |
93 | id: String, | 103 | id: String, |
94 | ping: Option<bool>, | 104 | ping: Option<bool>, |
95 | } | 105 | } |
96 | 106 | ||
97 | #[derive(Serialize)] | 107 | #[derive(Serialize, ToSchema)] |
98 | struct Response { | 108 | pub struct Response { |
99 | id: String, | 109 | id: String, |
100 | boot: bool, | 110 | boot: bool, |
101 | uuid: Option<String>, | 111 | uuid: Option<String>, |