refactor: decide to not create separate table for subscriptions
parent
3e79bec974
commit
064365fb4f
|
@ -29,41 +29,32 @@ create table devices (
|
|||
unique (user_id, device_id)
|
||||
);
|
||||
|
||||
create table subscriptions (
|
||||
id integer primary key not null,
|
||||
|
||||
url text unique not null
|
||||
);
|
||||
|
||||
create table device_subscriptions (
|
||||
id integer primary key not null,
|
||||
|
||||
device_id bigint not null
|
||||
references devices (id)
|
||||
on delete cascade,
|
||||
subscription_id bigint not null
|
||||
references subscriptions (id)
|
||||
on delete cascade
|
||||
|
||||
podcast_url text not null,
|
||||
|
||||
time_changed bigint not null default 0,
|
||||
deleted boolean not null default false,
|
||||
|
||||
unique (device_id, subscription_id)
|
||||
unique (device_id, podcast_url)
|
||||
);
|
||||
|
||||
create table episode_actions (
|
||||
id integer primary key not null,
|
||||
|
||||
subscription_id bigint not null
|
||||
references subscriptions (id)
|
||||
on delete set null,
|
||||
|
||||
-- Can be null, as the device is not always provided
|
||||
device_id bigint
|
||||
references devices (id)
|
||||
on delete set null,
|
||||
|
||||
podcast_url text not null,
|
||||
episode_url text not null,
|
||||
|
||||
timestamp bigint,
|
||||
action text not null,
|
||||
started integer,
|
||||
|
|
|
@ -5,7 +5,7 @@ mod schema;
|
|||
pub use models::device::{Device, DeviceType, NewDevice};
|
||||
pub use models::episode_action::{ActionType, EpisodeAction, NewEpisodeAction};
|
||||
pub use models::session::Session;
|
||||
pub use models::subscription::{NewSubscription, Subscription};
|
||||
pub use models::device_subscription::{NewDeviceSubscription, DeviceSubscription};
|
||||
pub use models::user::{NewUser, User};
|
||||
|
||||
pub use repository::SqliteRepository;
|
||||
|
|
|
@ -6,39 +6,39 @@ use serde::{Deserialize, Serialize};
|
|||
use crate::db::{schema::*, DbPool, DbResult};
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Queryable, Selectable)]
|
||||
#[diesel(table_name = subscriptions)]
|
||||
#[diesel(table_name = device_subscriptions)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct Subscription {
|
||||
pub struct DeviceSubscription {
|
||||
pub id: i64,
|
||||
pub device_id: i64,
|
||||
pub url: String,
|
||||
pub podcast_url: String,
|
||||
pub time_changed: i64,
|
||||
pub deleted: bool,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Insertable)]
|
||||
#[diesel(table_name = subscriptions)]
|
||||
#[diesel(table_name = device_subscriptions)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct NewSubscription {
|
||||
pub struct NewDeviceSubscription {
|
||||
pub device_id: i64,
|
||||
pub url: String,
|
||||
pub podcast_url: String,
|
||||
pub time_changed: i64,
|
||||
pub deleted: bool,
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
impl DeviceSubscription {
|
||||
pub fn for_device(pool: &DbPool, device_id: i64) -> DbResult<Vec<String>> {
|
||||
Ok(subscriptions::table
|
||||
.select(subscriptions::url)
|
||||
.filter(subscriptions::device_id.eq(device_id))
|
||||
Ok(device_subscriptions::table
|
||||
.select(device_subscriptions::podcast_url)
|
||||
.filter(device_subscriptions::device_id.eq(device_id))
|
||||
.get_results(&mut pool.get()?)?)
|
||||
}
|
||||
|
||||
pub fn for_user(pool: &DbPool, user_id: i64) -> DbResult<Vec<String>> {
|
||||
Ok(subscriptions::table
|
||||
Ok(device_subscriptions::table
|
||||
.inner_join(devices::table)
|
||||
.filter(devices::user_id.eq(user_id))
|
||||
.select(subscriptions::url)
|
||||
.select(device_subscriptions::podcast_url)
|
||||
.distinct()
|
||||
.get_results(&mut pool.get()?)?)
|
||||
}
|
||||
|
@ -55,9 +55,9 @@ impl Subscription {
|
|||
// on conflict. Therefore, we instead calculate which URLs should be inserted and which
|
||||
// updated, so we avoid conflicts.
|
||||
let urls: HashSet<String> = urls.into_iter().collect();
|
||||
let urls_in_db: HashSet<String> = subscriptions::table
|
||||
.select(subscriptions::url)
|
||||
.filter(subscriptions::device_id.eq(device_id))
|
||||
let urls_in_db: HashSet<String> = device_subscriptions::table
|
||||
.select(device_subscriptions::podcast_url)
|
||||
.filter(device_subscriptions::device_id.eq(device_id))
|
||||
.get_results(&mut pool.get()?)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
@ -75,41 +75,41 @@ impl Subscription {
|
|||
|
||||
// Mark the URLs to delete as properly deleted
|
||||
diesel::update(
|
||||
subscriptions::table.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::table.filter(
|
||||
device_subscriptions::device_id
|
||||
.eq(device_id)
|
||||
.and(subscriptions::url.eq_any(urls_to_delete)),
|
||||
.and(device_subscriptions::podcast_url.eq_any(urls_to_delete)),
|
||||
),
|
||||
)
|
||||
.set((
|
||||
subscriptions::deleted.eq(true),
|
||||
subscriptions::time_changed.eq(timestamp),
|
||||
device_subscriptions::deleted.eq(true),
|
||||
device_subscriptions::time_changed.eq(timestamp),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
// Update the existing deleted URLs that are reinserted as no longer deleted
|
||||
diesel::update(
|
||||
subscriptions::table.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::table.filter(
|
||||
device_subscriptions::device_id
|
||||
.eq(device_id)
|
||||
.and(subscriptions::url.eq_any(urls_to_update))
|
||||
.and(subscriptions::deleted.eq(true)),
|
||||
.and(device_subscriptions::podcast_url.eq_any(urls_to_update))
|
||||
.and(device_subscriptions::deleted.eq(true)),
|
||||
),
|
||||
)
|
||||
.set((
|
||||
subscriptions::deleted.eq(false),
|
||||
subscriptions::time_changed.eq(timestamp),
|
||||
device_subscriptions::deleted.eq(false),
|
||||
device_subscriptions::time_changed.eq(timestamp),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
// Insert the new values into the database
|
||||
diesel::insert_into(subscriptions::table)
|
||||
diesel::insert_into(device_subscriptions::table)
|
||||
.values(
|
||||
urls_to_insert
|
||||
.into_iter()
|
||||
.map(|url| NewSubscription {
|
||||
.map(|url| NewDeviceSubscription {
|
||||
device_id,
|
||||
url: url.to_string(),
|
||||
podcast_url: url.to_string(),
|
||||
deleted: false,
|
||||
time_changed: timestamp,
|
||||
})
|
||||
|
@ -134,9 +134,9 @@ impl Subscription {
|
|||
let removed: HashSet<_> = removed.into_iter().collect();
|
||||
|
||||
pool.get()?.transaction(|conn| {
|
||||
let urls_in_db: HashSet<String> = subscriptions::table
|
||||
.select(subscriptions::url)
|
||||
.filter(subscriptions::device_id.eq(device_id))
|
||||
let urls_in_db: HashSet<String> = device_subscriptions::table
|
||||
.select(device_subscriptions::podcast_url)
|
||||
.filter(device_subscriptions::device_id.eq(device_id))
|
||||
.get_results(&mut pool.get()?)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
@ -148,16 +148,16 @@ impl Subscription {
|
|||
let urls_to_delete = removed.intersection(&urls_in_db);
|
||||
|
||||
diesel::update(
|
||||
subscriptions::table.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::table.filter(
|
||||
device_subscriptions::device_id
|
||||
.eq(device_id)
|
||||
.and(subscriptions::url.eq_any(urls_to_delete))
|
||||
.and(subscriptions::deleted.eq(false)),
|
||||
.and(device_subscriptions::podcast_url.eq_any(urls_to_delete))
|
||||
.and(device_subscriptions::deleted.eq(false)),
|
||||
),
|
||||
)
|
||||
.set((
|
||||
subscriptions::deleted.eq(true),
|
||||
subscriptions::time_changed.eq(timestamp),
|
||||
device_subscriptions::deleted.eq(true),
|
||||
device_subscriptions::time_changed.eq(timestamp),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
|
@ -166,16 +166,16 @@ impl Subscription {
|
|||
let urls_to_update = added.intersection(&urls_in_db);
|
||||
|
||||
diesel::update(
|
||||
subscriptions::table.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::table.filter(
|
||||
device_subscriptions::device_id
|
||||
.eq(device_id)
|
||||
.and(subscriptions::url.eq_any(urls_to_update))
|
||||
.and(subscriptions::deleted.eq(true)),
|
||||
.and(device_subscriptions::podcast_url.eq_any(urls_to_update))
|
||||
.and(device_subscriptions::deleted.eq(true)),
|
||||
),
|
||||
)
|
||||
.set((
|
||||
subscriptions::deleted.eq(false),
|
||||
subscriptions::time_changed.eq(timestamp),
|
||||
device_subscriptions::deleted.eq(false),
|
||||
device_subscriptions::time_changed.eq(timestamp),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
|
@ -183,13 +183,13 @@ impl Subscription {
|
|||
// added list
|
||||
let urls_to_insert = added.difference(&urls_in_db);
|
||||
|
||||
diesel::insert_into(subscriptions::table)
|
||||
diesel::insert_into(device_subscriptions::table)
|
||||
.values(
|
||||
urls_to_insert
|
||||
.into_iter()
|
||||
.map(|url| NewSubscription {
|
||||
.map(|url| NewDeviceSubscription {
|
||||
device_id,
|
||||
url: url.to_string(),
|
||||
podcast_url: url.to_string(),
|
||||
deleted: false,
|
||||
time_changed: timestamp,
|
||||
})
|
||||
|
@ -206,12 +206,12 @@ impl Subscription {
|
|||
device_id: i64,
|
||||
timestamp: i64,
|
||||
) -> DbResult<Vec<Self>> {
|
||||
Ok(subscriptions::table
|
||||
Ok(device_subscriptions::table
|
||||
.select(Self::as_select())
|
||||
.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::device_id
|
||||
.eq(device_id)
|
||||
.and(subscriptions::time_changed.ge(timestamp)),
|
||||
.and(device_subscriptions::time_changed.ge(timestamp)),
|
||||
)
|
||||
.get_results(&mut pool.get()?)?)
|
||||
}
|
|
@ -19,9 +19,9 @@ use crate::db::{schema::*, DbPool, DbResult};
|
|||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct EpisodeAction {
|
||||
id: i64,
|
||||
subscription_id: i64,
|
||||
device_id: Option<i64>,
|
||||
episode: String,
|
||||
podcast_url: String,
|
||||
episode_url: String,
|
||||
timestamp: Option<i64>,
|
||||
action: ActionType,
|
||||
started: Option<i32>,
|
||||
|
@ -33,9 +33,9 @@ pub struct EpisodeAction {
|
|||
#[diesel(table_name = episode_actions)]
|
||||
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
|
||||
pub struct NewEpisodeAction {
|
||||
subscription_id: i64,
|
||||
device_id: Option<i64>,
|
||||
episode: String,
|
||||
podcast_url: String,
|
||||
episode_url: String,
|
||||
timestamp: Option<i64>,
|
||||
action: ActionType,
|
||||
started: Option<i32>,
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
pub mod device;
|
||||
pub mod device_subscription;
|
||||
pub mod episode_action;
|
||||
pub mod session;
|
||||
pub mod subscription;
|
||||
pub mod user;
|
||||
|
|
|
@ -13,10 +13,10 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
&self,
|
||||
user: &gpodder::User,
|
||||
) -> Result<Vec<String>, gpodder::AuthErr> {
|
||||
Ok(subscriptions::table
|
||||
Ok(device_subscriptions::table
|
||||
.inner_join(devices::table)
|
||||
.filter(devices::user_id.eq(user.id))
|
||||
.select(subscriptions::url)
|
||||
.select(device_subscriptions::podcast_url)
|
||||
.distinct()
|
||||
.get_results(&mut self.pool.get()?)?)
|
||||
}
|
||||
|
@ -26,14 +26,14 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
user: &gpodder::User,
|
||||
device_id: &str,
|
||||
) -> Result<Vec<String>, gpodder::AuthErr> {
|
||||
Ok(subscriptions::table
|
||||
Ok(device_subscriptions::table
|
||||
.inner_join(devices::table)
|
||||
.filter(
|
||||
devices::user_id
|
||||
.eq(user.id)
|
||||
.and(devices::device_id.eq(device_id)),
|
||||
)
|
||||
.select(subscriptions::url)
|
||||
.select(device_subscriptions::podcast_url)
|
||||
.get_results(&mut self.pool.get()?)?)
|
||||
}
|
||||
|
||||
|
@ -61,9 +61,9 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
// on conflict. Therefore, we instead calculate which URLs should be inserted and which
|
||||
// updated, so we avoid conflicts.
|
||||
let urls: HashSet<String> = urls.into_iter().collect();
|
||||
let urls_in_db: HashSet<String> = subscriptions::table
|
||||
.select(subscriptions::url)
|
||||
.filter(subscriptions::device_id.eq(device.id))
|
||||
let urls_in_db: HashSet<String> = device_subscriptions::table
|
||||
.select(device_subscriptions::podcast_url)
|
||||
.filter(device_subscriptions::device_id.eq(device.id))
|
||||
.get_results(conn)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
@ -81,41 +81,41 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
|
||||
// Mark the URLs to delete as properly deleted
|
||||
diesel::update(
|
||||
subscriptions::table.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::table.filter(
|
||||
device_subscriptions::device_id
|
||||
.eq(device.id)
|
||||
.and(subscriptions::url.eq_any(urls_to_delete)),
|
||||
.and(device_subscriptions::podcast_url.eq_any(urls_to_delete)),
|
||||
),
|
||||
)
|
||||
.set((
|
||||
subscriptions::deleted.eq(true),
|
||||
subscriptions::time_changed.eq(timestamp),
|
||||
device_subscriptions::deleted.eq(true),
|
||||
device_subscriptions::time_changed.eq(timestamp),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
// Update the existing deleted URLs that are reinserted as no longer deleted
|
||||
diesel::update(
|
||||
subscriptions::table.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::table.filter(
|
||||
device_subscriptions::device_id
|
||||
.eq(device.id)
|
||||
.and(subscriptions::url.eq_any(urls_to_update))
|
||||
.and(subscriptions::deleted.eq(true)),
|
||||
.and(device_subscriptions::podcast_url.eq_any(urls_to_update))
|
||||
.and(device_subscriptions::deleted.eq(true)),
|
||||
),
|
||||
)
|
||||
.set((
|
||||
subscriptions::deleted.eq(false),
|
||||
subscriptions::time_changed.eq(timestamp),
|
||||
device_subscriptions::deleted.eq(false),
|
||||
device_subscriptions::time_changed.eq(timestamp),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
// Insert the new values into the database
|
||||
diesel::insert_into(subscriptions::table)
|
||||
diesel::insert_into(device_subscriptions::table)
|
||||
.values(
|
||||
urls_to_insert
|
||||
.into_iter()
|
||||
.map(|url| db::NewSubscription {
|
||||
.map(|url| db::NewDeviceSubscription {
|
||||
device_id: device.id,
|
||||
url: url.to_string(),
|
||||
podcast_url: url.to_string(),
|
||||
deleted: false,
|
||||
time_changed: timestamp,
|
||||
})
|
||||
|
@ -154,9 +154,9 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
)
|
||||
.get_result(conn)?;
|
||||
|
||||
let urls_in_db: HashSet<String> = subscriptions::table
|
||||
.select(subscriptions::url)
|
||||
.filter(subscriptions::device_id.eq(device.id))
|
||||
let urls_in_db: HashSet<String> = device_subscriptions::table
|
||||
.select(device_subscriptions::podcast_url)
|
||||
.filter(device_subscriptions::device_id.eq(device.id))
|
||||
.get_results(conn)?
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
@ -168,16 +168,16 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
let urls_to_delete = remove.intersection(&urls_in_db);
|
||||
|
||||
diesel::update(
|
||||
subscriptions::table.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::table.filter(
|
||||
device_subscriptions::device_id
|
||||
.eq(device.id)
|
||||
.and(subscriptions::url.eq_any(urls_to_delete))
|
||||
.and(subscriptions::deleted.eq(false)),
|
||||
.and(device_subscriptions::podcast_url.eq_any(urls_to_delete))
|
||||
.and(device_subscriptions::deleted.eq(false)),
|
||||
),
|
||||
)
|
||||
.set((
|
||||
subscriptions::deleted.eq(true),
|
||||
subscriptions::time_changed.eq(timestamp),
|
||||
device_subscriptions::deleted.eq(true),
|
||||
device_subscriptions::time_changed.eq(timestamp),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
|
@ -186,16 +186,16 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
let urls_to_update = add.intersection(&urls_in_db);
|
||||
|
||||
diesel::update(
|
||||
subscriptions::table.filter(
|
||||
subscriptions::device_id
|
||||
device_subscriptions::table.filter(
|
||||
device_subscriptions::device_id
|
||||
.eq(device.id)
|
||||
.and(subscriptions::url.eq_any(urls_to_update))
|
||||
.and(subscriptions::deleted.eq(true)),
|
||||
.and(device_subscriptions::podcast_url.eq_any(urls_to_update))
|
||||
.and(device_subscriptions::deleted.eq(true)),
|
||||
),
|
||||
)
|
||||
.set((
|
||||
subscriptions::deleted.eq(false),
|
||||
subscriptions::time_changed.eq(timestamp),
|
||||
device_subscriptions::deleted.eq(false),
|
||||
device_subscriptions::time_changed.eq(timestamp),
|
||||
))
|
||||
.execute(conn)?;
|
||||
|
||||
|
@ -203,13 +203,13 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
// added list
|
||||
let urls_to_insert = add.difference(&urls_in_db);
|
||||
|
||||
diesel::insert_into(subscriptions::table)
|
||||
diesel::insert_into(device_subscriptions::table)
|
||||
.values(
|
||||
urls_to_insert
|
||||
.into_iter()
|
||||
.map(|url| db::NewSubscription {
|
||||
.map(|url| db::NewDeviceSubscription {
|
||||
device_id: device.id,
|
||||
url: url.to_string(),
|
||||
podcast_url: url.to_string(),
|
||||
deleted: false,
|
||||
time_changed: timestamp,
|
||||
})
|
||||
|
@ -231,23 +231,23 @@ impl gpodder::SubscriptionRepository for SqliteRepository {
|
|||
) -> Result<(i64, Vec<String>, Vec<String>), gpodder::AuthErr> {
|
||||
let (mut timestamp, mut added, mut removed) = (0, Vec::new(), Vec::new());
|
||||
|
||||
let query = subscriptions::table
|
||||
let query = device_subscriptions::table
|
||||
.inner_join(devices::table)
|
||||
.filter(
|
||||
devices::user_id
|
||||
.eq(user.id)
|
||||
.and(devices::device_id.eq(device_id))
|
||||
.and(subscriptions::time_changed.ge(since)),
|
||||
.and(device_subscriptions::time_changed.ge(since)),
|
||||
)
|
||||
.select(db::Subscription::as_select());
|
||||
.select(db::DeviceSubscription::as_select());
|
||||
|
||||
for sub in query.load_iter(&mut self.pool.get()?)? {
|
||||
let sub = sub?;
|
||||
|
||||
if sub.deleted {
|
||||
removed.push(sub.url);
|
||||
removed.push(sub.podcast_url);
|
||||
} else {
|
||||
added.push(sub.url);
|
||||
added.push(sub.podcast_url);
|
||||
}
|
||||
|
||||
timestamp = timestamp.max(sub.time_changed);
|
||||
|
|
|
@ -1,5 +1,15 @@
|
|||
// @generated automatically by Diesel CLI.
|
||||
|
||||
diesel::table! {
|
||||
device_subscriptions (id) {
|
||||
id -> BigInt,
|
||||
device_id -> BigInt,
|
||||
podcast_url -> Text,
|
||||
time_changed -> BigInt,
|
||||
deleted -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
devices (id) {
|
||||
id -> BigInt,
|
||||
|
@ -14,9 +24,9 @@ diesel::table! {
|
|||
diesel::table! {
|
||||
episode_actions (id) {
|
||||
id -> BigInt,
|
||||
subscription_id -> BigInt,
|
||||
device_id -> Nullable<BigInt>,
|
||||
episode -> Text,
|
||||
podcast_url -> Text,
|
||||
episode_url -> Text,
|
||||
timestamp -> Nullable<BigInt>,
|
||||
action -> Text,
|
||||
started -> Nullable<Integer>,
|
||||
|
@ -32,16 +42,6 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
subscriptions (id) {
|
||||
id -> BigInt,
|
||||
device_id -> BigInt,
|
||||
url -> Text,
|
||||
time_changed -> BigInt,
|
||||
deleted -> Bool,
|
||||
}
|
||||
}
|
||||
|
||||
diesel::table! {
|
||||
users (id) {
|
||||
id -> BigInt,
|
||||
|
@ -50,16 +50,15 @@ diesel::table! {
|
|||
}
|
||||
}
|
||||
|
||||
diesel::joinable!(device_subscriptions -> devices (device_id));
|
||||
diesel::joinable!(devices -> users (user_id));
|
||||
diesel::joinable!(episode_actions -> devices (device_id));
|
||||
diesel::joinable!(episode_actions -> subscriptions (subscription_id));
|
||||
diesel::joinable!(sessions -> users (user_id));
|
||||
diesel::joinable!(subscriptions -> devices (device_id));
|
||||
|
||||
diesel::allow_tables_to_appear_in_same_query!(
|
||||
device_subscriptions,
|
||||
devices,
|
||||
episode_actions,
|
||||
sessions,
|
||||
subscriptions,
|
||||
users,
|
||||
);
|
||||
|
|
Loading…
Reference in New Issue