From 8d79a8f69215a02ce9535de8cb3c0a97eb931d49 Mon Sep 17 00:00:00 2001 From: poignatov Date: Mon, 29 Dec 2025 20:58:34 +0300 Subject: [PATCH] =?UTF-8?q?Fix:=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=BF=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BD=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=BD=D0=B0=20=D0=B3=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=86=D0=B5=20=D0=B3=D0=BE=D0=B4=D0=B0=20(ISOYEAR)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Заменен EXTRACT(YEAR) на EXTRACT(ISOYEAR) в materialized view для корректной работы на границе года - Обновлена миграция 001_create_schema.sql для использования ISOYEAR - Создана миграция 006_fix_weekly_report_mv_structure.sql для исправления структуры view (LEFT JOIN) - Добавлен endpoint /admin/recreate-mv для пересоздания materialized view - Обновлена документация миграций в README.md - Обновлены зависимости Go (go.mod, go.sum) --- play-life-backend/go.mod | 7 +- play-life-backend/go.sum | 2 + play-life-backend/main.go | 72 ++++++++++++++++++- .../migrations/001_create_schema.sql | 5 +- .../006_fix_weekly_report_mv_structure.sql | 46 ++++++++++++ play-life-backend/migrations/README.md | 27 +++++++ 6 files changed, 151 insertions(+), 8 deletions(-) create mode 100644 play-life-backend/migrations/006_fix_weekly_report_mv_structure.sql diff --git a/play-life-backend/go.mod b/play-life-backend/go.mod index 19d3627..d3fbf27 100644 --- a/play-life-backend/go.mod +++ b/play-life-backend/go.mod @@ -3,12 +3,9 @@ module play-eng-backend go 1.21 require ( + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 github.com/gorilla/mux v1.8.1 + github.com/joho/godotenv v1.5.1 github.com/lib/pq v1.10.9 github.com/robfig/cron/v3 v3.0.1 ) - -require ( - github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 // indirect - github.com/joho/godotenv v1.5.1 // indirect -) diff --git a/play-life-backend/go.sum b/play-life-backend/go.sum index c9763d0..70f945b 100644 --- a/play-life-backend/go.sum +++ b/play-life-backend/go.sum @@ -6,3 +6,5 @@ github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= diff --git a/play-life-backend/main.go b/play-life-backend/main.go index c69be66..c419935 100644 --- a/play-life-backend/main.go +++ b/play-life-backend/main.go @@ -1871,7 +1871,7 @@ func (a *App) initPlayLifeDB() error { ( SELECT n.project_id, - EXTRACT(YEAR FROM e.created_date)::INTEGER AS report_year, + EXTRACT(ISOYEAR FROM e.created_date)::INTEGER AS report_year, EXTRACT(WEEK FROM e.created_date)::INTEGER AS report_week, SUM(n.score) AS total_score FROM @@ -2342,6 +2342,7 @@ func main() { r.HandleFunc("/d2dc349a-0d13-49b2-a8f0-1ab094bfba9b", app.getFullStatisticsHandler).Methods("GET", "OPTIONS") r.HandleFunc("/admin", app.adminHandler).Methods("GET") r.HandleFunc("/admin.html", app.adminHandler).Methods("GET") + r.HandleFunc("/admin/recreate-mv", app.recreateMaterializedViewHandler).Methods("POST", "OPTIONS") port := getEnv("PORT", "8080") log.Printf("Server starting on port %s", port) @@ -3147,6 +3148,75 @@ func (a *App) adminHandler(w http.ResponseWriter, r *http.Request) { http.ServeFile(w, r, adminPath) } +// recreateMaterializedViewHandler пересоздает materialized view с исправленной логикой ISOYEAR +func (a *App) recreateMaterializedViewHandler(w http.ResponseWriter, r *http.Request) { + if r.Method == "OPTIONS" { + setCORSHeaders(w) + w.WriteHeader(http.StatusOK) + return + } + setCORSHeaders(w) + + log.Printf("Recreating materialized view weekly_report_mv with ISOYEAR fix") + + // Удаляем старый view + dropMaterializedView := `DROP MATERIALIZED VIEW IF EXISTS weekly_report_mv` + if _, err := a.DB.Exec(dropMaterializedView); err != nil { + log.Printf("Error dropping materialized view: %v", err) + sendErrorWithCORS(w, fmt.Sprintf("Error dropping materialized view: %v", err), http.StatusInternalServerError) + return + } + + // Создаем новый view с ISOYEAR + createMaterializedView := ` + CREATE MATERIALIZED VIEW weekly_report_mv AS + SELECT + p.id AS project_id, + agg.report_year, + agg.report_week, + COALESCE(agg.total_score, 0.0000) AS total_score + FROM + projects p + LEFT JOIN + ( + SELECT + n.project_id, + EXTRACT(ISOYEAR FROM e.created_date)::INTEGER AS report_year, + EXTRACT(WEEK FROM e.created_date)::INTEGER AS report_week, + SUM(n.score) AS total_score + FROM + nodes n + JOIN + entries e ON n.entry_id = e.id + GROUP BY + 1, 2, 3 + ) agg + ON p.id = agg.project_id + ORDER BY + p.id, agg.report_year, agg.report_week + ` + + if _, err := a.DB.Exec(createMaterializedView); err != nil { + log.Printf("Error creating materialized view: %v", err) + sendErrorWithCORS(w, fmt.Sprintf("Error creating materialized view: %v", err), http.StatusInternalServerError) + return + } + + // Создаем индекс + createMVIndex := ` + CREATE INDEX IF NOT EXISTS idx_weekly_report_mv_project_year_week + ON weekly_report_mv(project_id, report_year, report_week) + ` + if _, err := a.DB.Exec(createMVIndex); err != nil { + log.Printf("Warning: Failed to create materialized view index: %v", err) + } + + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(map[string]string{ + "message": "Materialized view recreated successfully with ISOYEAR fix", + }) +} + func (a *App) getProjectsHandler(w http.ResponseWriter, r *http.Request) { if r.Method == "OPTIONS" { setCORSHeaders(w) diff --git a/play-life-backend/migrations/001_create_schema.sql b/play-life-backend/migrations/001_create_schema.sql index 6539fe0..90c8d26 100644 --- a/play-life-backend/migrations/001_create_schema.sql +++ b/play-life-backend/migrations/001_create_schema.sql @@ -73,9 +73,10 @@ FROM LEFT JOIN ( -- 1. Предварительная агрегация: суммируем score по неделям + -- Используем ISOYEAR для корректной работы на границе года SELECT n.project_id, - EXTRACT(YEAR FROM e.created_date)::INTEGER AS report_year, + EXTRACT(ISOYEAR FROM e.created_date)::INTEGER AS report_year, EXTRACT(WEEK FROM e.created_date)::INTEGER AS report_week, SUM(n.score) AS total_score FROM @@ -101,5 +102,5 @@ COMMENT ON TABLE projects IS 'Projects table storing project information with pr COMMENT ON TABLE entries IS 'Entries table storing entry creation timestamps'; COMMENT ON TABLE nodes IS 'Nodes table linking projects, entries and storing scores'; COMMENT ON TABLE weekly_goals IS 'Weekly goals for projects'; -COMMENT ON MATERIALIZED VIEW weekly_report_mv IS 'Materialized view aggregating weekly scores by project'; +COMMENT ON MATERIALIZED VIEW weekly_report_mv IS 'Materialized view aggregating weekly scores by project using ISOYEAR for correct week calculations at year boundaries'; diff --git a/play-life-backend/migrations/006_fix_weekly_report_mv_structure.sql b/play-life-backend/migrations/006_fix_weekly_report_mv_structure.sql new file mode 100644 index 0000000..9d11af3 --- /dev/null +++ b/play-life-backend/migrations/006_fix_weekly_report_mv_structure.sql @@ -0,0 +1,46 @@ +-- Migration: Fix weekly_report_mv structure to include all projects via LEFT JOIN +-- This ensures the view structure matches the code in main.go +-- Date: 2024-12-29 +-- +-- Issue: Migration 005 created the view without LEFT JOIN to projects table, +-- which means projects without data were not included in the view. +-- This migration fixes the structure to match main.go implementation. + +-- Drop existing materialized view +DROP MATERIALIZED VIEW IF EXISTS weekly_report_mv; + +-- Recreate materialized view with correct structure (LEFT JOIN with projects) +-- This ensures all projects are included, even if they have no data for a given week +CREATE MATERIALIZED VIEW weekly_report_mv AS +SELECT + p.id AS project_id, + agg.report_year, + agg.report_week, + COALESCE(agg.total_score, 0.0000) AS total_score +FROM + projects p +LEFT JOIN + ( + SELECT + n.project_id, + EXTRACT(ISOYEAR FROM e.created_date)::INTEGER AS report_year, + EXTRACT(WEEK FROM e.created_date)::INTEGER AS report_week, + SUM(n.score) AS total_score + FROM + nodes n + JOIN + entries e ON n.entry_id = e.id + GROUP BY + 1, 2, 3 + ) agg + ON p.id = agg.project_id +ORDER BY + p.id, agg.report_year, agg.report_week +WITH DATA; + +-- Recreate index +CREATE INDEX IF NOT EXISTS idx_weekly_report_mv_project_year_week + ON weekly_report_mv(project_id, report_year, report_week); + +COMMENT ON MATERIALIZED VIEW weekly_report_mv IS 'Materialized view aggregating weekly scores by project using ISOYEAR for correct week calculations at year boundaries. Includes all projects via LEFT JOIN.'; + diff --git a/play-life-backend/migrations/README.md b/play-life-backend/migrations/README.md index fa6c607..4684e3d 100644 --- a/play-life-backend/migrations/README.md +++ b/play-life-backend/migrations/README.md @@ -57,6 +57,33 @@ docker-compose exec db psql -U playeng -d playeng -f /migrations/001_create_sche - `report_week` (INTEGER) - `total_score` (NUMERIC) +## Миграции + +### Порядок применения миграций + +1. **001_create_schema.sql** - Создание базовой структуры (таблицы, индексы, materialized view) +2. **002_add_dictionaries.sql** - Добавление таблиц для словарей +3. **003_remove_words_unique_constraint.sql** - Удаление уникального ограничения на words.name +4. **004_add_config_dictionaries.sql** - Добавление связи между конфигурациями и словарями +5. **005_fix_weekly_report_mv.sql** - Исправление использования ISOYEAR вместо YEAR для корректной работы на границе года +6. **006_fix_weekly_report_mv_structure.sql** - Исправление структуры view (добавление LEFT JOIN для включения всех проектов) + +### Применение миграций + +Для существующей базы данных применяйте миграции последовательно: + +```bash +psql -U playeng -d playeng -f migrations/005_fix_weekly_report_mv.sql +psql -U playeng -d playeng -f migrations/006_fix_weekly_report_mv_structure.sql +``` + +Или через docker-compose: + +```bash +docker-compose exec db psql -U playeng -d playeng -f /migrations/005_fix_weekly_report_mv.sql +docker-compose exec db psql -U playeng -d playeng -f /migrations/006_fix_weekly_report_mv_structure.sql +``` + ## Обновление Materialized View После изменения данных в таблицах `nodes` или `entries`, необходимо обновить materialized view: