Исправлен доступ к желаниям на досках
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m3s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m3s
This commit is contained in:
@@ -9976,6 +9976,45 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
json.NewEncoder(w).Encode(createdItem)
|
||||
}
|
||||
|
||||
// checkWishlistAccess проверяет доступ пользователя к желанию
|
||||
// Возвращает (hasAccess, itemUserID, boardID, error)
|
||||
func (a *App) checkWishlistAccess(itemID int, userID int) (bool, int, sql.NullInt64, error) {
|
||||
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 {
|
||||
return false, 0, sql.NullInt64{}, err
|
||||
}
|
||||
if err != nil {
|
||||
return false, 0, sql.NullInt64{}, err
|
||||
}
|
||||
|
||||
// Проверяем доступ: владелец ИЛИ участник доски
|
||||
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
|
||||
err = 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)
|
||||
if err == nil {
|
||||
hasAccess = isMember
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return hasAccess, itemUserID, boardID, nil
|
||||
}
|
||||
|
||||
// getWishlistItemHandler возвращает одно желание
|
||||
func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method == "OPTIONS" {
|
||||
@@ -9999,44 +10038,27 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Проверяем доступ к желанию
|
||||
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)
|
||||
|
||||
hasAccess, itemUserID, boardID, err := a.checkWishlistAccess(itemID, userID)
|
||||
if err == sql.ErrNoRows {
|
||||
log.Printf("Wishlist item not found: id=%d, userID=%d", itemID, userID)
|
||||
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error getting wishlist item: %v", err)
|
||||
log.Printf("Error getting wishlist item (id=%d, userID=%d): %v", itemID, userID, err)
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
log.Printf("Wishlist item found: id=%d, itemUserID=%d, boardID=%v, currentUserID=%d", itemID, itemUserID, boardID, userID)
|
||||
|
||||
if !hasAccess {
|
||||
log.Printf("Access denied for wishlist item: id=%d, itemUserID=%d, boardID=%v, currentUserID=%d", itemID, itemUserID, boardID, userID)
|
||||
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("Access granted for wishlist item: id=%d, itemUserID=%d, boardID=%v, currentUserID=%d", itemID, itemUserID, boardID, userID)
|
||||
|
||||
// Сохраняем itemUserID для использования в качестве fallback, если conditionUserID NULL
|
||||
itemOwnerID := itemUserID
|
||||
@@ -10262,8 +10284,11 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
setCORSHeaders(w)
|
||||
|
||||
log.Printf("updateWishlistHandler called: method=%s, path=%s", r.Method, r.URL.Path)
|
||||
|
||||
userID, ok := getUserIDFromContext(r)
|
||||
if !ok {
|
||||
log.Printf("updateWishlistHandler: Unauthorized")
|
||||
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
}
|
||||
@@ -10271,26 +10296,34 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
itemID, err := strconv.Atoi(vars["id"])
|
||||
if err != nil {
|
||||
log.Printf("updateWishlistHandler: Invalid wishlist ID: %v", err)
|
||||
sendErrorWithCORS(w, "Invalid wishlist ID", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("updateWishlistHandler: itemID=%d, userID=%d", itemID, userID)
|
||||
|
||||
// Проверяем владельца
|
||||
var ownerID int
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT user_id FROM wishlist_items
|
||||
WHERE id = $1 AND deleted = FALSE
|
||||
`, itemID).Scan(&ownerID)
|
||||
if err == sql.ErrNoRows || ownerID != userID {
|
||||
// Проверяем доступ к желанию
|
||||
hasAccess, _, _, err := a.checkWishlistAccess(itemID, userID)
|
||||
if err == sql.ErrNoRows {
|
||||
log.Printf("updateWishlistHandler: Wishlist item not found: id=%d, userID=%d", itemID, userID)
|
||||
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error checking wishlist ownership: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError)
|
||||
log.Printf("updateWishlistHandler: Error getting wishlist item (id=%d, userID=%d): %v", itemID, userID, err)
|
||||
sendErrorWithCORS(w, "Error getting wishlist item", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasAccess {
|
||||
log.Printf("updateWishlistHandler: Access denied: id=%d, userID=%d", itemID, userID)
|
||||
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
log.Printf("updateWishlistHandler: Access granted: id=%d, userID=%d", itemID, userID)
|
||||
|
||||
var req WishlistRequest
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
log.Printf("Error decoding wishlist request: %v", err)
|
||||
@@ -10311,11 +10344,12 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
// Обновляем желание (не проверяем user_id в WHERE, так как доступ уже проверен выше)
|
||||
_, err = tx.Exec(`
|
||||
UPDATE wishlist_items
|
||||
SET name = $1, price = $2, link = $3, updated_at = NOW()
|
||||
WHERE id = $4 AND user_id = $5
|
||||
`, strings.TrimSpace(req.Name), req.Price, req.Link, itemID, userID)
|
||||
WHERE id = $4
|
||||
`, strings.TrimSpace(req.Name), req.Price, req.Link, itemID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error updating wishlist item: %v", err)
|
||||
@@ -10337,27 +10371,189 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Получаем обновлённое желание
|
||||
items, err := a.getWishlistItemsWithConditions(userID, true)
|
||||
// Получаем обновлённое желание через getWishlistItemHandler логику
|
||||
// Используем тот же запрос, что и в getWishlistItemHandler
|
||||
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 getting updated wishlist item: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error getting updated wishlist item: %v", err), http.StatusInternalServerError)
|
||||
log.Printf("Error querying updated wishlist item: %v", err)
|
||||
sendErrorWithCORS(w, "Error getting updated wishlist item", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
itemsMap := make(map[int]*WishlistItem)
|
||||
var itemOwnerID int
|
||||
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 updated wishlist item: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
item, exists := itemsMap[itemID]
|
||||
if !exists {
|
||||
// Получаем user_id для этого желания
|
||||
err = a.DB.QueryRow(`SELECT user_id FROM wishlist_items WHERE id = $1`, itemID).Scan(&itemOwnerID)
|
||||
if err != nil {
|
||||
log.Printf("Error getting item owner: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
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),
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
var updatedItem *WishlistItem
|
||||
for i := range items {
|
||||
if items[i].ID == itemID {
|
||||
updatedItem = &items[i]
|
||||
for _, it := range itemsMap {
|
||||
if it.ID == itemID {
|
||||
updatedItem = it
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if updatedItem == nil {
|
||||
log.Printf("Updated item not found: id=%d", itemID)
|
||||
sendErrorWithCORS(w, "Updated item not found", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем разблокировку
|
||||
updatedItem.Unlocked = true
|
||||
if len(updatedItem.UnlockConditions) > 0 {
|
||||
for _, cond := range updatedItem.UnlockConditions {
|
||||
if cond.Type == "task_completion" {
|
||||
if cond.TaskCompleted == nil || !*cond.TaskCompleted {
|
||||
updatedItem.Unlocked = false
|
||||
break
|
||||
}
|
||||
} else if cond.Type == "project_points" {
|
||||
if cond.CurrentPoints == nil || cond.RequiredPoints == nil || *cond.CurrentPoints < *cond.RequiredPoints {
|
||||
updatedItem.Unlocked = false
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unlocked, err := a.checkWishlistUnlock(itemID, userID)
|
||||
if err == nil {
|
||||
updatedItem.Unlocked = unlocked
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(updatedItem)
|
||||
}
|
||||
@@ -10384,27 +10580,27 @@ func (a *App) deleteWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем владельца
|
||||
var ownerID int
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT user_id FROM wishlist_items
|
||||
WHERE id = $1 AND deleted = FALSE
|
||||
`, itemID).Scan(&ownerID)
|
||||
if err == sql.ErrNoRows || ownerID != userID {
|
||||
// Проверяем доступ к желанию
|
||||
hasAccess, _, _, err := a.checkWishlistAccess(itemID, userID)
|
||||
if err == sql.ErrNoRows {
|
||||
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error checking wishlist ownership: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError)
|
||||
log.Printf("Error checking wishlist access: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist access: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !hasAccess {
|
||||
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE wishlist_items
|
||||
SET deleted = TRUE, updated_at = NOW()
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`, itemID, userID)
|
||||
WHERE id = $1
|
||||
`, itemID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error deleting wishlist item: %v", err)
|
||||
@@ -10441,19 +10637,19 @@ func (a *App) uploadWishlistImageHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем владельца
|
||||
var ownerID int
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT user_id FROM wishlist_items
|
||||
WHERE id = $1 AND deleted = FALSE
|
||||
`, wishlistID).Scan(&ownerID)
|
||||
if err == sql.ErrNoRows || ownerID != userID {
|
||||
// Проверяем доступ к желанию
|
||||
hasAccess, _, _, err := a.checkWishlistAccess(wishlistID, userID)
|
||||
if err == sql.ErrNoRows {
|
||||
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error checking wishlist ownership: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError)
|
||||
log.Printf("Error checking wishlist access: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist access: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !hasAccess {
|
||||
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -10519,8 +10715,8 @@ func (a *App) uploadWishlistImageHandler(w http.ResponseWriter, r *http.Request)
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE wishlist_items
|
||||
SET image_path = $1, updated_at = NOW()
|
||||
WHERE id = $2 AND user_id = $3
|
||||
`, imagePath, wishlistID, userID)
|
||||
WHERE id = $2
|
||||
`, imagePath, wishlistID)
|
||||
if err != nil {
|
||||
log.Printf("Error updating database: %v", err)
|
||||
sendErrorWithCORS(w, "Error updating database", http.StatusInternalServerError)
|
||||
@@ -10555,27 +10751,27 @@ func (a *App) completeWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем владельца
|
||||
var ownerID int
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT user_id FROM wishlist_items
|
||||
WHERE id = $1 AND deleted = FALSE
|
||||
`, itemID).Scan(&ownerID)
|
||||
if err == sql.ErrNoRows || ownerID != userID {
|
||||
// Проверяем доступ к желанию
|
||||
hasAccess, _, _, err := a.checkWishlistAccess(itemID, userID)
|
||||
if err == sql.ErrNoRows {
|
||||
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error checking wishlist ownership: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError)
|
||||
log.Printf("Error checking wishlist access: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist access: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !hasAccess {
|
||||
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE wishlist_items
|
||||
SET completed = TRUE, updated_at = NOW()
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`, itemID, userID)
|
||||
WHERE id = $1
|
||||
`, itemID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error completing wishlist item: %v", err)
|
||||
@@ -10685,27 +10881,27 @@ func (a *App) uncompleteWishlistHandler(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
// Проверяем владельца
|
||||
var ownerID int
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT user_id FROM wishlist_items
|
||||
WHERE id = $1 AND deleted = FALSE
|
||||
`, itemID).Scan(&ownerID)
|
||||
if err == sql.ErrNoRows || ownerID != userID {
|
||||
// Проверяем доступ к желанию
|
||||
hasAccess, _, _, err := a.checkWishlistAccess(itemID, userID)
|
||||
if err == sql.ErrNoRows {
|
||||
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
log.Printf("Error checking wishlist ownership: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist ownership: %v", err), http.StatusInternalServerError)
|
||||
log.Printf("Error checking wishlist access: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error checking wishlist access: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
if !hasAccess {
|
||||
sendErrorWithCORS(w, "Access denied", http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = a.DB.Exec(`
|
||||
UPDATE wishlist_items
|
||||
SET completed = FALSE, updated_at = NOW()
|
||||
WHERE id = $1 AND user_id = $2
|
||||
`, itemID, userID)
|
||||
WHERE id = $1
|
||||
`, itemID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error uncompleting wishlist item: %v", err)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "play-life-web",
|
||||
"version": "3.14.4",
|
||||
"version": "3.14.5",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
|
||||
@@ -914,6 +914,7 @@ function AppContent() {
|
||||
key={tabParams.wishlistId}
|
||||
onNavigate={handleNavigate}
|
||||
wishlistId={tabParams.wishlistId}
|
||||
boardId={tabParams.boardId}
|
||||
onRefresh={() => setWishlistRefreshTrigger(prev => prev + 1)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -8,7 +8,7 @@ import './TaskList.css'
|
||||
|
||||
const API_URL = '/api/wishlist'
|
||||
|
||||
function WishlistDetail({ wishlistId, onNavigate, onRefresh }) {
|
||||
function WishlistDetail({ wishlistId, onNavigate, onRefresh, boardId }) {
|
||||
const { authFetch, user } = useAuth()
|
||||
const [wishlistItem, setWishlistItem] = useState(null)
|
||||
const [loading, setLoading] = useState(true)
|
||||
@@ -51,7 +51,7 @@ function WishlistDetail({ wishlistId, onNavigate, onRefresh }) {
|
||||
}, [wishlistId, fetchWishlistDetail])
|
||||
|
||||
const handleEdit = () => {
|
||||
onNavigate?.('wishlist-form', { wishlistId: wishlistId })
|
||||
onNavigate?.('wishlist-form', { wishlistId: wishlistId, boardId: boardId })
|
||||
}
|
||||
|
||||
const handleComplete = async () => {
|
||||
|
||||
@@ -31,6 +31,7 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
|
||||
const [loadingWishlist, setLoadingWishlist] = useState(false)
|
||||
const [fetchingMetadata, setFetchingMetadata] = useState(false)
|
||||
const [restoredFromSession, setRestoredFromSession] = useState(false) // Флаг восстановления из sessionStorage
|
||||
const [loadedWishlistData, setLoadedWishlistData] = useState(null) // Данные желания для последующего маппинга условий
|
||||
const fileInputRef = useRef(null)
|
||||
|
||||
// Загрузка задач и проектов
|
||||
@@ -65,13 +66,41 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
|
||||
return
|
||||
}
|
||||
|
||||
if (wishlistId !== undefined && wishlistId !== null && tasks.length > 0 && projects.length > 0) {
|
||||
if (wishlistId !== undefined && wishlistId !== null) {
|
||||
// Загружаем желание независимо от наличия задач и проектов
|
||||
loadWishlist()
|
||||
} else if (wishlistId === undefined || wishlistId === null) {
|
||||
// Сбрасываем форму при создании новой задачи
|
||||
resetForm()
|
||||
setLoadedWishlistData(null)
|
||||
}
|
||||
}, [wishlistId, tasks, projects, restoredFromSession])
|
||||
}, [wishlistId, restoredFromSession])
|
||||
|
||||
// Обновляем маппинг условий после загрузки задач и проектов
|
||||
useEffect(() => {
|
||||
// Если есть загруженные данные желания, но маппинг еще не выполнен,
|
||||
// обновляем условия с правильным маппингом
|
||||
if (loadedWishlistData && tasks.length > 0 && projects.length > 0) {
|
||||
const data = loadedWishlistData
|
||||
setName(data.name || '')
|
||||
setPrice(data.price ? String(data.price) : '')
|
||||
setLink(data.link || '')
|
||||
setImageUrl(data.image_url || null)
|
||||
if (data.unlock_conditions) {
|
||||
setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({
|
||||
type: cond.type,
|
||||
task_id: cond.type === 'task_completion' ? tasks.find(t => t.name === cond.task_name)?.id : null,
|
||||
project_id: cond.type === 'project_points' ? projects.find(p => p.project_name === cond.project_name)?.project_id : null,
|
||||
required_points: cond.required_points || null,
|
||||
start_date: cond.start_date || null,
|
||||
display_order: idx,
|
||||
})))
|
||||
} else {
|
||||
setUnlockConditions([])
|
||||
}
|
||||
setLoadedWishlistData(null) // Очищаем после применения
|
||||
}
|
||||
}, [tasks, projects, loadedWishlistData])
|
||||
|
||||
// Сброс формы при размонтировании компонента или при изменении wishlistId на undefined
|
||||
useEffect(() => {
|
||||
@@ -199,22 +228,36 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
|
||||
throw new Error('Ошибка загрузки желания')
|
||||
}
|
||||
const data = await response.json()
|
||||
setName(data.name || '')
|
||||
setPrice(data.price ? String(data.price) : '')
|
||||
setLink(data.link || '')
|
||||
setImageUrl(data.image_url || null)
|
||||
setImageFile(null) // Сбрасываем imageFile при загрузке существующего желания
|
||||
if (data.unlock_conditions) {
|
||||
setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({
|
||||
type: cond.type,
|
||||
task_id: cond.type === 'task_completion' ? tasks.find(t => t.name === cond.task_name)?.id : null,
|
||||
project_id: cond.type === 'project_points' ? projects.find(p => p.project_name === cond.project_name)?.project_id : null,
|
||||
required_points: cond.required_points || null,
|
||||
start_date: cond.start_date || null,
|
||||
display_order: idx,
|
||||
})))
|
||||
|
||||
// Если задачи и проекты уже загружены, применяем данные сразу
|
||||
// Иначе сохраняем данные для последующего применения
|
||||
if (tasks.length > 0 && projects.length > 0) {
|
||||
setName(data.name || '')
|
||||
setPrice(data.price ? String(data.price) : '')
|
||||
setLink(data.link || '')
|
||||
setImageUrl(data.image_url || null)
|
||||
setImageFile(null) // Сбрасываем imageFile при загрузке существующего желания
|
||||
if (data.unlock_conditions) {
|
||||
setUnlockConditions(data.unlock_conditions.map((cond, idx) => ({
|
||||
type: cond.type,
|
||||
task_id: cond.type === 'task_completion' ? tasks.find(t => t.name === cond.task_name)?.id : null,
|
||||
project_id: cond.type === 'project_points' ? projects.find(p => p.project_name === cond.project_name)?.project_id : null,
|
||||
required_points: cond.required_points || null,
|
||||
start_date: cond.start_date || null,
|
||||
display_order: idx,
|
||||
})))
|
||||
} else {
|
||||
setUnlockConditions([])
|
||||
}
|
||||
} else {
|
||||
setUnlockConditions([])
|
||||
// Сохраняем данные для последующего применения после загрузки задач и проектов
|
||||
setLoadedWishlistData(data)
|
||||
// Применяем базовые данные сразу
|
||||
setName(data.name || '')
|
||||
setPrice(data.price ? String(data.price) : '')
|
||||
setLink(data.link || '')
|
||||
setImageUrl(data.image_url || null)
|
||||
setImageFile(null)
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err.message)
|
||||
|
||||
Reference in New Issue
Block a user