6.16.5: Оптимизация выполнения задачи
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 1m39s
Some checks failed
Build and Push Docker Image / build-and-push (push) Failing after 1m39s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8454,22 +8454,8 @@ func (a *App) getTodoistStatusHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Tasks handlers
|
||||
// ============================================
|
||||
|
||||
// getTasksHandler возвращает список задач пользователя
|
||||
func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
setCORSHeaders(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// Запрос с получением всех необходимых данных для группировки и отображения
|
||||
// fetchTasksForUser возвращает список задач пользователя из БД
|
||||
func (a *App) fetchTasksForUser(userID int) ([]Task, error) {
|
||||
query := `
|
||||
SELECT
|
||||
t.id,
|
||||
@@ -8526,8 +8512,7 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := a.DB.Query(query, userID)
|
||||
if err != nil {
|
||||
log.Printf("Error querying tasks: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error querying tasks: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
@@ -8637,6 +8622,30 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
tasks = append(tasks, task)
|
||||
}
|
||||
|
||||
return tasks, nil
|
||||
}
|
||||
|
||||
// getTasksHandler возвращает список задач пользователя
|
||||
func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
setCORSHeaders(w)
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
tasks, err := a.fetchTasksForUser(userID)
|
||||
if err != nil {
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error querying tasks: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(tasks)
|
||||
}
|
||||
@@ -10709,61 +10718,79 @@ func (a *App) executeTask(taskID int, userID int, req CompleteTaskRequest) error
|
||||
if err != nil {
|
||||
log.Printf("Error querying subtasks: %v", err)
|
||||
} else {
|
||||
defer subtaskRows.Close()
|
||||
for subtaskRows.Next() {
|
||||
var subtaskID int
|
||||
var subtaskName string
|
||||
var subtaskRewardMessage sql.NullString
|
||||
var subtaskProgressionBase sql.NullFloat64
|
||||
// Собираем подзадачи с reward_message
|
||||
type subtaskInfo struct {
|
||||
id int
|
||||
name string
|
||||
rewardMessage string
|
||||
progressionBase sql.NullFloat64
|
||||
}
|
||||
var subtasks []subtaskInfo
|
||||
subtaskIDs := make([]int, 0)
|
||||
|
||||
err := subtaskRows.Scan(&subtaskID, &subtaskName, &subtaskRewardMessage, &subtaskProgressionBase)
|
||||
for subtaskRows.Next() {
|
||||
var st subtaskInfo
|
||||
var subtaskRewardMessage sql.NullString
|
||||
err := subtaskRows.Scan(&st.id, &st.name, &subtaskRewardMessage, &st.progressionBase)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning subtask: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Пропускаем подзадачи с пустым reward_message
|
||||
if !subtaskRewardMessage.Valid || subtaskRewardMessage.String == "" {
|
||||
continue
|
||||
}
|
||||
st.rewardMessage = subtaskRewardMessage.String
|
||||
subtasks = append(subtasks, st)
|
||||
subtaskIDs = append(subtaskIDs, st.id)
|
||||
}
|
||||
subtaskRows.Close()
|
||||
|
||||
// Получаем награды подзадачи
|
||||
subtaskRewardRows, err := a.DB.Query(`
|
||||
SELECT rc.position, p.name AS project_name, rc.value, rc.use_progression
|
||||
// Батчевый запрос наград для всех подзадач за один раз
|
||||
subtaskRewardsMap := make(map[int][]Reward)
|
||||
if len(subtaskIDs) > 0 {
|
||||
idArgs := make([]interface{}, len(subtaskIDs))
|
||||
idPlaceholders := make([]string, len(subtaskIDs))
|
||||
for i, id := range subtaskIDs {
|
||||
idArgs[i] = id
|
||||
idPlaceholders[i] = fmt.Sprintf("$%d", i+1)
|
||||
}
|
||||
batchQuery := fmt.Sprintf(`
|
||||
SELECT rc.task_id, rc.position, p.name AS project_name, rc.value, rc.use_progression
|
||||
FROM reward_configs rc
|
||||
JOIN projects p ON rc.project_id = p.id
|
||||
WHERE rc.task_id = $1
|
||||
ORDER BY rc.position
|
||||
`, subtaskID)
|
||||
WHERE rc.task_id IN (%s)
|
||||
ORDER BY rc.task_id, rc.position
|
||||
`, strings.Join(idPlaceholders, ","))
|
||||
|
||||
rewardRows, err := a.DB.Query(batchQuery, idArgs...)
|
||||
if err != nil {
|
||||
log.Printf("Error querying subtask rewards: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
subtaskRewards := make([]Reward, 0)
|
||||
for subtaskRewardRows.Next() {
|
||||
var reward Reward
|
||||
err := subtaskRewardRows.Scan(&reward.Position, &reward.ProjectName, &reward.Value, &reward.UseProgression)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning subtask reward: %v", err)
|
||||
continue
|
||||
log.Printf("Error querying subtask rewards batch: %v", err)
|
||||
} else {
|
||||
for rewardRows.Next() {
|
||||
var stTaskID int
|
||||
var reward Reward
|
||||
err := rewardRows.Scan(&stTaskID, &reward.Position, &reward.ProjectName, &reward.Value, &reward.UseProgression)
|
||||
if err != nil {
|
||||
log.Printf("Error scanning subtask reward: %v", err)
|
||||
continue
|
||||
}
|
||||
subtaskRewardsMap[stTaskID] = append(subtaskRewardsMap[stTaskID], reward)
|
||||
}
|
||||
subtaskRewards = append(subtaskRewards, reward)
|
||||
rewardRows.Close()
|
||||
}
|
||||
subtaskRewardRows.Close()
|
||||
}
|
||||
|
||||
// Вычисляем score для наград подзадачи
|
||||
// Формируем сообщения для каждой подзадачи
|
||||
for _, st := range subtasks {
|
||||
subtaskRewardStrings := make(map[int]string)
|
||||
var subtaskProgressionBasePtr *float64
|
||||
if subtaskProgressionBase.Valid {
|
||||
subtaskProgressionBasePtr = &subtaskProgressionBase.Float64
|
||||
if st.progressionBase.Valid {
|
||||
subtaskProgressionBasePtr = &st.progressionBase.Float64
|
||||
} else if progressionBase.Valid {
|
||||
subtaskProgressionBasePtr = &progressionBase.Float64
|
||||
}
|
||||
for _, reward := range subtaskRewards {
|
||||
for _, reward := range subtaskRewardsMap[st.id] {
|
||||
score := calculateRewardScore(reward, req.Value, subtaskProgressionBasePtr)
|
||||
|
||||
var rewardStr string
|
||||
if score >= 0 {
|
||||
rewardStr = fmt.Sprintf("**%s+%.4g**", reward.ProjectName, score)
|
||||
@@ -10772,10 +10799,7 @@ func (a *App) executeTask(taskID int, userID int, req CompleteTaskRequest) error
|
||||
}
|
||||
subtaskRewardStrings[reward.Position] = rewardStr
|
||||
}
|
||||
|
||||
// Подставляем в reward_message подзадачи
|
||||
subtaskMessage := replaceRewardPlaceholders(subtaskRewardMessage.String, subtaskRewardStrings, task.Name, subtaskName)
|
||||
|
||||
subtaskMessage := replaceRewardPlaceholders(st.rewardMessage, subtaskRewardStrings, task.Name, st.name)
|
||||
subtaskMessages = append(subtaskMessages, subtaskMessage)
|
||||
}
|
||||
}
|
||||
@@ -10789,13 +10813,15 @@ func (a *App) executeTask(taskID int, userID int, req CompleteTaskRequest) error
|
||||
finalMessage.WriteString(subtaskMsg)
|
||||
}
|
||||
|
||||
// Отправляем сообщение через processMessage
|
||||
// Отправляем сообщение через processMessage асинхронно, чтобы не блокировать ответ
|
||||
userIDPtr := &userID
|
||||
_, err = a.processMessage(finalMessage.String(), userIDPtr)
|
||||
if err != nil {
|
||||
// Логируем ошибку, но не откатываем транзакцию
|
||||
log.Printf("Error sending message to Telegram: %v", err)
|
||||
}
|
||||
finalMessageStr := finalMessage.String()
|
||||
go func() {
|
||||
_, err := a.processMessage(finalMessageStr, userIDPtr)
|
||||
if err != nil {
|
||||
log.Printf("Error sending message to Telegram: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Обновляем completed и last_completed_at для основной задачи
|
||||
// Если repetition_date установлен, вычисляем next_show_at
|
||||
@@ -11171,10 +11197,23 @@ func (a *App) completeTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Возвращаем обновлённый список задач чтобы фронтенд не делал повторный GET
|
||||
tasks, err := a.fetchTasksForUser(userID)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching tasks after completion: %v", err)
|
||||
// Фолбэк: возвращаем минимальный ответ, фронтенд сам обновится
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "Task completed successfully",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "Task completed successfully",
|
||||
"tasks": tasks,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -11232,10 +11271,22 @@ func (a *App) completeAndDeleteTaskHandler(w http.ResponseWriter, r *http.Reques
|
||||
return
|
||||
}
|
||||
|
||||
// Возвращаем обновлённый список задач чтобы фронтенд не делал повторный GET
|
||||
tasks, err := a.fetchTasksForUser(userID)
|
||||
if err != nil {
|
||||
log.Printf("Error fetching tasks after completion: %v", err)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "Task completed and deleted successfully",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"success": true,
|
||||
"message": "Task completed and deleted successfully",
|
||||
"tasks": tasks,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "play-life-web",
|
||||
"version": "6.16.4",
|
||||
"version": "6.16.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -1325,7 +1325,13 @@ function AppContent() {
|
||||
backgroundLoading={tasksBackgroundLoading}
|
||||
error={tasksError}
|
||||
onRetry={() => fetchTasksData(false)}
|
||||
onRefresh={(isBackground = false) => fetchTasksData(isBackground)}
|
||||
onRefresh={(tasksOrBackground) => {
|
||||
if (Array.isArray(tasksOrBackground)) {
|
||||
setTasksData(tasksOrBackground)
|
||||
} else {
|
||||
fetchTasksData(tasksOrBackground === true)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -593,13 +593,17 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
throw new Error(errorData.message || 'Ошибка при выполнении задачи')
|
||||
}
|
||||
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
// Показываем уведомление о выполнении
|
||||
if (onTaskCompleted) {
|
||||
onTaskCompleted()
|
||||
}
|
||||
|
||||
// Обновляем список и закрываем модальное окно
|
||||
if (onRefresh) {
|
||||
// Если бэкенд вернул обновлённый список — передаём его, иначе делаем повторный GET
|
||||
if (data.tasks && onRefresh) {
|
||||
onRefresh(data.tasks)
|
||||
} else if (onRefresh) {
|
||||
onRefresh()
|
||||
}
|
||||
if (onClose) {
|
||||
@@ -656,13 +660,17 @@ function TaskDetail({ taskId, onClose, onRefresh, onTaskCompleted, onNavigate })
|
||||
throw new Error(errorData.message || 'Ошибка при выполнении задачи')
|
||||
}
|
||||
|
||||
const data = await response.json().catch(() => ({}))
|
||||
|
||||
// Показываем уведомление о выполнении
|
||||
if (onTaskCompleted) {
|
||||
onTaskCompleted()
|
||||
}
|
||||
|
||||
// Обновляем список и закрываем модальное окно
|
||||
if (onRefresh) {
|
||||
// Если бэкенд вернул обновлённый список — передаём его, иначе делаем повторный GET
|
||||
if (data.tasks && onRefresh) {
|
||||
onRefresh(data.tasks)
|
||||
} else if (onRefresh) {
|
||||
onRefresh()
|
||||
}
|
||||
if (onClose) {
|
||||
|
||||
Reference in New Issue
Block a user