Fix database conflict and update schema
This commit is contained in:
		@@ -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),
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ function App() {
 | 
			
		||||
          className={`tab ${activeTab === 'feeds' ? 'active' : ''}`}
 | 
			
		||||
          onClick={() => setActiveTab('feeds')}
 | 
			
		||||
        >
 | 
			
		||||
          フィード管理
 | 
			
		||||
          フィードリクエスト
 | 
			
		||||
        </button>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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<Feed[]>([])
 | 
			
		||||
  const [loading, setLoading] = useState(true)
 | 
			
		||||
  const [error, setError] = useState<string | null>(null)
 | 
			
		||||
  const [success, setSuccess] = useState<string | null>(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 <div className="loading">読み込み中...</div>
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <div>
 | 
			
		||||
      {error && <div className="error">{error}</div>}
 | 
			
		||||
      {success && <div className="success">{success}</div>}
 | 
			
		||||
 | 
			
		||||
      <div style={{ marginBottom: '30px' }}>
 | 
			
		||||
        <h2>新しいフィードを追加</h2>
 | 
			
		||||
        <form onSubmit={addFeed}>
 | 
			
		||||
        <h2>新しいフィードをリクエスト</h2>
 | 
			
		||||
        <p style={{ color: '#666', marginBottom: '20px' }}>
 | 
			
		||||
          追加したいRSSフィードのURLを送信してください。管理者が承認後、フィードが追加されます。
 | 
			
		||||
        </p>
 | 
			
		||||
        
 | 
			
		||||
        <form onSubmit={submitRequest}>
 | 
			
		||||
          <div className="form-group">
 | 
			
		||||
            <label className="form-label">RSS フィード URL</label>
 | 
			
		||||
            <label className="form-label">RSS フィード URL *</label>
 | 
			
		||||
            <input
 | 
			
		||||
              type="url"
 | 
			
		||||
              className="form-input"
 | 
			
		||||
@@ -145,66 +65,37 @@ function FeedManager() {
 | 
			
		||||
              required
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          
 | 
			
		||||
          <div className="form-group">
 | 
			
		||||
            <label className="form-label">メッセージ(任意)</label>
 | 
			
		||||
            <textarea
 | 
			
		||||
              className="form-input"
 | 
			
		||||
              value={requestMessage}
 | 
			
		||||
              onChange={(e) => setRequestMessage(e.target.value)}
 | 
			
		||||
              placeholder="このフィードについての説明や追加理由があれば記載してください"
 | 
			
		||||
              rows={3}
 | 
			
		||||
              style={{ resize: 'vertical', minHeight: '80px' }}
 | 
			
		||||
            />
 | 
			
		||||
          </div>
 | 
			
		||||
          
 | 
			
		||||
          <button 
 | 
			
		||||
            type="submit" 
 | 
			
		||||
            className="btn btn-primary"
 | 
			
		||||
            disabled={adding}
 | 
			
		||||
            disabled={requesting}
 | 
			
		||||
          >
 | 
			
		||||
            {adding ? '追加中...' : 'フィードを追加'}
 | 
			
		||||
            {requesting ? 'リクエスト送信中...' : 'フィードをリクエスト'}
 | 
			
		||||
          </button>
 | 
			
		||||
        </form>
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      <div>
 | 
			
		||||
        <h2>登録済みフィード ({feeds.length}件)</h2>
 | 
			
		||||
        
 | 
			
		||||
        {feeds.length === 0 ? (
 | 
			
		||||
          <div className="empty-state">
 | 
			
		||||
            <p>登録されているフィードがありません</p>
 | 
			
		||||
          </div>
 | 
			
		||||
        ) : (
 | 
			
		||||
          <div>
 | 
			
		||||
            {feeds.map((feed) => (
 | 
			
		||||
              <div key={feed.id} className="feed-item">
 | 
			
		||||
                <div className="feed-info">
 | 
			
		||||
                  <div className="feed-title">
 | 
			
		||||
                    {feed.title || 'タイトル不明'}
 | 
			
		||||
                    {!feed.active && (
 | 
			
		||||
                      <span style={{ 
 | 
			
		||||
                        marginLeft: '8px', 
 | 
			
		||||
                        padding: '2px 6px', 
 | 
			
		||||
                        backgroundColor: '#dc3545', 
 | 
			
		||||
                        color: 'white', 
 | 
			
		||||
                        fontSize: '12px', 
 | 
			
		||||
                        borderRadius: '3px' 
 | 
			
		||||
                      }}>
 | 
			
		||||
                        無効
 | 
			
		||||
                      </span>
 | 
			
		||||
                    )}
 | 
			
		||||
                  </div>
 | 
			
		||||
                  <div className="feed-url">{feed.url}</div>
 | 
			
		||||
                  <div style={{ fontSize: '12px', color: '#888', marginTop: '4px' }}>
 | 
			
		||||
                    最終更新: {formatDate(feed.lastUpdated)}
 | 
			
		||||
                  </div>
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="feed-actions">
 | 
			
		||||
                  <button
 | 
			
		||||
                    className={`btn ${feed.active ? 'btn-secondary' : 'btn-primary'}`}
 | 
			
		||||
                    onClick={() => toggleFeed(feed.id, !feed.active)}
 | 
			
		||||
                  >
 | 
			
		||||
                    {feed.active ? '無効化' : '有効化'}
 | 
			
		||||
                  </button>
 | 
			
		||||
                  <button
 | 
			
		||||
                    className="btn btn-danger"
 | 
			
		||||
                    onClick={() => deleteFeed(feed.id)}
 | 
			
		||||
                  >
 | 
			
		||||
                    削除
 | 
			
		||||
                  </button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            ))}
 | 
			
		||||
          </div>
 | 
			
		||||
        )}
 | 
			
		||||
      <div style={{ backgroundColor: '#f8f9fa', padding: '20px', borderRadius: '8px' }}>
 | 
			
		||||
        <h3 style={{ marginBottom: '15px' }}>フィードリクエストについて</h3>
 | 
			
		||||
        <ul style={{ paddingLeft: '20px', color: '#666' }}>
 | 
			
		||||
          <li>送信されたフィードリクエストは管理者が確認します</li>
 | 
			
		||||
          <li>適切なRSSフィードと判断された場合、承認されて自動的に追加されます</li>
 | 
			
		||||
          <li>承認までにお時間をいただく場合があります</li>
 | 
			
		||||
          <li>不適切なフィードや重複フィードは拒否される場合があります</li>
 | 
			
		||||
        </ul>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  )
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										15
									
								
								schema.sql
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								schema.sql
									
									
									
									
									
								
							@@ -53,6 +53,19 @@ CREATE TABLE IF NOT EXISTS tts_queue (
 | 
			
		||||
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'failed'))
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- Feed requests from users
 | 
			
		||||
CREATE TABLE IF NOT EXISTS feed_requests (
 | 
			
		||||
  id TEXT PRIMARY KEY,
 | 
			
		||||
  url TEXT NOT NULL,
 | 
			
		||||
  requested_by TEXT,
 | 
			
		||||
  request_message TEXT,
 | 
			
		||||
  status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
 | 
			
		||||
  created_at TEXT NOT NULL,
 | 
			
		||||
  reviewed_at TEXT,
 | 
			
		||||
  reviewed_by TEXT,
 | 
			
		||||
  admin_notes TEXT
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
-- Create indexes for better performance
 | 
			
		||||
CREATE INDEX IF NOT EXISTS idx_articles_feed_id ON articles(feed_id);
 | 
			
		||||
CREATE INDEX IF NOT EXISTS idx_articles_pub_date ON articles(pub_date);
 | 
			
		||||
@@ -61,3 +74,5 @@ CREATE INDEX IF NOT EXISTS idx_episodes_article_id ON episodes(article_id);
 | 
			
		||||
CREATE INDEX IF NOT EXISTS idx_feeds_active ON feeds(active);
 | 
			
		||||
CREATE INDEX IF NOT EXISTS idx_tts_queue_status ON tts_queue(status);
 | 
			
		||||
CREATE INDEX IF NOT EXISTS idx_tts_queue_created_at ON tts_queue(created_at);
 | 
			
		||||
CREATE INDEX IF NOT EXISTS idx_feed_requests_status ON feed_requests(status);
 | 
			
		||||
CREATE INDEX IF NOT EXISTS idx_feed_requests_created_at ON feed_requests(created_at);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										74
									
								
								server.ts
									
									
									
									
									
								
							
							
						
						
									
										74
									
								
								server.ts
									
									
									
									
									
								
							@@ -119,73 +119,29 @@ app.get("/api/episodes", async (c) => {
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.get("/api/feeds", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const { getAllFeedsIncludingInactive } = await import("./services/database.js");
 | 
			
		||||
    const feeds = await getAllFeedsIncludingInactive();
 | 
			
		||||
    return c.json(feeds);
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error fetching feeds:", error);
 | 
			
		||||
    return c.json({ error: "Failed to fetch feeds" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.post("/api/feeds", async (c) => {
 | 
			
		||||
app.post("/api/feed-requests", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const body = await c.req.json();
 | 
			
		||||
    const { url } = body;
 | 
			
		||||
    const { url, requestMessage } = body;
 | 
			
		||||
    
 | 
			
		||||
    if (!url || typeof url !== 'string') {
 | 
			
		||||
      return c.json({ error: "URL is required" }, 400);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { addNewFeedUrl } = await import("./scripts/fetch_and_generate.js");
 | 
			
		||||
    await addNewFeedUrl(url);
 | 
			
		||||
    return c.json({ success: true, message: "Feed added successfully" });
 | 
			
		||||
    const { submitFeedRequest } = await import("./services/database.js");
 | 
			
		||||
    const requestId = await submitFeedRequest({
 | 
			
		||||
      url,
 | 
			
		||||
      requestMessage,
 | 
			
		||||
    });
 | 
			
		||||
    
 | 
			
		||||
    return c.json({ 
 | 
			
		||||
      success: true, 
 | 
			
		||||
      message: "Feed request submitted successfully",
 | 
			
		||||
      requestId 
 | 
			
		||||
    });
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error adding feed:", error);
 | 
			
		||||
    return c.json({ error: "Failed to add feed" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.delete("/api/feeds/:id", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const feedId = c.req.param("id");
 | 
			
		||||
    const { deleteFeed } = await import("./services/database.js");
 | 
			
		||||
    const success = await deleteFeed(feedId);
 | 
			
		||||
    
 | 
			
		||||
    if (!success) {
 | 
			
		||||
      return c.json({ error: "Feed not found" }, 404);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return c.json({ success: true, message: "Feed deleted successfully" });
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error deleting feed:", error);
 | 
			
		||||
    return c.json({ error: "Failed to delete feed" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
app.patch("/api/feeds/:id/toggle", async (c) => {
 | 
			
		||||
  try {
 | 
			
		||||
    const feedId = c.req.param("id");
 | 
			
		||||
    const body = await c.req.json();
 | 
			
		||||
    const { active } = body;
 | 
			
		||||
    
 | 
			
		||||
    if (typeof active !== 'boolean') {
 | 
			
		||||
      return c.json({ error: "Active status must be boolean" }, 400);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const { toggleFeedActive } = await import("./services/database.js");
 | 
			
		||||
    const success = await toggleFeedActive(feedId, active);
 | 
			
		||||
    
 | 
			
		||||
    if (!success) {
 | 
			
		||||
      return c.json({ error: "Feed not found" }, 404);
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    return c.json({ success: true, message: "Feed status updated successfully" });
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error toggling feed:", error);
 | 
			
		||||
    return c.json({ error: "Failed to update feed status" }, 500);
 | 
			
		||||
    console.error("Error submitting feed request:", error);
 | 
			
		||||
    return c.json({ error: "Failed to submit feed request" }, 500);
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,12 @@ function initializeDatabase(): Database {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const db = new Database(config.paths.dbPath);
 | 
			
		||||
  
 | 
			
		||||
  // Enable WAL mode for better concurrent access
 | 
			
		||||
  db.exec("PRAGMA journal_mode = WAL;");
 | 
			
		||||
  db.exec("PRAGMA synchronous = NORMAL;");
 | 
			
		||||
  db.exec("PRAGMA cache_size = 1000;");
 | 
			
		||||
  db.exec("PRAGMA temp_store = memory;");
 | 
			
		||||
 | 
			
		||||
  // Ensure schema is set up - use the complete schema
 | 
			
		||||
  db.exec(`CREATE TABLE IF NOT EXISTS feeds (
 | 
			
		||||
@@ -70,13 +76,27 @@ function initializeDatabase(): Database {
 | 
			
		||||
    status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'failed'))
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  CREATE TABLE IF NOT EXISTS feed_requests (
 | 
			
		||||
    id TEXT PRIMARY KEY,
 | 
			
		||||
    url TEXT NOT NULL,
 | 
			
		||||
    requested_by TEXT,
 | 
			
		||||
    request_message TEXT,
 | 
			
		||||
    status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'approved', 'rejected')),
 | 
			
		||||
    created_at TEXT NOT NULL,
 | 
			
		||||
    reviewed_at TEXT,
 | 
			
		||||
    reviewed_by TEXT,
 | 
			
		||||
    admin_notes TEXT
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_articles_feed_id ON articles(feed_id);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_articles_pub_date ON articles(pub_date);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_articles_processed ON articles(processed);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_episodes_article_id ON episodes(article_id);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_feeds_active ON feeds(active);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_tts_queue_status ON tts_queue(status);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_tts_queue_created_at ON tts_queue(created_at);`);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_tts_queue_created_at ON tts_queue(created_at);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_feed_requests_status ON feed_requests(status);
 | 
			
		||||
  CREATE INDEX IF NOT EXISTS idx_feed_requests_created_at ON feed_requests(created_at);`);
 | 
			
		||||
 | 
			
		||||
  return db;
 | 
			
		||||
}
 | 
			
		||||
@@ -601,6 +621,83 @@ export async function removeFromQueue(queueId: string): Promise<void> {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Feed Request management functions
 | 
			
		||||
export interface FeedRequest {
 | 
			
		||||
  id: string;
 | 
			
		||||
  url: string;
 | 
			
		||||
  requestedBy?: string;
 | 
			
		||||
  requestMessage?: string;
 | 
			
		||||
  status: 'pending' | 'approved' | 'rejected';
 | 
			
		||||
  createdAt: string;
 | 
			
		||||
  reviewedAt?: string;
 | 
			
		||||
  reviewedBy?: string;
 | 
			
		||||
  adminNotes?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function submitFeedRequest(
 | 
			
		||||
  request: Omit<FeedRequest, "id" | "createdAt" | "status">
 | 
			
		||||
): Promise<string> {
 | 
			
		||||
  const id = crypto.randomUUID();
 | 
			
		||||
  const createdAt = new Date().toISOString();
 | 
			
		||||
 | 
			
		||||
  try {
 | 
			
		||||
    const stmt = db.prepare(
 | 
			
		||||
      "INSERT INTO feed_requests (id, url, requested_by, request_message, status, created_at) VALUES (?, ?, ?, ?, 'pending', ?)",
 | 
			
		||||
    );
 | 
			
		||||
    stmt.run(id, request.url, request.requestedBy || null, request.requestMessage || null, createdAt);
 | 
			
		||||
    console.log(`Feed request submitted: ${request.url}`);
 | 
			
		||||
    return id;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error submitting feed request:", error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function getFeedRequests(status?: string): Promise<FeedRequest[]> {
 | 
			
		||||
  try {
 | 
			
		||||
    const sql = status
 | 
			
		||||
      ? "SELECT * FROM feed_requests WHERE status = ? ORDER BY created_at DESC"
 | 
			
		||||
      : "SELECT * FROM feed_requests ORDER BY created_at DESC";
 | 
			
		||||
    
 | 
			
		||||
    const stmt = db.prepare(sql);
 | 
			
		||||
    const rows = status ? stmt.all(status) : stmt.all();
 | 
			
		||||
 | 
			
		||||
    return (rows as any[]).map((row) => ({
 | 
			
		||||
      id: row.id,
 | 
			
		||||
      url: row.url,
 | 
			
		||||
      requestedBy: row.requested_by,
 | 
			
		||||
      requestMessage: row.request_message,
 | 
			
		||||
      status: row.status,
 | 
			
		||||
      createdAt: row.created_at,
 | 
			
		||||
      reviewedAt: row.reviewed_at,
 | 
			
		||||
      reviewedBy: row.reviewed_by,
 | 
			
		||||
      adminNotes: row.admin_notes,
 | 
			
		||||
    }));
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error getting feed requests:", error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export async function updateFeedRequestStatus(
 | 
			
		||||
  requestId: string,
 | 
			
		||||
  status: 'approved' | 'rejected',
 | 
			
		||||
  reviewedBy?: string,
 | 
			
		||||
  adminNotes?: string,
 | 
			
		||||
): Promise<boolean> {
 | 
			
		||||
  try {
 | 
			
		||||
    const reviewedAt = new Date().toISOString();
 | 
			
		||||
    const stmt = db.prepare(
 | 
			
		||||
      "UPDATE feed_requests SET status = ?, reviewed_at = ?, reviewed_by = ?, admin_notes = ? WHERE id = ?",
 | 
			
		||||
    );
 | 
			
		||||
    const result = stmt.run(status, reviewedAt, reviewedBy || null, adminNotes || null, requestId);
 | 
			
		||||
    return result.changes > 0;
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    console.error("Error updating feed request status:", error);
 | 
			
		||||
    throw error;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function closeDatabase(): void {
 | 
			
		||||
  db.close();
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user