Исправление создания желаний на досках
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m27s

This commit is contained in:
poignatov
2026-01-21 18:46:36 +03:00
parent 24be9fad27
commit 068794a98c
8 changed files with 110 additions and 36 deletions

View File

@@ -1 +1 @@
3.25.0
3.25.1

View File

@@ -2,14 +2,14 @@
# Скрипт для создания дампа базы данных
# Использование:
# ./dump-db.sh [имя_дампа] # Дамп из .env
# ./dump-db.sh --env-file .env.prod [имя] # Дамп из указанного файла
# ./dump-db.sh production-backup # Именованный дамп из .env
# ./dump-db.sh [имя_дампа] # Дамп из .env.prod
# ./dump-db.sh --env-file .env [имя] # Дамп из указанного файла
# ./dump-db.sh production-backup # Именованный дамп из .env.prod
set -e
# Значения по умолчанию
DEFAULT_ENV_FILE=".env"
DEFAULT_ENV_FILE=".env.prod"
ENV_FILE="$DEFAULT_ENV_FILE"
DUMP_NAME=""

View File

@@ -9878,20 +9878,21 @@ func (a *App) saveWishlistConditions(
// Определяем user_id для условия:
// - Если условие имеет id и это условие существовало - проверяем, принадлежит ли оно текущему пользователю
// - Если условие принадлежит другому пользователю - пропускаем (не сохраняем)
// - Если условие имеет id, но не существовало (например, было только что добавлено) - игнорируем
// - Иначе - используем userID текущего пользователя
// - Если условие принадлежит другому пользователю - пропускаем (не сохраняем, так как чужие условия не редактируются)
// - Если условие имеет id, но не существовало (например, было только что добавлено) - это новое условие, используем userID текущего пользователя
// - Если условие без id - это новое условие, используем userID текущего пользователя
conditionUserID := userID
if condition.ID != nil {
if originalUserID, exists := existingConditions[*condition.ID]; exists {
// Если условие принадлежит другому пользователю - пропускаем (не сохраняем)
// Если условие принадлежит другому пользователю - пропускаем (не сохраняем, так как чужие условия не редактируются)
if originalUserID != userID {
continue
}
// Условие принадлежит текущему пользователю - обновляем его
conditionUserID = originalUserID
} else {
// Условие имеет id, но не существует в базе - пропускаем (не сохраняем)
continue
// Условие имеет id, но не существует в базе - это новое условие, используем userID текущего пользователя
conditionUserID = userID
}
}
@@ -10051,18 +10052,35 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
userID, ok := getUserIDFromContext(r)
if !ok {
log.Printf("createWishlistHandler: Unauthorized")
sendErrorWithCORS(w, "Unauthorized", http.StatusUnauthorized)
return
}
log.Printf("createWishlistHandler: userID=%d", userID)
var req WishlistRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
log.Printf("Error decoding wishlist request: %v", err)
log.Printf("createWishlistHandler: Error decoding wishlist request: %v", err)
sendErrorWithCORS(w, "Invalid request body", http.StatusBadRequest)
return
}
log.Printf("createWishlistHandler: decoded request - name='%s', price=%v, link='%s', conditions=%d",
req.Name, req.Price, req.Link, len(req.UnlockConditions))
if req.UnlockConditions == nil {
log.Printf("createWishlistHandler: WARNING - UnlockConditions is nil, initializing empty slice")
req.UnlockConditions = []UnlockConditionRequest{}
}
for i, cond := range req.UnlockConditions {
log.Printf("createWishlistHandler: condition %d - type='%s', task_id=%v, project_id=%v, required_points=%v, start_date='%v'",
i, cond.Type, cond.TaskID, cond.ProjectID, cond.RequiredPoints, cond.StartDate)
}
if strings.TrimSpace(req.Name) == "" {
log.Printf("createWishlistHandler: Name is required")
sendErrorWithCORS(w, "Name is required", http.StatusBadRequest)
return
}
@@ -10077,8 +10095,8 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
var wishlistID int
err = tx.QueryRow(`
INSERT INTO wishlist_items (user_id, name, price, link, completed, deleted)
VALUES ($1, $2, $3, $4, FALSE, FALSE)
INSERT INTO wishlist_items (user_id, author_id, name, price, link, completed, deleted)
VALUES ($1, $1, $2, $3, $4, FALSE, FALSE)
RETURNING id
`, userID, strings.TrimSpace(req.Name), req.Price, req.Link).Scan(&wishlistID)
@@ -10090,19 +10108,25 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
// Сохраняем условия
if len(req.UnlockConditions) > 0 {
err = a.saveWishlistConditions(tx, wishlistID, userID, req.UnlockConditions)
log.Printf("createWishlistHandler: saving %d conditions", len(req.UnlockConditions))
err = a.saveWishlistConditionsWithUserID(tx, wishlistID, userID, req.UnlockConditions)
if err != nil {
log.Printf("Error saving wishlist conditions: %v", err)
log.Printf("createWishlistHandler: Error saving wishlist conditions: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error saving wishlist conditions: %v", err), http.StatusInternalServerError)
return
}
log.Printf("createWishlistHandler: conditions saved successfully")
} else {
log.Printf("createWishlistHandler: no conditions to save")
}
log.Printf("createWishlistHandler: committing transaction")
if err := tx.Commit(); err != nil {
log.Printf("Error committing transaction: %v", err)
log.Printf("createWishlistHandler: Error committing transaction: %v", err)
sendErrorWithCORS(w, fmt.Sprintf("Error committing transaction: %v", err), http.StatusInternalServerError)
return
}
log.Printf("createWishlistHandler: transaction committed successfully")
// Получаем созданное желание с условиями
items, err := a.getWishlistItemsWithConditions(userID, false)
@@ -10121,10 +10145,14 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
}
if createdItem == nil {
log.Printf("createWishlistHandler: Created item not found")
sendErrorWithCORS(w, "Created item not found", http.StatusInternalServerError)
return
}
log.Printf("createWishlistHandler: Successfully created wishlist item id=%d, name='%s'",
createdItem.ID, createdItem.Name)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(createdItem)
}
@@ -12590,6 +12618,7 @@ func (a *App) createBoardItemHandler(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
boardID, err := strconv.Atoi(vars["boardId"])
if err != nil {
log.Printf("createBoardItemHandler: Error parsing boardId from URL: %v, vars['boardId']='%s'", err, vars["boardId"])
sendErrorWithCORS(w, "Invalid board ID", http.StatusBadRequest)
return
}
@@ -12617,11 +12646,21 @@ func (a *App) createBoardItemHandler(w http.ResponseWriter, r *http.Request) {
var req WishlistRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
log.Printf("createBoardItemHandler: Error decoding wishlist request: %v", err)
sendErrorWithCORS(w, "Invalid request body", http.StatusBadRequest)
return
}
log.Printf("createBoardItemHandler: decoded request - name='%s', price=%v, link='%s', conditions=%d",
req.Name, req.Price, req.Link, len(req.UnlockConditions))
if req.UnlockConditions == nil {
log.Printf("createBoardItemHandler: WARNING - UnlockConditions is nil, initializing empty slice")
req.UnlockConditions = []UnlockConditionRequest{}
}
if strings.TrimSpace(req.Name) == "" {
log.Printf("createBoardItemHandler: Name is required")
sendErrorWithCORS(w, "Name is required", http.StatusBadRequest)
return
}
@@ -12642,19 +12681,25 @@ func (a *App) createBoardItemHandler(w http.ResponseWriter, r *http.Request) {
`, ownerID, boardID, userID, strings.TrimSpace(req.Name), req.Price, req.Link).Scan(&itemID)
if err != nil {
log.Printf("Error creating board item: %v", err)
log.Printf("createBoardItemHandler: Error creating board item: %v", err)
sendErrorWithCORS(w, "Error creating item", http.StatusInternalServerError)
return
}
log.Printf("createBoardItemHandler: created wishlist item id=%d", itemID)
// Сохраняем условия с user_id текущего пользователя
if len(req.UnlockConditions) > 0 {
log.Printf("createBoardItemHandler: saving %d conditions", len(req.UnlockConditions))
err = a.saveWishlistConditionsWithUserID(tx, itemID, userID, req.UnlockConditions)
if err != nil {
log.Printf("Error saving conditions: %v", err)
log.Printf("createBoardItemHandler: Error saving wishlist conditions: %v", err)
sendErrorWithCORS(w, "Error saving conditions", http.StatusInternalServerError)
return
}
log.Printf("createBoardItemHandler: conditions saved successfully")
} else {
log.Printf("createBoardItemHandler: no conditions to save")
}
if err := tx.Commit(); err != nil {
@@ -12671,12 +12716,18 @@ func (a *App) createBoardItemHandler(w http.ResponseWriter, r *http.Request) {
// saveWishlistConditionsWithUserID сохраняет условия с указанием user_id
func (a *App) saveWishlistConditionsWithUserID(tx *sql.Tx, wishlistItemID int, userID int, conditions []UnlockConditionRequest) error {
log.Printf("saveWishlistConditionsWithUserID: wishlistItemID=%d, userID=%d, conditions=%d",
wishlistItemID, userID, len(conditions))
for i, cond := range conditions {
displayOrder := i
if cond.DisplayOrder != nil {
displayOrder = *cond.DisplayOrder
}
log.Printf("saveWishlistConditionsWithUserID: processing condition %d - type='%s', taskID=%v, projectID=%v",
i, cond.Type, cond.TaskID, cond.ProjectID)
switch cond.Type {
case "task_completion":
if cond.TaskID == nil {
@@ -12691,6 +12742,7 @@ func (a *App) saveWishlistConditionsWithUserID(tx *sql.Tx, wishlistItemID int, u
RETURNING id
`, *cond.TaskID).Scan(&taskConditionID)
if err != nil {
log.Printf("saveWishlistConditionsWithUserID: error creating task condition: %v", err)
return fmt.Errorf("error creating task condition: %w", err)
}
// Связываем с wishlist_item
@@ -12699,6 +12751,7 @@ func (a *App) saveWishlistConditionsWithUserID(tx *sql.Tx, wishlistItemID int, u
VALUES ($1, $2, $3, $4)
`, wishlistItemID, userID, taskConditionID, displayOrder)
if err != nil {
log.Printf("saveWishlistConditionsWithUserID: error linking task condition: %v", err)
return fmt.Errorf("error linking task condition: %w", err)
}
@@ -12719,6 +12772,7 @@ func (a *App) saveWishlistConditionsWithUserID(tx *sql.Tx, wishlistItemID int, u
RETURNING id
`, *cond.ProjectID, *cond.RequiredPoints, startDateVal).Scan(&scoreConditionID)
if err != nil {
log.Printf("saveWishlistConditionsWithUserID: error creating score condition: %v", err)
return fmt.Errorf("error creating score condition: %w", err)
}
// Связываем с wishlist_item
@@ -12727,6 +12781,7 @@ func (a *App) saveWishlistConditionsWithUserID(tx *sql.Tx, wishlistItemID int, u
VALUES ($1, $2, $3, $4)
`, wishlistItemID, userID, scoreConditionID, displayOrder)
if err != nil {
log.Printf("saveWishlistConditionsWithUserID: error linking score condition: %v", err)
return fmt.Errorf("error linking score condition: %w", err)
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "play-life-web",
"version": "3.25.0",
"version": "3.25.1",
"type": "module",
"scripts": {
"dev": "vite",

View File

@@ -653,7 +653,9 @@ function AppContent() {
// Для task-form и wishlist-form явно удаляем параметры, только если нет никаких параметров
// task-form может иметь taskId (редактирование), wishlistId (создание из желания), или returnTo (возврат после создания)
const isTaskFormWithNoParams = tab === 'task-form' && params.taskId === undefined && params.wishlistId === undefined && params.returnTo === undefined
const isWishlistFormWithNoParams = tab === 'wishlist-form' && params.wishlistId === undefined && params.newTaskId === undefined && params.boardId === undefined
// Проверяем, что boardId не null и не undefined (null означает "нет доски", но это валидное значение)
const hasBoardId = params.boardId !== null && params.boardId !== undefined
const isWishlistFormWithNoParams = tab === 'wishlist-form' && params.wishlistId === undefined && params.newTaskId === undefined && !hasBoardId
if (isTaskFormWithNoParams || isWishlistFormWithNoParams) {
setTabParams({})
if (isNewTabMain) {
@@ -1041,7 +1043,22 @@ function AppContent() {
{/* Кнопка добавления желания (только для таба wishlist) */}
{!isFullscreenTab && activeTab === 'wishlist' && (
<button
onClick={() => handleNavigate('wishlist-form', { wishlistId: undefined })}
onClick={() => {
// Получаем boardId из tabParams или из localStorage (где его сохраняет компонент Wishlist)
let boardId = tabParams.boardId
if (!boardId) {
try {
const saved = localStorage.getItem('wishlist_selected_board_id')
if (saved) {
const parsed = parseInt(saved, 10)
if (!isNaN(parsed)) boardId = parsed
}
} catch (err) {
console.error('Error loading boardId from localStorage:', err)
}
}
handleNavigate('wishlist-form', { wishlistId: undefined, boardId: boardId })
}}
className="fixed bottom-16 right-4 z-20 bg-gradient-to-r from-indigo-600 to-purple-600 hover:from-indigo-700 hover:to-purple-700 text-white w-[61px] h-[61px] rounded-2xl shadow-lg transition-all duration-200 hover:scale-105 flex items-center justify-center"
title="Добавить желание"
>

View File

@@ -399,7 +399,10 @@ function Wishlist({ onNavigate, refreshTrigger = 0, isActive = false, initialBoa
}
const handleAddClick = () => {
onNavigate?.('wishlist-form', { wishlistId: undefined, boardId: selectedBoardId })
// Если selectedBoardId равен null, но есть доски, используем первую доску
// Если доски еще не загружены, используем initialBoardId
const boardIdToUse = selectedBoardId || (boards.length > 0 ? boards[0].id : initialBoardId)
onNavigate?.('wishlist-form', { wishlistId: undefined, boardId: boardIdToUse })
}
const handleItemClick = (item) => {

View File

@@ -851,17 +851,16 @@ function WishlistForm({ onNavigate, wishlistId, editConditionIndex, newTaskId, b
/>
)}
{toastMessage && (
<Toast
message={toastMessage.text}
type={toastMessage.type}
onClose={() => setToastMessage(null)}
/>
)}
</>
)}
)}
</div>
{toastMessage && (
<Toast
message={toastMessage.text}
type={toastMessage.type}
onClose={() => setToastMessage(null)}
/>
)}
</>
)}
</div>
)
}

View File

@@ -2,9 +2,9 @@
# Скрипт для восстановления базы данных из дампа
# Использование:
# ./restore-db.sh [имя_дампа.sql.gz] # Восстановление в .env
# ./restore-db.sh [имя_дампа.sql.gz] # Восстановление в .env (локальная БД)
# ./restore-db.sh --env-file .env.prod [имя_дампа] # Восстановление в указанный файл
# ./restore-db.sh production-backup.sql.gz # Восстановление в .env
# ./restore-db.sh production-backup.sql.gz # Восстановление в .env (локальная БД)
set -e