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