@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.
Files changed (156) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DKDJK8Mp.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-CTcvfjva.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_UiacIN.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-DMSN2vxl.js} +2 -2
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-CRwfOatX.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-CRJsi-vz.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-pwmK9_Eh.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-m7kFzg2A.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-C1wpPvu6.js} +1 -1
  11. package/client/dist/assets/ListeningList-OzfEI1Ga.js +1 -0
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-ChRCExhK.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DSHJPo2A.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-CzOVY1wA.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-CJfzdUBb.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-CifQMfhs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CM10yB9V.js} +1 -1
  18. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-6Kovbsqw.js} +1 -1
  19. package/client/dist/assets/{Player-CsH4x_I_.js → Player-CK0hT-u4.js} +2 -2
  20. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-Bb66uqT9.js} +1 -1
  21. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-C_nTj8_q.js} +1 -1
  22. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-DUK7URAE.js} +1 -1
  23. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-Ck9LJThU.js} +1 -1
  24. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BUphIv0k.js} +1 -1
  25. package/client/dist/assets/{Search-DFkhuk-x.js → Search-zJyP15h6.js} +1 -1
  26. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-BYdoSEUh.js} +1 -1
  27. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CqgbOhjX.js} +1 -1
  28. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-BVjyv6CM.js} +1 -1
  29. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-L13ylswR.js} +1 -1
  30. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-Cvcm5U2D.js} +1 -1
  31. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-DhxfKvYm.js} +1 -1
  32. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-P8bFE6_X.js} +1 -1
  33. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-D5gVPjtb.js} +1 -1
  34. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-Cjq8l3rU.js} +1 -1
  35. package/client/dist/assets/{hls-BGCX7mJy.js → hls-BkzuANAg.js} +1 -1
  36. package/client/dist/assets/{index-CnfZwsds.js → index-CLVgzKLk.js} +19 -19
  37. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-DZJZu-so.js} +1 -1
  38. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CvrMGHuo.js} +1 -1
  39. package/client/dist/assets/useAsmrPopup-Bimy8Ouw.js +1 -0
  40. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary--MiN11uR.js} +1 -1
  41. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-CR5XhKKS.js} +1 -1
  42. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-DqdT4fA5.js} +1 -1
  43. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-BL0HMDyP.js} +1 -1
  44. package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-D4U4iqN8.js} +1 -1
  45. package/client/dist/index.html +1 -1
  46. package/package.json +3 -5
  47. package/server/dist/config.js +1 -174
  48. package/server/dist/server.js +1068 -289
  49. package/server/dist/shipped-defaults.js +1 -6
  50. package/server/package.json +4 -5
  51. package/client/dist/assets/Asmr.module-CdWlioTY.js +0 -1
  52. package/client/dist/assets/ListeningList-DguKjI_t.js +0 -1
  53. package/client/dist/assets/Music-BGH_0voi.css +0 -1
  54. package/client/dist/assets/Music-CQpyXTLH.js +0 -1
  55. package/client/dist/assets/Radio-CCHNFN7R.css +0 -1
  56. package/client/dist/assets/Radio-DF6g6odd.js +0 -1
  57. package/client/dist/assets/Radio.module-DGSZLpKM.js +0 -1
  58. package/client/dist/assets/useAsmrPopup-SymV1J-b.js +0 -1
  59. package/server/dist/app-auth.js +0 -157
  60. package/server/dist/app-hono.js +0 -598
  61. package/server/dist/db/drizzle.js +0 -16
  62. package/server/dist/db/schema-anime.js +0 -61
  63. package/server/dist/db/schema-asmr.js +0 -13
  64. package/server/dist/db/schema-manga.js +0 -31
  65. package/server/dist/db/schema-tv.js +0 -18
  66. package/server/dist/db.js +0 -217
  67. package/server/dist/discord-gateway.js +0 -385
  68. package/server/dist/discord-rpc.js +0 -413
  69. package/server/dist/github-sync.js +0 -508
  70. package/server/dist/google.js +0 -741
  71. package/server/dist/hono/asmr.js +0 -10
  72. package/server/dist/hono/auth.js +0 -49
  73. package/server/dist/hono/music.js +0 -209
  74. package/server/dist/hono/proxy.js +0 -827
  75. package/server/dist/hono/tv.js +0 -200
  76. package/server/dist/hono/watchlist.js +0 -341
  77. package/server/dist/lib/anilist.js +0 -1583
  78. package/server/dist/lib/anischedule.js +0 -191
  79. package/server/dist/lib/insights.js +0 -185
  80. package/server/dist/lib/ipc.js +0 -23
  81. package/server/dist/lib/kitsu.js +0 -517
  82. package/server/dist/lib/mal-import.js +0 -291
  83. package/server/dist/lib/mal.js +0 -957
  84. package/server/dist/lib/migration.js +0 -310
  85. package/server/dist/lib/offline-db.js +0 -395
  86. package/server/dist/lib/potoken.js +0 -135
  87. package/server/dist/lib/recommendations.service.js +0 -216
  88. package/server/dist/lib/temp-ids.js +0 -3
  89. package/server/dist/lib/tmdb.js +0 -72
  90. package/server/dist/lib/tracker/anilist-tracker.js +0 -308
  91. package/server/dist/lib/tracker/manga-sync.service.js +0 -773
  92. package/server/dist/lib/tracker/sync-state.js +0 -21
  93. package/server/dist/lib/tracker/sync.service.js +0 -476
  94. package/server/dist/lib/translate.js +0 -79
  95. package/server/dist/lib/ump.js +0 -106
  96. package/server/dist/lib/ytmusic.js +0 -162
  97. package/server/dist/logger.js +0 -16
  98. package/server/dist/providers/manga/manga.types.js +0 -1
  99. package/server/dist/providers/provider.interface.js +0 -1
  100. package/server/dist/providers/remote-loader.js +0 -614
  101. package/server/dist/providers/remote-types.js +0 -159
  102. package/server/dist/providers/title-matching.js +0 -226
  103. package/server/dist/providers/tv.types.js +0 -1
  104. package/server/dist/rclone.js +0 -120
  105. package/server/dist/repositories/asmr.repository.js +0 -77
  106. package/server/dist/repositories/insights.repository.js +0 -106
  107. package/server/dist/repositories/library.repository.js +0 -94
  108. package/server/dist/repositories/mal-cache.repository.js +0 -52
  109. package/server/dist/repositories/manga.repository.js +0 -95
  110. package/server/dist/repositories/notifications.repository.js +0 -60
  111. package/server/dist/repositories/queue.repository.js +0 -72
  112. package/server/dist/repositories/settings.repository.js +0 -30
  113. package/server/dist/repositories/shows-meta.repository.js +0 -58
  114. package/server/dist/repositories/temp-show-ids.repository.js +0 -63
  115. package/server/dist/repositories/tv.repository.js +0 -93
  116. package/server/dist/repositories/watched-episodes.repository.js +0 -82
  117. package/server/dist/repositories/watchlist.repository.js +0 -60
  118. package/server/dist/scripts/backfill-episode-duration.js +0 -42
  119. package/server/dist/sync-payload.js +0 -91
  120. package/server/dist/sync.js +0 -657
  121. package/server/dist/trpc/context.js +0 -7
  122. package/server/dist/trpc/errors.js +0 -15
  123. package/server/dist/trpc/index.js +0 -18
  124. package/server/dist/trpc/router.js +0 -49
  125. package/server/dist/trpc/routers/asmr-library.js +0 -112
  126. package/server/dist/trpc/routers/asmr-progress.js +0 -121
  127. package/server/dist/trpc/routers/asmr.js +0 -87
  128. package/server/dist/trpc/routers/auth.js +0 -194
  129. package/server/dist/trpc/routers/continue-watching.js +0 -81
  130. package/server/dist/trpc/routers/data.js +0 -1026
  131. package/server/dist/trpc/routers/discord.js +0 -285
  132. package/server/dist/trpc/routers/discovery.js +0 -11
  133. package/server/dist/trpc/routers/insights.js +0 -14
  134. package/server/dist/trpc/routers/manga-library.js +0 -263
  135. package/server/dist/trpc/routers/manga-progress.js +0 -169
  136. package/server/dist/trpc/routers/manga.js +0 -201
  137. package/server/dist/trpc/routers/music.js +0 -655
  138. package/server/dist/trpc/routers/notifications.js +0 -80
  139. package/server/dist/trpc/routers/progress.js +0 -109
  140. package/server/dist/trpc/routers/queue.js +0 -93
  141. package/server/dist/trpc/routers/radio.js +0 -145
  142. package/server/dist/trpc/routers/settings.js +0 -96
  143. package/server/dist/trpc/routers/tracker.js +0 -213
  144. package/server/dist/trpc/routers/tv-library.js +0 -135
  145. package/server/dist/trpc/routers/tv-progress.js +0 -159
  146. package/server/dist/trpc/routers/tv.js +0 -551
  147. package/server/dist/trpc/routers/watchlist.js +0 -550
  148. package/server/dist/trpc/validation.js +0 -258
  149. package/server/dist/utils/cache.utils.js +0 -25
  150. package/server/dist/utils/cookie.utils.js +0 -13
  151. package/server/dist/utils/db-utils.js +0 -17
  152. package/server/dist/utils/env.utils.js +0 -77
  153. package/server/dist/utils/http.utils.js +0 -82
  154. package/server/dist/utils/request-context.js +0 -2
  155. package/server/dist/utils/security.utils.js +0 -76
  156. /package/client/dist/assets/{Asmr-D1KuQwQy.css → useAsmrPopup-D1KuQwQy.css} +0 -0
@@ -1,94 +0,0 @@
1
- import { sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- const LIBRARY_TABLES = [
4
- 'watchlist',
5
- 'watched_episodes',
6
- 'queue',
7
- 'shows_meta',
8
- 'dismissed_notifications',
9
- 'discovered_notifications',
10
- 'legacy_id_mapping',
11
- 'temp_show_ids',
12
- ];
13
- const MANGA_LIBRARY_TABLES = ['manga_library', 'manga_progress'];
14
- const TV_LIBRARY_TABLES = ['tv_library', 'tv_progress'];
15
- const ASMR_LIBRARY_TABLES = ['asmr_library', 'asmr_progress'];
16
- async function countTables(db, tables) {
17
- const counts = {};
18
- for (const table of tables) {
19
- try {
20
- const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) AS rows FROM ${sql.raw(`"${table}"`)}`);
21
- counts[table] = rows[0]?.rows ?? 0;
22
- }
23
- catch {
24
- counts[table] = 0;
25
- }
26
- }
27
- return counts;
28
- }
29
- async function clearTables(db, tables) {
30
- for (const table of tables) {
31
- try {
32
- await getDrizzle(db).run(sql `DELETE FROM ${sql.raw(`"${table}"`)}`);
33
- }
34
- catch {
35
- // ignore
36
- }
37
- }
38
- }
39
- export function makeLibraryRepo(table) {
40
- const t = sql.raw(`"${table}"`);
41
- return {
42
- getById: async (db, id) => {
43
- const rows = await getDrizzle(db).all(sql `SELECT * FROM ${t} WHERE id = ${id}`);
44
- return rows[0];
45
- },
46
- getAll: (db, status, limit, offset) => {
47
- const q = sql `SELECT * FROM ${t}`;
48
- if (status && status !== 'All') {
49
- q.append(sql ` WHERE status = ${status}`);
50
- }
51
- q.append(sql ` ORDER BY updatedAt DESC`);
52
- if (limit !== undefined && offset !== undefined) {
53
- q.append(sql ` LIMIT ${limit} OFFSET ${offset}`);
54
- }
55
- return getDrizzle(db).all(q);
56
- },
57
- getCount: async (db, status) => {
58
- if (status && status !== 'All') {
59
- const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM ${t} WHERE status = ${status}`);
60
- return rows[0]?.total || 0;
61
- }
62
- const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM ${t}`);
63
- return rows[0]?.total || 0;
64
- },
65
- getIds: async (db) => {
66
- const rows = await getDrizzle(db).all(sql `SELECT id FROM ${t}`);
67
- return rows.map((r) => r.id);
68
- },
69
- updateStatus: (db, id, status) => getDrizzle(db).run(sql `
70
- UPDATE ${t} SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
71
- updateStatusMany: (db, ids, status) => {
72
- if (ids.length === 0)
73
- return Promise.resolve();
74
- return getDrizzle(db).run(sql `
75
- UPDATE ${t} SET status = ${status}, updatedAt = strftime('%s', 'now') WHERE id IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})`);
76
- },
77
- delete: (db, id) => getDrizzle(db).run(sql `DELETE FROM ${t} WHERE id = ${id}`),
78
- deleteMany: (db, ids) => {
79
- if (ids.length === 0)
80
- return Promise.resolve();
81
- return getDrizzle(db).run(sql `DELETE FROM ${t} WHERE id IN (${sql.join(ids.map((id) => sql `${id}`), sql `, `)})`);
82
- },
83
- };
84
- }
85
- export const LibraryRepository = {
86
- countAll: (db) => countTables(db, LIBRARY_TABLES),
87
- clearAll: (db) => clearTables(db, LIBRARY_TABLES),
88
- countManga: (db) => countTables(db, MANGA_LIBRARY_TABLES),
89
- clearManga: (db) => clearTables(db, MANGA_LIBRARY_TABLES),
90
- countTv: (db) => countTables(db, TV_LIBRARY_TABLES),
91
- clearTv: (db) => clearTables(db, TV_LIBRARY_TABLES),
92
- countAsmr: (db) => countTables(db, ASMR_LIBRARY_TABLES),
93
- clearAsmr: (db) => clearTables(db, ASMR_LIBRARY_TABLES),
94
- };
@@ -1,52 +0,0 @@
1
- import { DatabaseSync } from 'node:sqlite';
2
- import fs from 'fs';
3
- import path from 'path';
4
- import { CONFIG } from '../config.js';
5
- import { DatabaseWrapper } from '../db.js';
6
- import { dbGet, dbRun } from '../utils/db-utils.js';
7
- export const MAL_CACHE_MAX_ROWS = 5000;
8
- const DEFAULT_TTL_SECONDS = 6 * 3600;
9
- function cacheFile() {
10
- return path.join(CONFIG.ROOT, CONFIG.IS_DEV ? 'mal-cache.dev.db' : 'mal-cache.db');
11
- }
12
- let handle = null;
13
- function db() {
14
- if (!handle) {
15
- const file = cacheFile();
16
- fs.mkdirSync(path.dirname(file), { recursive: true });
17
- handle = new DatabaseWrapper(file, new DatabaseSync(file));
18
- dbRun(handle, `CREATE TABLE IF NOT EXISTS mal_cache (key TEXT PRIMARY KEY, payload TEXT NOT NULL, fetched_at TEXT NOT NULL, expires_at TEXT NOT NULL)`);
19
- }
20
- return handle;
21
- }
22
- export function malCacheGet(key) {
23
- const row = dbGet(db(), 'SELECT payload, fetched_at AS fetchedAt, expires_at AS expiresAt FROM mal_cache WHERE key = ?', [key]);
24
- if (!row)
25
- return null;
26
- return { payload: row.payload, fresh: Date.now() < Date.parse(row.expiresAt) };
27
- }
28
- export function malCachePut(key, payload, ttlSeconds = DEFAULT_TTL_SECONDS) {
29
- const database = db();
30
- const now = new Date();
31
- dbRun(database, 'INSERT OR REPLACE INTO mal_cache (key, payload, fetched_at, expires_at) VALUES (?, ?, ?, ?)', [key, payload, now.toISOString(), new Date(now.getTime() + ttlSeconds * 1000).toISOString()]);
32
- const count = dbGet(database, 'SELECT COUNT(*) AS rows FROM mal_cache')?.rows ?? 0;
33
- if (count < MAL_CACHE_MAX_ROWS)
34
- return;
35
- malCachePruneExpired();
36
- const remaining = dbGet(database, 'SELECT COUNT(*) AS rows FROM mal_cache')?.rows ?? 0;
37
- if (remaining < MAL_CACHE_MAX_ROWS)
38
- return;
39
- dbRun(database, `DELETE FROM mal_cache WHERE key NOT IN (SELECT key FROM mal_cache ORDER BY fetched_at DESC LIMIT ${MAL_CACHE_MAX_ROWS})`);
40
- }
41
- export function malCachePruneExpired() {
42
- const now = new Date().toISOString();
43
- const row = dbGet(db(), 'SELECT COUNT(*) AS rows FROM mal_cache WHERE expires_at < ?', [now]);
44
- dbRun(db(), 'DELETE FROM mal_cache WHERE expires_at < ?', [now]);
45
- return row?.rows ?? 0;
46
- }
47
- export function malCacheStore() {
48
- return {
49
- get: (key) => malCacheGet(key),
50
- put: (key, payload, ttl) => malCachePut(key, payload, ttl),
51
- };
52
- }
@@ -1,95 +0,0 @@
1
- import { eq, inArray, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { mangaLibrary, mangaProgress } from '../db/schema-manga.js';
4
- import { makeLibraryRepo } from './library.repository.js';
5
- export const MANGA_STATUSES = ['Reading', 'Completed', 'On-Hold', 'Dropped', 'Planned'];
6
- export function buildMangaId(provider, mangaId) {
7
- return `${provider.toLowerCase()}:${mangaId}`;
8
- }
9
- export const MangaLibraryRepository = {
10
- ...makeLibraryRepo('manga_library'),
11
- upsert: (db, data) => getDrizzle(db).run(sql `
12
- INSERT INTO manga_library (id, provider, mangaId, title, cover, status, author, altTitle, contentRating, anilistId, anilistIdSource, updatedAt)
13
- VALUES (${data.id}, ${data.provider}, ${data.mangaId}, ${data.title}, ${data.cover}, ${data.status}, ${data.author || null}, ${data.altTitle || null}, ${data.contentRating || null}, ${data.anilistId ?? null}, ${data.anilistIdSource ?? null}, strftime('%s', 'now'))
14
- ON CONFLICT(id) DO UPDATE SET
15
- provider = COALESCE(NULLIF(EXCLUDED.provider, ''), manga_library.provider),
16
- title = COALESCE(NULLIF(EXCLUDED.title, ''), manga_library.title),
17
- cover = COALESCE(NULLIF(EXCLUDED.cover, ''), manga_library.cover),
18
- status = COALESCE(NULLIF(EXCLUDED.status, ''), manga_library.status),
19
- author = COALESCE(NULLIF(EXCLUDED.author, ''), manga_library.author),
20
- altTitle = COALESCE(NULLIF(EXCLUDED.altTitle, ''), manga_library.altTitle),
21
- contentRating = COALESCE(EXCLUDED.contentRating, manga_library.contentRating),
22
- anilistId = COALESCE(EXCLUDED.anilistId, manga_library.anilistId),
23
- anilistIdSource = COALESCE(EXCLUDED.anilistIdSource, manga_library.anilistIdSource),
24
- updatedAt = strftime('%s', 'now')`),
25
- setAnilistId: (db, id, anilistId, source) => source
26
- ? getDrizzle(db)
27
- .update(mangaLibrary)
28
- .set({ anilistId, anilistIdSource: source })
29
- .where(eq(mangaLibrary.id, id))
30
- : getDrizzle(db).update(mangaLibrary).set({ anilistId }).where(eq(mangaLibrary.id, id)),
31
- touchProgress: (db, id, progress) => getDrizzle(db).run(sql `
32
- UPDATE manga_library SET lastChapterId = ${progress.chapterId}, lastChapterNumber = ${progress.chapterNumber}, lastPage = ${progress.page}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
33
- setProgressPointer: (db, id, chapterNumber) => getDrizzle(db).run(sql `
34
- UPDATE manga_library SET lastChapterNumber = ${chapterNumber}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
35
- };
36
- export const MangaProgressRepository = {
37
- getByManga: (db, mangaId) => getDrizzle(db).all(sql `
38
- SELECT mangaId, chapterId, chapterNumber, page, pageCount, updatedAt FROM manga_progress WHERE mangaId = ${mangaId} ORDER BY updatedAt DESC`),
39
- getChapter: async (db, mangaId, chapterId) => {
40
- const rows = await getDrizzle(db).all(sql `
41
- SELECT mangaId, chapterId, chapterNumber, page, pageCount, updatedAt FROM manga_progress WHERE mangaId = ${mangaId} AND chapterId = ${chapterId}`);
42
- return rows[0];
43
- },
44
- getLatest: async (db, mangaId) => {
45
- const rows = await getDrizzle(db).all(sql `
46
- SELECT mangaId, chapterId, chapterNumber, page, pageCount, updatedAt FROM manga_progress WHERE mangaId = ${mangaId} ORDER BY updatedAt DESC LIMIT 1`);
47
- return rows[0];
48
- },
49
- upsert: (db, data) => getDrizzle(db).run(sql `
50
- INSERT INTO manga_progress (mangaId, chapterId, chapterNumber, page, pageCount, title, cover, provider, altTitle, contentRating, updatedAt)
51
- VALUES (${data.mangaId}, ${data.chapterId}, ${data.chapterNumber}, ${data.page}, ${data.pageCount}, ${data.title ?? null}, ${data.cover ?? null}, ${data.provider ?? null}, ${data.altTitle ?? null}, ${data.contentRating ?? null}, strftime('%s', 'now'))
52
- ON CONFLICT(mangaId, chapterId) DO UPDATE SET
53
- chapterNumber = COALESCE(NULLIF(EXCLUDED.chapterNumber, ''), manga_progress.chapterNumber),
54
- page = EXCLUDED.page,
55
- pageCount = EXCLUDED.pageCount,
56
- title = COALESCE(EXCLUDED.title, manga_progress.title),
57
- cover = COALESCE(EXCLUDED.cover, manga_progress.cover),
58
- provider = COALESCE(EXCLUDED.provider, manga_progress.provider),
59
- altTitle = COALESCE(EXCLUDED.altTitle, manga_progress.altTitle),
60
- contentRating = COALESCE(EXCLUDED.contentRating, manga_progress.contentRating),
61
- updatedAt = strftime('%s', 'now')`),
62
- deleteByManga: (db, mangaId) => getDrizzle(db).delete(mangaProgress).where(eq(mangaProgress.mangaId, mangaId)),
63
- deleteMany: (db, ids) => {
64
- if (ids.length === 0)
65
- return Promise.resolve();
66
- return getDrizzle(db).delete(mangaProgress).where(inArray(mangaProgress.mangaId, ids));
67
- },
68
- deleteChapter: (db, mangaId, chapterId) => getDrizzle(db).run(sql `
69
- DELETE FROM manga_progress WHERE mangaId = ${mangaId} AND chapterId = ${chapterId}`),
70
- deleteSyntheticChapters: (db, mangaId, keepChapterId) => getDrizzle(db).run(sql `
71
- DELETE FROM manga_progress WHERE mangaId = ${mangaId} AND chapterId LIKE 'anilist:ch:%' AND chapterId != ${keepChapterId}`),
72
- moveSyntheticChapters: (db, fromMangaId, toMangaId) => getDrizzle(db).run(sql `
73
- UPDATE manga_progress SET mangaId = ${toMangaId}, updatedAt = strftime('%s', 'now') WHERE mangaId = ${fromMangaId} AND chapterId LIKE 'anilist:ch:%'`),
74
- getContinueReading: (db, limit) => {
75
- const limitClause = typeof limit === 'number' ? `LIMIT ${limit}` : '';
76
- return getDrizzle(db).all(sql `
77
- SELECT p.mangaId as id,
78
- COALESCE(l.mangaId, SUBSTR(p.mangaId, INSTR(p.mangaId, ':') + 1)) as mangaId,
79
- COALESCE(l.provider, p.provider) as provider,
80
- COALESCE(l.title, p.title) as title, COALESCE(l.cover, p.cover) as cover,
81
- COALESCE(l.author, '') as author, COALESCE(l.altTitle, p.altTitle) as altTitle,
82
- COALESCE(l.contentRating, p.contentRating) as contentRating,
83
- l.status as watchlistStatus,
84
- p.chapterId, p.chapterNumber, p.page, p.pageCount, p.updatedAt as progressAt
85
- FROM (
86
- SELECT *, ROW_NUMBER() OVER (PARTITION BY mangaId ORDER BY updatedAt DESC) as rn
87
- FROM manga_progress
88
- ) p
89
- LEFT JOIN manga_library l ON p.mangaId = l.id
90
- WHERE p.rn = 1
91
- AND (l.status IS NULL OR l.status = 'Reading')
92
- ORDER BY p.updatedAt DESC
93
- ${sql.raw(limitClause)}`);
94
- },
95
- };
@@ -1,60 +0,0 @@
1
- import { and, eq, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { discoveredNotifications, dismissedNotifications } from '../db/schema-anime.js';
4
- export const NotificationsRepository = {
5
- getDismissedByShow: (db, showId) => getDrizzle(db).all(sql `SELECT episodeNumber FROM dismissed_notifications WHERE showId = ${showId}`),
6
- getDiscoveredByShow: (db, showId) => getDrizzle(db).all(sql `SELECT episodeNumber FROM discovered_notifications WHERE showId = ${showId}`),
7
- addDiscovered: (db, showId, episodeNumber) => getDrizzle(db)
8
- .insert(discoveredNotifications)
9
- .values({ showId, episodeNumber })
10
- .onConflictDoNothing(),
11
- addDismissed: (db, showId, episodeNumber) => getDrizzle(db)
12
- .insert(dismissedNotifications)
13
- .values({ showId, episodeNumber })
14
- .onConflictDoNothing(),
15
- dismissFromDiscovered: (db, showId) => {
16
- if (showId) {
17
- return getDrizzle(db).run(sql `
18
- INSERT OR IGNORE INTO dismissed_notifications (showId, episodeNumber) SELECT showId, episodeNumber FROM discovered_notifications WHERE showId = ${showId}`);
19
- }
20
- return getDrizzle(db).run(sql `
21
- INSERT OR IGNORE INTO dismissed_notifications (showId, episodeNumber) SELECT showId, episodeNumber FROM discovered_notifications`);
22
- },
23
- deleteByShow: (db, showId) => Promise.all([
24
- getDrizzle(db)
25
- .delete(dismissedNotifications)
26
- .where(eq(dismissedNotifications.showId, showId)),
27
- getDrizzle(db)
28
- .delete(discoveredNotifications)
29
- .where(eq(discoveredNotifications.showId, showId)),
30
- ]),
31
- deleteSpecificDismissed: (db, showId, episodeNumber) => getDrizzle(db)
32
- .delete(dismissedNotifications)
33
- .where(and(eq(dismissedNotifications.showId, showId), eq(dismissedNotifications.episodeNumber, episodeNumber))),
34
- deleteDiscovered: (db, showId, episodeNumber) => getDrizzle(db)
35
- .delete(discoveredNotifications)
36
- .where(and(eq(discoveredNotifications.showId, showId), eq(discoveredNotifications.episodeNumber, episodeNumber))),
37
- cleanupWatchedNotifications: (db) => Promise.all([
38
- getDrizzle(db).run(sql `
39
- DELETE FROM dismissed_notifications WHERE EXISTS (SELECT 1 FROM watched_episodes we WHERE we.showId = dismissed_notifications.showId AND we.episodeNumber = dismissed_notifications.episodeNumber)`),
40
- getDrizzle(db).run(sql `
41
- DELETE FROM discovered_notifications WHERE EXISTS (SELECT 1 FROM watched_episodes we WHERE we.showId = discovered_notifications.showId AND we.episodeNumber = discovered_notifications.episodeNumber)`),
42
- ]),
43
- hasAnyDiscovered: async (db) => {
44
- const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as count FROM discovered_notifications`);
45
- return (rows[0]?.count || 0) > 0;
46
- },
47
- hasActiveNotifications: async (db) => {
48
- const rows = await getDrizzle(db).all(sql `
49
- SELECT COUNT(*) as count FROM discovered_notifications dn
50
- WHERE NOT EXISTS (
51
- SELECT 1 FROM dismissed_notifications dn2
52
- WHERE dn2.showId = dn.showId AND dn2.episodeNumber = dn.episodeNumber
53
- )
54
- AND NOT EXISTS (
55
- SELECT 1 FROM watched_episodes we
56
- WHERE we.showId = dn.showId AND we.episodeNumber = dn.episodeNumber
57
- )`);
58
- return (rows[0]?.count || 0) > 0;
59
- },
60
- };
@@ -1,72 +0,0 @@
1
- import { and, eq, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { queue } from '../db/schema-anime.js';
4
- export const QueueRepository = {
5
- getAll: (db) => getDrizzle(db).all(sql `
6
- SELECT
7
- q.id,
8
- q.showId,
9
- q.episodeNumber,
10
- q.queue_order,
11
- COALESCE(NULLIF(sm.name, ''), w.name) as name,
12
- COALESCE(NULLIF(sm.thumbnail, ''), NULLIF(w.thumbnail, ''), '') as thumbnail,
13
- COALESCE(NULLIF(sm.nativeName, ''), w.nativeName) as nativeName,
14
- COALESCE(NULLIF(sm.englishName, ''), w.englishName) as englishName,
15
- COALESCE(NULLIF(sm.type, ''), w.type) as type
16
- FROM queue q
17
- LEFT JOIN shows_meta sm ON q.showId = sm.id
18
- LEFT JOIN watchlist w ON q.showId = w.id
19
- ORDER BY q.queue_order ASC, q.id ASC`),
20
- getByEpisode: async (db, showId, episodeNumber) => {
21
- const rows = await getDrizzle(db).all(sql `SELECT * FROM queue WHERE showId = ${showId} AND episodeNumber = ${episodeNumber}`);
22
- return rows[0];
23
- },
24
- getByShow: (db, showId) => getDrizzle(db).all(sql `SELECT * FROM queue WHERE showId = ${showId} ORDER BY queue_order ASC`),
25
- getMaxOrder: async (db) => {
26
- const rows = await getDrizzle(db).all(sql `SELECT COALESCE(MAX(queue_order), -1) as maxOrder FROM queue`);
27
- return rows[0]?.maxOrder ?? -1;
28
- },
29
- addToEnd: (db, showId, episodeNumber) => getDrizzle(db).run(sql `
30
- INSERT INTO queue (showId, episodeNumber, queue_order) VALUES
31
- (${showId}, ${episodeNumber}, (SELECT COALESCE(MAX(queue_order), -1) + 1 FROM queue))`),
32
- removeEpisode: (db, showId, episodeNumber) => getDrizzle(db)
33
- .delete(queue)
34
- .where(and(eq(queue.showId, showId), eq(queue.episodeNumber, episodeNumber))),
35
- addManyToEnd: async (db, episodes) => {
36
- if (episodes.length === 0)
37
- return;
38
- let nextOrder = (await QueueRepository.getMaxOrder(db)) + 1;
39
- for (const episode of episodes) {
40
- const existing = await QueueRepository.getByEpisode(db, episode.showId, episode.episodeNumber);
41
- if (existing)
42
- continue;
43
- await getDrizzle(db).insert(queue).values({
44
- showId: episode.showId,
45
- episodeNumber: episode.episodeNumber,
46
- queueOrder: nextOrder,
47
- });
48
- nextOrder += 1;
49
- }
50
- },
51
- removeMany: (db, showId, episodeNumbers) => {
52
- if (episodeNumbers.length === 0)
53
- return Promise.resolve();
54
- return getDrizzle(db).run(sql `
55
- DELETE FROM queue WHERE showId = ${showId} AND episodeNumber IN (${sql.join(episodeNumbers.map((e) => sql `${e}`), sql `, `)})`);
56
- },
57
- clear: (db) => getDrizzle(db).delete(queue),
58
- reorder: (db, items) => Promise.all(items.map((item, index) => {
59
- if (item.id !== undefined) {
60
- return getDrizzle(db)
61
- .update(queue)
62
- .set({ queueOrder: index })
63
- .where(eq(queue.id, item.id));
64
- }
65
- return getDrizzle(db)
66
- .update(queue)
67
- .set({ queueOrder: index })
68
- .where(and(eq(queue.showId, item.showId ?? ''), eq(queue.episodeNumber, item.episodeNumber ?? '')));
69
- })),
70
- cleanupOrphanedShowsMeta: (db) => getDrizzle(db).run(sql `
71
- DELETE FROM shows_meta WHERE id NOT IN (SELECT id FROM watchlist) AND id NOT IN (SELECT showId FROM queue)`),
72
- };
@@ -1,30 +0,0 @@
1
- import { eq, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { settings, watchlist } from '../db/schema-anime.js';
4
- export const SettingsRepository = {
5
- getByKey: async (db, key) => {
6
- const rows = await getDrizzle(db).all(sql `SELECT value FROM settings WHERE key = ${key}`);
7
- return rows[0];
8
- },
9
- upsert: (db, key, value) => getDrizzle(db)
10
- .insert(settings)
11
- .values({ key, value })
12
- .onConflictDoUpdate({ target: settings.key, set: { value } }),
13
- deleteByKey: (db, key) => getDrizzle(db).delete(settings).where(eq(settings.key, key)),
14
- readContinueFlags: async (db, ignoreKey, listOnlyKey) => {
15
- const ignoreRow = await SettingsRepository.getByKey(db, ignoreKey);
16
- const listOnlyRow = await SettingsRepository.getByKey(db, listOnlyKey);
17
- return {
18
- ignoreAdult: ignoreRow ? ignoreRow.value !== 'false' : true,
19
- listOnly: listOnlyRow ? listOnlyRow.value === 'true' || listOnlyRow.value === '1' : false,
20
- };
21
- },
22
- clearWatchlist: (db) => getDrizzle(db).delete(watchlist),
23
- upsertWatchlistBatch: (db, shows) => {
24
- if (shows.length === 0)
25
- return Promise.resolve();
26
- return getDrizzle(db).run(sql `
27
- INSERT OR REPLACE INTO watchlist (id, name, thumbnail, status) VALUES
28
- ${sql.join(shows.map((s) => sql `(${s.id}, ${s.name}, ${s.thumbnail ?? null}, ${s.status})`), sql `, `)}`);
29
- },
30
- };
@@ -1,58 +0,0 @@
1
- import { eq, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { showsMeta } from '../db/schema-anime.js';
4
- export const ShowsMetaRepository = {
5
- getById: async (db, id) => {
6
- const rows = await getDrizzle(db).all(sql `SELECT * FROM shows_meta WHERE id = ${id}`);
7
- return rows[0];
8
- },
9
- getStatus: async (db, id) => {
10
- const rows = await getDrizzle(db).all(sql `SELECT status FROM shows_meta WHERE id = ${id}`);
11
- return rows[0]?.status;
12
- },
13
- upsert: (db, data) => {
14
- const row = {
15
- id: data.id,
16
- name: data.name ?? null,
17
- thumbnail: data.thumbnail ?? null,
18
- nativeName: data.nativeName ?? null,
19
- englishName: data.englishName ?? null,
20
- genres: data.genres ?? null,
21
- popularityScore: data.popularityScore ?? null,
22
- status: data.status ?? null,
23
- episodeCount: data.episodeCount ?? null,
24
- type: data.type ?? null,
25
- anilistId: data.anilistId ?? null,
26
- isAdult: data.isAdult ?? null,
27
- episodeDuration: data.episodeDuration ?? null,
28
- };
29
- return getDrizzle(db)
30
- .insert(showsMeta)
31
- .values(row)
32
- .onConflictDoUpdate({
33
- target: showsMeta.id,
34
- set: {
35
- name: sql `COALESCE(NULLIF(${row.name}, ''), ${showsMeta.name})`,
36
- thumbnail: sql `COALESCE(NULLIF(${row.thumbnail}, ''), ${showsMeta.thumbnail})`,
37
- nativeName: sql `COALESCE(NULLIF(${row.nativeName}, ''), ${showsMeta.nativeName})`,
38
- englishName: sql `COALESCE(NULLIF(${row.englishName}, ''), ${showsMeta.englishName})`,
39
- genres: sql `COALESCE(NULLIF(${row.genres}, ''), ${showsMeta.genres})`,
40
- popularityScore: sql `COALESCE(${row.popularityScore}, ${showsMeta.popularityScore})`,
41
- status: sql `COALESCE(NULLIF(${row.status}, ''), ${showsMeta.status})`,
42
- episodeCount: sql `COALESCE(${row.episodeCount}, ${showsMeta.episodeCount})`,
43
- type: sql `COALESCE(NULLIF(${row.type}, ''), ${showsMeta.type})`,
44
- anilistId: sql `COALESCE(${row.anilistId}, ${showsMeta.anilistId})`,
45
- isAdult: sql `COALESCE(${row.isAdult}, ${showsMeta.isAdult})`,
46
- episodeDuration: sql `COALESCE(NULLIF(${row.episodeDuration}, 0), ${showsMeta.episodeDuration})`,
47
- },
48
- });
49
- },
50
- updateEpisodeCount: (db, id, episodeCount) => getDrizzle(db).update(showsMeta).set({ episodeCount }).where(eq(showsMeta.id, id)),
51
- updateStatus: (db, id, status) => getDrizzle(db).update(showsMeta).set({ status }).where(eq(showsMeta.id, id)),
52
- cleanupOrphanedMeta: (db) => getDrizzle(db).run(sql `
53
- DELETE FROM shows_meta WHERE id NOT IN (SELECT id FROM watchlist) AND id NOT IN (SELECT showId FROM queue)`),
54
- getShowIdsMissingMeta: (db) => getDrizzle(db).all(sql `
55
- SELECT DISTINCT we.showId as id FROM watched_episodes we
56
- LEFT JOIN shows_meta sm ON sm.id = we.showId
57
- WHERE sm.thumbnail IS NULL OR TRIM(sm.thumbnail) = ''`),
58
- };
@@ -1,63 +0,0 @@
1
- import { sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { tempShowIds } from '../db/schema-anime.js';
4
- import { TEMP_SHOW_ID_PREFIX } from '../lib/temp-ids.js';
5
- export const TempShowIdsRepository = {
6
- getById: async (db, id) => {
7
- const rows = await getDrizzle(db).all(sql `SELECT * FROM temp_show_ids WHERE id = ${id}`);
8
- return rows[0];
9
- },
10
- getByProviderNative: async (db, provider, nativeId) => {
11
- const rows = await getDrizzle(db).all(sql `SELECT * FROM temp_show_ids WHERE provider = ${provider} AND nativeId = ${nativeId}`);
12
- return rows[0];
13
- },
14
- count: async (db) => {
15
- const rows = await getDrizzle(db).all(sql `SELECT COUNT(*) as total FROM temp_show_ids`);
16
- return rows[0]?.total || 0;
17
- },
18
- allocate: async (db, data) => {
19
- const existing = await TempShowIdsRepository.getByProviderNative(db, data.provider, data.nativeId);
20
- if (existing)
21
- return existing;
22
- const prefixLen = TEMP_SHOW_ID_PREFIX.length + 1;
23
- const maxRows = await getDrizzle(db).all(sql `
24
- SELECT MAX(CAST(SUBSTR(id, ${prefixLen}) AS INTEGER)) as maxId FROM temp_show_ids WHERE id LIKE ${`${TEMP_SHOW_ID_PREFIX}%`}`);
25
- const next = (maxRows[0]?.maxId || 0) + 1;
26
- const id = `${TEMP_SHOW_ID_PREFIX}${next}`;
27
- try {
28
- await getDrizzle(db)
29
- .insert(tempShowIds)
30
- .values({
31
- id,
32
- provider: data.provider,
33
- nativeId: data.nativeId,
34
- title: data.title,
35
- thumbnail: data.thumbnail || '',
36
- createdAt: Date.now(),
37
- });
38
- }
39
- catch {
40
- const winner = await TempShowIdsRepository.getByProviderNative(db, data.provider, data.nativeId);
41
- if (winner)
42
- return winner;
43
- throw new Error('temp id allocation failed');
44
- }
45
- return (await TempShowIdsRepository.getById(db, id));
46
- },
47
- purge: async (db) => {
48
- const total = await TempShowIdsRepository.count(db);
49
- if (total === 0)
50
- return 0;
51
- const likePattern = `${TEMP_SHOW_ID_PREFIX}%`;
52
- await getDrizzle(db).run(sql `DELETE FROM watchlist WHERE id LIKE ${likePattern}`);
53
- await getDrizzle(db).run(sql `DELETE FROM watched_episodes WHERE showId LIKE ${likePattern}`);
54
- await getDrizzle(db).run(sql `DELETE FROM queue WHERE showId LIKE ${likePattern}`);
55
- await getDrizzle(db).run(sql `DELETE FROM shows_meta WHERE id LIKE ${likePattern}`);
56
- await getDrizzle(db).run(sql `DELETE FROM dismissed_notifications WHERE showId LIKE ${likePattern}`);
57
- await getDrizzle(db).run(sql `DELETE FROM discovered_notifications WHERE showId LIKE ${likePattern}`);
58
- await getDrizzle(db).run(sql `
59
- DELETE FROM legacy_id_mapping WHERE legacyId LIKE ${likePattern} OR numericId LIKE ${likePattern}`);
60
- await getDrizzle(db).delete(tempShowIds);
61
- return total;
62
- },
63
- };
@@ -1,93 +0,0 @@
1
- import { eq, inArray, sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- import { tvProgress } from '../db/schema-tv.js';
4
- import { makeLibraryRepo } from './library.repository.js';
5
- export const TV_STATUSES = ['Watching', 'Completed', 'On-Hold', 'Dropped', 'Planned'];
6
- export function buildTvId(mediaType, tmdbId) {
7
- const t = String(mediaType).toLowerCase() === 'movie' ? 'movie' : 'tv';
8
- return `${t}:${tmdbId}`;
9
- }
10
- export function normalizeTvMediaId(raw) {
11
- const s = String(raw ?? '').trim();
12
- const m = /^(movie|tv)[:-](\d+)$/i.exec(s);
13
- if (m)
14
- return `${m[1].toLowerCase()}:${m[2]}`;
15
- return s;
16
- }
17
- export const TvLibraryRepository = {
18
- ...makeLibraryRepo('tv_library'),
19
- upsert: (db, data) => getDrizzle(db).run(sql `
20
- INSERT INTO tv_library (id, tmdbId, mediaType, title, poster, backdrop, year, overview, status, adult, updatedAt)
21
- VALUES (${data.id}, ${data.tmdbId}, ${data.mediaType}, ${data.title}, ${data.poster}, ${data.backdrop || null}, ${data.year || null}, ${data.overview || null}, ${data.status}, ${data.adult ? 1 : 0}, strftime('%s', 'now'))
22
- ON CONFLICT(id) DO UPDATE SET
23
- tmdbId = EXCLUDED.tmdbId,
24
- mediaType = EXCLUDED.mediaType,
25
- title = COALESCE(NULLIF(EXCLUDED.title, ''), tv_library.title),
26
- poster = COALESCE(NULLIF(EXCLUDED.poster, ''), tv_library.poster),
27
- backdrop = COALESCE(NULLIF(EXCLUDED.backdrop, ''), tv_library.backdrop),
28
- year = COALESCE(NULLIF(EXCLUDED.year, ''), tv_library.year),
29
- overview = COALESCE(NULLIF(EXCLUDED.overview, ''), tv_library.overview),
30
- status = COALESCE(NULLIF(EXCLUDED.status, ''), tv_library.status),
31
- adult = COALESCE(EXCLUDED.adult, tv_library.adult),
32
- updatedAt = strftime('%s', 'now')`),
33
- touchProgress: (db, id, progress) => getDrizzle(db).run(sql `
34
- UPDATE tv_library SET lastSeason = ${progress.season}, lastEpisode = ${progress.episode}, updatedAt = strftime('%s', 'now') WHERE id = ${id}`),
35
- };
36
- export const TvProgressRepository = {
37
- getByMedia: (db, mediaId) => getDrizzle(db).all(sql `
38
- SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${mediaId} ORDER BY updatedAt DESC, rowid DESC`),
39
- getEpisode: async (db, mediaId, season, episode) => {
40
- const rows = await getDrizzle(db).all(sql `
41
- SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${mediaId} AND season = ${season} AND episode = ${episode}`);
42
- return rows[0];
43
- },
44
- getLatest: async (db, mediaId) => {
45
- const rows = await getDrizzle(db).all(sql `
46
- SELECT mediaId, season, episode, currentTime, duration, completed, updatedAt FROM tv_progress WHERE mediaId = ${mediaId} ORDER BY updatedAt DESC, rowid DESC LIMIT 1`);
47
- return rows[0];
48
- },
49
- upsert: (db, data) => getDrizzle(db).run(sql `
50
- INSERT INTO tv_progress (mediaId, season, episode, currentTime, duration, completed, title, poster, backdrop, year, overview, tmdbId, mediaType, adult, updatedAt)
51
- VALUES (${data.mediaId}, ${data.season}, ${data.episode}, ${data.currentTime}, ${data.duration}, ${data.completed}, ${data.title ?? null}, ${data.poster ?? null}, ${data.backdrop ?? null}, ${data.year ?? null}, ${data.overview ?? null}, ${data.tmdbId ?? null}, ${data.mediaType ?? null}, ${data.adult ?? null}, strftime('%s', 'now'))
52
- ON CONFLICT(mediaId, season, episode) DO UPDATE SET
53
- currentTime = EXCLUDED.currentTime,
54
- duration = EXCLUDED.duration,
55
- completed = EXCLUDED.completed,
56
- title = COALESCE(EXCLUDED.title, tv_progress.title),
57
- poster = COALESCE(EXCLUDED.poster, tv_progress.poster),
58
- backdrop = COALESCE(EXCLUDED.backdrop, tv_progress.backdrop),
59
- year = COALESCE(EXCLUDED.year, tv_progress.year),
60
- overview = COALESCE(EXCLUDED.overview, tv_progress.overview),
61
- tmdbId = COALESCE(EXCLUDED.tmdbId, tv_progress.tmdbId),
62
- mediaType = COALESCE(EXCLUDED.mediaType, tv_progress.mediaType),
63
- adult = COALESCE(EXCLUDED.adult, tv_progress.adult),
64
- updatedAt = strftime('%s', 'now')`),
65
- deleteByMedia: (db, mediaId) => getDrizzle(db).delete(tvProgress).where(eq(tvProgress.mediaId, mediaId)),
66
- deleteMany: (db, ids) => {
67
- if (ids.length === 0)
68
- return Promise.resolve();
69
- return getDrizzle(db).delete(tvProgress).where(inArray(tvProgress.mediaId, ids));
70
- },
71
- deleteEpisode: (db, mediaId, season, episode) => getDrizzle(db).run(sql `
72
- DELETE FROM tv_progress WHERE mediaId = ${mediaId} AND season = ${season} AND episode = ${episode}`),
73
- getContinueWatching: (db, limit) => {
74
- const limitClause = typeof limit === 'number' ? `LIMIT ${limit}` : '';
75
- return getDrizzle(db).all(sql `
76
- SELECT p.mediaId as id,
77
- COALESCE(l.tmdbId, p.tmdbId, CAST(SUBSTR(REPLACE(p.mediaId, '-', ':'), INSTR(REPLACE(p.mediaId, '-', ':'), ':') + 1) AS INTEGER)) as tmdbId,
78
- COALESCE(l.mediaType, p.mediaType, SUBSTR(REPLACE(p.mediaId, '-', ':'), 1, INSTR(REPLACE(p.mediaId, '-', ':'), ':') - 1)) as mediaType,
79
- COALESCE(l.title, p.title) as title, COALESCE(l.poster, p.poster) as poster, COALESCE(l.backdrop, p.backdrop) as backdrop,
80
- COALESCE(l.year, p.year) as year, COALESCE(l.overview, p.overview) as overview,
81
- l.status as watchlistStatus, COALESCE(l.adult, p.adult) as adult,
82
- p.season, p.episode, p.currentTime, p.duration, p.completed, p.updatedAt as progressAt
83
- FROM (
84
- SELECT *, rowid AS progressRowid, ROW_NUMBER() OVER (PARTITION BY mediaId ORDER BY updatedAt DESC, rowid DESC) as rn
85
- FROM tv_progress
86
- ) p
87
- LEFT JOIN tv_library l ON p.mediaId = l.id
88
- WHERE p.rn = 1
89
- AND (l.status IS NULL OR l.status = 'Watching')
90
- ORDER BY p.updatedAt DESC, p.progressRowid DESC
91
- ${sql.raw(limitClause)}`);
92
- },
93
- };