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 {
|
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>(
|
||||||
|
@ -27,10 +31,11 @@ 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
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue