From 72da547b80e488a72aa924e308c044ca8aacc11f Mon Sep 17 00:00:00 2001 From: poignatov Date: Mon, 9 Feb 2026 15:47:57 +0300 Subject: [PATCH] =?UTF-8?q?5.0.8:=20=D1=87=D0=B0=D1=81=D0=BE=D0=B2=D0=BE?= =?UTF-8?q?=D0=B9=20=D0=BF=D0=BE=D1=8F=D1=81,=20=D1=80=D0=B0=D0=B7=D0=B1?= =?UTF-8?q?=D0=BB=D0=BE=D0=BA=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D1=8E=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=B4=D0=B0=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION | 2 +- play-life-backend/main.go | 139 +++++++++++++++++++++++++++---------- play-life-web/package.json | 2 +- 3 files changed, 106 insertions(+), 37 deletions(-) diff --git a/VERSION b/VERSION index c20c645..51e67ba 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.0.6 +5.0.8 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index 1ce11f1..f1133e4 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -3886,8 +3886,14 @@ func (a *App) syncFitbitDataForAllUsers() error { log.Printf("Syncing Fitbit data for %d users", len(userIDs)) - // Синхронизируем данные за сегодня для каждого пользователя - today := time.Now() + // Синхронизируем данные за сегодня для каждого пользователя (в настроенном часовом поясе) + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, err := time.LoadLocation(timezoneStr) + if err != nil { + log.Printf("Warning: Invalid timezone '%s': %v. Using UTC for Fitbit sync.", timezoneStr, err) + loc = time.UTC + } + today := time.Now().In(loc) for _, userID := range userIDs { if err := a.syncFitbitData(userID, today); err != nil { log.Printf("Failed to sync Fitbit data for user_id=%d: %v", userID, err) @@ -4062,8 +4068,9 @@ func main() { // Логируем параметры подключения к БД (без пароля) log.Printf("Database connection parameters: host=%s port=%s user=%s dbname=%s", dbHost, dbPort, dbUser, dbName) - dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", - dbHost, dbPort, dbUser, dbPassword, dbName) + timezoneStr := getEnv("TIMEZONE", "UTC") + dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable options='-c timezone=%s'", + dbHost, dbPort, dbUser, dbPassword, dbName, timezoneStr) var db *sql.DB var err error @@ -4832,8 +4839,14 @@ func (a *App) processTelegramMessage(fullText string, entities []TelegramEntity, } processedText := strings.Join(cleanedLines, "\n") - // Используем текущее время в формате ISO 8601 (UTC) - createdDate := time.Now().UTC().Format(time.RFC3339) + // Используем текущее время в формате ISO 8601 в настроенном часовом поясе + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, err := time.LoadLocation(timezoneStr) + if err != nil { + log.Printf("Warning: Invalid timezone '%s': %v. Using UTC instead.", timezoneStr, err) + loc = time.UTC + } + createdDate := time.Now().In(loc).Format(time.RFC3339) // Вставляем данные в БД только если есть nodes if len(scoreNodes) > 0 { @@ -4929,8 +4942,14 @@ func (a *App) processMessageInternal(rawText string, sendToTelegram bool, userID // Формируем Markdown (Legacy) контент: заменяем ** на * markdownText := strings.ReplaceAll(rawText, "**", "*") - // Используем текущее время - createdDate := time.Now().UTC().Format(time.RFC3339) + // Используем текущее время в настроенном часовом поясе + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, err := time.LoadLocation(timezoneStr) + if err != nil { + log.Printf("Warning: Invalid timezone '%s': %v. Using UTC instead.", timezoneStr, err) + loc = time.UTC + } + createdDate := time.Now().In(loc).Format(time.RFC3339) // Вставляем данные в БД только если есть nodes if len(nodes) > 0 { @@ -6696,8 +6715,13 @@ func (a *App) getFullStatisticsHandler(w http.ResponseWriter, r *http.Request) { return } - // Получаем ISO год и неделю для текущей даты - now := time.Now() + // Получаем ISO год и неделю для текущей даты (в настроенном часовом поясе) + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, locErr := time.LoadLocation(timezoneStr) + if locErr != nil { + loc = time.UTC + } + now := time.Now().In(loc) _, currentWeekInt := now.ISOWeek() currentYearInt := now.Year() @@ -6849,10 +6873,10 @@ func (a *App) getFullStatisticsHandler(w http.ResponseWriter, r *http.Request) { normalizedScore = math.Min(totalScore, maxGoalScore) } - _, weekISO := time.Now().ISOWeek() + _, weekISO := now.ISOWeek() item := FullStatisticsItem{ ProjectName: projectName, - ReportYear: time.Now().Year(), + ReportYear: now.Year(), ReportWeek: weekISO, TotalScore: totalScore, NormalizedTotalScore: normalizedScore, @@ -6892,7 +6916,7 @@ func (a *App) getTodayEntriesHandler(w http.ResponseWriter, r *http.Request) { projectFilter = &projectName } - // Получаем дату из query string (формат: YYYY-MM-DD), если не указана - используем сегодня + // Получаем дату из query string (формат: YYYY-MM-DD), если не указана - используем сегодня в настроенном часовом поясе dateParam := r.URL.Query().Get("date") var targetDate time.Time if dateParam != "" { @@ -6904,7 +6928,13 @@ func (a *App) getTodayEntriesHandler(w http.ResponseWriter, r *http.Request) { } targetDate = parsedDate } else { - targetDate = time.Now() + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, locErr := time.LoadLocation(timezoneStr) + if locErr != nil { + log.Printf("Warning: Invalid timezone '%s': %v. Using UTC for today entries.", timezoneStr, locErr) + loc = time.UTC + } + targetDate = time.Now().In(loc) } // Запрос для получения entries с nodes за указанный день @@ -10758,8 +10788,14 @@ func (a *App) fitbitSyncHandler(w http.ResponseWriter, r *http.Request) { return } - // Синхронизируем данные за сегодня - err := a.syncFitbitData(userID, time.Now()) + // Синхронизируем данные за сегодня (в настроенном часовом поясе) + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, locErr := time.LoadLocation(timezoneStr) + if locErr != nil { + log.Printf("Warning: Invalid timezone '%s': %v. Using UTC for Fitbit sync.", timezoneStr, locErr) + loc = time.UTC + } + err := a.syncFitbitData(userID, time.Now().In(loc)) if err != nil { log.Printf("Fitbit sync error: %v", err) sendErrorWithCORS(w, fmt.Sprintf("Sync failed: %v", err), http.StatusInternalServerError) @@ -10788,10 +10824,15 @@ func (a *App) getFitbitStatsHandler(w http.ResponseWriter, r *http.Request) { return } - // Получаем дату из query параметра (по умолчанию сегодня) + // Получаем дату из query параметра (по умолчанию сегодня в настроенном часовом поясе) dateStr := r.URL.Query().Get("date") if dateStr == "" { - dateStr = time.Now().Format("2006-01-02") + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, locErr := time.LoadLocation(timezoneStr) + if locErr != nil { + loc = time.UTC + } + dateStr = time.Now().In(loc).Format("2006-01-02") } var steps, floors, azm sql.NullInt64 @@ -11084,7 +11125,8 @@ func (a *App) checkWishlistUnlock(itemID int, userID int) (bool, error) { } else if err != nil { return false, err } else { - conditionMet = completed > 0 + // Задача существует и не удалена — условие не выполнено + conditionMet = false } } else if scoreConditionID.Valid { @@ -11472,8 +11514,9 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool) completedBool := true condition.TaskCompleted = &completedBool } else if err == nil { - conditionMet = completed > 0 - completedBool := conditionMet + // Задача существует и не удалена — условие не выполнено + conditionMet = false + completedBool := false condition.TaskCompleted = &completedBool } } @@ -11538,7 +11581,8 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool) completedBool := true condition.TaskCompleted = &completedBool } else if err == nil { - completedBool := completed > 0 + // Задача существует и не удалена — условие не выполнено + completedBool := false condition.TaskCompleted = &completedBool } } @@ -12381,9 +12425,14 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) { condition.TaskID = &taskIDVal var taskCompleted int err := a.DB.QueryRow(`SELECT completed FROM tasks WHERE id = $1 AND user_id = $2 AND deleted = FALSE`, taskID.Int64, conditionOwnerID).Scan(&taskCompleted) - if err == nil { - isCompleted := taskCompleted > 0 - condition.TaskCompleted = &isCompleted + if err == sql.ErrNoRows { + // Задача удалена или не существует — условие выполнено + t := true + condition.TaskCompleted = &t + } else if err == nil { + // Задача существует и не удалена — условие не выполнено + f := false + condition.TaskCompleted = &f } } if taskNextShowAt.Valid { @@ -12777,9 +12826,14 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) { condition.TaskID = &taskIDVal var taskCompleted int err := a.DB.QueryRow(`SELECT completed FROM tasks WHERE id = $1 AND user_id = $2 AND deleted = FALSE`, taskID.Int64, conditionOwnerID).Scan(&taskCompleted) - if err == nil { - isCompleted := taskCompleted > 0 - condition.TaskCompleted = &isCompleted + if err == sql.ErrNoRows { + // Задача удалена или не существует — условие выполнено + t := true + condition.TaskCompleted = &t + } else if err == nil { + // Задача существует и не удалена — условие не выполнено + f := false + condition.TaskCompleted = &f } } } else if scoreConditionID.Valid { @@ -14917,15 +14971,20 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem, if taskName.Valid { condition.TaskName = &taskName.String } - // Проверяем выполнена ли задача для владельца условия + // Условие выполнено, если задача удалена или не существует if taskID.Valid { taskIDVal := int(taskID.Int64) condition.TaskID = &taskIDVal var taskCompleted int err := a.DB.QueryRow(`SELECT completed FROM tasks WHERE id = $1 AND user_id = $2 AND deleted = FALSE`, taskID.Int64, conditionOwnerID).Scan(&taskCompleted) - if err == nil { - isCompleted := taskCompleted > 0 - condition.TaskCompleted = &isCompleted + if err == sql.ErrNoRows { + // Задача удалена или не существует — условие выполнено + t := true + condition.TaskCompleted = &t + } else if err == nil { + // Задача существует и не удалена — условие не выполнено + f := false + condition.TaskCompleted = &f } } } else if scoreConditionID.Valid { @@ -15803,8 +15862,13 @@ func (a *App) proxyImageHandler(w http.ResponseWriter, r *http.Request) { // getWeeklyStatsDataForUserAndWeek получает данные о проектах для конкретного пользователя и недели func (a *App) getWeeklyStatsDataForUserAndWeek(userID int, year int, week int) (*WeeklyStatsResponse, error) { - // Определяем, является ли это текущей неделей - now := time.Now() + // Определяем, является ли это текущей неделей (в настроенном часовом поясе) + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, locErr := time.LoadLocation(timezoneStr) + if locErr != nil { + loc = time.UTC + } + now := time.Now().In(loc) currentYear, currentWeek := now.ISOWeek() isCurrentWeek := year == currentYear && week == currentWeek @@ -16000,7 +16064,12 @@ func (a *App) getTrackingStatsHandler(w http.ResponseWriter, r *http.Request) { weekStr := r.URL.Query().Get("week") var year, week int - now := time.Now() + timezoneStr := getEnv("TIMEZONE", "UTC") + loc, locErr := time.LoadLocation(timezoneStr) + if locErr != nil { + loc = time.UTC + } + now := time.Now().In(loc) if yearStr == "" || weekStr == "" { // Если не указаны - текущая неделя diff --git a/play-life-web/package.json b/play-life-web/package.json index f172dec..3f17b8d 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "5.0.6", + "version": "5.0.8", "type": "module", "scripts": { "dev": "vite",