Compare commits
2 Commits
251d245aeb
...
e135a34d4c
| Author | SHA1 | Date | |
|---|---|---|---|
| e135a34d4c | |||
| 9b093e6739 |
1642
Cargo.lock
generated
1642
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -6,7 +6,11 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
gpio-cdev = "0.6.0"
|
gpio-cdev = "0.6.0"
|
||||||
cron_tab = { version = "0.2", features = ["async"] }
|
cron_tab = { version = "0.2", features = ["async"] }
|
||||||
chrono = "0.4.42"
|
chrono = { version = "0.4.42", features = ["serde"] }
|
||||||
axum = { version = "0.8.8", features = ["macros"] }
|
axum = { version = "0.8.8", features = ["macros"] }
|
||||||
tokio = { version = "1.49.0", features = ["full"] }
|
tokio = { version = "1.49.0", features = ["full"] }
|
||||||
serde = { version = "1.0.228", features = ["derive"] }
|
serde = { version = "1.0.228", features = ["derive"] }
|
||||||
|
actix-web = "4.12.1"
|
||||||
|
paperclip = { version = "0.9.5", features = ["actix4"] }
|
||||||
|
apistos = { version = "0.6.0", features = ["swagger-ui"] }
|
||||||
|
schemars = { package = "apistos-schemars", version = "0.8" }
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
use axum::{Json, extract::State};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
use crate::{AppState};
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize)]
|
|
||||||
pub struct CreateAlarmRequest {
|
|
||||||
hour: String,
|
|
||||||
minute: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[axum::debug_handler]
|
|
||||||
pub async fn create_alarm(State(state): State<AppState>, Json(alarm): Json<CreateAlarmRequest>) -> String {
|
|
||||||
let result = state.scheduler.add_alarm(alarm.hour.as_str(), alarm.minute.as_str());
|
|
||||||
|
|
||||||
match result {
|
|
||||||
Ok(_) => "Alarm created".to_string(),
|
|
||||||
Err(e) => e.to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
pub mod alarm;
|
|
||||||
41
src/main.rs
41
src/main.rs
@@ -1,22 +1,17 @@
|
|||||||
use std::{
|
use std::sync::{Arc, Mutex};
|
||||||
sync::{Arc, Mutex},
|
|
||||||
};
|
|
||||||
|
|
||||||
use chrono::{DateTime, Local};
|
use actix_web::{App, HttpServer, web};
|
||||||
|
use apistos::{SwaggerUIConfig, app::{BuildConfig, OpenApiWrapper}, info::Info, spec::Spec};
|
||||||
use cron_tab::Cron;
|
use cron_tab::Cron;
|
||||||
use gpio_cdev::Chip;
|
use gpio_cdev::Chip;
|
||||||
|
|
||||||
use crate::{ringer::BeepRinger, scheduler::Scheduler};
|
use crate::{ringer::BeepRinger, scheduler::Scheduler};
|
||||||
|
|
||||||
mod handler;
|
|
||||||
mod router;
|
|
||||||
mod scheduler;
|
mod scheduler;
|
||||||
mod ringer;
|
mod ringer;
|
||||||
|
mod types;
|
||||||
|
mod resources;
|
||||||
|
|
||||||
struct Alarm {
|
|
||||||
enabled: bool,
|
|
||||||
time: DateTime<Local>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct AppState {
|
struct AppState {
|
||||||
@@ -40,16 +35,30 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
app_state.scheduler.start();
|
app_state.scheduler.start();
|
||||||
|
|
||||||
start_server(app_state).await;
|
let _ = start_actix_server(app_state).await;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async fn start_server(app_state: AppState) {
|
async fn start_actix_server(app_state: AppState) -> std::io::Result<()> {
|
||||||
let router = router::router(app_state);
|
let _ = HttpServer::new(move || {
|
||||||
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
|
let spec = Spec {
|
||||||
|
info: Info {
|
||||||
|
title: "Snooze Pal".to_string(),
|
||||||
|
version: "0.0.1".to_string(),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
println!("Listening on http://0.0.0.0:8080");
|
App::new()
|
||||||
axum::serve(listener, router).await.unwrap();
|
.document(spec)
|
||||||
|
.service(resources::v1())
|
||||||
|
.app_data(web::Data::new(app_state.clone()))
|
||||||
|
.build_with("/openapi.json", BuildConfig::default().with(SwaggerUIConfig::new(&"/swagger")))
|
||||||
|
|
||||||
|
}).bind("0.0.0.0:8080")?.run().await;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
7
src/resources/alarm/mod.rs
Normal file
7
src/resources/alarm/mod.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
mod post;
|
||||||
|
use apistos::web;
|
||||||
|
|
||||||
|
pub fn resource() -> web::Resource {
|
||||||
|
web::resource("/alarm")
|
||||||
|
.route(web::post().to(post::post))
|
||||||
|
}
|
||||||
56
src/resources/alarm/post.rs
Normal file
56
src/resources/alarm/post.rs
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
use std::{fmt::Display};
|
||||||
|
|
||||||
|
use actix_web::{ResponseError, web::{Data, Json}};
|
||||||
|
use apistos::{ApiComponent, ApiErrorComponent, actix::CreatedJson};
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Serialize, Deserialize};
|
||||||
|
|
||||||
|
use crate::AppState;
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, JsonSchema, ApiComponent)]
|
||||||
|
pub struct RequestBody {
|
||||||
|
time: DateTime<Local>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, JsonSchema, ApiComponent)]
|
||||||
|
pub struct SuccessResponse {
|
||||||
|
pub time: DateTime<Local>,
|
||||||
|
pub enabled: bool
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, ApiErrorComponent)]
|
||||||
|
#[openapi_error(status(code = 500, description = "The alarm could not be created"))]
|
||||||
|
pub enum ErrorResponse {
|
||||||
|
InternalError(String)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for ErrorResponse {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
ErrorResponse::InternalError(e) => write!(f, "Internal error: {}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ResponseError for ErrorResponse {
|
||||||
|
fn status_code(&self) -> actix_web::http::StatusCode {
|
||||||
|
match self {
|
||||||
|
ErrorResponse::InternalError(_) => actix_web::http::StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[apistos::api_operation(
|
||||||
|
summary = "Add new alarm",
|
||||||
|
description = r###"Creates new Alarm"###,
|
||||||
|
error_code= 500,
|
||||||
|
)]
|
||||||
|
pub async fn post(data: Data<AppState>, body: Json<RequestBody>) -> Result<CreatedJson<SuccessResponse>, ErrorResponse> {
|
||||||
|
let result = data.scheduler.add_alarm(body.time);
|
||||||
|
|
||||||
|
match result {
|
||||||
|
Ok(alarm) => Ok(CreatedJson(SuccessResponse { time: alarm.time, enabled: alarm.enabled })),
|
||||||
|
Err(e) => Err(ErrorResponse::InternalError(e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
5
src/resources/mod.rs
Normal file
5
src/resources/mod.rs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
mod alarm;
|
||||||
|
|
||||||
|
pub fn v1() -> apistos::web::Scope {
|
||||||
|
apistos::web::scope("/v1").service(alarm::resource())
|
||||||
|
}
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
use crate::{handler::alarm::create_alarm, AppState};
|
|
||||||
use axum::{routing::post, Router};
|
|
||||||
|
|
||||||
pub fn router() -> Router<AppState> {
|
|
||||||
Router::new().route("/", post(create_alarm))
|
|
||||||
}
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
use axum::Router;
|
|
||||||
|
|
||||||
mod alarm;
|
|
||||||
|
|
||||||
pub fn router(app_state: crate::AppState) -> Router {
|
|
||||||
Router::new()
|
|
||||||
.nest("/alarm", alarm::router())
|
|
||||||
.with_state(app_state)
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,17 @@
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use chrono::Local;
|
use chrono::{DateTime, Local, Timelike};
|
||||||
use cron_tab::Cron;
|
use cron_tab::Cron;
|
||||||
|
|
||||||
use crate::ringer::Ringer;
|
use crate::ringer::Ringer;
|
||||||
|
use crate::types::Alarm;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Scheduler<T: Ringer + 'static> {
|
pub struct Scheduler<T: Ringer + 'static> {
|
||||||
ringer: Arc<Mutex<T>>,
|
ringer: Arc<Mutex<T>>,
|
||||||
cron: Arc<Mutex<Cron<Local>>>,
|
cron: Arc<Mutex<Cron<Local>>>,
|
||||||
alarms: Arc<Mutex<Vec<String>>>,
|
alarms: Arc<Mutex<Vec<Alarm>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Ringer> Scheduler<T> {
|
impl<T: Ringer> Scheduler<T> {
|
||||||
@@ -39,13 +41,15 @@ impl<T: Ringer> Scheduler<T> {
|
|||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_alarm(&self, hour: &str, minute: &str) -> Result<(), String> {
|
pub fn add_alarm(&self, time: DateTime<Local>) -> Result<Alarm, String> {
|
||||||
let mut alarms = self.alarms.lock().map_err(|e| e.to_string())?;
|
let mut alarms = self.alarms.lock().map_err(|e| e.to_string())?;
|
||||||
let cron_schedule = format!("{} {} {} * * *", "*", minute, hour);
|
let cron_schedule = format!("{} {} {} * * *", "*", time.minute(), time.hour());
|
||||||
alarms.push(cron_schedule.clone());
|
let alarm = Alarm::new(true, time);
|
||||||
|
alarms.push(alarm.clone());
|
||||||
|
self.schedule(&cron_schedule).map_err(|e| e.to_string())?;
|
||||||
println!("Added alarm {}", cron_schedule);
|
println!("Added alarm {}", cron_schedule);
|
||||||
|
|
||||||
Ok(())
|
Ok(alarm)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn start(&self) {
|
pub fn start(&self) {
|
||||||
|
|||||||
16
src/types.rs
Normal file
16
src/types.rs
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
use apistos::ApiComponent;
|
||||||
|
use chrono::{DateTime, Local};
|
||||||
|
use schemars::JsonSchema;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Deserialize, Clone, JsonSchema, ApiComponent)]
|
||||||
|
pub struct Alarm {
|
||||||
|
pub enabled: bool,
|
||||||
|
pub time: DateTime<Local>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Alarm {
|
||||||
|
pub fn new(enabled: bool, time: DateTime<Local>) -> Self {
|
||||||
|
Self { enabled, time }
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user