@serifpersia/dango 3.6.5 → 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 (151) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-JkXG40Es.js → AnimeCard-DraA_TF9.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-2L0UJkbB.js → AnimeInfo-BJ7pzs-P.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-BDs03vKk.js → AnimeInfoPage-B_esUr5C.js} +2 -2
  5. package/client/dist/assets/{Asmr-D2mVKEl9.js → Asmr-Ckuh2xfW.js} +1 -1
  6. package/client/dist/assets/{AsmrResetProgressModal-MQA_bNQM.js → AsmrResetProgressModal-BS6UFJN4.js} +1 -1
  7. package/client/dist/assets/AvSyncCalibrator-DwHCd9yB.js +10 -0
  8. package/client/dist/assets/{Home-DxMBHFmc.js → Home-DuM1EuvN.js} +1 -1
  9. package/client/dist/assets/{Insights-hmLWsSXY.js → Insights-D3Pd9c9V.js} +1 -1
  10. package/client/dist/assets/{LibraryListPage-CWI7hgUh.js → LibraryListPage-Dm2yVn0-.js} +1 -1
  11. package/client/dist/assets/{ListeningList-NOc28KAV.js → ListeningList-D-zt6EXZ.js} +1 -1
  12. package/client/dist/assets/{Manga-B5qNhI7v.js → Manga-JwprMKBk.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-D4wIyb-V.js → MangaInfoPage-DaeyJl2p.js} +1 -1
  14. package/client/dist/assets/{MangaReadPage-Ce1VX7-0.js → MangaReadPage-COg0fHI9.js} +1 -1
  15. package/client/dist/assets/{MangaResetProgressModal-P3oY4_14.js → MangaResetProgressModal-kguXv0_M.js} +1 -1
  16. package/client/dist/assets/{Mature-D8EwGYQq.js → Mature-IHDaasrs.js} +1 -1
  17. package/client/dist/assets/{MediaCard-Dp8DjyXB.js → MediaCard-CpwsrXo5.js} +1 -1
  18. package/client/dist/assets/{Music-DULtVsAM.js → Music-Dks4tPeK.js} +1 -1
  19. package/client/dist/assets/{OptionListMenu-fIHdE-nq.js → OptionListMenu-11CcAsy8.js} +1 -1
  20. package/client/dist/assets/{Player-B8XeOmso.js → Player-DQoEQkHL.js} +6 -6
  21. package/client/dist/assets/{PlayerSettings-BycNCiCs.js → PlayerSettings-D4Ud4kv4.js} +1 -1
  22. package/client/dist/assets/{QueueOptionsButton-T1J37IZT.js → QueueOptionsButton-B8PlPKjC.js} +1 -1
  23. package/client/dist/assets/{QueueRail-BAwI-apz.js → QueueRail-B4Dok7U3.js} +1 -1
  24. package/client/dist/assets/{Radio-Bv9DyKtZ.js → Radio-BHK6LSk4.js} +1 -1
  25. package/client/dist/assets/{ReadingList-Cl0CcVp3.js → ReadingList-BCsxIz57.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal--N60WWCB.js → ResetProgressModal-BXHxSfy-.js} +1 -1
  27. package/client/dist/assets/{Search-DvgVDlUR.js → Search-PK1ds07O.js} +1 -1
  28. package/client/dist/assets/{Settings-dCz_-bJn.js → Settings-Bxk_x55l.js} +1 -1
  29. package/client/dist/assets/{SkeletonGrid-BZHKk1I6.js → SkeletonGrid-CpJCZSRN.js} +1 -1
  30. package/client/dist/assets/{Trackers-C1T5VcPu.js → Trackers-RieCh_84.js} +1 -1
  31. package/client/dist/assets/{Tv-Dhv_cg4y.js → Tv-DLytPJUj.js} +4 -4
  32. package/client/dist/assets/{TvInfo-DgaLK2H6.js → TvInfo-C8djKebR.js} +1 -1
  33. package/client/dist/assets/{TvPopup-DyX7f8kC.js → TvPopup-40JGGJDk.js} +1 -1
  34. package/client/dist/assets/{TvSearch-Bp0H6kue.js → TvSearch-A3hbospf.js} +1 -1
  35. package/client/dist/assets/{TvWatchlist-ohMtMeKa.js → TvWatchlist-DuXjqmZM.js} +1 -1
  36. package/client/dist/assets/{Watchlist-BbQtzsER.js → Watchlist-B9wGBh0u.js} +1 -1
  37. package/client/dist/assets/{hls-Bi1cjpLZ.js → hls-CZ5X3Ya-.js} +1 -1
  38. package/client/dist/assets/{index-BTnhzofk.js → index-dRhc9-ol.js} +2 -2
  39. package/client/dist/assets/{useAnimeInfoData-Cvmso3IK.js → useAnimeInfoData-ZzDtLfvD.js} +1 -1
  40. package/client/dist/assets/{useAsmr-CbzGCzmv.js → useAsmr-CKOdHgol.js} +1 -1
  41. package/client/dist/assets/{useAsmrPopup-DaREZ9Ie.js → useAsmrPopup-SFHPhpcl.js} +1 -1
  42. package/client/dist/assets/{useMangaLibrary-BVQRhsFh.js → useMangaLibrary-CKATe31P.js} +1 -1
  43. package/client/dist/assets/{useMangaPopup-CzsCbndG.js → useMangaPopup-C75QuYgY.js} +1 -1
  44. package/client/dist/assets/{useProviders-UuUPzft-.js → useProviders-CMtvZbEi.js} +1 -1
  45. package/client/dist/assets/{useTv-CSsbWKH9.js → useTv-DUX4zYMr.js} +1 -1
  46. package/client/dist/assets/{useTvLibrary-a_Y0c2FS.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 -905
  66. package/server/dist/hono/tv.js +5 -169
  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 -605
  92. package/server/dist/providers/remote-types.js +1 -124
  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 -307
  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
  150. package/client/dist/assets/AvSyncCalibrator-DmoH5eRA.js +0 -10
  151. package/server/dist/utils/megaplay.utils.js +0 -40
@@ -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};
@@ -1,63 +1,3 @@
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
+ import{sql as a}from"drizzle-orm";import{getDrizzle as i}from"../db/drizzle.js";import{tempShowIds as p}from"../db/schema-anime.js";import{TEMP_SHOW_ID_PREFIX as o}from"../lib/temp-ids.js";const n={getById:async(t,e)=>(await i(t).all(a`SELECT * FROM temp_show_ids WHERE id = ${e}`))[0],getByProviderNative:async(t,e,r)=>(await i(t).all(a`SELECT * FROM temp_show_ids WHERE provider = ${e} AND nativeId = ${r}`))[0],count:async t=>(await i(t).all(a`SELECT COUNT(*) as total FROM temp_show_ids`))[0]?.total||0,allocate:async(t,e)=>{const r=await n.getByProviderNative(t,e.provider,e.nativeId);if(r)return r;const s=o.length+1,m=((await i(t).all(a`
2
+ SELECT MAX(CAST(SUBSTR(id, ${s}) AS INTEGER)) as maxId FROM temp_show_ids WHERE id LIKE ${`${o}%`}`))[0]?.maxId||0)+1,E=`${o}${m}`;try{await i(t).insert(p).values({id:E,provider:e.provider,nativeId:e.nativeId,title:e.title,thumbnail:e.thumbnail||"",createdAt:Date.now()})}catch{const w=await n.getByProviderNative(t,e.provider,e.nativeId);if(w)return w;throw new Error("temp id allocation failed")}return await n.getById(t,E)},purge:async t=>{const e=await n.count(t);if(e===0)return 0;const r=`${o}%`;return await i(t).run(a`DELETE FROM watchlist WHERE id LIKE ${r}`),await i(t).run(a`DELETE FROM watched_episodes WHERE showId LIKE ${r}`),await i(t).run(a`DELETE FROM queue WHERE showId LIKE ${r}`),await i(t).run(a`DELETE FROM shows_meta WHERE id LIKE ${r}`),await i(t).run(a`DELETE FROM dismissed_notifications WHERE showId LIKE ${r}`),await i(t).run(a`DELETE FROM discovered_notifications WHERE showId LIKE ${r}`),await i(t).run(a`
3
+ DELETE FROM legacy_id_mapping WHERE legacyId LIKE ${r} OR numericId LIKE ${r}`),await i(t).delete(p),e}};export{n as TempShowIdsRepository};