5.0.8: часовой пояс, разблокировка по удалению задачи
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m21s

This commit is contained in:
poignatov
2026-02-09 15:47:57 +03:00
parent 1fe3819be6
commit 72da547b80
3 changed files with 106 additions and 37 deletions

View File

@@ -1 +1 @@
5.0.6 5.0.8

View File

@@ -3886,8 +3886,14 @@ func (a *App) syncFitbitDataForAllUsers() error {
log.Printf("Syncing Fitbit data for %d users", len(userIDs)) 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 { for _, userID := range userIDs {
if err := a.syncFitbitData(userID, today); err != nil { if err := a.syncFitbitData(userID, today); err != nil {
log.Printf("Failed to sync Fitbit data for user_id=%d: %v", userID, err) 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) 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", timezoneStr := getEnv("TIMEZONE", "UTC")
dbHost, dbPort, dbUser, dbPassword, dbName) 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 db *sql.DB
var err error var err error
@@ -4832,8 +4839,14 @@ func (a *App) processTelegramMessage(fullText string, entities []TelegramEntity,
} }
processedText := strings.Join(cleanedLines, "\n") processedText := strings.Join(cleanedLines, "\n")
// Используем текущее время в формате ISO 8601 (UTC) // Используем текущее время в формате ISO 8601 в настроенном часовом поясе
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 // Вставляем данные в БД только если есть nodes
if len(scoreNodes) > 0 { if len(scoreNodes) > 0 {
@@ -4929,8 +4942,14 @@ func (a *App) processMessageInternal(rawText string, sendToTelegram bool, userID
// Формируем Markdown (Legacy) контент: заменяем ** на * // Формируем Markdown (Legacy) контент: заменяем ** на *
markdownText := strings.ReplaceAll(rawText, "**", "*") 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 // Вставляем данные в БД только если есть nodes
if len(nodes) > 0 { if len(nodes) > 0 {
@@ -6696,8 +6715,13 @@ func (a *App) getFullStatisticsHandler(w http.ResponseWriter, r *http.Request) {
return return
} }
// Получаем ISO год и неделю для текущей даты // Получаем ISO год и неделю для текущей даты (в настроенном часовом поясе)
now := time.Now() timezoneStr := getEnv("TIMEZONE", "UTC")
loc, locErr := time.LoadLocation(timezoneStr)
if locErr != nil {
loc = time.UTC
}
now := time.Now().In(loc)
_, currentWeekInt := now.ISOWeek() _, currentWeekInt := now.ISOWeek()
currentYearInt := now.Year() currentYearInt := now.Year()
@@ -6849,10 +6873,10 @@ func (a *App) getFullStatisticsHandler(w http.ResponseWriter, r *http.Request) {
normalizedScore = math.Min(totalScore, maxGoalScore) normalizedScore = math.Min(totalScore, maxGoalScore)
} }
_, weekISO := time.Now().ISOWeek() _, weekISO := now.ISOWeek()
item := FullStatisticsItem{ item := FullStatisticsItem{
ProjectName: projectName, ProjectName: projectName,
ReportYear: time.Now().Year(), ReportYear: now.Year(),
ReportWeek: weekISO, ReportWeek: weekISO,
TotalScore: totalScore, TotalScore: totalScore,
NormalizedTotalScore: normalizedScore, NormalizedTotalScore: normalizedScore,
@@ -6892,7 +6916,7 @@ func (a *App) getTodayEntriesHandler(w http.ResponseWriter, r *http.Request) {
projectFilter = &projectName projectFilter = &projectName
} }
// Получаем дату из query string (формат: YYYY-MM-DD), если не указана - используем сегодня // Получаем дату из query string (формат: YYYY-MM-DD), если не указана - используем сегодня в настроенном часовом поясе
dateParam := r.URL.Query().Get("date") dateParam := r.URL.Query().Get("date")
var targetDate time.Time var targetDate time.Time
if dateParam != "" { if dateParam != "" {
@@ -6904,7 +6928,13 @@ func (a *App) getTodayEntriesHandler(w http.ResponseWriter, r *http.Request) {
} }
targetDate = parsedDate targetDate = parsedDate
} else { } 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 за указанный день // Запрос для получения entries с nodes за указанный день
@@ -10758,8 +10788,14 @@ func (a *App) fitbitSyncHandler(w http.ResponseWriter, r *http.Request) {
return 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 { if err != nil {
log.Printf("Fitbit sync error: %v", err) log.Printf("Fitbit sync error: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Sync failed: %v", err), http.StatusInternalServerError) 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 return
} }
// Получаем дату из query параметра (по умолчанию сегодня) // Получаем дату из query параметра (по умолчанию сегодня в настроенном часовом поясе)
dateStr := r.URL.Query().Get("date") dateStr := r.URL.Query().Get("date")
if dateStr == "" { 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 var steps, floors, azm sql.NullInt64
@@ -11084,7 +11125,8 @@ func (a *App) checkWishlistUnlock(itemID int, userID int) (bool, error) {
} else if err != nil { } else if err != nil {
return false, err return false, err
} else { } else {
conditionMet = completed > 0 // Задача существует и не удалена — условие не выполнено
conditionMet = false
} }
} else if scoreConditionID.Valid { } else if scoreConditionID.Valid {
@@ -11472,8 +11514,9 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
completedBool := true completedBool := true
condition.TaskCompleted = &completedBool condition.TaskCompleted = &completedBool
} else if err == nil { } else if err == nil {
conditionMet = completed > 0 // Задача существует и не удалена — условие не выполнено
completedBool := conditionMet conditionMet = false
completedBool := false
condition.TaskCompleted = &completedBool condition.TaskCompleted = &completedBool
} }
} }
@@ -11538,7 +11581,8 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
completedBool := true completedBool := true
condition.TaskCompleted = &completedBool condition.TaskCompleted = &completedBool
} else if err == nil { } else if err == nil {
completedBool := completed > 0 // Задача существует и не удалена — условие не выполнено
completedBool := false
condition.TaskCompleted = &completedBool condition.TaskCompleted = &completedBool
} }
} }
@@ -12381,9 +12425,14 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
condition.TaskID = &taskIDVal condition.TaskID = &taskIDVal
var taskCompleted int 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) 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 { if err == sql.ErrNoRows {
isCompleted := taskCompleted > 0 // Задача удалена или не существует — условие выполнено
condition.TaskCompleted = &isCompleted t := true
condition.TaskCompleted = &t
} else if err == nil {
// Задача существует и не удалена — условие не выполнено
f := false
condition.TaskCompleted = &f
} }
} }
if taskNextShowAt.Valid { if taskNextShowAt.Valid {
@@ -12777,9 +12826,14 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
condition.TaskID = &taskIDVal condition.TaskID = &taskIDVal
var taskCompleted int 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) 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 { if err == sql.ErrNoRows {
isCompleted := taskCompleted > 0 // Задача удалена или не существует — условие выполнено
condition.TaskCompleted = &isCompleted t := true
condition.TaskCompleted = &t
} else if err == nil {
// Задача существует и не удалена — условие не выполнено
f := false
condition.TaskCompleted = &f
} }
} }
} else if scoreConditionID.Valid { } else if scoreConditionID.Valid {
@@ -14917,15 +14971,20 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
if taskName.Valid { if taskName.Valid {
condition.TaskName = &taskName.String condition.TaskName = &taskName.String
} }
// Проверяем выполнена ли задача для владельца условия // Условие выполнено, если задача удалена или не существует
if taskID.Valid { if taskID.Valid {
taskIDVal := int(taskID.Int64) taskIDVal := int(taskID.Int64)
condition.TaskID = &taskIDVal condition.TaskID = &taskIDVal
var taskCompleted int 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) 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 { if err == sql.ErrNoRows {
isCompleted := taskCompleted > 0 // Задача удалена или не существует — условие выполнено
condition.TaskCompleted = &isCompleted t := true
condition.TaskCompleted = &t
} else if err == nil {
// Задача существует и не удалена — условие не выполнено
f := false
condition.TaskCompleted = &f
} }
} }
} else if scoreConditionID.Valid { } else if scoreConditionID.Valid {
@@ -15803,8 +15862,13 @@ func (a *App) proxyImageHandler(w http.ResponseWriter, r *http.Request) {
// getWeeklyStatsDataForUserAndWeek получает данные о проектах для конкретного пользователя и недели // getWeeklyStatsDataForUserAndWeek получает данные о проектах для конкретного пользователя и недели
func (a *App) getWeeklyStatsDataForUserAndWeek(userID int, year int, week int) (*WeeklyStatsResponse, error) { 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() currentYear, currentWeek := now.ISOWeek()
isCurrentWeek := year == currentYear && week == currentWeek 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") weekStr := r.URL.Query().Get("week")
var year, week int 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 == "" { if yearStr == "" || weekStr == "" {
// Если не указаны - текущая неделя // Если не указаны - текущая неделя

View File

@@ -1,6 +1,6 @@
{ {
"name": "play-life-web", "name": "play-life-web",
"version": "5.0.6", "version": "5.0.8",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",