* Fix Telegram notification spam with channel-level rate limiting
Addresses issue #950 where Telegram notifications were sent excessively
(96-288 messages per day per task) due to the delete-and-recreate pattern
added in commit 105a913a to fix navbar notification pile-up.
Changes:
- Add channel_sent_at JSON field to notifications table to track when
each channel (telegram, email, push) was last sent
- Add helper methods to notification model:
- markChannelAsSent(channel): Records send timestamp
- wasChannelRecentlySent(channel, threshold): Checks if sent within 24h
- Modify sendTelegramNotification() to check rate limit before sending
- Update service layer (dueTaskService, deferredTaskService,
dueProjectService) to preserve channel_sent_at when recreating
notifications
- Add comprehensive unit and integration tests (20 tests, all passing)
Impact:
- Reduces Telegram notifications from 96-288/day to 1/day per item
- Preserves in-app notification refresh behavior (every 5-15 min)
- Maintains navbar pile-up fix from original commit
- Rate limit configurable (default: 24 hours)
Fixes #950
* Fix linting and formatting issues
* Fix integration test that was trying to access private function
* Fix prettier formatting in integration test
36 lines
975 B
JavaScript
36 lines
975 B
JavaScript
'use strict';
|
|
|
|
const {
|
|
safeAddColumns,
|
|
safeRemoveColumn,
|
|
} = require('../utils/migration-utils');
|
|
|
|
/**
|
|
* Migration to add channel_sent_at JSON column to notifications table.
|
|
* This tracks when each notification channel (telegram, email, push) was last sent,
|
|
* enabling rate limiting to prevent notification spam.
|
|
*
|
|
* Example value: {"telegram": "2026-03-19T10:30:00Z", "email": "2026-03-19T11:00:00Z"}
|
|
*/
|
|
module.exports = {
|
|
async up(queryInterface, Sequelize) {
|
|
await safeAddColumns(queryInterface, 'notifications', [
|
|
{
|
|
name: 'channel_sent_at',
|
|
definition: {
|
|
type: Sequelize.JSON,
|
|
allowNull: true,
|
|
defaultValue: null,
|
|
},
|
|
},
|
|
]);
|
|
},
|
|
|
|
async down(queryInterface) {
|
|
await safeRemoveColumn(
|
|
queryInterface,
|
|
'notifications',
|
|
'channel_sent_at'
|
|
);
|
|
},
|
|
};
|