diff --git a/admin-panel/src/App.tsx b/admin-panel/src/App.tsx index 1d1ab45..7089116 100644 --- a/admin-panel/src/App.tsx +++ b/admin-panel/src/App.tsx @@ -1,4 +1,5 @@ -import React, { useState, useEffect } from "react"; +import type React from "react"; +import { useEffect, useState } from "react"; interface Feed { id: string; diff --git a/admin-panel/vite.config.ts b/admin-panel/vite.config.ts index 87fcb4b..6542018 100644 --- a/admin-panel/vite.config.ts +++ b/admin-panel/vite.config.ts @@ -1,5 +1,5 @@ -import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; +import { defineConfig } from "vite"; export default defineConfig({ plugins: [react()], diff --git a/admin-server.ts b/admin-server.ts index 0230a65..1e6fbe4 100644 --- a/admin-server.ts +++ b/admin-server.ts @@ -1,22 +1,22 @@ -import { Hono } from "hono"; -import { serve } from "@hono/node-server"; -import { basicAuth } from "hono/basic-auth"; +import { Database } from "bun:sqlite"; import path from "path"; +import { serve } from "@hono/node-server"; +import { Hono } from "hono"; +import { basicAuth } from "hono/basic-auth"; +import { addNewFeedUrl, batchProcess } from "./scripts/fetch_and_generate.js"; +import { batchScheduler } from "./services/batch-scheduler.js"; import { config, validateConfig } from "./services/config.js"; import { - getAllFeedsIncludingInactive, deleteFeed, - toggleFeedActive, - getFeedByUrl, - getFeedById, fetchAllEpisodes, fetchEpisodesWithArticles, + getAllFeedsIncludingInactive, + getFeedById, + getFeedByUrl, getFeedRequests, + toggleFeedActive, updateFeedRequestStatus, } from "./services/database.js"; -import { Database } from "bun:sqlite"; -import { batchProcess, addNewFeedUrl } from "./scripts/fetch_and_generate.js"; -import { batchScheduler } from "./services/batch-scheduler.js"; // Validate configuration on startup try { diff --git a/bun.lock b/bun.lock index 7185d96..02fc308 100644 --- a/bun.lock +++ b/bun.lock @@ -13,6 +13,7 @@ "openai": "^4.104.0", "react": "^19.1.0", "react-dom": "^19.1.0", + "react-helmet-async": "^2.0.5", "react-router-dom": "^7.6.2", "rss-parser": "^3.13.0", "xml2js": "^0.6.2", @@ -478,12 +479,16 @@ "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="], + "invariant": ["invariant@2.2.4", "", { "dependencies": { "loose-envify": "^1.0.0" } }, "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA=="], + "js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="], "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="], "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="], + "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": { "loose-envify": "cli.js" } }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="], + "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], @@ -526,6 +531,10 @@ "react-dom": ["react-dom@19.1.0", "", { "dependencies": { "scheduler": "^0.26.0" }, "peerDependencies": { "react": "^19.1.0" } }, "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g=="], + "react-fast-compare": ["react-fast-compare@3.2.2", "", {}, "sha512-nsO+KSNgo1SbJqJEYRE9ERzo7YtYbou/OqjSQKxV7jcKox7+usiUVZOAC+XnDOABXggQTno0Y1CpVnuWEc1boQ=="], + + "react-helmet-async": ["react-helmet-async@2.0.5", "", { "dependencies": { "invariant": "^2.2.4", "react-fast-compare": "^3.2.2", "shallowequal": "^1.1.0" }, "peerDependencies": { "react": "^16.6.0 || ^17.0.0 || ^18.0.0" } }, "sha512-rYUYHeus+i27MvFE+Jaa4WsyBKGkL6qVgbJvSBoX8mbsWoABJXdEO0bZyi0F6i+4f0NuIb8AvqPMj3iXFHkMwg=="], + "react-refresh": ["react-refresh@0.17.0", "", {}, "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ=="], "react-router": ["react-router@7.6.2", "", { "dependencies": { "cookie": "^1.0.1", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "react": ">=18", "react-dom": ">=18" }, "optionalPeers": ["react-dom"] }, "sha512-U7Nv3y+bMimgWjhlT5CRdzHPu2/KVmqPwKUCChW8en5P3znxUqwlYFlbmyj8Rgp1SF6zs5X4+77kBVknkg6a0w=="], @@ -550,6 +559,8 @@ "set-cookie-parser": ["set-cookie-parser@2.7.1", "", {}, "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ=="], + "shallowequal": ["shallowequal@1.1.0", "", {}, "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="], + "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], "string_decoder": ["string_decoder@1.3.0", "", { "dependencies": { "safe-buffer": "~5.2.0" } }, "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA=="], diff --git a/frontend/eslint.config.js b/frontend/eslint.config.js index 79a552e..f6acaea 100644 --- a/frontend/eslint.config.js +++ b/frontend/eslint.config.js @@ -1,7 +1,7 @@ import js from "@eslint/js"; -import globals from "globals"; import reactHooks from "eslint-plugin-react-hooks"; import reactRefresh from "eslint-plugin-react-refresh"; +import globals from "globals"; import tseslint from "typescript-eslint"; export default tseslint.config( diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 0f91326..fd319e2 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -1,9 +1,9 @@ -import { Routes, Route, Link, useLocation } from "react-router-dom"; -import EpisodeList from "./components/EpisodeList"; -import FeedManager from "./components/FeedManager"; -import FeedList from "./components/FeedList"; -import FeedDetail from "./components/FeedDetail"; +import { Link, Route, Routes, useLocation } from "react-router-dom"; import EpisodeDetail from "./components/EpisodeDetail"; +import EpisodeList from "./components/EpisodeList"; +import FeedDetail from "./components/FeedDetail"; +import FeedList from "./components/FeedList"; +import FeedManager from "./components/FeedManager"; function App() { const location = useLocation(); diff --git a/frontend/src/components/EpisodeDetail.tsx b/frontend/src/components/EpisodeDetail.tsx index d9400da..168006d 100644 --- a/frontend/src/components/EpisodeDetail.tsx +++ b/frontend/src/components/EpisodeDetail.tsx @@ -1,5 +1,6 @@ -import { useState, useEffect } from "react"; -import { useParams, Link } from "react-router-dom"; +import { useEffect, useState } from "react"; +import { Helmet } from "react-helmet-async"; +import { Link, useParams } from "react-router-dom"; interface EpisodeWithFeedInfo { id: string; @@ -162,6 +163,66 @@ function EpisodeDetail() { return (