From 9b189630413a536ac9d2e7a941704b85f3b6ef28 Mon Sep 17 00:00:00 2001 From: Satsuki Akiba Date: Sat, 7 Jun 2025 13:58:08 +0900 Subject: [PATCH] Fix database conflict and update schema --- admin-server.ts | 87 +++++++++++ frontend/src/App.tsx | 2 +- frontend/src/components/FeedManager.tsx | 197 ++++++------------------ schema.sql | 15 ++ server.ts | 74 ++------- services/database.ts | 99 +++++++++++- 6 files changed, 260 insertions(+), 214 deletions(-) diff --git a/admin-server.ts b/admin-server.ts index 91323d2..a457c81 100644 --- a/admin-server.ts +++ b/admin-server.ts @@ -10,6 +10,8 @@ import { getFeedByUrl, fetchAllEpisodes, fetchEpisodesWithArticles, + getFeedRequests, + updateFeedRequestStatus, } from "./services/database.js"; import { batchProcess, addNewFeedUrl } from "./scripts/fetch_and_generate.js"; @@ -200,17 +202,102 @@ app.get("/api/admin/episodes/simple", async (c) => { } }); +// Feed requests management +app.get("/api/admin/feed-requests", async (c) => { + try { + const status = c.req.query("status"); + const requests = await getFeedRequests(status); + return c.json(requests); + } catch (error) { + console.error("Error fetching feed requests:", error); + return c.json({ error: "Failed to fetch feed requests" }, 500); + } +}); + +app.patch("/api/admin/feed-requests/:id/approve", async (c) => { + try { + const requestId = c.req.param("id"); + const body = await c.req.json(); + const { adminNotes } = body; + + // First get the request to get the URL + const requests = await getFeedRequests(); + const request = requests.find(r => r.id === requestId); + + if (!request) { + return c.json({ error: "Feed request not found" }, 404); + } + + if (request.status !== 'pending') { + return c.json({ error: "Feed request already processed" }, 400); + } + + // Add the feed + await addNewFeedUrl(request.url); + + // Update request status + const updated = await updateFeedRequestStatus( + requestId, + 'approved', + 'admin', + adminNotes + ); + + if (!updated) { + return c.json({ error: "Failed to update request status" }, 500); + } + + return c.json({ + success: true, + message: "Feed request approved and feed added successfully" + }); + } catch (error) { + console.error("Error approving feed request:", error); + return c.json({ error: "Failed to approve feed request" }, 500); + } +}); + +app.patch("/api/admin/feed-requests/:id/reject", async (c) => { + try { + const requestId = c.req.param("id"); + const body = await c.req.json(); + const { adminNotes } = body; + + const updated = await updateFeedRequestStatus( + requestId, + 'rejected', + 'admin', + adminNotes + ); + + if (!updated) { + return c.json({ error: "Feed request not found" }, 404); + } + + return c.json({ + success: true, + message: "Feed request rejected successfully" + }); + } catch (error) { + console.error("Error rejecting feed request:", error); + return c.json({ error: "Failed to reject feed request" }, 500); + } +}); + // System management app.get("/api/admin/stats", async (c) => { try { const feeds = await getAllFeedsIncludingInactive(); const episodes = await fetchAllEpisodes(); + const feedRequests = await getFeedRequests(); const stats = { totalFeeds: feeds.length, activeFeeds: feeds.filter(f => f.active).length, inactiveFeeds: feeds.filter(f => !f.active).length, totalEpisodes: episodes.length, + pendingRequests: feedRequests.filter(r => r.status === 'pending').length, + totalRequests: feedRequests.length, lastUpdated: new Date().toISOString(), adminPort: config.admin.port, authEnabled: !!(config.admin.username && config.admin.password), diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index ef21092..f517e24 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,7 +23,7 @@ function App() { className={`tab ${activeTab === 'feeds' ? 'active' : ''}`} onClick={() => setActiveTab('feeds')} > - フィード管理 + フィードリクエスト diff --git a/frontend/src/components/FeedManager.tsx b/frontend/src/components/FeedManager.tsx index 3c14959..b14c8e4 100644 --- a/frontend/src/components/FeedManager.tsx +++ b/frontend/src/components/FeedManager.tsx @@ -1,141 +1,61 @@ -import { useState, useEffect } from 'react' - -interface Feed { - id: string - url: string - title?: string - description?: string - active: boolean - lastUpdated?: string -} +import { useState } from 'react' function FeedManager() { - const [feeds, setFeeds] = useState([]) - const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [success, setSuccess] = useState(null) const [newFeedUrl, setNewFeedUrl] = useState('') - const [adding, setAdding] = useState(false) + const [requestMessage, setRequestMessage] = useState('') + const [requesting, setRequesting] = useState(false) - useEffect(() => { - fetchFeeds() - }, []) - - const fetchFeeds = async () => { - try { - setLoading(true) - const response = await fetch('/api/feeds') - if (!response.ok) throw new Error('フィードの取得に失敗しました') - const data = await response.json() - setFeeds(data) - } catch (err) { - setError(err instanceof Error ? err.message : 'エラーが発生しました') - } finally { - setLoading(false) - } - } - - const addFeed = async (e: React.FormEvent) => { + const submitRequest = async (e: React.FormEvent) => { e.preventDefault() if (!newFeedUrl.trim()) return try { - setAdding(true) + setRequesting(true) setError(null) setSuccess(null) - const response = await fetch('/api/feeds', { + const response = await fetch('/api/feed-requests', { method: 'POST', headers: { 'Content-Type': 'application/json', }, - body: JSON.stringify({ url: newFeedUrl.trim() }), + body: JSON.stringify({ + url: newFeedUrl.trim(), + requestMessage: requestMessage.trim() || undefined + }), }) if (!response.ok) { const errorData = await response.json() - throw new Error(errorData.error || 'フィードの追加に失敗しました') + throw new Error(errorData.error || 'リクエストの送信に失敗しました') } - setSuccess('フィードを追加しました') + setSuccess('フィードリクエストを送信しました。管理者の承認をお待ちください。') setNewFeedUrl('') - await fetchFeeds() + setRequestMessage('') } catch (err) { setError(err instanceof Error ? err.message : 'エラーが発生しました') } finally { - setAdding(false) + setRequesting(false) } } - const deleteFeed = async (feedId: string) => { - if (!confirm('このフィードを削除しますか?関連するエピソードも削除されます。')) { - return - } - - try { - setError(null) - setSuccess(null) - - const response = await fetch(`/api/feeds/${feedId}`, { - method: 'DELETE', - }) - - if (!response.ok) { - const errorData = await response.json() - throw new Error(errorData.error || 'フィードの削除に失敗しました') - } - - setSuccess('フィードを削除しました') - await fetchFeeds() - } catch (err) { - setError(err instanceof Error ? err.message : 'エラーが発生しました') - } - } - - const toggleFeed = async (feedId: string, active: boolean) => { - try { - setError(null) - setSuccess(null) - - const response = await fetch(`/api/feeds/${feedId}/toggle`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ active }), - }) - - if (!response.ok) { - const errorData = await response.json() - throw new Error(errorData.error || 'フィードの状態変更に失敗しました') - } - - setSuccess(`フィードを${active ? '有効' : '無効'}にしました`) - await fetchFeeds() - } catch (err) { - setError(err instanceof Error ? err.message : 'エラーが発生しました') - } - } - - const formatDate = (dateString?: string) => { - if (!dateString) return '未更新' - return new Date(dateString).toLocaleString('ja-JP') - } - - if (loading) { - return
読み込み中...
- } - return (
{error &&
{error}
} {success &&
{success}
}
-

新しいフィードを追加

-
+

新しいフィードをリクエスト

+

+ 追加したいRSSフィードのURLを送信してください。管理者が承認後、フィードが追加されます。 +

+ +
- +
+ +
+ +