4.25.0: Группы вместо проектов для задач и желаний
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m31s
All checks were successful
Build and Push Docker Image / build-and-push (push) Successful in 1m31s
This commit is contained in:
@@ -330,6 +330,7 @@ type Task struct {
|
||||
Position *int `json:"position,omitempty"` // Position for subtasks
|
||||
// Дополнительные поля для списка задач (без omitempty чтобы всегда передавались)
|
||||
ProjectNames []string `json:"project_names"`
|
||||
GroupName *string `json:"group_name,omitempty"` // Название группы задачи
|
||||
SubtasksCount int `json:"subtasks_count"`
|
||||
HasProgression bool `json:"has_progression"`
|
||||
AutoComplete bool `json:"auto_complete"`
|
||||
@@ -391,6 +392,7 @@ type TaskRequest struct {
|
||||
RepetitionDate *string `json:"repetition_date,omitempty"`
|
||||
WishlistID *int `json:"wishlist_id,omitempty"`
|
||||
RewardPolicy *string `json:"reward_policy,omitempty"` // "personal" или "general" для задач, связанных с желаниями
|
||||
GroupName *string `json:"group_name,omitempty"` // Название группы задачи
|
||||
Rewards []RewardRequest `json:"rewards,omitempty"`
|
||||
Subtasks []SubtaskRequest `json:"subtasks,omitempty"`
|
||||
// Test-specific fields
|
||||
@@ -464,9 +466,8 @@ type WishlistItem struct {
|
||||
LockedConditionsCount int `json:"locked_conditions_count,omitempty"` // Общее количество заблокированных условий
|
||||
UnlockConditions []UnlockConditionDisplay `json:"unlock_conditions,omitempty"`
|
||||
LinkedTask *LinkedTask `json:"linked_task,omitempty"`
|
||||
TasksCount int `json:"tasks_count,omitempty"` // Количество задач для этого желания
|
||||
ProjectID *int `json:"project_id,omitempty"` // ID проекта, к которому принадлежит желание
|
||||
ProjectName *string `json:"project_name,omitempty"` // Название проекта
|
||||
TasksCount int `json:"tasks_count,omitempty"` // Количество задач для этого желания
|
||||
GroupName *string `json:"group_name,omitempty"` // Название группы желания
|
||||
}
|
||||
|
||||
type UnlockConditionDisplay struct {
|
||||
@@ -494,7 +495,7 @@ type WishlistRequest struct {
|
||||
Name string `json:"name"`
|
||||
Price *float64 `json:"price,omitempty"`
|
||||
Link *string `json:"link,omitempty"`
|
||||
ProjectID *int `json:"project_id,omitempty"` // ID проекта, к которому принадлежит желание
|
||||
GroupName *string `json:"group_name,omitempty"` // Название группы желания
|
||||
UnlockConditions []UnlockConditionRequest `json:"unlock_conditions,omitempty"`
|
||||
}
|
||||
|
||||
@@ -4227,6 +4228,9 @@ func main() {
|
||||
protected.HandleFunc("/api/wishlist/{id}/uncomplete", app.uncompleteWishlistHandler).Methods("POST", "OPTIONS")
|
||||
protected.HandleFunc("/api/wishlist/{id}/copy", app.copyWishlistHandler).Methods("POST", "OPTIONS")
|
||||
|
||||
// Group suggestions
|
||||
protected.HandleFunc("/api/group-suggestions", app.getGroupSuggestionsHandler).Methods("GET", "OPTIONS")
|
||||
|
||||
// Admin operations
|
||||
protected.HandleFunc("/admin/recreate-mv", app.recreateMaterializedViewHandler).Methods("POST", "OPTIONS")
|
||||
|
||||
@@ -5868,6 +5872,11 @@ func (a *App) moveProjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем MV для групповых саджестов (имя проекта изменилось)
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"message": "Project renamed successfully",
|
||||
@@ -5949,8 +5958,10 @@ func (a *App) moveProjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// MV обновляется только по крону в понедельник в 6:00 утра
|
||||
// Данные текущей недели берутся напрямую из nodes
|
||||
// Обновляем MV для групповых саджестов (проект переименован или удалён)
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
@@ -6027,8 +6038,10 @@ func (a *App) deleteProjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// MV обновляется только по крону в понедельник в 6:00 утра
|
||||
// Данные текущей недели берутся напрямую из nodes
|
||||
// Обновляем MV для групповых саджестов (проект удалён)
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
@@ -6094,6 +6107,11 @@ func (a *App) createProjectHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем MV для групповых саджестов (проекты попадают в саджесты)
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"message": "Project created successfully",
|
||||
@@ -7333,6 +7351,7 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
t.wishlist_id,
|
||||
t.config_id,
|
||||
t.reward_policy,
|
||||
t.group_name,
|
||||
COALESCE((
|
||||
SELECT COUNT(*)
|
||||
FROM tasks st
|
||||
@@ -7389,6 +7408,7 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var wishlistID sql.NullInt64
|
||||
var configID sql.NullInt64
|
||||
var rewardPolicy sql.NullString
|
||||
var groupName sql.NullString
|
||||
var projectNames pq.StringArray
|
||||
var subtaskProjectNames pq.StringArray
|
||||
var autoComplete bool
|
||||
@@ -7405,6 +7425,7 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
&wishlistID,
|
||||
&configID,
|
||||
&rewardPolicy,
|
||||
&groupName,
|
||||
&task.SubtasksCount,
|
||||
&projectNames,
|
||||
&subtaskProjectNames,
|
||||
@@ -7444,6 +7465,10 @@ func (a *App) getTasksHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if rewardPolicy.Valid {
|
||||
task.RewardPolicy = &rewardPolicy.String
|
||||
}
|
||||
if groupName.Valid && groupName.String != "" {
|
||||
groupNameVal := groupName.String
|
||||
task.GroupName = &groupNameVal
|
||||
}
|
||||
task.AutoComplete = autoComplete
|
||||
|
||||
// Объединяем проекты из основной задачи и подзадач
|
||||
@@ -7504,6 +7529,7 @@ func (a *App) getTaskDetailHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var wishlistID sql.NullInt64
|
||||
var configID sql.NullInt64
|
||||
var rewardPolicy sql.NullString
|
||||
var groupName sql.NullString
|
||||
|
||||
// Сначала получаем значение как строку напрямую, чтобы избежать проблем с NULL
|
||||
var repetitionPeriodStr string
|
||||
@@ -7514,11 +7540,12 @@ func (a *App) getTaskDetailHandler(w http.ResponseWriter, r *http.Request) {
|
||||
COALESCE(repetition_date, '') as repetition_date,
|
||||
wishlist_id,
|
||||
config_id,
|
||||
reward_policy
|
||||
reward_policy,
|
||||
group_name
|
||||
FROM tasks
|
||||
WHERE id = $1 AND user_id = $2 AND deleted = FALSE
|
||||
`, taskID, userID).Scan(
|
||||
&task.ID, &task.Name, &task.Completed, &lastCompletedAt, &nextShowAt, &rewardMessage, &progressionBase, &repetitionPeriodStr, &repetitionDateStr, &wishlistID, &configID, &rewardPolicy,
|
||||
&task.ID, &task.Name, &task.Completed, &lastCompletedAt, &nextShowAt, &rewardMessage, &progressionBase, &repetitionPeriodStr, &repetitionDateStr, &wishlistID, &configID, &rewardPolicy, &groupName,
|
||||
)
|
||||
|
||||
log.Printf("Scanned repetition_period for task %d: String='%s', repetition_date='%s'", taskID, repetitionPeriodStr, repetitionDateStr)
|
||||
@@ -7578,6 +7605,10 @@ func (a *App) getTaskDetailHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if rewardPolicy.Valid {
|
||||
task.RewardPolicy = &rewardPolicy.String
|
||||
}
|
||||
if groupName.Valid && groupName.String != "" {
|
||||
groupNameVal := groupName.String
|
||||
task.GroupName = &groupNameVal
|
||||
}
|
||||
|
||||
// Получаем награды основной задачи
|
||||
rewards := make([]Reward, 0)
|
||||
@@ -8111,11 +8142,11 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
now := time.Now().In(loc)
|
||||
insertSQL = `
|
||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, next_show_at, completed, deleted, wishlist_id, reward_policy)
|
||||
VALUES ($1, $2, $3, $4, $5::INTERVAL, NULL, $6, 0, FALSE, $7, $8)
|
||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, next_show_at, completed, deleted, wishlist_id, reward_policy, group_name)
|
||||
VALUES ($1, $2, $3, $4, $5::INTERVAL, NULL, $6, 0, FALSE, $7, $8, $9)
|
||||
RETURNING id
|
||||
`
|
||||
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionPeriodValue, now, wishlistIDValue, rewardPolicyValue}
|
||||
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionPeriodValue, now, wishlistIDValue, rewardPolicyValue, req.GroupName}
|
||||
} else if repetitionDate.Valid {
|
||||
// Вычисляем next_show_at для задачи с repetition_date
|
||||
|
||||
@@ -8130,26 +8161,26 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
nextShowAt := calculateNextShowAtFromRepetitionDate(repetitionDate.String, time.Now().In(loc))
|
||||
if nextShowAt != nil {
|
||||
insertSQL = `
|
||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, next_show_at, completed, deleted, wishlist_id, reward_policy)
|
||||
VALUES ($1, $2, $3, $4, NULL, $5, $6, 0, FALSE, $7, $8)
|
||||
RETURNING id
|
||||
`
|
||||
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, nextShowAt, wishlistIDValue, rewardPolicyValue}
|
||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, next_show_at, completed, deleted, wishlist_id, reward_policy, group_name)
|
||||
VALUES ($1, $2, $3, $4, NULL, $5, $6, 0, FALSE, $7, $8, $9)
|
||||
RETURNING id
|
||||
`
|
||||
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, nextShowAt, wishlistIDValue, rewardPolicyValue, req.GroupName}
|
||||
} else {
|
||||
insertSQL = `
|
||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, completed, deleted, wishlist_id, reward_policy)
|
||||
VALUES ($1, $2, $3, $4, NULL, $5, 0, FALSE, $6, $7)
|
||||
RETURNING id
|
||||
`
|
||||
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, wishlistIDValue, rewardPolicyValue}
|
||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, completed, deleted, wishlist_id, reward_policy, group_name)
|
||||
VALUES ($1, $2, $3, $4, NULL, $5, 0, FALSE, $6, $7, $8)
|
||||
RETURNING id
|
||||
`
|
||||
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, wishlistIDValue, rewardPolicyValue, req.GroupName}
|
||||
}
|
||||
} else {
|
||||
insertSQL = `
|
||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, completed, deleted, wishlist_id, reward_policy)
|
||||
VALUES ($1, $2, $3, $4, NULL, NULL, 0, FALSE, $5, $6)
|
||||
INSERT INTO tasks (user_id, name, reward_message, progression_base, repetition_period, repetition_date, completed, deleted, wishlist_id, reward_policy, group_name)
|
||||
VALUES ($1, $2, $3, $4, NULL, NULL, 0, FALSE, $5, $6, $7)
|
||||
RETURNING id
|
||||
`
|
||||
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, wishlistIDValue, rewardPolicyValue}
|
||||
insertArgs = []interface{}{userID, strings.TrimSpace(req.Name), rewardMessage, progressionBase, wishlistIDValue, rewardPolicyValue, req.GroupName}
|
||||
}
|
||||
|
||||
err = tx.QueryRow(insertSQL, insertArgs...).Scan(&taskID)
|
||||
@@ -8316,6 +8347,13 @@ func (a *App) createTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем MV для групповых саджестов
|
||||
if req.GroupName != nil && *req.GroupName != "" {
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Возвращаем созданную задачу
|
||||
var createdTask Task
|
||||
var lastCompletedAt sql.NullString
|
||||
@@ -8530,35 +8568,35 @@ func (a *App) updateTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
now := time.Now().In(loc)
|
||||
updateSQL = `
|
||||
UPDATE tasks
|
||||
SET name = $1, reward_message = $2, progression_base = $3, repetition_period = $4::INTERVAL, repetition_date = NULL, next_show_at = $5, wishlist_id = $6, reward_policy = $7
|
||||
WHERE id = $8
|
||||
SET name = $1, reward_message = $2, progression_base = $3, repetition_period = $4::INTERVAL, repetition_date = NULL, next_show_at = $5, wishlist_id = $6, reward_policy = $7, group_name = $8
|
||||
WHERE id = $9
|
||||
`
|
||||
updateArgs = []interface{}{strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionPeriod.String, now, newWishlistID, rewardPolicyValue, taskID}
|
||||
updateArgs = []interface{}{strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionPeriod.String, now, newWishlistID, rewardPolicyValue, req.GroupName, taskID}
|
||||
} else if repetitionDate.Valid {
|
||||
// Вычисляем next_show_at для задачи с repetition_date
|
||||
nextShowAt := calculateNextShowAtFromRepetitionDate(repetitionDate.String, time.Now().In(loc))
|
||||
if nextShowAt != nil {
|
||||
updateSQL = `
|
||||
UPDATE tasks
|
||||
SET name = $1, reward_message = $2, progression_base = $3, repetition_period = NULL, repetition_date = $4, next_show_at = $5, wishlist_id = $6, reward_policy = $7
|
||||
WHERE id = $8
|
||||
`
|
||||
updateArgs = []interface{}{strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, nextShowAt, newWishlistID, rewardPolicyValue, taskID}
|
||||
UPDATE tasks
|
||||
SET name = $1, reward_message = $2, progression_base = $3, repetition_period = NULL, repetition_date = $4, next_show_at = $5, wishlist_id = $6, reward_policy = $7, group_name = $8
|
||||
WHERE id = $9
|
||||
`
|
||||
updateArgs = []interface{}{strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, nextShowAt, newWishlistID, rewardPolicyValue, req.GroupName, taskID}
|
||||
} else {
|
||||
updateSQL = `
|
||||
UPDATE tasks
|
||||
SET name = $1, reward_message = $2, progression_base = $3, repetition_period = NULL, repetition_date = $4, wishlist_id = $5, reward_policy = $6
|
||||
WHERE id = $7
|
||||
`
|
||||
updateArgs = []interface{}{strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, newWishlistID, rewardPolicyValue, taskID}
|
||||
UPDATE tasks
|
||||
SET name = $1, reward_message = $2, progression_base = $3, repetition_period = NULL, repetition_date = $4, wishlist_id = $5, reward_policy = $6, group_name = $7
|
||||
WHERE id = $8
|
||||
`
|
||||
updateArgs = []interface{}{strings.TrimSpace(req.Name), rewardMessage, progressionBase, repetitionDate.String, newWishlistID, rewardPolicyValue, req.GroupName, taskID}
|
||||
}
|
||||
} else {
|
||||
updateSQL = `
|
||||
UPDATE tasks
|
||||
SET name = $1, reward_message = $2, progression_base = $3, repetition_period = NULL, repetition_date = NULL, next_show_at = NULL, wishlist_id = $4, reward_policy = $5
|
||||
WHERE id = $6
|
||||
SET name = $1, reward_message = $2, progression_base = $3, repetition_period = NULL, repetition_date = NULL, next_show_at = NULL, wishlist_id = $4, reward_policy = $5, group_name = $6
|
||||
WHERE id = $7
|
||||
`
|
||||
updateArgs = []interface{}{strings.TrimSpace(req.Name), rewardMessage, progressionBase, newWishlistID, rewardPolicyValue, taskID}
|
||||
updateArgs = []interface{}{strings.TrimSpace(req.Name), rewardMessage, progressionBase, newWishlistID, rewardPolicyValue, req.GroupName, taskID}
|
||||
}
|
||||
|
||||
_, err = tx.Exec(updateSQL, updateArgs...)
|
||||
@@ -8877,6 +8915,13 @@ func (a *App) updateTaskHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем MV для групповых саджестов
|
||||
if req.GroupName != nil && *req.GroupName != "" {
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Возвращаем обновленную задачу
|
||||
var updatedTask Task
|
||||
var lastCompletedAt sql.NullString
|
||||
@@ -10347,8 +10392,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
|
||||
wi.image_path,
|
||||
wi.link,
|
||||
wi.completed,
|
||||
wi.project_id AS item_project_id,
|
||||
wp.name AS item_project_name,
|
||||
wi.group_name,
|
||||
wc.id AS condition_id,
|
||||
wc.display_order,
|
||||
wc.task_condition_id,
|
||||
@@ -10361,7 +10405,6 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
|
||||
sc.required_points,
|
||||
sc.start_date
|
||||
FROM wishlist_items wi
|
||||
LEFT JOIN projects wp ON wi.project_id = wp.id AND wp.deleted = FALSE
|
||||
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 AND t.deleted = FALSE
|
||||
@@ -10388,8 +10431,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
|
||||
var price sql.NullFloat64
|
||||
var imagePath, link sql.NullString
|
||||
var completed bool
|
||||
var itemProjectID sql.NullInt64
|
||||
var itemProjectName sql.NullString
|
||||
var groupName sql.NullString
|
||||
|
||||
var conditionID, displayOrder sql.NullInt64
|
||||
var taskConditionID, scoreConditionID sql.NullInt64
|
||||
@@ -10403,7 +10445,7 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
|
||||
|
||||
err := rows.Scan(
|
||||
&itemID, &name, &price, &imagePath, &link, &completed,
|
||||
&itemProjectID, &itemProjectName,
|
||||
&groupName,
|
||||
&conditionID, &displayOrder,
|
||||
&taskConditionID, &scoreConditionID, &conditionUserID,
|
||||
&taskID, &taskName,
|
||||
@@ -10434,13 +10476,9 @@ func (a *App) getWishlistItemsWithConditions(userID int, includeCompleted bool)
|
||||
l := link.String
|
||||
item.Link = &l
|
||||
}
|
||||
if itemProjectID.Valid {
|
||||
projectIDVal := int(itemProjectID.Int64)
|
||||
item.ProjectID = &projectIDVal
|
||||
}
|
||||
if itemProjectName.Valid {
|
||||
projectNameVal := itemProjectName.String
|
||||
item.ProjectName = &projectNameVal
|
||||
if groupName.Valid && groupName.String != "" {
|
||||
groupNameVal := groupName.String
|
||||
item.GroupName = &groupNameVal
|
||||
}
|
||||
itemsMap[itemID] = item
|
||||
}
|
||||
@@ -11108,10 +11146,10 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var wishlistID int
|
||||
err = tx.QueryRow(`
|
||||
INSERT INTO wishlist_items (user_id, author_id, name, price, link, project_id, completed, deleted)
|
||||
INSERT INTO wishlist_items (user_id, author_id, name, price, link, group_name, completed, deleted)
|
||||
VALUES ($1, $1, $2, $3, $4, $5, FALSE, FALSE)
|
||||
RETURNING id
|
||||
`, userID, strings.TrimSpace(req.Name), req.Price, req.Link, req.ProjectID).Scan(&wishlistID)
|
||||
`, userID, strings.TrimSpace(req.Name), req.Price, req.Link, req.GroupName).Scan(&wishlistID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error creating wishlist item: %v", err)
|
||||
@@ -11141,6 +11179,13 @@ func (a *App) createWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
log.Printf("createWishlistHandler: transaction committed successfully")
|
||||
|
||||
// Обновляем MV для групповых саджестов
|
||||
if req.GroupName != nil && *req.GroupName != "" {
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем созданное желание с условиями
|
||||
items, err := a.getWishlistItemsWithConditions(userID, false)
|
||||
if err != nil {
|
||||
@@ -11322,8 +11367,7 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
wi.image_path,
|
||||
wi.link,
|
||||
wi.completed,
|
||||
wi.project_id AS item_project_id,
|
||||
wp.name AS item_project_name,
|
||||
wi.group_name,
|
||||
wc.id AS condition_id,
|
||||
wc.display_order,
|
||||
wc.task_condition_id,
|
||||
@@ -11337,7 +11381,6 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
sc.required_points,
|
||||
sc.start_date
|
||||
FROM wishlist_items wi
|
||||
LEFT JOIN projects wp ON wi.project_id = wp.id AND wp.deleted = FALSE
|
||||
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 AND t.deleted = FALSE
|
||||
@@ -11364,8 +11407,7 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var imagePath sql.NullString
|
||||
var link sql.NullString
|
||||
var completed bool
|
||||
var itemProjectID sql.NullInt64
|
||||
var itemProjectName sql.NullString
|
||||
var groupName sql.NullString
|
||||
var conditionID sql.NullInt64
|
||||
var displayOrder sql.NullInt64
|
||||
var taskConditionID sql.NullInt64
|
||||
@@ -11380,7 +11422,7 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var startDate sql.NullTime
|
||||
|
||||
err := rows.Scan(
|
||||
&itemID, &name, &price, &imagePath, &link, &completed, &itemProjectID, &itemProjectName,
|
||||
&itemID, &name, &price, &imagePath, &link, &completed, &groupName,
|
||||
&conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID,
|
||||
&taskID, &taskName, &taskNextShowAt, &projectID, &projectName, &requiredPoints, &startDate,
|
||||
)
|
||||
@@ -11410,13 +11452,9 @@ func (a *App) getWishlistItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if link.Valid {
|
||||
item.Link = &link.String
|
||||
}
|
||||
if itemProjectID.Valid {
|
||||
projectIDVal := int(itemProjectID.Int64)
|
||||
item.ProjectID = &projectIDVal
|
||||
}
|
||||
if itemProjectName.Valid {
|
||||
projectNameVal := itemProjectName.String
|
||||
item.ProjectName = &projectNameVal
|
||||
if groupName.Valid && groupName.String != "" {
|
||||
groupNameVal := groupName.String
|
||||
item.GroupName = &groupNameVal
|
||||
}
|
||||
itemsMap[itemID] = item
|
||||
}
|
||||
@@ -11679,9 +11717,9 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Обновляем желание (не проверяем user_id в WHERE, так как доступ уже проверен выше)
|
||||
_, err = tx.Exec(`
|
||||
UPDATE wishlist_items
|
||||
SET name = $1, price = $2, link = $3, project_id = $4, updated_at = NOW()
|
||||
SET name = $1, price = $2, link = $3, group_name = $4, updated_at = NOW()
|
||||
WHERE id = $5
|
||||
`, strings.TrimSpace(req.Name), req.Price, req.Link, req.ProjectID, itemID)
|
||||
`, strings.TrimSpace(req.Name), req.Price, req.Link, req.GroupName, itemID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("Error updating wishlist item: %v", err)
|
||||
@@ -11703,6 +11741,13 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем MV для групповых саджестов
|
||||
if req.GroupName != nil && *req.GroupName != "" {
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем обновлённое желание через getWishlistItemHandler логику
|
||||
// Используем тот же запрос, что и в getWishlistItemHandler
|
||||
query := `
|
||||
@@ -11713,6 +11758,7 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
wi.image_path,
|
||||
wi.link,
|
||||
wi.completed,
|
||||
wi.group_name,
|
||||
wc.id AS condition_id,
|
||||
wc.display_order,
|
||||
wc.task_condition_id,
|
||||
@@ -11752,6 +11798,7 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var imagePath sql.NullString
|
||||
var link sql.NullString
|
||||
var completed bool
|
||||
var groupName sql.NullString
|
||||
var conditionID sql.NullInt64
|
||||
var displayOrder sql.NullInt64
|
||||
var taskConditionID sql.NullInt64
|
||||
@@ -11765,7 +11812,7 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var startDate sql.NullTime
|
||||
|
||||
err := rows.Scan(
|
||||
&itemID, &name, &price, &imagePath, &link, &completed,
|
||||
&itemID, &name, &price, &imagePath, &link, &completed, &groupName,
|
||||
&conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID,
|
||||
&taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate,
|
||||
)
|
||||
@@ -11802,6 +11849,10 @@ func (a *App) updateWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
if link.Valid {
|
||||
item.Link = &link.String
|
||||
}
|
||||
if groupName.Valid && groupName.String != "" {
|
||||
groupNameVal := groupName.String
|
||||
item.GroupName = &groupNameVal
|
||||
}
|
||||
itemsMap[itemID] = item
|
||||
}
|
||||
|
||||
@@ -12416,11 +12467,12 @@ func (a *App) copyWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var ownerID int
|
||||
var boardID sql.NullInt64
|
||||
var authorID sql.NullInt64
|
||||
var groupName sql.NullString
|
||||
err = a.DB.QueryRow(`
|
||||
SELECT user_id, name, price, link, image_path, board_id, author_id
|
||||
SELECT user_id, name, price, link, image_path, board_id, author_id, group_name
|
||||
FROM wishlist_items
|
||||
WHERE id = $1 AND deleted = FALSE
|
||||
`, itemID).Scan(&ownerID, &name, &price, &link, &imagePath, &boardID, &authorID)
|
||||
`, itemID).Scan(&ownerID, &name, &price, &link, &imagePath, &boardID, &authorID, &groupName)
|
||||
|
||||
if err == sql.ErrNoRows || ownerID != userID {
|
||||
sendErrorWithCORS(w, "Wishlist item not found", http.StatusNotFound)
|
||||
@@ -12512,7 +12564,7 @@ func (a *App) copyWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// Определяем значения для board_id и author_id
|
||||
var boardIDVal, authorIDVal interface{}
|
||||
var boardIDVal, authorIDVal, groupNameVal interface{}
|
||||
if boardID.Valid {
|
||||
boardIDVal = int(boardID.Int64)
|
||||
}
|
||||
@@ -12522,12 +12574,15 @@ func (a *App) copyWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
// Если author_id не был установлен, используем текущего пользователя
|
||||
authorIDVal = userID
|
||||
}
|
||||
if groupName.Valid {
|
||||
groupNameVal = groupName.String
|
||||
}
|
||||
|
||||
err = tx.QueryRow(`
|
||||
INSERT INTO wishlist_items (user_id, board_id, author_id, name, price, link, completed, deleted)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, FALSE, FALSE)
|
||||
INSERT INTO wishlist_items (user_id, board_id, author_id, name, price, link, group_name, completed, deleted)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, FALSE, FALSE)
|
||||
RETURNING id
|
||||
`, ownerID, boardIDVal, authorIDVal, name+" (копия)", priceVal, linkVal).Scan(&newWishlistID)
|
||||
`, ownerID, boardIDVal, authorIDVal, name+" (копия)", priceVal, linkVal, groupNameVal).Scan(&newWishlistID)
|
||||
if err != nil {
|
||||
log.Printf("Error creating wishlist copy: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error creating wishlist copy: %v", err), http.StatusInternalServerError)
|
||||
@@ -12615,6 +12670,13 @@ func (a *App) copyWishlistHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем MV для групповых саджестов
|
||||
if groupName.Valid && groupName.String != "" {
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Получаем созданное желание с условиями
|
||||
items, err := a.getWishlistItemsWithConditions(userID, false)
|
||||
if err != nil {
|
||||
@@ -13844,8 +13906,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
|
||||
wi.image_path,
|
||||
wi.link,
|
||||
wi.completed,
|
||||
wi.project_id AS item_project_id,
|
||||
wp.name AS item_project_name,
|
||||
wi.group_name,
|
||||
COALESCE(wi.author_id, wi.user_id) AS item_owner_id,
|
||||
wc.id AS condition_id,
|
||||
wc.display_order,
|
||||
@@ -13859,7 +13920,6 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
|
||||
sc.required_points,
|
||||
sc.start_date
|
||||
FROM wishlist_items wi
|
||||
LEFT JOIN projects wp ON wi.project_id = wp.id AND wp.deleted = FALSE
|
||||
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 AND t.deleted = FALSE
|
||||
@@ -13886,8 +13946,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
|
||||
var imagePath sql.NullString
|
||||
var link sql.NullString
|
||||
var completed bool
|
||||
var itemProjectID sql.NullInt64
|
||||
var itemProjectName sql.NullString
|
||||
var groupName sql.NullString
|
||||
var itemOwnerID sql.NullInt64
|
||||
var conditionID sql.NullInt64
|
||||
var displayOrder sql.NullInt64
|
||||
@@ -13902,7 +13961,7 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
|
||||
var startDate sql.NullTime
|
||||
|
||||
err := rows.Scan(
|
||||
&itemID, &name, &price, &imagePath, &link, &completed, &itemProjectID, &itemProjectName, &itemOwnerID,
|
||||
&itemID, &name, &price, &imagePath, &link, &completed, &groupName, &itemOwnerID,
|
||||
&conditionID, &displayOrder, &taskConditionID, &scoreConditionID, &conditionUserID,
|
||||
&taskID, &taskName, &projectID, &projectName, &requiredPoints, &startDate,
|
||||
)
|
||||
@@ -13932,13 +13991,9 @@ func (a *App) getWishlistItemsByBoard(boardID int, userID int) ([]WishlistItem,
|
||||
if link.Valid {
|
||||
item.Link = &link.String
|
||||
}
|
||||
if itemProjectID.Valid {
|
||||
projectIDVal := int(itemProjectID.Int64)
|
||||
item.ProjectID = &projectIDVal
|
||||
}
|
||||
if itemProjectName.Valid {
|
||||
projectNameVal := itemProjectName.String
|
||||
item.ProjectName = &projectNameVal
|
||||
if groupName.Valid && groupName.String != "" {
|
||||
groupNameVal := groupName.String
|
||||
item.GroupName = &groupNameVal
|
||||
}
|
||||
itemsMap[itemID] = item
|
||||
}
|
||||
@@ -14211,10 +14266,10 @@ func (a *App) createBoardItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
var itemID int
|
||||
err = tx.QueryRow(`
|
||||
INSERT INTO wishlist_items (user_id, board_id, author_id, name, price, link, project_id, completed, deleted)
|
||||
INSERT INTO wishlist_items (user_id, board_id, author_id, name, price, link, group_name, completed, deleted)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, FALSE, FALSE)
|
||||
RETURNING id
|
||||
`, ownerID, boardID, userID, strings.TrimSpace(req.Name), req.Price, req.Link, req.ProjectID).Scan(&itemID)
|
||||
`, ownerID, boardID, userID, strings.TrimSpace(req.Name), req.Price, req.Link, req.GroupName).Scan(&itemID)
|
||||
|
||||
if err != nil {
|
||||
log.Printf("createBoardItemHandler: Error creating board item: %v", err)
|
||||
@@ -14244,6 +14299,13 @@ func (a *App) createBoardItemHandler(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
// Обновляем MV для групповых саджестов
|
||||
if req.GroupName != nil && *req.GroupName != "" {
|
||||
if err := a.refreshGroupSuggestionsMV(); err != nil {
|
||||
log.Printf("Warning: Failed to refresh group suggestions MV: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Возвращаем созданное желание
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
@@ -15521,3 +15583,57 @@ func decodeHTMLEntities(s string) string {
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// refreshGroupSuggestionsMV обновляет materialized view для групповых саджестов
|
||||
func (a *App) refreshGroupSuggestionsMV() error {
|
||||
_, err := a.DB.Exec("REFRESH MATERIALIZED VIEW CONCURRENTLY user_group_suggestions_mv")
|
||||
if err != nil {
|
||||
log.Printf("Error refreshing user_group_suggestions_mv: %v", err)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getGroupSuggestionsHandler возвращает список уникальных имён групп для текущего пользователя
|
||||
func (a *App) getGroupSuggestionsHandler(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
|
||||
}
|
||||
|
||||
query := `
|
||||
SELECT DISTINCT group_name
|
||||
FROM user_group_suggestions_mv
|
||||
WHERE user_id = $1
|
||||
ORDER BY group_name
|
||||
`
|
||||
|
||||
rows, err := a.DB.Query(query, userID)
|
||||
if err != nil {
|
||||
log.Printf("Error querying group suggestions: %v", err)
|
||||
sendErrorWithCORS(w, fmt.Sprintf("Error querying group suggestions: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
groups := make([]string, 0)
|
||||
for rows.Next() {
|
||||
var groupName string
|
||||
if err := rows.Scan(&groupName); err != nil {
|
||||
log.Printf("Error scanning group name: %v", err)
|
||||
continue
|
||||
}
|
||||
groups = append(groups, groupName)
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(groups)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user