use std::sync::Mutex;
use actix_web::{
delete,
error::{ErrorConflict, ErrorNotFound, ErrorServiceUnavailable},
get, patch, post, put,
web::{Data, Json, Path},
HttpResponse, Responder, Result,
};
use log::error;
use uuid::Uuid;
use crate::{
models::{Light, LightRequest, LightingResponse},
storage::Storage,
worker::Worker,
};
#[utoipa::path(
request_body = Light,
responses(
(status = 200, description = "OK", body = Uuid),
(status = 409, description = "Conflict", body = String),
),
params(
("id", description = "Room ID")
)
)]
#[post("/v1/room/{id}/lights")]
async fn create(
id: Path<Uuid>,
req: Json<Light>,
storage: Data<Mutex<Storage>>,
) -> Result<impl Responder> {
let id = id.into_inner();
let light = req.into_inner();
let mut data = storage.lock().unwrap();
if let Ok(id) = data.new_light(&id, light) {
Ok(HttpResponse::Ok().json(id))
} else {
Err(ErrorConflict("Failed to create new light"))
}
}
#[utoipa::path(
request_body = LightRequest,
responses(
(status = 204, description = "OK"),
(status = 404, description = "Not Found", body = String),
(status = 503, description = "Unavailable", body = String),
),
params(
("id", description = "Room ID"),
),
)]
#[put("/v1/room/{id}/lights")]
async fn update_room(
id: Path<Uuid>,
req: Json<LightRequest>,
storage: Data<Mutex<Storage>>,
worker: Data<Mutex<Worker>>,
) -> Result<impl Responder> {
let id = id.into_inner();
let req = req.into_inner();
let room = {
let data = storage.lock().unwrap();
match data.read(&id) {
Some(room) => room,
None => return Err(ErrorNotFound(format!("No such room: {}", id))),
}
};
if let Some(lights) = room.list() {
let mut worker = worker.lock().unwrap();
for light_id in lights {
if let Some(light) = room.read(light_id) {
if worker.create_task(light.ip(), req.clone()).is_err() {
return Err(ErrorServiceUnavailable("No available workers".to_string()));
}
}
}
Ok(HttpResponse::Ok())
} else {
Err(ErrorNotFound(format!("No lights in room: {}", id)))
}
}
#[utoipa::path(
request_body = LightRequest,
responses(
(status = 204, description = "OK"),
(status = 404, description = "Not Found", body = String),
(status = 503, description = "Unavailable", body = String),
),
params(
("id", description = "Room ID"),
("light_id", description = "Light ID"),
)
)]
#[put("/v1/room/{id}/light/{light_id}")]
async fn update(
ids: Path<(Uuid, Uuid)>,
req: Json<LightRequest>,
storage: Data<Mutex<Storage>>,
worker: Data<Mutex<Worker>>,
) -> Result<impl Responder> {
let (room_id, light_id) = ids.into_inner();
let req = req.into_inner();
let room = {
let data = storage.lock().unwrap();
match data.read(&room_id) {
Some(room) => room,
None => return Err(ErrorNotFound(format!("No such room: {}", room_id))),
}
};
if let Some(light) = room.read(&light_id) {
let mut worker = worker.lock().unwrap();
match worker.create_task(light.ip(), req) {
Ok(_) => Ok(HttpResponse::Ok()),
Err(_) => Err(ErrorServiceUnavailable("No available workers".to_string())),
}
} else {
Err(ErrorNotFound(format!("No such light: {}", light_id)))
}
}
#[utoipa::path(
responses(
(status = 200, description = "OK", body = LightStatus),
(status = 404, description = "Not Found", body = String),
(status = 503, description = "Unavailable", body = String),
),
params(
("id", description = "Room ID"),
("light_id", description = "Light ID"),
)
)]
#[get("/v1/room/{id}/light/{light_id}/status")]
async fn status(
ids: Path<(Uuid, Uuid)>,
data: Data<Mutex<Storage>>,
worker: Data<Mutex<Worker>>,
) -> Result<impl Responder> {
let (room_id, light_id) = ids.into_inner();
let room = {
let data = data.lock().unwrap();
match data.read(&room_id) {
Some(room) => room,
None => return Err(ErrorNotFound(format!("No such room: {}", room_id))),
}
};
if let Some(light) = room.read(&light_id) {
match light.get_status() {
Ok(status) => {
let mut worker = worker.lock().unwrap();
if let Err(e) =
worker.queue_update(LightingResponse::status(light.ip(), status.clone()))
{
error!("Failed to queue write: {}", e);
}
Ok(HttpResponse::Ok().json(status))
}
Err(e) => Err(ErrorServiceUnavailable(format!(
"Failed to fetch status: {}",
e
))),
}
} else {
Err(ErrorNotFound(format!("No such light: {}", light_id)))
}
}
#[utoipa::path(
request_body = Light,
responses(
(status = 204, description = "OK"),
(status = 404, description = "Not Found", body = String),
),
params(
("id", description = "Room ID"),
("light_id", description = "Light ID"),
)
)]
#[patch("/v1/room/{id}/light/{light_id}")]
async fn update_light(
ids: Path<(Uuid, Uuid)>,
light: Json<Light>,
storage: Data<Mutex<Storage>>,
) -> Result<impl Responder> {
let (room_id, light_id) = ids.into_inner();
let light = light.into_inner();
let mut data = storage.lock().unwrap();
if data.update_light(&room_id, &light_id, &light).is_ok() {
Ok(HttpResponse::Ok())
} else {
Err(ErrorNotFound(format!("Not found: {}", room_id)))
}
}
#[utoipa::path(
responses(
(status = 204, description = "OK"),
(status = 404, description = "Not Found", body = String),
),
params(
("id", description = "Room ID"),
("light_id", description = "Light ID")
)
)]
#[delete("/v1/room/{id}/light/{light_id}")]
async fn destroy(ids: Path<(Uuid, Uuid)>, storage: Data<Mutex<Storage>>) -> Result<impl Responder> {
let (room_id, light_id) = ids.into_inner();
let mut data = storage.lock().unwrap();
if data.delete_light(&room_id, &light_id).is_ok() {
Ok(HttpResponse::Ok())
} else {
Err(ErrorNotFound(format!(
"Not found: {} in room {}",
light_id, room_id
)))
}
}