summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorFxQnLr <[email protected]>2024-03-04 21:37:55 +0100
committerFxQnLr <[email protected]>2024-03-04 21:37:55 +0100
commit0a058ba2064d323451462a79c71580dea7d8ec8c (patch)
tree3e0a5a8c2d48aac8b1ff1720455db47a0c50386a /src
parent0920c86de3523785b5f4ac67e2090f0736f9fcb2 (diff)
downloadwebol-0a058ba2064d323451462a79c71580dea7d8ec8c.tar
webol-0a058ba2064d323451462a79c71580dea7d8ec8c.tar.gz
webol-0a058ba2064d323451462a79c71580dea7d8ec8c.zip
Closes #19. Added OpenApi through `utoipa`
Diffstat (limited to 'src')
-rw-r--r--src/db.rs11
-rw-r--r--src/error.rs3
-rw-r--r--src/main.rs89
-rw-r--r--src/routes.rs2
-rw-r--r--src/routes/device.rs91
-rw-r--r--src/routes/start.rs16
6 files changed, 172 insertions, 40 deletions
diff --git a/src/db.rs b/src/db.rs
index 47e907d..a2b2009 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -1,6 +1,7 @@
1use serde::Serialize; 1use serde::Serialize;
2use sqlx::{PgPool, postgres::PgPoolOptions, types::{ipnetwork::IpNetwork, mac_address::MacAddress}}; 2use sqlx::{PgPool, postgres::PgPoolOptions, types::{ipnetwork::IpNetwork, mac_address::MacAddress}};
3use tracing::{debug, info}; 3use tracing::{debug, info};
4use utoipa::ToSchema;
4 5
5#[derive(Serialize, Debug)] 6#[derive(Serialize, Debug)]
6pub struct Device { 7pub 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)]
17pub 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
14pub async fn init_db_pool(db_url: &str) -> PgPool { 25pub 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};
5use axum::Json; 5use axum::Json;
6use mac_address::MacParseError; 6use mac_address::MacParseError;
7use serde_json::json; 7use serde_json::json;
8use utoipa::ToSchema;
8use std::io; 9use std::io;
9use tracing::error; 10use tracing::error;
10 11
11#[derive(Debug, thiserror::Error)] 12#[derive(Debug, thiserror::Error, ToSchema)]
12pub enum Error { 13pub 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 @@
1use crate::config::Config; 1use crate::{
2use crate::db::init_db_pool; 2 config::Config,
3use crate::routes::device; 3 db::init_db_pool,
4use crate::routes::start::start; 4 routes::{device, start, status},
5use crate::routes::status::status; 5 services::ping::{BroadcastCommand, StatusMap},
6use crate::services::ping::StatusMap; 6};
7use axum::middleware::from_fn_with_state; 7use axum::{
8use axum::routing::{get, put}; 8 middleware::from_fn_with_state,
9use axum::{routing::post, Router}; 9 routing::{get, post},
10 Router,
11};
10use dashmap::DashMap; 12use dashmap::DashMap;
11use services::ping::BroadcastCommand;
12use sqlx::PgPool; 13use sqlx::PgPool;
13use std::env; 14use std::{env, sync::Arc};
14use std::sync::Arc;
15use tokio::sync::broadcast::{channel, Sender}; 15use tokio::sync::broadcast::{channel, Sender};
16use tracing::{info, level_filters::LevelFilter}; 16use tracing::{info, level_filters::LevelFilter};
17use tracing_subscriber::fmt::time::UtcTime; 17use tracing_subscriber::{
18use tracing_subscriber::{fmt, prelude::*, EnvFilter}; 18 fmt::{self, time::UtcTime},
19 prelude::*,
20 EnvFilter,
21};
22use utoipa::{
23 openapi::security::{ApiKey, ApiKeyValue, SecurityScheme},
24 Modify, OpenApi,
25};
26use utoipa_swagger_ui::SwaggerUi;
19 27
20mod config; 28mod config;
21mod db; 29mod db;
@@ -25,7 +33,47 @@ mod routes;
25mod services; 33mod services;
26mod wol; 34mod 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)]
60struct ApiDoc;
61
62struct SecurityAddon;
63
64impl 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)]
29async fn main() -> color_eyre::eyre::Result<()> { 77async 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 @@
1pub mod start; 1pub mod start;
2pub mod device; 2pub mod device;
3pub mod status; \ No newline at end of file 3pub 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 @@
1use crate::db::Device; 1use crate::db::Device;
2use crate::error::Error; 2use crate::error::Error;
3use axum::extract::State; 3use axum::extract::{Path, State};
4use axum::Json; 4use axum::Json;
5use mac_address::MacAddress; 5use mac_address::MacAddress;
6use serde::{Deserialize, Serialize}; 6use serde::Deserialize;
7use serde_json::{json, Value}; 7use serde_json::{json, Value};
8use sqlx::types::ipnetwork::IpNetwork; 8use sqlx::types::ipnetwork::IpNetwork;
9use std::{sync::Arc, str::FromStr}; 9use std::{str::FromStr, sync::Arc};
10use tracing::{debug, info}; 10use tracing::{debug, info};
11use 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]
12pub async fn get( 23pub 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)]
56pub 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)]
35pub struct GetDevicePayload { 79pub 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)]
39pub async fn put( 92pub 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)]
67pub struct PutDevicePayload { 122pub 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(
75pub 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)]
79pub async fn post( 138pub 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)]
108pub struct PostDevicePayload { 167pub 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;
6use axum::Json; 6use axum::Json;
7use serde::{Deserialize, Serialize}; 7use serde::{Deserialize, Serialize};
8use serde_json::{json, Value}; 8use serde_json::{json, Value};
9use utoipa::ToSchema;
9use std::sync::Arc; 10use std::sync::Arc;
10use tracing::{debug, info}; 11use tracing::{debug, info};
11use uuid::Uuid; 12use 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)]
13pub async fn start( 23pub 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)]
92pub struct Payload { 102pub 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)]
98struct Response { 108pub struct Response {
99 id: String, 109 id: String,
100 boot: bool, 110 boot: bool,
101 uuid: Option<String>, 111 uuid: Option<String>,