diff --git a/internal/bot/bot.go b/internal/bot/bot.go
index ba2fa80..d3bf759 100644
--- a/internal/bot/bot.go
+++ b/internal/bot/bot.go
@@ -47,11 +47,14 @@ func (b *Bot) Start() {
updates := b.api.GetUpdatesChan(u)
for update := range updates {
- if update.Message == nil {
+ if update.CallbackQuery != nil {
+ go b.handleCallback(update.CallbackQuery)
continue
}
-
- go b.handleMessage(update.Message)
+
+ if update.Message != nil {
+ go b.handleMessage(update.Message)
+ }
}
}
@@ -66,6 +69,20 @@ func (b *Bot) SendMessage(chatID int64, text string) error {
return err
}
+func (b *Bot) SendMessageWithKeyboard(chatID int64, text string, keyboard *tgbotapi.InlineKeyboardMarkup) error {
+ if b == nil || b.api == nil {
+ return nil
+ }
+
+ msg := tgbotapi.NewMessage(chatID, text)
+ msg.ParseMode = "HTML"
+ if keyboard != nil {
+ msg.ReplyMarkup = keyboard
+ }
+ _, err := b.api.Send(msg)
+ return err
+}
+
func (b *Bot) GetAPI() *tgbotapi.BotAPI {
if b == nil {
return nil
diff --git a/internal/bot/handlers.go b/internal/bot/handlers.go
index 66c0494..f89277e 100644
--- a/internal/bot/handlers.go
+++ b/internal/bot/handlers.go
@@ -16,7 +16,19 @@ func (b *Bot) handleMessage(msg *tgbotapi.Message) {
return
}
- switch msg.Command() {
+ cmd := msg.Command()
+
+ // Handle commands like /done_123 or /check_123
+ if strings.HasPrefix(cmd, "done_") {
+ b.handleDoneByID(msg, strings.TrimPrefix(cmd, "done_"))
+ return
+ }
+ if strings.HasPrefix(cmd, "check_") {
+ b.handleCheckByID(msg, strings.TrimPrefix(cmd, "check_"))
+ return
+ }
+
+ switch cmd {
case "start":
b.handleStart(msg)
case "tasks":
@@ -32,6 +44,196 @@ func (b *Bot) handleMessage(msg *tgbotapi.Message) {
}
}
+func (b *Bot) handleCallback(callback *tgbotapi.CallbackQuery) {
+ data := callback.Data
+ chatID := callback.Message.Chat.ID
+ messageID := callback.Message.MessageID
+
+ user, err := b.userRepo.GetByTelegramChatID(chatID)
+ if err != nil {
+ b.answerCallback(callback.ID, "❌ Аккаунт не найден")
+ return
+ }
+
+ parts := strings.Split(data, "_")
+ if len(parts) < 2 {
+ return
+ }
+
+ action := parts[0]
+ id, _ := strconv.ParseInt(parts[1], 10, 64)
+
+ switch action {
+ case "donetask":
+ err = b.taskRepo.Complete(id, user.ID)
+ if err != nil {
+ b.answerCallback(callback.ID, "❌ Ошибка")
+ return
+ }
+ b.answerCallback(callback.ID, "✅ Задача выполнена!")
+ b.refreshTasksMessage(chatID, messageID, user.ID)
+
+ case "deltask":
+ err = b.taskRepo.Delete(id, user.ID)
+ if err != nil {
+ b.answerCallback(callback.ID, "❌ Ошибка")
+ return
+ }
+ b.answerCallback(callback.ID, "🗑 Задача удалена")
+ b.refreshTasksMessage(chatID, messageID, user.ID)
+
+ case "checkhabit":
+ log := &model.HabitLog{
+ HabitID: id,
+ UserID: user.ID,
+ Date: time.Now(),
+ Count: 1,
+ }
+ err = b.habitRepo.CreateLog(log)
+ if err != nil {
+ b.answerCallback(callback.ID, "❌ Ошибка")
+ return
+ }
+ b.answerCallback(callback.ID, "✅ Привычка отмечена!")
+ b.refreshHabitsMessage(chatID, messageID, user.ID)
+ }
+}
+
+func (b *Bot) answerCallback(callbackID, text string) {
+ callback := tgbotapi.NewCallback(callbackID, text)
+ b.api.Request(callback)
+}
+
+func (b *Bot) refreshTasksMessage(chatID int64, messageID int, userID int64) {
+ tasks, err := b.taskRepo.GetTodayTasks(userID)
+ if err != nil {
+ return
+ }
+
+ text, keyboard := b.buildTasksMessage(tasks)
+
+ edit := tgbotapi.NewEditMessageText(chatID, messageID, text)
+ edit.ParseMode = "HTML"
+ if keyboard != nil {
+ edit.ReplyMarkup = keyboard
+ }
+ b.api.Send(edit)
+}
+
+func (b *Bot) refreshHabitsMessage(chatID int64, messageID int, userID int64) {
+ habits, _ := b.habitRepo.ListByUser(userID, false)
+
+ text, keyboard := b.buildHabitsMessage(habits, userID)
+
+ edit := tgbotapi.NewEditMessageText(chatID, messageID, text)
+ edit.ParseMode = "HTML"
+ if keyboard != nil {
+ edit.ReplyMarkup = keyboard
+ }
+ b.api.Send(edit)
+}
+
+func (b *Bot) buildTasksMessage(tasks []model.Task) (string, *tgbotapi.InlineKeyboardMarkup) {
+ if len(tasks) == 0 {
+ return "✨ На сегодня задач нет!", nil
+ }
+
+ text := "📋 Задачи на сегодня:\n\n"
+ var rows [][]tgbotapi.InlineKeyboardButton
+
+ for _, task := range tasks {
+ priority := ""
+ switch task.Priority {
+ case 3:
+ priority = "🔴 "
+ case 2:
+ priority = "🟡 "
+ case 1:
+ priority = "🔵 "
+ }
+
+ status := "⬜"
+ if task.CompletedAt.Valid {
+ status = "✅"
+ }
+
+ text += fmt.Sprintf("%s %s%s %s\n", status, priority, task.Icon, task.Title)
+ if task.Description != "" {
+ text += fmt.Sprintf(" %s\n", task.Description)
+ }
+ text += "\n"
+
+ // Add buttons only for incomplete tasks
+ if !task.CompletedAt.Valid {
+ row := []tgbotapi.InlineKeyboardButton{
+ tgbotapi.NewInlineKeyboardButtonData("✅ Выполнить", fmt.Sprintf("donetask_%d", task.ID)),
+ tgbotapi.NewInlineKeyboardButtonData("🗑 Удалить", fmt.Sprintf("deltask_%d", task.ID)),
+ }
+ rows = append(rows, row)
+ }
+ }
+
+ if len(rows) == 0 {
+ return text, nil
+ }
+
+ keyboard := tgbotapi.NewInlineKeyboardMarkup(rows...)
+ return text, &keyboard
+}
+
+func (b *Bot) buildHabitsMessage(habits []model.Habit, userID int64) (string, *tgbotapi.InlineKeyboardMarkup) {
+ today := int(time.Now().Weekday())
+ var todayHabits []model.Habit
+
+ for _, habit := range habits {
+ if habit.Frequency == "daily" {
+ todayHabits = append(todayHabits, habit)
+ } else {
+ for _, day := range habit.TargetDays {
+ if day == today {
+ todayHabits = append(todayHabits, habit)
+ break
+ }
+ }
+ }
+ }
+
+ if len(todayHabits) == 0 {
+ return "✨ На сегодня привычек нет!", nil
+ }
+
+ text := "🎯 Привычки на сегодня:\n\n"
+ var rows [][]tgbotapi.InlineKeyboardButton
+
+ for _, habit := range todayHabits {
+ completed, _ := b.habitRepo.IsHabitCompletedToday(habit.ID, userID)
+ status := "⬜"
+ if completed {
+ status = "✅"
+ }
+
+ text += fmt.Sprintf("%s %s %s\n", status, habit.Icon, habit.Name)
+ if habit.Description != "" {
+ text += fmt.Sprintf(" %s\n", habit.Description)
+ }
+ text += "\n"
+
+ if !completed {
+ row := []tgbotapi.InlineKeyboardButton{
+ tgbotapi.NewInlineKeyboardButtonData(fmt.Sprintf("✅ %s", habit.Name), fmt.Sprintf("checkhabit_%d", habit.ID)),
+ }
+ rows = append(rows, row)
+ }
+ }
+
+ if len(rows) == 0 {
+ return text, nil
+ }
+
+ keyboard := tgbotapi.NewInlineKeyboardMarkup(rows...)
+ return text, &keyboard
+}
+
func (b *Bot) handleStart(msg *tgbotapi.Message) {
text := fmt.Sprintf(`👋 Привет! Я бот Pulse.
@@ -42,8 +244,6 @@ func (b *Bot) handleStart(msg *tgbotapi.Message) {
Доступные команды:
/tasks — задачи на сегодня
/habits — привычки на сегодня
-/done <id> — отметить задачу выполненной
-/check <id> — отметить привычку выполненной
/help — справка`, msg.Chat.ID)
b.SendMessage(msg.Chat.ID, text)
@@ -55,8 +255,6 @@ func (b *Bot) handleHelp(msg *tgbotapi.Message) {
/start — получить твой Chat ID
/tasks — список задач на сегодня
/habits — список привычек
-/done <id> — отметить задачу выполненной
-/check <id> — отметить привычку выполненной
💡 Чтобы получать уведомления, добавь свой Chat ID в настройках Pulse.`
@@ -80,30 +278,8 @@ func (b *Bot) handleTasks(msg *tgbotapi.Message) {
return
}
- if len(tasks) == 0 {
- b.SendMessage(msg.Chat.ID, "✨ На сегодня задач нет!")
- return
- }
-
- text := "📋 Задачи на сегодня:\n\n"
- for _, task := range tasks {
- priority := ""
- switch task.Priority {
- case 3:
- priority = "🔴 "
- case 2:
- priority = "🟡 "
- case 1:
- priority = "🔵 "
- }
- text += fmt.Sprintf("%s%s %s\n", priority, task.Icon, task.Title)
- if task.Description != "" {
- text += fmt.Sprintf(" %s\n", task.Description)
- }
- text += fmt.Sprintf(" /done_%d\n\n", task.ID)
- }
-
- b.SendMessage(msg.Chat.ID, text)
+ text, keyboard := b.buildTasksMessage(tasks)
+ b.SendMessageWithKeyboard(msg.Chat.ID, text, keyboard)
}
func (b *Bot) handleHabits(msg *tgbotapi.Message) {
@@ -123,74 +299,29 @@ func (b *Bot) handleHabits(msg *tgbotapi.Message) {
return
}
- if len(habits) == 0 {
- b.SendMessage(msg.Chat.ID, "✨ У тебя пока нет привычек!")
- return
- }
-
- // Filter habits for today
- today := int(time.Now().Weekday())
- var todayHabits []model.Habit
- for _, habit := range habits {
- if habit.Frequency == "daily" {
- todayHabits = append(todayHabits, habit)
- } else {
- for _, day := range habit.TargetDays {
- if day == today {
- todayHabits = append(todayHabits, habit)
- break
- }
- }
- }
- }
-
- if len(todayHabits) == 0 {
- b.SendMessage(msg.Chat.ID, "✨ На сегодня привычек нет!")
- return
- }
-
- text := "🎯 Привычки на сегодня:\n\n"
- for _, habit := range todayHabits {
- completed, _ := b.habitRepo.IsHabitCompletedToday(habit.ID, user.ID)
- status := "⬜"
- if completed {
- status = "✅"
- }
- text += fmt.Sprintf("%s %s %s\n", status, habit.Icon, habit.Name)
- if habit.Description != "" {
- text += fmt.Sprintf(" %s\n", habit.Description)
- }
- if !completed {
- text += fmt.Sprintf(" /check_%d\n", habit.ID)
- }
- text += "\n"
- }
-
- b.SendMessage(msg.Chat.ID, text)
+ text, keyboard := b.buildHabitsMessage(habits, user.ID)
+ b.SendMessageWithKeyboard(msg.Chat.ID, text, keyboard)
}
func (b *Bot) handleDone(msg *tgbotapi.Message) {
+ args := msg.CommandArguments()
+ if args == "" {
+ b.SendMessage(msg.Chat.ID, "❌ Укажи ID задачи: /done ")
+ return
+ }
+ b.handleDoneByID(msg, args)
+}
+
+func (b *Bot) handleDoneByID(msg *tgbotapi.Message, idStr string) {
user, err := b.userRepo.GetByTelegramChatID(msg.Chat.ID)
if err != nil {
b.SendMessage(msg.Chat.ID, "❌ Аккаунт не найден")
return
}
- // Parse task ID from command argument or from command like /done_123
- var taskID int64
- args := msg.CommandArguments()
- if args != "" {
- taskID, _ = strconv.ParseInt(args, 10, 64)
- } else {
- // Try to parse from command like /done_123
- cmd := msg.Command()
- if strings.HasPrefix(cmd, "done_") {
- taskID, _ = strconv.ParseInt(strings.TrimPrefix(cmd, "done_"), 10, 64)
- }
- }
-
+ taskID, _ := strconv.ParseInt(idStr, 10, 64)
if taskID == 0 {
- b.SendMessage(msg.Chat.ID, "❌ Укажи ID задачи: /done <id>")
+ b.SendMessage(msg.Chat.ID, "❌ Неверный ID задачи")
return
}
@@ -208,31 +339,27 @@ func (b *Bot) handleDone(msg *tgbotapi.Message) {
}
func (b *Bot) handleCheck(msg *tgbotapi.Message) {
+ args := msg.CommandArguments()
+ if args == "" {
+ b.SendMessage(msg.Chat.ID, "❌ Укажи ID привычки: /check ")
+ return
+ }
+ b.handleCheckByID(msg, args)
+}
+
+func (b *Bot) handleCheckByID(msg *tgbotapi.Message, idStr string) {
user, err := b.userRepo.GetByTelegramChatID(msg.Chat.ID)
if err != nil {
b.SendMessage(msg.Chat.ID, "❌ Аккаунт не найден")
return
}
- // Parse habit ID from command argument or from command like /check_123
- var habitID int64
- args := msg.CommandArguments()
- if args != "" {
- habitID, _ = strconv.ParseInt(args, 10, 64)
- } else {
- // Try to parse from command like /check_123
- cmd := msg.Command()
- if strings.HasPrefix(cmd, "check_") {
- habitID, _ = strconv.ParseInt(strings.TrimPrefix(cmd, "check_"), 10, 64)
- }
- }
-
+ habitID, _ := strconv.ParseInt(idStr, 10, 64)
if habitID == 0 {
- b.SendMessage(msg.Chat.ID, "❌ Укажи ID привычки: /check <id>")
+ b.SendMessage(msg.Chat.ID, "❌ Неверный ID привычки")
return
}
- // Verify habit exists and belongs to user
_, err = b.habitRepo.GetByID(habitID, user.ID)
if err != nil {
if err == repository.ErrHabitNotFound {
@@ -243,7 +370,6 @@ func (b *Bot) handleCheck(msg *tgbotapi.Message) {
return
}
- // Create log for today
log := &model.HabitLog{
HabitID: habitID,
UserID: user.ID,
diff --git a/internal/handler/profile.go b/internal/handler/profile.go
index f27074f..56df786 100644
--- a/internal/handler/profile.go
+++ b/internal/handler/profile.go
@@ -28,11 +28,13 @@ func (h *ProfileHandler) Get(w http.ResponseWriter, r *http.Request) {
user.ProcessForJSON()
- // Return only profile fields
profile := map[string]interface{}{
+ "username": user.Username,
"telegram_chat_id": user.TelegramChatIDValue,
"notifications_enabled": user.NotificationsEnabled,
"timezone": user.Timezone,
+ "morning_reminder_time": user.MorningTime,
+ "evening_reminder_time": user.EveningTime,
}
writeJSON(w, profile, http.StatusOK)
@@ -63,9 +65,12 @@ func (h *ProfileHandler) Update(w http.ResponseWriter, r *http.Request) {
user.ProcessForJSON()
profile := map[string]interface{}{
+ "username": user.Username,
"telegram_chat_id": user.TelegramChatIDValue,
"notifications_enabled": user.NotificationsEnabled,
"timezone": user.Timezone,
+ "morning_reminder_time": user.MorningTime,
+ "evening_reminder_time": user.EveningTime,
}
writeJSON(w, profile, http.StatusOK)
diff --git a/internal/model/user.go b/internal/model/user.go
index 4227a95..53af0ae 100644
--- a/internal/model/user.go
+++ b/internal/model/user.go
@@ -6,23 +6,37 @@ import (
)
type User struct {
- ID int64 `db:"id" json:"id"`
- Email string `db:"email" json:"email"`
- Username string `db:"username" json:"username"`
- PasswordHash string `db:"password_hash" json:"-"`
- EmailVerified bool `db:"email_verified" json:"email_verified"`
- TelegramChatID sql.NullInt64 `db:"telegram_chat_id" json:"-"`
- TelegramChatIDValue *int64 `db:"-" json:"telegram_chat_id"`
- NotificationsEnabled bool `db:"notifications_enabled" json:"notifications_enabled"`
- Timezone string `db:"timezone" json:"timezone"`
- CreatedAt time.Time `db:"created_at" json:"created_at"`
- UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
+ ID int64 `db:"id" json:"id"`
+ Email string `db:"email" json:"email"`
+ Username string `db:"username" json:"username"`
+ PasswordHash string `db:"password_hash" json:"-"`
+ EmailVerified bool `db:"email_verified" json:"email_verified"`
+ TelegramChatID sql.NullInt64 `db:"telegram_chat_id" json:"-"`
+ TelegramChatIDValue *int64 `db:"-" json:"telegram_chat_id"`
+ NotificationsEnabled bool `db:"notifications_enabled" json:"notifications_enabled"`
+ Timezone string `db:"timezone" json:"timezone"`
+ MorningReminderTime sql.NullString `db:"morning_reminder_time" json:"-"`
+ EveningReminderTime sql.NullString `db:"evening_reminder_time" json:"-"`
+ MorningTime string `db:"-" json:"morning_reminder_time"`
+ EveningTime string `db:"-" json:"evening_reminder_time"`
+ CreatedAt time.Time `db:"created_at" json:"created_at"`
+ UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
}
func (u *User) ProcessForJSON() {
if u.TelegramChatID.Valid {
u.TelegramChatIDValue = &u.TelegramChatID.Int64
}
+ if u.MorningReminderTime.Valid {
+ u.MorningTime = u.MorningReminderTime.String[:5] // "09:00:00" -> "09:00"
+ } else {
+ u.MorningTime = "09:00"
+ }
+ if u.EveningReminderTime.Valid {
+ u.EveningTime = u.EveningReminderTime.String[:5]
+ } else {
+ u.EveningTime = "21:00"
+ }
}
type RegisterRequest struct {
@@ -41,6 +55,8 @@ type UpdateProfileRequest struct {
TelegramChatID *int64 `json:"telegram_chat_id,omitempty"`
NotificationsEnabled *bool `json:"notifications_enabled,omitempty"`
Timezone *string `json:"timezone,omitempty"`
+ MorningReminderTime *string `json:"morning_reminder_time,omitempty"`
+ EveningReminderTime *string `json:"evening_reminder_time,omitempty"`
}
type ChangePasswordRequest struct {
diff --git a/internal/repository/user.go b/internal/repository/user.go
index 38cfec6..fa65d03 100644
--- a/internal/repository/user.go
+++ b/internal/repository/user.go
@@ -182,6 +182,16 @@ func (r *UserRepository) UpdateProfile(id int64, req *model.UpdateProfileRequest
args = append(args, *req.Timezone)
argIdx++
}
+ if req.MorningReminderTime != nil {
+ query += fmt.Sprintf(", morning_reminder_time = $%d", argIdx)
+ args = append(args, *req.MorningReminderTime)
+ argIdx++
+ }
+ if req.EveningReminderTime != nil {
+ query += fmt.Sprintf(", evening_reminder_time = $%d", argIdx)
+ args = append(args, *req.EveningReminderTime)
+ argIdx++
+ }
query += " WHERE id = $1"
_, err := r.db.Exec(query, args...)
diff --git a/internal/scheduler/scheduler.go b/internal/scheduler/scheduler.go
index 1f3ac0d..bead38b 100644
--- a/internal/scheduler/scheduler.go
+++ b/internal/scheduler/scheduler.go
@@ -6,6 +6,7 @@ import (
"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"
@@ -76,15 +77,30 @@ func (s *Scheduler) checkUserNotifications(user model.User) {
today := now.Format("2006-01-02")
weekday := int(now.Weekday())
- // 1. Morning briefing at 09:00
- if currentTime == "09:00" {
+ // 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. Task reminders
+ // 2. Evening summary
+ if currentTime == eveningTime {
+ s.sendEveningSummary(user.ID, chatID, loc)
+ }
+
+ // 3. Task reminders
s.checkTaskReminders(user.ID, chatID, currentTime, today)
- // 3. Habit reminders
+ // 4. Habit reminders
s.checkHabitReminders(user.ID, chatID, currentTime, weekday)
}
@@ -139,7 +155,125 @@ func (s *Scheduler) sendMorningBriefing(userID, chatID int64, loc *time.Location
text += fmt.Sprintf("🎯 Привычек: %d\n", todayHabits)
}
- text += "\n/tasks — посмотреть задачи\n/habits — посмотреть привычки"
+ 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 := "🌙 Итоги дня\n\n"
+
+ // Tasks summary
+ if totalTasks > 0 {
+ text += "📋 Задачи:\n"
+ text += fmt.Sprintf(" ✅ Выполнено: %d\n", completedTasks)
+ text += fmt.Sprintf(" ⬜ Осталось: %d\n", incompleteTasks)
+ text += "\n"
+ }
+
+ // Habits summary
+ if totalHabits > 0 {
+ text += "🎯 Привычки:\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)
}
@@ -160,9 +294,15 @@ func (s *Scheduler) checkTaskReminders(userID, chatID int64, currentTime, today
if task.Description != "" {
text += fmt.Sprintf("\n%s", task.Description)
}
- text += fmt.Sprintf("\n\n/done_%d — отметить выполненной", task.ID)
- s.bot.SendMessage(chatID, text)
+ 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)
}
}
@@ -188,8 +328,14 @@ func (s *Scheduler) checkHabitReminders(userID, chatID int64, currentTime string
if habit.Description != "" {
text += fmt.Sprintf("\n%s", habit.Description)
}
- text += fmt.Sprintf("\n\n/check_%d — отметить выполненной", habit.ID)
- s.bot.SendMessage(chatID, text)
+ 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)
}
}
diff --git a/internal/service/auth.go b/internal/service/auth.go
index 329ed2b..d5dece1 100644
--- a/internal/service/auth.go
+++ b/internal/service/auth.go
@@ -244,7 +244,7 @@ func (s *AuthService) generateAuthResponse(user *model.User) (*model.AuthRespons
return nil, err
}
- refreshToken, err := s.generateToken(user.ID, "refresh", 30*24*time.Hour)
+ refreshToken, err := s.generateToken(user.ID, "refresh", 365*24*time.Hour)
if err != nil {
return nil, err
}