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:
clucraft 2026-01-22 14:07:44 -05:00
parent f1deb924d8
commit 433c0a0b12
7 changed files with 156 additions and 11 deletions

View file

@ -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');

View file

@ -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;

View file

@ -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) {

View file

@ -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)
);