diff --git a/Cargo.lock b/Cargo.lock index bc750cf..630c663 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1260,7 +1260,6 @@ dependencies = [ "http-body-util", "rand", "serde", - "serde_urlencoded", "tera", "tokio", "tower-http", diff --git a/Justfile b/Justfile index 1280487..c19ef20 100644 --- a/Justfile +++ b/Justfile @@ -46,7 +46,7 @@ run: --log debug doc: - cargo doc --workspace --frozen --open + cargo doc --workspace --frozen publish-release-binaries tag: build-release-static curl \ diff --git a/otter.toml b/otter.toml index 185fe94..89481b4 100644 --- a/otter.toml +++ b/otter.toml @@ -1,9 +1,9 @@ data_dir = "./data" [net] -type = "tcp" -domain = "127.0.0.1" -port = 8080 +# type = "tcp" +# domain = "127.0.0.1" +# port = 8080 -# type = "unix" -# path = "./otter.socket" +type = "unix" +path = "./otter.socket" diff --git a/otter/Cargo.toml b/otter/Cargo.toml index 0c3bf8f..5b98adc 100644 --- a/otter/Cargo.toml +++ b/otter/Cargo.toml @@ -25,4 +25,3 @@ http-body-util = "0.1.3" tokio = { version = "1.43.0", features = ["full"] } tracing-subscriber = "0.3.19" tera = "1.20.0" -serde_urlencoded = "0.7.1" diff --git a/otter/src/cli/mod.rs b/otter/src/cli/mod.rs index 6dc1dab..8493451 100644 --- a/otter/src/cli/mod.rs +++ b/otter/src/cli/mod.rs @@ -6,8 +6,8 @@ use std::path::PathBuf; use clap::{Args, Parser, Subcommand, ValueEnum}; use figment::{ - Figment, providers::{Env, Format, Serialized, Toml}, + Figment, }; use serde::Serialize; diff --git a/otter/src/server/gpodder/advanced/auth.rs b/otter/src/server/gpodder/advanced/auth.rs index 9c920dc..b7d98dd 100644 --- a/otter/src/server/gpodder/advanced/auth.rs +++ b/otter/src/server/gpodder/advanced/auth.rs @@ -1,20 +1,20 @@ use axum::{ - Router, extract::{Path, State}, routing::post, + Router, }; use axum_extra::{ + extract::{cookie::Cookie, CookieJar}, + headers::{authorization::Basic, Authorization, UserAgent}, TypedHeader, - extract::{CookieJar, cookie::Cookie}, - headers::{Authorization, UserAgent, authorization::Basic}, }; use cookie::time::Duration; use gpodder::AuthErr; use crate::server::{ - Context, error::{AppError, AppResult}, gpodder::SESSION_ID_COOKIE, + Context, }; pub fn router() -> Router { diff --git a/otter/src/server/gpodder/advanced/devices.rs b/otter/src/server/gpodder/advanced/devices.rs index 30bec37..ed85bf2 100644 --- a/otter/src/server/gpodder/advanced/devices.rs +++ b/otter/src/server/gpodder/advanced/devices.rs @@ -1,18 +1,18 @@ use axum::{ - Extension, Json, Router, extract::{Path, State}, middleware, routing::{get, post}, + Extension, Json, Router, }; use crate::server::{ - Context, error::{AppError, AppResult}, gpodder::{ auth_api_middleware, format::{Format, StringWithFormat}, models, }, + Context, }; pub fn router(ctx: Context) -> Router { diff --git a/otter/src/server/gpodder/advanced/episodes.rs b/otter/src/server/gpodder/advanced/episodes.rs index 2d62333..3f61477 100644 --- a/otter/src/server/gpodder/advanced/episodes.rs +++ b/otter/src/server/gpodder/advanced/episodes.rs @@ -1,14 +1,13 @@ use axum::{ - Extension, Json, Router, extract::{Path, Query, State}, middleware, routing::post, + Extension, Json, Router, }; use chrono::DateTime; use serde::{Deserialize, Serialize}; use crate::server::{ - Context, error::{AppError, AppResult}, gpodder::{ auth_api_middleware, @@ -16,6 +15,7 @@ use crate::server::{ models, models::UpdatedUrlsResponse, }, + Context, }; pub fn router(ctx: Context) -> Router { diff --git a/otter/src/server/gpodder/advanced/subscriptions.rs b/otter/src/server/gpodder/advanced/subscriptions.rs index b430b3e..0395874 100644 --- a/otter/src/server/gpodder/advanced/subscriptions.rs +++ b/otter/src/server/gpodder/advanced/subscriptions.rs @@ -1,19 +1,19 @@ use axum::{ - Extension, Json, Router, extract::{Path, Query, State}, middleware, routing::post, + Extension, Json, Router, }; use serde::Deserialize; use crate::server::{ - Context, error::{AppError, AppResult}, gpodder::{ auth_api_middleware, format::{Format, StringWithFormat}, models::{SubscriptionDelta, SubscriptionDeltaResponse, UpdatedUrlsResponse}, }, + Context, }; pub fn router(ctx: Context) -> Router { diff --git a/otter/src/server/gpodder/advanced/sync.rs b/otter/src/server/gpodder/advanced/sync.rs index 1ac2c33..63f8c7c 100644 --- a/otter/src/server/gpodder/advanced/sync.rs +++ b/otter/src/server/gpodder/advanced/sync.rs @@ -1,18 +1,18 @@ use axum::{ - Extension, Json, Router, extract::{Path, State}, middleware, routing::get, + Extension, Json, Router, }; use crate::server::{ - Context, error::{AppError, AppResult}, gpodder::{ auth_api_middleware, format::{Format, StringWithFormat}, models::{SyncStatus, SyncStatusDelta}, }, + Context, }; pub fn router(ctx: Context) -> Router { diff --git a/otter/src/server/gpodder/format.rs b/otter/src/server/gpodder/format.rs index d2ff395..8839219 100644 --- a/otter/src/server/gpodder/format.rs +++ b/otter/src/server/gpodder/format.rs @@ -1,8 +1,8 @@ use std::ops::Deref; use serde::{ + de::{value::StrDeserializer, Visitor}, Deserialize, - de::{Visitor, value::StrDeserializer}, }; #[derive(Deserialize, Debug, PartialEq, Eq)] diff --git a/otter/src/server/gpodder/mod.rs b/otter/src/server/gpodder/mod.rs index e8f77e3..1bf8061 100644 --- a/otter/src/server/gpodder/mod.rs +++ b/otter/src/server/gpodder/mod.rs @@ -4,16 +4,16 @@ mod models; mod simple; use axum::{ - RequestExt, Router, extract::{Request, State}, - http::{HeaderName, HeaderValue, StatusCode, header::WWW_AUTHENTICATE}, + http::{header::WWW_AUTHENTICATE, HeaderName, HeaderValue, StatusCode}, middleware::Next, response::{IntoResponse, Response}, + RequestExt, Router, }; use axum_extra::{ + extract::{cookie::Cookie, CookieJar}, + headers::{authorization::Basic, Authorization}, TypedHeader, - extract::{CookieJar, cookie::Cookie}, - headers::{Authorization, authorization::Basic}, }; use tower_http::set_header::SetResponseHeaderLayer; diff --git a/otter/src/server/gpodder/simple/subscriptions.rs b/otter/src/server/gpodder/simple/subscriptions.rs index cd960f1..027e575 100644 --- a/otter/src/server/gpodder/simple/subscriptions.rs +++ b/otter/src/server/gpodder/simple/subscriptions.rs @@ -1,14 +1,14 @@ use axum::{ - Extension, Json, Router, extract::{Path, State}, middleware, routing::get, + Extension, Json, Router, }; use crate::server::{ - Context, error::{AppError, AppResult}, gpodder::{auth_api_middleware, format::StringWithFormat}, + Context, }; pub fn router(ctx: Context) -> Router { diff --git a/otter/src/server/mod.rs b/otter/src/server/mod.rs index 20886c6..7ceb245 100644 --- a/otter/src/server/mod.rs +++ b/otter/src/server/mod.rs @@ -6,12 +6,12 @@ mod web; use std::sync::Arc; use axum::{ - Router, body::Body, extract::Request, http::StatusCode, middleware::Next, response::{IntoResponse, Response}, + Router, }; use http_body_util::BodyExt; use tower_http::trace::TraceLayer; diff --git a/otter/src/server/static/mod.rs b/otter/src/server/static/mod.rs index a97f5de..568061c 100644 --- a/otter/src/server/static/mod.rs +++ b/otter/src/server/static/mod.rs @@ -1,7 +1,7 @@ use std::io::Cursor; -use axum::{Router, routing::get}; -use axum_extra::{TypedHeader, headers::Range}; +use axum::{routing::get, Router}; +use axum_extra::{headers::Range, TypedHeader}; use axum_range::{KnownSize, Ranged}; use super::Context; diff --git a/otter/src/server/web/mod.rs b/otter/src/server/web/mod.rs index dac3229..b8ec379 100644 --- a/otter/src/server/web/mod.rs +++ b/otter/src/server/web/mod.rs @@ -1,19 +1,17 @@ -mod sessions; - use axum::{ Form, RequestExt, Router, extract::{Request, State}, - http::HeaderMap, - middleware::Next, + http::{HeaderMap, HeaderName, HeaderValue, header}, + middleware::{self, Next}, response::{IntoResponse, Redirect, Response}, routing::{get, post}, }; use axum_extra::{TypedHeader, extract::CookieJar, headers::UserAgent}; use cookie::{Cookie, time::Duration}; use gpodder::{AuthErr, Session}; -use serde::{Deserialize, Serialize}; +use serde::Deserialize; -use crate::web::{Page, Query, TemplateExt, TemplateResponse, ToQuery, View}; +use crate::web::{Page, TemplateExt, TemplateResponse, View}; use super::{ Context, @@ -22,50 +20,13 @@ use super::{ const SESSION_ID_COOKIE: &str = "sessionid"; -#[derive(Deserialize, Clone)] -#[serde(default)] -pub struct Pagination { - page: u32, - per_page: u32, -} - -impl From for gpodder::Page { - fn from(value: Pagination) -> Self { - Self { - page: value.page, - per_page: value.per_page, - } - } -} - -impl Default for Pagination { - fn default() -> Self { - Self { - page: 0, - per_page: 1, - } - } -} - -impl ToQuery for Pagination { - fn to_query(self) -> Query { - Query::default() - .parameter("page", self.page) - .parameter("per_page", self.per_page) - } -} - -impl Pagination { - pub fn next_page(&self) -> Self { - Self { - page: self.page + 1, - per_page: self.per_page, - } - } -} - pub fn router(ctx: Context) -> Router { Router::new() + .route("/sessions", get(get_sessions)) + .route_layer(axum::middleware::from_fn_with_state( + ctx.clone(), + auth_web_middleware, + )) .route("/", get(get_index)) // .layer(middleware::from_fn_with_state( // ctx.clone(), @@ -75,7 +36,6 @@ pub fn router(ctx: Context) -> Router { // loop .route("/login", get(get_login).post(post_login)) .route("/logout", post(post_logout)) - .merge(sessions::router(ctx.clone())) } async fn get_index( @@ -206,3 +166,14 @@ pub async fn auth_web_middleware( Err(err) => err.into_response(), } } + +pub async fn get_sessions( + State(ctx): State, + headers: HeaderMap, +) -> TemplateResponse> { + View::Sessions(Vec::new()) + .page(&headers) + .headers(&headers) + .authenticated(true) + .response(&ctx.tera) +} diff --git a/otter/src/server/web/sessions.rs b/otter/src/server/web/sessions.rs deleted file mode 100644 index 611372a..0000000 --- a/otter/src/server/web/sessions.rs +++ /dev/null @@ -1,42 +0,0 @@ -use axum::{ - Extension, Router, - extract::{Query, State}, - http::HeaderMap, - routing::get, -}; - -use crate::{ - server::{Context, error::AppResult}, - web::{Page, TemplateExt, TemplateResponse, ToQuery, View}, -}; - -pub fn router(ctx: Context) -> Router { - Router::new() - .route("/sessions", get(get_sessions)) - .route_layer(axum::middleware::from_fn_with_state( - ctx.clone(), - super::auth_web_middleware, - )) -} - -pub async fn get_sessions( - State(ctx): State, - headers: HeaderMap, - Extension(user): Extension, - Query(page): Query, -) -> AppResult>> { - let next_page = page.next_page(); - let sessions = - tokio::task::spawn_blocking(move || ctx.store.paginated_sessions(&user, page.into())) - .await - .unwrap()?; - - let next_page_query = - (sessions.len() == next_page.per_page as usize).then_some(next_page.to_query()); - - Ok(View::Sessions(sessions, next_page_query) - .page(&headers) - .headers(&headers) - .authenticated(true) - .response(&ctx.tera)) -} diff --git a/otter/src/web/mod.rs b/otter/src/web/mod.rs index f99ff53..0035779 100644 --- a/otter/src/web/mod.rs +++ b/otter/src/web/mod.rs @@ -1,5 +1,4 @@ mod page; -mod query; mod view; use std::sync::Arc; @@ -11,7 +10,6 @@ use axum::{ }; pub use page::Page; -pub use query::{Query, ToQuery}; pub use view::View; const BASE_TEMPLATE: &str = "base.html"; @@ -24,7 +22,7 @@ pub trait Template { /// Render the template using the given Tera instance. /// /// Templates are expected to manage their own context requirements if needed. - fn render(self, tera: &tera::Tera) -> tera::Result; + fn render(&self, tera: &tera::Tera) -> tera::Result; } /// Useful additional functions on sized Template implementors @@ -82,7 +80,7 @@ pub fn initialize_tera() -> tera::Result { include_str!("templates/views/login.html"), ), ( - View::Sessions(Vec::new(), None).template(), + View::Sessions(Vec::new()).template(), include_str!("templates/views/sessions.html"), ), ])?; diff --git a/otter/src/web/page.rs b/otter/src/web/page.rs index e7f7f6b..d7c0b4c 100644 --- a/otter/src/web/page.rs +++ b/otter/src/web/page.rs @@ -18,7 +18,7 @@ impl Template for Page { self.template.template() } - fn render(self, tera: &tera::Tera) -> tera::Result { + fn render(&self, tera: &tera::Tera) -> tera::Result { let inner = self.template.render(tera)?; if self.wrap_with_base { diff --git a/otter/src/web/query.rs b/otter/src/web/query.rs deleted file mode 100644 index 409afd5..0000000 --- a/otter/src/web/query.rs +++ /dev/null @@ -1,37 +0,0 @@ -/// Represents a list of query parameters -#[derive(Default)] -pub struct Query(Vec<(String, String)>); - -impl Query { - /// Combine two queries into one - pub fn join(mut self, other: impl ToQuery) -> Self { - let mut other = other.to_query(); - self.0.append(&mut other.0); - - self - } - - /// Convert the query into a url-encoded query parameter string - pub fn encode(self) -> String { - // TODO is this unwrap safe? - serde_urlencoded::to_string(&self.0).unwrap() - } - - /// Builder-style method that appends a parameter to the query - pub fn parameter(mut self, key: impl ToString, value: impl ToString) -> Self { - self.0.push((key.to_string(), value.to_string())); - - self - } -} - -/// Allows objects to be converted into queries -pub trait ToQuery { - fn to_query(self) -> Query; -} - -impl ToQuery for Query { - fn to_query(self) -> Query { - self - } -} diff --git a/otter/src/web/templates/views/sessions.html b/otter/src/web/templates/views/sessions.html index 1a59e34..679a565 100644 --- a/otter/src/web/templates/views/sessions.html +++ b/otter/src/web/templates/views/sessions.html @@ -9,20 +9,10 @@ - {% for session in sessions %} - {{ session.user_agent }} - {{ session.last_seen }} + Firefox + yesterday Remove - {% endfor %} - {% if next_page_query %} - - {% endif %} diff --git a/otter/src/web/view.rs b/otter/src/web/view.rs index 5636a16..4f15834 100644 --- a/otter/src/web/view.rs +++ b/otter/src/web/view.rs @@ -1,18 +1,9 @@ -use chrono::{DateTime, Utc}; -use serde::Serialize; - -use super::{Query, Template}; +use super::Template; pub enum View { Index, Login, - Sessions(Vec, Option), -} - -#[derive(Serialize)] -struct Session { - user_agent: Option, - last_seen: DateTime, + Sessions(Vec), } impl Template for View { @@ -20,37 +11,19 @@ impl Template for View { match self { Self::Index => "views/index.html", Self::Login => "views/login.html", - Self::Sessions(..) => "views/sessions.html", + Self::Sessions(_) => "views/sessions.html", } } - fn render(self, tera: &tera::Tera) -> tera::Result { + fn render(&self, tera: &tera::Tera) -> tera::Result { let mut ctx = tera::Context::new(); - let template = self.template(); - match self { - Self::Sessions(sessions, query) => { - ctx.insert( - "sessions", - &sessions.into_iter().map(Session::from).collect::>(), - ); + // match self { + // Self::Sessions(sessions) => { + // ctx.insert("sessions", sessions); + // } + // }; - if let Some(query) = query { - ctx.insert("next_page_query", &query.encode()); - } - } - _ => {} - }; - - tera.render(template, &ctx) - } -} - -impl From for Session { - fn from(value: gpodder::Session) -> Self { - Self { - user_agent: value.user_agent, - last_seen: value.last_seen, - } + tera.render(self.template(), &ctx) } }