Use enum for Role everywhere, fix time shift, fix #433, get config also as normal user
This commit is contained in:
parent
809b649226
commit
7d3173533f
17
Cargo.lock
generated
17
Cargo.lock
generated
@ -565,9 +565,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "asynchronous-codec"
|
||||
version = "0.6.2"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4057f2c32adbb2fc158e22fb38433c8e9bbf76b75a4732c7c0cbaf695fb65568"
|
||||
checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"futures-sink",
|
||||
@ -1134,7 +1134,7 @@ checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5"
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout"
|
||||
version = "0.20.0-beta4"
|
||||
version = "0.20.0-beta5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
@ -1156,7 +1156,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout-api"
|
||||
version = "0.20.0-beta4"
|
||||
version = "0.20.0-beta5"
|
||||
dependencies = [
|
||||
"actix-files",
|
||||
"actix-multipart",
|
||||
@ -1192,7 +1192,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ffplayout-lib"
|
||||
version = "0.20.0-beta4"
|
||||
version = "0.20.0-beta5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
@ -3189,7 +3189,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tests"
|
||||
version = "0.20.0-beta4"
|
||||
version = "0.20.0-beta5"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"crossbeam-channel",
|
||||
@ -3755,8 +3755,9 @@ checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"
|
||||
|
||||
[[package]]
|
||||
name = "zeromq"
|
||||
version = "0.3.3"
|
||||
source = "git+https://github.com/zeromq/zmq.rs.git#7baeeffde9e4cb9741d1841cfdee5f00f354b578"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2db35fbc7d9082d39a85c9831ec5dc7b7b135038d2f00bb5ff2a4c0275893da1"
|
||||
dependencies = [
|
||||
"async-std",
|
||||
"async-trait",
|
||||
|
@ -4,7 +4,7 @@ default-members = ["ffplayout-api", "ffplayout-engine", "tests"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.20.0-beta4"
|
||||
version = "0.20.0-beta5"
|
||||
license = "GPL-3.0"
|
||||
repository = "https://github.com/ffplayout/ffplayout"
|
||||
authors = ["Jonathan Baecker <jonbae77@gmail.com>"]
|
||||
|
@ -1,11 +1,19 @@
|
||||
use std::{env, path::Path};
|
||||
|
||||
use static_files::NpmBuild;
|
||||
|
||||
fn main() -> std::io::Result<()> {
|
||||
NpmBuild::new("../ffplayout-frontend")
|
||||
.install()?
|
||||
.run("generate")?
|
||||
.target("../ffplayout-frontend/.output/public")
|
||||
.change_detection()
|
||||
.to_resource_dir()
|
||||
.build()
|
||||
let gen_path = Path::new(&env::var("OUT_DIR").unwrap()).join("generated.rs");
|
||||
|
||||
if Ok("release".to_owned()) == env::var("PROFILE") || !gen_path.is_file() {
|
||||
NpmBuild::new("../ffplayout-frontend")
|
||||
.install()?
|
||||
.run("generate")?
|
||||
.target("../ffplayout-frontend/.output/public")
|
||||
.change_detection()
|
||||
.to_resource_dir()
|
||||
.build()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ use chrono::{Duration, Utc};
|
||||
use jsonwebtoken::{self, DecodingKey, EncodingKey, Header, Validation};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::utils::GlobalSettings;
|
||||
use crate::utils::{GlobalSettings, Role};
|
||||
|
||||
// Token lifetime
|
||||
const JWT_EXPIRATION_DAYS: i64 = 7;
|
||||
@ -13,12 +13,12 @@ const JWT_EXPIRATION_DAYS: i64 = 7;
|
||||
pub struct Claims {
|
||||
pub id: i32,
|
||||
pub username: String,
|
||||
pub role: String,
|
||||
pub role: Role,
|
||||
exp: i64,
|
||||
}
|
||||
|
||||
impl Claims {
|
||||
pub fn new(id: i32, username: String, role: String) -> Self {
|
||||
pub fn new(id: i32, username: String, role: Role) -> Self {
|
||||
Self {
|
||||
id,
|
||||
username,
|
||||
|
@ -170,7 +170,7 @@ pub async fn login(pool: web::Data<Pool<Sqlite>>, credentials: web::Json<User>)
|
||||
{
|
||||
let role = handles::select_role(&conn, &user.role_id.unwrap_or_default())
|
||||
.await
|
||||
.unwrap_or_else(|_| "guest".to_string());
|
||||
.unwrap_or(Role::Guest);
|
||||
let claims = Claims::new(user.id, user.username.clone(), role.clone());
|
||||
|
||||
if let Ok(token) = create_jwt(claims) {
|
||||
@ -340,7 +340,7 @@ async fn get_channel(
|
||||
/// curl -X GET http://127.0.0.1:8787/api/channels -H "Authorization: Bearer <TOKEN>"
|
||||
/// ```
|
||||
#[get("/channels")]
|
||||
#[has_any_role("Role::Admin", type = "Role")]
|
||||
#[has_any_role("Role::Admin", "Role::User", type = "Role")]
|
||||
async fn get_all_channels(pool: web::Data<Pool<Sqlite>>) -> Result<impl Responder, ServiceError> {
|
||||
if let Ok(channel) = handles::select_all_channels(&pool.into_inner()).await {
|
||||
return Ok(web::Json(channel));
|
||||
|
@ -13,12 +13,7 @@ use crate::db::{
|
||||
db_pool,
|
||||
models::{Channel, TextPreset, User},
|
||||
};
|
||||
use crate::utils::{db_path, local_utc_offset, GlobalSettings};
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
struct Role {
|
||||
name: String,
|
||||
}
|
||||
use crate::utils::{db_path, local_utc_offset, GlobalSettings, Role};
|
||||
|
||||
async fn create_schema(conn: &Pool<Sqlite>) -> Result<SqliteQueryResult, sqlx::Error> {
|
||||
let query = "PRAGMA foreign_keys = ON;
|
||||
@ -217,11 +212,11 @@ pub async fn select_last_channel(conn: &Pool<Sqlite>) -> Result<i32, sqlx::Error
|
||||
sqlx::query_scalar(query).fetch_one(conn).await
|
||||
}
|
||||
|
||||
pub async fn select_role(conn: &Pool<Sqlite>, id: &i32) -> Result<String, sqlx::Error> {
|
||||
pub async fn select_role(conn: &Pool<Sqlite>, id: &i32) -> Result<Role, sqlx::Error> {
|
||||
let query = "SELECT name FROM roles WHERE id = $1";
|
||||
let result: Role = sqlx::query_as(query).bind(id).fetch_one(conn).await?;
|
||||
|
||||
Ok(result.name)
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub async fn select_login(conn: &Pool<Sqlite>, user: &str) -> Result<User, sqlx::Error> {
|
||||
|
@ -25,6 +25,7 @@ pub struct User {
|
||||
#[serde(skip_serializing)]
|
||||
pub channel_id: Option<i32>,
|
||||
#[sqlx(default)]
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub token: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,7 @@ pub mod utils;
|
||||
|
||||
use api::{auth, routes::*};
|
||||
use db::{db_pool, models::LoginUser};
|
||||
use utils::{args_parse::Args, control::ProcessControl, db_path, init_config, run_args, Role};
|
||||
use utils::{args_parse::Args, control::ProcessControl, db_path, init_config, run_args};
|
||||
|
||||
use ffplayout_lib::utils::{init_logging, PlayoutConfig};
|
||||
|
||||
@ -29,7 +29,7 @@ async fn validator(
|
||||
// We just get permissions from JWT
|
||||
match auth::decode_jwt(credentials.token()).await {
|
||||
Ok(claims) => {
|
||||
req.attach(vec![Role::set_role(&claims.role)]);
|
||||
req.attach(vec![claims.role]);
|
||||
|
||||
req.extensions_mut()
|
||||
.insert(LoginUser::new(claims.id, claims.username));
|
||||
|
@ -1,9 +1,11 @@
|
||||
use std::{
|
||||
env,
|
||||
error::Error,
|
||||
fmt,
|
||||
fs::{self, File},
|
||||
io::{stdin, stdout, Write},
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use chrono::{format::ParseErrorKind, prelude::*};
|
||||
@ -11,9 +13,9 @@ use faccess::PathExt;
|
||||
use once_cell::sync::OnceCell;
|
||||
use path_clean::PathClean;
|
||||
use rpassword::read_password;
|
||||
use serde::{de, Deserialize, Deserializer};
|
||||
use serde::{de, Deserialize, Deserializer, Serialize};
|
||||
use simplelog::*;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use sqlx::{sqlite::SqliteRow, FromRow, Pool, Row, Sqlite};
|
||||
|
||||
pub mod args_parse;
|
||||
pub mod channels;
|
||||
@ -30,7 +32,7 @@ use crate::db::{
|
||||
use crate::utils::{args_parse::Args, errors::ServiceError};
|
||||
use ffplayout_lib::utils::{time_to_sec, PlayoutConfig};
|
||||
|
||||
#[derive(Clone, Eq, PartialEq)]
|
||||
#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]
|
||||
pub enum Role {
|
||||
Admin,
|
||||
User,
|
||||
@ -47,6 +49,51 @@ impl Role {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Role {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(input: &str) -> Result<Self, Self::Err> {
|
||||
match input {
|
||||
"admin" => Ok(Self::Admin),
|
||||
"user" => Ok(Self::User),
|
||||
_ => Ok(Self::Guest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Role {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
Self::Admin => write!(f, "admin"),
|
||||
Self::User => write!(f, "user"),
|
||||
Self::Guest => write!(f, "guest"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'r> sqlx::decode::Decode<'r, ::sqlx::Sqlite> for Role
|
||||
where
|
||||
&'r str: sqlx::decode::Decode<'r, sqlx::Sqlite>,
|
||||
{
|
||||
fn decode(
|
||||
value: <sqlx::Sqlite as sqlx::database::HasValueRef<'r>>::ValueRef,
|
||||
) -> Result<Role, Box<dyn Error + 'static + Send + Sync>> {
|
||||
let value = <&str as sqlx::decode::Decode<sqlx::Sqlite>>::decode(value)?;
|
||||
|
||||
Ok(value.parse()?)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRow<'_, SqliteRow> for Role {
|
||||
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||
match row.get("name") {
|
||||
"admin" => Ok(Self::Admin),
|
||||
"user" => Ok(Self::User),
|
||||
_ => Ok(Self::Guest),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, sqlx::FromRow)]
|
||||
pub struct GlobalSettings {
|
||||
pub secret: String,
|
||||
|
@ -26,7 +26,7 @@ serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
simplelog = { version = "0.12", features = ["paris"] }
|
||||
tiny_http = { version = "0.12", default-features = false }
|
||||
zeromq = { git = "https://github.com/zeromq/zmq.rs.git", default-features = false, features = [
|
||||
zeromq = { version = "0.3", default-features = false, features = [
|
||||
"async-std-runtime",
|
||||
"tcp-transport",
|
||||
] }
|
||||
|
@ -277,8 +277,9 @@ impl CurrentProgram {
|
||||
|
||||
// de-instance node to preserve original values in list
|
||||
let mut node_clone = nodes[index].clone();
|
||||
node_clone.seek = time_sec
|
||||
- (node_clone.begin.unwrap() - *self.playout_stat.time_shift.lock().unwrap());
|
||||
|
||||
node_clone.seek = time_sec - node_clone.begin.unwrap();
|
||||
self.current_node = handle_list_init(
|
||||
&self.config,
|
||||
node_clone,
|
||||
|
@ -144,6 +144,7 @@ pub fn write_hls(
|
||||
let config_clone = config.clone();
|
||||
let ff_log_format = format!("level+{}", config.logging.ffmpeg_level.to_lowercase());
|
||||
let play_stat = playout_stat.clone();
|
||||
let play_stat2 = playout_stat.clone();
|
||||
let proc_control_c = proc_control.clone();
|
||||
|
||||
let get_source = source_generator(
|
||||
@ -177,12 +178,15 @@ pub fn write_hls(
|
||||
);
|
||||
|
||||
if config.task.enable {
|
||||
let task_config = config.clone();
|
||||
let task_node = node.clone();
|
||||
let server_running = proc_control.server_is_running.load(Ordering::SeqCst);
|
||||
|
||||
if config.task.path.is_file() {
|
||||
thread::spawn(move || task_runner::run(task_config, task_node, server_running));
|
||||
let task_config = config.clone();
|
||||
let task_node = node.clone();
|
||||
let server_running = proc_control.server_is_running.load(Ordering::SeqCst);
|
||||
let stat = play_stat2.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
task_runner::run(task_config, task_node, stat, server_running)
|
||||
});
|
||||
} else {
|
||||
error!(
|
||||
"<bright-blue>{:?}</> executable not exists!",
|
||||
|
@ -45,6 +45,7 @@ pub fn player(
|
||||
let mut buffer = [0; 65088];
|
||||
let mut live_on = false;
|
||||
let playlist_init = playout_stat.list_init.clone();
|
||||
let play_stat = playout_stat.clone();
|
||||
|
||||
// get source iterator
|
||||
let get_source = source_generator(
|
||||
@ -111,12 +112,15 @@ pub fn player(
|
||||
);
|
||||
|
||||
if config.task.enable {
|
||||
let task_config = config.clone();
|
||||
let task_node = node.clone();
|
||||
let server_running = proc_control.server_is_running.load(Ordering::SeqCst);
|
||||
|
||||
if config.task.path.is_file() {
|
||||
thread::spawn(move || task_runner::run(task_config, task_node, server_running));
|
||||
let task_config = config.clone();
|
||||
let task_node = node.clone();
|
||||
let server_running = proc_control.server_is_running.load(Ordering::SeqCst);
|
||||
let stat = play_stat.clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
task_runner::run(task_config, task_node, stat, server_running)
|
||||
});
|
||||
} else {
|
||||
error!(
|
||||
"<bright-blue>{:?}</> executable not exists!",
|
||||
|
@ -376,11 +376,17 @@ fn control_text(
|
||||
/// media info: get infos about current clip
|
||||
fn media_current(
|
||||
config: &PlayoutConfig,
|
||||
playout_stat: &PlayoutStatus,
|
||||
play_control: &PlayerControl,
|
||||
proc: &ProcessControl,
|
||||
) -> Response<Cursor<Vec<u8>>> {
|
||||
if let Some(media) = play_control.current_media.lock().unwrap().clone() {
|
||||
let data_map = get_data_map(config, media, proc.server_is_running.load(Ordering::SeqCst));
|
||||
let data_map = get_data_map(
|
||||
config,
|
||||
media,
|
||||
playout_stat,
|
||||
proc.server_is_running.load(Ordering::SeqCst),
|
||||
);
|
||||
|
||||
return json_response(data_map);
|
||||
};
|
||||
@ -389,14 +395,18 @@ fn media_current(
|
||||
}
|
||||
|
||||
/// media info: get infos about next clip
|
||||
fn media_next(config: &PlayoutConfig, play_control: &PlayerControl) -> Response<Cursor<Vec<u8>>> {
|
||||
fn media_next(
|
||||
config: &PlayoutConfig,
|
||||
playout_stat: &PlayoutStatus,
|
||||
play_control: &PlayerControl,
|
||||
) -> Response<Cursor<Vec<u8>>> {
|
||||
let index = play_control.current_index.load(Ordering::SeqCst);
|
||||
let current_list = play_control.current_list.lock().unwrap();
|
||||
|
||||
if index < current_list.len() {
|
||||
let media = current_list[index].clone();
|
||||
|
||||
let data_map = get_data_map(config, media, false);
|
||||
let data_map = get_data_map(config, media, playout_stat, false);
|
||||
|
||||
return json_response(data_map);
|
||||
}
|
||||
@ -405,14 +415,18 @@ fn media_next(config: &PlayoutConfig, play_control: &PlayerControl) -> Response<
|
||||
}
|
||||
|
||||
/// media info: get infos about last clip
|
||||
fn media_last(config: &PlayoutConfig, play_control: &PlayerControl) -> Response<Cursor<Vec<u8>>> {
|
||||
fn media_last(
|
||||
config: &PlayoutConfig,
|
||||
playout_stat: &PlayoutStatus,
|
||||
play_control: &PlayerControl,
|
||||
) -> Response<Cursor<Vec<u8>>> {
|
||||
let index = play_control.current_index.load(Ordering::SeqCst);
|
||||
let current_list = play_control.current_list.lock().unwrap();
|
||||
|
||||
if index > 1 && index - 2 < current_list.len() {
|
||||
let media = current_list[index - 2].clone();
|
||||
|
||||
let data_map = get_data_map(config, media, false);
|
||||
let data_map = get_data_map(config, media, playout_stat, false);
|
||||
|
||||
return json_response(data_map);
|
||||
}
|
||||
@ -464,13 +478,18 @@ fn build_response(
|
||||
} else if let Some(media_value) = data.get("media").and_then(|m| m.as_str()) {
|
||||
match media_value {
|
||||
"current" => {
|
||||
let _ = request.respond(media_current(config, play_control, proc_control));
|
||||
let _ = request.respond(media_current(
|
||||
config,
|
||||
playout_stat,
|
||||
play_control,
|
||||
proc_control,
|
||||
));
|
||||
}
|
||||
"next" => {
|
||||
let _ = request.respond(media_next(config, play_control));
|
||||
let _ = request.respond(media_next(config, playout_stat, play_control));
|
||||
}
|
||||
"last" => {
|
||||
let _ = request.respond(media_last(config, play_control));
|
||||
let _ = request.respond(media_last(config, playout_stat, play_control));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
@ -16,7 +16,7 @@ use ffplayout_lib::{
|
||||
filter::Filters,
|
||||
utils::{
|
||||
config::Template, get_sec, parse_log_level_filter, sec_to_time, time_to_sec, Media,
|
||||
OutputMode::*, PlayoutConfig, ProcessMode::*,
|
||||
OutputMode::*, PlayoutConfig, PlayoutStatus, ProcessMode::*,
|
||||
},
|
||||
vec_strings,
|
||||
};
|
||||
@ -258,10 +258,13 @@ pub fn get_media_map(media: Media) -> Value {
|
||||
pub fn get_data_map(
|
||||
config: &PlayoutConfig,
|
||||
media: Media,
|
||||
playout_stat: &PlayoutStatus,
|
||||
server_is_running: bool,
|
||||
) -> Map<String, Value> {
|
||||
let mut data_map = Map::new();
|
||||
let begin = media.begin.unwrap_or(0.0);
|
||||
let current_time = get_sec();
|
||||
let shift = *playout_stat.time_shift.lock().unwrap();
|
||||
let begin = media.begin.unwrap_or(0.0) - shift;
|
||||
|
||||
data_map.insert("play_mode".to_string(), json!(config.processing.mode));
|
||||
data_map.insert("ingest_runs".to_string(), json!(server_is_running));
|
||||
@ -269,7 +272,7 @@ pub fn get_data_map(
|
||||
data_map.insert("start_sec".to_string(), json!(begin));
|
||||
|
||||
if begin > 0.0 {
|
||||
let played_time = get_sec() - begin;
|
||||
let played_time = current_time - begin;
|
||||
let remaining_time = media.out - played_time;
|
||||
|
||||
data_map.insert("start_time".to_string(), json!(sec_to_time(begin)));
|
||||
|
@ -3,10 +3,11 @@ use std::process::Command;
|
||||
use simplelog::*;
|
||||
|
||||
use crate::utils::get_data_map;
|
||||
use ffplayout_lib::utils::{config::PlayoutConfig, Media};
|
||||
use ffplayout_lib::utils::{config::PlayoutConfig, Media, PlayoutStatus};
|
||||
|
||||
pub fn run(config: PlayoutConfig, node: Media, server_running: bool) {
|
||||
let obj = serde_json::to_string(&get_data_map(&config, node, server_running)).unwrap();
|
||||
pub fn run(config: PlayoutConfig, node: Media, playout_stat: PlayoutStatus, server_running: bool) {
|
||||
let obj =
|
||||
serde_json::to_string(&get_data_map(&config, node, &playout_stat, server_running)).unwrap();
|
||||
trace!("Run task: {obj}");
|
||||
|
||||
match Command::new(config.task.path).arg(obj).spawn() {
|
||||
|
@ -1 +1 @@
|
||||
Subproject commit 0898bcb2413408cca47707db36f00c74ce55c456
|
||||
Subproject commit 8f615c358290b263244f52d10f6783ac39c6948c
|
Loading…
Reference in New Issue
Block a user