refactor: decoupled gpodder and server models
parent
8a5e625e6f
commit
bd51c1c768
|
@ -1,5 +1,4 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct User {
|
||||
|
@ -8,8 +7,6 @@ pub struct User {
|
|||
pub password_hash: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DeviceType {
|
||||
Desktop,
|
||||
Laptop,
|
||||
|
@ -18,7 +15,6 @@ pub enum DeviceType {
|
|||
Other,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Device {
|
||||
pub id: String,
|
||||
pub caption: String,
|
||||
|
@ -26,37 +22,28 @@ pub struct Device {
|
|||
pub subscriptions: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DevicePatch {
|
||||
pub caption: Option<String>,
|
||||
pub r#type: Option<DeviceType>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[serde(tag = "action")]
|
||||
pub enum EpisodeActionType {
|
||||
Download,
|
||||
Play {
|
||||
#[serde(default)]
|
||||
started: Option<i32>,
|
||||
position: i32,
|
||||
#[serde(default)]
|
||||
total: Option<i32>,
|
||||
},
|
||||
Delete,
|
||||
New,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct EpisodeAction {
|
||||
pub podcast: String,
|
||||
pub episode: String,
|
||||
pub timestamp: Option<DateTime<Utc>>,
|
||||
pub time_changed: DateTime<Utc>,
|
||||
#[serde(default)]
|
||||
pub device: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub action: EpisodeActionType,
|
||||
}
|
||||
|
||||
|
|
|
@ -12,6 +12,7 @@ use crate::{
|
|||
gpodder::{
|
||||
auth_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models,
|
||||
},
|
||||
Context,
|
||||
},
|
||||
|
@ -28,7 +29,7 @@ async fn get_devices(
|
|||
State(ctx): State<Context>,
|
||||
Path(username): Path<StringWithFormat>,
|
||||
Extension(user): Extension<gpodder::User>,
|
||||
) -> AppResult<Json<Vec<gpodder::Device>>> {
|
||||
) -> AppResult<Json<Vec<models::Device>>> {
|
||||
if username.format != Format::Json {
|
||||
return Err(AppError::NotFound);
|
||||
}
|
||||
|
@ -41,7 +42,7 @@ async fn get_devices(
|
|||
tokio::task::spawn_blocking(move || ctx.store.devices_for_user(&user))
|
||||
.await
|
||||
.unwrap()
|
||||
.map(Json)?,
|
||||
.map(|devices| Json(devices.into_iter().map(models::Device::from).collect()))?,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -49,13 +50,13 @@ async fn post_device(
|
|||
State(ctx): State<Context>,
|
||||
Path((_username, id)): Path<(String, StringWithFormat)>,
|
||||
Extension(user): Extension<gpodder::User>,
|
||||
Json(patch): Json<gpodder::DevicePatch>,
|
||||
Json(patch): Json<models::DevicePatch>,
|
||||
) -> AppResult<()> {
|
||||
if id.format != Format::Json {
|
||||
return Err(AppError::NotFound);
|
||||
}
|
||||
|
||||
tokio::task::spawn_blocking(move || ctx.store.update_device_info(&user, &id, patch))
|
||||
tokio::task::spawn_blocking(move || ctx.store.update_device_info(&user, &id, patch.into()))
|
||||
.await
|
||||
.unwrap()?;
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ use crate::{
|
|||
gpodder::{
|
||||
auth_middleware,
|
||||
format::{Format, StringWithFormat},
|
||||
models,
|
||||
models::UpdatedUrlsResponse,
|
||||
},
|
||||
Context,
|
||||
|
@ -33,7 +34,7 @@ async fn post_episode_actions(
|
|||
State(ctx): State<Context>,
|
||||
Path(username): Path<StringWithFormat>,
|
||||
Extension(user): Extension<gpodder::User>,
|
||||
Json(actions): Json<Vec<gpodder::EpisodeAction>>,
|
||||
Json(actions): Json<Vec<models::EpisodeAction>>,
|
||||
) -> AppResult<Json<UpdatedUrlsResponse>> {
|
||||
if username.format != Format::Json {
|
||||
return Err(AppError::NotFound);
|
||||
|
@ -43,17 +44,18 @@ async fn post_episode_actions(
|
|||
return Err(AppError::BadRequest);
|
||||
}
|
||||
|
||||
Ok(
|
||||
tokio::task::spawn_blocking(move || ctx.store.add_episode_actions(&user, actions))
|
||||
.await
|
||||
.unwrap()
|
||||
.map(|time_changed| {
|
||||
Json(UpdatedUrlsResponse {
|
||||
timestamp: time_changed.timestamp(),
|
||||
update_urls: Vec::new(),
|
||||
})
|
||||
})?,
|
||||
)
|
||||
Ok(tokio::task::spawn_blocking(move || {
|
||||
ctx.store
|
||||
.add_episode_actions(&user, actions.into_iter().map(Into::into).collect())
|
||||
})
|
||||
.await
|
||||
.unwrap()
|
||||
.map(|time_changed| {
|
||||
Json(UpdatedUrlsResponse {
|
||||
timestamp: time_changed.timestamp(),
|
||||
update_urls: Vec::new(),
|
||||
})
|
||||
})?)
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
|
@ -68,7 +70,7 @@ struct FilterQuery {
|
|||
#[derive(Serialize)]
|
||||
struct EpisodeActionsResponse {
|
||||
timestamp: i64,
|
||||
actions: Vec<gpodder::EpisodeAction>,
|
||||
actions: Vec<models::EpisodeAction>,
|
||||
}
|
||||
|
||||
async fn get_episode_actions(
|
||||
|
@ -104,7 +106,7 @@ async fn get_episode_actions(
|
|||
.map(|(ts, actions)| {
|
||||
Json(EpisodeActionsResponse {
|
||||
timestamp: ts.timestamp(),
|
||||
actions,
|
||||
actions: actions.into_iter().map(Into::into).collect(),
|
||||
})
|
||||
})?)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
use chrono::{DateTime, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::gpodder;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
pub struct SubscriptionDelta {
|
||||
pub add: Vec<String>,
|
||||
|
@ -18,3 +21,164 @@ pub struct UpdatedUrlsResponse {
|
|||
pub timestamp: i64,
|
||||
pub update_urls: Vec<(String, String)>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum DeviceType {
|
||||
Desktop,
|
||||
Laptop,
|
||||
Mobile,
|
||||
Server,
|
||||
Other,
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
pub struct Device {
|
||||
pub id: String,
|
||||
pub caption: String,
|
||||
pub r#type: DeviceType,
|
||||
pub subscriptions: i64,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct DevicePatch {
|
||||
pub caption: Option<String>,
|
||||
pub r#type: Option<DeviceType>,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[serde(tag = "action")]
|
||||
pub enum EpisodeActionType {
|
||||
Download,
|
||||
Play {
|
||||
#[serde(default)]
|
||||
started: Option<i32>,
|
||||
position: i32,
|
||||
#[serde(default)]
|
||||
total: Option<i32>,
|
||||
},
|
||||
Delete,
|
||||
New,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
pub struct EpisodeAction {
|
||||
pub podcast: String,
|
||||
pub episode: String,
|
||||
pub timestamp: Option<i64>,
|
||||
#[serde(default)]
|
||||
pub device: Option<String>,
|
||||
#[serde(flatten)]
|
||||
pub action: EpisodeActionType,
|
||||
}
|
||||
|
||||
impl From<gpodder::DeviceType> for DeviceType {
|
||||
fn from(value: gpodder::DeviceType) -> Self {
|
||||
match value {
|
||||
gpodder::DeviceType::Other => Self::Other,
|
||||
gpodder::DeviceType::Laptop => Self::Laptop,
|
||||
gpodder::DeviceType::Mobile => Self::Mobile,
|
||||
gpodder::DeviceType::Server => Self::Server,
|
||||
gpodder::DeviceType::Desktop => Self::Desktop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DeviceType> for gpodder::DeviceType {
|
||||
fn from(value: DeviceType) -> Self {
|
||||
match value {
|
||||
DeviceType::Other => gpodder::DeviceType::Other,
|
||||
DeviceType::Laptop => gpodder::DeviceType::Laptop,
|
||||
DeviceType::Mobile => gpodder::DeviceType::Mobile,
|
||||
DeviceType::Server => gpodder::DeviceType::Server,
|
||||
DeviceType::Desktop => gpodder::DeviceType::Desktop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gpodder::Device> for Device {
|
||||
fn from(value: gpodder::Device) -> Self {
|
||||
Self {
|
||||
id: value.id,
|
||||
caption: value.caption,
|
||||
r#type: value.r#type.into(),
|
||||
subscriptions: value.subscriptions,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<DevicePatch> for gpodder::DevicePatch {
|
||||
fn from(value: DevicePatch) -> Self {
|
||||
Self {
|
||||
caption: value.caption,
|
||||
r#type: value.r#type.map(Into::into),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gpodder::EpisodeActionType> for EpisodeActionType {
|
||||
fn from(value: gpodder::EpisodeActionType) -> Self {
|
||||
match value {
|
||||
gpodder::EpisodeActionType::New => Self::New,
|
||||
gpodder::EpisodeActionType::Delete => Self::Delete,
|
||||
gpodder::EpisodeActionType::Download => Self::Download,
|
||||
gpodder::EpisodeActionType::Play {
|
||||
started,
|
||||
position,
|
||||
total,
|
||||
} => Self::Play {
|
||||
started,
|
||||
position,
|
||||
total,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EpisodeActionType> for gpodder::EpisodeActionType {
|
||||
fn from(value: EpisodeActionType) -> Self {
|
||||
match value {
|
||||
EpisodeActionType::New => gpodder::EpisodeActionType::New,
|
||||
EpisodeActionType::Delete => gpodder::EpisodeActionType::Delete,
|
||||
EpisodeActionType::Download => gpodder::EpisodeActionType::Download,
|
||||
EpisodeActionType::Play {
|
||||
started,
|
||||
position,
|
||||
total,
|
||||
} => gpodder::EpisodeActionType::Play {
|
||||
started,
|
||||
position,
|
||||
total,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<gpodder::EpisodeAction> for EpisodeAction {
|
||||
fn from(value: gpodder::EpisodeAction) -> Self {
|
||||
Self {
|
||||
podcast: value.podcast,
|
||||
episode: value.episode,
|
||||
timestamp: value.timestamp.map(|ts| ts.timestamp()),
|
||||
device: value.device,
|
||||
action: value.action.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EpisodeAction> for gpodder::EpisodeAction {
|
||||
fn from(value: EpisodeAction) -> Self {
|
||||
Self {
|
||||
podcast: value.podcast,
|
||||
episode: value.episode,
|
||||
// TODO remove this unwrap
|
||||
timestamp: value
|
||||
.timestamp
|
||||
.map(|ts| DateTime::from_timestamp(ts, 0).unwrap()),
|
||||
device: value.device,
|
||||
action: value.action.into(),
|
||||
time_changed: DateTime::<Utc>::MIN_UTC,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue