refactor: refactor subscriptions
This commit is contained in:
16
apps/webui/src/domains/recorder/context.ts
Normal file
16
apps/webui/src/domains/recorder/context.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import type { Provider } from '@outposts/injection-js';
|
||||
import { MikanService } from './services/mikan.service';
|
||||
import { SubscriptionService } from './services/subscription.service';
|
||||
|
||||
export function provideRecorder(): Provider[] {
|
||||
return [
|
||||
{
|
||||
provide: MikanService,
|
||||
useClass: MikanService,
|
||||
},
|
||||
{
|
||||
provide: SubscriptionService,
|
||||
useClass: SubscriptionService,
|
||||
},
|
||||
];
|
||||
}
|
||||
192
apps/webui/src/domains/recorder/schema/mikan.ts
Normal file
192
apps/webui/src/domains/recorder/schema/mikan.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
import { UnimplementedError } from '@/infra/errors/common';
|
||||
import { SubscriptionCategoryEnum } from '@/infra/graphql/gql/graphql';
|
||||
import { type ArkErrors, type } from 'arktype';
|
||||
|
||||
export const MIKAN_UNKNOWN_FANSUB_NAME = '生肉/不明字幕';
|
||||
export const MIKAN_UNKNOWN_FANSUB_ID = '202';
|
||||
export const MIKAN_ACCOUNT_MANAGE_PAGE_PATH = '/Account/Manage';
|
||||
export const MIKAN_SEASON_FLOW_PAGE_PATH = '/Home/BangumiCoverFlow';
|
||||
export const MIKAN_BANGUMI_HOMEPAGE_PATH = '/Home/Bangumi';
|
||||
export const MIKAN_BANGUMI_EXPAND_SUBSCRIBED_PAGE_PATH = '/Home/ExpandBangumi';
|
||||
export const MIKAN_EPISODE_HOMEPAGE_PATH = '/Home/Episode';
|
||||
export const MIKAN_BANGUMI_POSTER_PATH = '/images/Bangumi';
|
||||
export const MIKAN_EPISODE_TORRENT_PATH = '/Download';
|
||||
export const MIKAN_SUBSCRIBER_SUBSCRIPTION_RSS_PATH = '/RSS/MyBangumi';
|
||||
export const MIKAN_BANGUMI_RSS_PATH = '/RSS/Bangumi';
|
||||
export const MIKAN_BANGUMI_ID_QUERY_KEY = 'bangumiId';
|
||||
export const MIKAN_FANSUB_ID_QUERY_KEY = 'subgroupid';
|
||||
export const MIKAN_SUBSCRIBER_SUBSCRIPTION_TOKEN_QUERY_KEY = 'token';
|
||||
export const MIKAN_SEASON_STR_QUERY_KEY = 'seasonStr';
|
||||
export const MIKAN_YEAR_QUERY_KEY = 'year';
|
||||
|
||||
export const MikanSubscriptionCategoryEnum = {
|
||||
MikanBangumi: SubscriptionCategoryEnum.MikanBangumi,
|
||||
MikanSeason: SubscriptionCategoryEnum.MikanSeason,
|
||||
MikanSubscriber: SubscriptionCategoryEnum.MikanSubscriber,
|
||||
} as const;
|
||||
|
||||
export type MikanSubscriptionCategoryEnum =
|
||||
(typeof MikanSubscriptionCategoryEnum)[keyof typeof MikanSubscriptionCategoryEnum];
|
||||
|
||||
export const MikanSeasonEnum = {
|
||||
Spring: '春',
|
||||
Summer: '夏',
|
||||
Autumn: '秋',
|
||||
Winter: '冬',
|
||||
} as const;
|
||||
|
||||
export type MikanSeasonEnum =
|
||||
(typeof MikanSeasonEnum)[keyof typeof MikanSeasonEnum];
|
||||
|
||||
export const MikanSeasonSchema = type.enumerated(
|
||||
MikanSeasonEnum.Spring,
|
||||
MikanSeasonEnum.Summer,
|
||||
MikanSeasonEnum.Autumn,
|
||||
MikanSeasonEnum.Winter
|
||||
);
|
||||
|
||||
export const MikanSubscriptionBangumiSourceUrlSchema = type({
|
||||
category: `'${SubscriptionCategoryEnum.MikanBangumi}'`,
|
||||
mikanBangumiId: 'string>0',
|
||||
mikanFansubId: 'string>0',
|
||||
});
|
||||
|
||||
export type MikanSubscriptionBangumiSourceUrl =
|
||||
typeof MikanSubscriptionBangumiSourceUrlSchema.infer;
|
||||
|
||||
export const MikanSubscriptionSeasonSourceUrlSchema = type({
|
||||
category: `'${SubscriptionCategoryEnum.MikanSeason}'`,
|
||||
seasonStr: MikanSeasonSchema,
|
||||
year: 'number>0',
|
||||
});
|
||||
|
||||
export type MikanSubscriptionSeasonSourceUrl =
|
||||
typeof MikanSubscriptionSeasonSourceUrlSchema.infer;
|
||||
|
||||
export const MikanSubscriptionSubscriberSourceUrlSchema = type({
|
||||
category: `'${SubscriptionCategoryEnum.MikanSubscriber}'`,
|
||||
mikanSubscriptionToken: 'string>0',
|
||||
});
|
||||
|
||||
export type MikanSubscriptionSubscriberSourceUrl =
|
||||
typeof MikanSubscriptionSubscriberSourceUrlSchema.infer;
|
||||
|
||||
export const MikanSubscriptionSourceUrlSchema =
|
||||
MikanSubscriptionBangumiSourceUrlSchema.or(
|
||||
MikanSubscriptionSeasonSourceUrlSchema
|
||||
).or(MikanSubscriptionSubscriberSourceUrlSchema);
|
||||
|
||||
export type MikanSubscriptionSourceUrl =
|
||||
typeof MikanSubscriptionSourceUrlSchema.infer;
|
||||
|
||||
export function isSubscriptionMikanCategory(
|
||||
category: SubscriptionCategoryEnum
|
||||
): category is MikanSubscriptionCategoryEnum {
|
||||
return (
|
||||
category === SubscriptionCategoryEnum.MikanBangumi ||
|
||||
category === SubscriptionCategoryEnum.MikanSeason ||
|
||||
category === SubscriptionCategoryEnum.MikanSubscriber
|
||||
);
|
||||
}
|
||||
|
||||
export function buildMikanSubscriptionSeasonSourceUrl(
|
||||
mikanBaseUrl: string,
|
||||
formParts: MikanSubscriptionSeasonSourceUrl
|
||||
): URL {
|
||||
const u = new URL(mikanBaseUrl);
|
||||
u.pathname = MIKAN_SEASON_FLOW_PAGE_PATH;
|
||||
u.searchParams.set(MIKAN_YEAR_QUERY_KEY, formParts.year.toString());
|
||||
u.searchParams.set(MIKAN_SEASON_STR_QUERY_KEY, formParts.seasonStr);
|
||||
return u;
|
||||
}
|
||||
|
||||
export function buildMikanSubscriptionBangumiSourceUrl(
|
||||
mikanBaseUrl: string,
|
||||
formParts: MikanSubscriptionBangumiSourceUrl
|
||||
): URL {
|
||||
const u = new URL(mikanBaseUrl);
|
||||
u.pathname = MIKAN_BANGUMI_RSS_PATH;
|
||||
u.searchParams.set(MIKAN_BANGUMI_ID_QUERY_KEY, formParts.mikanBangumiId);
|
||||
u.searchParams.set(MIKAN_FANSUB_ID_QUERY_KEY, formParts.mikanFansubId);
|
||||
return u;
|
||||
}
|
||||
|
||||
export function buildMikanSubscriptionSubscriberSourceUrl(
|
||||
mikanBaseUrl: string,
|
||||
formParts: MikanSubscriptionSubscriberSourceUrl
|
||||
): URL {
|
||||
const u = new URL(mikanBaseUrl);
|
||||
u.pathname = MIKAN_SUBSCRIBER_SUBSCRIPTION_RSS_PATH;
|
||||
u.searchParams.set(
|
||||
MIKAN_SUBSCRIBER_SUBSCRIPTION_TOKEN_QUERY_KEY,
|
||||
formParts.mikanSubscriptionToken
|
||||
);
|
||||
return u;
|
||||
}
|
||||
|
||||
export function buildMikanSubscriptionSourceUrl(
|
||||
mikanBaseUrl: string,
|
||||
formParts: MikanSubscriptionSourceUrl
|
||||
): URL {
|
||||
if (formParts.category === SubscriptionCategoryEnum.MikanBangumi) {
|
||||
return buildMikanSubscriptionBangumiSourceUrl(mikanBaseUrl, formParts);
|
||||
}
|
||||
if (formParts.category === SubscriptionCategoryEnum.MikanSeason) {
|
||||
return buildMikanSubscriptionSeasonSourceUrl(mikanBaseUrl, formParts);
|
||||
}
|
||||
if (formParts.category === SubscriptionCategoryEnum.MikanSubscriber) {
|
||||
return buildMikanSubscriptionSubscriberSourceUrl(mikanBaseUrl, formParts);
|
||||
}
|
||||
|
||||
throw new UnimplementedError(
|
||||
// @ts-ignore
|
||||
`source url category = ${formParts.category as any} is not implemented`
|
||||
);
|
||||
}
|
||||
|
||||
export function extractMikanSubscriptionSeasonSourceUrl(
|
||||
sourceUrl: string
|
||||
): MikanSubscriptionSeasonSourceUrl | ArkErrors {
|
||||
const u = new URL(sourceUrl);
|
||||
return MikanSubscriptionSeasonSourceUrlSchema({
|
||||
category: SubscriptionCategoryEnum.MikanSeason,
|
||||
seasonStr: u.searchParams.get(
|
||||
MIKAN_SEASON_STR_QUERY_KEY
|
||||
) as MikanSeasonEnum,
|
||||
year: Number(u.searchParams.get(MIKAN_YEAR_QUERY_KEY)),
|
||||
});
|
||||
}
|
||||
|
||||
export function extractMikanSubscriptionBangumiSourceUrl(
|
||||
sourceUrl: string
|
||||
): MikanSubscriptionBangumiSourceUrl | ArkErrors {
|
||||
const u = new URL(sourceUrl);
|
||||
return MikanSubscriptionBangumiSourceUrlSchema({
|
||||
category: SubscriptionCategoryEnum.MikanBangumi,
|
||||
mikanBangumiId: u.searchParams.get(MIKAN_BANGUMI_ID_QUERY_KEY),
|
||||
mikanFansubId: u.searchParams.get(MIKAN_FANSUB_ID_QUERY_KEY),
|
||||
});
|
||||
}
|
||||
|
||||
export function extractMikanSubscriptionSubscriberSourceUrl(
|
||||
sourceUrl: string
|
||||
): MikanSubscriptionSubscriberSourceUrl | ArkErrors {
|
||||
const u = new URL(sourceUrl);
|
||||
return MikanSubscriptionSubscriberSourceUrlSchema({
|
||||
category: SubscriptionCategoryEnum.MikanSubscriber,
|
||||
mikanSubscriptionToken: u.searchParams.get(
|
||||
MIKAN_SUBSCRIBER_SUBSCRIPTION_TOKEN_QUERY_KEY
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
export function extractMikanSubscriptionSourceUrl(
|
||||
sourceUrl: string
|
||||
): MikanSubscriptionSourceUrl | ArkErrors {
|
||||
const u = new URL(sourceUrl);
|
||||
return MikanSubscriptionSourceUrlSchema({
|
||||
category: SubscriptionCategoryEnum.MikanBangumi,
|
||||
mikanBangumiId: u.searchParams.get(MIKAN_BANGUMI_ID_QUERY_KEY),
|
||||
mikanFansubId: u.searchParams.get(MIKAN_FANSUB_ID_QUERY_KEY),
|
||||
});
|
||||
}
|
||||
@@ -1,5 +1,16 @@
|
||||
import type { GetSubscriptionsQuery } from '@/infra/graphql/gql/graphql';
|
||||
import { arkValidatorToTypeNarrower } from '@/infra/errors/arktype';
|
||||
import {
|
||||
type GetSubscriptionsQuery,
|
||||
SubscriptionCategoryEnum,
|
||||
} from '@/infra/graphql/gql/graphql';
|
||||
import { gql } from '@apollo/client';
|
||||
import { type } from 'arktype';
|
||||
import {
|
||||
MikanSubscriptionSeasonSourceUrlSchema,
|
||||
extractMikanSubscriptionBangumiSourceUrl,
|
||||
extractMikanSubscriptionSeasonSourceUrl,
|
||||
extractMikanSubscriptionSubscriberSourceUrl,
|
||||
} from './mikan';
|
||||
|
||||
export const GET_SUBSCRIPTIONS = gql`
|
||||
query GetSubscriptions($filters: SubscriptionsFilterInput!, $orderBy: SubscriptionsOrderInput!, $pagination: PaginationInput!) {
|
||||
@@ -16,6 +27,7 @@ export const GET_SUBSCRIPTIONS = gql`
|
||||
category
|
||||
sourceUrl
|
||||
enabled
|
||||
credentialId
|
||||
}
|
||||
paginationInfo {
|
||||
total
|
||||
@@ -25,6 +37,21 @@ export const GET_SUBSCRIPTIONS = gql`
|
||||
}
|
||||
`;
|
||||
|
||||
export const INSERT_SUBSCRIPTION = gql`
|
||||
mutation InsertSubscription($data: SubscriptionsInsertInput!) {
|
||||
subscriptionsCreateOne(data: $data) {
|
||||
id
|
||||
createdAt
|
||||
updatedAt
|
||||
displayName
|
||||
category
|
||||
sourceUrl
|
||||
enabled
|
||||
credentialId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export type SubscriptionDto =
|
||||
GetSubscriptionsQuery['subscriptions']['nodes'][number];
|
||||
|
||||
@@ -67,6 +94,9 @@ query GetSubscriptionDetail ($id: Int!) {
|
||||
category
|
||||
sourceUrl
|
||||
enabled
|
||||
credential3rd {
|
||||
id
|
||||
}
|
||||
bangumi {
|
||||
nodes {
|
||||
createdAt
|
||||
@@ -89,3 +119,39 @@ query GetSubscriptionDetail ($id: Int!) {
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const SubscriptionTypedMikanSeasonSchema =
|
||||
MikanSubscriptionSeasonSourceUrlSchema.and(
|
||||
type({
|
||||
credentialId: 'number>0',
|
||||
})
|
||||
);
|
||||
|
||||
export const SubscriptionTypedMikanBangumiSchema = type({
|
||||
category: `'${SubscriptionCategoryEnum.MikanBangumi}'`,
|
||||
sourceUrl: type.string
|
||||
.atLeastLength(1)
|
||||
.narrow(
|
||||
arkValidatorToTypeNarrower(extractMikanSubscriptionBangumiSourceUrl)
|
||||
),
|
||||
});
|
||||
|
||||
export const SubscriptionTypedMikanSubscriberSchema = type({
|
||||
category: `'${SubscriptionCategoryEnum.MikanSubscriber}'`,
|
||||
sourceUrl: type.string
|
||||
.atLeastLength(1)
|
||||
.narrow(
|
||||
arkValidatorToTypeNarrower(extractMikanSubscriptionSubscriberSourceUrl)
|
||||
),
|
||||
});
|
||||
|
||||
export const SubscriptionTypedSchema = SubscriptionTypedMikanSeasonSchema.or(
|
||||
SubscriptionTypedMikanBangumiSchema
|
||||
).or(SubscriptionTypedMikanSubscriberSchema);
|
||||
|
||||
export const SubscriptionInsertFormSchema = type({
|
||||
enabled: 'boolean',
|
||||
displayName: 'string>0',
|
||||
}).and(SubscriptionTypedSchema);
|
||||
|
||||
export type SubscriptionInsertForm = typeof SubscriptionInsertFormSchema.infer;
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { Injectable } from '@outposts/injection-js';
|
||||
|
||||
@Injectable()
|
||||
export class MikanService {
|
||||
mikanBaseUrl = 'https://mikanani.me';
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
import {
|
||||
SubscriptionCategoryEnum,
|
||||
type SubscriptionsInsertInput,
|
||||
} from '@/infra/graphql/gql/graphql';
|
||||
import { Injectable, inject } from '@outposts/injection-js';
|
||||
import { buildMikanSubscriptionSeasonSourceUrl } from '../schema/mikan';
|
||||
import type { SubscriptionInsertForm } from '../schema/subscriptions';
|
||||
import { MikanService } from './mikan.service';
|
||||
|
||||
@Injectable()
|
||||
export class SubscriptionService {
|
||||
private mikan = inject(MikanService);
|
||||
|
||||
transformInsertFormToInput(
|
||||
form: SubscriptionInsertForm
|
||||
): SubscriptionsInsertInput {
|
||||
let sourceUrl: string;
|
||||
if (form.category === SubscriptionCategoryEnum.MikanSeason) {
|
||||
sourceUrl = buildMikanSubscriptionSeasonSourceUrl(
|
||||
this.mikan.mikanBaseUrl,
|
||||
form
|
||||
).toString();
|
||||
} else {
|
||||
sourceUrl = form.sourceUrl;
|
||||
}
|
||||
return {
|
||||
...form,
|
||||
sourceUrl,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user