Compare commits
	
		
			3 Commits 
		
	
	
		
			974ca80298
			...
			7abce21aee
		
	
	| Author | SHA1 | Date | 
|---|---|---|
| 
							
							
								
									
								
								 | 
						7abce21aee | |
| 
							
							
								
									
								
								 | 
						279983c64c | |
| 
							
							
								
									
								
								 | 
						f3ede6f9a6 | 
| 
						 | 
				
			
			@ -1243,7 +1243,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 | 
			
		|||
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
 | 
			
		||||
 | 
			
		||||
[[package]]
 | 
			
		||||
name = "otter"
 | 
			
		||||
name = "otterd"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
dependencies = [
 | 
			
		||||
 "axum",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										26
									
								
								Cargo.toml
								
								
								
								
							
							
						
						
									
										26
									
								
								Cargo.toml
								
								
								
								
							| 
						 | 
				
			
			@ -1,30 +1,12 @@
 | 
			
		|||
[workspace]
 | 
			
		||||
resolver = '2'
 | 
			
		||||
members = [
 | 
			
		||||
    'server',
 | 
			
		||||
    'gpodder',
 | 
			
		||||
    'gpodder_sqlite'
 | 
			
		||||
]
 | 
			
		||||
 | 
			
		||||
[package]
 | 
			
		||||
name = "otter"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
gpodder = { path = "./gpodder" }
 | 
			
		||||
gpodder_sqlite = { path = "./gpodder_sqlite" }
 | 
			
		||||
 | 
			
		||||
axum = { version = "0.8.1", features = ["macros"] }
 | 
			
		||||
axum-extra = { version = "0.10", features = ["cookie", "typed-header"] }
 | 
			
		||||
chrono = { version = "0.4.39", features = ["serde"] }
 | 
			
		||||
clap = { version = "4.5.30", features = ["derive", "env"] }
 | 
			
		||||
cookie = "0.18.1"
 | 
			
		||||
figment = { version = "0.10.19", features = ["env", "toml"] }
 | 
			
		||||
http-body-util = "0.1.3"
 | 
			
		||||
[workspace.dependencies]
 | 
			
		||||
rand = "0.8.5"
 | 
			
		||||
serde = { version = "1.0.218", features = ["derive"] }
 | 
			
		||||
tokio = { version = "1.43.0", features = ["full"] }
 | 
			
		||||
tower-http = { version = "0.6.2", features = ["set-header", "trace"] }
 | 
			
		||||
tracing = "0.1.41"
 | 
			
		||||
tracing-subscriber = "0.3.19"
 | 
			
		||||
tera = "1.20.0"
 | 
			
		||||
axum-range = "0.5.0"
 | 
			
		||||
chrono = "0.4.39"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ version = "0.1.0"
 | 
			
		|||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
chrono = { version = "0.4.39", features = ["serde"] }
 | 
			
		||||
rand = { workspace = true }
 | 
			
		||||
chrono = { workspace = true }
 | 
			
		||||
 | 
			
		||||
argon2 = "0.5.3"
 | 
			
		||||
rand = "0.8.5"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,12 +9,14 @@ harness = false
 | 
			
		|||
 | 
			
		||||
[dependencies]
 | 
			
		||||
gpodder = { path = "../gpodder" }
 | 
			
		||||
 | 
			
		||||
rand = { workspace = true }
 | 
			
		||||
tracing = { workspace = true }
 | 
			
		||||
chrono = { workspace = true, features = ["serde"] }
 | 
			
		||||
 | 
			
		||||
libsqlite3-sys = { version = "0.31.0", features = ["bundled"] }
 | 
			
		||||
diesel = { version = "2.2.7", features = ["r2d2", "sqlite", "returning_clauses_for_sqlite_3_35"] }
 | 
			
		||||
diesel_migrations = { version = "2.2.0", features = ["sqlite"] }
 | 
			
		||||
tracing = "0.1.41"
 | 
			
		||||
chrono = { version = "0.4.39", features = ["serde"] }
 | 
			
		||||
rand = "0.8.5"
 | 
			
		||||
libsqlite3-sys = { version = "0.31.0", features = ["bundled"] }
 | 
			
		||||
 | 
			
		||||
[dev-dependencies]
 | 
			
		||||
criterion = "0.5.1"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -17,10 +17,8 @@ use crate::schema::*;
 | 
			
		|||
pub struct Device {
 | 
			
		||||
    pub id: i64,
 | 
			
		||||
    pub device_id: String,
 | 
			
		||||
    pub user_id: i64,
 | 
			
		||||
    pub caption: String,
 | 
			
		||||
    pub type_: DeviceType,
 | 
			
		||||
    pub sync_group_id: Option<i64>,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Insertable)]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,8 +6,6 @@ use crate::schema::*;
 | 
			
		|||
#[diesel(table_name = device_subscriptions)]
 | 
			
		||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
 | 
			
		||||
pub struct DeviceSubscription {
 | 
			
		||||
    pub id: i64,
 | 
			
		||||
    pub device_id: i64,
 | 
			
		||||
    pub podcast_url: String,
 | 
			
		||||
    pub time_changed: i64,
 | 
			
		||||
    pub deleted: bool,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -16,9 +16,6 @@ use crate::schema::*;
 | 
			
		|||
#[diesel(table_name = episode_actions)]
 | 
			
		||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
 | 
			
		||||
pub struct EpisodeAction {
 | 
			
		||||
    pub id: i64,
 | 
			
		||||
    pub user_id: i64,
 | 
			
		||||
    pub device_id: Option<i64>,
 | 
			
		||||
    pub podcast_url: String,
 | 
			
		||||
    pub episode_url: String,
 | 
			
		||||
    pub time_changed: i64,
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,7 +35,7 @@ fn test_create_session() {
 | 
			
		|||
 | 
			
		||||
    let new_session = Session {
 | 
			
		||||
        id: 123,
 | 
			
		||||
        //
 | 
			
		||||
        user_agent: None,
 | 
			
		||||
        last_seen: chrono::Utc::now().trunc_subsecs(0),
 | 
			
		||||
        user: users[0].clone(),
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -56,7 +56,7 @@ fn test_remove_session() {
 | 
			
		|||
 | 
			
		||||
    let new_session = Session {
 | 
			
		||||
        id: 123,
 | 
			
		||||
        //
 | 
			
		||||
        user_agent: None,
 | 
			
		||||
        last_seen: chrono::Utc::now().trunc_subsecs(0),
 | 
			
		||||
        user: users[0].clone(),
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -86,7 +86,7 @@ fn test_refresh_session() {
 | 
			
		|||
 | 
			
		||||
    let mut new_session = Session {
 | 
			
		||||
        id: 123,
 | 
			
		||||
        //
 | 
			
		||||
        user_agent: None,
 | 
			
		||||
        last_seen: chrono::Utc::now().trunc_subsecs(0) - TimeDelta::seconds(10),
 | 
			
		||||
        user: users[0].clone(),
 | 
			
		||||
    };
 | 
			
		||||
| 
						 | 
				
			
			@ -124,6 +124,7 @@ fn test_remove_old_sessions() {
 | 
			
		|||
        store
 | 
			
		||||
            .insert_session(&Session {
 | 
			
		||||
                id: i as i64,
 | 
			
		||||
                user_agent: None,
 | 
			
		||||
                last_seen: ts,
 | 
			
		||||
                user: users[0].clone(),
 | 
			
		||||
            })
 | 
			
		||||
| 
						 | 
				
			
			@ -143,6 +144,7 @@ fn test_remove_old_sessions() {
 | 
			
		|||
        store.get_session(0).expect("get session shouldn't fail"),
 | 
			
		||||
        Some(Session {
 | 
			
		||||
            id: 0,
 | 
			
		||||
            user_agent: None,
 | 
			
		||||
            last_seen: now,
 | 
			
		||||
            user: users[0].clone(),
 | 
			
		||||
        })
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
[package]
 | 
			
		||||
name = "otterd"
 | 
			
		||||
version = "0.1.0"
 | 
			
		||||
edition = "2021"
 | 
			
		||||
 | 
			
		||||
[dependencies]
 | 
			
		||||
gpodder = { path = "../gpodder" }
 | 
			
		||||
gpodder_sqlite = { path = "../gpodder_sqlite" }
 | 
			
		||||
 | 
			
		||||
chrono = { workspace = true, features = ["serde"] }
 | 
			
		||||
rand = { workspace = true }
 | 
			
		||||
tracing = { workspace = true }
 | 
			
		||||
 | 
			
		||||
serde = { version = "1.0.218", features = ["derive"] }
 | 
			
		||||
figment = { version = "0.10.19", features = ["env", "toml"] }
 | 
			
		||||
clap = { version = "4.5.30", features = ["derive", "env"] }
 | 
			
		||||
 | 
			
		||||
tower-http = { version = "0.6.2", features = ["set-header", "trace"] }
 | 
			
		||||
axum = { version = "0.8.1", features = ["macros"] }
 | 
			
		||||
axum-extra = { version = "0.10", features = ["cookie", "typed-header"] }
 | 
			
		||||
axum-range = "0.5.0"
 | 
			
		||||
 | 
			
		||||
cookie = "0.18.1"
 | 
			
		||||
http-body-util = "0.1.3"
 | 
			
		||||
tokio = { version = "1.43.0", features = ["full"] }
 | 
			
		||||
tracing-subscriber = "0.3.19"
 | 
			
		||||
tera = "1.20.0"
 | 
			
		||||
| 
						 | 
				
			
			@ -20,7 +20,7 @@ use crate::server::{
 | 
			
		|||
pub fn router() -> Router<Context> {
 | 
			
		||||
    Router::new()
 | 
			
		||||
        .route("/{username}/login.json", post(post_login))
 | 
			
		||||
        .route("/{username}/logout.json", post(post_logout))
 | 
			
		||||
        .route("/{_username}/logout.json", post(post_logout))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn post_login(
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ async fn post_login(
 | 
			
		|||
 | 
			
		||||
async fn post_logout(
 | 
			
		||||
    State(ctx): State<Context>,
 | 
			
		||||
    Path(username): Path<String>,
 | 
			
		||||
    Path(_username): Path<String>,
 | 
			
		||||
    jar: CookieJar,
 | 
			
		||||
) -> AppResult<CookieJar> {
 | 
			
		||||
    if let Some(session_id) = jar.get(SESSION_ID_COOKIE) {
 | 
			
		||||
| 
						 | 
				
			
			@ -19,7 +19,10 @@ pub fn router(ctx: Context) -> Router<Context> {
 | 
			
		|||
    Router::new()
 | 
			
		||||
        .route("/{username}", get(get_devices))
 | 
			
		||||
        .route("/{username}/{id}", post(post_device))
 | 
			
		||||
        .layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
 | 
			
		||||
        .layer(middleware::from_fn_with_state(
 | 
			
		||||
            ctx.clone(),
 | 
			
		||||
            auth_api_middleware,
 | 
			
		||||
        ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async fn get_devices(
 | 
			
		||||
| 
						 | 
				
			
			@ -22,7 +22,10 @@ pub fn router(ctx: Context) -> Router<Context> {
 | 
			
		|||
            "/{username}/{id}",
 | 
			
		||||
            post(post_subscription_changes).get(get_subscription_changes),
 | 
			
		||||
        )
 | 
			
		||||
        .layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
 | 
			
		||||
        .layer(middleware::from_fn_with_state(
 | 
			
		||||
            ctx.clone(),
 | 
			
		||||
            auth_api_middleware,
 | 
			
		||||
        ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn post_subscription_changes(
 | 
			
		||||
| 
						 | 
				
			
			@ -21,7 +21,10 @@ pub fn router(ctx: Context) -> Router<Context> {
 | 
			
		|||
            "/{username}",
 | 
			
		||||
            get(get_sync_status).post(post_sync_status_changes),
 | 
			
		||||
        )
 | 
			
		||||
        .layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
 | 
			
		||||
        .layer(middleware::from_fn_with_state(
 | 
			
		||||
            ctx.clone(),
 | 
			
		||||
            auth_api_middleware,
 | 
			
		||||
        ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_sync_status(
 | 
			
		||||
| 
						 | 
				
			
			@ -34,7 +34,7 @@ impl<'de> Deserialize<'de> for StringWithFormat {
 | 
			
		|||
    {
 | 
			
		||||
        struct StrVisitor;
 | 
			
		||||
 | 
			
		||||
        impl<'de> Visitor<'de> for StrVisitor {
 | 
			
		||||
        impl Visitor<'_> for StrVisitor {
 | 
			
		||||
            type Value = StringWithFormat;
 | 
			
		||||
 | 
			
		||||
            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
 | 
			
		||||
| 
						 | 
				
			
			@ -18,7 +18,10 @@ pub fn router(ctx: Context) -> Router<Context> {
 | 
			
		|||
            get(get_device_subscriptions).put(put_device_subscriptions),
 | 
			
		||||
        )
 | 
			
		||||
        .route("/{username}", get(get_user_subscriptions))
 | 
			
		||||
        .layer(middleware::from_fn_with_state(ctx.clone(), auth_api_middleware))
 | 
			
		||||
        .layer(middleware::from_fn_with_state(
 | 
			
		||||
            ctx.clone(),
 | 
			
		||||
            auth_api_middleware,
 | 
			
		||||
        ))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
pub async fn get_device_subscriptions(
 | 
			
		||||
| 
						 | 
				
			
			@ -61,7 +61,6 @@ struct LoginForm {
 | 
			
		|||
async fn post_login(
 | 
			
		||||
    State(ctx): State<Context>,
 | 
			
		||||
    user_agent: Option<TypedHeader<UserAgent>>,
 | 
			
		||||
    _headers: HeaderMap,
 | 
			
		||||
    jar: CookieJar,
 | 
			
		||||
    Form(login): Form<LoginForm>,
 | 
			
		||||
) -> AppResult<Response> {
 | 
			
		||||
		Loading…
	
		Reference in New Issue