summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/db.rs3
-rw-r--r--src/error.rs31
-rw-r--r--src/main.rs35
-rw-r--r--src/routes/device.rs92
-rw-r--r--src/routes/mod.rs3
-rw-r--r--src/routes/start.rs52
6 files changed, 169 insertions, 47 deletions
diff --git a/src/db.rs b/src/db.rs
index 79eca91..87943ca 100644
--- a/src/db.rs
+++ b/src/db.rs
@@ -1,3 +1,6 @@
1use serde::Serialize;
2
3#[derive(Serialize)]
1pub struct Device { 4pub struct Device {
2 pub id: String, 5 pub id: String,
3 pub mac: String, 6 pub mac: String,
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..afed111
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,31 @@
1use std::error::Error;
2use axum::http::StatusCode;
3use axum::Json;
4use axum::response::{IntoResponse, Response};
5use serde_json::json;
6use tracing::error;
7use crate::auth::AuthError;
8
9pub enum WebolError {
10 Auth(AuthError),
11 Generic,
12 Server(Box<dyn Error>),
13}
14
15impl IntoResponse for WebolError {
16 fn into_response(self) -> Response {
17 let (status, error_message) = match self {
18 WebolError::Auth(err) => err.get(),
19 WebolError::Generic => (StatusCode::INTERNAL_SERVER_ERROR, ""),
20 WebolError::Server(err) => {
21 error!("server error: {}", err.to_string());
22 (StatusCode::INTERNAL_SERVER_ERROR, "Server Error")
23 },
24
25 };
26 let body = Json(json!({
27 "error": error_message,
28 }));
29 (status, body).into_response()
30 }
31}
diff --git a/src/main.rs b/src/main.rs
index 761e925..bb37dc2 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,9 +1,13 @@
1use std::env;
1use std::sync::Arc; 2use std::sync::Arc;
2use axum::{Router, routing::post}; 3use axum::{Router, routing::post};
3use sqlx::SqlitePool; 4use axum::routing::{get, put};
5use sqlx::PgPool;
6use sqlx::postgres::PgPoolOptions;
4use time::util::local_offset; 7use time::util::local_offset;
5use tracing::{debug, info, level_filters::LevelFilter}; 8use tracing::{debug, info, level_filters::LevelFilter};
6use tracing_subscriber::{EnvFilter, fmt::{self, time::LocalTime}, prelude::*}; 9use tracing_subscriber::{EnvFilter, fmt::{self, time::LocalTime}, prelude::*};
10use crate::routes::device::{get_device, post_device, put_device};
7use crate::routes::start::start; 11use crate::routes::start::start;
8 12
9mod auth; 13mod auth;
@@ -11,6 +15,7 @@ mod config;
11mod routes; 15mod routes;
12mod wol; 16mod wol;
13mod db; 17mod db;
18mod error;
14 19
15#[tokio::main] 20#[tokio::main]
16async fn main() { 21async fn main() {
@@ -30,20 +35,20 @@ async fn main() {
30 ) 35 )
31 .init(); 36 .init();
32 37
33 debug!("connecting to db");
34 let db = SqlitePool::connect("sqlite:devices.sqlite").await.unwrap();
35 sqlx::migrate!().run(&db).await.unwrap();
36 info!("connected to db");
37
38 let version = env!("CARGO_PKG_VERSION"); 38 let version = env!("CARGO_PKG_VERSION");
39 39
40 info!("starting webol v{}", version); 40 info!("starting webol v{}", version);
41 41
42 let db = init_db_pool().await;
43
42 let shared_state = Arc::new(AppState { db }); 44 let shared_state = Arc::new(AppState { db });
43 45
44 // build our application with a single route 46 // build our application with a single route
45 let app = Router::new() 47 let app = Router::new()
46 .route("/start", post(start)) 48 .route("/start", post(start))
49 .route("/device", get(get_device))
50 .route("/device", put(put_device))
51 .route("/device", post(post_device))
47 .with_state(shared_state); 52 .with_state(shared_state);
48 53
49 // run it with hyper on localhost:3000 54 // run it with hyper on localhost:3000
@@ -54,5 +59,21 @@ async fn main() {
54} 59}
55 60
56pub struct AppState { 61pub struct AppState {
57 db: SqlitePool 62 db: PgPool
63}
64
65async fn init_db_pool() -> PgPool {
66 let db_url = env::var("DATABASE_URL").unwrap();
67
68 debug!("attempting to connect dbPool to '{}'", db_url);
69
70 let pool = PgPoolOptions::new()
71 .max_connections(5)
72 .connect(&db_url)
73 .await
74 .unwrap();
75
76 info!("dbPool successfully connected to '{}'", db_url);
77
78 pool
58} \ No newline at end of file 79} \ No newline at end of file
diff --git a/src/routes/device.rs b/src/routes/device.rs
new file mode 100644
index 0000000..d5d7144
--- /dev/null
+++ b/src/routes/device.rs
@@ -0,0 +1,92 @@
1use std::sync::Arc;
2use axum::extract::State;
3use axum::headers::HeaderMap;
4use axum::Json;
5use serde::{Deserialize, Serialize};
6use serde_json::{json, Value};
7use crate::auth::auth;
8use crate::db::Device;
9use crate::error::WebolError;
10
11pub async fn get_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<GetDevicePayload>) -> Result<Json<Value>, WebolError> {
12 let secret = headers.get("authorization");
13 if auth(secret).map_err(WebolError::Auth)? {
14 let device = sqlx::query_as!(
15 Device,
16 r#"
17 SELECT id, mac, broadcast_addr
18 FROM devices
19 WHERE id = $1;
20 "#,
21 payload.id
22 ).fetch_one(&state.db).await.map_err(|err| WebolError::Server(Box::new(err)))?;
23
24 Ok(Json(json!(device)))
25 } else {
26 Err(WebolError::Generic)
27 }
28}
29
30#[derive(Deserialize)]
31pub struct GetDevicePayload {
32 id: String,
33}
34
35pub async fn put_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PutDevicePayload>) -> Result<Json<Value>, WebolError> {
36 let secret = headers.get("authorization");
37 if auth(secret).map_err(WebolError::Auth)? {
38 sqlx::query!(
39 r#"
40 INSERT INTO devices (id, mac, broadcast_addr)
41 VALUES ($1, $2, $3);
42 "#,
43 payload.id,
44 payload.mac,
45 payload.broadcast_addr
46 ).execute(&state.db).await.map_err(|err| WebolError::Server(Box::new(err)))?;
47
48 Ok(Json(json!(PutDeviceResponse { success: true })))
49 } else {
50 Err(WebolError::Generic)
51 }
52}
53
54#[derive(Deserialize)]
55pub struct PutDevicePayload {
56 id: String,
57 mac: String,
58 broadcast_addr: String,
59}
60
61#[derive(Serialize)]
62pub struct PutDeviceResponse {
63 success: bool
64}
65
66pub async fn post_device(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<PostDevicePayload>) -> Result<Json<Value>, WebolError> {
67 let secret = headers.get("authorization");
68 if auth(secret).map_err(WebolError::Auth)? {
69 let device = sqlx::query_as!(
70 Device,
71 r#"
72 UPDATE devices
73 SET mac = $1, broadcast_addr = $2 WHERE id = $3
74 RETURNING id, mac, broadcast_addr;
75 "#,
76 payload.mac,
77 payload.broadcast_addr,
78 payload.id
79 ).fetch_one(&state.db).await.map_err(|err| WebolError::Server(Box::new(err)))?;
80
81 Ok(Json(json!(device)))
82 } else {
83 Err(WebolError::Generic)
84 }
85}
86
87#[derive(Deserialize)]
88pub struct PostDevicePayload {
89 id: String,
90 mac: String,
91 broadcast_addr: String,
92} \ No newline at end of file
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
index 78d4375..12fbfab 100644
--- a/src/routes/mod.rs
+++ b/src/routes/mod.rs
@@ -1 +1,2 @@
1pub mod start; \ No newline at end of file 1pub mod start;
2pub mod device; \ No newline at end of file
diff --git a/src/routes/start.rs b/src/routes/start.rs
index 2d505fc..d16ea4e 100644
--- a/src/routes/start.rs
+++ b/src/routes/start.rs
@@ -1,45 +1,43 @@
1use axum::headers::HeaderMap; 1use axum::headers::HeaderMap;
2use axum::http::StatusCode;
3use axum::Json; 2use axum::Json;
4use axum::response::{IntoResponse, Response};
5use serde::{Deserialize, Serialize}; 3use serde::{Deserialize, Serialize};
6use std::error::Error;
7use std::sync::Arc; 4use std::sync::Arc;
8use axum::extract::State; 5use axum::extract::State;
9use serde_json::{json, Value}; 6use serde_json::{json, Value};
10use tracing::{error, info}; 7use tracing::info;
11use crate::auth::{auth, AuthError}; 8use crate::auth::auth;
12use crate::config::SETTINGS; 9use crate::config::SETTINGS;
13use crate::wol::{create_buffer, send_packet}; 10use crate::wol::{create_buffer, send_packet};
14use crate::db::Device; 11use crate::db::Device;
12use crate::error::WebolError;
15 13
16pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<StartPayload>) -> Result<Json<Value>, StartError> { 14pub async fn start(State(state): State<Arc<crate::AppState>>, headers: HeaderMap, Json(payload): Json<StartPayload>) -> Result<Json<Value>, WebolError> {
17 let secret = headers.get("authorization"); 15 let secret = headers.get("authorization");
18 if auth(secret).map_err(StartError::Auth)? { 16 if auth(secret).map_err(WebolError::Auth)? {
19 let device = sqlx::query_as!( 17 let device = sqlx::query_as!(
20 Device, 18 Device,
21 r#" 19 r#"
22 SELECT id, mac, broadcast_addr 20 SELECT id, mac, broadcast_addr
23 FROM devices 21 FROM devices
24 WHERE id = ?1; 22 WHERE id = $1;
25 "#, 23 "#,
26 payload.id 24 payload.id
27 ).fetch_one(&state.db).await.map_err(|err| StartError::Server(Box::new(err)))?; 25 ).fetch_one(&state.db).await.map_err(|err| WebolError::Server(Box::new(err)))?;
28 26
29 info!("starting {}", device.id); 27 info!("starting {}", device.id);
30 28
31 let bind_addr = SETTINGS 29 let bind_addr = SETTINGS
32 .get_string("bindaddr") 30 .get_string("bindaddr")
33 .map_err(|err| StartError::Server(Box::new(err)))?; 31 .map_err(|err| WebolError::Server(Box::new(err)))?;
34 32
35 let _ = send_packet( 33 let _ = send_packet(
36 &bind_addr.parse().map_err(|err| StartError::Server(Box::new(err)))?, 34 &bind_addr.parse().map_err(|err| WebolError::Server(Box::new(err)))?,
37 &device.broadcast_addr.parse().map_err(|err| StartError::Server(Box::new(err)))?, 35 &device.broadcast_addr.parse().map_err(|err| WebolError::Server(Box::new(err)))?,
38 create_buffer(&device.mac).map_err(|err| StartError::Server(Box::new(err)))? 36 create_buffer(&device.mac).map_err(|err| WebolError::Server(Box::new(err)))?
39 ).map_err(|err| StartError::Server(Box::new(err))); 37 ).map_err(|err| WebolError::Server(Box::new(err)));
40 Ok(Json(json!(StartResponse { id: device.id, boot: true }))) 38 Ok(Json(json!(StartResponse { id: device.id, boot: true })))
41 } else { 39 } else {
42 Err(StartError::Generic) 40 Err(WebolError::Generic)
43 } 41 }
44} 42}
45 43
@@ -53,28 +51,4 @@ pub struct StartPayload {
53struct StartResponse { 51struct StartResponse {
54 id: String, 52 id: String,
55 boot: bool, 53 boot: bool,
56}
57
58pub enum StartError {
59 Auth(AuthError),
60 Generic,
61 Server(Box<dyn Error>),
62}
63
64impl IntoResponse for StartError {
65 fn into_response(self) -> Response {
66 let (status, error_message) = match self {
67 StartError::Auth(err) => err.get(),
68 StartError::Generic => (StatusCode::INTERNAL_SERVER_ERROR, ""),
69 StartError::Server(err) => {
70 error!("server error: {}", err.to_string());
71 (StatusCode::INTERNAL_SERVER_ERROR, "Server Error")
72 },
73
74 };
75 let body = Json(json!({
76 "error": error_message,
77 }));
78 (status, body).into_response()
79 }
80} \ No newline at end of file 54} \ No newline at end of file