const { DataTypes } = require('sequelize'); const { v4: uuid } = require('uuid'); module.exports = (sequelize) => { const Notification = sequelize.define( 'Notification', { id: { type: DataTypes.INTEGER, primaryKey: true, autoIncrement: true, }, uid: { type: DataTypes.STRING, unique: true, allowNull: false, defaultValue: () => uuid(), }, user_id: { type: DataTypes.INTEGER, allowNull: false, references: { model: 'users', key: 'id', }, }, type: { type: DataTypes.STRING, allowNull: false, validate: { isIn: [ [ 'task_assigned', 'task_completed', 'task_due_soon', 'task_overdue', 'comment_added', 'mention', 'reminder', 'system', 'project_due_soon', 'project_overdue', ], ], }, }, level: { type: DataTypes.STRING, allowNull: false, defaultValue: 'info', validate: { isIn: [['info', 'warning', 'error', 'success']], }, }, title: { type: DataTypes.STRING, allowNull: false, }, message: { type: DataTypes.TEXT, allowNull: true, }, data: { type: DataTypes.JSON, allowNull: true, }, sources: { type: DataTypes.JSON, allowNull: false, defaultValue: [], validate: { isValidSources(value) { if (!Array.isArray(value)) { throw new Error('Sources must be an array'); } const validSources = ['telegram', 'mobile', 'email']; const invalidSources = value.filter( (s) => !validSources.includes(s) ); if (invalidSources.length > 0) { throw new Error( `Invalid sources: ${invalidSources.join(', ')}` ); } }, }, }, read_at: { type: DataTypes.DATE, allowNull: true, }, sent_at: { type: DataTypes.DATE, allowNull: true, }, dismissed_at: { type: DataTypes.DATE, allowNull: true, }, }, { tableName: 'notifications', timestamps: true, createdAt: 'created_at', updatedAt: 'updated_at', indexes: [ { fields: ['user_id'], }, { fields: ['read_at'], }, { fields: ['created_at'], }, { fields: ['user_id', 'read_at'], }, { fields: ['dismissed_at'], }, { fields: ['user_id', 'dismissed_at'], }, ], } ); Notification.associate = function (models) { Notification.belongsTo(models.User, { foreignKey: 'user_id', as: 'User', }); }; Notification.createNotification = async function ({ userId, type, title, message, data = null, sources = [], sentAt = null, level = 'info', }) { const notification = await Notification.create({ user_id: userId, type, title, message, data, sources, level, sent_at: sentAt || new Date(), }); if (sources.includes('email')) { await sendEmailNotification(userId, title, message, Notification); } if (sources.includes('telegram')) { await sendTelegramNotification( userId, title, message, data, Notification ); } return notification; }; async function sendEmailNotification( userId, title, message, NotificationModel ) { try { const { sendEmail, isEmailEnabled, } = require('../services/emailService'); if (!isEmailEnabled() || !message) { return; } const UserModel = NotificationModel.sequelize.models.User; const user = await UserModel.findByPk(userId, { attributes: ['email', 'name'], }); if (user?.email) { await sendEmail({ to: user.email, subject: title, text: message, }); } } catch (error) { console.error('Failed to send email notification:', error); } } async function sendTelegramNotification( userId, title, message, data, NotificationModel ) { try { const telegramService = require('../modules/telegram/telegramNotificationService'); if (!message) { return; } const UserModel = NotificationModel.sequelize.models.User; const user = await UserModel.findByPk(userId, { attributes: [ 'id', 'name', 'surname', 'telegram_bot_token', 'telegram_chat_id', ], }); if (user && telegramService.isTelegramConfigured(user)) { await telegramService.sendTelegramNotification(user, { title, message, data, level: 'info', }); } } catch (error) { console.error('Failed to send Telegram notification:', error); } } /** * Mark a notification as read */ Notification.prototype.markAsRead = async function () { if (!this.read_at) { this.read_at = new Date(); await this.save(); } return this; }; Notification.prototype.markAsUnread = async function () { this.read_at = null; await this.save(); return this; }; Notification.prototype.isRead = function () { return this.read_at !== null; }; Notification.prototype.dismiss = async function () { if (!this.dismissed_at) { this.dismissed_at = new Date(); await this.save(); } return this; }; Notification.prototype.isDismissed = function () { return this.dismissed_at !== null; }; Notification.getUserNotifications = async function (userId, options = {}) { const { limit = 10, offset = 0, includeRead = true, type = null, } = options; const where = { user_id: userId, dismissed_at: null, }; if (!includeRead) { where.read_at = null; } if (type) { where.type = type; } const result = await Notification.findAndCountAll({ where, order: [['created_at', 'DESC']], limit, offset, }); return { notifications: result.rows, total: result.count, }; }; Notification.getUnreadCount = async function (userId) { return await Notification.count({ where: { user_id: userId, read_at: null, dismissed_at: null, }, }); }; Notification.markAllAsRead = async function (userId) { return await Notification.update( { read_at: new Date() }, { where: { user_id: userId, read_at: null, dismissed_at: null, }, } ); }; return Notification; };