refactor: refactor graphql
This commit is contained in:
@@ -64,7 +64,9 @@ impl Model {
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
RecorderError::from_model_not_found_detail("auth", format!("pid {pid} not found"))
|
||||
RecorderError::from_entity_not_found_detail::<Entity, _>(format!(
|
||||
"pid {pid} not found"
|
||||
))
|
||||
})?;
|
||||
Ok(subscriber_auth)
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
pub const CRON_DUE_EVENT: &str = "cron_due";
|
||||
|
||||
pub const CHECK_AND_CLEANUP_EXPIRED_CRON_LOCKS_FUNCTION_NAME: &str =
|
||||
@@ -7,3 +9,15 @@ pub const CHECK_AND_TRIGGER_DUE_CRONS_FUNCTION_NAME: &str = "check_and_trigger_d
|
||||
pub const NOTIFY_DUE_CRON_WHEN_MUTATING_FUNCTION_NAME: &str = "notify_due_cron_when_mutating";
|
||||
pub const NOTIFY_DUE_CRON_WHEN_MUTATING_TRIGGER_NAME: &str =
|
||||
"notify_due_cron_when_mutating_trigger";
|
||||
pub const SETUP_CRON_EXTRA_FOREIGN_KEYS_FUNCTION_NAME: &str = "setup_cron_extra_foreign_keys";
|
||||
pub const SETUP_CRON_EXTRA_FOREIGN_KEYS_TRIGGER_NAME: &str =
|
||||
"setup_cron_extra_foreign_keys_trigger";
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct CronCreateOptions {
|
||||
pub cron_expr: String,
|
||||
pub priority: Option<i32>,
|
||||
pub timeout_ms: Option<i32>,
|
||||
pub max_attempts: Option<i32>,
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ mod registry;
|
||||
|
||||
pub use core::{
|
||||
CHECK_AND_CLEANUP_EXPIRED_CRON_LOCKS_FUNCTION_NAME, CHECK_AND_TRIGGER_DUE_CRONS_FUNCTION_NAME,
|
||||
CRON_DUE_EVENT, NOTIFY_DUE_CRON_WHEN_MUTATING_FUNCTION_NAME,
|
||||
NOTIFY_DUE_CRON_WHEN_MUTATING_TRIGGER_NAME,
|
||||
CRON_DUE_EVENT, CronCreateOptions, NOTIFY_DUE_CRON_WHEN_MUTATING_FUNCTION_NAME,
|
||||
NOTIFY_DUE_CRON_WHEN_MUTATING_TRIGGER_NAME, SETUP_CRON_EXTRA_FOREIGN_KEYS_FUNCTION_NAME,
|
||||
SETUP_CRON_EXTRA_FOREIGN_KEYS_TRIGGER_NAME,
|
||||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
@@ -17,21 +18,7 @@ use sea_orm::{
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
app::AppContextTrait,
|
||||
errors::{RecorderError, RecorderResult},
|
||||
models::subscriptions::{self},
|
||||
};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, DeriveActiveEnum, EnumIter, DeriveDisplay, Serialize, Deserialize,
|
||||
)]
|
||||
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "cron_source")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum CronSource {
|
||||
#[sea_orm(string_value = "subscription")]
|
||||
Subscription,
|
||||
}
|
||||
use crate::{app::AppContextTrait, errors::RecorderResult, models::subscriber_tasks};
|
||||
|
||||
#[derive(
|
||||
Debug, Clone, PartialEq, Eq, DeriveActiveEnum, EnumIter, DeriveDisplay, Serialize, Deserialize,
|
||||
@@ -58,7 +45,6 @@ pub struct Model {
|
||||
pub updated_at: DateTimeUtc,
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub cron_source: CronSource,
|
||||
pub subscriber_id: Option<i32>,
|
||||
pub subscription_id: Option<i32>,
|
||||
pub cron_expr: String,
|
||||
@@ -67,6 +53,7 @@ pub struct Model {
|
||||
pub last_error: Option<String>,
|
||||
pub locked_by: Option<String>,
|
||||
pub locked_at: Option<DateTimeUtc>,
|
||||
#[sea_orm(default_expr = "5000")]
|
||||
pub timeout_ms: i32,
|
||||
#[sea_orm(default_expr = "0")]
|
||||
pub attempts: i32,
|
||||
@@ -77,6 +64,7 @@ pub struct Model {
|
||||
pub status: CronStatus,
|
||||
#[sea_orm(default_expr = "true")]
|
||||
pub enabled: bool,
|
||||
pub subscriber_task: Option<subscriber_tasks::SubscriberTask>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
@@ -119,6 +107,38 @@ pub enum RelatedEntity {
|
||||
Subscription,
|
||||
}
|
||||
|
||||
impl ActiveModel {
|
||||
pub fn from_subscriber_task(
|
||||
subscriber_task: subscriber_tasks::SubscriberTask,
|
||||
cron_options: CronCreateOptions,
|
||||
) -> RecorderResult<Self> {
|
||||
let mut active_model = Self {
|
||||
next_run: Set(Some(Model::calculate_next_run(&cron_options.cron_expr)?)),
|
||||
cron_expr: Set(cron_options.cron_expr),
|
||||
subscriber_task: Set(Some(subscriber_task)),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
if let Some(priority) = cron_options.priority {
|
||||
active_model.priority = Set(priority);
|
||||
}
|
||||
|
||||
if let Some(timeout_ms) = cron_options.timeout_ms {
|
||||
active_model.timeout_ms = Set(timeout_ms);
|
||||
}
|
||||
|
||||
if let Some(max_attempts) = cron_options.max_attempts {
|
||||
active_model.max_attempts = Set(max_attempts);
|
||||
}
|
||||
|
||||
if let Some(enabled) = cron_options.enabled {
|
||||
active_model.enabled = Set(enabled);
|
||||
}
|
||||
|
||||
Ok(active_model)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
||||
|
||||
@@ -196,19 +216,13 @@ impl Model {
|
||||
}
|
||||
|
||||
async fn exec_cron(&self, ctx: &dyn AppContextTrait) -> RecorderResult<()> {
|
||||
match self.cron_source {
|
||||
CronSource::Subscription => {
|
||||
let subscription_id = self.subscription_id.unwrap_or_else(|| {
|
||||
unreachable!("Subscription cron must have a subscription id")
|
||||
});
|
||||
|
||||
let subscription = subscriptions::Entity::find_by_id(subscription_id)
|
||||
.one(ctx.db())
|
||||
.await?
|
||||
.ok_or_else(|| RecorderError::from_model_not_found("Subscription"))?;
|
||||
|
||||
subscription.exec_cron(ctx).await?;
|
||||
}
|
||||
if let Some(subscriber_task) = self.subscriber_task.as_ref() {
|
||||
let task_service = ctx.task();
|
||||
task_service
|
||||
.add_subscriber_task(subscriber_task.clone())
|
||||
.await?;
|
||||
} else {
|
||||
unimplemented!("Cron without subscriber task is not supported now");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -217,7 +231,7 @@ impl Model {
|
||||
async fn mark_cron_completed(&self, ctx: &dyn AppContextTrait) -> RecorderResult<()> {
|
||||
let db = ctx.db();
|
||||
|
||||
let next_run = self.calculate_next_run(&self.cron_expr)?;
|
||||
let next_run = Self::calculate_next_run(&self.cron_expr)?;
|
||||
|
||||
ActiveModel {
|
||||
id: Set(self.id),
|
||||
@@ -250,7 +264,7 @@ impl Model {
|
||||
let next_run = if should_retry {
|
||||
Some(Utc::now() + chrono::Duration::seconds(5))
|
||||
} else {
|
||||
Some(self.calculate_next_run(&self.cron_expr)?)
|
||||
Some(Self::calculate_next_run(&self.cron_expr)?)
|
||||
};
|
||||
|
||||
ActiveModel {
|
||||
@@ -295,7 +309,7 @@ impl Model {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn calculate_next_run(&self, cron_expr: &str) -> RecorderResult<DateTime<Utc>> {
|
||||
pub fn calculate_next_run(cron_expr: &str) -> RecorderResult<DateTime<Utc>> {
|
||||
let cron_expr = Cron::new(cron_expr).parse()?;
|
||||
|
||||
let next = cron_expr.find_next_occurrence(&Utc::now(), false)?;
|
||||
|
||||
@@ -122,7 +122,7 @@ impl Model {
|
||||
.filter(Column::FeedType.eq(FeedType::Rss))
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or(RecorderError::from_model_not_found("Feed"))?;
|
||||
.ok_or(RecorderError::from_entity_not_found::<Entity>())?;
|
||||
|
||||
let feed = Feed::from_model(ctx, feed_model).await?;
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ impl Feed {
|
||||
.await?;
|
||||
(subscription, episodes)
|
||||
} else {
|
||||
return Err(RecorderError::from_model_not_found("Subscription"));
|
||||
return Err(RecorderError::from_entity_not_found::<subscriptions::Entity>());
|
||||
};
|
||||
|
||||
Ok(Feed::SubscritpionEpisodes(
|
||||
|
||||
@@ -131,7 +131,7 @@ impl Model {
|
||||
let db = ctx.db();
|
||||
|
||||
let subscriber = Entity::find_by_id(id).one(db).await?.ok_or_else(|| {
|
||||
RecorderError::from_model_not_found_detail("subscribers", format!("id {id} not found"))
|
||||
RecorderError::from_entity_not_found_detail::<Entity, _>(format!("id {id} not found"))
|
||||
})?;
|
||||
Ok(subscriber)
|
||||
}
|
||||
|
||||
@@ -11,10 +11,7 @@ pub use registry::{
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
app::AppContextTrait,
|
||||
errors::{RecorderError, RecorderResult},
|
||||
};
|
||||
use crate::{app::AppContextTrait, errors::RecorderResult};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "subscriptions")]
|
||||
@@ -155,50 +152,6 @@ impl ActiveModelBehavior for ActiveModel {}
|
||||
impl ActiveModel {}
|
||||
|
||||
impl Model {
|
||||
pub async fn toggle_with_ids(
|
||||
ctx: &dyn AppContextTrait,
|
||||
ids: impl Iterator<Item = i32>,
|
||||
enabled: bool,
|
||||
) -> RecorderResult<()> {
|
||||
let db = ctx.db();
|
||||
Entity::update_many()
|
||||
.col_expr(Column::Enabled, Expr::value(enabled))
|
||||
.filter(Column::Id.is_in(ids))
|
||||
.exec(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_with_ids(
|
||||
ctx: &dyn AppContextTrait,
|
||||
ids: impl Iterator<Item = i32>,
|
||||
) -> RecorderResult<()> {
|
||||
let db = ctx.db();
|
||||
Entity::delete_many()
|
||||
.filter(Column::Id.is_in(ids))
|
||||
.exec(db)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn find_by_id_and_subscriber_id(
|
||||
ctx: &dyn AppContextTrait,
|
||||
subscriber_id: i32,
|
||||
subscription_id: i32,
|
||||
) -> RecorderResult<Self> {
|
||||
let db = ctx.db();
|
||||
let subscription_model = Entity::find_by_id(subscription_id)
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| RecorderError::from_model_not_found("Subscription"))?;
|
||||
|
||||
if subscription_model.subscriber_id != subscriber_id {
|
||||
Err(RecorderError::from_model_not_found("Subscription"))?;
|
||||
}
|
||||
|
||||
Ok(subscription_model)
|
||||
}
|
||||
|
||||
pub async fn exec_cron(&self, _ctx: &dyn AppContextTrait) -> RecorderResult<()> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
@@ -1,129 +1,147 @@
|
||||
use std::{fmt::Debug, sync::Arc};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use sea_orm::{DeriveActiveEnum, DeriveDisplay, EnumIter};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
app::AppContextTrait,
|
||||
errors::{RecorderError, RecorderResult},
|
||||
errors::RecorderResult,
|
||||
extract::mikan::{
|
||||
MikanBangumiSubscription, MikanSeasonSubscription, MikanSubscriberSubscription,
|
||||
},
|
||||
models::subscriptions::{self, SubscriptionTrait},
|
||||
};
|
||||
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum, Serialize, Deserialize, DeriveDisplay,
|
||||
)]
|
||||
#[sea_orm(
|
||||
rs_type = "String",
|
||||
db_type = "Enum",
|
||||
enum_name = "subscription_category"
|
||||
)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum SubscriptionCategory {
|
||||
#[sea_orm(string_value = "mikan_subscriber")]
|
||||
MikanSubscriber,
|
||||
#[sea_orm(string_value = "mikan_season")]
|
||||
MikanSeason,
|
||||
#[sea_orm(string_value = "mikan_bangumi")]
|
||||
MikanBangumi,
|
||||
#[sea_orm(string_value = "manual")]
|
||||
Manual,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(tag = "category")]
|
||||
pub enum Subscription {
|
||||
#[serde(rename = "mikan_subscriber")]
|
||||
MikanSubscriber(MikanSubscriberSubscription),
|
||||
#[serde(rename = "mikan_season")]
|
||||
MikanSeason(MikanSeasonSubscription),
|
||||
#[serde(rename = "mikan_bangumi")]
|
||||
MikanBangumi(MikanBangumiSubscription),
|
||||
#[serde(rename = "manual")]
|
||||
Manual,
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
pub fn category(&self) -> SubscriptionCategory {
|
||||
match self {
|
||||
Self::MikanSubscriber(_) => SubscriptionCategory::MikanSubscriber,
|
||||
Self::MikanSeason(_) => SubscriptionCategory::MikanSeason,
|
||||
Self::MikanBangumi(_) => SubscriptionCategory::MikanBangumi,
|
||||
Self::Manual => SubscriptionCategory::Manual,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl SubscriptionTrait for Subscription {
|
||||
fn get_subscriber_id(&self) -> i32 {
|
||||
match self {
|
||||
Self::MikanSubscriber(subscription) => subscription.get_subscriber_id(),
|
||||
Self::MikanSeason(subscription) => subscription.get_subscriber_id(),
|
||||
Self::MikanBangumi(subscription) => subscription.get_subscriber_id(),
|
||||
Self::Manual => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_subscription_id(&self) -> i32 {
|
||||
match self {
|
||||
Self::MikanSubscriber(subscription) => subscription.get_subscription_id(),
|
||||
Self::MikanSeason(subscription) => subscription.get_subscription_id(),
|
||||
Self::MikanBangumi(subscription) => subscription.get_subscription_id(),
|
||||
Self::Manual => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
async fn sync_feeds_incremental(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
||||
match self {
|
||||
Self::MikanSubscriber(subscription) => subscription.sync_feeds_incremental(ctx).await,
|
||||
Self::MikanSeason(subscription) => subscription.sync_feeds_incremental(ctx).await,
|
||||
Self::MikanBangumi(subscription) => subscription.sync_feeds_incremental(ctx).await,
|
||||
Self::Manual => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn sync_feeds_full(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
||||
match self {
|
||||
Self::MikanSubscriber(subscription) => subscription.sync_feeds_full(ctx).await,
|
||||
Self::MikanSeason(subscription) => subscription.sync_feeds_full(ctx).await,
|
||||
Self::MikanBangumi(subscription) => subscription.sync_feeds_full(ctx).await,
|
||||
Self::Manual => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn sync_sources(&self, ctx: Arc<dyn AppContextTrait>) -> RecorderResult<()> {
|
||||
match self {
|
||||
Self::MikanSubscriber(subscription) => subscription.sync_sources(ctx).await,
|
||||
Self::MikanSeason(subscription) => subscription.sync_sources(ctx).await,
|
||||
Self::MikanBangumi(subscription) => subscription.sync_sources(ctx).await,
|
||||
Self::Manual => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from_model(model: &subscriptions::Model) -> RecorderResult<Self> {
|
||||
match model.category {
|
||||
SubscriptionCategory::MikanSubscriber => {
|
||||
MikanSubscriberSubscription::try_from_model(model).map(Self::MikanSubscriber)
|
||||
macro_rules! register_subscription_type {
|
||||
(
|
||||
subscription_category_enum: {
|
||||
$(#[$subscription_category_enum_meta:meta])*
|
||||
pub enum $type_enum_name:ident {
|
||||
$(
|
||||
$(#[$variant_meta:meta])*
|
||||
$variant:ident => $string_value:literal
|
||||
),* $(,)?
|
||||
}
|
||||
SubscriptionCategory::MikanSeason => {
|
||||
MikanSeasonSubscription::try_from_model(model).map(Self::MikanSeason)
|
||||
}$(,)?
|
||||
subscription_enum: {
|
||||
$(#[$subscription_enum_meta:meta])*
|
||||
pub enum $subscription_enum_name:ident {
|
||||
$(
|
||||
$subscription_variant:ident($subscription_type:ty)
|
||||
),* $(,)?
|
||||
}
|
||||
SubscriptionCategory::MikanBangumi => {
|
||||
MikanBangumiSubscription::try_from_model(model).map(Self::MikanBangumi)
|
||||
}
|
||||
) => {
|
||||
$(#[$subscription_category_enum_meta])*
|
||||
#[sea_orm(
|
||||
rs_type = "String",
|
||||
db_type = "Enum",
|
||||
enum_name = "subscription_category"
|
||||
)]
|
||||
pub enum $type_enum_name {
|
||||
$(
|
||||
$(#[$variant_meta])*
|
||||
#[serde(rename = $string_value)]
|
||||
#[sea_orm(string_value = $string_value)]
|
||||
$variant,
|
||||
)*
|
||||
}
|
||||
|
||||
|
||||
$(#[$subscription_enum_meta])*
|
||||
#[serde(tag = "category")]
|
||||
pub enum $subscription_enum_name {
|
||||
$(
|
||||
#[serde(rename = $string_value)]
|
||||
$subscription_variant($subscription_type),
|
||||
)*
|
||||
}
|
||||
|
||||
impl $subscription_enum_name {
|
||||
pub fn category(&self) -> $type_enum_name {
|
||||
match self {
|
||||
$(Self::$subscription_variant(_) => $type_enum_name::$variant,)*
|
||||
}
|
||||
}
|
||||
SubscriptionCategory::Manual => Ok(Self::Manual),
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl $crate::models::subscriptions::SubscriptionTrait for $subscription_enum_name {
|
||||
fn get_subscriber_id(&self) -> i32 {
|
||||
match self {
|
||||
$(Self::$subscription_variant(subscription) => subscription.get_subscriber_id(),)*
|
||||
}
|
||||
}
|
||||
|
||||
fn get_subscription_id(&self) -> i32 {
|
||||
match self {
|
||||
$(Self::$subscription_variant(subscription) => subscription.get_subscription_id(),)*
|
||||
}
|
||||
}
|
||||
|
||||
async fn sync_feeds_incremental(&self, ctx: Arc<dyn $crate::app::AppContextTrait>) -> $crate::errors::RecorderResult<()> {
|
||||
match self {
|
||||
$(Self::$subscription_variant(subscription) => subscription.sync_feeds_incremental(ctx).await,)*
|
||||
}
|
||||
}
|
||||
|
||||
async fn sync_feeds_full(&self, ctx: Arc<dyn $crate::app::AppContextTrait>) -> $crate::errors::RecorderResult<()> {
|
||||
match self {
|
||||
$(Self::$subscription_variant(subscription) => subscription.sync_feeds_full(ctx).await,)*
|
||||
}
|
||||
}
|
||||
|
||||
async fn sync_sources(&self, ctx: Arc<dyn $crate::app::AppContextTrait>) -> $crate::errors::RecorderResult<()> {
|
||||
match self {
|
||||
$(Self::$subscription_variant(subscription) => subscription.sync_sources(ctx).await,)*
|
||||
}
|
||||
}
|
||||
|
||||
fn try_from_model(model: &subscriptions::Model) -> RecorderResult<Self> {
|
||||
|
||||
match model.category {
|
||||
$($type_enum_name::$variant => {
|
||||
<$subscription_type as $crate::models::subscriptions::SubscriptionTrait>::try_from_model(model).map(Self::$subscription_variant)
|
||||
})*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&$crate::models::subscriptions::Model> for $subscription_enum_name {
|
||||
type Error = $crate::errors::RecorderError;
|
||||
|
||||
fn try_from(model: &$crate::models::subscriptions::Model) -> Result<Self, Self::Error> {
|
||||
Self::try_from_model(model)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
register_subscription_type! {
|
||||
subscription_category_enum: {
|
||||
#[derive(
|
||||
Clone,
|
||||
Debug,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Copy,
|
||||
DeriveActiveEnum,
|
||||
DeriveDisplay,
|
||||
EnumIter,
|
||||
)]
|
||||
pub enum SubscriptionCategory {
|
||||
MikanSubscriber => "mikan_subscriber",
|
||||
MikanSeason => "mikan_season",
|
||||
MikanBangumi => "mikan_bangumi",
|
||||
}
|
||||
}
|
||||
subscription_enum: {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub enum Subscription {
|
||||
MikanSubscriber(MikanSubscriberSubscription),
|
||||
MikanSeason(MikanSeasonSubscription),
|
||||
MikanBangumi(MikanBangumiSubscription)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&subscriptions::Model> for Subscription {
|
||||
type Error = RecorderError;
|
||||
|
||||
fn try_from(model: &subscriptions::Model) -> Result<Self, Self::Error> {
|
||||
Self::try_from_model(model)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user