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 {
sub: String,
scope: String,
user: User,
user: String,
is_cof: bool,
}
#[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> {
let decoded_token = decode::<Claims>(
@ -27,10 +31,11 @@ fn check_token(token: &str, jwt: &str) -> Option<User> {
);
match decoded_token {
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" {
tracing::info!("Successful auth {user:?}");
Some(user)
Some(User { name: user, is_cof })
} else {
tracing::debug!("Failed auth: {user:?} don't have modify scope");
None

View file

@ -1,5 +1,5 @@
use crate::authorization::User;
use crate::model::{DMXArray, DMXAtom, DMXColor, DB};
use crate::model::{ColorArray, DMXAtom, DMXBeam, DMXBeamChange, DMXColorAtom, DMXColor, DB};
use axum::{
debug_handler,
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(
State(db): State<DB>,
Extension(user): Extension<User>,
Json(body): Json<Vec<DMXAtom>>,
Json(body): Json<Vec<DMXColorAtom>>,
) -> Result<(), StatusCode> {
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){
return Err(StatusCode::TOO_MANY_REQUESTS);
}
for i in &body {
check_id(i.address, &lock.dmx)?;
check_id(i.address, &lock.dmx.colors)?;
}
for i in &body {
lock.dmx[i.address] = i.value;
match db.static_state.color_change_channel.send(*i) {
lock.dmx.colors[i.address] = i.value;
match db.static_state.change_channel.send(DMXAtom::Color(*i)) {
Ok(_) => (),
Err(_) => (),
}
@ -56,8 +56,8 @@ pub async fn get_value_handler(
State(db): State<DB>,
) -> Result<impl IntoResponse, StatusCode> {
let lock = db.mut_state.read().await;
check_id(id, &lock.dmx)?;
Ok(Json(lock.dmx[id]))
check_id(id, &lock.dmx.colors)?;
Ok(Json(lock.dmx.colors[id]))
}
#[debug_handler]
@ -67,12 +67,12 @@ pub async fn edit_value_handler(
Json(body): Json<DMXColor>,
) -> Result<(), StatusCode> {
let mut lock = db.mut_state.write().await;
check_id(id, &lock.dmx)?;
lock.dmx[id] = body;
check_id(id, &lock.dmx.colors)?;
lock.dmx.colors[id] = body;
match db
.static_state
.color_change_channel
.send(DMXAtom::new(id, body))
.change_channel
.send(DMXAtom::Color(DMXColorAtom::new(id, body)))
{
Ok(_) => (),
Err(_) => (),
@ -81,20 +81,55 @@ pub async fn edit_value_handler(
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]
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 motor: DMXBeam;
{
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)| {
Ok(DMXAtom {
Ok(DMXAtom::Color(DMXColorAtom {
address: i,
value: x,
})
});
}))
}).chain(std::iter::once(Ok(DMXAtom::Motor(motor))));
let init = stream::iter(init_data);
let stream = init
.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() {
return Err(StatusCode::NOT_FOUND);
};

View file

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

View file

@ -60,6 +60,13 @@ pub fn create_router() -> Router {
.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)
.with_state(db)
}

View file

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