//! The database's tables as SeaORM entities: the one description of the schema, from which //! `Db::open` creates what a database is missing, on SQLite or Postgres alike (see //! `db::create_missing`). Times are Unix seconds. pub mod feeds { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "feeds")] pub struct Model { #[sea_orm(primary_key, auto_increment = false, column_type = "Text")] pub id: String, #[sea_orm(column_type = "Text")] pub url: String, #[sea_orm(column_type = "Text", nullable)] pub title: Option, #[sea_orm(column_type = "Text", nullable)] pub image: Option, /// The channel's first , for the Directory. #[sea_orm(column_type = "Text", nullable)] pub category: Option, #[sea_orm(column_type = "Text", nullable)] pub etag: Option, #[sea_orm(column_type = "Text", nullable)] pub last_modified: Option, pub last_checked: Option, pub ttl_mins: Option, #[sea_orm(column_type = "Text", nullable)] pub last_error: Option, /// When the current run of failures began; NULL while the feed is healthy. Kept through /// repeated failures so the UI can tell a blip (macmanx: failed once, fine an hour /// later) from a feed that has been down for a day. pub error_since: Option, /// Came from a subscribed OPML that no longer lists it, but has downloads, so kept. #[sea_orm(default_value = false)] pub orphaned: bool, /// The OPML subscription this feed came from. #[sea_orm(column_type = "Text", nullable)] pub group_id: Option, /// Derived from an OPML and not written to config.toml. Writing 80-odd generated entries /// into a hand-edited file made it unreadable; the OPML is the source of truth, so they /// are re-derived instead. Customising one promotes it to config. #[sea_orm(default_value = false)] pub managed: bool, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } pub mod entries { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "entries")] pub struct Model { #[sea_orm(primary_key, auto_increment = false, column_type = "Text")] pub feed_id: String, #[sea_orm(primary_key, auto_increment = false, column_type = "Text")] pub guid: String, #[sea_orm(column_type = "Text", nullable)] pub title: Option, #[sea_orm(column_type = "Text", nullable)] pub link: Option, pub published: Option, #[sea_orm(column_type = "Text", nullable)] pub description: Option, pub first_seen: i64, #[sea_orm(column_type = "Text", nullable)] pub image: Option, pub duration: Option, pub episode: Option, pub season: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } pub mod enclosures { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "enclosures")] pub struct Model { #[sea_orm(primary_key)] pub id: i64, #[sea_orm(column_type = "Text")] pub feed_id: String, #[sea_orm(column_type = "Text")] pub guid: String, /// The dedupe key, and the reason one file serves every subscriber. A reaped file keeps /// its row with path NULL and state 'reaped', so a purged episode is never fetched again. #[sea_orm(unique, column_type = "Text")] pub url: String, #[sea_orm(column_type = "Text", nullable)] pub mime: Option, pub length: Option, #[sea_orm(column_type = "Text", nullable)] pub path: Option, #[sea_orm(column_type = "Text")] pub state: String, #[sea_orm(default_value = 0)] pub bytes_done: i64, pub downloaded_at: Option, #[sea_orm(column_type = "Text", nullable)] pub last_error: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } pub mod users { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "users")] pub struct Model { #[sea_orm(primary_key)] pub id: i64, /// Unique without regard to case: `db::create_missing` adds the index on lower(name), which /// works the same on both databases where SQLite's COLLATE NOCASE does not. #[sea_orm(column_type = "Text")] pub name: String, /// NULL for someone who only ever arrives through the proxy: there is no password to /// check, and leaving it empty is not the same as leaving it unset. #[sea_orm(column_type = "Text", nullable)] pub pass_hash: Option, #[sea_orm(default_value = false)] pub is_admin: bool, /// For whoever maintains the server. NULL where it is not known. pub created: Option, pub last_login: Option, /// The theme chosen in Settings, and light, dark or auto. NULL until one is chosen. #[sea_orm(column_type = "Text", nullable)] pub theme: Option, #[sea_orm(column_type = "Text", nullable)] pub theme_mode: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation {} impl ActiveModelBehavior for ActiveModel {} } /// A table of one person's rows, gone when they are. macro_rules! owned_by_user { () => { #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] pub enum Relation { #[sea_orm( belongs_to = "super::users::Entity", from = "Column::UserId", to = "super::users::Column::Id", on_delete = "Cascade" )] User, } impl Related for Entity { fn to() -> RelationDef { Relation::User.def() } } impl ActiveModelBehavior for ActiveModel {} }; } /// What one person wants from a feed. The feed, its items and its files are shared; this is the /// part that is not. NULL in a column means: follow the feed's own setting. pub mod subscriptions { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "subscriptions")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub user_id: i64, #[sea_orm(primary_key, auto_increment = false, column_type = "Text")] pub feed_id: String, /// JSON array of strings; NULL follows the feed. #[sea_orm(column_type = "Text", nullable)] pub keywords: Option, pub auto_download: Option, pub allow_explicit: Option, pub max_new_per_check: Option, /// Pinned to the top of this person's feed list, a feed inside a folder included. #[sea_orm(default_value = false)] pub pinned: bool, } owned_by_user!(); } /// Read, kept and how far in. One row per person per item, created on first touch; an item /// nobody has touched has no row at all, which is what unread means. pub mod entry_state { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "entry_state")] pub struct Model { #[sea_orm(primary_key, auto_increment = false)] pub user_id: i64, #[sea_orm(primary_key, auto_increment = false, column_type = "Text")] pub feed_id: String, #[sea_orm(primary_key, auto_increment = false, column_type = "Text")] pub guid: String, #[sea_orm(default_value = false)] pub read: bool, #[sea_orm(default_value = false)] pub flagged: bool, #[sea_orm(default_value = 0)] pub position: i64, /// The length this person's player measured, beside the position it is measured against. pub duration: Option, } owned_by_user!(); } pub mod sessions { use sea_orm::entity::prelude::*; #[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)] #[sea_orm(table_name = "sessions")] pub struct Model { #[sea_orm(primary_key, auto_increment = false, column_type = "Text")] pub token: String, pub user_id: i64, pub seen: i64, } owned_by_user!(); }