Update admin panel and server configuration
This commit is contained in:
@ -126,86 +126,21 @@ function initializeDatabase(): Database {
|
||||
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 (
|
||||
id TEXT PRIMARY KEY,
|
||||
url TEXT NOT NULL UNIQUE,
|
||||
title TEXT,
|
||||
description TEXT,
|
||||
category TEXT,
|
||||
last_updated TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
active BOOLEAN DEFAULT 1
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS articles (
|
||||
id TEXT PRIMARY KEY,
|
||||
feed_id TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
link TEXT NOT NULL UNIQUE,
|
||||
description TEXT,
|
||||
content TEXT,
|
||||
pub_date TEXT NOT NULL,
|
||||
discovered_at TEXT NOT NULL,
|
||||
processed BOOLEAN DEFAULT 0,
|
||||
FOREIGN KEY(feed_id) REFERENCES feeds(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS episodes (
|
||||
id TEXT PRIMARY KEY,
|
||||
article_id TEXT NOT NULL,
|
||||
title TEXT NOT NULL,
|
||||
description TEXT,
|
||||
audio_path TEXT NOT NULL,
|
||||
duration INTEGER,
|
||||
file_size INTEGER,
|
||||
category TEXT,
|
||||
created_at TEXT NOT NULL,
|
||||
FOREIGN KEY(article_id) REFERENCES articles(id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS processed_feed_items (
|
||||
feed_url TEXT NOT NULL,
|
||||
item_id TEXT NOT NULL,
|
||||
processed_at TEXT NOT NULL,
|
||||
PRIMARY KEY(feed_url, item_id)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS tts_queue (
|
||||
id TEXT PRIMARY KEY,
|
||||
item_id TEXT NOT NULL,
|
||||
script_text TEXT NOT NULL,
|
||||
retry_count INTEGER DEFAULT 0,
|
||||
created_at TEXT NOT NULL,
|
||||
last_attempted_at TEXT,
|
||||
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_feed_requests_status ON feed_requests(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_feed_requests_created_at ON feed_requests(created_at);`);
|
||||
// Load and execute schema from file
|
||||
const schemaPath = path.join(config.paths.projectRoot, "schema.sql");
|
||||
if (fs.existsSync(schemaPath)) {
|
||||
const schema = fs.readFileSync(schemaPath, "utf-8");
|
||||
db.exec(schema);
|
||||
} else {
|
||||
throw new Error(`Schema file not found: ${schemaPath}`);
|
||||
}
|
||||
|
||||
// Perform database integrity checks and fixes
|
||||
performDatabaseIntegrityFixes(db);
|
||||
|
||||
// Initialize settings table with default values
|
||||
initializeSettings();
|
||||
|
||||
// ALTER
|
||||
// ALTER TABLE feeds ADD COLUMN category TEXT DEFAULT NULL;
|
||||
// Ensure the category column exists in feeds
|
||||
@ -1666,6 +1601,267 @@ export async function getEpisodeCategoryMigrationStatus(): Promise<{
|
||||
}
|
||||
}
|
||||
|
||||
// Settings management functions
|
||||
export interface Setting {
|
||||
key: string;
|
||||
value: string | null;
|
||||
isCredential: boolean;
|
||||
description: string;
|
||||
defaultValue: string;
|
||||
required: boolean;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export async function initializeSettings(): Promise<void> {
|
||||
const defaultSettings: Omit<Setting, "updatedAt">[] = [
|
||||
{
|
||||
key: "OPENAI_API_KEY",
|
||||
value: null,
|
||||
isCredential: true,
|
||||
description: "OpenAI API Key for content generation",
|
||||
defaultValue: "",
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
key: "OPENAI_API_ENDPOINT",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "OpenAI API Endpoint URL",
|
||||
defaultValue: "https://api.openai.com/v1",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "OPENAI_MODEL_NAME",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "OpenAI Model Name",
|
||||
defaultValue: "gpt-4o-mini",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "VOICEVOX_HOST",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "VOICEVOX Server Host URL",
|
||||
defaultValue: "http://localhost:50021",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "VOICEVOX_STYLE_ID",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "VOICEVOX Voice Style ID",
|
||||
defaultValue: "0",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "PODCAST_TITLE",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Podcast Title",
|
||||
defaultValue: "自動生成ポッドキャスト",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "PODCAST_LINK",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Podcast Link URL",
|
||||
defaultValue: "https://your-domain.com/podcast",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "PODCAST_DESCRIPTION",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Podcast Description",
|
||||
defaultValue: "RSSフィードから自動生成された音声ポッドキャスト",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "PODCAST_LANGUAGE",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Podcast Language",
|
||||
defaultValue: "ja",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "PODCAST_AUTHOR",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Podcast Author",
|
||||
defaultValue: "管理者",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "PODCAST_CATEGORIES",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Podcast Categories",
|
||||
defaultValue: "Technology",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "PODCAST_TTL",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Podcast TTL",
|
||||
defaultValue: "60",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "PODCAST_BASE_URL",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Podcast Base URL",
|
||||
defaultValue: "https://your-domain.com",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "ADMIN_PORT",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Admin Panel Port",
|
||||
defaultValue: "3001",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "ADMIN_USERNAME",
|
||||
value: null,
|
||||
isCredential: true,
|
||||
description: "Admin Panel Username",
|
||||
defaultValue: "",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "ADMIN_PASSWORD",
|
||||
value: null,
|
||||
isCredential: true,
|
||||
description: "Admin Panel Password",
|
||||
defaultValue: "",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "DISABLE_INITIAL_BATCH",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Disable Initial Batch Process",
|
||||
defaultValue: "false",
|
||||
required: false,
|
||||
},
|
||||
{
|
||||
key: "FEED_URLS_FILE",
|
||||
value: null,
|
||||
isCredential: false,
|
||||
description: "Feed URLs File Path",
|
||||
defaultValue: "feed_urls.txt",
|
||||
required: false,
|
||||
},
|
||||
];
|
||||
|
||||
const now = new Date().toISOString();
|
||||
|
||||
for (const setting of defaultSettings) {
|
||||
try {
|
||||
const stmt = db.prepare(
|
||||
"INSERT OR IGNORE INTO settings (key, value, is_credential, description, default_value, required, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)"
|
||||
);
|
||||
stmt.run(
|
||||
setting.key,
|
||||
setting.value,
|
||||
setting.isCredential ? 1 : 0,
|
||||
setting.description,
|
||||
setting.defaultValue,
|
||||
setting.required ? 1 : 0,
|
||||
now
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(`Error initializing setting ${setting.key}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function getAllSettings(): Promise<Setting[]> {
|
||||
try {
|
||||
const stmt = db.prepare("SELECT * FROM settings ORDER BY key");
|
||||
const rows = stmt.all() as any[];
|
||||
|
||||
return rows.map((row) => ({
|
||||
key: row.key,
|
||||
value: row.value,
|
||||
isCredential: Boolean(row.is_credential),
|
||||
description: row.description,
|
||||
defaultValue: row.default_value,
|
||||
required: Boolean(row.required),
|
||||
updatedAt: row.updated_at,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error getting all settings:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSetting(key: string): Promise<Setting | null> {
|
||||
try {
|
||||
const stmt = db.prepare("SELECT * FROM settings WHERE key = ?");
|
||||
const row = stmt.get(key) as any;
|
||||
if (!row) return null;
|
||||
|
||||
return {
|
||||
key: row.key,
|
||||
value: row.value,
|
||||
isCredential: Boolean(row.is_credential),
|
||||
description: row.description,
|
||||
defaultValue: row.default_value,
|
||||
required: Boolean(row.required),
|
||||
updatedAt: row.updated_at,
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error getting setting ${key}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function updateSetting(key: string, value: string): Promise<boolean> {
|
||||
try {
|
||||
const now = new Date().toISOString();
|
||||
const stmt = db.prepare(
|
||||
"UPDATE settings SET value = ?, updated_at = ? WHERE key = ?"
|
||||
);
|
||||
const result = stmt.run(value, now, key);
|
||||
return result.changes > 0;
|
||||
} catch (error) {
|
||||
console.error(`Error updating setting ${key}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteSetting(key: string): Promise<boolean> {
|
||||
try {
|
||||
const stmt = db.prepare("DELETE FROM settings WHERE key = ?");
|
||||
const result = stmt.run(key);
|
||||
return result.changes > 0;
|
||||
} catch (error) {
|
||||
console.error(`Error deleting setting ${key}:`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getSettingsForAdminUI(): Promise<Setting[]> {
|
||||
try {
|
||||
const settings = await getAllSettings();
|
||||
return settings.map((setting) => ({
|
||||
...setting,
|
||||
// Mask credential values for security
|
||||
value: setting.isCredential && setting.value ? "••••••••" : setting.value,
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error("Error getting settings for admin UI:", error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export function closeDatabase(): void {
|
||||
db.close();
|
||||
}
|
||||
|
Reference in New Issue
Block a user