Files
pulse-api/internal/scheduler/scheduler.go

342 lines
8.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package scheduler
import (
"fmt"
"log"
"time"
"github.com/robfig/cron/v3"
tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5"
"github.com/daniil/homelab-api/internal/bot"
"github.com/daniil/homelab-api/internal/model"
"github.com/daniil/homelab-api/internal/repository"
)
type Scheduler struct {
cron *cron.Cron
bot *bot.Bot
userRepo *repository.UserRepository
taskRepo *repository.TaskRepository
habitRepo *repository.HabitRepository
}
func New(b *bot.Bot, userRepo *repository.UserRepository, taskRepo *repository.TaskRepository, habitRepo *repository.HabitRepository) *Scheduler {
return &Scheduler{
cron: cron.New(),
bot: b,
userRepo: userRepo,
taskRepo: taskRepo,
habitRepo: habitRepo,
}
}
func (s *Scheduler) Start() {
if s.bot == nil || s.bot.GetAPI() == nil {
log.Println("Scheduler: bot not configured, skipping")
return
}
// Run every minute
s.cron.AddFunc("* * * * *", s.checkNotifications)
s.cron.Start()
log.Println("Scheduler started")
}
func (s *Scheduler) Stop() {
s.cron.Stop()
}
func (s *Scheduler) checkNotifications() {
users, err := s.userRepo.GetUsersWithNotifications()
if err != nil {
log.Printf("Scheduler: error getting users: %v", err)
return
}
for _, user := range users {
go s.checkUserNotifications(user)
}
}
func (s *Scheduler) checkUserNotifications(user model.User) {
if !user.TelegramChatID.Valid {
return
}
chatID := user.TelegramChatID.Int64
// Get user timezone
loc, err := time.LoadLocation(user.Timezone)
if err != nil {
loc = time.FixedZone("Europe/Moscow", 3*60*60)
}
now := time.Now().In(loc)
currentTime := now.Format("15:04")
today := now.Format("2006-01-02")
weekday := int(now.Weekday())
// Get user's reminder times
morningTime := "09:00"
if user.MorningReminderTime.Valid && len(user.MorningReminderTime.String) >= 5 {
morningTime = user.MorningReminderTime.String[:5]
}
eveningTime := "21:00"
if user.EveningReminderTime.Valid && len(user.EveningReminderTime.String) >= 5 {
eveningTime = user.EveningReminderTime.String[:5]
}
// 1. Morning briefing
if currentTime == morningTime {
s.sendMorningBriefing(user.ID, chatID, loc)
}
// 2. Evening summary
if currentTime == eveningTime {
s.sendEveningSummary(user.ID, chatID, loc)
}
// 3. Task reminders
s.checkTaskReminders(user.ID, chatID, currentTime, today)
// 4. Habit reminders
s.checkHabitReminders(user.ID, chatID, currentTime, weekday)
}
func (s *Scheduler) sendMorningBriefing(userID, chatID int64, loc *time.Location) {
tasks, err := s.taskRepo.GetTodayTasks(userID)
if err != nil {
log.Printf("Scheduler: error getting tasks for user %d: %v", userID, err)
return
}
habits, err := s.habitRepo.ListByUser(userID, false)
if err != nil {
log.Printf("Scheduler: error getting habits for user %d: %v", userID, err)
return
}
// Filter habits for today
weekday := int(time.Now().In(loc).Weekday())
var todayHabits int
for _, habit := range habits {
if habit.Frequency == "daily" {
todayHabits++
} else {
for _, day := range habit.TargetDays {
if day == weekday {
todayHabits++
break
}
}
}
}
if len(tasks) == 0 && todayHabits == 0 {
return // Nothing to report
}
text := "☀️ <b>Доброе утро!</b>\n\n"
if len(tasks) > 0 {
text += fmt.Sprintf("📋 Задач на сегодня: <b>%d</b>\n", len(tasks))
for i, task := range tasks {
if i >= 3 {
text += fmt.Sprintf(" ... и ещё %d\n", len(tasks)-3)
break
}
text += fmt.Sprintf(" • %s %s\n", task.Icon, task.Title)
}
text += "\n"
}
if todayHabits > 0 {
text += fmt.Sprintf("🎯 Привычек: <b>%d</b>\n", todayHabits)
}
text += "\nИспользуй /tasks и /habits для просмотра"
s.bot.SendMessage(chatID, text)
}
func (s *Scheduler) sendEveningSummary(userID, chatID int64, loc *time.Location) {
// Get tasks
tasks, err := s.taskRepo.GetTodayTasks(userID)
if err != nil {
log.Printf("Scheduler: error getting tasks for user %d: %v", userID, err)
return
}
// Count completed/incomplete tasks
var completedTasks, incompleteTasks int
for _, task := range tasks {
if task.CompletedAt.Valid {
completedTasks++
} else {
incompleteTasks++
}
}
// Get habits
habits, err := s.habitRepo.ListByUser(userID, false)
if err != nil {
log.Printf("Scheduler: error getting habits for user %d: %v", userID, err)
return
}
// Filter and count today's habits
weekday := int(time.Now().In(loc).Weekday())
var completedHabits, incompleteHabits int
var incompleteHabitNames []string
for _, habit := range habits {
isToday := false
if habit.Frequency == "daily" {
isToday = true
} else {
for _, day := range habit.TargetDays {
if day == weekday {
isToday = true
break
}
}
}
if isToday {
completed, _ := s.habitRepo.IsHabitCompletedToday(habit.ID, userID)
if completed {
completedHabits++
} else {
incompleteHabits++
incompleteHabitNames = append(incompleteHabitNames, habit.Icon+" "+habit.Name)
}
}
}
// Don't send if nothing to report
totalTasks := completedTasks + incompleteTasks
totalHabits := completedHabits + incompleteHabits
if totalTasks == 0 && totalHabits == 0 {
return
}
text := "🌙 <b>Итоги дня</b>\n\n"
// Tasks summary
if totalTasks > 0 {
text += "📋 <b>Задачи:</b>\n"
text += fmt.Sprintf(" ✅ Выполнено: %d\n", completedTasks)
text += fmt.Sprintf(" ⬜ Осталось: %d\n", incompleteTasks)
text += "\n"
}
// Habits summary
if totalHabits > 0 {
text += "🎯 <b>Привычки:</b>\n"
text += fmt.Sprintf(" ✅ Выполнено: %d\n", completedHabits)
text += fmt.Sprintf(" ⬜ Осталось: %d\n", incompleteHabits)
// Show incomplete habits
if len(incompleteHabitNames) > 0 && len(incompleteHabitNames) <= 5 {
text += "\n Не выполнены:\n"
for _, name := range incompleteHabitNames {
text += fmt.Sprintf(" • %s\n", name)
}
}
text += "\n"
}
// Motivational message
taskPercent := 0
if totalTasks > 0 {
taskPercent = completedTasks * 100 / totalTasks
}
habitPercent := 0
if totalHabits > 0 {
habitPercent = completedHabits * 100 / totalHabits
}
avgPercent := (taskPercent + habitPercent) / 2
if totalTasks == 0 {
avgPercent = habitPercent
}
if totalHabits == 0 {
avgPercent = taskPercent
}
if avgPercent == 100 {
text += "🎉 Отличный день! Всё выполнено!"
} else if avgPercent >= 75 {
text += "👍 Хороший день! Почти всё сделано."
} else if avgPercent >= 50 {
text += "💪 Неплохо! Завтра будет лучше."
} else {
text += "🌱 Бывает. Завтра новый день!"
}
s.bot.SendMessage(chatID, text)
}
func (s *Scheduler) checkTaskReminders(userID, chatID int64, currentTime, today string) {
tasks, err := s.taskRepo.GetTasksWithReminder(currentTime, today)
if err != nil {
log.Printf("Scheduler: error getting task reminders: %v", err)
return
}
for _, task := range tasks {
if task.UserID != userID {
continue
}
text := fmt.Sprintf("⏰ <b>Напоминание о задаче:</b>\n\n%s <b>%s</b>", task.Icon, task.Title)
if task.Description != "" {
text += fmt.Sprintf("\n<i>%s</i>", task.Description)
}
keyboard := tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("✅ Выполнено", fmt.Sprintf("donetask_%d", task.ID)),
tgbotapi.NewInlineKeyboardButtonData("⏰ +30 мин", fmt.Sprintf("snoozetask_%d", task.ID)),
),
)
s.bot.SendMessageWithKeyboard(chatID, text, &keyboard)
}
}
func (s *Scheduler) checkHabitReminders(userID, chatID int64, currentTime string, weekday int) {
habits, err := s.habitRepo.GetHabitsWithReminder(currentTime, weekday)
if err != nil {
log.Printf("Scheduler: error getting habit reminders: %v", err)
return
}
for _, habit := range habits {
if habit.UserID != userID {
continue
}
// Check if already completed today
completed, _ := s.habitRepo.IsHabitCompletedToday(habit.ID, userID)
if completed {
continue
}
text := fmt.Sprintf("⏰ <b>Напоминание о привычке:</b>\n\n%s <b>%s</b>", habit.Icon, habit.Name)
if habit.Description != "" {
text += fmt.Sprintf("\n<i>%s</i>", habit.Description)
}
keyboard := tgbotapi.NewInlineKeyboardMarkup(
tgbotapi.NewInlineKeyboardRow(
tgbotapi.NewInlineKeyboardButtonData("✅ Выполнено", fmt.Sprintf("checkhabit_%d", habit.ID)),
tgbotapi.NewInlineKeyboardButtonData("⏰ +30 мин", fmt.Sprintf("snoozehabit_%d", habit.ID)),
),
)
s.bot.SendMessageWithKeyboard(chatID, text, &keyboard)
}
}