♻️ refactor
Some checks failed
ci/woodpecker/push/build Pipeline failed

This commit is contained in:
JMARyA 2025-02-09 07:09:20 +01:00
parent 2ac2559c23
commit 59dae90b4d
Signed by: jmarya
GPG key ID: 901B2ADDF27C2263
13 changed files with 368 additions and 138 deletions

125
Cargo.lock generated
View file

@ -71,9 +71,9 @@ dependencies = [
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.85" version = "0.1.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f934833b4b7233644e5848f235df3f57ed8c80f1528a26c3dfa13d2147fa056" checksum = "644dd749086bf3771a2fbc5f256fdb982d53f011c7d5d560304eafeecebce79d"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -146,7 +146,7 @@ checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
[[package]] [[package]]
name = "based" name = "based"
version = "0.1.0" version = "0.1.0"
source = "git+https://git.hydrar.de/jmarya/based?branch=ui#6a39c0441d72cbaa1f9864af9c53ac41362f549e" source = "git+https://git.hydrar.de/jmarya/based?branch=ui#c05d0dcc0a46e721e8664a15e6e2f264aa8d4b53"
dependencies = [ dependencies = [
"bcrypt", "bcrypt",
"chrono", "chrono",
@ -180,7 +180,7 @@ checksum = "2b1866ecef4f2d06a0bb77880015fdf2b89e25a1c2e5addacb87e459c86dc67e"
dependencies = [ dependencies = [
"base64 0.22.1", "base64 0.22.1",
"blowfish", "blowfish",
"getrandom", "getrandom 0.2.15",
"subtle", "subtle",
"zeroize", "zeroize",
] ]
@ -227,9 +227,9 @@ dependencies = [
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
version = "3.16.0" version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]] [[package]]
name = "bytemuck" name = "bytemuck"
@ -245,15 +245,15 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "bytes" name = "bytes"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" checksum = "f61dac84819c6588b558454b194026eb1f09c293b9036ae9b159e74e73ab6cf9"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.2.10" version = "1.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229" checksum = "c7777341816418c02e033934a09f20dc0ccaf65a5201ef8a450ae0105a573fda"
dependencies = [ dependencies = [
"shlex", "shlex",
] ]
@ -333,9 +333,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.16" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16b80225097f2e5ae4e7179dd2266824648f3e2f49d9134d584b76389d31c4c3" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [ dependencies = [
"libc", "libc",
] ]
@ -768,7 +768,19 @@ checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
]
[[package]]
name = "getrandom"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8"
dependencies = [
"cfg-if",
"libc",
"wasi 0.13.3+wasi-0.2.2",
"windows-targets 0.52.6",
] ]
[[package]] [[package]]
@ -914,9 +926,9 @@ dependencies = [
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.9.5" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" checksum = "f2d708df4e7140240a16cd6ab0ab65c972d7433ab77819ea693fde9c43811e2a"
[[package]] [[package]]
name = "httpdate" name = "httpdate"
@ -1331,7 +1343,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [ dependencies = [
"libc", "libc",
"wasi", "wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
@ -1356,9 +1368,9 @@ dependencies = [
[[package]] [[package]]
name = "native-tls" name = "native-tls"
version = "0.2.12" version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" checksum = "0dab59f8e050d5df8e4dd87d9206fb6f65a483e20ac9fda365ade4fab353196c"
dependencies = [ dependencies = [
"libc", "libc",
"log", "log",
@ -1455,15 +1467,15 @@ dependencies = [
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.20.2" version = "1.20.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" checksum = "945462a4b81e43c4e3ba96bd7b49d834c6f61198356aa858733bc4acf3cbe62e"
[[package]] [[package]]
name = "openssl" name = "openssl"
version = "0.10.68" version = "0.10.70"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6174bc48f102d208783c2c84bf931bb75927a617866870de8a4ea85597f871f5" checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6"
dependencies = [ dependencies = [
"bitflags 2.8.0", "bitflags 2.8.0",
"cfg-if", "cfg-if",
@ -1487,15 +1499,15 @@ dependencies = [
[[package]] [[package]]
name = "openssl-probe" name = "openssl-probe"
version = "0.1.5" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e"
[[package]] [[package]]
name = "openssl-sys" name = "openssl-sys"
version = "0.9.104" version = "0.9.105"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45abf306cbf99debc8195b66b7346498d7b10c210de50418b5ccd7ceba08c741" checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc"
dependencies = [ dependencies = [
"cc", "cc",
"libc", "libc",
@ -1711,7 +1723,7 @@ version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.2.15",
] ]
[[package]] [[package]]
@ -2017,9 +2029,9 @@ checksum = "f7c45b9784283f1b2e7fb61b42047c2fd678ef0960d4f6f1eba131594cc369d4"
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.18" version = "1.0.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" checksum = "6ea1a2d0a644769cc99faa24c3ad26b379b786fe7c36fd3c546254801650e6dd"
[[package]] [[package]]
name = "same-file" name = "same-file"
@ -2096,9 +2108,9 @@ dependencies = [
[[package]] [[package]]
name = "serde_json" name = "serde_json"
version = "1.0.137" version = "1.0.138"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "930cfb6e6abf99298aaad7d29abbef7a9999a9a8806a40088f55f0dcec03146b" checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
dependencies = [ dependencies = [
"itoa", "itoa",
"memchr", "memchr",
@ -2475,9 +2487,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.96" version = "2.0.98"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" checksum = "36147f1a48ae0ec2b5b3bc5b537d267457555a10dc06f3dbc8cb11ba3006d3b1"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -2524,13 +2536,13 @@ dependencies = [
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.15.0" version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8a559c81686f576e8cd0290cd2a24a2a9ad80c98b3478856500fcbd7acd704" checksum = "38c246215d7d24f48ae091a2902398798e05d978b24315d6efbc00ede9a8bb91"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
"getrandom", "getrandom 0.3.1",
"once_cell", "once_cell",
"rustix", "rustix",
"windows-sys 0.59.0", "windows-sys 0.59.0",
@ -2696,9 +2708,9 @@ dependencies = [
[[package]] [[package]]
name = "toml" name = "toml"
version = "0.8.19" version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" checksum = "cd87a5cdd6ffab733b2f74bc4fd7ee5fff6634124999ac278c35fc78c6120148"
dependencies = [ dependencies = [
"serde", "serde",
"serde_spanned", "serde_spanned",
@ -2717,9 +2729,9 @@ dependencies = [
[[package]] [[package]]
name = "toml_edit" name = "toml_edit"
version = "0.22.22" version = "0.22.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" checksum = "02a8b472d1a3d7c18e2d61a489aee3453fd9031c33e4f55bd533f4a7adca1bee"
dependencies = [ dependencies = [
"indexmap", "indexmap",
"serde", "serde",
@ -2851,9 +2863,9 @@ checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.14" version = "1.0.16"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" checksum = "a210d160f08b701c8721ba1c726c11662f877ea6b7094007e1ca9a1041945034"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
@ -2907,11 +2919,11 @@ checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.12.1" version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b3758f5e68192bb96cc8f9b7e2c2cfdabb435499a28499a42f8f984092adad4b" checksum = "ced87ca4be083373936a67f8de945faa23b6b42384bd5b64434850802c6dccd0"
dependencies = [ dependencies = [
"getrandom", "getrandom 0.3.1",
"serde", "serde",
] ]
@ -2958,6 +2970,15 @@ version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasi"
version = "0.13.3+wasi-0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2"
dependencies = [
"wit-bindgen-rt",
]
[[package]] [[package]]
name = "wasite" name = "wasite"
version = "0.1.0" version = "0.1.0"
@ -3058,6 +3079,7 @@ dependencies = [
"serde_json", "serde_json",
"sqlx", "sqlx",
"tokio", "tokio",
"toml",
"uuid", "uuid",
"walkdir", "walkdir",
] ]
@ -3281,9 +3303,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]] [[package]]
name = "winnow" name = "winnow"
version = "0.6.24" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d71a593cc5c42ad7876e2c1fda56f314f3754c084128833e64f1345ff8a03a" checksum = "86e376c75f4f43f44db463cf729e0d3acbf954d13e22c51e26e4c264b4ab545f"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
@ -3298,6 +3320,15 @@ dependencies = [
"windows-sys 0.48.0", "windows-sys 0.48.0",
] ]
[[package]]
name = "wit-bindgen-rt"
version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c"
dependencies = [
"bitflags 2.8.0",
]
[[package]] [[package]]
name = "write16" name = "write16"
version = "1.0.0" version = "1.0.0"

View file

@ -25,3 +25,4 @@ rand = "0.8.5"
data-encoding = "2.6.0" data-encoding = "2.6.0"
bcrypt = "0.16.0" bcrypt = "0.16.0"
based = { git = "https://git.hydrar.de/jmarya/based", features = ["cache"], branch = "ui" } based = { git = "https://git.hydrar.de/jmarya/based", features = ["cache"], branch = "ui" }
toml = "0.8.20"

2
README.md Normal file
View file

@ -0,0 +1,2 @@
# WatchDogs
WatchDogs is a video server

7
config.toml Normal file
View file

@ -0,0 +1,7 @@
[general]
# Private Instance (Login required for watching content)
private = true
# Path to videos
video_path = "./videos"

View file

@ -11,7 +11,6 @@ services:
- "DATABASE_URL=postgres://user:pass@postgres/watchdogs" - "DATABASE_URL=postgres://user:pass@postgres/watchdogs"
- "RUST_LOG=info" - "RUST_LOG=info"
- "ROCKET_ADDRESS=0.0.0.0" - "ROCKET_ADDRESS=0.0.0.0"
command: "/watchdogs /videos"
postgres: postgres:
image: timescale/timescaledb:latest-pg16 image: timescale/timescaledb:latest-pg16

12
src/config.rs Normal file
View file

@ -0,0 +1,12 @@
use serde::Deserialize;
#[derive(Debug, Clone, Deserialize)]
pub struct Config {
pub general: GeneralConfig,
}
#[derive(Debug, Clone, Deserialize)]
pub struct GeneralConfig {
pub private: bool,
pub video_path: String,
}

View file

@ -1,10 +1,17 @@
use based::{asset::AssetRoutes, auth::User, get_pg}; use based::{
asset::AssetRoutes,
auth::User,
get_pg,
ui::components::{AppBar, Shell},
};
use rocket::{http::Method, routes}; use rocket::{http::Method, routes};
use std::path::Path; use std::path::Path;
mod config;
mod library; mod library;
mod meta; mod meta;
mod pages; mod pages;
mod yt_meta; mod yt_meta;
use based::ui::prelude::*;
#[rocket::launch] #[rocket::launch]
async fn launch() -> _ { async fn launch() -> _ {
@ -12,14 +19,10 @@ async fn launch() -> _ {
std::env::set_var("RUST_BACKTRACE", "1"); std::env::set_var("RUST_BACKTRACE", "1");
env_logger::init(); env_logger::init();
let args: Vec<String> = std::env::args().collect(); let conf: config::Config =
toml::from_str(&std::fs::read_to_string("config.toml").unwrap()).unwrap();
if args.len() != 2 { let dir_path = conf.general.video_path.clone();
eprintln!("Usage: {} <directory_path>", args[0]);
std::process::exit(1);
}
let dir_path = args[1].clone();
let pg = get_pg!(); let pg = get_pg!();
@ -50,6 +53,13 @@ async fn launch() -> _ {
.to_cors() .to_cors()
.expect("error creating CORS options"); .expect("error creating CORS options");
let shell = Shell::new(
Nothing(),
Nothing(),
Background(Text("").white()).color(Colors::Black),
)
.use_ui();
rocket::build() rocket::build()
.mount_assets() .mount_assets()
.mount( .mount(
@ -74,4 +84,6 @@ async fn launch() -> _ {
) )
.attach(cors) .attach(cors)
.manage(lib) .manage(lib)
.manage(conf)
.manage(shell)
} }

View file

@ -1,12 +1,21 @@
use based::request::assets::DataResponse; use based::{auth::MaybeUser, request::assets::DataResponse};
use rocket::{get, State}; use rocket::{get, State};
use tokio::{fs::File, io::AsyncReadExt}; use tokio::{fs::File, io::AsyncReadExt};
use crate::library::Library; use crate::{config::Config, library::Library};
#[get("/video/raw?<v>")] #[get("/video/raw?<v>")]
pub async fn video_file(v: &str, library: &State<Library>) -> Option<DataResponse> { pub async fn video_file(
v: &str,
library: &State<Library>,
conf: &State<Config>,
user: MaybeUser,
) -> Option<DataResponse> {
if conf.general.private && user.user().is_none() {
return None;
}
let video = if let Some(video) = library.get_video_by_id(v).await { let video = if let Some(video) = library.get_video_by_id(v).await {
video video
} else { } else {
@ -33,7 +42,16 @@ pub async fn video_file(v: &str, library: &State<Library>) -> Option<DataRespons
} }
#[get("/video/thumbnail?<v>")] #[get("/video/thumbnail?<v>")]
pub async fn video_thumbnail(v: &str, library: &State<Library>) -> Option<DataResponse> { pub async fn video_thumbnail(
v: &str,
library: &State<Library>,
conf: &State<Config>,
user: MaybeUser,
) -> Option<DataResponse> {
if conf.general.private && user.user().is_none() {
return None;
}
let video = if let Some(video) = library.get_video_by_id(v).await { let video = if let Some(video) = library.get_video_by_id(v).await {
video video
} else { } else {

View file

@ -1,5 +1,9 @@
use crate::library::Video; use crate::library::Video;
use based::ui::components::{AppBar, Shell}; use based::ui::components::prelude::Avatar;
use based::ui::components::prelude::*;
use based::ui::components::{AppBar, Card, NavBar, Shell};
use based::ui::primitives::flex::Column;
use based::ui::primitives::Optional;
use based::ui::wrapper::HoverWrapper; use based::ui::wrapper::HoverWrapper;
use based::ui::{prelude::*, UIWidget}; use based::ui::{prelude::*, UIWidget};
use based::{ use based::{
@ -10,22 +14,37 @@ use based::{
use maud::{html, PreEscaped, Render}; use maud::{html, PreEscaped, Render};
pub async fn render_page( pub async fn render_page(
shell: &Shell,
ctx: RequestContext, ctx: RequestContext,
content: PreEscaped<String>, content: PreEscaped<String>,
title: &str, title: &str,
user: Option<User>, user: Option<User>,
) -> StringResponse { ) -> StringResponse {
Shell::new( shell
Nothing(), .extend()
Div() .with_navbar(
.vanish() NavBar("WatchDogs")
.push(script(include_str!("../scripts/header.js"))) .icon(Sized(
.push(AppBar("WatchDogs", user)), ScreenValue::_8,
Background(Text("").white()).color(Colors::Black), ScreenValue::_8,
) Rounded(Image("/favicon").alt("Logo")).size(Size::Medium),
.use_ui() ))
.render_page(content, title, ctx) .extra(Optional(user.as_ref(), |user| {
.await // TODO : profile pictures
DropDown(
Avatar("", &user.username).use_initials(),
Column(vec![Link(
"/history",
Padding(Text("Video History").medium())
.x(ScreenValue::_4)
.y(ScreenValue::_2),
)
.use_htmx()]),
)
})),
)
.render_page(content, title, ctx)
.await
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
@ -48,7 +67,18 @@ pub fn Title<T: UIWidget + 'static>(title: T) -> PreEscaped<String> {
} }
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub fn VerticalVideoGrid<T: UIWidget + 'static>(inner: T) -> PreEscaped<String> { pub async fn VerticalVideoGrid(videos: &mut [Video]) -> PreEscaped<String> {
let video_elements = html! {
@for mut vid in videos {
( video_element(&mut vid).await );
};
};
VerticalVideoGridRaw(video_elements)
}
#[allow(non_snake_case)]
pub fn VerticalVideoGridRaw<T: UIWidget + 'static>(inner: T) -> PreEscaped<String> {
Screen::large(Grid(Nothing()).columns(GridAmount::_3).gap(ScreenValue::_6)) Screen::large(Grid(Nothing()).columns(GridAmount::_3).gap(ScreenValue::_6))
.on(Padding(Margin(inner).bottom(ScreenValue::_4)).all(ScreenValue::_6)) .on(Padding(Margin(inner).bottom(ScreenValue::_4)).all(ScreenValue::_6))
.render() .render()
@ -141,7 +171,7 @@ pub fn video_thumbnail_with_time(video: &Video) -> PreEscaped<String> {
Position( Position(
PositionKind::Relative, PositionKind::Relative,
Div() Div()
.push(Context(Width( .push(Context(ObjectFit::Cover(Width(
ScreenValue::full, ScreenValue::full,
Height( Height(
ScreenValue::auto, ScreenValue::auto,
@ -151,7 +181,7 @@ pub fn video_thumbnail_with_time(video: &Video) -> PreEscaped<String> {
.side(Side::Top), .side(Side::Top),
), ),
), ),
))) ))))
.push(Context( .push(Context(
Position( Position(
PositionKind::Absolute, PositionKind::Absolute,
@ -216,3 +246,19 @@ pub async fn video_element(video: &mut Video) -> PreEscaped<String> {
) )
.render() .render()
} }
#[allow(non_snake_case)]
pub fn DirectoryBadge(dir: &str) -> PreEscaped<String> {
Margin(
Padding(
Hover(Background(Cursor::Pointer.on(Nothing())).color(Purple::_600)).on(Background(
Rounded(Link(&format!("/d/{dir}"), Text(dir).white()).use_htmx()).size(Size::Full),
)
.color(Purple::_500)),
)
.x(ScreenValue::_3)
.y(ScreenValue::_2),
)
.all(ScreenValue::_2)
.render()
}

View file

@ -1,4 +1,7 @@
use based::ui::htmx::HTMXAttributes; use based::page;
use based::request::respond_html;
use based::ui::components::prelude::InfinityScroll;
use based::ui::components::{ColoredSpinner, Shell};
use based::ui::prelude::*; use based::ui::prelude::*;
use based::{ use based::{
auth::MaybeUser, auth::MaybeUser,
@ -7,24 +10,42 @@ use based::{
RequestContext, StringResponse, RequestContext, StringResponse,
}, },
}; };
use maud::{html, Render}; use maud::{html, PreEscaped, Render};
use rocket::{get, State}; use rocket::{get, uri, State};
use serde_json::json; use serde_json::json;
use crate::pages::components::{Title, VerticalVideoGrid}; use crate::config::Config;
use crate::library::Video;
use crate::pages::components::{Title, VerticalVideoGrid, VerticalVideoGridRaw};
use crate::{library::Library, pages::components::video_element}; use crate::{library::Library, pages::components::video_element};
use super::components::DirectoryBadge;
use super::{ use super::{
api_response, api_response,
components::{render_page, video_element_wide}, components::{render_page, video_element_wide},
}; };
#[macro_export]
macro_rules! check_private {
($conf:ident, $user:ident, $shell:ident, $ctx:ident) => {
if $conf.general.private && $user.user().is_none() {
return $crate::pages::index::is_private_page($shell, $ctx).await;
}
};
}
#[get("/search?<query>&<offset>")] #[get("/search?<query>&<offset>")]
pub async fn search( pub async fn search(
query: &str, query: &str,
offset: Option<i64>, offset: Option<i64>,
library: &State<Library>, library: &State<Library>,
conf: &State<Config>,
user: MaybeUser,
shell: &State<Shell>,
ctx: RequestContext,
) -> Option<serde_json::Value> { ) -> Option<serde_json::Value> {
// todo : check_private!(conf, user, shell, ctx);
const NUM_OF_RESULTS: i64 = 20; const NUM_OF_RESULTS: i64 = 20;
// get start parameter for search result chunks // get start parameter for search result chunks
@ -36,32 +57,87 @@ pub async fn search(
} }
#[get("/latest.json")] #[get("/latest.json")]
pub async fn latest_api(library: &State<Library>) -> FallibleApiResponse { pub async fn latest_api(
library: &State<Library>,
user: MaybeUser,
conf: &State<Config>,
shell: &State<Shell>,
ctx: RequestContext,
) -> FallibleApiResponse {
// todo : check_private!(conf, user, shell, ctx);
let videos = library.get_newly_added(20).await; let videos = library.get_newly_added(20).await;
let vid_api = vec_to_api(&videos).await; let vid_api = vec_to_api(&videos).await;
return Ok(serde_json::json!(vid_api)); return Ok(serde_json::json!(vid_api));
} }
#[get("/latest")] #[allow(non_snake_case)]
pub async fn latest_page( pub async fn VideoList(videos: &[Video]) -> PreEscaped<String> {
ctx: RequestContext,
library: &State<Library>,
user: MaybeUser,
) -> StringResponse {
let videos = library.get_newly_added(20).await;
let video_elements = html! { let video_elements = html! {
@for mut vid in videos { @for mut vid in videos {
( video_element_wide(&mut vid).await ); ( video_element_wide(&mut vid).await );
}; };
}; };
Padding(video_elements).all(ScreenValue::_6).render()
}
#[get("/latest?<offset>")]
pub async fn latest_page(
ctx: RequestContext,
library: &State<Library>,
user: MaybeUser,
shell: &State<Shell>,
conf: &State<Config>,
offset: Option<u32>,
) -> StringResponse {
check_private!(conf, user, shell, ctx);
let mut videos: Vec<_> = library
.get_newly_added(offset.unwrap_or_default() as i64 + 20)
.await
.into_iter()
.skip(offset.unwrap_or_default() as usize)
.take(20)
.collect();
let has_content = !videos.is_empty();
let video_elements = VerticalVideoGrid(&mut videos).await;
if ctx.is_htmx && !ctx.htmx_redirect {
return respond_html(
Div()
.vanish()
.push(video_elements)
.push_if(has_content, || {
Width(
ScreenValue::fit,
Margin(InfinityScroll(
ColoredSpinner(Purple::_600),
&format!("/latest?offset={}", offset.unwrap_or_default() + 20),
))
.x(ScreenValue::auto),
)
})
.render()
.0,
);
}
let content = Div() let content = Div()
.vanish() .vanish()
.push(Title("Recent videos")) .push(Title("Recent videos"))
.push(Padding(video_elements).all(ScreenValue::_6)) .push(video_elements)
.push(Width(
ScreenValue::fit,
Margin(InfinityScroll(
ColoredSpinner(Purple::_600),
&format!("/latest?offset={}", offset.unwrap_or_default() + 20),
))
.x(ScreenValue::auto),
))
.render(); .render();
render_page(ctx, content, "Recent videos", user.into()).await render_page(&shell, ctx, content, "Recent videos", user.into()).await
} }
#[get("/d/<dir>")] #[get("/d/<dir>")]
@ -70,7 +146,11 @@ pub async fn dir_page(
dir: &str, dir: &str,
library: &State<Library>, library: &State<Library>,
user: MaybeUser, user: MaybeUser,
shell: &State<Shell>,
conf: &State<Config>,
) -> StringResponse { ) -> StringResponse {
check_private!(conf, user, shell, ctx);
if dir.ends_with(".json") { if dir.ends_with(".json") {
let dir_videos = library let dir_videos = library
.get_directory_videos(dir.split_once(".json").map(|x| x.0).unwrap_or_default()) .get_directory_videos(dir.split_once(".json").map(|x| x.0).unwrap_or_default())
@ -91,7 +171,11 @@ pub async fn dir_page(
.push(Padding(video_elements).all(ScreenValue::_6)) .push(Padding(video_elements).all(ScreenValue::_6))
.render(); .render();
render_page(ctx, content, dir, user.into()).await render_page(&shell, ctx, content, dir, user.into()).await
}
pub async fn is_private_page(shell: &Shell, ctx: RequestContext) -> StringResponse {
page!(shell, ctx, "WatchDogs", Text("This is a private instance"))
} }
#[get("/")] #[get("/")]
@ -99,7 +183,11 @@ pub async fn index_page(
ctx: RequestContext, ctx: RequestContext,
library: &State<Library>, library: &State<Library>,
user: MaybeUser, user: MaybeUser,
shell: &State<Shell>,
conf: &State<Config>,
) -> StringResponse { ) -> StringResponse {
check_private!(conf, user, shell, ctx);
let random_video_elements = html! { let random_video_elements = html! {
@for mut vid in library.get_random_videos(3).await { @for mut vid in library.get_random_videos(3).await {
( video_element(&mut vid).await ); ( video_element(&mut vid).await );
@ -115,34 +203,22 @@ pub async fn index_page(
let content = Div() let content = Div()
.vanish() .vanish()
.push(Title("Random Videos")) .push(Title("Random Videos"))
.push(VerticalVideoGrid(random_video_elements)) .push(VerticalVideoGridRaw(random_video_elements))
.push(Title(Link("/latest", "Latest Videos").use_htmx())) .push(Title(Link("/latest", "Latest Videos").use_htmx()))
.push(VerticalVideoGrid(newly_added_elements)) .push(VerticalVideoGridRaw(newly_added_elements))
.push(Title("Directories:")) .push(Title("Directories:"))
.push( .push(
Padding( Padding(
Flex(Div().vanish().push_for_each(&directories, |dir: &_| { Flex(
Margin( Div()
Padding( .vanish()
Hover(Background(Cursor::Pointer.on(Nothing())).color(Purple::_600)) .push_for_each(&directories, |dir: &_| DirectoryBadge(dir)),
.on(Background( )
Rounded(
Link(&format!("/d/{dir}"), Text(&dir).white()).use_htmx(),
)
.size(Size::Full),
)
.color(Purple::_500)),
)
.x(ScreenValue::_3)
.y(ScreenValue::_2),
)
.all(ScreenValue::_2)
}))
.wrap(Wrap::Wrap), .wrap(Wrap::Wrap),
) )
.all(ScreenValue::_10), .all(ScreenValue::_10),
) )
.render(); .render();
render_page(ctx, content, "WatchDogs", user.into()).await render_page(&shell, ctx, content, "WatchDogs", user.into()).await
} }

View file

@ -1,9 +1,12 @@
use based::auth::{Sessions, User}; use based::auth::{Sessions, User};
use based::page;
use based::request::StringResponse; use based::request::StringResponse;
use based::ui::components::{Card, Shell};
use based::ui::prelude::*; use based::ui::prelude::*;
use based::{auth::MaybeUser, request::RequestContext}; use based::{auth::MaybeUser, request::RequestContext};
use maud::{html, Render}; use maud::{html, Render};
use rocket::http::CookieJar; use rocket::http::CookieJar;
use rocket::State;
use rocket::{form::Form, get, http::Cookie, post, response::Redirect, FromForm}; use rocket::{form::Form, get, http::Cookie, post, response::Redirect, FromForm};
use crate::library::history::VideoHistory; use crate::library::history::VideoHistory;
@ -12,17 +15,32 @@ use crate::pages::components::{video_element_wide, Title};
use super::components::render_page; use super::components::render_page;
#[get("/login")] #[get("/login")]
pub async fn login(ctx: RequestContext, user: MaybeUser) -> StringResponse { pub async fn login(ctx: RequestContext, user: MaybeUser, shell: &State<Shell>) -> StringResponse {
let content = html!( page!(
h2 { "Login" }; shell,
form action="/login" method="POST" { ctx,
input type="text" name="username" placeholder="Username" required; "Login",
input type="password" name="password" placeholder="Password" required; Margin(Width(
input type="submit" value="Login"; ScreenValue::_80,
} Div().push(Text("Login")._2xl()).push(
); Margin(
based::ui::primitives::input::Form::new("/login")
render_page(ctx, content, "Login", user.into()).await .method(FormMethod::POST)
.add_input(TextInput("username").placeholder("Username").required())
.add_input(
TextInput("password")
.placeholder("Password")
.required()
.password()
)
.add_input(FormSubmitButton("Login").value("Login"))
)
.top(ScreenValue::_4)
)
))
.x(ScreenValue::auto)
.top(ScreenValue::_6)
)
} }
#[derive(FromForm)] #[derive(FromForm)]
@ -49,7 +67,7 @@ pub async fn login_post(login_form: Form<LoginForm>, cookies: &CookieJar<'_>) ->
} }
#[get("/history")] #[get("/history")]
pub async fn history_page(ctx: RequestContext, user: User) -> StringResponse { pub async fn history_page(ctx: RequestContext, user: User, shell: &State<Shell>) -> StringResponse {
let video_elements = html! { let video_elements = html! {
@for mut vid in user.history_of(10).await { @for mut vid in user.history_of(10).await {
( video_element_wide(&mut vid).await ); ( video_element_wide(&mut vid).await );
@ -62,5 +80,5 @@ pub async fn history_page(ctx: RequestContext, user: User) -> StringResponse {
.push(Padding(video_elements).all(ScreenValue::_6)) .push(Padding(video_elements).all(ScreenValue::_6))
.render(); .render();
render_page(ctx, content, "History", Some(user)).await render_page(&shell, ctx, content, "History", Some(user)).await
} }

View file

@ -1,3 +1,4 @@
use based::ui::components::Shell;
use based::ui::primitives::space::Fraction; use based::ui::primitives::space::Fraction;
use based::ui::{prelude::*, AttrExtendable}; use based::ui::{prelude::*, AttrExtendable};
use based::{ use based::{
@ -8,6 +9,8 @@ use based::{
use maud::{html, PreEscaped, Render}; use maud::{html, PreEscaped, Render};
use rocket::{get, State}; use rocket::{get, State};
use crate::check_private;
use crate::config::Config;
use crate::library::Video; use crate::library::Video;
use crate::{ use crate::{
library::{history::VideoHistory, Library}, library::{history::VideoHistory, Library},
@ -22,7 +25,11 @@ pub async fn watch_page(
library: &State<Library>, library: &State<Library>,
v: String, v: String,
user: MaybeUser, user: MaybeUser,
conf: &State<Config>,
shell: &State<Shell>,
) -> StringResponse { ) -> StringResponse {
check_private!(conf, user, shell, ctx);
let video = if let Some(video) = library.get_video_by_id(&v).await { let video = if let Some(video) = library.get_video_by_id(&v).await {
video video
} else { } else {
@ -37,7 +44,7 @@ pub async fn watch_page(
let youtube_meta = video.youtube_meta().await; let youtube_meta = video.youtube_meta().await;
let rec = build_rec(&library, &video).await; let rec = build_rec(&library, &video).await;
let content = Container( let content =
Margin( Margin(
Screen::large(Flex(Nothing()).direction(Direction::Row)).on( Screen::large(Flex(Nothing()).direction(Direction::Row)).on(
Flex( Flex(
@ -50,7 +57,7 @@ pub async fn watch_page(
Rounded( Rounded(
Video().controls().autoplay().width(1080).add_src( Video().controls().autoplay().width(1080).add_src(
Source(&format!("/video/raw?v={}", video.id), Some("video/mp4".to_string())) Source(&format!("/video/raw?v={}", video.id), Some("video/mp4".to_string()))
) ).poster(&format!("/video/thumbnail?v={}", video.id))
).size(Size::Large) ).size(Size::Large)
).color(Colors::Black) ).color(Colors::Black)
)) ))
@ -94,10 +101,10 @@ pub async fn watch_page(
rec rec
) )
).direction(Direction::Column).gap(ScreenValue::_6)) ).direction(Direction::Column).gap(ScreenValue::_6))
).x(ScreenValue::_10).top(ScreenValue::_6) ).x(ScreenValue::_10).top(ScreenValue::_6).render();
).render();
render_page( render_page(
&shell,
ctx, ctx,
content, content,
&format!("{} - WatchDogs", video.title), &format!("{} - WatchDogs", video.title),
@ -123,10 +130,18 @@ pub async fn build_rec(library: &Library, video: &Video) -> PreEscaped<String> {
Margin( Margin(
Paragraph(Context( Paragraph(Context(
SpaceBetween( SpaceBetween(
Flex(Div().vanish().push(Span("In ")).push(Link( Flex(
&format!("/d/{}", video.directory), Div()
Text(&video.directory).color(&Blue::_500), .vanish()
))) .push(Span("In ")._4xl().extrabold())
.push(Link(
&format!("/d/{}", video.directory),
Text(&video.directory)
.color(&Blue::_500)
._4xl()
.extrabold(),
)),
)
.group() .group()
.justify(Justify::Center), .justify(Justify::Center),
) )

View file

@ -1,7 +0,0 @@
function stopAllVideos() {
const videos = document.querySelectorAll('video');
videos.forEach(video => {
video.pause();
});
}