feat: add Telegram bot with notifications and scheduler

This commit is contained in:
Cosmo
2026-02-06 13:16:50 +00:00
parent 5a40127edd
commit 9e467b0448
19 changed files with 1007 additions and 110 deletions

View File

@@ -23,8 +23,8 @@ func NewHabitRepository(db *sqlx.DB) *HabitRepository {
func (r *HabitRepository) Create(habit *model.Habit) error {
query := `
INSERT INTO habits (user_id, name, description, color, icon, frequency, target_days, target_count)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
INSERT INTO habits (user_id, name, description, color, icon, frequency, target_days, target_count, reminder_time)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
RETURNING id, created_at, updated_at`
targetDays := pq.Array(habit.TargetDays)
@@ -41,6 +41,7 @@ func (r *HabitRepository) Create(habit *model.Habit) error {
habit.Frequency,
targetDays,
habit.TargetCount,
habit.ReminderTime,
).Scan(&habit.ID, &habit.CreatedAt, &habit.UpdatedAt)
}
@@ -49,13 +50,13 @@ func (r *HabitRepository) GetByID(id, userID int64) (*model.Habit, error) {
var targetDays pq.Int64Array
query := `
SELECT id, user_id, name, description, color, icon, frequency, target_days, target_count, is_archived, created_at, updated_at
SELECT id, user_id, name, description, color, icon, frequency, target_days, target_count, reminder_time, is_archived, created_at, updated_at
FROM habits WHERE id = $1 AND user_id = $2`
err := r.db.QueryRow(query, id, userID).Scan(
&habit.ID, &habit.UserID, &habit.Name, &habit.Description,
&habit.Color, &habit.Icon, &habit.Frequency, &targetDays,
&habit.TargetCount, &habit.IsArchived, &habit.CreatedAt, &habit.UpdatedAt,
&habit.TargetCount, &habit.ReminderTime, &habit.IsArchived, &habit.CreatedAt, &habit.UpdatedAt,
)
if err != nil {
@@ -69,13 +70,14 @@ func (r *HabitRepository) GetByID(id, userID int64) (*model.Habit, error) {
for i, v := range targetDays {
habit.TargetDays[i] = int(v)
}
habit.ProcessForJSON()
return &habit, nil
}
func (r *HabitRepository) ListByUser(userID int64, includeArchived bool) ([]model.Habit, error) {
query := `
SELECT id, user_id, name, description, color, icon, frequency, target_days, target_count, is_archived, created_at, updated_at
SELECT id, user_id, name, description, color, icon, frequency, target_days, target_count, reminder_time, is_archived, created_at, updated_at
FROM habits WHERE user_id = $1`
if !includeArchived {
@@ -97,7 +99,7 @@ func (r *HabitRepository) ListByUser(userID int64, includeArchived bool) ([]mode
if err := rows.Scan(
&habit.ID, &habit.UserID, &habit.Name, &habit.Description,
&habit.Color, &habit.Icon, &habit.Frequency, &targetDays,
&habit.TargetCount, &habit.IsArchived, &habit.CreatedAt, &habit.UpdatedAt,
&habit.TargetCount, &habit.ReminderTime, &habit.IsArchived, &habit.CreatedAt, &habit.UpdatedAt,
); err != nil {
return nil, err
}
@@ -106,6 +108,49 @@ func (r *HabitRepository) ListByUser(userID int64, includeArchived bool) ([]mode
for i, v := range targetDays {
habit.TargetDays[i] = int(v)
}
habit.ProcessForJSON()
habits = append(habits, habit)
}
return habits, nil
}
func (r *HabitRepository) GetHabitsWithReminder(reminderTime string, weekday int) ([]model.Habit, error) {
query := `
SELECT h.id, h.user_id, h.name, h.description, h.color, h.icon, h.frequency, h.target_days, h.target_count, h.reminder_time, h.is_archived, h.created_at, h.updated_at
FROM habits h
JOIN users u ON h.user_id = u.id
WHERE h.reminder_time = $1
AND h.is_archived = false
AND (h.frequency = 'daily' OR $2 = ANY(h.target_days))
AND u.telegram_chat_id IS NOT NULL
AND u.notifications_enabled = true`
rows, err := r.db.Query(query, reminderTime, weekday)
if err != nil {
return nil, err
}
defer rows.Close()
var habits []model.Habit
for rows.Next() {
var habit model.Habit
var targetDays pq.Int64Array
if err := rows.Scan(
&habit.ID, &habit.UserID, &habit.Name, &habit.Description,
&habit.Color, &habit.Icon, &habit.Frequency, &targetDays,
&habit.TargetCount, &habit.ReminderTime, &habit.IsArchived, &habit.CreatedAt, &habit.UpdatedAt,
); err != nil {
return nil, err
}
habit.TargetDays = make([]int, len(targetDays))
for i, v := range targetDays {
habit.TargetDays[i] = int(v)
}
habit.ProcessForJSON()
habits = append(habits, habit)
}
@@ -117,8 +162,8 @@ func (r *HabitRepository) Update(habit *model.Habit) error {
query := `
UPDATE habits
SET name = $2, description = $3, color = $4, icon = $5, frequency = $6,
target_days = $7, target_count = $8, is_archived = $9, updated_at = CURRENT_TIMESTAMP
WHERE id = $1 AND user_id = $10
target_days = $7, target_count = $8, reminder_time = $9, is_archived = $10, updated_at = CURRENT_TIMESTAMP
WHERE id = $1 AND user_id = $11
RETURNING updated_at`
return r.db.QueryRow(query,
@@ -130,6 +175,7 @@ func (r *HabitRepository) Update(habit *model.Habit) error {
habit.Frequency,
pq.Array(habit.TargetDays),
habit.TargetCount,
habit.ReminderTime,
habit.IsArchived,
habit.UserID,
).Scan(&habit.UpdatedAt)
@@ -208,6 +254,13 @@ func (r *HabitRepository) GetUserLogsForDate(userID int64, date time.Time) ([]mo
return logs, nil
}
func (r *HabitRepository) IsHabitCompletedToday(habitID, userID int64) (bool, error) {
today := time.Now().Format("2006-01-02")
var count int
err := r.db.Get(&count, `SELECT COUNT(*) FROM habit_logs WHERE habit_id = $1 AND user_id = $2 AND date = $3`, habitID, userID, today)
return count > 0, err
}
func (r *HabitRepository) GetStats(habitID, userID int64) (*model.HabitStats, error) {
stats := &model.HabitStats{HabitID: habitID}