diff --git a/VERSION b/VERSION index da71773..0104088 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -3.14.3 +3.14.4 diff --git a/play-life-backend/main.go b/play-life-backend/main.go index 3a810ef..b455266 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -9244,6 +9244,7 @@ func (a *App) checkWishlistUnlock(itemID int, userID int) (bool, error) { wc.display_order, wc.task_condition_id, wc.score_condition_id, + wc.user_id AS condition_user_id, tc.task_id, sc.project_id, sc.required_points, @@ -9268,6 +9269,7 @@ func (a *App) checkWishlistUnlock(itemID int, userID int) (bool, error) { var wcID, displayOrder int var taskConditionID, scoreConditionID sql.NullInt64 + var conditionUserID sql.NullInt64 var taskID sql.NullInt64 var projectID sql.NullInt64 var requiredPoints sql.NullFloat64 @@ -9275,13 +9277,19 @@ func (a *App) checkWishlistUnlock(itemID int, userID int) (bool, error) { err := rows.Scan( &wcID, &displayOrder, - &taskConditionID, &scoreConditionID, + &taskConditionID, &scoreConditionID, &conditionUserID, &taskID, &projectID, &requiredPoints, &startDate, ) if err != nil { return false, err } + // Используем user_id из условия, если он есть, иначе используем текущего пользователя + conditionOwnerID := userID + if conditionUserID.Valid { + conditionOwnerID = int(conditionUserID.Int64) + } + var conditionMet bool if taskConditionID.Valid { @@ -9295,7 +9303,7 @@ func (a *App) checkWishlistUnlock(itemID int, userID int) (bool, error) { SELECT completed FROM tasks WHERE id = $1 AND user_id = $2 - `, taskID.Int64, userID).Scan(&completed) + `, taskID.Int64, conditionOwnerID).Scan(&completed) if err == sql.ErrNoRows { conditionMet = false @@ -9314,7 +9322,7 @@ func (a *App) checkWishlistUnlock(itemID int, userID int) (bool, error) { totalScore, err := a.calculateProjectPointsFromDate( int(projectID.Int64), startDate, - userID, + conditionOwnerID, ) if err != nil { return false, err @@ -9352,6 +9360,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool) wc.display_order, wc.task_condition_id, wc.score_condition_id, + wc.user_id AS condition_user_id, tc.task_id, t.name AS task_name, sc.project_id, @@ -9388,6 +9397,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool) var conditionID, displayOrder sql.NullInt64 var taskConditionID, scoreConditionID sql.NullInt64 + var conditionUserID sql.NullInt64 var taskID sql.NullInt64 var taskName sql.NullString var projectID sql.NullInt64 @@ -9398,7 +9408,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool) err := rows.Scan( &itemID, &name, &price, &imagePath, &link, &completed, &conditionID, &displayOrder, - &taskConditionID, &scoreConditionID, + &taskConditionID, &scoreConditionID, &conditionUserID, &taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate, ) @@ -9482,36 +9492,38 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool) var err error if condition.Type == "task_completion" { - // Находим task_id для этого условия + // Находим task_id и user_id для этого условия var taskID int + var conditionOwnerID int err = a.DB.QueryRow(` - SELECT tc.task_id + SELECT tc.task_id, COALESCE(wc.user_id, $2) FROM wishlist_conditions wc JOIN task_conditions tc ON wc.task_condition_id = tc.id WHERE wc.id = $1 - `, condition.ID).Scan(&taskID) + `, condition.ID, userID).Scan(&taskID, &conditionOwnerID) if err == nil { var completed int err = a.DB.QueryRow(` SELECT completed FROM tasks WHERE id = $1 AND user_id = $2 - `, taskID, userID).Scan(&completed) + `, taskID, conditionOwnerID).Scan(&completed) conditionMet = err == nil && completed > 0 completedBool := conditionMet condition.TaskCompleted = &completedBool } } else if condition.Type == "project_points" { - // Находим project_id и required_points для этого условия + // Находим project_id, required_points и user_id для этого условия var projectID int var requiredPoints float64 var startDate sql.NullTime + var conditionOwnerID int err = a.DB.QueryRow(` - SELECT sc.project_id, sc.required_points, sc.start_date + SELECT sc.project_id, sc.required_points, sc.start_date, COALESCE(wc.user_id, $2) FROM wishlist_conditions wc JOIN score_conditions sc ON wc.score_condition_id = sc.id WHERE wc.id = $1 - `, condition.ID).Scan(&projectID, &requiredPoints, &startDate) + `, condition.ID, userID).Scan(&projectID, &requiredPoints, &startDate, &conditionOwnerID) if err == nil { - totalScore, err := a.calculateProjectPointsFromDate(projectID, startDate, userID) + totalScore, err := a.calculateProjectPointsFromDate(projectID, startDate, conditionOwnerID) if err != nil { // Если ошибка при расчете, устанавливаем 0 zeroScore := 0.0 @@ -9541,17 +9553,18 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool) condition := &item.UnlockConditions[i] if condition.Type == "task_completion" { var taskID int + var conditionOwnerID int err := a.DB.QueryRow(` - SELECT tc.task_id + SELECT tc.task_id, COALESCE(wc.user_id, $2) FROM wishlist_conditions wc JOIN task_conditions tc ON wc.task_condition_id = tc.id WHERE wc.id = $1 - `, condition.ID).Scan(&taskID) + `, condition.ID, userID).Scan(&taskID, &conditionOwnerID) if err == nil { var completed int err = a.DB.QueryRow(` SELECT completed FROM tasks WHERE id = $1 AND user_id = $2 - `, taskID, userID).Scan(&completed) + `, taskID, conditionOwnerID).Scan(&completed) if err == nil { completedBool := completed > 0 condition.TaskCompleted = &completedBool @@ -9561,14 +9574,15 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool) var projectID int var requiredPoints float64 var startDate sql.NullTime + var conditionOwnerID int err := a.DB.QueryRow(` - SELECT sc.project_id, sc.required_points, sc.start_date + SELECT sc.project_id, sc.required_points, sc.start_date, COALESCE(wc.user_id, $2) FROM wishlist_conditions wc JOIN score_conditions sc ON wc.score_condition_id = sc.id WHERE wc.id = $1 - `, condition.ID).Scan(&projectID, &requiredPoints, &startDate) + `, condition.ID, userID).Scan(&projectID, &requiredPoints, &startDate, &conditionOwnerID) if err == nil { - totalScore, err := a.calculateProjectPointsFromDate(projectID, startDate, userID) + totalScore, err := a.calculateProjectPointsFromDate(projectID, startDate, conditionOwnerID) if err != nil { // Если ошибка при расчете, устанавливаем 0 zeroScore := 0.0 @@ -9984,17 +9998,193 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) { return } - items, err := a.getWishlistItemsWithConditions(userID, true) + // Проверяем доступ к желанию + var itemUserID int + var boardID sql.NullInt64 + err = a.DB.QueryRow(` + SELECT user_id, board_id + FROM wishlist_items + WHERE id = $1 AND deleted = FALSE + `, itemID).Scan(&itemUserID, &boardID) + + if err == sql.ErrNoRows { + sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound) + return + } if err != nil { log.Printf("Error getting wishlist item: %v", err) - sendErrorWithCORS(w, fmt.Sprintf("Error getting wishlist item: %v", err), http.StatusInternalServerError) + sendErrorWithCORS(w, "Error getting wishlist item", http.StatusInternalServerError) return } + // Проверяем доступ: владелец ИЛИ участник доски + hasAccess := itemUserID == userID + if !hasAccess && boardID.Valid { + var ownerID int + err = a.DB.QueryRow(`SELECT owner_id FROM wishlist_boards WHERE id = $1 AND deleted = FALSE`, boardID.Int64).Scan(&ownerID) + if err == nil { + hasAccess = ownerID == userID + if !hasAccess { + var isMember bool + a.DB.QueryRow(`SELECT EXISTS(SELECT 1 FROM wishlist_board_members WHERE board_id = $1 AND user_id = $2)`, + int(boardID.Int64), userID).Scan(&isMember) + hasAccess = isMember + } + } + } + + if !hasAccess { + sendErrorWithCORS(w, "Access denied", http.StatusForbidden) + return + } + + // Сохраняем itemUserID для использования в качестве fallback, если conditionUserID NULL + itemOwnerID := itemUserID + + // Загружаем полную информацию о желании + query := ` + SELECT + wi.id, + wi.name, + wi.price, + wi.image_path, + wi.link, + wi.completed, + wc.id AS condition_id, + wc.display_order, + wc.task_condition_id, + wc.score_condition_id, + wc.user_id AS condition_user_id, + tc.task_id, + t.name AS task_name, + sc.project_id, + p.name AS project_name, + sc.required_points, + sc.start_date + FROM wishlist_items wi + LEFT JOIN wishlist_conditions wc ON wi.id = wc.wishlist_item_id + LEFT JOIN task_conditions tc ON wc.task_condition_id = tc.id + LEFT JOIN tasks t ON tc.task_id = t.id + LEFT JOIN score_conditions sc ON wc.score_condition_id = sc.id + LEFT JOIN projects p ON sc.project_id = p.id + WHERE wi.id = $1 + AND wi.deleted = FALSE + ORDER BY wc.display_order, wc.id + ` + + rows, err := a.DB.Query(query, itemID) + if err != nil { + log.Printf("Error querying wishlist item: %v", err) + sendErrorWithCORS(w, "Error getting wishlist item", http.StatusInternalServerError) + return + } + defer rows.Close() + + itemsMap := make(map[int]*WishlistItem) + for rows.Next() { + var itemID int + var name string + var price sql.NullFloat64 + var imagePath sql.NullString + var link sql.NullString + var completed bool + var conditionID sql.NullInt64 + var displayOrder sql.NullInt64 + var taskConditionID sql.NullInt64 + var scoreConditionID sql.NullInt64 + var conditionUserID sql.NullInt64 + var taskID sql.NullInt64 + var taskName sql.NullString + var projectID sql.NullInt64 + var projectName sql.NullString + var requiredPoints sql.NullFloat64 + var startDate sql.NullTime + + err := rows.Scan( + &itemID, &name, &price, &imagePath, &link, &completed, + &conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID, + &taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate, + ) + if err != nil { + log.Printf("Error scanning wishlist item: %v", err) + continue + } + + item, exists := itemsMap[itemID] + if !exists { + item = &WishlistItem{ + ID: itemID, + Name: name, + Completed: completed, + UnlockConditions: []UnlockConditionDisplay{}, + } + if price.Valid { + item.Price = &price.Float64 + } + if imagePath.Valid && imagePath.String != "" { + url := imagePath.String + if !strings.HasPrefix(url, "http") { + url = url + "?t=" + strconv.FormatInt(time.Now().Unix(), 10) + } + item.ImageURL = &url + } + if link.Valid { + item.Link = &link.String + } + itemsMap[itemID] = item + } + + if conditionID.Valid { + condition := UnlockConditionDisplay{ + ID: int(conditionID.Int64), + DisplayOrder: int(displayOrder.Int64), + } + + // Используем user_id из условия, если он есть, иначе используем владельца желания + // Это важно для старых условий, созданных до добавления user_id в wishlist_conditions + conditionOwnerID := itemOwnerID + if conditionUserID.Valid { + conditionOwnerID = int(conditionUserID.Int64) + } + + if taskConditionID.Valid { + condition.Type = "task_completion" + if taskName.Valid { + condition.TaskName = &taskName.String + } + if taskID.Valid { + var taskCompleted int + a.DB.QueryRow(`SELECT completed FROM tasks WHERE id = $1 AND user_id = $2`, taskID.Int64, conditionOwnerID).Scan(&taskCompleted) + isCompleted := taskCompleted > 0 + condition.TaskCompleted = &isCompleted + } + } else if scoreConditionID.Valid { + condition.Type = "project_points" + if projectName.Valid { + condition.ProjectName = &projectName.String + } + if requiredPoints.Valid { + condition.RequiredPoints = &requiredPoints.Float64 + } + if startDate.Valid { + dateStr := startDate.Time.Format("2006-01-02") + condition.StartDate = &dateStr + } + if projectID.Valid { + points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID) + condition.CurrentPoints = &points + } + } + + item.UnlockConditions = append(item.UnlockConditions, condition) + } + } + + // Получаем желание из map var item *WishlistItem - for i := range items { - if items[i].ID == itemID { - item = &items[i] + for _, it := range itemsMap { + if it.ID == itemID { + item = it break } } @@ -10004,6 +10194,30 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) { return } + // Проверяем разблокировку + item.Unlocked = true + if len(item.UnlockConditions) > 0 { + for _, cond := range item.UnlockConditions { + if cond.Type == "task_completion" { + if cond.TaskCompleted == nil || !*cond.TaskCompleted { + item.Unlocked = false + break + } + } else if cond.Type == "project_points" { + if cond.CurrentPoints == nil || cond.RequiredPoints == nil || *cond.CurrentPoints < *cond.RequiredPoints { + item.Unlocked = false + break + } + } + } + } + + // Также проверяем через checkWishlistUnlock для совместимости + unlocked, err := a.checkWishlistUnlock(itemID, userID) + if err == nil { + item.Unlocked = unlocked + } + // Загружаем связанную задачу, если есть var linkedTaskID, linkedTaskCompleted, linkedTaskUserID sql.NullInt64 var linkedTaskName sql.NullString @@ -11772,6 +11986,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem, wc.display_order, wc.task_condition_id, wc.score_condition_id, + wc.user_id AS condition_user_id, tc.task_id, t.name AS task_name, sc.project_id, @@ -11809,6 +12024,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem, var displayOrder sql.NullInt64 var taskConditionID sql.NullInt64 var scoreConditionID sql.NullInt64 + var conditionUserID sql.NullInt64 var taskID sql.NullInt64 var taskName sql.NullString var projectID sql.NullInt64 @@ -11818,7 +12034,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem, err := rows.Scan( &itemID, &name, &price, &imagePath, &link, &completed, - &conditionID, &displayOrder, &taskConditionID, &scoreConditionID, + &conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID, &taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate, ) if err != nil { @@ -11856,15 +12072,21 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem, DisplayOrder: int(displayOrder.Int64), } + // Используем user_id из условия, если он есть, иначе используем текущего пользователя + conditionOwnerID := userID + if conditionUserID.Valid { + conditionOwnerID = int(conditionUserID.Int64) + } + if taskConditionID.Valid { condition.Type = "task_completion" if taskName.Valid { condition.TaskName = &taskName.String } - // Проверяем выполнена ли задача + // Проверяем выполнена ли задача для владельца условия if taskID.Valid { var taskCompleted int - a.DB.QueryRow(`SELECT completed FROM tasks WHERE id = $1`, taskID.Int64).Scan(&taskCompleted) + a.DB.QueryRow(`SELECT completed FROM tasks WHERE id = $1 AND user_id = $2`, taskID.Int64, conditionOwnerID).Scan(&taskCompleted) isCompleted := taskCompleted > 0 condition.TaskCompleted = &isCompleted } @@ -11880,9 +12102,9 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem, dateStr := startDate.Time.Format("2006-01-02") condition.StartDate = &dateStr } - // Считаем текущие баллы + // Считаем текущие баллы для владельца условия if projectID.Valid { - points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, userID) + points, _ := a.calculateProjectPointsFromDate(int(projectID.Int64), startDate, conditionOwnerID) condition.CurrentPoints = &points } } diff --git a/play-life-web/package.json b/play-life-web/package.json index 8ab4e8f..3c915a8 100644 --- a/play-life-web/package.json +++ b/play-life-web/package.json @@ -1,6 +1,6 @@ { "name": "play-life-web", - "version": "3.14.3", + "version": "3.14.4", "type": "module", "scripts": { "dev": "vite",