5.0.8: часовой пояс, разблокировка по удалению задачи
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m21s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m21s
This commit is contained in:
@@ -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 == "" {
|
||||||
// Если не указаны - текущая неделя
|
// Если не указаны - текущая неделя
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user