@serifpersia/dango 3.6.7 → 3.6.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DKDJK8Mp.js} +1 -1
- package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-CTcvfjva.js} +1 -1
- package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_UiacIN.js} +2 -2
- package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-DMSN2vxl.js} +2 -2
- package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-CRwfOatX.js} +1 -1
- package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-CRJsi-vz.js} +1 -1
- package/client/dist/assets/{Home-DUkUTTTy.js → Home-pwmK9_Eh.js} +1 -1
- package/client/dist/assets/{Insights-QCh4xW12.js → Insights-m7kFzg2A.js} +1 -1
- package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-C1wpPvu6.js} +1 -1
- package/client/dist/assets/ListeningList-OzfEI1Ga.js +1 -0
- package/client/dist/assets/{Manga-HydR-L7o.js → Manga-ChRCExhK.js} +1 -1
- package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DSHJPo2A.js} +1 -1
- package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-CzOVY1wA.js} +1 -1
- package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-CJfzdUBb.js} +1 -1
- package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-CifQMfhs.js} +1 -1
- package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CM10yB9V.js} +1 -1
- package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-6Kovbsqw.js} +1 -1
- package/client/dist/assets/{Player-CsH4x_I_.js → Player-CK0hT-u4.js} +2 -2
- package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-Bb66uqT9.js} +1 -1
- package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-C_nTj8_q.js} +1 -1
- package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-DUK7URAE.js} +1 -1
- package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-Ck9LJThU.js} +1 -1
- package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BUphIv0k.js} +1 -1
- package/client/dist/assets/{Search-DFkhuk-x.js → Search-zJyP15h6.js} +1 -1
- package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-BYdoSEUh.js} +1 -1
- package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CqgbOhjX.js} +1 -1
- package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-BVjyv6CM.js} +1 -1
- package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-L13ylswR.js} +1 -1
- package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-Cvcm5U2D.js} +1 -1
- package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-DhxfKvYm.js} +1 -1
- package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-P8bFE6_X.js} +1 -1
- package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-D5gVPjtb.js} +1 -1
- package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-Cjq8l3rU.js} +1 -1
- package/client/dist/assets/{hls-BGCX7mJy.js → hls-BkzuANAg.js} +1 -1
- package/client/dist/assets/{index-CnfZwsds.js → index-CLVgzKLk.js} +19 -19
- package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-DZJZu-so.js} +1 -1
- package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CvrMGHuo.js} +1 -1
- package/client/dist/assets/useAsmrPopup-Bimy8Ouw.js +1 -0
- package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary--MiN11uR.js} +1 -1
- package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-CR5XhKKS.js} +1 -1
- package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-DqdT4fA5.js} +1 -1
- package/client/dist/assets/{useTv-DGflCsSI.js → useTv-BL0HMDyP.js} +1 -1
- package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-D4U4iqN8.js} +1 -1
- package/client/dist/index.html +1 -1
- package/package.json +3 -5
- package/server/dist/config.js +1 -174
- package/server/dist/server.js +1068 -289
- package/server/dist/shipped-defaults.js +1 -6
- package/server/package.json +4 -5
- package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
- package/client/dist/assets/ListeningList-DguKjI_t.js +0 -1
- package/client/dist/assets/Music-BGH_0voi.css +0 -1
- package/client/dist/assets/Music-CQpyXTLH.js +0 -1
- package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
- package/client/dist/assets/Radio-DF6g6odd.js +0 -1
- package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
- package/client/dist/assets/useAsmrPopup-SymV1J-b.js +0 -1
- package/server/dist/app-auth.js +0 -157
- package/server/dist/app-hono.js +0 -598
- package/server/dist/db/drizzle.js +0 -16
- package/server/dist/db/schema-anime.js +0 -61
- package/server/dist/db/schema-asmr.js +0 -13
- package/server/dist/db/schema-manga.js +0 -31
- package/server/dist/db/schema-tv.js +0 -18
- package/server/dist/db.js +0 -217
- package/server/dist/discord-gateway.js +0 -385
- package/server/dist/discord-rpc.js +0 -413
- package/server/dist/github-sync.js +0 -508
- package/server/dist/google.js +0 -741
- package/server/dist/hono/asmr.js +0 -10
- package/server/dist/hono/auth.js +0 -49
- package/server/dist/hono/music.js +0 -209
- package/server/dist/hono/proxy.js +0 -827
- package/server/dist/hono/tv.js +0 -200
- package/server/dist/hono/watchlist.js +0 -341
- package/server/dist/lib/anilist.js +0 -1583
- package/server/dist/lib/anischedule.js +0 -191
- package/server/dist/lib/insights.js +0 -185
- package/server/dist/lib/ipc.js +0 -23
- package/server/dist/lib/kitsu.js +0 -517
- package/server/dist/lib/mal-import.js +0 -291
- package/server/dist/lib/mal.js +0 -957
- package/server/dist/lib/migration.js +0 -310
- package/server/dist/lib/offline-db.js +0 -395
- package/server/dist/lib/potoken.js +0 -135
- package/server/dist/lib/recommendations.service.js +0 -216
- package/server/dist/lib/temp-ids.js +0 -3
- package/server/dist/lib/tmdb.js +0 -72
- package/server/dist/lib/tracker/anilist-tracker.js +0 -308
- package/server/dist/lib/tracker/manga-sync.service.js +0 -773
- package/server/dist/lib/tracker/sync-state.js +0 -21
- package/server/dist/lib/tracker/sync.service.js +0 -476
- package/server/dist/lib/translate.js +0 -79
- package/server/dist/lib/ump.js +0 -106
- package/server/dist/lib/ytmusic.js +0 -162
- package/server/dist/logger.js +0 -16
- package/server/dist/providers/manga/manga.types.js +0 -1
- package/server/dist/providers/provider.interface.js +0 -1
- package/server/dist/providers/remote-loader.js +0 -614
- package/server/dist/providers/remote-types.js +0 -159
- package/server/dist/providers/title-matching.js +0 -226
- package/server/dist/providers/tv.types.js +0 -1
- package/server/dist/rclone.js +0 -120
- package/server/dist/repositories/asmr.repository.js +0 -77
- package/server/dist/repositories/insights.repository.js +0 -106
- package/server/dist/repositories/library.repository.js +0 -94
- package/server/dist/repositories/mal-cache.repository.js +0 -52
- package/server/dist/repositories/manga.repository.js +0 -95
- package/server/dist/repositories/notifications.repository.js +0 -60
- package/server/dist/repositories/queue.repository.js +0 -72
- package/server/dist/repositories/settings.repository.js +0 -30
- package/server/dist/repositories/shows-meta.repository.js +0 -58
- package/server/dist/repositories/temp-show-ids.repository.js +0 -63
- package/server/dist/repositories/tv.repository.js +0 -93
- package/server/dist/repositories/watched-episodes.repository.js +0 -82
- package/server/dist/repositories/watchlist.repository.js +0 -60
- package/server/dist/scripts/backfill-episode-duration.js +0 -42
- package/server/dist/sync-payload.js +0 -91
- package/server/dist/sync.js +0 -657
- package/server/dist/trpc/context.js +0 -7
- package/server/dist/trpc/errors.js +0 -15
- package/server/dist/trpc/index.js +0 -18
- package/server/dist/trpc/router.js +0 -49
- package/server/dist/trpc/routers/asmr-library.js +0 -112
- package/server/dist/trpc/routers/asmr-progress.js +0 -121
- package/server/dist/trpc/routers/asmr.js +0 -87
- package/server/dist/trpc/routers/auth.js +0 -194
- package/server/dist/trpc/routers/continue-watching.js +0 -81
- package/server/dist/trpc/routers/data.js +0 -1026
- package/server/dist/trpc/routers/discord.js +0 -285
- package/server/dist/trpc/routers/discovery.js +0 -11
- package/server/dist/trpc/routers/insights.js +0 -14
- package/server/dist/trpc/routers/manga-library.js +0 -263
- package/server/dist/trpc/routers/manga-progress.js +0 -169
- package/server/dist/trpc/routers/manga.js +0 -201
- package/server/dist/trpc/routers/music.js +0 -655
- package/server/dist/trpc/routers/notifications.js +0 -80
- package/server/dist/trpc/routers/progress.js +0 -109
- package/server/dist/trpc/routers/queue.js +0 -93
- package/server/dist/trpc/routers/radio.js +0 -145
- package/server/dist/trpc/routers/settings.js +0 -96
- package/server/dist/trpc/routers/tracker.js +0 -213
- package/server/dist/trpc/routers/tv-library.js +0 -135
- package/server/dist/trpc/routers/tv-progress.js +0 -159
- package/server/dist/trpc/routers/tv.js +0 -551
- package/server/dist/trpc/routers/watchlist.js +0 -550
- package/server/dist/trpc/validation.js +0 -258
- package/server/dist/utils/cache.utils.js +0 -25
- package/server/dist/utils/cookie.utils.js +0 -13
- package/server/dist/utils/db-utils.js +0 -17
- package/server/dist/utils/env.utils.js +0 -77
- package/server/dist/utils/http.utils.js +0 -82
- package/server/dist/utils/request-context.js +0 -2
- package/server/dist/utils/security.utils.js +0 -76
- /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { initTRPC } from '@trpc/server';
|
|
2
|
-
import { lanAuthRequiredError } from './errors.js';
|
|
3
|
-
const t = initTRPC.context().create({
|
|
4
|
-
errorFormatter: ({ shape, error }) => ({
|
|
5
|
-
...shape,
|
|
6
|
-
data: {
|
|
7
|
-
...shape.data,
|
|
8
|
-
cause: error.cause ?? null,
|
|
9
|
-
},
|
|
10
|
-
}),
|
|
11
|
-
});
|
|
12
|
-
export const router = t.router;
|
|
13
|
-
const lanGuard = t.middleware(({ ctx, next }) => {
|
|
14
|
-
if (!ctx.lanAuthed)
|
|
15
|
-
throw lanAuthRequiredError();
|
|
16
|
-
return next({ ctx });
|
|
17
|
-
});
|
|
18
|
-
export const protectedProcedure = t.procedure.use(lanGuard);
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { router } from './index.js';
|
|
2
|
-
import { settingsRouter } from './routers/settings.js';
|
|
3
|
-
import { queueRouter } from './routers/queue.js';
|
|
4
|
-
import { watchlistRouter } from './routers/watchlist.js';
|
|
5
|
-
import { progressRouter } from './routers/progress.js';
|
|
6
|
-
import { notificationsRouter } from './routers/notifications.js';
|
|
7
|
-
import { continueWatchingRouter } from './routers/continue-watching.js';
|
|
8
|
-
import { discoveryRouter } from './routers/discovery.js';
|
|
9
|
-
import { mangaLibraryRouter } from './routers/manga-library.js';
|
|
10
|
-
import { mangaProgressRouter } from './routers/manga-progress.js';
|
|
11
|
-
import { asmrLibraryRouter } from './routers/asmr-library.js';
|
|
12
|
-
import { asmrProgressRouter } from './routers/asmr-progress.js';
|
|
13
|
-
import { tvLibraryRouter } from './routers/tv-library.js';
|
|
14
|
-
import { tvProgressRouter } from './routers/tv-progress.js';
|
|
15
|
-
import { tvRouter } from './routers/tv.js';
|
|
16
|
-
import { mangaRouter } from './routers/manga.js';
|
|
17
|
-
import { asmrRouter } from './routers/asmr.js';
|
|
18
|
-
import { musicRouter } from './routers/music.js';
|
|
19
|
-
import { radioRouter } from './routers/radio.js';
|
|
20
|
-
import { trackerRouter } from './routers/tracker.js';
|
|
21
|
-
import { insightsRouter } from './routers/insights.js';
|
|
22
|
-
import { authRouter } from './routers/auth.js';
|
|
23
|
-
import { dataRouter } from './routers/data.js';
|
|
24
|
-
import { discordRouter } from './routers/discord.js';
|
|
25
|
-
export const appRouter = router({
|
|
26
|
-
settings: settingsRouter,
|
|
27
|
-
queue: queueRouter,
|
|
28
|
-
watchlist: watchlistRouter,
|
|
29
|
-
progress: progressRouter,
|
|
30
|
-
notifications: notificationsRouter,
|
|
31
|
-
continueWatching: continueWatchingRouter,
|
|
32
|
-
discovery: discoveryRouter,
|
|
33
|
-
mangaLibrary: mangaLibraryRouter,
|
|
34
|
-
mangaProgress: mangaProgressRouter,
|
|
35
|
-
asmrLibrary: asmrLibraryRouter,
|
|
36
|
-
asmrProgress: asmrProgressRouter,
|
|
37
|
-
tvLibrary: tvLibraryRouter,
|
|
38
|
-
tvProgress: tvProgressRouter,
|
|
39
|
-
tv: tvRouter,
|
|
40
|
-
manga: mangaRouter,
|
|
41
|
-
asmr: asmrRouter,
|
|
42
|
-
music: musicRouter,
|
|
43
|
-
radio: radioRouter,
|
|
44
|
-
tracker: trackerRouter,
|
|
45
|
-
insights: insightsRouter,
|
|
46
|
-
auth: authRouter,
|
|
47
|
-
data: dataRouter,
|
|
48
|
-
discord: discordRouter,
|
|
49
|
-
});
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
import { TRPCError } from '@trpc/server';
|
|
2
|
-
import { protectedProcedure, router } from '../index.js';
|
|
3
|
-
import { performAsmrWriteTransactionAsync } from '../../sync.js';
|
|
4
|
-
import { buildAsmrId, ASMR_STATUSES, AsmrLibraryRepository, AsmrProgressRepository, } from '../../repositories/asmr.repository.js';
|
|
5
|
-
import { defineSchema, reqObj, reqStr, optStr, reqStrArray, readPageLimit } from '../validation.js';
|
|
6
|
-
const asmrLibraryListInput = () => defineSchema((value) => {
|
|
7
|
-
const obj = reqObj(value);
|
|
8
|
-
const out = { ...readPageLimit(obj) };
|
|
9
|
-
const status = optStr(obj, 'status');
|
|
10
|
-
if (status !== undefined)
|
|
11
|
-
out.status = status;
|
|
12
|
-
return out;
|
|
13
|
-
});
|
|
14
|
-
const asmrLibraryIdInput = () => defineSchema((value) => ({
|
|
15
|
-
id: reqStr(reqObj(value), 'id'),
|
|
16
|
-
}));
|
|
17
|
-
const asmrLibraryAddInput = () => defineSchema((value) => {
|
|
18
|
-
const obj = reqObj(value);
|
|
19
|
-
return {
|
|
20
|
-
rjCode: optStr(obj, 'rjCode'),
|
|
21
|
-
id: optStr(obj, 'id'),
|
|
22
|
-
title: optStr(obj, 'title'),
|
|
23
|
-
thumbnail: optStr(obj, 'thumbnail'),
|
|
24
|
-
status: optStr(obj, 'status'),
|
|
25
|
-
isAdult: obj.isAdult,
|
|
26
|
-
};
|
|
27
|
-
});
|
|
28
|
-
const asmrLibraryStatusInput = () => defineSchema((value) => {
|
|
29
|
-
const obj = reqObj(value);
|
|
30
|
-
return { id: reqStr(obj, 'id'), status: reqStr(obj, 'status') };
|
|
31
|
-
});
|
|
32
|
-
const asmrLibraryBatchStatusInput = () => defineSchema((value) => {
|
|
33
|
-
const obj = reqObj(value);
|
|
34
|
-
return { ids: reqStrArray(obj, 'ids', true), status: reqStr(obj, 'status') };
|
|
35
|
-
});
|
|
36
|
-
const asmrLibraryIdsInput = () => defineSchema((value) => ({
|
|
37
|
-
ids: reqStrArray(reqObj(value), 'ids', true),
|
|
38
|
-
}));
|
|
39
|
-
export const asmrLibraryRouter = router({
|
|
40
|
-
list: protectedProcedure.input(asmrLibraryListInput()).query(async ({ ctx, input }) => {
|
|
41
|
-
const page = Math.max(parseInt(String(input.page ?? 1)) || 1, 1);
|
|
42
|
-
const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
|
|
43
|
-
const offset = (page - 1) * limit;
|
|
44
|
-
const rows = await AsmrLibraryRepository.getAll(ctx.asmrDb, input.status, limit, offset);
|
|
45
|
-
const total = await AsmrLibraryRepository.getCount(ctx.asmrDb, input.status);
|
|
46
|
-
return { data: rows, total, page, limit };
|
|
47
|
-
}),
|
|
48
|
-
ids: protectedProcedure.query(async ({ ctx }) => {
|
|
49
|
-
return { ids: await AsmrLibraryRepository.getIds(ctx.asmrDb) };
|
|
50
|
-
}),
|
|
51
|
-
check: protectedProcedure.input(asmrLibraryIdInput()).query(async ({ ctx, input }) => {
|
|
52
|
-
const item = await AsmrLibraryRepository.getById(ctx.asmrDb, input.id);
|
|
53
|
-
return { inLibrary: !!item, status: item?.status ?? null };
|
|
54
|
-
}),
|
|
55
|
-
add: protectedProcedure.input(asmrLibraryAddInput()).mutation(async ({ ctx, input }) => {
|
|
56
|
-
const rawCode = String(input.rjCode || input.id || '').trim();
|
|
57
|
-
if (!rawCode) {
|
|
58
|
-
throw new TRPCError({ code: 'BAD_REQUEST', message: 'rjCode is required' });
|
|
59
|
-
}
|
|
60
|
-
const id = input.id && input.id.trim() ? buildAsmrId(input.id) : buildAsmrId(rawCode);
|
|
61
|
-
const cleanStatus = typeof input.status === 'string' && ASMR_STATUSES.includes(input.status)
|
|
62
|
-
? input.status
|
|
63
|
-
: 'Listening';
|
|
64
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
65
|
-
await AsmrLibraryRepository.upsert(tx, {
|
|
66
|
-
id,
|
|
67
|
-
rjCode: buildAsmrId(rawCode),
|
|
68
|
-
title: String(input.title || ''),
|
|
69
|
-
thumbnail: String(input.thumbnail || ''),
|
|
70
|
-
status: cleanStatus,
|
|
71
|
-
isAdult: input.isAdult === true || input.isAdult === 1,
|
|
72
|
-
});
|
|
73
|
-
});
|
|
74
|
-
return { success: true, id };
|
|
75
|
-
}),
|
|
76
|
-
remove: protectedProcedure.input(asmrLibraryIdInput()).mutation(async ({ ctx, input }) => {
|
|
77
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
78
|
-
await AsmrLibraryRepository.delete(tx, String(input.id));
|
|
79
|
-
await AsmrProgressRepository.deleteByWork(tx, String(input.id));
|
|
80
|
-
});
|
|
81
|
-
return { success: true };
|
|
82
|
-
}),
|
|
83
|
-
setStatus: protectedProcedure.input(asmrLibraryStatusInput()).mutation(async ({ ctx, input }) => {
|
|
84
|
-
if (!input.id || !ASMR_STATUSES.includes(input.status)) {
|
|
85
|
-
throw new TRPCError({ code: 'BAD_REQUEST', message: 'id and a valid status are required' });
|
|
86
|
-
}
|
|
87
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
88
|
-
await AsmrLibraryRepository.updateStatus(tx, String(input.id), String(input.status));
|
|
89
|
-
});
|
|
90
|
-
return { success: true };
|
|
91
|
-
}),
|
|
92
|
-
batchStatus: protectedProcedure
|
|
93
|
-
.input(asmrLibraryBatchStatusInput())
|
|
94
|
-
.mutation(async ({ ctx, input }) => {
|
|
95
|
-
if (!input.status || !ASMR_STATUSES.includes(input.status)) {
|
|
96
|
-
throw new TRPCError({ code: 'BAD_REQUEST', message: 'a valid status is required' });
|
|
97
|
-
}
|
|
98
|
-
const ids = input.ids.map((id) => String(id));
|
|
99
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
100
|
-
await AsmrLibraryRepository.updateStatusMany(tx, ids, String(input.status));
|
|
101
|
-
});
|
|
102
|
-
return { success: true, updated: ids.length };
|
|
103
|
-
}),
|
|
104
|
-
removeMany: protectedProcedure.input(asmrLibraryIdsInput()).mutation(async ({ ctx, input }) => {
|
|
105
|
-
const ids = input.ids.map((id) => String(id));
|
|
106
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
107
|
-
await AsmrLibraryRepository.deleteMany(tx, ids);
|
|
108
|
-
await AsmrProgressRepository.deleteMany(tx, ids);
|
|
109
|
-
});
|
|
110
|
-
return { success: true, removed: ids.length };
|
|
111
|
-
}),
|
|
112
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import { protectedProcedure, router } from '../index.js';
|
|
2
|
-
import { performAsmrWriteTransactionAsync } from '../../sync.js';
|
|
3
|
-
import { adultNonListIds } from '../../utils/db-utils.js';
|
|
4
|
-
import { SettingsRepository } from '../../repositories/settings.repository.js';
|
|
5
|
-
import { AsmrLibraryRepository, AsmrProgressRepository, } from '../../repositories/asmr.repository.js';
|
|
6
|
-
import { defineSchema, reqObj, reqStr, reqStrArray, strOrNull } from '../validation.js';
|
|
7
|
-
const getAdultNonListWorkIds = (db) => adultNonListIds(db, 'asmr_progress', 'asmr_library', 'workId', 'COALESCE(l.isAdult, p.isAdult) = 1');
|
|
8
|
-
const asmrProgressIdsInput = () => defineSchema((value) => ({
|
|
9
|
-
ids: reqStrArray(reqObj(value), 'ids', true),
|
|
10
|
-
}));
|
|
11
|
-
const asmrProgressWorkInput = () => defineSchema((value) => ({
|
|
12
|
-
workId: reqStr(reqObj(value), 'workId'),
|
|
13
|
-
}));
|
|
14
|
-
const asmrProgressSaveInput = () => defineSchema((value) => {
|
|
15
|
-
const obj = reqObj(value);
|
|
16
|
-
return {
|
|
17
|
-
workId: reqStr(obj, 'workId'),
|
|
18
|
-
trackIndex: obj.trackIndex,
|
|
19
|
-
trackLabel: obj.trackLabel,
|
|
20
|
-
currentTime: obj.currentTime,
|
|
21
|
-
duration: obj.duration,
|
|
22
|
-
title: strOrNull(obj, 'title'),
|
|
23
|
-
thumbnail: strOrNull(obj, 'thumbnail'),
|
|
24
|
-
rjCode: strOrNull(obj, 'rjCode'),
|
|
25
|
-
isAdult: obj.isAdult ?? null,
|
|
26
|
-
};
|
|
27
|
-
});
|
|
28
|
-
const asmrProgressRemoveInput = () => defineSchema((value) => {
|
|
29
|
-
const obj = reqObj(value);
|
|
30
|
-
return { workId: reqStr(obj, 'workId'), trackIndex: obj.trackIndex ?? undefined };
|
|
31
|
-
});
|
|
32
|
-
const asmrContinueListeningInput = () => defineSchema((value) => {
|
|
33
|
-
const obj = reqObj(value);
|
|
34
|
-
const out = {};
|
|
35
|
-
if (obj.limit !== undefined && obj.limit !== null && obj.limit !== '') {
|
|
36
|
-
const limit = Number(obj.limit);
|
|
37
|
-
if (Number.isFinite(limit))
|
|
38
|
-
out.limit = limit;
|
|
39
|
-
}
|
|
40
|
-
return out;
|
|
41
|
-
});
|
|
42
|
-
export const asmrProgressRouter = router({
|
|
43
|
-
getByWork: protectedProcedure.input(asmrProgressWorkInput()).query(async ({ ctx, input }) => {
|
|
44
|
-
const rows = await AsmrProgressRepository.getByWork(ctx.asmrDb, input.workId);
|
|
45
|
-
return { progress: rows };
|
|
46
|
-
}),
|
|
47
|
-
latest: protectedProcedure.input(asmrProgressWorkInput()).query(async ({ ctx, input }) => {
|
|
48
|
-
const row = await AsmrProgressRepository.getLatest(ctx.asmrDb, input.workId);
|
|
49
|
-
return row || { currentTime: 0, duration: 0 };
|
|
50
|
-
}),
|
|
51
|
-
save: protectedProcedure.input(asmrProgressSaveInput()).mutation(async ({ ctx, input }) => {
|
|
52
|
-
const trackIndexNum = Math.max(Math.round(Number(input.trackIndex)) || 0, 0);
|
|
53
|
-
const timeNum = Math.max(Number(input.currentTime) || 0, 0);
|
|
54
|
-
const durationNum = Math.max(Number(input.duration) || 0, 0);
|
|
55
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
56
|
-
await AsmrProgressRepository.upsert(tx, {
|
|
57
|
-
workId: String(input.workId),
|
|
58
|
-
trackIndex: trackIndexNum,
|
|
59
|
-
trackLabel: String(input.trackLabel || ''),
|
|
60
|
-
currentTime: timeNum,
|
|
61
|
-
duration: durationNum,
|
|
62
|
-
title: input.title ?? null,
|
|
63
|
-
thumbnail: input.thumbnail ?? null,
|
|
64
|
-
rjCode: input.rjCode ?? null,
|
|
65
|
-
isAdult: input.isAdult != null ? Number(input.isAdult) : null,
|
|
66
|
-
});
|
|
67
|
-
await AsmrLibraryRepository.touchProgress(tx, String(input.workId), {
|
|
68
|
-
trackIndex: trackIndexNum,
|
|
69
|
-
trackLabel: String(input.trackLabel || ''),
|
|
70
|
-
position: timeNum,
|
|
71
|
-
});
|
|
72
|
-
});
|
|
73
|
-
return { success: true };
|
|
74
|
-
}),
|
|
75
|
-
remove: protectedProcedure.input(asmrProgressRemoveInput()).mutation(async ({ ctx, input }) => {
|
|
76
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
77
|
-
if (input.trackIndex !== undefined && input.trackIndex !== null) {
|
|
78
|
-
await AsmrProgressRepository.deleteTrack(tx, String(input.workId), Number(input.trackIndex));
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
await AsmrProgressRepository.deleteByWork(tx, String(input.workId));
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
return { success: true };
|
|
85
|
-
}),
|
|
86
|
-
removeMany: protectedProcedure.input(asmrProgressIdsInput()).mutation(async ({ ctx, input }) => {
|
|
87
|
-
const ids = input.ids.map((id) => String(id));
|
|
88
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
89
|
-
await AsmrProgressRepository.deleteMany(tx, ids);
|
|
90
|
-
});
|
|
91
|
-
return { success: true, removed: ids.length };
|
|
92
|
-
}),
|
|
93
|
-
continueListening: protectedProcedure
|
|
94
|
-
.input(asmrContinueListeningInput())
|
|
95
|
-
.query(async ({ ctx, input }) => {
|
|
96
|
-
const limit = Math.min(Math.max(parseInt(String(input.limit ?? 24)) || 24, 1), 100);
|
|
97
|
-
const { ignoreAdult, listOnly } = await SettingsRepository.readContinueFlags(ctx.db, 'asmrIgnoreAdultContent', 'asmrCwWatchlistOnly');
|
|
98
|
-
const rows = (await AsmrProgressRepository.getContinueListening(ctx.asmrDb, limit)).filter((row) => {
|
|
99
|
-
if (ignoreAdult && row.isAdult === 1)
|
|
100
|
-
return false;
|
|
101
|
-
if (listOnly && row.watchlistStatus !== 'Listening')
|
|
102
|
-
return false;
|
|
103
|
-
return true;
|
|
104
|
-
});
|
|
105
|
-
return { data: rows, total: rows.length };
|
|
106
|
-
}),
|
|
107
|
-
adultCount: protectedProcedure.query(async ({ ctx }) => {
|
|
108
|
-
const ids = await getAdultNonListWorkIds(ctx.asmrDb);
|
|
109
|
-
return { count: ids.length };
|
|
110
|
-
}),
|
|
111
|
-
purgeAdult: protectedProcedure.mutation(async ({ ctx }) => {
|
|
112
|
-
const ids = await getAdultNonListWorkIds(ctx.asmrDb);
|
|
113
|
-
if (ids.length > 0) {
|
|
114
|
-
await performAsmrWriteTransactionAsync(ctx.asmrDb, async (tx) => {
|
|
115
|
-
for (const id of ids)
|
|
116
|
-
await AsmrProgressRepository.deleteByWork(tx, id);
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
return { success: true, removed: ids.length };
|
|
120
|
-
}),
|
|
121
|
-
});
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { protectedProcedure, router } from '../index.js';
|
|
2
|
-
import { defineSchema, optStr, reqObj, reqStr } from '../validation.js';
|
|
3
|
-
import { authRequiredError } from '../errors.js';
|
|
4
|
-
import logger from '../../logger.js';
|
|
5
|
-
async function cached(ctx, key, ttl, produce) {
|
|
6
|
-
const hit = ctx.apiCache.get(key);
|
|
7
|
-
if (hit)
|
|
8
|
-
return hit;
|
|
9
|
-
const body = await produce();
|
|
10
|
-
if (ttl !== undefined)
|
|
11
|
-
ctx.apiCache.set(key, body, ttl);
|
|
12
|
-
else
|
|
13
|
-
ctx.apiCache.set(key, body);
|
|
14
|
-
return body;
|
|
15
|
-
}
|
|
16
|
-
const asmrBrowseInput = () => defineSchema((value) => {
|
|
17
|
-
if (value === undefined || value === null)
|
|
18
|
-
return {};
|
|
19
|
-
const obj = reqObj(value);
|
|
20
|
-
const out = {};
|
|
21
|
-
const q = optStr(obj, 'q');
|
|
22
|
-
if (q !== undefined)
|
|
23
|
-
out.q = q;
|
|
24
|
-
if (obj.page !== undefined)
|
|
25
|
-
out.page = parseInt(String(obj.page), 10) || 1;
|
|
26
|
-
const sort = optStr(obj, 'sort');
|
|
27
|
-
if (sort !== undefined)
|
|
28
|
-
out.sort = sort;
|
|
29
|
-
const rating = optStr(obj, 'rating');
|
|
30
|
-
if (rating !== undefined)
|
|
31
|
-
out.rating = rating;
|
|
32
|
-
return out;
|
|
33
|
-
});
|
|
34
|
-
const asmrWorkInput = () => defineSchema((value) => ({ rj: reqStr(reqObj(value), 'rj') }));
|
|
35
|
-
export const asmrRouter = router({
|
|
36
|
-
browse: protectedProcedure.input(asmrBrowseInput()).query(async ({ ctx, input }) => {
|
|
37
|
-
const q = input.q || '';
|
|
38
|
-
const page = input.page || 1;
|
|
39
|
-
const sort = input.sort || '';
|
|
40
|
-
const rating = input.rating || '';
|
|
41
|
-
return cached(ctx, `route-asmr-browse-${q}-${page}-${sort}-${rating}-${sort === 'random' ? Date.now() : ''}`, 300, async () => {
|
|
42
|
-
try {
|
|
43
|
-
const provider = ctx.getJasmr();
|
|
44
|
-
if (!provider)
|
|
45
|
-
return { shows: [], hasNext: false };
|
|
46
|
-
return await provider.browse({ query: q, page, sort, rating });
|
|
47
|
-
}
|
|
48
|
-
catch (err) {
|
|
49
|
-
if (err.message === 'AUTH_REQUIRED')
|
|
50
|
-
throw authRequiredError('jasmr');
|
|
51
|
-
logger.error({ err }, '[Asmr] browse failed');
|
|
52
|
-
return { shows: [], hasNext: false };
|
|
53
|
-
}
|
|
54
|
-
});
|
|
55
|
-
}),
|
|
56
|
-
work: protectedProcedure.input(asmrWorkInput()).query(async ({ ctx, input }) => {
|
|
57
|
-
const rj = input.rj;
|
|
58
|
-
return cached(ctx, `route-asmr-work-${rj}`, 1800, async () => {
|
|
59
|
-
try {
|
|
60
|
-
const provider = ctx.getJasmr();
|
|
61
|
-
if (!provider) {
|
|
62
|
-
return { rjCode: rj, description: '', tracks: [], images: [], chapters: [] };
|
|
63
|
-
}
|
|
64
|
-
const rjCode = String(rj).trim().toUpperCase();
|
|
65
|
-
const episodes = await provider.getEpisodes(rjCode);
|
|
66
|
-
const [streams, images, chapters] = await Promise.all([
|
|
67
|
-
provider.getStreamUrls(rjCode, '1'),
|
|
68
|
-
provider.getImages(rjCode),
|
|
69
|
-
provider.getChapters(rjCode),
|
|
70
|
-
]);
|
|
71
|
-
return {
|
|
72
|
-
rjCode,
|
|
73
|
-
description: episodes?.description || '',
|
|
74
|
-
tracks: streams?.[0]?.links || [],
|
|
75
|
-
images,
|
|
76
|
-
chapters,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
if (err.message === 'AUTH_REQUIRED')
|
|
81
|
-
throw authRequiredError('jasmr');
|
|
82
|
-
logger.error({ err, rj }, '[Asmr] work fetch failed');
|
|
83
|
-
return { rjCode: rj, description: '', tracks: [], images: [], chapters: [] };
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
}),
|
|
87
|
-
});
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import logger from '../../logger.js';
|
|
2
|
-
import { TRPCError } from '@trpc/server';
|
|
3
|
-
import { googleDriveService } from '../../google.js';
|
|
4
|
-
import { githubSyncService } from '../../github-sync.js';
|
|
5
|
-
import { initSyncProvider, getActiveProvider, runFullSyncSequence } from '../../sync.js';
|
|
6
|
-
import { CONFIG } from '../../config.js';
|
|
7
|
-
import { rcloneService } from '../../rclone.js';
|
|
8
|
-
import { updateEnvFile } from '../../utils/env.utils.js';
|
|
9
|
-
import { protectedProcedure, router } from '../index.js';
|
|
10
|
-
import { defineSchema, reqObj } from '../validation.js';
|
|
11
|
-
async function runSyncFromCtx(ctx, database, preferredProvider) {
|
|
12
|
-
const remoteFolder = CONFIG.IS_DEV ? CONFIG.REMOTE_FOLDER_DEV : CONFIG.REMOTE_FOLDER_PROD;
|
|
13
|
-
await runFullSyncSequence({ db: database, mangaDb: ctx.mangaDb, tvDb: ctx.tvDb, asmrDb: ctx.asmrDb }, { remoteFolder, preferredProvider });
|
|
14
|
-
}
|
|
15
|
-
const googleAuthSaveInput = () => defineSchema((value) => {
|
|
16
|
-
const obj = reqObj(value);
|
|
17
|
-
return { clientId: obj.clientId, clientSecret: obj.clientSecret, workerUrl: obj.workerUrl };
|
|
18
|
-
});
|
|
19
|
-
const githubAuthSaveInput = () => defineSchema((value) => {
|
|
20
|
-
const obj = reqObj(value);
|
|
21
|
-
if (typeof obj.clientId !== 'string')
|
|
22
|
-
throw new Error('clientId required');
|
|
23
|
-
return { clientId: obj.clientId };
|
|
24
|
-
});
|
|
25
|
-
const syncProviderInput = () => defineSchema((value) => ({
|
|
26
|
-
provider: reqObj(value).provider,
|
|
27
|
-
}));
|
|
28
|
-
const rcloneSaveInput = () => defineSchema((value) => ({
|
|
29
|
-
remote: reqObj(value).remote,
|
|
30
|
-
}));
|
|
31
|
-
const serverPortInput = () => defineSchema((value) => ({
|
|
32
|
-
port: reqObj(value).port,
|
|
33
|
-
}));
|
|
34
|
-
export const authRouter = router({
|
|
35
|
-
configStatus: protectedProcedure.query(async () => {
|
|
36
|
-
const useWorker = !!CONFIG.GOOGLE_AUTH_WORKER_URL;
|
|
37
|
-
const hasLegacyConfig = !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET;
|
|
38
|
-
return { hasConfig: useWorker || hasLegacyConfig, useWorker };
|
|
39
|
-
}),
|
|
40
|
-
googleAuth: protectedProcedure.query(async () => {
|
|
41
|
-
return {
|
|
42
|
-
useWorker: !!CONFIG.GOOGLE_AUTH_WORKER_URL,
|
|
43
|
-
hasCustomWorkerUrl: !!process.env.GOOGLE_AUTH_WORKER_URL,
|
|
44
|
-
hasCustomClientId: !!process.env.GOOGLE_CLIENT_ID,
|
|
45
|
-
hasClientSecret: !!process.env.GOOGLE_CLIENT_SECRET,
|
|
46
|
-
};
|
|
47
|
-
}),
|
|
48
|
-
saveGoogleAuth: protectedProcedure.input(googleAuthSaveInput()).mutation(async ({ input }) => {
|
|
49
|
-
const { clientId, clientSecret, workerUrl } = input;
|
|
50
|
-
const updates = {};
|
|
51
|
-
if (typeof clientId === 'string') {
|
|
52
|
-
updates.GOOGLE_CLIENT_ID = clientId;
|
|
53
|
-
}
|
|
54
|
-
if (typeof clientSecret === 'string') {
|
|
55
|
-
updates.GOOGLE_CLIENT_SECRET = clientSecret;
|
|
56
|
-
}
|
|
57
|
-
if (typeof workerUrl === 'string') {
|
|
58
|
-
updates.GOOGLE_AUTH_WORKER_URL = workerUrl;
|
|
59
|
-
}
|
|
60
|
-
await updateEnvFile(updates);
|
|
61
|
-
if (typeof workerUrl === 'string') {
|
|
62
|
-
;
|
|
63
|
-
CONFIG.GOOGLE_AUTH_WORKER_URL = workerUrl;
|
|
64
|
-
}
|
|
65
|
-
if (typeof clientId === 'string') {
|
|
66
|
-
;
|
|
67
|
-
CONFIG.GOOGLE_CLIENT_ID = clientId || undefined;
|
|
68
|
-
}
|
|
69
|
-
if (typeof clientSecret === 'string') {
|
|
70
|
-
;
|
|
71
|
-
CONFIG.GOOGLE_CLIENT_SECRET =
|
|
72
|
-
clientSecret || undefined;
|
|
73
|
-
}
|
|
74
|
-
await initSyncProvider();
|
|
75
|
-
return { success: true };
|
|
76
|
-
}),
|
|
77
|
-
githubAuth: protectedProcedure.query(async () => {
|
|
78
|
-
return { hasCustomClientId: !!process.env.GITHUB_CLIENT_ID };
|
|
79
|
-
}),
|
|
80
|
-
saveGithubAuth: protectedProcedure.input(githubAuthSaveInput()).mutation(async ({ input }) => {
|
|
81
|
-
await updateEnvFile({ GITHUB_CLIENT_ID: input.clientId });
|
|
82
|
-
return { success: true };
|
|
83
|
-
}),
|
|
84
|
-
rcloneSettings: protectedProcedure.query(async () => {
|
|
85
|
-
const remotes = await rcloneService.listRemotes();
|
|
86
|
-
return {
|
|
87
|
-
remote: CONFIG.RCLONE_REMOTE || '',
|
|
88
|
-
availableRemotes: remotes,
|
|
89
|
-
activeRemote: rcloneService.isActive() ? rcloneService.getRemoteName() : null,
|
|
90
|
-
};
|
|
91
|
-
}),
|
|
92
|
-
syncSettings: protectedProcedure.query(async () => {
|
|
93
|
-
return {
|
|
94
|
-
activeProvider: process.env.SYNC_PROVIDER || 'default',
|
|
95
|
-
actualActiveProvider: getActiveProvider(),
|
|
96
|
-
authenticatedProviders: {
|
|
97
|
-
github: githubSyncService.isAuthenticated(),
|
|
98
|
-
google: googleDriveService.isAuthenticated(),
|
|
99
|
-
rclone: rcloneService.isActive(),
|
|
100
|
-
},
|
|
101
|
-
};
|
|
102
|
-
}),
|
|
103
|
-
saveSyncProvider: protectedProcedure.input(syncProviderInput()).mutation(async ({ input }) => {
|
|
104
|
-
const { provider } = input;
|
|
105
|
-
const value = provider === 'default' ? '' : provider;
|
|
106
|
-
await updateEnvFile({ SYNC_PROVIDER: value });
|
|
107
|
-
await initSyncProvider();
|
|
108
|
-
return { success: true, activeProvider: process.env.SYNC_PROVIDER || 'default' };
|
|
109
|
-
}),
|
|
110
|
-
githubStatus: protectedProcedure.query(async () => {
|
|
111
|
-
try {
|
|
112
|
-
const user = await githubSyncService.getUserProfile();
|
|
113
|
-
return {
|
|
114
|
-
authenticated: !!user,
|
|
115
|
-
user,
|
|
116
|
-
device: githubSyncService.getDeviceState(),
|
|
117
|
-
hasCustomClientId: !!process.env.GITHUB_CLIENT_ID,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
logger.error({ err: error }, 'Failed to fetch GitHub auth status');
|
|
122
|
-
return {
|
|
123
|
-
authenticated: false,
|
|
124
|
-
user: null,
|
|
125
|
-
device: githubSyncService.getDeviceState(),
|
|
126
|
-
hasCustomClientId: !!process.env.GITHUB_CLIENT_ID,
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
}),
|
|
130
|
-
githubStart: protectedProcedure.mutation(async ({ ctx }) => {
|
|
131
|
-
const state = await githubSyncService.startDeviceAuth(ctx.db, (db, provider) => runSyncFromCtx(ctx, db, provider));
|
|
132
|
-
return state;
|
|
133
|
-
}),
|
|
134
|
-
githubPoll: protectedProcedure.query(async () => {
|
|
135
|
-
return githubSyncService.getDeviceState();
|
|
136
|
-
}),
|
|
137
|
-
githubLogout: protectedProcedure.mutation(async () => {
|
|
138
|
-
await githubSyncService.logout();
|
|
139
|
-
await updateEnvFile({ SYNC_PROVIDER: '' });
|
|
140
|
-
await initSyncProvider();
|
|
141
|
-
return { success: true };
|
|
142
|
-
}),
|
|
143
|
-
saveRclone: protectedProcedure.input(rcloneSaveInput()).mutation(async ({ ctx, input }) => {
|
|
144
|
-
const { remote } = input;
|
|
145
|
-
await updateEnvFile({
|
|
146
|
-
RCLONE_REMOTE: remote,
|
|
147
|
-
SYNC_PROVIDER: 'rclone',
|
|
148
|
-
});
|
|
149
|
-
await runSyncFromCtx(ctx, ctx.db, 'rclone');
|
|
150
|
-
return { success: true };
|
|
151
|
-
}),
|
|
152
|
-
setServerPort: protectedProcedure.input(serverPortInput()).mutation(async ({ input }) => {
|
|
153
|
-
const port = Number(input.port);
|
|
154
|
-
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
155
|
-
throw new TRPCError({
|
|
156
|
-
code: 'BAD_REQUEST',
|
|
157
|
-
message: 'Port must be an integer between 1 and 65535',
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
await updateEnvFile({ PORT: String(port) });
|
|
161
|
-
logger.info({ port }, 'Server port saved. Restart required to apply.');
|
|
162
|
-
return { success: true, port };
|
|
163
|
-
}),
|
|
164
|
-
googleAuthUrl: protectedProcedure.query(async () => {
|
|
165
|
-
const url = await googleDriveService.getAuthUrl();
|
|
166
|
-
return { url };
|
|
167
|
-
}),
|
|
168
|
-
googleLogin: protectedProcedure.mutation(async ({ ctx }) => {
|
|
169
|
-
if (googleDriveService.isAuthenticated()) {
|
|
170
|
-
const user = await googleDriveService.getUserProfile();
|
|
171
|
-
if (user) {
|
|
172
|
-
await updateEnvFile({ SYNC_PROVIDER: 'google' });
|
|
173
|
-
await runSyncFromCtx(ctx, ctx.db, 'google');
|
|
174
|
-
return { url: null, authenticated: true };
|
|
175
|
-
}
|
|
176
|
-
else {
|
|
177
|
-
logger.warn('Google tokens found but invalid. Clearing and requesting new auth.');
|
|
178
|
-
await googleDriveService.logout();
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
const url = await googleDriveService.getAuthUrl();
|
|
182
|
-
return { url, authenticated: false };
|
|
183
|
-
}),
|
|
184
|
-
user: protectedProcedure.query(async () => {
|
|
185
|
-
const user = await googleDriveService.getUserProfile();
|
|
186
|
-
return user;
|
|
187
|
-
}),
|
|
188
|
-
logout: protectedProcedure.mutation(async () => {
|
|
189
|
-
await googleDriveService.logout();
|
|
190
|
-
await updateEnvFile({ SYNC_PROVIDER: '' });
|
|
191
|
-
await initSyncProvider();
|
|
192
|
-
return { success: true };
|
|
193
|
-
}),
|
|
194
|
-
});
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
import { protectedProcedure, router } from '../index.js';
|
|
2
|
-
import { performWriteTransactionAsync } from '../../sync.js';
|
|
3
|
-
import { WatchedEpisodesRepository } from '../../repositories/watched-episodes.repository.js';
|
|
4
|
-
import { NotificationsRepository } from '../../repositories/notifications.repository.js';
|
|
5
|
-
import { getMigratedId } from '../../lib/migration.js';
|
|
6
|
-
import { getAdultNonWatchlistShowIds } from '../../hono/watchlist.js';
|
|
7
|
-
import { dbAll } from '../../utils/db-utils.js';
|
|
8
|
-
import { idsInput, showIdInput } from '../validation.js';
|
|
9
|
-
export const continueWatchingRouter = router({
|
|
10
|
-
remove: protectedProcedure.input(showIdInput()).mutation(async ({ ctx, input }) => {
|
|
11
|
-
const showId = await getMigratedId(ctx.db, input.showId);
|
|
12
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
13
|
-
await WatchedEpisodesRepository.deleteByShow(tx, showId);
|
|
14
|
-
await NotificationsRepository.deleteByShow(tx, showId);
|
|
15
|
-
});
|
|
16
|
-
return { success: true };
|
|
17
|
-
}),
|
|
18
|
-
removeMany: protectedProcedure.input(idsInput()).mutation(async ({ ctx, input }) => {
|
|
19
|
-
const ids = await Promise.all(input.ids.map((id) => getMigratedId(ctx.db, id)));
|
|
20
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
21
|
-
for (const id of ids) {
|
|
22
|
-
await WatchedEpisodesRepository.deleteByShow(tx, id);
|
|
23
|
-
await NotificationsRepository.deleteByShow(tx, id);
|
|
24
|
-
}
|
|
25
|
-
});
|
|
26
|
-
return { success: true, removed: ids.length };
|
|
27
|
-
}),
|
|
28
|
-
adultCount: protectedProcedure.query(async ({ ctx }) => {
|
|
29
|
-
const ids = await getAdultNonWatchlistShowIds(ctx.db);
|
|
30
|
-
return { count: ids.length };
|
|
31
|
-
}),
|
|
32
|
-
purgeAdult: protectedProcedure.mutation(async ({ ctx }) => {
|
|
33
|
-
const ids = await getAdultNonWatchlistShowIds(ctx.db);
|
|
34
|
-
if (ids.length > 0) {
|
|
35
|
-
await performWriteTransactionAsync(ctx.db, async (tx) => {
|
|
36
|
-
for (const id of ids) {
|
|
37
|
-
await WatchedEpisodesRepository.deleteByShow(tx, id);
|
|
38
|
-
await NotificationsRepository.deleteByShow(tx, id);
|
|
39
|
-
}
|
|
40
|
-
});
|
|
41
|
-
}
|
|
42
|
-
return { success: true, removed: ids.length };
|
|
43
|
-
}),
|
|
44
|
-
thisWeek: protectedProcedure.query(async ({ ctx }) => {
|
|
45
|
-
const rows = await dbAll(ctx.db, `SELECT
|
|
46
|
-
w.id, w.name, w.thumbnail, w.nativeName, w.englishName, w.type,
|
|
47
|
-
dn.episodeNumber, dn.discoveredAt
|
|
48
|
-
FROM discovered_notifications dn
|
|
49
|
-
JOIN watchlist w ON dn.showId = w.id
|
|
50
|
-
WHERE w.status = 'Watching'
|
|
51
|
-
AND EXISTS (
|
|
52
|
-
SELECT 1 FROM discovered_notifications dn_recent
|
|
53
|
-
WHERE dn_recent.showId = dn.showId
|
|
54
|
-
AND dn_recent.discoveredAt >= datetime('now', '-7 days')
|
|
55
|
-
)
|
|
56
|
-
AND CAST(dn.episodeNumber AS INTEGER) = (
|
|
57
|
-
SELECT MIN(CAST(dn2.episodeNumber AS INTEGER))
|
|
58
|
-
FROM discovered_notifications dn2
|
|
59
|
-
WHERE dn2.showId = dn.showId
|
|
60
|
-
AND NOT EXISTS (
|
|
61
|
-
SELECT 1 FROM watched_episodes we2
|
|
62
|
-
WHERE we2.showId = dn2.showId AND we2.episodeNumber = dn2.episodeNumber
|
|
63
|
-
)
|
|
64
|
-
)
|
|
65
|
-
AND NOT EXISTS (
|
|
66
|
-
SELECT 1 FROM watched_episodes we
|
|
67
|
-
WHERE we.showId = dn.showId AND we.episodeNumber = dn.episodeNumber
|
|
68
|
-
)
|
|
69
|
-
ORDER BY CAST(dn.episodeNumber AS INTEGER) ASC`);
|
|
70
|
-
return rows.map((row) => ({
|
|
71
|
-
_id: row.id,
|
|
72
|
-
id: row.id,
|
|
73
|
-
name: row.name,
|
|
74
|
-
thumbnail: row.thumbnail || '',
|
|
75
|
-
nativeName: row.nativeName,
|
|
76
|
-
englishName: row.englishName,
|
|
77
|
-
type: row.type,
|
|
78
|
-
episodeNumber: parseInt(row.episodeNumber) || row.episodeNumber,
|
|
79
|
-
}));
|
|
80
|
-
}),
|
|
81
|
-
});
|