| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import { z } from 'zod'; |
| import { alertsRepository } from './alerts.repository.js'; |
| import { watchlistRepository } from '../watchlist/watchlist.repository.js'; |
| import { sendMessage, formatPriceAlert } from './telegram.client.js'; |
| import { emitPriceAlert } from '../socket/broadcaster.js'; |
| import { logger } from '../utils/logger.js'; |
|
|
| const listQuery = z.object({ |
| limit: z.coerce.number().int().min(1).max(200).default(50), |
| offset: z.coerce.number().int().min(0).default(0), |
| }); |
|
|
| const DEDUP_WINDOW_MS = 5 * 60 * 1000; |
|
|
| export const alertsService = { |
| list(userId, query) { |
| const { limit, offset } = listQuery.parse(query); |
| return alertsRepository.findByUser(userId, { limit, offset }); |
| }, |
|
|
| async processAll() { |
| const entries = await watchlistRepository.findAllWithThreshold(); |
|
|
| for (const entry of entries) { |
| const { alertThreshold, user, market } = entry; |
| if (!market.yesPrice || market.yesPrice < alertThreshold) continue; |
|
|
| const recent = await alertsRepository.findRecent(user.id, market.id, 'price_threshold', DEDUP_WINDOW_MS); |
| if (recent) continue; |
|
|
| const message = formatPriceAlert(market.question, market.yesPrice, alertThreshold); |
|
|
| await alertsRepository.create({ userId: user.id, marketId: market.id, type: 'price_threshold', message }); |
| await sendMessage(user.telegramChatId, message); |
| emitPriceAlert({ marketId: market.id, type: 'price_threshold', message }); |
| logger.info({ marketId: market.id, userId: user.id }, 'price alert sent'); |
| } |
| }, |
| }; |
|
|