Skip to content

Commit ea06e23

Browse files
Merge pull request #710 from madeofpendletonwool/pod-mem
Updates and minor bugs
2 parents b76191b + e3fabff commit ea06e23

File tree

23 files changed

+957
-420
lines changed

23 files changed

+957
-420
lines changed

rust-api/Cargo.lock

Lines changed: 383 additions & 168 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

rust-api/Cargo.toml

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,27 @@ rust-version = "1.89"
66

77
[dependencies]
88
# Web Framework
9-
axum = { version = "0.8.4", features = ["macros", "multipart", "ws"] }
10-
tokio = { version = "1.47.1", features = ["full"] }
9+
axum = { version = "0.8.6", features = ["macros", "multipart", "ws"] }
10+
tokio = { version = "1.48.0", features = ["full"] }
1111
tower = { version = "0.5.2", features = ["util", "timeout", "load-shed", "limit"] }
1212
tower-http = { version = "0.6.6", features = ["fs", "trace", "cors", "compression-gzip"] }
1313

1414
# Serialization
15-
serde = { version = "1.0.225", features = ["derive"] }
15+
serde = { version = "1.0.228", features = ["derive"] }
1616
serde_json = "1.0.145"
1717

1818
# Database
1919
sqlx = { version = "0.8.6", features = ["runtime-tokio-rustls", "postgres", "mysql", "uuid", "chrono", "json", "bigdecimal"] }
20-
bigdecimal = "0.4.8"
20+
bigdecimal = "0.4.9"
2121

2222
# Redis/Valkey
23-
redis = { version = "0.32.5", features = ["aio", "tokio-comp"] }
23+
redis = { version = "0.32.7", features = ["aio", "tokio-comp"] }
2424

2525
# HTTP Client
26-
reqwest = { version = "0.12.23", features = ["json", "rustls-tls", "stream", "cookies"] }
26+
reqwest = { version = "0.12.24", features = ["json", "rustls-tls", "stream", "cookies"] }
2727

2828
# Configuration and Environment
29-
config = "0.15.16"
29+
config = "0.15.18"
3030
dotenvy = "0.15.7"
3131

3232
# Logging
@@ -35,18 +35,18 @@ tracing-subscriber = { version = "0.3.20", features = ["env-filter"] }
3535

3636
# Utilities
3737
uuid = { version = "1.18.1", features = ["v4", "serde"] }
38-
chrono = { version = "0.4.41", features = ["serde"] }
38+
chrono = { version = "0.4.42", features = ["serde"] }
3939
chrono-tz = "0.10.0"
40-
anyhow = "1.0.99"
41-
thiserror = "2.0.16"
40+
anyhow = "1.0.100"
41+
thiserror = "2.0.17"
4242
async-trait = "0.1.89"
4343
base64 = "0.22.1"
4444
lazy_static = "1.5.0"
4545
urlencoding = "2.1.3"
4646

4747
# Authentication and Crypto
48-
argon2 = "0.6.0-rc.0"
49-
jsonwebtoken = "9.3.1"
48+
argon2 = "0.6.0-rc.1"
49+
jsonwebtoken = { version = "10.1.0", features = ["aws_lc_rs"] }
5050
rand = "0.9.2"
5151

5252
# MFA/TOTP Support
@@ -60,7 +60,7 @@ fernet = "0.2.2"
6060
# RSS/Feed Processing
6161
feed-rs = "2.3.1"
6262
url = "2.5.7"
63-
regex = "1.11.2"
63+
regex = "1.12.2"
6464

6565
# Audio metadata tagging
6666
id3 = "1.16.3"
@@ -74,7 +74,7 @@ lettre = { version = "0.11.18", default-features = false, features = ["tokio1-ru
7474
hyper = "1.7.0"
7575

7676
# Background Tasks and Task Management
77-
tokio-cron-scheduler = "0.14.0"
77+
tokio-cron-scheduler = "0.15.1"
7878
tokio-stream = "0.1.17"
7979
futures = "0.3.31"
8080

rust-api/src/database.rs

Lines changed: 62 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14953,15 +14953,24 @@ impl DatabasePool {
1495314953
// Subscribe to person - matches Python subscribe_to_person function exactly
1495414954
pub async fn subscribe_to_person(&self, user_id: i32, person_id: i32, person_name: &str, person_img: &str, podcast_id: i32) -> AppResult<i32> {
1495514955
println!("Subscribing user {} to person {}: {}", user_id, person_id, person_name);
14956-
14956+
1495714957
// Check if person already exists for this user and handle accordingly
1495814958
let result = match self {
1495914959
DatabasePool::Postgres(pool) => {
14960-
let existing = sqlx::query(r#"SELECT personid FROM "People" WHERE userid = $1 AND peopledbid = $2"#)
14961-
.bind(user_id)
14962-
.bind(person_id)
14963-
.fetch_optional(pool)
14964-
.await?;
14960+
// When peopledbid is 0 or not set, use name for lookup to avoid collisions
14961+
let existing = if person_id == 0 {
14962+
sqlx::query(r#"SELECT personid FROM "People" WHERE userid = $1 AND LOWER(name) = LOWER($2)"#)
14963+
.bind(user_id)
14964+
.bind(person_name)
14965+
.fetch_optional(pool)
14966+
.await?
14967+
} else {
14968+
sqlx::query(r#"SELECT personid FROM "People" WHERE userid = $1 AND peopledbid = $2"#)
14969+
.bind(user_id)
14970+
.bind(person_id)
14971+
.fetch_optional(pool)
14972+
.await?
14973+
};
1496514974

1496614975
if let Some(row) = existing {
1496714976
let person_db_id: i32 = row.try_get("personid")?;
@@ -15013,11 +15022,20 @@ impl DatabasePool {
1501315022
}
1501415023
}
1501515024
DatabasePool::MySQL(pool) => {
15016-
let existing = sqlx::query("SELECT PersonID FROM People WHERE UserID = ? AND PeopleDBID = ?")
15017-
.bind(user_id)
15018-
.bind(person_id)
15019-
.fetch_optional(pool)
15020-
.await?;
15025+
// When peopledbid is 0 or not set, use name for lookup to avoid collisions
15026+
let existing = if person_id == 0 {
15027+
sqlx::query("SELECT PersonID FROM People WHERE UserID = ? AND LOWER(Name) = LOWER(?)")
15028+
.bind(user_id)
15029+
.bind(person_name)
15030+
.fetch_optional(pool)
15031+
.await?
15032+
} else {
15033+
sqlx::query("SELECT PersonID FROM People WHERE UserID = ? AND PeopleDBID = ?")
15034+
.bind(user_id)
15035+
.bind(person_id)
15036+
.fetch_optional(pool)
15037+
.await?
15038+
};
1502115039

1502215040
if let Some(row) = existing {
1502315041
let person_db_id: i32 = row.try_get("PersonID")?;
@@ -15075,24 +15093,44 @@ impl DatabasePool {
1507515093
// Unsubscribe from person - matches Python unsubscribe_from_person function exactly
1507615094
pub async fn unsubscribe_from_person(&self, user_id: i32, person_id: i32, person_name: &str) -> AppResult<bool> {
1507715095
println!("Unsubscribing user {} from person {}: {}", user_id, person_id, person_name);
15078-
15096+
1507915097
// Find and delete the person record
1508015098
let rows_affected = match self {
1508115099
DatabasePool::Postgres(pool) => {
15082-
sqlx::query(r#"DELETE FROM "People" WHERE userid = $1 AND peopledbid = $2"#)
15083-
.bind(user_id)
15084-
.bind(person_id)
15085-
.execute(pool)
15086-
.await?
15087-
.rows_affected()
15100+
// When peopledbid is 0 or not set, use name for lookup to avoid collisions
15101+
if person_id == 0 {
15102+
sqlx::query(r#"DELETE FROM "People" WHERE userid = $1 AND LOWER(name) = LOWER($2)"#)
15103+
.bind(user_id)
15104+
.bind(person_name)
15105+
.execute(pool)
15106+
.await?
15107+
.rows_affected()
15108+
} else {
15109+
sqlx::query(r#"DELETE FROM "People" WHERE userid = $1 AND peopledbid = $2"#)
15110+
.bind(user_id)
15111+
.bind(person_id)
15112+
.execute(pool)
15113+
.await?
15114+
.rows_affected()
15115+
}
1508815116
}
1508915117
DatabasePool::MySQL(pool) => {
15090-
sqlx::query("DELETE FROM People WHERE UserID = ? AND PeopleDBID = ?")
15091-
.bind(user_id)
15092-
.bind(person_id)
15093-
.execute(pool)
15094-
.await?
15095-
.rows_affected()
15118+
// When peopledbid is 0 or not set, use name for lookup to avoid collisions
15119+
if person_id == 0 {
15120+
sqlx::query("DELETE FROM People WHERE UserID = ? AND LOWER(Name) = LOWER(?)")
15121+
.bind(user_id)
15122+
.bind(person_name)
15123+
.execute(pool)
15124+
.await?
15125+
.rows_affected()
15126+
} else {
15127+
sqlx::query("DELETE FROM People WHERE UserID = ? AND PeopleDBID = ?")
15128+
.bind(user_id)
15129+
.bind(person_id)
15130+
.execute(pool)
15131+
.await?
15132+
.rows_affected()
15133+
}
1509615134
}
1509715135
};
1509815136

rust-api/src/handlers/feed.rs

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,12 @@ pub struct RssKeyInfo {
8181
}
8282

8383
fn extract_domain_from_request(request: &Request<axum::body::Body>) -> String {
84-
// Check HOSTNAME environment variable first (includes scheme and port)
85-
if let Ok(hostname) = std::env::var("HOSTNAME") {
86-
return hostname;
84+
// Check SERVER_URL environment variable first (includes scheme and port)
85+
// Note: We use SERVER_URL instead of HOSTNAME because Docker automatically sets HOSTNAME to the container ID
86+
// The startup script saves the user's HOSTNAME value to SERVER_URL before Docker overwrites it
87+
if let Ok(server_url) = std::env::var("SERVER_URL") {
88+
tracing::info!("Using SERVER_URL env var: {}", server_url);
89+
return server_url;
8790
}
8891

8992
// Try to get domain from Host header
@@ -95,10 +98,13 @@ fn extract_domain_from_request(request: &Request<axum::body::Body>) -> String {
9598
.and_then(|h| h.to_str().ok())
9699
.unwrap_or("http");
97100

98-
return format!("{}://{}", scheme, host_str);
101+
let domain = format!("{}://{}", scheme, host_str);
102+
tracing::info!("Using Host header: {}", domain);
103+
return domain;
99104
}
100105
}
101106

102107
// Fallback
108+
tracing::info!("Using fallback domain");
103109
"http://localhost:8041".to_string()
104110
}

rust-api/src/handlers/podcasts.rs

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2014,26 +2014,52 @@ pub async fn stream_episode(
20142014
Query(query): Query<StreamQuery>,
20152015
) -> Result<axum::response::Response, AppError> {
20162016
let api_key = &query.api_key;
2017-
2018-
// Try API key validation first
2017+
println!("Stream request for episode {} with api_key {} and user_id {}", episode_id, api_key, query.user_id);
2018+
2019+
// Try RSS key validation FIRST (RSS keys are used in RSS feeds for streaming)
20192020
let mut is_valid = false;
20202021
let mut is_web_key = false;
20212022
let mut key_user_id = None;
2022-
2023-
if let Ok(_) = validate_api_key(&state, api_key).await {
2024-
is_valid = true;
2025-
is_web_key = state.db_pool.is_web_key(api_key).await?;
2026-
key_user_id = Some(state.db_pool.get_user_id_from_api_key(api_key).await?);
2023+
2024+
println!("Trying RSS key validation first");
2025+
match state.db_pool.get_rss_key_if_valid(api_key, None).await {
2026+
Ok(Some(rss_info)) => {
2027+
println!("Valid RSS key for user {}", rss_info.user_id);
2028+
is_valid = true;
2029+
// Don't set key_user_id for RSS keys - they don't need permission checks
2030+
}
2031+
Ok(None) => {
2032+
println!("Not an RSS key, trying regular API key");
2033+
}
2034+
Err(e) => {
2035+
println!("RSS key validation error: {}", e);
2036+
}
20272037
}
2028-
2029-
// If not a valid API key, try RSS key validation
2038+
2039+
// If not a valid RSS key, try regular API key validation
20302040
if !is_valid {
2031-
if let Ok(Some(_rss_info)) = state.db_pool.get_rss_key_if_valid(api_key, None).await {
2032-
// RSS key is valid - allow access for any user (as per requirements)
2033-
is_valid = true;
2041+
match validate_api_key(&state, api_key).await {
2042+
Ok(_) => {
2043+
println!("Valid API key");
2044+
// Try to get user_id, but don't fail if it errors (might be cached RSS key)
2045+
match state.db_pool.get_user_id_from_api_key(api_key).await {
2046+
Ok(user_id) => {
2047+
println!("API key user_id: {}", user_id);
2048+
is_valid = true;
2049+
is_web_key = state.db_pool.is_web_key(api_key).await?;
2050+
key_user_id = Some(user_id);
2051+
}
2052+
Err(e) => {
2053+
println!("Failed to get user_id for API key (might be RSS key): {}", e);
2054+
}
2055+
}
2056+
}
2057+
Err(e) => {
2058+
println!("API key validation failed: {}", e);
2059+
}
20342060
}
20352061
}
2036-
2062+
20372063
if !is_valid {
20382064
return Err(AppError::unauthorized("Invalid API key or RSS key"));
20392065
}

startup/startup.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ export VALKEY_HOST=${VALKEY_HOST:-'valkey'}
3232
export VALKEY_PORT=${VALKEY_PORT:-'6379'}
3333
export DEFAULT_LANGUAGE=${DEFAULT_LANGUAGE:-'en'}
3434

35+
# Save user's HOSTNAME to SERVER_URL before Docker overwrites it with container ID
36+
# This preserves the user-configured server URL for RSS feed generation
37+
export SERVER_URL=${HOSTNAME}
38+
3539
# Export OIDC environment variables
3640
export OIDC_DISABLE_STANDARD_LOGIN=${OIDC_DISABLE_STANDARD_LOGIN:-'false'}
3741
export OIDC_PROVIDER_NAME=${OIDC_PROVIDER_NAME}

0 commit comments

Comments
 (0)