@serifpersia/dango 3.6.7 → 3.6.8

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 (149) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-C3P9zWi9.js → AnimeCard-DraA_TF9.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-Et6687Tl.js → AnimeInfo-BJ7pzs-P.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-DX4eNnlH.js → AnimeInfoPage-B_esUr5C.js} +2 -2
  5. package/client/dist/assets/{Asmr-CCdEWrFi.js → Asmr-Ckuh2xfW.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-DgEWBJOw.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
  7. package/client/dist/assets/{AvSyncCalibrator-CQ2fjf2E.js → AvSyncCalibrator-DwHCd9yB.js} +1 -1
  8. package/client/dist/assets/{Home-DUkUTTTy.js → Home-DuM1EuvN.js} +1 -1
  9. package/client/dist/assets/{Insights-QCh4xW12.js → Insights-D3Pd9c9V.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-BoTxRqHz.js → LibraryListPage-Dm2yVn0-.js} +1 -1
  11. package/client/dist/assets/{ListeningList-DguKjI_t.js → ListeningList-D-zt6EXZ.js} +1 -1
  12. package/client/dist/assets/{Manga-HydR-L7o.js → Manga-JwprMKBk.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-CBakEI9a.js → MangaInfoPage-DaeyJl2p.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-DwAR3iPy.js → MangaReadPage-COg0fHI9.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-DGzl8vsO.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
  16. package/client/dist/assets/{Mature-Pi0TxKuF.js → Mature-IHDaasrs.js} +1 -1
  17. package/client/dist/assets/{MediaCard--cuFxOCS.js → MediaCard-CpwsrXo5.js} +1 -1
  18. package/client/dist/assets/{Music-CQpyXTLH.js → Music-Dks4tPeK.js} +1 -1
  19. package/client/dist/assets/{OptionListMenu-B3D39dGS.js → OptionListMenu-11CcAsy8.js} +1 -1
  20. package/client/dist/assets/{Player-CsH4x_I_.js → Player-DQoEQkHL.js} +2 -2
  21. package/client/dist/assets/{PlayerSettings-DGxBsi3F.js → PlayerSettings-D4Ud4kv4.js} +1 -1
  22. package/client/dist/assets/{QueueOptionsButton-Djq4Cpex.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
  23. package/client/dist/assets/{QueueRail-DOrYgNVX.js → QueueRail-B4Dok7U3.js} +1 -1
  24. package/client/dist/assets/{Radio-DF6g6odd.js → Radio-BHK6LSk4.js} +1 -1
  25. package/client/dist/assets/{ReadingList-DwYoBrNk.js → ReadingList-BCsxIz57.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal-CMF86mX6.js → ResetProgressModal-BXHxSfy-.js} +1 -1
  27. package/client/dist/assets/{Search-DFkhuk-x.js → Search-PK1ds07O.js} +1 -1
  28. package/client/dist/assets/{Settings-DBB2r0VQ.js → Settings-Bxk_x55l.js} +1 -1
  29. package/client/dist/assets/{SkeletonGrid-DJelJgAv.js → SkeletonGrid-CpJCZSRN.js} +1 -1
  30. package/client/dist/assets/{Trackers-DGYXFhYZ.js → Trackers-RieCh_84.js} +1 -1
  31. package/client/dist/assets/{Tv-C5MDGD2Z.js → Tv-DLytPJUj.js} +1 -1
  32. package/client/dist/assets/{TvInfo-DrKuK13u.js → TvInfo-C8djKebR.js} +1 -1
  33. package/client/dist/assets/{TvPopup-DtHilRDT.js → TvPopup-40JGGJDk.js} +1 -1
  34. package/client/dist/assets/{TvSearch-CzNxdu_w.js → TvSearch-A3hbospf.js} +1 -1
  35. package/client/dist/assets/{TvWatchlist-D7p4br6S.js → TvWatchlist-DuXjqmZM.js} +1 -1
  36. package/client/dist/assets/{Watchlist-DrOzswl2.js → Watchlist-B9wGBh0u.js} +1 -1
  37. package/client/dist/assets/{hls-BGCX7mJy.js → hls-CZ5X3Ya-.js} +1 -1
  38. package/client/dist/assets/{index-CnfZwsds.js → index-dRhc9-ol.js} +2 -2
  39. package/client/dist/assets/{useAnimeInfoData-DYXWBPF-.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
  40. package/client/dist/assets/{useAsmr-BbhQ59bk.js → useAsmr-CKOdHgol.js} +1 -1
  41. package/client/dist/assets/{useAsmrPopup-SymV1J-b.js → useAsmrPopup-SFHPhpcl.js} +1 -1
  42. package/client/dist/assets/{useMangaLibrary-DuesWQaE.js → useMangaLibrary-CKATe31P.js} +1 -1
  43. package/client/dist/assets/{useMangaPopup-D57OVGUr.js → useMangaPopup-C75QuYgY.js} +1 -1
  44. package/client/dist/assets/{useProviders-mpQb_dix.js → useProviders-CMtvZbEi.js} +1 -1
  45. package/client/dist/assets/{useTv-DGflCsSI.js → useTv-DUX4zYMr.js} +1 -1
  46. package/client/dist/assets/{useTvLibrary-BqeSmyc4.js → useTvLibrary-By4Gt-R_.js} +1 -1
  47. package/client/dist/index.html +1 -1
  48. package/package.json +2 -2
  49. package/server/dist/app-auth.js +1 -157
  50. package/server/dist/app-hono.js +7 -557
  51. package/server/dist/config.js +1 -174
  52. package/server/dist/db/drizzle.js +1 -16
  53. package/server/dist/db/schema-anime.js +1 -61
  54. package/server/dist/db/schema-asmr.js +1 -13
  55. package/server/dist/db/schema-manga.js +1 -31
  56. package/server/dist/db/schema-tv.js +1 -18
  57. package/server/dist/db.js +1 -217
  58. package/server/dist/discord-gateway.js +1 -385
  59. package/server/dist/discord-rpc.js +1 -413
  60. package/server/dist/github-sync.js +1 -508
  61. package/server/dist/google.js +1 -741
  62. package/server/dist/hono/asmr.js +1 -10
  63. package/server/dist/hono/auth.js +3 -26
  64. package/server/dist/hono/music.js +1 -209
  65. package/server/dist/hono/proxy.js +36 -783
  66. package/server/dist/hono/tv.js +5 -200
  67. package/server/dist/hono/watchlist.js +2 -338
  68. package/server/dist/lib/anilist.js +26 -1458
  69. package/server/dist/lib/anischedule.js +1 -191
  70. package/server/dist/lib/insights.js +1 -185
  71. package/server/dist/lib/ipc.js +1 -23
  72. package/server/dist/lib/kitsu.js +1 -517
  73. package/server/dist/lib/mal-import.js +1 -291
  74. package/server/dist/lib/mal.js +1 -957
  75. package/server/dist/lib/migration.js +1 -310
  76. package/server/dist/lib/offline-db.js +4 -384
  77. package/server/dist/lib/potoken.js +1 -135
  78. package/server/dist/lib/recommendations.service.js +1 -216
  79. package/server/dist/lib/temp-ids.js +1 -3
  80. package/server/dist/lib/tmdb.js +1 -72
  81. package/server/dist/lib/tracker/anilist-tracker.js +8 -245
  82. package/server/dist/lib/tracker/manga-sync.service.js +1 -773
  83. package/server/dist/lib/tracker/sync-state.js +1 -21
  84. package/server/dist/lib/tracker/sync.service.js +1 -476
  85. package/server/dist/lib/translate.js +3 -79
  86. package/server/dist/lib/ump.js +1 -106
  87. package/server/dist/lib/ytmusic.js +1 -162
  88. package/server/dist/logger.js +1 -16
  89. package/server/dist/providers/manga/manga.types.js +0 -1
  90. package/server/dist/providers/provider.interface.js +0 -1
  91. package/server/dist/providers/remote-loader.js +2 -614
  92. package/server/dist/providers/remote-types.js +1 -159
  93. package/server/dist/providers/title-matching.js +1 -226
  94. package/server/dist/providers/tv.types.js +0 -1
  95. package/server/dist/rclone.js +2 -120
  96. package/server/dist/repositories/asmr.repository.js +11 -46
  97. package/server/dist/repositories/insights.repository.js +11 -25
  98. package/server/dist/repositories/library.repository.js +3 -94
  99. package/server/dist/repositories/mal-cache.repository.js +1 -52
  100. package/server/dist/repositories/manga.repository.js +14 -58
  101. package/server/dist/repositories/notifications.repository.js +6 -52
  102. package/server/dist/repositories/queue.repository.js +5 -58
  103. package/server/dist/repositories/settings.repository.js +2 -29
  104. package/server/dist/repositories/shows-meta.repository.js +3 -56
  105. package/server/dist/repositories/temp-show-ids.repository.js +3 -63
  106. package/server/dist/repositories/tv.repository.js +11 -54
  107. package/server/dist/repositories/watched-episodes.repository.js +6 -55
  108. package/server/dist/repositories/watchlist.repository.js +3 -54
  109. package/server/dist/scripts/backfill-episode-duration.js +1 -42
  110. package/server/dist/server.js +1 -310
  111. package/server/dist/shipped-defaults.js +1 -6
  112. package/server/dist/sync-payload.js +1 -91
  113. package/server/dist/sync.js +3 -647
  114. package/server/dist/trpc/context.js +1 -7
  115. package/server/dist/trpc/errors.js +1 -15
  116. package/server/dist/trpc/index.js +1 -18
  117. package/server/dist/trpc/router.js +1 -49
  118. package/server/dist/trpc/routers/asmr-library.js +1 -112
  119. package/server/dist/trpc/routers/asmr-progress.js +1 -121
  120. package/server/dist/trpc/routers/asmr.js +1 -87
  121. package/server/dist/trpc/routers/auth.js +1 -194
  122. package/server/dist/trpc/routers/continue-watching.js +2 -58
  123. package/server/dist/trpc/routers/data.js +1 -1026
  124. package/server/dist/trpc/routers/discord.js +1 -285
  125. package/server/dist/trpc/routers/discovery.js +1 -11
  126. package/server/dist/trpc/routers/insights.js +1 -14
  127. package/server/dist/trpc/routers/manga-library.js +1 -263
  128. package/server/dist/trpc/routers/manga-progress.js +1 -169
  129. package/server/dist/trpc/routers/manga.js +1 -201
  130. package/server/dist/trpc/routers/music.js +1 -655
  131. package/server/dist/trpc/routers/notifications.js +1 -80
  132. package/server/dist/trpc/routers/progress.js +1 -109
  133. package/server/dist/trpc/routers/queue.js +1 -93
  134. package/server/dist/trpc/routers/radio.js +1 -145
  135. package/server/dist/trpc/routers/settings.js +1 -96
  136. package/server/dist/trpc/routers/tracker.js +1 -213
  137. package/server/dist/trpc/routers/tv-library.js +1 -135
  138. package/server/dist/trpc/routers/tv-progress.js +1 -159
  139. package/server/dist/trpc/routers/tv.js +1 -551
  140. package/server/dist/trpc/routers/watchlist.js +2 -549
  141. package/server/dist/trpc/validation.js +1 -258
  142. package/server/dist/utils/cache.utils.js +1 -25
  143. package/server/dist/utils/cookie.utils.js +1 -13
  144. package/server/dist/utils/db-utils.js +5 -17
  145. package/server/dist/utils/env.utils.js +5 -77
  146. package/server/dist/utils/http.utils.js +1 -82
  147. package/server/dist/utils/request-context.js +1 -2
  148. package/server/dist/utils/security.utils.js +1 -76
  149. package/server/package.json +3 -2
@@ -1,7 +1,4 @@
1
- import { sql } from 'drizzle-orm';
2
- import { getDrizzle } from '../db/drizzle.js';
3
- export const InsightsRepository = {
4
- getCoreStats: async (db) => (await getDrizzle(db).all(sql `
1
+ import{sql as t}from"drizzle-orm";import{getDrizzle as s}from"../db/drizzle.js";const w={getCoreStats:async e=>(await s(e).all(t`
5
2
  SELECT
6
3
  (SELECT SUM(
7
4
  CASE WHEN we.currentTime > 0 THEN we.currentTime
@@ -18,12 +15,9 @@ export const InsightsRepository = {
18
15
  (SELECT COUNT(*) FROM watched_episodes) as totalEpisodes,
19
16
  (SELECT COUNT(DISTINCT showId) FROM watched_episodes) as totalAnime,
20
17
  (SELECT COUNT(*) FROM watchlist WHERE status = 'Completed') as completedCount,
21
- (SELECT COUNT(*) FROM watchlist) as totalWatchlist`))[0],
22
- getActivityGrid: (db) => getDrizzle(db).all(sql `
23
- SELECT date(watchedAt) as day, COUNT(*) as count FROM watched_episodes WHERE NOT (currentTime = 0 AND duration = 0) GROUP BY day`),
24
- getHourlyDist: (db) => getDrizzle(db).all(sql `
25
- SELECT strftime('%H', watchedAt) as hour, COUNT(*) as count FROM watched_episodes WHERE NOT (currentTime = 0 AND duration = 0) GROUP BY hour`),
26
- getSeasonality: (db) => getDrizzle(db).all(sql `
18
+ (SELECT COUNT(*) FROM watchlist) as totalWatchlist`))[0],getActivityGrid:e=>s(e).all(t`
19
+ SELECT date(watchedAt) as day, COUNT(*) as count FROM watched_episodes WHERE NOT (currentTime = 0 AND duration = 0) GROUP BY day`),getHourlyDist:e=>s(e).all(t`
20
+ SELECT strftime('%H', watchedAt) as hour, COUNT(*) as count FROM watched_episodes WHERE NOT (currentTime = 0 AND duration = 0) GROUP BY hour`),getSeasonality:e=>s(e).all(t`
27
21
  SELECT strftime('%m', we.watchedAt) as month, SUM(
28
22
  CASE WHEN we.currentTime > 0 THEN we.currentTime
29
23
  WHEN we.currentTime = 0 AND we.duration = 0 THEN
@@ -36,8 +30,7 @@ export const InsightsRepository = {
36
30
  ELSE 0 END) as seconds
37
31
  FROM watched_episodes we
38
32
  LEFT JOIN shows_meta sm ON sm.id = we.showId
39
- GROUP BY month`),
40
- getAllWatches: (db) => getDrizzle(db).all(sql `
33
+ GROUP BY month`),getAllWatches:e=>s(e).all(t`
41
34
  SELECT we.watchedAt, we.currentTime,
42
35
  CASE WHEN we.currentTime > 0 THEN we.currentTime
43
36
  WHEN we.currentTime = 0 AND we.duration = 0 THEN
@@ -50,27 +43,23 @@ export const InsightsRepository = {
50
43
  ELSE 0 END as effectiveSeconds
51
44
  FROM watched_episodes we
52
45
  LEFT JOIN shows_meta sm ON sm.id = we.showId
53
- ORDER BY we.watchedAt ASC`),
54
- getWatchedShowsMeta: (db) => getDrizzle(db).all(sql `
46
+ ORDER BY we.watchedAt ASC`),getWatchedShowsMeta:e=>s(e).all(t`
55
47
  SELECT DISTINCT sm.id, sm.genres, sm.popularityScore
56
48
  FROM shows_meta sm
57
- JOIN watched_episodes we ON sm.id = we.showId`),
58
- getDroppedShows: (db) => getDrizzle(db).all(sql `
49
+ JOIN watched_episodes we ON sm.id = we.showId`),getDroppedShows:e=>s(e).all(t`
59
50
  SELECT w.id, w.name, MAX(we.watchedAt) as lastActivity
60
51
  FROM watchlist w
61
52
  JOIN watched_episodes we ON w.id = we.showId
62
53
  WHERE w.status = 'Watching' AND NOT (we.currentTime = 0 AND we.duration = 0)
63
54
  GROUP BY w.id
64
- HAVING lastActivity < date('now', '-90 days')`),
65
- getCompletionVelocities: (db) => getDrizzle(db).all(sql `
55
+ HAVING lastActivity < date('now', '-90 days')`),getCompletionVelocities:e=>s(e).all(t`
66
56
  SELECT
67
57
  (julianday(MAX(we.watchedAt)) - julianday(MIN(we.watchedAt))) as daysToFinish
68
58
  FROM watchlist w
69
59
  JOIN watched_episodes we ON w.id = we.showId
70
60
  WHERE w.status = 'Completed'
71
61
  GROUP BY w.id
72
- HAVING SUM(we.currentTime) > 0`),
73
- getWatchedEpisodesWithMeta: (db) => getDrizzle(db).all(sql `
62
+ HAVING SUM(we.currentTime) > 0`),getWatchedEpisodesWithMeta:e=>s(e).all(t`
74
63
  SELECT
75
64
  we.showId,
76
65
  we.currentTime,
@@ -91,8 +80,7 @@ export const InsightsRepository = {
91
80
  sm.englishName,
92
81
  sm.thumbnail
93
82
  FROM watched_episodes we
94
- JOIN shows_meta sm ON we.showId = sm.id`),
95
- getLibraryShowsWithGenres: (db) => getDrizzle(db).all(sql `
83
+ JOIN shows_meta sm ON we.showId = sm.id`),getLibraryShowsWithGenres:e=>s(e).all(t`
96
84
  SELECT
97
85
  w.id,
98
86
  w.status,
@@ -101,6 +89,4 @@ export const InsightsRepository = {
101
89
  (SELECT COUNT(*) FROM watched_episodes we WHERE we.showId = w.id) as episodesWatched,
102
90
  COALESCE(sm.popularityScore, 0) as popularityScore
103
91
  FROM watchlist w
104
- LEFT JOIN shows_meta sm ON sm.id = w.id`),
105
- getAllAnilistIds: (db) => getDrizzle(db).all(sql `SELECT anilistId FROM shows_meta WHERE anilistId IS NOT NULL`),
106
- };
92
+ LEFT JOIN shows_meta sm ON sm.id = w.id`),getAllAnilistIds:e=>s(e).all(t`SELECT anilistId FROM shows_meta WHERE anilistId IS NOT NULL`)};export{w as InsightsRepository};
@@ -1,94 +1,3 @@
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
+ import{sql as a}from"drizzle-orm";import{getDrizzle as n}from"../db/drizzle.js";const l=["watchlist","watched_episodes","queue","shows_meta","dismissed_notifications","discovered_notifications","legacy_id_mapping","temp_show_ids"],b=["manga_library","manga_progress"],m=["tv_library","tv_progress"],E=["asmr_library","asmr_progress"];async function u(e,o){const t={};for(const r of o)try{const s=await n(e).all(a`SELECT COUNT(*) AS rows FROM ${a.raw(`"${r}"`)}`);t[r]=s[0]?.rows??0}catch{t[r]=0}return t}async function c(e,o){for(const t of o)try{await n(e).run(a`DELETE FROM ${a.raw(`"${t}"`)}`)}catch{}}function A(e){const o=a.raw(`"${e}"`);return{getById:async(t,r)=>(await n(t).all(a`SELECT * FROM ${o} WHERE id = ${r}`))[0],getAll:(t,r,s,i)=>{const p=a`SELECT * FROM ${o}`;return r&&r!=="All"&&p.append(a` WHERE status = ${r}`),p.append(a` ORDER BY updatedAt DESC`),s!==void 0&&i!==void 0&&p.append(a` LIMIT ${s} OFFSET ${i}`),n(t).all(p)},getCount:async(t,r)=>r&&r!=="All"?(await n(t).all(a`SELECT COUNT(*) as total FROM ${o} WHERE status = ${r}`))[0]?.total||0:(await n(t).all(a`SELECT COUNT(*) as total FROM ${o}`))[0]?.total||0,getIds:async t=>(await n(t).all(a`SELECT id FROM ${o}`)).map(s=>s.id),updateStatus:(t,r,s)=>n(t).run(a`
2
+ UPDATE ${o} SET status = ${s}, updatedAt = strftime('%s', 'now') WHERE id = ${r}`),updateStatusMany:(t,r,s)=>r.length===0?Promise.resolve():n(t).run(a`
3
+ UPDATE ${o} SET status = ${s}, updatedAt = strftime('%s', 'now') WHERE id IN (${a.join(r.map(i=>a`${i}`),a`, `)})`),delete:(t,r)=>n(t).run(a`DELETE FROM ${o} WHERE id = ${r}`),deleteMany:(t,r)=>r.length===0?Promise.resolve():n(t).run(a`DELETE FROM ${o} WHERE id IN (${a.join(r.map(s=>a`${s}`),a`, `)})`)}}const L={countAll:e=>u(e,l),clearAll:e=>c(e,l),countManga:e=>u(e,b),clearManga:e=>c(e,b),countTv:e=>u(e,m),clearTv:e=>c(e,m),countAsmr:e=>u(e,E),clearAsmr:e=>c(e,E)};export{L as LibraryRepository,A as makeLibraryRepo};
@@ -1,52 +1 @@
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
+ import{DatabaseSync as p}from"node:sqlite";import T from"fs";import m from"path";import{CONFIG as l}from"../config.js";import{DatabaseWrapper as u}from"../db.js";import{dbGet as a,dbRun as n}from"../utils/db-utils.js";const s=5e3,S=6*3600;function h(){return m.join(l.ROOT,l.IS_DEV?"mal-cache.dev.db":"mal-cache.db")}let o=null;function c(){if(!o){const e=h();T.mkdirSync(m.dirname(e),{recursive:!0}),o=new u(e,new p(e)),n(o,"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)")}return o}function f(e){const t=a(c(),"SELECT payload, fetched_at AS fetchedAt, expires_at AS expiresAt FROM mal_cache WHERE key = ?",[e]);return t?{payload:t.payload,fresh:Date.now()<Date.parse(t.expiresAt)}:null}function O(e,t,i=S){const r=c(),E=new Date;n(r,"INSERT OR REPLACE INTO mal_cache (key, payload, fetched_at, expires_at) VALUES (?, ?, ?, ?)",[e,t,E.toISOString(),new Date(E.getTime()+i*1e3).toISOString()]),!((a(r,"SELECT COUNT(*) AS rows FROM mal_cache")?.rows??0)<s||(d(),(a(r,"SELECT COUNT(*) AS rows FROM mal_cache")?.rows??0)<s))&&n(r,`DELETE FROM mal_cache WHERE key NOT IN (SELECT key FROM mal_cache ORDER BY fetched_at DESC LIMIT ${s})`)}function d(){const e=new Date().toISOString(),t=a(c(),"SELECT COUNT(*) AS rows FROM mal_cache WHERE expires_at < ?",[e]);return n(c(),"DELETE FROM mal_cache WHERE expires_at < ?",[e]),t?.rows??0}function N(){return{get:e=>f(e),put:(e,t,i)=>O(e,t,i)}}export{s as MAL_CACHE_MAX_ROWS,f as malCacheGet,d as malCachePruneExpired,O as malCachePut,N as malCacheStore};
@@ -1,16 +1,6 @@
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 `
1
+ import{eq as l,inArray as p,sql as n}from"drizzle-orm";import{getDrizzle as e}from"../db/drizzle.js";import{mangaLibrary as i,mangaProgress as s}from"../db/schema-manga.js";import{makeLibraryRepo as o}from"./library.repository.js";const I=["Reading","Completed","On-Hold","Dropped","Planned"];function L(a,t){return`${a.toLowerCase()}:${t}`}const D={...o("manga_library"),upsert:(a,t)=>e(a).run(n`
12
2
  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'))
3
+ VALUES (${t.id}, ${t.provider}, ${t.mangaId}, ${t.title}, ${t.cover}, ${t.status}, ${t.author||null}, ${t.altTitle||null}, ${t.contentRating||null}, ${t.anilistId??null}, ${t.anilistIdSource??null}, strftime('%s', 'now'))
14
4
  ON CONFLICT(id) DO UPDATE SET
15
5
  provider = COALESCE(NULLIF(EXCLUDED.provider, ''), manga_library.provider),
16
6
  title = COALESCE(NULLIF(EXCLUDED.title, ''), manga_library.title),
@@ -21,34 +11,14 @@ export const MangaLibraryRepository = {
21
11
  contentRating = COALESCE(EXCLUDED.contentRating, manga_library.contentRating),
22
12
  anilistId = COALESCE(EXCLUDED.anilistId, manga_library.anilistId),
23
13
  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 `
14
+ updatedAt = strftime('%s', 'now')`),setAnilistId:(a,t,r,g)=>g?e(a).update(i).set({anilistId:r,anilistIdSource:g}).where(l(i.id,t)):e(a).update(i).set({anilistId:r}).where(l(i.id,t)),touchProgress:(a,t,r)=>e(a).run(n`
15
+ UPDATE manga_library SET lastChapterId = ${r.chapterId}, lastChapterNumber = ${r.chapterNumber}, lastPage = ${r.page}, updatedAt = strftime('%s', 'now') WHERE id = ${t}`),setProgressPointer:(a,t,r)=>e(a).run(n`
16
+ UPDATE manga_library SET lastChapterNumber = ${r}, updatedAt = strftime('%s', 'now') WHERE id = ${t}`)},b={getByManga:(a,t)=>e(a).all(n`
17
+ SELECT mangaId, chapterId, chapterNumber, page, pageCount, updatedAt FROM manga_progress WHERE mangaId = ${t} ORDER BY updatedAt DESC`),getChapter:async(a,t,r)=>(await e(a).all(n`
18
+ SELECT mangaId, chapterId, chapterNumber, page, pageCount, updatedAt FROM manga_progress WHERE mangaId = ${t} AND chapterId = ${r}`))[0],getLatest:async(a,t)=>(await e(a).all(n`
19
+ SELECT mangaId, chapterId, chapterNumber, page, pageCount, updatedAt FROM manga_progress WHERE mangaId = ${t} ORDER BY updatedAt DESC LIMIT 1`))[0],upsert:(a,t)=>e(a).run(n`
50
20
  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'))
21
+ VALUES (${t.mangaId}, ${t.chapterId}, ${t.chapterNumber}, ${t.page}, ${t.pageCount}, ${t.title??null}, ${t.cover??null}, ${t.provider??null}, ${t.altTitle??null}, ${t.contentRating??null}, strftime('%s', 'now'))
52
22
  ON CONFLICT(mangaId, chapterId) DO UPDATE SET
53
23
  chapterNumber = COALESCE(NULLIF(EXCLUDED.chapterNumber, ''), manga_progress.chapterNumber),
54
24
  page = EXCLUDED.page,
@@ -58,22 +28,10 @@ export const MangaProgressRepository = {
58
28
  provider = COALESCE(EXCLUDED.provider, manga_progress.provider),
59
29
  altTitle = COALESCE(EXCLUDED.altTitle, manga_progress.altTitle),
60
30
  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 `
31
+ updatedAt = strftime('%s', 'now')`),deleteByManga:(a,t)=>e(a).delete(s).where(l(s.mangaId,t)),deleteMany:(a,t)=>t.length===0?Promise.resolve():e(a).delete(s).where(p(s.mangaId,t)),deleteChapter:(a,t,r)=>e(a).run(n`
32
+ DELETE FROM manga_progress WHERE mangaId = ${t} AND chapterId = ${r}`),deleteSyntheticChapters:(a,t,r)=>e(a).run(n`
33
+ DELETE FROM manga_progress WHERE mangaId = ${t} AND chapterId LIKE 'anilist:ch:%' AND chapterId != ${r}`),moveSyntheticChapters:(a,t,r)=>e(a).run(n`
34
+ UPDATE manga_progress SET mangaId = ${r}, updatedAt = strftime('%s', 'now') WHERE mangaId = ${t} AND chapterId LIKE 'anilist:ch:%'`),getContinueReading:(a,t)=>{const r=typeof t=="number"?`LIMIT ${t}`:"";return e(a).all(n`
77
35
  SELECT p.mangaId as id,
78
36
  COALESCE(l.mangaId, SUBSTR(p.mangaId, INSTR(p.mangaId, ':') + 1)) as mangaId,
79
37
  COALESCE(l.provider, p.provider) as provider,
@@ -90,6 +48,4 @@ export const MangaProgressRepository = {
90
48
  WHERE p.rn = 1
91
49
  AND (l.status IS NULL OR l.status = 'Reading')
92
50
  ORDER BY p.updatedAt DESC
93
- ${sql.raw(limitClause)}`);
94
- },
95
- };
51
+ ${n.raw(r)}`)}};export{I as MANGA_STATUSES,D as MangaLibraryRepository,b as MangaProgressRepository,L as buildMangaId};
@@ -1,51 +1,8 @@
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 `
1
+ import{and as n,eq as t,sql as o}from"drizzle-orm";import{getDrizzle as i}from"../db/drizzle.js";import{discoveredNotifications as d,dismissedNotifications as a}from"../db/schema-anime.js";const N={getDismissedByShow:(e,s)=>i(e).all(o`SELECT episodeNumber FROM dismissed_notifications WHERE showId = ${s}`),getDiscoveredByShow:(e,s)=>i(e).all(o`SELECT episodeNumber FROM discovered_notifications WHERE showId = ${s}`),addDiscovered:(e,s,r)=>i(e).insert(d).values({showId:s,episodeNumber:r}).onConflictDoNothing(),addDismissed:(e,s,r)=>i(e).insert(a).values({showId:s,episodeNumber:r}).onConflictDoNothing(),dismissFromDiscovered:(e,s)=>s?i(e).run(o`
2
+ INSERT OR IGNORE INTO dismissed_notifications (showId, episodeNumber) SELECT showId, episodeNumber FROM discovered_notifications WHERE showId = ${s}`):i(e).run(o`
3
+ INSERT OR IGNORE INTO dismissed_notifications (showId, episodeNumber) SELECT showId, episodeNumber FROM discovered_notifications`),deleteByShow:(e,s)=>Promise.all([i(e).delete(a).where(t(a.showId,s)),i(e).delete(d).where(t(d.showId,s))]),deleteSpecificDismissed:(e,s,r)=>i(e).delete(a).where(n(t(a.showId,s),t(a.episodeNumber,r))),deleteDiscovered:(e,s,r)=>i(e).delete(d).where(n(t(d.showId,s),t(d.episodeNumber,r))),cleanupWatchedNotifications:e=>Promise.all([i(e).run(o`
4
+ DELETE FROM dismissed_notifications WHERE EXISTS (SELECT 1 FROM watched_episodes we WHERE we.showId = dismissed_notifications.showId AND we.episodeNumber = dismissed_notifications.episodeNumber)`),i(e).run(o`
5
+ DELETE FROM discovered_notifications WHERE EXISTS (SELECT 1 FROM watched_episodes we WHERE we.showId = discovered_notifications.showId AND we.episodeNumber = discovered_notifications.episodeNumber)`)]),hasAnyDiscovered:async e=>((await i(e).all(o`SELECT COUNT(*) as count FROM discovered_notifications`))[0]?.count||0)>0,hasActiveNotifications:async e=>((await i(e).all(o`
49
6
  SELECT COUNT(*) as count FROM discovered_notifications dn
50
7
  WHERE NOT EXISTS (
51
8
  SELECT 1 FROM dismissed_notifications dn2
@@ -54,7 +11,4 @@ export const NotificationsRepository = {
54
11
  AND NOT EXISTS (
55
12
  SELECT 1 FROM watched_episodes we
56
13
  WHERE we.showId = dn.showId AND we.episodeNumber = dn.episodeNumber
57
- )`);
58
- return (rows[0]?.count || 0) > 0;
59
- },
60
- };
14
+ )`))[0]?.count||0)>0};export{N as NotificationsRepository};
@@ -1,8 +1,4 @@
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 `
1
+ import{and as n,eq as o,sql as u}from"drizzle-orm";import{getDrizzle as s}from"../db/drizzle.js";import{queue as i}from"../db/schema-anime.js";const d={getAll:e=>s(e).all(u`
6
2
  SELECT
7
3
  q.id,
8
4
  q.showId,
@@ -16,57 +12,8 @@ export const QueueRepository = {
16
12
  FROM queue q
17
13
  LEFT JOIN shows_meta sm ON q.showId = sm.id
18
14
  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 `
15
+ ORDER BY q.queue_order ASC, q.id ASC`),getByEpisode:async(e,a,r)=>(await s(e).all(u`SELECT * FROM queue WHERE showId = ${a} AND episodeNumber = ${r}`))[0],getByShow:(e,a)=>s(e).all(u`SELECT * FROM queue WHERE showId = ${a} ORDER BY queue_order ASC`),getMaxOrder:async e=>(await s(e).all(u`SELECT COALESCE(MAX(queue_order), -1) as maxOrder FROM queue`))[0]?.maxOrder??-1,addToEnd:(e,a,r)=>s(e).run(u`
30
16
  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
- };
17
+ (${a}, ${r}, (SELECT COALESCE(MAX(queue_order), -1) + 1 FROM queue))`),removeEpisode:(e,a,r)=>s(e).delete(i).where(n(o(i.showId,a),o(i.episodeNumber,r))),addManyToEnd:async(e,a)=>{if(a.length===0)return;let r=await d.getMaxOrder(e)+1;for(const t of a)await d.getByEpisode(e,t.showId,t.episodeNumber)||(await s(e).insert(i).values({showId:t.showId,episodeNumber:t.episodeNumber,queueOrder:r}),r+=1)},removeMany:(e,a,r)=>r.length===0?Promise.resolve():s(e).run(u`
18
+ DELETE FROM queue WHERE showId = ${a} AND episodeNumber IN (${u.join(r.map(t=>u`${t}`),u`, `)})`),clear:e=>s(e).delete(i),reorder:(e,a)=>Promise.all(a.map((r,t)=>r.id!==void 0?s(e).update(i).set({queueOrder:t}).where(o(i.id,r.id)):s(e).update(i).set({queueOrder:t}).where(n(o(i.showId,r.showId??""),o(i.episodeNumber,r.episodeNumber??""))))),cleanupOrphanedShowsMeta:e=>s(e).run(u`
19
+ DELETE FROM shows_meta WHERE id NOT IN (SELECT id FROM watchlist) AND id NOT IN (SELECT showId FROM queue)`)};export{d as QueueRepository};
@@ -1,30 +1,3 @@
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 `
1
+ import{eq as g,sql as r}from"drizzle-orm";import{getDrizzle as s}from"../db/drizzle.js";import{settings as n,watchlist as p}from"../db/schema-anime.js";const o={getByKey:async(t,e)=>(await s(t).all(r`SELECT value FROM settings WHERE key = ${e}`))[0],upsert:(t,e,a)=>s(t).insert(n).values({key:e,value:a}).onConflictDoUpdate({target:n.key,set:{value:a}}),deleteByKey:(t,e)=>s(t).delete(n).where(g(n.key,e)),readContinueFlags:async(t,e,a)=>{const l=await o.getByKey(t,e),i=await o.getByKey(t,a);return{ignoreAdult:l?l.value!=="false":!0,listOnly:i?i.value==="true"||i.value==="1":!1}},clearWatchlist:t=>s(t).delete(p),upsertWatchlistBatch:(t,e)=>e.length===0?Promise.resolve():s(t).run(r`
27
2
  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
- };
3
+ ${r.join(e.map(a=>r`(${a.id}, ${a.name}, ${a.thumbnail??null}, ${a.status})`),r`, `)}`)};export{o as SettingsRepository};
@@ -1,58 +1,5 @@
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 `
1
+ import{eq as r,sql as i}from"drizzle-orm";import{getDrizzle as a}from"../db/drizzle.js";import{showsMeta as s}from"../db/schema-anime.js";const E={getById:async(n,e)=>(await a(n).all(i`SELECT * FROM shows_meta WHERE id = ${e}`))[0],getStatus:async(n,e)=>(await a(n).all(i`SELECT status FROM shows_meta WHERE id = ${e}`))[0]?.status,upsert:(n,e)=>{const t={id:e.id,name:e.name??null,thumbnail:e.thumbnail??null,nativeName:e.nativeName??null,englishName:e.englishName??null,genres:e.genres??null,popularityScore:e.popularityScore??null,status:e.status??null,episodeCount:e.episodeCount??null,type:e.type??null,anilistId:e.anilistId??null,isAdult:e.isAdult??null,episodeDuration:e.episodeDuration??null};return a(n).insert(s).values(t).onConflictDoUpdate({target:s.id,set:{name:i`COALESCE(NULLIF(${t.name}, ''), ${s.name})`,thumbnail:i`COALESCE(NULLIF(${t.thumbnail}, ''), ${s.thumbnail})`,nativeName:i`COALESCE(NULLIF(${t.nativeName}, ''), ${s.nativeName})`,englishName:i`COALESCE(NULLIF(${t.englishName}, ''), ${s.englishName})`,genres:i`COALESCE(NULLIF(${t.genres}, ''), ${s.genres})`,popularityScore:i`COALESCE(${t.popularityScore}, ${s.popularityScore})`,status:i`COALESCE(NULLIF(${t.status}, ''), ${s.status})`,episodeCount:i`COALESCE(${t.episodeCount}, ${s.episodeCount})`,type:i`COALESCE(NULLIF(${t.type}, ''), ${s.type})`,anilistId:i`COALESCE(${t.anilistId}, ${s.anilistId})`,isAdult:i`COALESCE(${t.isAdult}, ${s.isAdult})`,episodeDuration:i`COALESCE(NULLIF(${t.episodeDuration}, 0), ${s.episodeDuration})`}})},updateEpisodeCount:(n,e,t)=>a(n).update(s).set({episodeCount:t}).where(r(s.id,e)),updateStatus:(n,e,t)=>a(n).update(s).set({status:t}).where(r(s.id,e)),cleanupOrphanedMeta:n=>a(n).run(i`
2
+ DELETE FROM shows_meta WHERE id NOT IN (SELECT id FROM watchlist) AND id NOT IN (SELECT showId FROM queue)`),getShowIdsMissingMeta:n=>a(n).all(i`
55
3
  SELECT DISTINCT we.showId as id FROM watched_episodes we
56
4
  LEFT JOIN shows_meta sm ON sm.id = we.showId
57
- WHERE sm.thumbnail IS NULL OR TRIM(sm.thumbnail) = ''`),
58
- };
5
+ WHERE sm.thumbnail IS NULL OR TRIM(sm.thumbnail) = ''`)};export{E as ShowsMetaRepository};