summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorfx <[email protected]>2023-10-09 17:26:59 +0200
committerfx <[email protected]>2023-10-09 17:26:59 +0200
commit3e6a72428824c5a50a873a4284b86d0a9e47a778 (patch)
tree7f3594f4068a8009210039bc33e0205a672828f7
parent732c487d3dab4af9fc561527591d3d56299e39f2 (diff)
downloadwebol-3e6a72428824c5a50a873a4284b86d0a9e47a778.tar
webol-3e6a72428824c5a50a873a4284b86d0a9e47a778.tar.gz
webol-3e6a72428824c5a50a873a4284b86d0a9e47a778.zip
db int for api
-rw-r--r--.env2
-rw-r--r--.sqlx/query-3ed66b56b7e78a6b6bad9d49c49607520a8d09f902b996467dddf58737a3757f.json16
-rw-r--r--.sqlx/query-6a81887a8b44b527ad04c8963d5186b764ec9125d597f9fba566e280bd09d352.json36
-rw-r--r--.sqlx/query-bb9c6a42084b92c339a85f9b1c094959a49c6b7383f1a0b49df242e2720a9185.json (renamed from .sqlx/query-035eea2a3ffbc6fcf5e1646f4e13e879b6e13325e38cd10d5ee160c5aded2caf.json)16
-rw-r--r--Cargo.lock1
-rw-r--r--Cargo.toml2
-rw-r--r--docker-compose.yml11
-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
13 files changed, 244 insertions, 56 deletions
diff --git a/.env b/.env
index 814cb3d..4516d34 100644
--- a/.env
+++ b/.env
@@ -1,2 +1,2 @@
1DATABASE_URL="sqlite:devices.sqlite" 1DATABASE_URL="postgres://postgres:postgres@localhost:5432/webol"
2SQLX_OFFLINE_DIR="./.sqlx" \ No newline at end of file 2SQLX_OFFLINE_DIR="./.sqlx" \ No newline at end of file
diff --git a/.sqlx/query-3ed66b56b7e78a6b6bad9d49c49607520a8d09f902b996467dddf58737a3757f.json b/.sqlx/query-3ed66b56b7e78a6b6bad9d49c49607520a8d09f902b996467dddf58737a3757f.json
new file mode 100644
index 0000000..feedac8
--- /dev/null
+++ b/.sqlx/query-3ed66b56b7e78a6b6bad9d49c49607520a8d09f902b996467dddf58737a3757f.json
@@ -0,0 +1,16 @@
1{
2 "db_name": "PostgreSQL",
3 "query": "\n INSERT INTO devices (id, mac, broadcast_addr)\n VALUES ($1, $2, $3);\n ",
4 "describe": {
5 "columns": [],
6 "parameters": {
7 "Left": [
8 "Text",
9 "Text",
10 "Text"
11 ]
12 },
13 "nullable": []
14 },
15 "hash": "3ed66b56b7e78a6b6bad9d49c49607520a8d09f902b996467dddf58737a3757f"
16}
diff --git a/.sqlx/query-6a81887a8b44b527ad04c8963d5186b764ec9125d597f9fba566e280bd09d352.json b/.sqlx/query-6a81887a8b44b527ad04c8963d5186b764ec9125d597f9fba566e280bd09d352.json
new file mode 100644
index 0000000..89f6bbe
--- /dev/null
+++ b/.sqlx/query-6a81887a8b44b527ad04c8963d5186b764ec9125d597f9fba566e280bd09d352.json
@@ -0,0 +1,36 @@
1{
2 "db_name": "PostgreSQL",
3 "query": "\n UPDATE devices\n SET mac = $1, broadcast_addr = $2 WHERE id = $3\n RETURNING id, mac, broadcast_addr;\n ",
4 "describe": {
5 "columns": [
6 {
7 "ordinal": 0,
8 "name": "id",
9 "type_info": "Text"
10 },
11 {
12 "ordinal": 1,
13 "name": "mac",
14 "type_info": "Text"
15 },
16 {
17 "ordinal": 2,
18 "name": "broadcast_addr",
19 "type_info": "Text"
20 }
21 ],
22 "parameters": {
23 "Left": [
24 "Text",
25 "Text",
26 "Text"
27 ]
28 },
29 "nullable": [
30 false,
31 false,
32 false
33 ]
34 },
35 "hash": "6a81887a8b44b527ad04c8963d5186b764ec9125d597f9fba566e280bd09d352"
36}
diff --git a/.sqlx/query-035eea2a3ffbc6fcf5e1646f4e13e879b6e13325e38cd10d5ee160c5aded2caf.json b/.sqlx/query-bb9c6a42084b92c339a85f9b1c094959a49c6b7383f1a0b49df242e2720a9185.json
index b1e99f6..efd2830 100644
--- a/.sqlx/query-035eea2a3ffbc6fcf5e1646f4e13e879b6e13325e38cd10d5ee160c5aded2caf.json
+++ b/.sqlx/query-bb9c6a42084b92c339a85f9b1c094959a49c6b7383f1a0b49df242e2720a9185.json
@@ -1,26 +1,28 @@
1{ 1{
2 "db_name": "SQLite", 2 "db_name": "PostgreSQL",
3 "query": "\n SELECT id, mac, broadcast_addr\n FROM devices\n WHERE id = ?1;\n ", 3 "query": "\n SELECT id, mac, broadcast_addr\n FROM devices\n WHERE id = $1;\n ",
4 "describe": { 4 "describe": {
5 "columns": [ 5 "columns": [
6 { 6 {
7 "name": "id",
8 "ordinal": 0, 7 "ordinal": 0,
8 "name": "id",
9 "type_info": "Text" 9 "type_info": "Text"
10 }, 10 },
11 { 11 {
12 "name": "mac",
13 "ordinal": 1, 12 "ordinal": 1,
13 "name": "mac",
14 "type_info": "Text" 14 "type_info": "Text"
15 }, 15 },
16 { 16 {
17 "name": "broadcast_addr",
18 "ordinal": 2, 17 "ordinal": 2,
18 "name": "broadcast_addr",
19 "type_info": "Text" 19 "type_info": "Text"
20 } 20 }
21 ], 21 ],
22 "parameters": { 22 "parameters": {
23 "Right": 1 23 "Left": [
24 "Text"
25 ]
24 }, 26 },
25 "nullable": [ 27 "nullable": [
26 false, 28 false,
@@ -28,5 +30,5 @@
28 false 30 false
29 ] 31 ]
30 }, 32 },
31 "hash": "035eea2a3ffbc6fcf5e1646f4e13e879b6e13325e38cd10d5ee160c5aded2caf" 33 "hash": "bb9c6a42084b92c339a85f9b1c094959a49c6b7383f1a0b49df242e2720a9185"
32} 34}
diff --git a/Cargo.lock b/Cargo.lock
index 6e4b450..7f5521b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1547,6 +1547,7 @@ dependencies = [
1547 "sha2", 1547 "sha2",
1548 "sqlx-core", 1548 "sqlx-core",
1549 "sqlx-mysql", 1549 "sqlx-mysql",
1550 "sqlx-postgres",
1550 "sqlx-sqlite", 1551 "sqlx-sqlite",
1551 "syn 1.0.109", 1552 "syn 1.0.109",
1552 "tempfile", 1553 "tempfile",
diff --git a/Cargo.toml b/Cargo.toml
index 3a1dc44..e772810 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,4 +15,4 @@ serde = { version = "1.0.188", features = ["derive"] }
15serde_json = "1.0.107" 15serde_json = "1.0.107"
16config = "0.13.3" 16config = "0.13.3"
17once_cell = "1.18.0" 17once_cell = "1.18.0"
18sqlx = { version = "0.7.1", features = ["sqlite", "runtime-tokio"]} 18sqlx = { version = "0.7.1", features = ["postgres", "runtime-tokio"]}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..1980356
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,11 @@
1version: '3.1'
2
3services:
4 db:
5 image: postgres
6 restart: no
7 environment:
8 POSTGRES_PASSWORD: postgres
9
10 ports:
11 - "5432:5432"
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