@serifpersia/dango 3.3.9 → 3.4.0

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 (180) hide show
  1. package/README.md +1 -1
  2. package/client/dist/assets/{AnimeCard-URCiiy5W.js → AnimeCard-CJy1SXH3.js} +1 -1
  3. package/client/dist/assets/{AnimeInfo-e3z3er9t.js → AnimeInfo-BoXlVvZ-.js} +1 -1
  4. package/client/dist/assets/{AnimeInfoPage-B7Djlmy_.js → AnimeInfoPage-BxbA_7AV.js} +2 -2
  5. package/client/dist/assets/Asmr--Zc8G9zv.js +2 -0
  6. package/client/dist/assets/AsmrResetProgressModal-CKYm-8cp.js +1 -0
  7. package/client/dist/assets/AvSyncCalibrator-3yjZS7s6.js +10 -0
  8. package/client/dist/assets/Home-rrhOGQcg.js +1 -0
  9. package/client/dist/assets/Insights-Bay90Yeg.js +1 -0
  10. package/client/dist/assets/{LibraryListPage-B0qWqhb_.js → LibraryListPage-HzuDavbb.js} +1 -1
  11. package/client/dist/assets/{ListeningList-CSqD8j1C.js → ListeningList-DYcQnnsG.js} +1 -1
  12. package/client/dist/assets/{Manga-Cg2YuObF.js → Manga-D8CT9aO0.js} +1 -1
  13. package/client/dist/assets/{MangaInfoPage-DcArhEsy.js → MangaInfoPage-DGK6CYp1.js} +1 -1
  14. package/client/dist/assets/MangaReadPage-B9XoDKF9.js +1 -0
  15. package/client/dist/assets/{MangaResetProgressModal-D_yT3d1v.js → MangaResetProgressModal-QjZyIAjl.js} +1 -1
  16. package/client/dist/assets/Mature-CHt_-Hz5.js +1 -0
  17. package/client/dist/assets/MediaCard-B5584Cq9.js +1 -0
  18. package/client/dist/assets/Music-HeLrEZ2d.js +1 -0
  19. package/client/dist/assets/OptionListMenu-CJ90CdwI.js +1 -0
  20. package/client/dist/assets/Player-BYtEax_z.js +56 -0
  21. package/client/dist/assets/PlayerSettings-4-cUAY4B.js +1 -0
  22. package/client/dist/assets/QueueOptionsButton-Ba0dg_82.js +1 -0
  23. package/client/dist/assets/{QueueRail-B7f1-e6i.js → QueueRail-O7f4RqjB.js} +1 -1
  24. package/client/dist/assets/Radio-DPZxQnyD.js +1 -0
  25. package/client/dist/assets/{ReadingList-BaIem8H1.js → ReadingList-n6rr8ygy.js} +1 -1
  26. package/client/dist/assets/{ResetProgressModal-DES5Y-Fl.js → ResetProgressModal-CcStlbhP.js} +1 -1
  27. package/client/dist/assets/Search-C2wrWOdy.js +1 -0
  28. package/client/dist/assets/Settings-CluUYs8U.js +21 -0
  29. package/client/dist/assets/{SkeletonGrid-BxfgjP4i.js → SkeletonGrid-CLEPY9nP.js} +1 -1
  30. package/client/dist/assets/Trackers-CTGPxs0V.js +2 -0
  31. package/client/dist/assets/Tv-BwHlDdi8.js +4 -0
  32. package/client/dist/assets/Tv-ZEA45gXK.css +1 -0
  33. package/client/dist/assets/TvInfo-CXnrLekr.js +1 -0
  34. package/client/dist/assets/TvPopup-BekfiNcX.js +1 -0
  35. package/client/dist/assets/{TvSearch-BWDwWnHe.js → TvSearch-DuqR-z3v.js} +1 -1
  36. package/client/dist/assets/{TvWatchlist-D3n5_4MI.js → TvWatchlist-CBvgFrA0.js} +1 -1
  37. package/client/dist/assets/Watchlist-mj8mTOam.js +1 -0
  38. package/client/dist/assets/{hls-BO_L1WoN.js → hls-Boje3IOE.js} +1 -1
  39. package/client/dist/assets/index-BXwhFWB_.js +178 -0
  40. package/client/dist/assets/useAnimeInfoData-DJ7A4B4T.js +1 -0
  41. package/client/dist/assets/useAsmr-DIwIsJXF.js +1 -0
  42. package/client/dist/assets/{useAsmrPopup-D_o9-QlR.js → useAsmrPopup-e4zNN9ig.js} +1 -1
  43. package/client/dist/assets/useMangaLibrary-C9Lci5N4.js +1 -0
  44. package/client/dist/assets/{useMangaPopup-TFzkC-tG.js → useMangaPopup-B8nnpvyi.js} +1 -1
  45. package/client/dist/assets/useProviders-B5Qr4I1y.js +1 -0
  46. package/client/dist/assets/useTv-Dm8MCYlL.js +1 -0
  47. package/client/dist/assets/useTvLibrary-B7jEk2Q7.js +1 -0
  48. package/client/dist/assets/{vendor-query-BXRECSI6.js → vendor-query-BUTye89G.js} +1 -1
  49. package/client/dist/index.html +2 -2
  50. package/orchestrator.js +1 -2
  51. package/package.json +2 -6
  52. package/server/dist/app-auth.js +9 -29
  53. package/server/dist/app-hono.js +621 -0
  54. package/server/dist/config.js +0 -1
  55. package/server/dist/db/drizzle.js +32 -0
  56. package/server/dist/db/schema-anime.js +64 -0
  57. package/server/dist/db/schema-asmr.js +25 -0
  58. package/server/dist/db/schema-manga.js +31 -0
  59. package/server/dist/db/schema-tv.js +33 -0
  60. package/server/dist/db.js +75 -4
  61. package/server/dist/discord-gateway.js +1 -1
  62. package/server/dist/google.js +0 -7
  63. package/server/dist/hono/asmr.js +1 -0
  64. package/server/dist/hono/auth.js +56 -0
  65. package/server/dist/hono/music.js +419 -0
  66. package/server/dist/{controllers/proxy.controller.js → hono/proxy.js} +390 -394
  67. package/server/dist/hono/tv.js +197 -0
  68. package/server/dist/hono/watchlist.js +733 -0
  69. package/server/dist/lib/anilist.js +3 -3
  70. package/server/dist/lib/insights.js +185 -0
  71. package/server/dist/lib/ipc.js +1 -2
  72. package/server/dist/lib/kitsu.js +1 -28
  73. package/server/dist/lib/mal-import.js +291 -0
  74. package/server/dist/lib/migration.js +20 -34
  75. package/server/dist/lib/offline-db.js +36 -36
  76. package/server/dist/lib/tracker/manga-sync.service.js +21 -21
  77. package/server/dist/lib/tracker/sync.service.js +20 -20
  78. package/server/dist/lib/translate.js +79 -0
  79. package/server/dist/lib/ytmusic.js +0 -2
  80. package/server/dist/providers/title-matching.js +0 -1
  81. package/server/dist/repositories/asmr.repository.js +90 -87
  82. package/server/dist/repositories/insights.repository.js +24 -13
  83. package/server/dist/repositories/library.repository.js +33 -93
  84. package/server/dist/repositories/manga.repository.js +108 -116
  85. package/server/dist/repositories/notifications.repository.js +50 -31
  86. package/server/dist/repositories/queue.repository.js +36 -25
  87. package/server/dist/repositories/settings.repository.js +18 -8
  88. package/server/dist/repositories/shows-meta.repository.js +54 -41
  89. package/server/dist/repositories/temp-show-ids.repository.js +43 -31
  90. package/server/dist/repositories/tv.repository.js +98 -115
  91. package/server/dist/repositories/watched-episodes.repository.js +52 -20
  92. package/server/dist/repositories/watchlist.repository.js +36 -48
  93. package/server/dist/server.js +59 -151
  94. package/server/dist/sync.js +41 -4
  95. package/server/dist/trpc/context.js +7 -0
  96. package/server/dist/trpc/errors.js +21 -0
  97. package/server/dist/trpc/index.js +19 -0
  98. package/server/dist/trpc/router.js +49 -0
  99. package/server/dist/trpc/routers/asmr-library.js +122 -0
  100. package/server/dist/trpc/routers/asmr-progress.js +133 -0
  101. package/server/dist/trpc/routers/asmr.js +87 -0
  102. package/server/dist/trpc/routers/auth.js +247 -0
  103. package/server/dist/trpc/routers/continue-watching.js +83 -0
  104. package/server/dist/trpc/routers/data.js +1039 -0
  105. package/server/dist/trpc/routers/discord.js +288 -0
  106. package/server/dist/trpc/routers/discovery.js +15 -0
  107. package/server/dist/trpc/routers/insights.js +45 -0
  108. package/server/dist/trpc/routers/manga-library.js +280 -0
  109. package/server/dist/trpc/routers/manga-progress.js +181 -0
  110. package/server/dist/trpc/routers/manga.js +201 -0
  111. package/server/dist/{controllers/music.controller.js → trpc/routers/music.js} +155 -288
  112. package/server/dist/trpc/routers/notifications.js +80 -0
  113. package/server/dist/trpc/routers/progress.js +15 -0
  114. package/server/dist/trpc/routers/queue.js +95 -0
  115. package/server/dist/{routes/radio.routes.js → trpc/routers/radio.js} +30 -23
  116. package/server/dist/trpc/routers/settings.js +105 -0
  117. package/server/dist/trpc/routers/tracker.js +219 -0
  118. package/server/dist/trpc/routers/tv-library.js +145 -0
  119. package/server/dist/trpc/routers/tv-progress.js +171 -0
  120. package/server/dist/trpc/routers/tv.js +565 -0
  121. package/server/dist/trpc/routers/watchlist.js +564 -0
  122. package/server/dist/trpc/validation.js +159 -0
  123. package/server/dist/utils/security.utils.js +0 -18
  124. package/server/package.json +5 -10
  125. package/client/dist/assets/Asmr-C28TfSvX.js +0 -2
  126. package/client/dist/assets/AsmrResetProgressModal-6Xh71AgR.js +0 -1
  127. package/client/dist/assets/AvSyncCalibrator-BwCtRW0P.js +0 -10
  128. package/client/dist/assets/Home-BJdbHYja.js +0 -1
  129. package/client/dist/assets/Insights-CjRy42YE.js +0 -1
  130. package/client/dist/assets/MangaReadPage-YCETZRte.js +0 -1
  131. package/client/dist/assets/Mature-BGxRWkV5.js +0 -1
  132. package/client/dist/assets/MediaCard-CAzoxsRS.js +0 -1
  133. package/client/dist/assets/Music-Cgo-yQ7E.js +0 -1
  134. package/client/dist/assets/OptionListMenu--Owe9Jle.js +0 -1
  135. package/client/dist/assets/Player-yRXG2BrP.js +0 -56
  136. package/client/dist/assets/PlayerSettings-DrZLa0CC.js +0 -1
  137. package/client/dist/assets/QueueOptionsButton-BVZyPyKf.js +0 -1
  138. package/client/dist/assets/Radio-BFjxlzuF.js +0 -1
  139. package/client/dist/assets/Search-BOBo0Ogh.js +0 -1
  140. package/client/dist/assets/Settings-DOvNQ0gX.js +0 -21
  141. package/client/dist/assets/Trackers-DZ9KqOWv.js +0 -2
  142. package/client/dist/assets/Tv-5mATEU4p.js +0 -4
  143. package/client/dist/assets/Tv-C2SWcVzN.css +0 -1
  144. package/client/dist/assets/TvInfo-CF-5C5Fs.js +0 -1
  145. package/client/dist/assets/TvPopup-Dfqifj3s.js +0 -1
  146. package/client/dist/assets/Watchlist-B8MHNsAU.js +0 -1
  147. package/client/dist/assets/index-D2RkUJIN.js +0 -178
  148. package/client/dist/assets/useAnimeInfoData-BWShDHqE.js +0 -1
  149. package/client/dist/assets/useAsmr-FyfD9G7b.js +0 -1
  150. package/client/dist/assets/useMangaLibrary-BJU5x8Vz.js +0 -1
  151. package/client/dist/assets/useProviders-DDRxa8vl.js +0 -1
  152. package/client/dist/assets/useTv-Bh6WmNnZ.js +0 -1
  153. package/client/dist/assets/useTvLibrary-_Pqm8re1.js +0 -1
  154. package/server/dist/controllers/asmr-library.controller.js +0 -288
  155. package/server/dist/controllers/auth.controller.js +0 -225
  156. package/server/dist/controllers/data.controller.js +0 -767
  157. package/server/dist/controllers/insights.controller.js +0 -203
  158. package/server/dist/controllers/manga-library.controller.js +0 -350
  159. package/server/dist/controllers/settings.controller.js +0 -609
  160. package/server/dist/controllers/tv-library.controller.js +0 -314
  161. package/server/dist/controllers/watchlist.controller.js +0 -1069
  162. package/server/dist/request-logger.js +0 -16
  163. package/server/dist/routes/asmr-library.routes.js +0 -23
  164. package/server/dist/routes/asmr.routes.js +0 -81
  165. package/server/dist/routes/auth.routes.js +0 -25
  166. package/server/dist/routes/data.routes.js +0 -47
  167. package/server/dist/routes/discord-gateway.routes.js +0 -51
  168. package/server/dist/routes/insights.routes.js +0 -35
  169. package/server/dist/routes/lan-auth.routes.js +0 -46
  170. package/server/dist/routes/manga-library.routes.js +0 -25
  171. package/server/dist/routes/manga.routes.js +0 -141
  172. package/server/dist/routes/music.routes.js +0 -20
  173. package/server/dist/routes/providers.routes.js +0 -17
  174. package/server/dist/routes/proxy.routes.js +0 -12
  175. package/server/dist/routes/settings.routes.js +0 -24
  176. package/server/dist/routes/tracker.routes.js +0 -287
  177. package/server/dist/routes/translate.routes.js +0 -88
  178. package/server/dist/routes/tv-library.routes.js +0 -23
  179. package/server/dist/routes/tv.routes.js +0 -629
  180. package/server/dist/routes/watchlist.routes.js +0 -341
@@ -1,314 +0,0 @@
1
- import logger from '../logger.js';
2
- import { performTvWriteTransaction } from '../sync.js';
3
- import { dbAll } from '../utils/db-utils.js';
4
- import { SettingsRepository } from '../repositories/settings.repository.js';
5
- import { buildTvId, normalizeTvMediaId, TV_STATUSES, TvLibraryRepository, TvProgressRepository, } from '../repositories/tv.repository.js';
6
- const isTvContinueAdult = (row) => row.adult === true || row.adult === 1;
7
- function tvDb(req) {
8
- const db = req.tvDb;
9
- if (!db || db.isClosedCheck())
10
- throw new Error('TV database is not ready');
11
- return db;
12
- }
13
- function normalizeMediaType(raw) {
14
- return String(raw).toLowerCase() === 'movie' ? 'movie' : 'tv';
15
- }
16
- export class TvLibraryController {
17
- getLibrary = async (req, res) => {
18
- try {
19
- const { status, page: pageStr, limit: limitStr } = req.query;
20
- const page = Math.max(parseInt(pageStr) || 1, 1);
21
- const limit = Math.min(Math.max(parseInt(limitStr) || 24, 1), 100);
22
- const offset = (page - 1) * limit;
23
- const db = tvDb(req);
24
- const rows = TvLibraryRepository.getAll(db, status, limit, offset);
25
- const total = TvLibraryRepository.getCount(db, status);
26
- res.json({ data: rows, total, page, limit });
27
- }
28
- catch (err) {
29
- logger.error({ err }, 'Failed to get TV library');
30
- res.status(500).json({ error: 'Failed to load TV library' });
31
- }
32
- };
33
- checkLibrary = async (req, res) => {
34
- try {
35
- const item = TvLibraryRepository.getById(tvDb(req), req.params.id);
36
- res.json({ inLibrary: !!item, status: item?.status ?? null });
37
- }
38
- catch {
39
- res.json({ inLibrary: false, status: null });
40
- }
41
- };
42
- getLibraryIds = async (req, res) => {
43
- try {
44
- res.json({ ids: TvLibraryRepository.getIds(tvDb(req)) });
45
- }
46
- catch {
47
- res.json({ ids: [] });
48
- }
49
- };
50
- addToLibrary = async (req, res) => {
51
- const { tmdbId, mediaType: mediaTypeRaw, id: idRaw, title, poster, backdrop, year, overview, status, adult, } = req.body ?? {};
52
- const tmdbIdNum = Number(tmdbId);
53
- if (!tmdbIdNum || (!idRaw && !mediaTypeRaw)) {
54
- return res.status(400).json({ error: 'tmdbId and mediaType are required' });
55
- }
56
- const mediaType = normalizeMediaType(mediaTypeRaw || (String(idRaw).startsWith('movie:') ? 'movie' : 'tv'));
57
- const id = idRaw && String(idRaw).includes(':') ? String(idRaw) : buildTvId(mediaType, tmdbIdNum);
58
- const cleanStatus = typeof status === 'string' && TV_STATUSES.includes(status) ? status : 'Watching';
59
- try {
60
- await performTvWriteTransaction(tvDb(req), (tx) => {
61
- TvLibraryRepository.upsert(tx, {
62
- id,
63
- tmdbId: tmdbIdNum,
64
- mediaType,
65
- title: String(title || ''),
66
- poster: String(poster || ''),
67
- backdrop: backdrop ? String(backdrop) : undefined,
68
- year: year ? String(year) : undefined,
69
- overview: overview ? String(overview) : undefined,
70
- status: cleanStatus,
71
- adult: adult === true,
72
- });
73
- });
74
- res.json({ success: true, id });
75
- }
76
- catch (err) {
77
- logger.error({ err }, 'Failed to add TV title to library');
78
- res.status(500).json({ error: 'Failed to add to TV watchlist' });
79
- }
80
- };
81
- removeFromLibrary = async (req, res) => {
82
- const { id } = req.body ?? {};
83
- if (!id)
84
- return res.status(400).json({ error: 'id is required' });
85
- try {
86
- await performTvWriteTransaction(tvDb(req), (tx) => {
87
- TvLibraryRepository.delete(tx, String(id));
88
- TvProgressRepository.deleteByMedia(tx, String(id));
89
- });
90
- res.json({ success: true });
91
- }
92
- catch (err) {
93
- logger.error({ err }, 'Failed to remove TV title from library');
94
- res.status(500).json({ error: 'Failed to remove from TV watchlist' });
95
- }
96
- };
97
- updateStatus = async (req, res) => {
98
- const { id, status } = req.body ?? {};
99
- if (!id || !TV_STATUSES.includes(status)) {
100
- return res.status(400).json({ error: 'id and a valid status are required' });
101
- }
102
- try {
103
- await performTvWriteTransaction(tvDb(req), (tx) => {
104
- TvLibraryRepository.updateStatus(tx, String(id), String(status));
105
- });
106
- res.json({ success: true });
107
- }
108
- catch (err) {
109
- logger.error({ err }, 'Failed to update TV status');
110
- res.status(500).json({ error: 'Failed to update status' });
111
- }
112
- };
113
- getProgress = async (req, res) => {
114
- try {
115
- const rows = TvProgressRepository.getByMedia(tvDb(req), normalizeTvMediaId(req.params.mediaId));
116
- res.json({ progress: rows });
117
- }
118
- catch {
119
- res.json({ progress: [] });
120
- }
121
- };
122
- getLatestProgress = async (req, res) => {
123
- try {
124
- const db = tvDb(req);
125
- const mediaId = normalizeTvMediaId(req.params.mediaId);
126
- const season = parseInt(req.query.season, 10);
127
- const episode = parseInt(req.query.episode, 10);
128
- if (Number.isFinite(season) && Number.isFinite(episode)) {
129
- const row = TvProgressRepository.getEpisode(db, mediaId, season, episode);
130
- res.json(row || { currentTime: 0, duration: 0, completed: 0 });
131
- return;
132
- }
133
- const row = TvProgressRepository.getLatest(db, mediaId);
134
- res.json(row || { currentTime: 0, duration: 0, completed: 0 });
135
- }
136
- catch {
137
- res.json({ currentTime: 0, duration: 0, completed: 0 });
138
- }
139
- };
140
- saveProgress = async (req, res) => {
141
- const { mediaId: mediaIdRaw, season, episode, currentTime, duration, completed, title, poster, backdrop, year, overview, tmdbId, mediaType, adult, } = req.body ?? {};
142
- const mediaId = normalizeTvMediaId(mediaIdRaw);
143
- if (!mediaId) {
144
- return res.status(400).json({ error: 'mediaId is required' });
145
- }
146
- const seasonNum = Math.max(Number(season) || 1, 1);
147
- const episodeNum = Math.max(Number(episode) || 1, 1);
148
- const timeNum = Math.max(Number(currentTime) || 0, 0);
149
- const durationNum = Math.max(Number(duration) || 0, 0);
150
- const clampedTime = durationNum > 0 ? Math.min(timeNum, durationNum) : timeNum;
151
- const storedTime = durationNum > 0 && clampedTime >= durationNum * 0.8 ? durationNum : clampedTime;
152
- const completedNow = Number(completed) === 1 || (durationNum > 0 && storedTime >= durationNum * 0.8) ? 1 : 0;
153
- try {
154
- await performTvWriteTransaction(tvDb(req), (tx) => {
155
- TvProgressRepository.upsert(tx, {
156
- mediaId: String(mediaId),
157
- season: Math.round(seasonNum),
158
- episode: Math.round(episodeNum),
159
- currentTime: storedTime,
160
- duration: durationNum,
161
- completed: completedNow,
162
- title: title ?? null,
163
- poster: poster ?? null,
164
- backdrop: backdrop ?? null,
165
- year: year ?? null,
166
- overview: overview ?? null,
167
- tmdbId: tmdbId != null ? Number(tmdbId) : null,
168
- mediaType: mediaType ?? null,
169
- adult: adult != null ? Number(adult) : null,
170
- });
171
- TvLibraryRepository.touchProgress(tx, String(mediaId), {
172
- season: Math.round(seasonNum),
173
- episode: Math.round(episodeNum),
174
- });
175
- });
176
- res.json({ success: true });
177
- }
178
- catch (err) {
179
- logger.error({ err }, 'Failed to save TV progress');
180
- res.status(500).json({ error: 'Failed to save progress' });
181
- }
182
- };
183
- getContinueWatching = async (req, res) => {
184
- try {
185
- const limit = Math.min(Math.max(parseInt(req.query.limit) || 24, 1), 100);
186
- const ignoreAdultRow = await SettingsRepository.getByKey(req.db, 'tvIgnoreAdultContent');
187
- const ignoreAdult = ignoreAdultRow ? ignoreAdultRow.value !== 'false' : true;
188
- const listOnlyRow = await SettingsRepository.getByKey(req.db, 'tvCwWatchlistOnly');
189
- const listOnly = listOnlyRow
190
- ? listOnlyRow.value === 'true' || listOnlyRow.value === '1'
191
- : false;
192
- const rows = TvProgressRepository.getContinueWatching(tvDb(req), limit).filter((row) => {
193
- if (ignoreAdult && isTvContinueAdult(row))
194
- return false;
195
- if (listOnly && row.watchlistStatus !== 'Watching')
196
- return false;
197
- return true;
198
- });
199
- res.json({ data: rows, total: rows.length });
200
- }
201
- catch {
202
- res.json({ data: [], total: 0 });
203
- }
204
- };
205
- async getAdultNonListMediaIds(db) {
206
- const rows = await dbAll(db, `SELECT DISTINCT p.mediaId as mediaId
207
- FROM tv_progress p
208
- LEFT JOIN tv_library l ON l.id = p.mediaId
209
- WHERE COALESCE(l.adult, p.adult) = 1
210
- AND l.id IS NULL`);
211
- return rows.map((r) => r.mediaId);
212
- }
213
- getAdultContinueWatchingCount = async (req, res) => {
214
- try {
215
- const ids = await this.getAdultNonListMediaIds(tvDb(req));
216
- res.json({ count: ids.length });
217
- }
218
- catch {
219
- res.json({ count: 0 });
220
- }
221
- };
222
- purgeAdultContinueWatching = async (req, res) => {
223
- try {
224
- const ids = await this.getAdultNonListMediaIds(tvDb(req));
225
- if (ids.length > 0) {
226
- await performTvWriteTransaction(tvDb(req), (tx) => {
227
- for (const id of ids)
228
- TvProgressRepository.deleteByMedia(tx, id);
229
- });
230
- }
231
- res.json({ success: true, removed: ids.length });
232
- }
233
- catch (err) {
234
- logger.error({ err }, 'Failed to purge adult TV progress');
235
- res.status(500).json({ error: 'Failed to purge adult entries' });
236
- }
237
- };
238
- removeProgress = async (req, res) => {
239
- const { mediaId: mediaIdRaw, season, episode } = req.body ?? {};
240
- const mediaId = normalizeTvMediaId(mediaIdRaw);
241
- if (!mediaId)
242
- return res.status(400).json({ error: 'mediaId is required' });
243
- try {
244
- await performTvWriteTransaction(tvDb(req), (tx) => {
245
- if (season !== undefined && episode !== undefined) {
246
- TvProgressRepository.deleteEpisode(tx, String(mediaId), Number(season), Number(episode));
247
- }
248
- else {
249
- TvProgressRepository.deleteByMedia(tx, String(mediaId));
250
- }
251
- });
252
- res.json({ success: true });
253
- }
254
- catch (err) {
255
- logger.error({ err }, 'Failed to remove TV progress');
256
- res.status(500).json({ error: 'Failed to reset progress' });
257
- }
258
- };
259
- batchUpdateStatus = async (req, res) => {
260
- const { ids: idsRaw, status } = req.body ?? {};
261
- if (!Array.isArray(idsRaw) || idsRaw.length === 0) {
262
- return res.status(400).json({ error: 'ids must be a non-empty array' });
263
- }
264
- if (!status || !TV_STATUSES.includes(status)) {
265
- return res.status(400).json({ error: 'a valid status is required' });
266
- }
267
- const ids = idsRaw.map((id) => String(id));
268
- try {
269
- await performTvWriteTransaction(tvDb(req), (tx) => {
270
- TvLibraryRepository.updateStatusMany(tx, ids, String(status));
271
- });
272
- res.json({ success: true, updated: ids.length });
273
- }
274
- catch (err) {
275
- logger.error({ err }, 'Failed to batch update TV status');
276
- res.status(500).json({ error: 'Failed to update statuses' });
277
- }
278
- };
279
- batchRemove = async (req, res) => {
280
- const { ids: idsRaw } = req.body ?? {};
281
- if (!Array.isArray(idsRaw) || idsRaw.length === 0) {
282
- return res.status(400).json({ error: 'ids must be a non-empty array' });
283
- }
284
- const ids = idsRaw.map((id) => String(id));
285
- try {
286
- await performTvWriteTransaction(tvDb(req), (tx) => {
287
- TvLibraryRepository.deleteMany(tx, ids);
288
- TvProgressRepository.deleteMany(tx, ids);
289
- });
290
- res.json({ success: true, removed: ids.length });
291
- }
292
- catch (err) {
293
- logger.error({ err }, 'Failed to batch remove TV titles');
294
- res.status(500).json({ error: 'Failed to remove from TV watchlist' });
295
- }
296
- };
297
- batchRemoveProgress = async (req, res) => {
298
- const { ids: idsRaw } = req.body ?? {};
299
- if (!Array.isArray(idsRaw) || idsRaw.length === 0) {
300
- return res.status(400).json({ error: 'ids must be a non-empty array' });
301
- }
302
- const ids = idsRaw.map((id) => normalizeTvMediaId(id));
303
- try {
304
- await performTvWriteTransaction(tvDb(req), (tx) => {
305
- TvProgressRepository.deleteMany(tx, ids);
306
- });
307
- res.json({ success: true, removed: ids.length });
308
- }
309
- catch (err) {
310
- logger.error({ err }, 'Failed to batch remove TV progress');
311
- res.status(500).json({ error: 'Failed to reset progress' });
312
- }
313
- };
314
- }