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