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