ani-web 1.5.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.

Potentially problematic release.


This version of ani-web might be problematic. Click here for more details.

Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +174 -0
  3. package/client/dist/assets/AnimeInfo-C7DQp7Oo.js +1 -0
  4. package/client/dist/assets/AnimeInfo-Sb3YiXHJ.css +1 -0
  5. package/client/dist/assets/AnimeInfoPage-DJA7AJQ8.js +2 -0
  6. package/client/dist/assets/Button-Fq9KaUOg.css +1 -0
  7. package/client/dist/assets/Button-o0l9V_NG.js +1 -0
  8. package/client/dist/assets/ErrorMessage-Ddf2zmRx.js +1 -0
  9. package/client/dist/assets/ErrorMessage-FOxXyZC9.css +1 -0
  10. package/client/dist/assets/Home-CKHJA97j.css +1 -0
  11. package/client/dist/assets/Home-Dey0azy1.js +1 -0
  12. package/client/dist/assets/Insights-BSRcCkDs.css +1 -0
  13. package/client/dist/assets/Insights-CogjPOd_.js +1 -0
  14. package/client/dist/assets/MAL-CYArH4yf.js +1 -0
  15. package/client/dist/assets/MAL-DeQNXXPx.css +1 -0
  16. package/client/dist/assets/Player-BWFN9gud.js +9 -0
  17. package/client/dist/assets/Player-CBCYW7uG.css +1 -0
  18. package/client/dist/assets/PlayerSettings-BgStUrrP.css +1 -0
  19. package/client/dist/assets/PlayerSettings-rWZuATQf.js +1 -0
  20. package/client/dist/assets/RemoveConfirmationModal-BBiogSdf.css +1 -0
  21. package/client/dist/assets/RemoveConfirmationModal-CLYqyGOv.js +1 -0
  22. package/client/dist/assets/Search-DZAWgKwq.js +1 -0
  23. package/client/dist/assets/Search-lWsVQ0Ke.css +1 -0
  24. package/client/dist/assets/Settings-Bv9fX-x3.css +1 -0
  25. package/client/dist/assets/Settings-DyisJGeD.js +1 -0
  26. package/client/dist/assets/ToggleSwitch-CLnWnAuY.js +1 -0
  27. package/client/dist/assets/ToggleSwitch-DInRb7iM.css +1 -0
  28. package/client/dist/assets/Watchlist-2dVYksxq.css +1 -0
  29. package/client/dist/assets/Watchlist-CuqJISI3.js +1 -0
  30. package/client/dist/assets/hls.light-DcbkToIY.js +27 -0
  31. package/client/dist/assets/index-BK_Zaqaw.css +1 -0
  32. package/client/dist/assets/index-CHVF4D4L.js +178 -0
  33. package/client/dist/assets/useAnimeInfoData-Cr58brCY.js +1 -0
  34. package/client/dist/assets/useIsMobile-gHo4t6g6.js +1 -0
  35. package/client/dist/assets/vendor-DdbgYKo4.js +3 -0
  36. package/client/dist/favicon.ico +0 -0
  37. package/client/dist/index.html +35 -0
  38. package/client/dist/logo.png +0 -0
  39. package/client/dist/placeholder.svg +4 -0
  40. package/client/dist/robots.txt +3 -0
  41. package/client/package.json +54 -0
  42. package/orchestrator.js +302 -0
  43. package/package.json +69 -0
  44. package/server/dist/config.js +86 -0
  45. package/server/dist/constants.json +1359 -0
  46. package/server/dist/controllers/auth.controller.js +213 -0
  47. package/server/dist/controllers/data.controller.js +126 -0
  48. package/server/dist/controllers/insights.controller.js +125 -0
  49. package/server/dist/controllers/proxy.controller.js +235 -0
  50. package/server/dist/controllers/settings.controller.js +147 -0
  51. package/server/dist/controllers/watchlist.controller.js +499 -0
  52. package/server/dist/db.js +231 -0
  53. package/server/dist/github-sync.js +279 -0
  54. package/server/dist/google.js +274 -0
  55. package/server/dist/logger.js +21 -0
  56. package/server/dist/providers/123anime.provider.js +229 -0
  57. package/server/dist/providers/allanime.provider.js +773 -0
  58. package/server/dist/providers/animepahe.provider.js +313 -0
  59. package/server/dist/providers/animeya.provider.js +799 -0
  60. package/server/dist/providers/provider.interface.js +2 -0
  61. package/server/dist/rclone.js +126 -0
  62. package/server/dist/repositories/insights.repository.js +30 -0
  63. package/server/dist/repositories/notifications.repository.js +22 -0
  64. package/server/dist/repositories/settings.repository.js +13 -0
  65. package/server/dist/repositories/shows-meta.repository.js +39 -0
  66. package/server/dist/repositories/watched-episodes.repository.js +60 -0
  67. package/server/dist/repositories/watchlist.repository.js +49 -0
  68. package/server/dist/routes/auth.routes.js +23 -0
  69. package/server/dist/routes/data.routes.js +43 -0
  70. package/server/dist/routes/insights.routes.js +11 -0
  71. package/server/dist/routes/proxy.routes.js +13 -0
  72. package/server/dist/routes/settings.routes.js +26 -0
  73. package/server/dist/routes/watchlist.routes.js +26 -0
  74. package/server/dist/server.js +179 -0
  75. package/server/dist/sync-config.js +28 -0
  76. package/server/dist/sync.js +383 -0
  77. package/server/dist/utils/db-utils.js +36 -0
  78. package/server/dist/utils/env.utils.js +70 -0
  79. package/server/package.json +54 -0
@@ -0,0 +1,213 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.AuthController = void 0;
40
+ const logger_1 = __importDefault(require("../logger"));
41
+ const google_1 = require("../google");
42
+ const github_sync_1 = require("../github-sync");
43
+ const config_1 = require("../config");
44
+ const rclone_1 = require("../rclone");
45
+ class AuthController {
46
+ runSyncSequence;
47
+ constructor(runSyncSequence) {
48
+ this.runSyncSequence = runSyncSequence;
49
+ }
50
+ getConfigStatus = (_req, res) => {
51
+ const hasConfig = !!process.env.GOOGLE_CLIENT_ID && !!process.env.GOOGLE_CLIENT_SECRET;
52
+ res.json({ hasConfig });
53
+ };
54
+ getGoogleAuthSettings = (_req, res) => {
55
+ res.json({
56
+ clientId: process.env.GOOGLE_CLIENT_ID || '',
57
+ hasClientSecret: !!process.env.GOOGLE_CLIENT_SECRET,
58
+ });
59
+ };
60
+ updateGoogleAuthSettings = async (req, res) => {
61
+ const { clientId, clientSecret } = req.body;
62
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
63
+ try {
64
+ const updates = {};
65
+ if (typeof clientId === 'string') {
66
+ updates.GOOGLE_CLIENT_ID = clientId;
67
+ }
68
+ if (typeof clientSecret === 'string') {
69
+ updates.GOOGLE_CLIENT_SECRET = clientSecret;
70
+ }
71
+ await updateEnvFile(updates);
72
+ res.json({ success: true });
73
+ }
74
+ catch (error) {
75
+ logger_1.default.error({ err: error }, 'Failed to update .env file');
76
+ res.status(500).json({ error: 'Failed to save configuration' });
77
+ }
78
+ };
79
+ getRcloneSettings = async (_req, res) => {
80
+ try {
81
+ const remotes = await rclone_1.rcloneService.listRemotes();
82
+ res.json({
83
+ remote: config_1.CONFIG.RCLONE_REMOTE || '',
84
+ availableRemotes: remotes,
85
+ activeRemote: rclone_1.rcloneService.isActive() ? rclone_1.rcloneService.getRemoteName() : null,
86
+ });
87
+ }
88
+ catch {
89
+ res.status(500).json({ error: 'Failed to fetch Rclone settings' });
90
+ }
91
+ };
92
+ getGitHubAuthStatus = async (_req, res) => {
93
+ try {
94
+ const user = await github_sync_1.githubSyncService.getUserProfile();
95
+ res.json({
96
+ authenticated: !!user,
97
+ user,
98
+ device: github_sync_1.githubSyncService.getDeviceState(),
99
+ clientId: process.env.GITHUB_CLIENT_ID || '',
100
+ usingDefaultClientId: !process.env.GITHUB_CLIENT_ID,
101
+ });
102
+ }
103
+ catch (error) {
104
+ logger_1.default.error({ err: error }, 'Failed to fetch GitHub auth status');
105
+ res.json({
106
+ authenticated: false,
107
+ user: null,
108
+ device: github_sync_1.githubSyncService.getDeviceState(),
109
+ clientId: process.env.GITHUB_CLIENT_ID || '',
110
+ usingDefaultClientId: !process.env.GITHUB_CLIENT_ID,
111
+ });
112
+ }
113
+ };
114
+ startGitHubDeviceAuth = async (req, res) => {
115
+ try {
116
+ const state = await github_sync_1.githubSyncService.startDeviceAuth(req.db, this.runSyncSequence);
117
+ res.json(state);
118
+ }
119
+ catch (error) {
120
+ logger_1.default.error({ err: error }, 'Failed to start GitHub device auth');
121
+ res.status(500).json({ error: 'Failed to start GitHub authentication' });
122
+ }
123
+ };
124
+ pollGitHubDeviceAuth = (_req, res) => {
125
+ res.json(github_sync_1.githubSyncService.getDeviceState());
126
+ };
127
+ logoutGitHub = async (_req, res) => {
128
+ try {
129
+ await github_sync_1.githubSyncService.logout();
130
+ res.json({ success: true });
131
+ }
132
+ catch (error) {
133
+ logger_1.default.error({ err: error }, 'Failed to sign out of GitHub');
134
+ res.status(500).json({ error: 'Failed to sign out of GitHub' });
135
+ }
136
+ };
137
+ updateRcloneSettings = async (req, res) => {
138
+ const { remote } = req.body;
139
+ const { updateEnvFile } = await Promise.resolve().then(() => __importStar(require('../utils/env.utils')));
140
+ try {
141
+ await updateEnvFile({
142
+ RCLONE_REMOTE: remote,
143
+ });
144
+ res.json({ success: true });
145
+ }
146
+ catch (error) {
147
+ logger_1.default.error({ err: error }, 'Failed to update .env for Rclone');
148
+ res.status(500).json({ error: 'Failed to save Rclone configuration' });
149
+ }
150
+ };
151
+ getAuthUrl = (_req, res) => {
152
+ try {
153
+ const url = google_1.googleDriveService.getAuthUrl();
154
+ res.json({ url });
155
+ }
156
+ catch (error) {
157
+ logger_1.default.error({ err: error }, 'Failed to generate auth URL');
158
+ res.status(500).json({ error: 'Auth configuration error' });
159
+ }
160
+ };
161
+ handleCallback = async (req, res) => {
162
+ const code = req.query.code;
163
+ if (!code) {
164
+ return res.status(400).send('No code provided');
165
+ }
166
+ try {
167
+ await google_1.googleDriveService.handleCallback(code);
168
+ const user = await google_1.googleDriveService.getUserProfile();
169
+ logger_1.default.info('User logged in. Syncing database (please wait)...');
170
+ try {
171
+ await this.runSyncSequence(req.db);
172
+ }
173
+ catch (err) {
174
+ logger_1.default.error({ err }, 'Post-login sync failed');
175
+ }
176
+ const responseHtml = `
177
+ <html>
178
+ <body>
179
+ <h1>Authentication Successful</h1>
180
+ <p>Database synced. Closing window...</p>
181
+ <script>
182
+ if (window.opener) {
183
+ window.opener.postMessage({ type: 'GOOGLE_AUTH_SUCCESS', user: ${JSON.stringify(user)} }, '*');
184
+ window.close();
185
+ } else {
186
+ window.location.href = '/';
187
+ }
188
+ </script>
189
+ </body>
190
+ </html>
191
+ `;
192
+ res.send(responseHtml);
193
+ }
194
+ catch (error) {
195
+ logger_1.default.error({ err: error }, 'Auth callback failed');
196
+ res.status(500).send('Authentication failed');
197
+ }
198
+ };
199
+ getUserProfile = async (_req, res) => {
200
+ try {
201
+ const user = await google_1.googleDriveService.getUserProfile();
202
+ res.json(user);
203
+ }
204
+ catch (error) {
205
+ res.json(null);
206
+ }
207
+ };
208
+ logout = async (_req, res) => {
209
+ await google_1.googleDriveService.logout();
210
+ res.json({ success: true });
211
+ };
212
+ }
213
+ exports.AuthController = AuthController;
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.DataController = void 0;
7
+ const constants_json_1 = require("../constants.json");
8
+ const logger_1 = __importDefault(require("../logger"));
9
+ class DataController {
10
+ providers;
11
+ constructor(providers) {
12
+ this.providers = providers;
13
+ }
14
+ getProvider(req) {
15
+ const providerName = req.query.provider || 'allanime';
16
+ return this.providers[providerName.toLowerCase()] || this.providers['allanime'];
17
+ }
18
+ getPopular = async (req, res) => {
19
+ const timeframe = req.params.timeframe.toLowerCase();
20
+ try {
21
+ const data = await this.getProvider(req).getPopular(timeframe);
22
+ res.set('Cache-Control', 'public, max-age=300').json(data);
23
+ }
24
+ catch {
25
+ res.status(500).send('Error');
26
+ }
27
+ };
28
+ getSchedule = async (req, res) => {
29
+ try {
30
+ const data = await this.getProvider(req).getSchedule(new Date(req.params.date + 'T00:00:00.000Z'));
31
+ res.set('Cache-Control', 'public, max-age=300').json(data);
32
+ }
33
+ catch {
34
+ res.status(500).send('Error');
35
+ }
36
+ };
37
+ getSkipTimes = async (req, res) => {
38
+ try {
39
+ const data = await this.getProvider(req).getSkipTimes(req.params.showId, req.params.episodeNumber);
40
+ res.json(data);
41
+ }
42
+ catch {
43
+ res.json({ found: false, results: [] });
44
+ }
45
+ };
46
+ getVideo = async (req, res) => {
47
+ try {
48
+ const urls = await this.getProvider(req).getStreamUrls(req.query.showId, req.query.episodeNumber, req.query.mode);
49
+ res.json(urls || []);
50
+ }
51
+ catch (e) {
52
+ // Return empty array instead of 500 so frontend can stay functional
53
+ // and allow provider switching.
54
+ logger_1.default.error({ err: e, provider: req.query.provider }, 'Provider video fetch failed');
55
+ res.json([]);
56
+ }
57
+ };
58
+ getEpisodes = async (req, res) => {
59
+ try {
60
+ res.json(await this.getProvider(req).getEpisodes(req.query.showId, req.query.mode));
61
+ }
62
+ catch {
63
+ res.status(500).send('Error');
64
+ }
65
+ };
66
+ search = async (req, res) => {
67
+ try {
68
+ res.json(await this.getProvider(req).search(req.query));
69
+ }
70
+ catch {
71
+ res.status(500).send('Error');
72
+ }
73
+ };
74
+ getSeasonal = async (req, res) => {
75
+ try {
76
+ const page = parseInt(req.query.page) || 1;
77
+ res.json(await this.getProvider(req).getSeasonal(page));
78
+ }
79
+ catch {
80
+ res.status(500).send('Error');
81
+ }
82
+ };
83
+ getLatestReleases = async (req, res) => {
84
+ try {
85
+ res.json(await this.getProvider(req).getLatestReleases());
86
+ }
87
+ catch {
88
+ res.status(500).send('Error');
89
+ }
90
+ };
91
+ getShowMeta = async (req, res) => {
92
+ try {
93
+ const [meta, details] = await Promise.all([
94
+ this.getProvider(req).getShowMeta(req.params.id),
95
+ this.getProvider(req)
96
+ .getShowDetails(req.params.id)
97
+ .catch(() => ({})),
98
+ ]);
99
+ const merged = { ...meta, ...details };
100
+ res.json(merged);
101
+ }
102
+ catch {
103
+ res.status(500).send('Error');
104
+ }
105
+ };
106
+ getShowDetails = async (req, res) => {
107
+ try {
108
+ res.json(await this.getProvider(req).getShowDetails(req.params.id));
109
+ }
110
+ catch {
111
+ res.status(404).send('Not found');
112
+ }
113
+ };
114
+ getAllmangaDetails = async (req, res) => {
115
+ try {
116
+ res.json(await this.getProvider(req).getAllmangaDetails(req.params.id));
117
+ }
118
+ catch {
119
+ res.status(500).send('Error');
120
+ }
121
+ };
122
+ getGenresAndTags = (_req, res) => {
123
+ res.json({ genres: constants_json_1.genres, tags: constants_json_1.tags, studios: constants_json_1.studios });
124
+ };
125
+ }
126
+ exports.DataController = DataController;
@@ -0,0 +1,125 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.InsightsController = void 0;
7
+ const logger_1 = __importDefault(require("../logger"));
8
+ const insights_repository_1 = require("../repositories/insights.repository");
9
+ class InsightsController {
10
+ provider;
11
+ constructor(provider) {
12
+ this.provider = provider;
13
+ }
14
+ getWatchInsights = async (req, res) => {
15
+ const db = req.db;
16
+ try {
17
+ const [core, activityGrid, hourlyDist, seasonality, allWatches, watchedShows, droppedWarning, velocities,] = (await Promise.all([
18
+ insights_repository_1.InsightsRepository.getCoreStats(db),
19
+ insights_repository_1.InsightsRepository.getActivityGrid(db),
20
+ insights_repository_1.InsightsRepository.getHourlyDist(db),
21
+ insights_repository_1.InsightsRepository.getSeasonality(db),
22
+ insights_repository_1.InsightsRepository.getAllWatches(db),
23
+ insights_repository_1.InsightsRepository.getWatchedShowsMeta(db),
24
+ insights_repository_1.InsightsRepository.getDroppedShows(db),
25
+ insights_repository_1.InsightsRepository.getCompletionVelocities(db),
26
+ ]));
27
+ const bingeFactor = activityGrid.length > 0 ? Math.max(...activityGrid.map((a) => a.count)) : 0;
28
+ const sessions = [];
29
+ if (allWatches.length > 0) {
30
+ let currentSessionSeconds = allWatches[0].currentTime;
31
+ for (let i = 1; i < allWatches.length; i++) {
32
+ const prev = new Date(allWatches[i - 1].watchedAt).getTime();
33
+ const curr = new Date(allWatches[i].watchedAt).getTime();
34
+ if (curr - prev < 3600000) {
35
+ currentSessionSeconds += allWatches[i].currentTime;
36
+ }
37
+ else {
38
+ sessions.push(currentSessionSeconds);
39
+ currentSessionSeconds = allWatches[i].currentTime;
40
+ }
41
+ }
42
+ sessions.push(currentSessionSeconds);
43
+ }
44
+ const avgSessionMinutes = sessions.length > 0
45
+ ? Math.round(sessions.reduce((a, b) => a + b, 0) / sessions.length / 60)
46
+ : 0;
47
+ const genreCounts = {};
48
+ let totalPopScore = 0;
49
+ let popCount = 0;
50
+ for (const show of watchedShows) {
51
+ let genres = [];
52
+ if (show.genres) {
53
+ try {
54
+ if (show.genres.startsWith('[')) {
55
+ genres = JSON.parse(show.genres);
56
+ }
57
+ else {
58
+ genres = show.genres.split(',').map((g) => g.trim());
59
+ }
60
+ }
61
+ catch (e) {
62
+ logger_1.default.warn({ err: e, showId: show.id }, 'Failed to parse genres for insights');
63
+ }
64
+ }
65
+ for (const g of genres) {
66
+ genreCounts[g] = (genreCounts[g] || 0) + 1;
67
+ }
68
+ if (show.popularityScore) {
69
+ totalPopScore += show.popularityScore;
70
+ popCount++;
71
+ }
72
+ }
73
+ const topGenre = Object.entries(genreCounts).sort((a, b) => b[1] - a[1])[0]?.[0];
74
+ const personaMap = {
75
+ Action: 'Shonen Warrior',
76
+ Romance: 'Hopeless Romantic',
77
+ Comedy: 'Chaos Enjoyer',
78
+ 'Slice of Life': 'Vibe Seeker',
79
+ Horror: 'Fearless Watcher',
80
+ Fantasy: 'Isekai Traveller',
81
+ 'Sci-Fi': 'Future Scientist',
82
+ Drama: 'Feels Collector',
83
+ };
84
+ const persona = personaMap[topGenre || ''] || 'Anime Enthusiast';
85
+ const avgCompletionDays = velocities.length > 0
86
+ ? Math.round(velocities.reduce((a, b) => a + b.daysToFinish, 0) / velocities.length)
87
+ : 0;
88
+ res.json({
89
+ totalHours: Math.round((core?.totalSeconds || 0) / 3600),
90
+ totalEpisodes: core?.totalEpisodes || 0,
91
+ completedAnime: core?.completedCount || 0,
92
+ completionRate: core?.totalWatchlist > 0
93
+ ? Math.round((core.completedCount / core.totalWatchlist) * 100)
94
+ : 0,
95
+ persona,
96
+ bingeFactor,
97
+ avgSessionMinutes,
98
+ avgCompletionDays,
99
+ popularityScore: popCount > 0 ? Math.round(totalPopScore / popCount) : 0,
100
+ genreSplit: Object.entries(genreCounts)
101
+ .map(([name, count]) => ({ name, count }))
102
+ .sort((a, b) => b.count - a.count)
103
+ .slice(0, 8) || [],
104
+ activityGrid: activityGrid || [],
105
+ hourlyDist: Array.from({ length: 24 }, (_, i) => {
106
+ const hour = i.toString().padStart(2, '0');
107
+ return { hour, count: hourlyDist?.find((d) => d.hour === hour)?.count || 0 };
108
+ }) || [],
109
+ seasonality: Array.from({ length: 12 }, (_, i) => {
110
+ const month = (i + 1).toString().padStart(2, '0');
111
+ return {
112
+ month,
113
+ seconds: seasonality?.find((s) => s.month === month)?.seconds || 0,
114
+ };
115
+ }) || [],
116
+ droppedShows: (droppedWarning || []).slice(0, 5),
117
+ });
118
+ }
119
+ catch (error) {
120
+ logger_1.default.error({ err: error }, 'Failed to fetch expanded insights');
121
+ res.status(500).json({ error: 'Failed to fetch insights' });
122
+ }
123
+ };
124
+ }
125
+ exports.InsightsController = InsightsController;