feat(cof): Add special role and motorized fixture support
This commit is contained in:
parent
3d1673d3f5
commit
799c891475
5 changed files with 112 additions and 41 deletions
|
@ -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>(
|
||||
|
@ -28,9 +32,10 @@ fn check_token(token: &str, jwt: &str) -> Option<User> {
|
|||
match decoded_token {
|
||||
Ok(token_data) => {
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue