mirror of
https://github.com/clucraft/PriceGhost.git
synced 2026-04-25 00:36:32 +02:00
Add toggles to enable/disable notification channels
- Add telegram_enabled, discord_enabled, pushover_enabled columns to database - Update notification service to check enabled status before sending - Add toggle switches in Settings UI for each configured channel - Update ProductDetail to only show badges for enabled channels - Channels default to enabled so existing users keep notifications Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
f1deb924d8
commit
433c0a0b12
7 changed files with 156 additions and 11 deletions
|
|
@ -37,6 +37,15 @@ async function runMigrations() {
|
|||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'pushover_app_token') THEN
|
||||
ALTER TABLE users ADD COLUMN pushover_app_token TEXT;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'telegram_enabled') THEN
|
||||
ALTER TABLE users ADD COLUMN telegram_enabled BOOLEAN DEFAULT true;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'discord_enabled') THEN
|
||||
ALTER TABLE users ADD COLUMN discord_enabled BOOLEAN DEFAULT true;
|
||||
END IF;
|
||||
IF NOT EXISTS (SELECT 1 FROM information_schema.columns WHERE table_name = 'users' AND column_name = 'pushover_enabled') THEN
|
||||
ALTER TABLE users ADD COLUMN pushover_enabled BOOLEAN DEFAULT true;
|
||||
END IF;
|
||||
END $$;
|
||||
`);
|
||||
console.log('Database migrations completed');
|
||||
|
|
|
|||
|
|
@ -24,9 +24,12 @@ export interface UserProfile {
|
|||
export interface NotificationSettings {
|
||||
telegram_bot_token: string | null;
|
||||
telegram_chat_id: string | null;
|
||||
telegram_enabled: boolean;
|
||||
discord_webhook_url: string | null;
|
||||
discord_enabled: boolean;
|
||||
pushover_user_key: string | null;
|
||||
pushover_app_token: string | null;
|
||||
pushover_enabled: boolean;
|
||||
}
|
||||
|
||||
export interface AISettings {
|
||||
|
|
@ -63,7 +66,10 @@ export const userQueries = {
|
|||
|
||||
getNotificationSettings: async (id: number): Promise<NotificationSettings | null> => {
|
||||
const result = await pool.query(
|
||||
'SELECT telegram_bot_token, telegram_chat_id, discord_webhook_url, pushover_user_key, pushover_app_token FROM users WHERE id = $1',
|
||||
`SELECT telegram_bot_token, telegram_chat_id, COALESCE(telegram_enabled, true) as telegram_enabled,
|
||||
discord_webhook_url, COALESCE(discord_enabled, true) as discord_enabled,
|
||||
pushover_user_key, pushover_app_token, COALESCE(pushover_enabled, true) as pushover_enabled
|
||||
FROM users WHERE id = $1`,
|
||||
[id]
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
|
|
@ -74,7 +80,7 @@ export const userQueries = {
|
|||
settings: Partial<NotificationSettings>
|
||||
): Promise<NotificationSettings | null> => {
|
||||
const fields: string[] = [];
|
||||
const values: (string | null)[] = [];
|
||||
const values: (string | boolean | null)[] = [];
|
||||
let paramIndex = 1;
|
||||
|
||||
if (settings.telegram_bot_token !== undefined) {
|
||||
|
|
@ -85,10 +91,18 @@ export const userQueries = {
|
|||
fields.push(`telegram_chat_id = $${paramIndex++}`);
|
||||
values.push(settings.telegram_chat_id);
|
||||
}
|
||||
if (settings.telegram_enabled !== undefined) {
|
||||
fields.push(`telegram_enabled = $${paramIndex++}`);
|
||||
values.push(settings.telegram_enabled);
|
||||
}
|
||||
if (settings.discord_webhook_url !== undefined) {
|
||||
fields.push(`discord_webhook_url = $${paramIndex++}`);
|
||||
values.push(settings.discord_webhook_url);
|
||||
}
|
||||
if (settings.discord_enabled !== undefined) {
|
||||
fields.push(`discord_enabled = $${paramIndex++}`);
|
||||
values.push(settings.discord_enabled);
|
||||
}
|
||||
if (settings.pushover_user_key !== undefined) {
|
||||
fields.push(`pushover_user_key = $${paramIndex++}`);
|
||||
values.push(settings.pushover_user_key);
|
||||
|
|
@ -97,13 +111,19 @@ export const userQueries = {
|
|||
fields.push(`pushover_app_token = $${paramIndex++}`);
|
||||
values.push(settings.pushover_app_token);
|
||||
}
|
||||
if (settings.pushover_enabled !== undefined) {
|
||||
fields.push(`pushover_enabled = $${paramIndex++}`);
|
||||
values.push(settings.pushover_enabled);
|
||||
}
|
||||
|
||||
if (fields.length === 0) return null;
|
||||
|
||||
values.push(id.toString());
|
||||
const result = await pool.query(
|
||||
`UPDATE users SET ${fields.join(', ')} WHERE id = $${paramIndex}
|
||||
RETURNING telegram_bot_token, telegram_chat_id, discord_webhook_url, pushover_user_key, pushover_app_token`,
|
||||
RETURNING telegram_bot_token, telegram_chat_id, COALESCE(telegram_enabled, true) as telegram_enabled,
|
||||
discord_webhook_url, COALESCE(discord_enabled, true) as discord_enabled,
|
||||
pushover_user_key, pushover_app_token, COALESCE(pushover_enabled, true) as pushover_enabled`,
|
||||
values
|
||||
);
|
||||
return result.rows[0] || null;
|
||||
|
|
|
|||
|
|
@ -22,8 +22,11 @@ router.get('/notifications', async (req: AuthRequest, res: Response) => {
|
|||
res.json({
|
||||
telegram_configured: !!(settings.telegram_bot_token && settings.telegram_chat_id),
|
||||
telegram_chat_id: settings.telegram_chat_id,
|
||||
telegram_enabled: settings.telegram_enabled ?? true,
|
||||
discord_configured: !!settings.discord_webhook_url,
|
||||
discord_enabled: settings.discord_enabled ?? true,
|
||||
pushover_configured: !!(settings.pushover_user_key && settings.pushover_app_token),
|
||||
pushover_enabled: settings.pushover_enabled ?? true,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching notification settings:', error);
|
||||
|
|
@ -35,14 +38,26 @@ router.get('/notifications', async (req: AuthRequest, res: Response) => {
|
|||
router.put('/notifications', async (req: AuthRequest, res: Response) => {
|
||||
try {
|
||||
const userId = req.userId!;
|
||||
const { telegram_bot_token, telegram_chat_id, discord_webhook_url, pushover_user_key, pushover_app_token } = req.body;
|
||||
const {
|
||||
telegram_bot_token,
|
||||
telegram_chat_id,
|
||||
telegram_enabled,
|
||||
discord_webhook_url,
|
||||
discord_enabled,
|
||||
pushover_user_key,
|
||||
pushover_app_token,
|
||||
pushover_enabled,
|
||||
} = req.body;
|
||||
|
||||
const settings = await userQueries.updateNotificationSettings(userId, {
|
||||
telegram_bot_token,
|
||||
telegram_chat_id,
|
||||
telegram_enabled,
|
||||
discord_webhook_url,
|
||||
discord_enabled,
|
||||
pushover_user_key,
|
||||
pushover_app_token,
|
||||
pushover_enabled,
|
||||
});
|
||||
|
||||
if (!settings) {
|
||||
|
|
@ -53,8 +68,11 @@ router.put('/notifications', async (req: AuthRequest, res: Response) => {
|
|||
res.json({
|
||||
telegram_configured: !!(settings.telegram_bot_token && settings.telegram_chat_id),
|
||||
telegram_chat_id: settings.telegram_chat_id,
|
||||
telegram_enabled: settings.telegram_enabled ?? true,
|
||||
discord_configured: !!settings.discord_webhook_url,
|
||||
discord_enabled: settings.discord_enabled ?? true,
|
||||
pushover_configured: !!(settings.pushover_user_key && settings.pushover_app_token),
|
||||
pushover_enabled: settings.pushover_enabled ?? true,
|
||||
message: 'Notification settings updated successfully',
|
||||
});
|
||||
} catch (error) {
|
||||
|
|
|
|||
|
|
@ -187,25 +187,29 @@ export async function sendNotifications(
|
|||
settings: {
|
||||
telegram_bot_token: string | null;
|
||||
telegram_chat_id: string | null;
|
||||
telegram_enabled?: boolean;
|
||||
discord_webhook_url: string | null;
|
||||
discord_enabled?: boolean;
|
||||
pushover_user_key: string | null;
|
||||
pushover_app_token: string | null;
|
||||
pushover_enabled?: boolean;
|
||||
},
|
||||
payload: NotificationPayload
|
||||
): Promise<void> {
|
||||
const promises: Promise<boolean>[] = [];
|
||||
|
||||
if (settings.telegram_bot_token && settings.telegram_chat_id) {
|
||||
// Only send if channel is configured AND enabled (default to true if not specified)
|
||||
if (settings.telegram_bot_token && settings.telegram_chat_id && settings.telegram_enabled !== false) {
|
||||
promises.push(
|
||||
sendTelegramNotification(settings.telegram_bot_token, settings.telegram_chat_id, payload)
|
||||
);
|
||||
}
|
||||
|
||||
if (settings.discord_webhook_url) {
|
||||
if (settings.discord_webhook_url && settings.discord_enabled !== false) {
|
||||
promises.push(sendDiscordNotification(settings.discord_webhook_url, payload));
|
||||
}
|
||||
|
||||
if (settings.pushover_user_key && settings.pushover_app_token) {
|
||||
if (settings.pushover_user_key && settings.pushover_app_token && settings.pushover_enabled !== false) {
|
||||
promises.push(
|
||||
sendPushoverNotification(settings.pushover_user_key, settings.pushover_app_token, payload)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -126,8 +126,11 @@ export const pricesApi = {
|
|||
export interface NotificationSettings {
|
||||
telegram_configured: boolean;
|
||||
telegram_chat_id: string | null;
|
||||
telegram_enabled: boolean;
|
||||
discord_configured: boolean;
|
||||
discord_enabled: boolean;
|
||||
pushover_configured: boolean;
|
||||
pushover_enabled: boolean;
|
||||
}
|
||||
|
||||
export const settingsApi = {
|
||||
|
|
@ -137,9 +140,12 @@ export const settingsApi = {
|
|||
updateNotifications: (data: {
|
||||
telegram_bot_token?: string | null;
|
||||
telegram_chat_id?: string | null;
|
||||
telegram_enabled?: boolean;
|
||||
discord_webhook_url?: string | null;
|
||||
discord_enabled?: boolean;
|
||||
pushover_user_key?: string | null;
|
||||
pushover_app_token?: string | null;
|
||||
pushover_enabled?: boolean;
|
||||
}) => api.put<NotificationSettings & { message: string }>('/settings/notifications', data),
|
||||
|
||||
testTelegram: () =>
|
||||
|
|
|
|||
|
|
@ -521,7 +521,11 @@ export default function ProductDetail() {
|
|||
onRangeChange={handleRangeChange}
|
||||
/>
|
||||
|
||||
{notificationSettings && (notificationSettings.telegram_configured || notificationSettings.discord_configured || notificationSettings.pushover_configured) && (
|
||||
{notificationSettings && (
|
||||
(notificationSettings.telegram_configured && notificationSettings.telegram_enabled) ||
|
||||
(notificationSettings.discord_configured && notificationSettings.discord_enabled) ||
|
||||
(notificationSettings.pushover_configured && notificationSettings.pushover_enabled)
|
||||
) && (
|
||||
<>
|
||||
<style>{`
|
||||
.notification-settings-card {
|
||||
|
|
@ -669,13 +673,13 @@ export default function ProductDetail() {
|
|||
<span className="notification-settings-icon">🔔</span>
|
||||
<h2 className="notification-settings-title">Notification Settings</h2>
|
||||
<div className="notification-settings-channels">
|
||||
{notificationSettings.telegram_configured && (
|
||||
{notificationSettings.telegram_configured && notificationSettings.telegram_enabled && (
|
||||
<span className="notification-channel-badge">Telegram</span>
|
||||
)}
|
||||
{notificationSettings.discord_configured && (
|
||||
{notificationSettings.discord_configured && notificationSettings.discord_enabled && (
|
||||
<span className="notification-channel-badge">Discord</span>
|
||||
)}
|
||||
{notificationSettings.pushover_configured && (
|
||||
{notificationSettings.pushover_configured && notificationSettings.pushover_enabled && (
|
||||
<span className="notification-channel-badge">Pushover</span>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -32,9 +32,12 @@ export default function Settings() {
|
|||
const [notificationSettings, setNotificationSettings] = useState<NotificationSettings | null>(null);
|
||||
const [telegramBotToken, setTelegramBotToken] = useState('');
|
||||
const [telegramChatId, setTelegramChatId] = useState('');
|
||||
const [telegramEnabled, setTelegramEnabled] = useState(true);
|
||||
const [discordWebhookUrl, setDiscordWebhookUrl] = useState('');
|
||||
const [discordEnabled, setDiscordEnabled] = useState(true);
|
||||
const [pushoverUserKey, setPushoverUserKey] = useState('');
|
||||
const [pushoverAppToken, setPushoverAppToken] = useState('');
|
||||
const [pushoverEnabled, setPushoverEnabled] = useState(true);
|
||||
const [isSavingNotifications, setIsSavingNotifications] = useState(false);
|
||||
const [isTesting, setIsTesting] = useState<'telegram' | 'discord' | 'pushover' | null>(null);
|
||||
|
||||
|
|
@ -76,6 +79,9 @@ export default function Settings() {
|
|||
if (notificationsRes.data.telegram_chat_id) {
|
||||
setTelegramChatId(notificationsRes.data.telegram_chat_id);
|
||||
}
|
||||
setTelegramEnabled(notificationsRes.data.telegram_enabled ?? true);
|
||||
setDiscordEnabled(notificationsRes.data.discord_enabled ?? true);
|
||||
setPushoverEnabled(notificationsRes.data.pushover_enabled ?? true);
|
||||
setAISettings(aiRes.data);
|
||||
setAIEnabled(aiRes.data.ai_enabled);
|
||||
if (aiRes.data.ai_provider) {
|
||||
|
|
@ -250,6 +256,39 @@ export default function Settings() {
|
|||
}
|
||||
};
|
||||
|
||||
const handleToggleTelegram = async (enabled: boolean) => {
|
||||
setTelegramEnabled(enabled);
|
||||
try {
|
||||
const response = await settingsApi.updateNotifications({ telegram_enabled: enabled });
|
||||
setNotificationSettings(response.data);
|
||||
} catch {
|
||||
setTelegramEnabled(!enabled);
|
||||
setError('Failed to update Telegram status');
|
||||
}
|
||||
};
|
||||
|
||||
const handleToggleDiscord = async (enabled: boolean) => {
|
||||
setDiscordEnabled(enabled);
|
||||
try {
|
||||
const response = await settingsApi.updateNotifications({ discord_enabled: enabled });
|
||||
setNotificationSettings(response.data);
|
||||
} catch {
|
||||
setDiscordEnabled(!enabled);
|
||||
setError('Failed to update Discord status');
|
||||
}
|
||||
};
|
||||
|
||||
const handleTogglePushover = async (enabled: boolean) => {
|
||||
setPushoverEnabled(enabled);
|
||||
try {
|
||||
const response = await settingsApi.updateNotifications({ pushover_enabled: enabled });
|
||||
setNotificationSettings(response.data);
|
||||
} catch {
|
||||
setPushoverEnabled(!enabled);
|
||||
setError('Failed to update Pushover status');
|
||||
}
|
||||
};
|
||||
|
||||
// AI handlers
|
||||
const handleSaveAI = async () => {
|
||||
clearMessages();
|
||||
|
|
@ -893,6 +932,21 @@ export default function Settings() {
|
|||
and get your chat ID.
|
||||
</p>
|
||||
|
||||
{notificationSettings?.telegram_configured && (
|
||||
<div className="settings-toggle">
|
||||
<div className="settings-toggle-label">
|
||||
<span className="settings-toggle-title">Enable Telegram Notifications</span>
|
||||
<span className="settings-toggle-description">
|
||||
Toggle to enable or disable Telegram alerts
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className={`toggle-switch ${telegramEnabled ? 'active' : ''}`}
|
||||
onClick={() => handleToggleTelegram(!telegramEnabled)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="settings-form-group">
|
||||
<label>Bot Token</label>
|
||||
<input
|
||||
|
|
@ -948,6 +1002,21 @@ export default function Settings() {
|
|||
Discord server settings.
|
||||
</p>
|
||||
|
||||
{notificationSettings?.discord_configured && (
|
||||
<div className="settings-toggle">
|
||||
<div className="settings-toggle-label">
|
||||
<span className="settings-toggle-title">Enable Discord Notifications</span>
|
||||
<span className="settings-toggle-description">
|
||||
Toggle to enable or disable Discord alerts
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className={`toggle-switch ${discordEnabled ? 'active' : ''}`}
|
||||
onClick={() => handleToggleDiscord(!discordEnabled)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="settings-form-group">
|
||||
<label>Webhook URL</label>
|
||||
<input
|
||||
|
|
@ -992,6 +1061,21 @@ export default function Settings() {
|
|||
and an application to get your keys.
|
||||
</p>
|
||||
|
||||
{notificationSettings?.pushover_configured && (
|
||||
<div className="settings-toggle">
|
||||
<div className="settings-toggle-label">
|
||||
<span className="settings-toggle-title">Enable Pushover Notifications</span>
|
||||
<span className="settings-toggle-description">
|
||||
Toggle to enable or disable Pushover alerts
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
className={`toggle-switch ${pushoverEnabled ? 'active' : ''}`}
|
||||
onClick={() => handleTogglePushover(!pushoverEnabled)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="settings-form-group">
|
||||
<label>User Key</label>
|
||||
<input
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue