feat(cof): Add special role and motorized fixture support

This commit is contained in:
sinavir 2024-10-10 13:27:37 +02:00
parent 3d1673d3f5
commit 799c891475
5 changed files with 112 additions and 41 deletions

View file

@ -13,11 +13,15 @@ use serde::{Deserialize, Serialize};
struct Claims { struct Claims {
sub: String, sub: String,
scope: String, scope: String,
user: User, user: String,
is_cof: bool,
} }
#[derive(Eq, PartialEq, Hash, Debug, Serialize, Deserialize, Clone)] #[derive(Eq, PartialEq, Hash, Debug, Serialize, Deserialize, Clone)]
pub struct User(String); pub struct User {
name: String,
pub is_cof: bool,
}
fn check_token(token: &str, jwt: &str) -> Option<User> { fn check_token(token: &str, jwt: &str) -> Option<User> {
let decoded_token = decode::<Claims>( let decoded_token = decode::<Claims>(
@ -28,9 +32,10 @@ fn check_token(token: &str, jwt: &str) -> Option<User> {
match decoded_token { match decoded_token {
Ok(token_data) => { Ok(token_data) => {
let user = token_data.claims.user; let user = token_data.claims.user;
let is_cof =token_data.claims.is_cof;
if token_data.claims.scope == "modify" { if token_data.claims.scope == "modify" {
tracing::info!("Successful auth {user:?}"); tracing::info!("Successful auth {user:?}");
Some(user) Some(User { name: user, is_cof })
} else { } else {
tracing::debug!("Failed auth: {user:?} don't have modify scope"); tracing::debug!("Failed auth: {user:?} don't have modify scope");
None None

View file

@ -1,5 +1,5 @@
use crate::authorization::User; use crate::authorization::User;
use crate::model::{DMXArray, DMXAtom, DMXColor, DB}; use crate::model::{ColorArray, DMXAtom, DMXBeam, DMXBeamChange, DMXColorAtom, DMXColor, DB};
use axum::{ use axum::{
debug_handler, debug_handler,
extract::{Path, State}, extract::{Path, State},
@ -30,18 +30,18 @@ pub async fn list_values_handler(State(db): State<DB>) -> impl IntoResponse {
pub async fn batch_edit_value_handler( pub async fn batch_edit_value_handler(
State(db): State<DB>, State(db): State<DB>,
Extension(user): Extension<User>, Extension(user): Extension<User>,
Json(body): Json<Vec<DMXAtom>>, Json(body): Json<Vec<DMXColorAtom>>,
) -> Result<(), StatusCode> { ) -> Result<(), StatusCode> {
let mut lock = db.mut_state.write().await; let mut lock = db.mut_state.write().await;
if lock.ratelimit_info.get(&user).map(|d| d.elapsed() <= Duration::from_millis(500)).unwrap_or(false){ if lock.ratelimit_info.get(&user).map(|d| d.elapsed() <= Duration::from_millis(500)).unwrap_or(false){
return Err(StatusCode::TOO_MANY_REQUESTS); return Err(StatusCode::TOO_MANY_REQUESTS);
} }
for i in &body { for i in &body {
check_id(i.address, &lock.dmx)?; check_id(i.address, &lock.dmx.colors)?;
} }
for i in &body { for i in &body {
lock.dmx[i.address] = i.value; lock.dmx.colors[i.address] = i.value;
match db.static_state.color_change_channel.send(*i) { match db.static_state.change_channel.send(DMXAtom::Color(*i)) {
Ok(_) => (), Ok(_) => (),
Err(_) => (), Err(_) => (),
} }
@ -56,8 +56,8 @@ pub async fn get_value_handler(
State(db): State<DB>, State(db): State<DB>,
) -> Result<impl IntoResponse, StatusCode> { ) -> Result<impl IntoResponse, StatusCode> {
let lock = db.mut_state.read().await; let lock = db.mut_state.read().await;
check_id(id, &lock.dmx)?; check_id(id, &lock.dmx.colors)?;
Ok(Json(lock.dmx[id])) Ok(Json(lock.dmx.colors[id]))
} }
#[debug_handler] #[debug_handler]
@ -67,12 +67,12 @@ pub async fn edit_value_handler(
Json(body): Json<DMXColor>, Json(body): Json<DMXColor>,
) -> Result<(), StatusCode> { ) -> Result<(), StatusCode> {
let mut lock = db.mut_state.write().await; let mut lock = db.mut_state.write().await;
check_id(id, &lock.dmx)?; check_id(id, &lock.dmx.colors)?;
lock.dmx[id] = body; lock.dmx.colors[id] = body;
match db match db
.static_state .static_state
.color_change_channel .change_channel
.send(DMXAtom::new(id, body)) .send(DMXAtom::Color(DMXColorAtom::new(id, body)))
{ {
Ok(_) => (), Ok(_) => (),
Err(_) => (), Err(_) => (),
@ -81,20 +81,55 @@ pub async fn edit_value_handler(
Ok(()) Ok(())
} }
#[debug_handler]
pub async fn get_motor_value_handler(
State(db): State<DB>,
) -> Result<impl IntoResponse, StatusCode> {
let lock = db.mut_state.read().await;
Ok(Json(lock.dmx.motor))
}
#[debug_handler]
pub async fn edit_motor_value_handler(
State(db): State<DB>,
Extension(user): Extension<User>,
Json(body): Json<DMXBeamChange>,
) -> Result<(), StatusCode> {
if !user.is_cof {
return Err(StatusCode::FORBIDDEN);
}
let mut lock = db.mut_state.write().await;
lock.dmx.motor = DMXBeam {
pan: body.pan.unwrap_or(lock.dmx.motor.pan),
tilt: body.tilt.unwrap_or(lock.dmx.motor.tilt),
focus: body.focus.unwrap_or(lock.dmx.motor.focus),
};
let _ = db
.static_state
.change_channel
.send(DMXAtom::Motor(lock.dmx.motor))
;
Ok(())
}
#[debug_handler] #[debug_handler]
pub async fn sse_handler(State(db): State<DB>) -> impl IntoResponse { pub async fn sse_handler(State(db): State<DB>) -> impl IntoResponse {
let rx = db.static_state.color_change_channel.subscribe(); let rx = db.static_state.change_channel.subscribe();
let data: Vec<DMXColor>; let data: Vec<DMXColor>;
let motor: DMXBeam;
{ {
let lock = db.mut_state.read().await; let lock = db.mut_state.read().await;
data = lock.dmx.clone(); data = lock.dmx.colors.clone();
motor = lock.dmx.motor.clone();
} }
let init_data = data.into_iter().enumerate().map(|(i, x)| { let init_data = data.into_iter().enumerate().map(|(i, x)| {
Ok(DMXAtom { Ok(DMXAtom::Color(DMXColorAtom {
address: i, address: i,
value: x, value: x,
}) }))
}); }).chain(std::iter::once(Ok(DMXAtom::Motor(motor))));
let init = stream::iter(init_data); let init = stream::iter(init_data);
let stream = init let stream = init
.chain(stream::wrappers::BroadcastStream::new(rx)) .chain(stream::wrappers::BroadcastStream::new(rx))
@ -110,7 +145,7 @@ pub async fn sse_handler(State(db): State<DB>) -> impl IntoResponse {
) )
} }
fn check_id(id: usize, val: &DMXArray) -> Result<(), StatusCode> { fn check_id(id: usize, val: &ColorArray) -> Result<(), StatusCode> {
if id >= val.len() { if id >= val.len() {
return Err(StatusCode::NOT_FOUND); return Err(StatusCode::NOT_FOUND);
}; };

View file

@ -8,39 +8,66 @@ use std::sync::Arc;
use std::time::Instant; use std::time::Instant;
use tokio::sync::{broadcast, RwLock}; use tokio::sync::{broadcast, RwLock};
#[derive(Debug, Deserialize, Serialize, Copy, Clone)] #[derive(Debug, Default, Deserialize, Serialize, Copy, Clone)]
pub struct DMXColor { pub struct DMXColor {
pub red: u8, pub red: u8,
pub green: u8, pub green: u8,
pub blue: u8, pub blue: u8,
} }
pub type DMXArray = Vec<DMXColor>; #[derive(Debug, Default, Deserialize, Serialize, Copy, Clone)]
pub struct DMXBeam {
pub pan: u8,
pub tilt: u8,
pub focus: u8,
}
#[derive(Debug, Default, Deserialize, Serialize, Copy, Clone)]
pub struct DMXBeamChange {
pub pan: Option<u8>,
pub tilt: Option<u8>,
pub focus: Option<u8>,
}
pub type ColorArray = Vec<DMXColor>;
#[derive(Debug, Deserialize, Serialize, Copy, Clone)] #[derive(Debug, Deserialize, Serialize, Copy, Clone)]
pub struct DMXAtom { #[serde(tag = "type")]
pub enum DMXAtom {
Color(DMXColorAtom),
Motor(DMXBeam),
}
#[derive(Debug, Deserialize, Serialize, Copy, Clone)]
pub struct DMXColorAtom {
pub address: usize, pub address: usize,
pub value: DMXColor, pub value: DMXColor,
} }
impl DMXAtom { impl DMXColorAtom {
pub fn new(address: usize, value: DMXColor) -> DMXAtom { pub fn new(address: usize, value: DMXColor) -> DMXColorAtom {
DMXAtom { address, value } DMXColorAtom { address, value }
} }
} }
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct DMXState {
pub colors: ColorArray,
pub motor: DMXBeam,
}
pub struct AppState { pub struct AppState {
pub dmx: DMXArray, pub dmx: DMXState,
pub ratelimit_info: HashMap<User, Instant>, pub ratelimit_info: HashMap<User, Instant>,
} }
impl AppState { impl AppState {
pub fn new(size: usize, save_path: &str) -> AppState { pub fn new(size: usize, save_path: &str) -> AppState {
let data: Result<DMXArray, ()> = let data: Result<DMXState, ()> =
match fs::File::open(save_path).map(std::io::BufReader::new) { match fs::File::open(save_path).map(std::io::BufReader::new) {
Ok(read) => serde_json::from_reader(read) Ok(read) => serde_json::from_reader(read)
.map(|mut v: DMXArray| { .map(|mut v: DMXState| {
v.resize( v.colors.resize(
size, size,
DMXColor { DMXColor {
red: 0, red: 0,
@ -58,14 +85,10 @@ impl AppState {
Err(()) Err(())
} }
}; };
let dmx = data.unwrap_or(vec![ let dmx = data.unwrap_or(DMXState {
DMXColor { colors: vec![DMXColor::default(); size],
red: 0, motor: DMXBeam::default(),
green: 0, });
blue: 0,
};
size
]);
AppState { AppState {
dmx, dmx,
@ -76,7 +99,7 @@ impl AppState {
pub struct StaticState { pub struct StaticState {
pub jwt_key: String, pub jwt_key: String,
pub color_change_channel: broadcast::Sender<DMXAtom>, pub change_channel: broadcast::Sender<DMXAtom>,
pub save_path: String, pub save_path: String,
} }
@ -100,7 +123,7 @@ pub fn make_db() -> DB {
.as_ref(), .as_ref(),
) )
.expect("an JSON string"), .expect("an JSON string"),
color_change_channel: broadcast::Sender::new(512), change_channel: broadcast::Sender::new(512),
save_path, save_path,
}, },
mut_state, mut_state,

View file

@ -60,6 +60,13 @@ pub fn create_router() -> Router {
.layer(middleware::from_fn_with_state(db.clone(), jwt_middleware)), .layer(middleware::from_fn_with_state(db.clone(), jwt_middleware)),
), ),
) )
.route(
"/api/motor",
get(handler::get_motor_value_handler).post(
handler::edit_motor_value_handler
.layer(middleware::from_fn_with_state(db.clone(), jwt_middleware)),
),
)
.layer(cors) .layer(cors)
.with_state(db) .with_state(db)
} }

View file

@ -47,6 +47,7 @@ class TokenView(LoginRequiredMixin, View):
"exp": datetime.now(tz=timezone.utc) + timedelta(hours=9), "exp": datetime.now(tz=timezone.utc) + timedelta(hours=9),
"sub": "ragb", "sub": "ragb",
"user": self.request.user.username, "user": self.request.user.username,
"is_cof": self.requests.user.groups.filter(name="cof").exists(),
"scope": "modify", "scope": "modify",
}, },
settings.JWT_SECRET, settings.JWT_SECRET,