From 7f30292a91919a8b8b636dc27db7634908a0cc2b Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Tue, 18 Mar 2025 12:45:21 +0400 Subject: [PATCH 01/26] feat(storage): add postgres connection --- .env.example | 5 + cmd/base/main.go | 11 ++ internal/storage/initTables.go | 270 +++++++++++++++++++++++++++++++++ internal/storage/storage.go | 38 +++++ 4 files changed, 324 insertions(+) create mode 100644 .env.example create mode 100644 cmd/base/main.go create mode 100644 internal/storage/initTables.go create mode 100644 internal/storage/storage.go diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7ad4c37 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +HOST="localhost" +PORT=5432 +POSTGRES_USERNAME="root" +POSTGRES_PASSWORD="123" +DBNAME="base" \ No newline at end of file diff --git a/cmd/base/main.go b/cmd/base/main.go new file mode 100644 index 0000000..365800c --- /dev/null +++ b/cmd/base/main.go @@ -0,0 +1,11 @@ +package main + +import "theAesthetics.ru/base/internal/storage" + +func main() { + // init logger + // init db + DB := storage.InitPostgres() + _ = DB + // run http server +} diff --git a/internal/storage/initTables.go b/internal/storage/initTables.go new file mode 100644 index 0000000..3225105 --- /dev/null +++ b/internal/storage/initTables.go @@ -0,0 +1,270 @@ +package storage + +import ( + "database/sql" + "fmt" + "log" +) + +// Выполнение SQL-запроса с проверкой ошибки +func execQuery(db *sql.DB, query string) error { + _, err := db.Exec(query) + if err != nil { + return fmt.Errorf("ошибка выполнения запроса: %w", err) + } + return nil +} + +func InitTables(db *sql.DB) { + // Создание всех таблиц + if err := createUsersTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы users: %v", err) + } + if err := createAchievementsTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы achievements: %v", err) + } + if err := createUsersAchievementsTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы users_achievements: %v", err) + } + if err := createMusclesTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы muscles: %v", err) + } + if err := createExercisesTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы exercises: %v", err) + } + if err := createEquipmentsTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы equipments: %v", err) + } + if err := createTrainsTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы trains: %v", err) + } + if err := createTrainsExercisesTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы trains_exercises: %v", err) + } + if err := createExercisesHelpMuscleTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы exercises_help_muscle: %v", err) + } + if err := createTrainHelpMuscleTable(db); err != nil { + log.Fatalf("Ошибка создания таблицы train_help_muscle: %v", err) + } + + // Создание всех связей между таблицами + if err := createExercisesRelations(db); err != nil { + log.Fatalf("Ошибка создания связей для таблицы exercises: %v", err) + } + if err := createTrainsExercisesRelations(db); err != nil { + log.Fatalf("Ошибка создания связей для таблицы trains_exercises: %v", err) + } + if err := createUsersAchievementsRelations(db); err != nil { + log.Fatalf("Ошибка создания связей для таблицы users_achievements: %v", err) + } + if err := createTrainsRelations(db); err != nil { + log.Fatalf("Ошибка создания связей для таблицы trains: %v", err) + } + if err := createExercisesHelpMuscleRelations(db); err != nil { + log.Fatalf("Ошибка создания связей для таблицы exercises_help_muscle: %v", err) + } + if err := createTrainHelpMuscleRelations(db); err != nil { + log.Fatalf("Ошибка создания связей для таблицы train_help_muscle: %v", err) + } + + fmt.Println("Все таблицы и связи успешно созданы!") +} + +// Таблица пользователей +func createUsersTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + login VARCHAR UNIQUE NOT NULL, + password VARCHAR NOT NULL, + nickname VARCHAR NOT NULL, + avatar VARCHAR, + advanced_version BOOLEAN DEFAULT false, + phone VARCHAR UNIQUE, + is_verified_phone BOOLEAN DEFAULT false, + email VARCHAR UNIQUE NOT NULL, + is_verified_mail BOOLEAN DEFAULT false, + age INT, + height INT, + weight INT, + sex VARCHAR, + day_streak INT DEFAULT 0, + is_train_today BOOLEAN DEFAULT false, + points INT DEFAULT 0, + created_at BIGINT, + update_at BIGINT + );` + return execQuery(db, query) +} + +// Таблица достижений +func createAchievementsTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS achievements ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + description VARCHAR UNIQUE NOT NULL, + image VARCHAR NOT NULL + );` + return execQuery(db, query) +} + +// Таблица достижений пользователей +func createUsersAchievementsTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS users_achievements ( + id SERIAL PRIMARY KEY, + user_id INT REFERENCES users(id), + achievement_id INT REFERENCES achievements(id) + );` + return execQuery(db, query) +} + +// Таблица мышц +func createMusclesTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS muscles ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + image VARCHAR UNIQUE NOT NULL + );` + return execQuery(db, query) +} + +// Таблица упражнений +func createExercisesTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS exercises ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + description VARCHAR UNIQUE NOT NULL, + image VARCHAR, + video_url VARCHAR, + equipment_id INT NOT NULL, + sets INT NOT NULL, + reps INT NOT NULL, + difficult INT NOT NULL, + lead_muscle_id INT NOT NULL + );` + return execQuery(db, query) +} + +// Таблица оборудования +func createEquipmentsTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS equipments ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + image VARCHAR UNIQUE NOT NULL + );` + return execQuery(db, query) +} + +// Таблица тренировок +func createTrainsTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS trains ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + description VARCHAR NOT NULL, + image VARCHAR, + video_url VARCHAR, + difficult INT, + duration_train INT, + lead_muscle_id INT NOT NULL + );` + return execQuery(db, query) +} + +// Таблица упражнений в тренировках +func createTrainsExercisesTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS trains_exercises ( + id SERIAL PRIMARY KEY, + train_id INT REFERENCES trains(id), + exercise_id INT REFERENCES exercises(id) + );` + return execQuery(db, query) +} + +// Таблица вспомогательных мышц упражнений +func createExercisesHelpMuscleTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS exercises_help_muscle ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id), + help_muscle_id INT REFERENCES muscles(id) + );` + return execQuery(db, query) +} + +// Таблица вспомогательных мышц тренировок +func createTrainHelpMuscleTable(db *sql.DB) error { + query := ` + CREATE TABLE IF NOT EXISTS train_help_muscle ( + id SERIAL PRIMARY KEY, + train_id INT REFERENCES trains(id), + help_muscle_id INT REFERENCES muscles(id) + );` + return execQuery(db, query) +} + +// Создание связей для таблицы exercises +func createExercisesRelations(db *sql.DB) error { + query := ` + ALTER TABLE exercises + ADD FOREIGN KEY (lead_muscle_id) REFERENCES muscles(id), + ADD FOREIGN KEY (equipment_id) REFERENCES equipments(id); + ` + return execQuery(db, query) +} + +// Создание связей для таблицы trains_exercises +func createTrainsExercisesRelations(db *sql.DB) error { + query := ` + ALTER TABLE trains_exercises + ADD FOREIGN KEY (train_id) REFERENCES trains(id), + ADD FOREIGN KEY (exercise_id) REFERENCES exercises(id); + ` + return execQuery(db, query) +} + +// Создание связей для таблицы users_achievements +func createUsersAchievementsRelations(db *sql.DB) error { + query := ` + ALTER TABLE users_achievements + ADD FOREIGN KEY (user_id) REFERENCES users(id), + ADD FOREIGN KEY (achievement_id) REFERENCES achievements(id); + ` + return execQuery(db, query) +} + +// Создание связей для таблицы trains +func createTrainsRelations(db *sql.DB) error { + query := ` + ALTER TABLE trains + ADD FOREIGN KEY (lead_muscle_id) REFERENCES muscles(id); + ` + return execQuery(db, query) +} + +// Создание связей для таблицы exercises_help_muscle +func createExercisesHelpMuscleRelations(db *sql.DB) error { + query := ` + ALTER TABLE exercises_help_muscle + ADD FOREIGN KEY (exercise_id) REFERENCES exercises(id), + ADD FOREIGN KEY (help_muscle_id) REFERENCES muscles(id); + ` + return execQuery(db, query) +} + +// Создание связей для таблицы train_help_muscle +func createTrainHelpMuscleRelations(db *sql.DB) error { + query := ` + ALTER TABLE train_help_muscle + ADD FOREIGN KEY (train_id) REFERENCES trains(id), + ADD FOREIGN KEY (help_muscle_id) REFERENCES muscles(id); + ` + return execQuery(db, query) +} diff --git a/internal/storage/storage.go b/internal/storage/storage.go new file mode 100644 index 0000000..192aa70 --- /dev/null +++ b/internal/storage/storage.go @@ -0,0 +1,38 @@ +package storage + +import ( + "database/sql" + "fmt" + "os" + + "github.com/joho/godotenv" + _ "github.com/lib/pq" +) + +func InitPostgres() *sql.DB { + err := godotenv.Load() + + if err != nil { + fmt.Println("Ошибка загрузки .env") + } + + host := os.Getenv("HOST") + port := os.Getenv("PORT") + username := os.Getenv("POSTGRES_USERNAME") + password := os.Getenv("POSTGRES_PASSWORD") + databaseName := os.Getenv("DBNAME") + + psqlInfo := fmt.Sprintf("host=%s port=%s user=%s "+ + "password=%s dbname=%s sslmode=disable", + host, port, username, password, databaseName) + + DB, err := sql.Open("postgres", psqlInfo) + if err != nil { + fmt.Println(err.Error()) + panic("База данных нихуя не работает") + } + + InitTables(DB) + + return DB +} From 47f90f6ef165a247a5198deaa7a819c412424cec Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Tue, 18 Mar 2025 17:15:55 +0400 Subject: [PATCH 02/26] feat(storage): remove pq, add pgx, add config --- .env.example | 4 +- cmd/base/main.go | 14 ++++- config/config.go | 19 ++++++ go.mod | 38 ++++-------- go.sum | 82 +++++-------------------- internal/storage/initTables.go | 106 +++++++++++++++++---------------- internal/storage/storage.go | 28 ++++----- 7 files changed, 123 insertions(+), 168 deletions(-) create mode 100644 config/config.go diff --git a/.env.example b/.env.example index 7ad4c37..df435bc 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,3 @@ HOST="localhost" PORT=5432 -POSTGRES_USERNAME="root" -POSTGRES_PASSWORD="123" -DBNAME="base" \ No newline at end of file +POSTGRES_DSN="postgres://root:123@localhost:5432/base?sslmode=disable" diff --git a/cmd/base/main.go b/cmd/base/main.go index 365800c..cd66217 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -1,11 +1,19 @@ package main -import "theAesthetics.ru/base/internal/storage" +import ( + "theAesthetics.ru/base/config" + "theAesthetics.ru/base/internal/storage" +) func main() { + // init config + cfg, err := config.NewConfig() + if err != nil { + panic(err.Error()) + } // init logger - // init db - DB := storage.InitPostgres() + // init pool + DB := storage.InitPostgres(cfg) _ = DB // run http server } diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..3ca3662 --- /dev/null +++ b/config/config.go @@ -0,0 +1,19 @@ +package config + +import "github.com/caarlos0/env" + +type Config struct { + Host string `env:"HOST" envDefault:"9090"` + Port string `env:"PORT" envDefault:"debug"` //info + POSTGRES_DSN string `env:"POSTGRES_DSN" envDefault:"postgresql://user3:password1@localhost:5433/pool1?sslmode=disable"` +} + +func NewConfig() (*Config, error) { + cfg := Config{} + + if err := env.Parse(&cfg); err != nil { + return nil, err + } + + return &cfg, nil +} diff --git a/go.mod b/go.mod index f922980..01ee3b7 100644 --- a/go.mod +++ b/go.mod @@ -3,33 +3,17 @@ module theAesthetics.ru/base go 1.23.2 require ( - github.com/bytedance/sonic v1.13.1 // indirect - github.com/bytedance/sonic/loader v0.2.4 // indirect - github.com/cloudwego/base64x v0.1.5 // indirect - github.com/cloudwego/iasm v0.2.0 // indirect - github.com/gabriel-vasile/mimetype v1.4.8 // indirect - github.com/gin-contrib/sse v1.0.0 // indirect - github.com/gin-gonic/gin v1.10.0 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.25.0 // indirect - github.com/goccy/go-json v0.10.5 // indirect - github.com/joho/godotenv v1.5.1 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/cpuid/v2 v2.2.10 // indirect - github.com/leodido/go-urn v1.4.0 // indirect - github.com/lib/pq v1.10.9 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/twitchyliquid64/golang-asm v0.15.1 // indirect - github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.15.0 // indirect + github.com/caarlos0/env v3.5.0+incompatible + github.com/jackc/pgx/v5 v5.7.2 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/stretchr/testify v1.10.0 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.37.0 // indirect - golang.org/x/sys v0.31.0 // indirect + golang.org/x/sync v0.12.0 // indirect golang.org/x/text v0.23.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index abb76fc..60af2e3 100644 --- a/go.sum +++ b/go.sum @@ -1,82 +1,32 @@ -github.com/bytedance/sonic v1.13.1 h1:Jyd5CIvdFnkOWuKXr+wm4Nyk2h0yAFsr8ucJgEasO3g= -github.com/bytedance/sonic v1.13.1/go.mod h1:o68xyaF9u2gvVBuGHPlUVCy+ZfmNNO5ETf1+KgkJhz4= -github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= -github.com/bytedance/sonic/loader v0.2.4 h1:ZWCw4stuXUsn1/+zQDqeE7JKP+QO47tz7QCNan80NzY= -github.com/bytedance/sonic/loader v0.2.4/go.mod h1:N8A3vUdtUebEY2/VQC0MyhYeKUFosQU6FxH2JmUe6VI= -github.com/cloudwego/base64x v0.1.5 h1:XPciSp1xaq2VCSt6lF0phncD4koWyULpl5bUxbfCyP4= -github.com/cloudwego/base64x v0.1.5/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= -github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= -github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/caarlos0/env v3.5.0+incompatible h1:Yy0UN8o9Wtr/jGHZDpCBLpNrzcFLLM2yixi/rBrKyJs= +github.com/caarlos0/env v3.5.0+incompatible/go.mod h1:tdCsowwCzMLdkqRYDlHpZCp2UooDD3MspDBjZ2AD02Y= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= -github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= -github.com/gin-contrib/sse v1.0.0 h1:y3bT1mUWUxDpW4JLQg/HnTqV4rozuW4tC9eFKTxYI9E= -github.com/gin-contrib/sse v1.0.0/go.mod h1:zNuFdwarAygJBht0NTKiSi3jRf6RbqeILZ9Sp6Slhe0= -github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= -github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= -github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= -github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.25.0 h1:5Dh7cjvzR7BRZadnsVOzPhWsrwUr0nmsZJxEAnFLNO8= -github.com/go-playground/validator/v10 v10.25.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= -github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= -github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= -github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE= -github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= -github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= -github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= -github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= -github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= -github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= -github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= -github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= -github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= -golang.org/x/arch v0.15.0 h1:QtOrQd0bTUnhNVNndMpLHNWrDmYzZ2KDqSrEymqInZw= -golang.org/x/arch v0.15.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5poolSLXVDPFyf8C91i36aY= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= -golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= -golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= -google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM= -google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/storage/initTables.go b/internal/storage/initTables.go index 3225105..ac47091 100644 --- a/internal/storage/initTables.go +++ b/internal/storage/initTables.go @@ -1,70 +1,72 @@ package storage import ( - "database/sql" + "context" "fmt" "log" + + "github.com/jackc/pgx/v5/pgxpool" ) // Выполнение SQL-запроса с проверкой ошибки -func execQuery(db *sql.DB, query string) error { - _, err := db.Exec(query) +func execQuery(pool *pgxpool.Pool, query string) error { + _, err := pool.Exec(context.Background(), query) if err != nil { return fmt.Errorf("ошибка выполнения запроса: %w", err) } return nil } -func InitTables(db *sql.DB) { +func InitTables(pool *pgxpool.Pool) { // Создание всех таблиц - if err := createUsersTable(db); err != nil { + if err := createUsersTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы users: %v", err) } - if err := createAchievementsTable(db); err != nil { + if err := createAchievementsTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы achievements: %v", err) } - if err := createUsersAchievementsTable(db); err != nil { + if err := createUsersAchievementsTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы users_achievements: %v", err) } - if err := createMusclesTable(db); err != nil { + if err := createMusclesTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы muscles: %v", err) } - if err := createExercisesTable(db); err != nil { + if err := createExercisesTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы exercises: %v", err) } - if err := createEquipmentsTable(db); err != nil { + if err := createEquipmentsTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы equipments: %v", err) } - if err := createTrainsTable(db); err != nil { + if err := createTrainsTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы trains: %v", err) } - if err := createTrainsExercisesTable(db); err != nil { + if err := createTrainsExercisesTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы trains_exercises: %v", err) } - if err := createExercisesHelpMuscleTable(db); err != nil { + if err := createExercisesHelpMuscleTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы exercises_help_muscle: %v", err) } - if err := createTrainHelpMuscleTable(db); err != nil { + if err := createTrainHelpMuscleTable(pool); err != nil { log.Fatalf("Ошибка создания таблицы train_help_muscle: %v", err) } // Создание всех связей между таблицами - if err := createExercisesRelations(db); err != nil { + if err := createExercisesRelations(pool); err != nil { log.Fatalf("Ошибка создания связей для таблицы exercises: %v", err) } - if err := createTrainsExercisesRelations(db); err != nil { + if err := createTrainsExercisesRelations(pool); err != nil { log.Fatalf("Ошибка создания связей для таблицы trains_exercises: %v", err) } - if err := createUsersAchievementsRelations(db); err != nil { + if err := createUsersAchievementsRelations(pool); err != nil { log.Fatalf("Ошибка создания связей для таблицы users_achievements: %v", err) } - if err := createTrainsRelations(db); err != nil { + if err := createTrainsRelations(pool); err != nil { log.Fatalf("Ошибка создания связей для таблицы trains: %v", err) } - if err := createExercisesHelpMuscleRelations(db); err != nil { + if err := createExercisesHelpMuscleRelations(pool); err != nil { log.Fatalf("Ошибка создания связей для таблицы exercises_help_muscle: %v", err) } - if err := createTrainHelpMuscleRelations(db); err != nil { + if err := createTrainHelpMuscleRelations(pool); err != nil { log.Fatalf("Ошибка создания связей для таблицы train_help_muscle: %v", err) } @@ -72,7 +74,7 @@ func InitTables(db *sql.DB) { } // Таблица пользователей -func createUsersTable(db *sql.DB) error { +func createUsersTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS users ( id SERIAL PRIMARY KEY, @@ -95,11 +97,11 @@ func createUsersTable(db *sql.DB) error { created_at BIGINT, update_at BIGINT );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица достижений -func createAchievementsTable(db *sql.DB) error { +func createAchievementsTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS achievements ( id SERIAL PRIMARY KEY, @@ -107,33 +109,33 @@ func createAchievementsTable(db *sql.DB) error { description VARCHAR UNIQUE NOT NULL, image VARCHAR NOT NULL );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица достижений пользователей -func createUsersAchievementsTable(db *sql.DB) error { +func createUsersAchievementsTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS users_achievements ( id SERIAL PRIMARY KEY, user_id INT REFERENCES users(id), achievement_id INT REFERENCES achievements(id) );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица мышц -func createMusclesTable(db *sql.DB) error { +func createMusclesTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS muscles ( id SERIAL PRIMARY KEY, title VARCHAR UNIQUE NOT NULL, image VARCHAR UNIQUE NOT NULL );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица упражнений -func createExercisesTable(db *sql.DB) error { +func createExercisesTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS exercises ( id SERIAL PRIMARY KEY, @@ -147,22 +149,22 @@ func createExercisesTable(db *sql.DB) error { difficult INT NOT NULL, lead_muscle_id INT NOT NULL );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица оборудования -func createEquipmentsTable(db *sql.DB) error { +func createEquipmentsTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS equipments ( id SERIAL PRIMARY KEY, title VARCHAR UNIQUE NOT NULL, image VARCHAR UNIQUE NOT NULL );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица тренировок -func createTrainsTable(db *sql.DB) error { +func createTrainsTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS trains ( id SERIAL PRIMARY KEY, @@ -174,97 +176,97 @@ func createTrainsTable(db *sql.DB) error { duration_train INT, lead_muscle_id INT NOT NULL );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица упражнений в тренировках -func createTrainsExercisesTable(db *sql.DB) error { +func createTrainsExercisesTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS trains_exercises ( id SERIAL PRIMARY KEY, train_id INT REFERENCES trains(id), exercise_id INT REFERENCES exercises(id) );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица вспомогательных мышц упражнений -func createExercisesHelpMuscleTable(db *sql.DB) error { +func createExercisesHelpMuscleTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS exercises_help_muscle ( id SERIAL PRIMARY KEY, exercise_id INT REFERENCES exercises(id), help_muscle_id INT REFERENCES muscles(id) );` - return execQuery(db, query) + return execQuery(pool, query) } // Таблица вспомогательных мышц тренировок -func createTrainHelpMuscleTable(db *sql.DB) error { +func createTrainHelpMuscleTable(pool *pgxpool.Pool) error { query := ` CREATE TABLE IF NOT EXISTS train_help_muscle ( id SERIAL PRIMARY KEY, train_id INT REFERENCES trains(id), help_muscle_id INT REFERENCES muscles(id) );` - return execQuery(db, query) + return execQuery(pool, query) } // Создание связей для таблицы exercises -func createExercisesRelations(db *sql.DB) error { +func createExercisesRelations(pool *pgxpool.Pool) error { query := ` ALTER TABLE exercises ADD FOREIGN KEY (lead_muscle_id) REFERENCES muscles(id), ADD FOREIGN KEY (equipment_id) REFERENCES equipments(id); ` - return execQuery(db, query) + return execQuery(pool, query) } // Создание связей для таблицы trains_exercises -func createTrainsExercisesRelations(db *sql.DB) error { +func createTrainsExercisesRelations(pool *pgxpool.Pool) error { query := ` ALTER TABLE trains_exercises ADD FOREIGN KEY (train_id) REFERENCES trains(id), ADD FOREIGN KEY (exercise_id) REFERENCES exercises(id); ` - return execQuery(db, query) + return execQuery(pool, query) } // Создание связей для таблицы users_achievements -func createUsersAchievementsRelations(db *sql.DB) error { +func createUsersAchievementsRelations(pool *pgxpool.Pool) error { query := ` ALTER TABLE users_achievements ADD FOREIGN KEY (user_id) REFERENCES users(id), ADD FOREIGN KEY (achievement_id) REFERENCES achievements(id); ` - return execQuery(db, query) + return execQuery(pool, query) } // Создание связей для таблицы trains -func createTrainsRelations(db *sql.DB) error { +func createTrainsRelations(pool *pgxpool.Pool) error { query := ` ALTER TABLE trains ADD FOREIGN KEY (lead_muscle_id) REFERENCES muscles(id); ` - return execQuery(db, query) + return execQuery(pool, query) } // Создание связей для таблицы exercises_help_muscle -func createExercisesHelpMuscleRelations(db *sql.DB) error { +func createExercisesHelpMuscleRelations(pool *pgxpool.Pool) error { query := ` ALTER TABLE exercises_help_muscle ADD FOREIGN KEY (exercise_id) REFERENCES exercises(id), ADD FOREIGN KEY (help_muscle_id) REFERENCES muscles(id); ` - return execQuery(db, query) + return execQuery(pool, query) } // Создание связей для таблицы train_help_muscle -func createTrainHelpMuscleRelations(db *sql.DB) error { +func createTrainHelpMuscleRelations(pool *pgxpool.Pool) error { query := ` ALTER TABLE train_help_muscle ADD FOREIGN KEY (train_id) REFERENCES trains(id), ADD FOREIGN KEY (help_muscle_id) REFERENCES muscles(id); ` - return execQuery(db, query) + return execQuery(pool, query) } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 192aa70..5734b13 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -1,38 +1,32 @@ package storage import ( - "database/sql" + "context" "fmt" - "os" + "github.com/jackc/pgx/v5/pgxpool" "github.com/joho/godotenv" - _ "github.com/lib/pq" + "theAesthetics.ru/base/config" ) -func InitPostgres() *sql.DB { +func InitPostgres(cfg *config.Config) error { + ctx := context.Background() err := godotenv.Load() if err != nil { fmt.Println("Ошибка загрузки .env") } - host := os.Getenv("HOST") - port := os.Getenv("PORT") - username := os.Getenv("POSTGRES_USERNAME") - password := os.Getenv("POSTGRES_PASSWORD") - databaseName := os.Getenv("DBNAME") + pool, err := pgxpool.New(ctx, cfg.POSTGRES_DSN) - psqlInfo := fmt.Sprintf("host=%s port=%s user=%s "+ - "password=%s dbname=%s sslmode=disable", - host, port, username, password, databaseName) + err = pool.Ping(ctx) - DB, err := sql.Open("postgres", psqlInfo) if err != nil { - fmt.Println(err.Error()) - panic("База данных нихуя не работает") + return fmt.Errorf("ошибка подключения к базе данных: %w", err) } - InitTables(DB) + defer pool.Close() + InitTables(pool) - return DB + return nil } From c425ec8f9bd74edd990161daae51908bbfa1d630 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Tue, 18 Mar 2025 17:16:50 +0400 Subject: [PATCH 03/26] feat(storage): add err handler --- internal/storage/storage.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 5734b13..74de406 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -19,6 +19,10 @@ func InitPostgres(cfg *config.Config) error { pool, err := pgxpool.New(ctx, cfg.POSTGRES_DSN) + if err != nil { + return fmt.Errorf("ошибка подключения к базе данных: %w", err) + } + err = pool.Ping(ctx) if err != nil { From e6b3648b2cce10c6e470aa488ba5fa92a1af5e88 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Tue, 18 Mar 2025 17:19:38 +0400 Subject: [PATCH 04/26] feat(storage): refactoring --- cmd/base/main.go | 4 ++-- internal/storage/storage.go | 15 ++++++++------- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/cmd/base/main.go b/cmd/base/main.go index cd66217..c67a6a9 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -13,7 +13,7 @@ func main() { } // init logger // init pool - DB := storage.InitPostgres(cfg) - _ = DB + pool := storage.InitPostgres(cfg) + _ = pool // run http server } diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 74de406..036eed5 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -2,35 +2,36 @@ package storage import ( "context" - "fmt" + "log" "github.com/jackc/pgx/v5/pgxpool" "github.com/joho/godotenv" "theAesthetics.ru/base/config" ) -func InitPostgres(cfg *config.Config) error { +func InitPostgres(cfg *config.Config) *pgxpool.Pool { ctx := context.Background() err := godotenv.Load() if err != nil { - fmt.Println("Ошибка загрузки .env") + log.Println("Ошибка загрузки .env") } pool, err := pgxpool.New(ctx, cfg.POSTGRES_DSN) if err != nil { - return fmt.Errorf("ошибка подключения к базе данных: %w", err) + log.Fatalf("ошибка подключения к базе данных: %w", err) + return nil } err = pool.Ping(ctx) if err != nil { - return fmt.Errorf("ошибка подключения к базе данных: %w", err) + log.Fatalf("ошибка подключения к базе данных: %w", err) + return nil } defer pool.Close() InitTables(pool) - - return nil + return pool } From 6a61901c3f75bd58204479595ba2e80f713ff090 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Wed, 19 Mar 2025 06:07:55 +0400 Subject: [PATCH 05/26] feat(storage): init echo, init migrations --- .gitignore | 7 +- cmd/base/main.go | 8 +- config/config.go | 11 +- go.mod | 16 +- go.sum | 34 ++- internal/storage/initTables.go | 272 ------------------ internal/storage/storage.go | 13 +- .../migrations/20250319015139_init_schema.sql | 150 ++++++++++ 8 files changed, 222 insertions(+), 289 deletions(-) delete mode 100644 internal/storage/initTables.go create mode 100644 storage/migrations/20250319015139_init_schema.sql diff --git a/.gitignore b/.gitignore index 2eea525..cde8e1b 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,6 @@ -.env \ No newline at end of file +.env +# Flyway ignores +*.user.toml +*.artifact +report.html +report.json \ No newline at end of file diff --git a/cmd/base/main.go b/cmd/base/main.go index c67a6a9..abd3aec 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -1,6 +1,7 @@ package main import ( + "github.com/labstack/echo/v4" "theAesthetics.ru/base/config" "theAesthetics.ru/base/internal/storage" ) @@ -15,5 +16,10 @@ func main() { // init pool pool := storage.InitPostgres(cfg) _ = pool - // run http server + // run echo server + e := echo.New() + e.GET("/", func(c echo.Context) error { + return c.String(200, "Hello, World!") + }) + e.Logger.Fatal(e.Start(":1323")) } diff --git a/config/config.go b/config/config.go index 3ca3662..f154395 100644 --- a/config/config.go +++ b/config/config.go @@ -1,6 +1,11 @@ package config -import "github.com/caarlos0/env" +import ( + "log" + + "github.com/caarlos0/env" + "github.com/joho/godotenv" +) type Config struct { Host string `env:"HOST" envDefault:"9090"` @@ -11,6 +16,10 @@ type Config struct { func NewConfig() (*Config, error) { cfg := Config{} + if err := godotenv.Load(".env"); err != nil { + log.Fatalf("Ошибка загрузки .env файла: %v", err) + } + if err := env.Parse(&cfg); err != nil { return nil, err } diff --git a/go.mod b/go.mod index 01ee3b7..8751cd0 100644 --- a/go.mod +++ b/go.mod @@ -5,15 +5,29 @@ go 1.23.2 require ( github.com/caarlos0/env v3.5.0+incompatible github.com/jackc/pgx/v5 v5.7.2 - github.com/joho/godotenv v1.5.1 ) require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/labstack/echo/v4 v4.13.3 // indirect + github.com/labstack/gommon v0.4.2 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mfridman/interpolate v0.0.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/pressly/goose v2.7.0+incompatible // indirect + github.com/pressly/goose/v3 v3.24.1 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect github.com/stretchr/testify v1.10.0 // indirect + github.com/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect + go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.36.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.12.0 // indirect + golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect ) diff --git a/go.sum b/go.sum index 60af2e3..4feee97 100644 --- a/go.sum +++ b/go.sum @@ -13,17 +13,47 @@ github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/labstack/echo/v4 v4.13.3 h1:pwhpCPrTl5qry5HRdM5FwdXnhXSLSY+WE+YQSeCaafY= +github.com/labstack/echo/v4 v4.13.3/go.mod h1:o90YNEeQWjDozo584l7AwhJMHN0bOC4tAfg+Xox9q5g= +github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0= +github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pressly/goose v2.7.0+incompatible h1:PWejVEv07LCerQEzMMeAtjuyCKbyprZ/LBa6K5P0OCQ= +github.com/pressly/goose v2.7.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8= +github.com/pressly/goose/v3 v3.24.1 h1:bZmxRco2uy5uu5Ng1MMVEfYsFlrMJI+e/VMXHQ3C4LY= +github.com/pressly/goose/v3 v3.24.1/go.mod h1:rEWreU9uVtt0DHCyLzF9gRcWiiTF/V+528DV+4DORug= +github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= +github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5poolSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= +github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/internal/storage/initTables.go b/internal/storage/initTables.go deleted file mode 100644 index ac47091..0000000 --- a/internal/storage/initTables.go +++ /dev/null @@ -1,272 +0,0 @@ -package storage - -import ( - "context" - "fmt" - "log" - - "github.com/jackc/pgx/v5/pgxpool" -) - -// Выполнение SQL-запроса с проверкой ошибки -func execQuery(pool *pgxpool.Pool, query string) error { - _, err := pool.Exec(context.Background(), query) - if err != nil { - return fmt.Errorf("ошибка выполнения запроса: %w", err) - } - return nil -} - -func InitTables(pool *pgxpool.Pool) { - // Создание всех таблиц - if err := createUsersTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы users: %v", err) - } - if err := createAchievementsTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы achievements: %v", err) - } - if err := createUsersAchievementsTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы users_achievements: %v", err) - } - if err := createMusclesTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы muscles: %v", err) - } - if err := createExercisesTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы exercises: %v", err) - } - if err := createEquipmentsTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы equipments: %v", err) - } - if err := createTrainsTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы trains: %v", err) - } - if err := createTrainsExercisesTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы trains_exercises: %v", err) - } - if err := createExercisesHelpMuscleTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы exercises_help_muscle: %v", err) - } - if err := createTrainHelpMuscleTable(pool); err != nil { - log.Fatalf("Ошибка создания таблицы train_help_muscle: %v", err) - } - - // Создание всех связей между таблицами - if err := createExercisesRelations(pool); err != nil { - log.Fatalf("Ошибка создания связей для таблицы exercises: %v", err) - } - if err := createTrainsExercisesRelations(pool); err != nil { - log.Fatalf("Ошибка создания связей для таблицы trains_exercises: %v", err) - } - if err := createUsersAchievementsRelations(pool); err != nil { - log.Fatalf("Ошибка создания связей для таблицы users_achievements: %v", err) - } - if err := createTrainsRelations(pool); err != nil { - log.Fatalf("Ошибка создания связей для таблицы trains: %v", err) - } - if err := createExercisesHelpMuscleRelations(pool); err != nil { - log.Fatalf("Ошибка создания связей для таблицы exercises_help_muscle: %v", err) - } - if err := createTrainHelpMuscleRelations(pool); err != nil { - log.Fatalf("Ошибка создания связей для таблицы train_help_muscle: %v", err) - } - - fmt.Println("Все таблицы и связи успешно созданы!") -} - -// Таблица пользователей -func createUsersTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS users ( - id SERIAL PRIMARY KEY, - login VARCHAR UNIQUE NOT NULL, - password VARCHAR NOT NULL, - nickname VARCHAR NOT NULL, - avatar VARCHAR, - advanced_version BOOLEAN DEFAULT false, - phone VARCHAR UNIQUE, - is_verified_phone BOOLEAN DEFAULT false, - email VARCHAR UNIQUE NOT NULL, - is_verified_mail BOOLEAN DEFAULT false, - age INT, - height INT, - weight INT, - sex VARCHAR, - day_streak INT DEFAULT 0, - is_train_today BOOLEAN DEFAULT false, - points INT DEFAULT 0, - created_at BIGINT, - update_at BIGINT - );` - return execQuery(pool, query) -} - -// Таблица достижений -func createAchievementsTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS achievements ( - id SERIAL PRIMARY KEY, - title VARCHAR UNIQUE NOT NULL, - description VARCHAR UNIQUE NOT NULL, - image VARCHAR NOT NULL - );` - return execQuery(pool, query) -} - -// Таблица достижений пользователей -func createUsersAchievementsTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS users_achievements ( - id SERIAL PRIMARY KEY, - user_id INT REFERENCES users(id), - achievement_id INT REFERENCES achievements(id) - );` - return execQuery(pool, query) -} - -// Таблица мышц -func createMusclesTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS muscles ( - id SERIAL PRIMARY KEY, - title VARCHAR UNIQUE NOT NULL, - image VARCHAR UNIQUE NOT NULL - );` - return execQuery(pool, query) -} - -// Таблица упражнений -func createExercisesTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS exercises ( - id SERIAL PRIMARY KEY, - title VARCHAR UNIQUE NOT NULL, - description VARCHAR UNIQUE NOT NULL, - image VARCHAR, - video_url VARCHAR, - equipment_id INT NOT NULL, - sets INT NOT NULL, - reps INT NOT NULL, - difficult INT NOT NULL, - lead_muscle_id INT NOT NULL - );` - return execQuery(pool, query) -} - -// Таблица оборудования -func createEquipmentsTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS equipments ( - id SERIAL PRIMARY KEY, - title VARCHAR UNIQUE NOT NULL, - image VARCHAR UNIQUE NOT NULL - );` - return execQuery(pool, query) -} - -// Таблица тренировок -func createTrainsTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS trains ( - id SERIAL PRIMARY KEY, - title VARCHAR UNIQUE NOT NULL, - description VARCHAR NOT NULL, - image VARCHAR, - video_url VARCHAR, - difficult INT, - duration_train INT, - lead_muscle_id INT NOT NULL - );` - return execQuery(pool, query) -} - -// Таблица упражнений в тренировках -func createTrainsExercisesTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS trains_exercises ( - id SERIAL PRIMARY KEY, - train_id INT REFERENCES trains(id), - exercise_id INT REFERENCES exercises(id) - );` - return execQuery(pool, query) -} - -// Таблица вспомогательных мышц упражнений -func createExercisesHelpMuscleTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS exercises_help_muscle ( - id SERIAL PRIMARY KEY, - exercise_id INT REFERENCES exercises(id), - help_muscle_id INT REFERENCES muscles(id) - );` - return execQuery(pool, query) -} - -// Таблица вспомогательных мышц тренировок -func createTrainHelpMuscleTable(pool *pgxpool.Pool) error { - query := ` - CREATE TABLE IF NOT EXISTS train_help_muscle ( - id SERIAL PRIMARY KEY, - train_id INT REFERENCES trains(id), - help_muscle_id INT REFERENCES muscles(id) - );` - return execQuery(pool, query) -} - -// Создание связей для таблицы exercises -func createExercisesRelations(pool *pgxpool.Pool) error { - query := ` - ALTER TABLE exercises - ADD FOREIGN KEY (lead_muscle_id) REFERENCES muscles(id), - ADD FOREIGN KEY (equipment_id) REFERENCES equipments(id); - ` - return execQuery(pool, query) -} - -// Создание связей для таблицы trains_exercises -func createTrainsExercisesRelations(pool *pgxpool.Pool) error { - query := ` - ALTER TABLE trains_exercises - ADD FOREIGN KEY (train_id) REFERENCES trains(id), - ADD FOREIGN KEY (exercise_id) REFERENCES exercises(id); - ` - return execQuery(pool, query) -} - -// Создание связей для таблицы users_achievements -func createUsersAchievementsRelations(pool *pgxpool.Pool) error { - query := ` - ALTER TABLE users_achievements - ADD FOREIGN KEY (user_id) REFERENCES users(id), - ADD FOREIGN KEY (achievement_id) REFERENCES achievements(id); - ` - return execQuery(pool, query) -} - -// Создание связей для таблицы trains -func createTrainsRelations(pool *pgxpool.Pool) error { - query := ` - ALTER TABLE trains - ADD FOREIGN KEY (lead_muscle_id) REFERENCES muscles(id); - ` - return execQuery(pool, query) -} - -// Создание связей для таблицы exercises_help_muscle -func createExercisesHelpMuscleRelations(pool *pgxpool.Pool) error { - query := ` - ALTER TABLE exercises_help_muscle - ADD FOREIGN KEY (exercise_id) REFERENCES exercises(id), - ADD FOREIGN KEY (help_muscle_id) REFERENCES muscles(id); - ` - return execQuery(pool, query) -} - -// Создание связей для таблицы train_help_muscle -func createTrainHelpMuscleRelations(pool *pgxpool.Pool) error { - query := ` - ALTER TABLE train_help_muscle - ADD FOREIGN KEY (train_id) REFERENCES trains(id), - ADD FOREIGN KEY (help_muscle_id) REFERENCES muscles(id); - ` - return execQuery(pool, query) -} diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 036eed5..4b4b1f2 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -5,33 +5,24 @@ import ( "log" "github.com/jackc/pgx/v5/pgxpool" - "github.com/joho/godotenv" "theAesthetics.ru/base/config" ) func InitPostgres(cfg *config.Config) *pgxpool.Pool { ctx := context.Background() - err := godotenv.Load() - - if err != nil { - log.Println("Ошибка загрузки .env") - } - pool, err := pgxpool.New(ctx, cfg.POSTGRES_DSN) if err != nil { - log.Fatalf("ошибка подключения к базе данных: %w", err) + log.Fatalf("ошибка подключения к базе данных: %s", err.Error()) return nil } err = pool.Ping(ctx) if err != nil { - log.Fatalf("ошибка подключения к базе данных: %w", err) + log.Fatalf("ошибка подключения к базе данных: %s", err.Error()) return nil } - defer pool.Close() - InitTables(pool) return pool } diff --git a/storage/migrations/20250319015139_init_schema.sql b/storage/migrations/20250319015139_init_schema.sql new file mode 100644 index 0000000..561985b --- /dev/null +++ b/storage/migrations/20250319015139_init_schema.sql @@ -0,0 +1,150 @@ +-- +goose Up +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS users ( + id SERIAL PRIMARY KEY, + login VARCHAR UNIQUE NOT NULL, + password VARCHAR NOT NULL, + nickname VARCHAR NOT NULL, + avatar VARCHAR, + advanced_version BOOLEAN DEFAULT false, + phone VARCHAR UNIQUE, + is_verified_phone BOOLEAN DEFAULT false, + email VARCHAR UNIQUE NOT NULL, + is_verified_mail BOOLEAN DEFAULT false, + age INT, + height INT, + weight INT, + sex VARCHAR, + day_streak INT DEFAULT 0, + is_train_today BOOLEAN DEFAULT false, + points INT DEFAULT 0, + created_at BIGINT, + update_at BIGINT +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS achievements ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + description VARCHAR UNIQUE NOT NULL, + image VARCHAR NOT NULL +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS users_achievements ( + id SERIAL PRIMARY KEY, + user_id INT REFERENCES users(id), + achievement_id INT REFERENCES achievements(id) +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS muscles ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + image VARCHAR UNIQUE NOT NULL +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS exercises ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + description VARCHAR UNIQUE NOT NULL, + image VARCHAR, + video_url VARCHAR, + equipment_id INT NOT NULL, + sets INT NOT NULL, + reps INT NOT NULL, + difficult INT NOT NULL, + lead_muscle_id INT NOT NULL +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS equipments ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + image VARCHAR UNIQUE NOT NULL +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS trains ( + id SERIAL PRIMARY KEY, + title VARCHAR UNIQUE NOT NULL, + description VARCHAR NOT NULL, + image VARCHAR, + video_url VARCHAR, + difficult INT, + duration_train INT, + lead_muscle_id INT NOT NULL +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS trains_exercises ( + id SERIAL PRIMARY KEY, + train_id INT REFERENCES trains(id), + exercise_id INT REFERENCES exercises(id) +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS exercises_help_muscle ( + id SERIAL PRIMARY KEY, + exercise_id INT REFERENCES exercises(id), + help_muscle_id INT REFERENCES muscles(id) +); +-- +goose StatementEnd + +-- +goose StatementBegin +CREATE TABLE IF NOT EXISTS train_help_muscle ( + id SERIAL PRIMARY KEY, + train_id INT REFERENCES trains(id), + help_muscle_id INT REFERENCES muscles(id) +); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE IF EXISTS users; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS achievements; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS users_achievements; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS muscles; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS exercises; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS equipments; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS trains; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS trains_exercises; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS exercises_help_muscle; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS train_help_muscle; +-- +goose StatementEnd From be055921091fde7d1a48239857fdb318a77a7d3b Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Wed, 19 Mar 2025 06:23:22 +0400 Subject: [PATCH 06/26] feat(storage): init logger --- cmd/base/main.go | 6 ++++++ config/config.go | 1 + go.mod | 1 + go.sum | 3 +++ internal/logger/logger.go | 22 ++++++++++++++++++++++ 5 files changed, 33 insertions(+) create mode 100644 internal/logger/logger.go diff --git a/cmd/base/main.go b/cmd/base/main.go index abd3aec..dd37a1e 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -2,7 +2,10 @@ package main import ( "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" + "theAesthetics.ru/base/config" + "theAesthetics.ru/base/internal/logger" "theAesthetics.ru/base/internal/storage" ) @@ -13,9 +16,12 @@ func main() { panic(err.Error()) } // init logger + logger.InitLogger(cfg.LogLevel) + logrus.Info("test log") // init pool pool := storage.InitPostgres(cfg) _ = pool + // run echo server e := echo.New() e.GET("/", func(c echo.Context) error { diff --git a/config/config.go b/config/config.go index f154395..77a4217 100644 --- a/config/config.go +++ b/config/config.go @@ -11,6 +11,7 @@ type Config struct { Host string `env:"HOST" envDefault:"9090"` Port string `env:"PORT" envDefault:"debug"` //info POSTGRES_DSN string `env:"POSTGRES_DSN" envDefault:"postgresql://user3:password1@localhost:5433/pool1?sslmode=disable"` + LogLevel string `env:"LOG_LEVEL" envDefault:debug` } func NewConfig() (*Config, error) { diff --git a/go.mod b/go.mod index 8751cd0..cd7fa10 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/pressly/goose v2.7.0+incompatible // indirect github.com/pressly/goose/v3 v3.24.1 // indirect github.com/sethvargo/go-retry v0.3.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect github.com/stretchr/testify v1.10.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect diff --git a/go.sum b/go.sum index 4feee97..75a59e9 100644 --- a/go.sum +++ b/go.sum @@ -34,6 +34,8 @@ github.com/pressly/goose/v3 v3.24.1 h1:bZmxRco2uy5uu5Ng1MMVEfYsFlrMJI+e/VMXHQ3C4 github.com/pressly/goose/v3 v3.24.1/go.mod h1:rEWreU9uVtt0DHCyLzF9gRcWiiTF/V+528DV+4DORug= github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= @@ -50,6 +52,7 @@ golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..c1c5a18 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,22 @@ +package logger + +import ( + "fmt" + "runtime" + + log "github.com/sirupsen/logrus" +) + +func InitLogger(logLevel string) { + level, err := log.ParseLevel(logLevel) + if err != nil { + log.Fatalf("Ошибка при парсинге уровня логирования: %v", err) + } + log.SetLevel(level) + log.SetReportCaller(true) + log.SetFormatter(&log.JSONFormatter{ + CallerPrettyfier: func(frame *runtime.Frame) (function string, file string) { + return fmt.Sprintf("%s:%d", frame.Function, frame.Line), "" + }, + }) +} From 4870f89a23c17ddbcf02a14d75d7f56dcb7d01c0 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Wed, 19 Mar 2025 16:46:01 +0400 Subject: [PATCH 07/26] feat(storage): add handlers --- .env.example | 4 ++++ cmd/base/main.go | 16 ++++++++++++---- go.mod | 16 +++++----------- go.sum | 15 +++------------ internal/handlers/achievement_handler.go | 1 + internal/handlers/equipment_handler.go | 1 + internal/handlers/exercise_handler.go | 1 + internal/handlers/muscle_handler.go | 1 + internal/handlers/train_handler.go | 1 + internal/handlers/user_handler.go | 1 + internal/storage/storage.go | 2 +- 11 files changed, 31 insertions(+), 28 deletions(-) create mode 100644 internal/handlers/achievement_handler.go create mode 100644 internal/handlers/equipment_handler.go create mode 100644 internal/handlers/exercise_handler.go create mode 100644 internal/handlers/muscle_handler.go create mode 100644 internal/handlers/train_handler.go create mode 100644 internal/handlers/user_handler.go diff --git a/.env.example b/.env.example index df435bc..452da38 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,7 @@ HOST="localhost" PORT=5432 POSTGRES_DSN="postgres://root:123@localhost:5432/base?sslmode=disable" +LOG_LEVEL="debug" +GOOSE_DRIVER=postgres +GOOSE_DBSTRING=postgres://root:123@localhost:5432/base +GOOSE_MIGRATION_DIR=./storage/migrations \ No newline at end of file diff --git a/cmd/base/main.go b/cmd/base/main.go index dd37a1e..5120532 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -2,11 +2,12 @@ package main import ( "github.com/labstack/echo/v4" + "github.com/labstack/echo/v4/middleware" "github.com/sirupsen/logrus" - "theAesthetics.ru/base/config" - "theAesthetics.ru/base/internal/logger" - "theAesthetics.ru/base/internal/storage" + "theaesthetics.ru/base/config" + "theaesthetics.ru/base/internal/logger" + "theaesthetics.ru/base/internal/storage" ) func main() { @@ -15,16 +16,23 @@ func main() { if err != nil { panic(err.Error()) } + // init logger logger.InitLogger(cfg.LogLevel) logrus.Info("test log") + // init pool pool := storage.InitPostgres(cfg) _ = pool // run echo server e := echo.New() - e.GET("/", func(c echo.Context) error { + e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ + Format: "method=${method}, uri=${uri}, status=${status}\n", + })) + + api := e.Group("/api/v1") + api.GET("/", func(c echo.Context) error { return c.String(200, "Hello, World!") }) e.Logger.Fatal(e.Start(":1323")) diff --git a/go.mod b/go.mod index cd7fa10..7827406 100644 --- a/go.mod +++ b/go.mod @@ -1,34 +1,28 @@ -module theAesthetics.ru/base +module theaesthetics.ru/base go 1.23.2 require ( github.com/caarlos0/env v3.5.0+incompatible github.com/jackc/pgx/v5 v5.7.2 + github.com/joho/godotenv v1.5.1 + github.com/labstack/echo/v4 v4.13.3 + github.com/sirupsen/logrus v1.9.3 ) require ( github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect - github.com/joho/godotenv v1.5.1 // indirect - github.com/labstack/echo/v4 v4.13.3 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mfridman/interpolate v0.0.2 // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pressly/goose v2.7.0+incompatible // indirect - github.com/pressly/goose/v3 v3.24.1 // indirect - github.com/sethvargo/go-retry v0.3.0 // indirect - github.com/sirupsen/logrus v1.9.3 // indirect - github.com/stretchr/testify v1.10.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.36.0 // indirect golang.org/x/net v0.33.0 // indirect golang.org/x/sync v0.12.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.23.0 // indirect + golang.org/x/time v0.8.0 // indirect ) diff --git a/go.sum b/go.sum index 75a59e9..e49199b 100644 --- a/go.sum +++ b/go.sum @@ -22,30 +22,19 @@ github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovk github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= -github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pressly/goose v2.7.0+incompatible h1:PWejVEv07LCerQEzMMeAtjuyCKbyprZ/LBa6K5P0OCQ= -github.com/pressly/goose v2.7.0+incompatible/go.mod h1:m+QHWCqxR3k8D9l7qfzuC/djtlfzxr34mozWDYEu1z8= -github.com/pressly/goose/v3 v3.24.1 h1:bZmxRco2uy5uu5Ng1MMVEfYsFlrMJI+e/VMXHQ3C4LY= -github.com/pressly/goose/v3 v3.24.1/go.mod h1:rEWreU9uVtt0DHCyLzF9gRcWiiTF/V+528DV+4DORug= -github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah2SE= -github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= @@ -59,6 +48,8 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg= +golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= diff --git a/internal/handlers/achievement_handler.go b/internal/handlers/achievement_handler.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/internal/handlers/achievement_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/equipment_handler.go b/internal/handlers/equipment_handler.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/internal/handlers/equipment_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/exercise_handler.go b/internal/handlers/exercise_handler.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/internal/handlers/exercise_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/muscle_handler.go b/internal/handlers/muscle_handler.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/internal/handlers/muscle_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/train_handler.go b/internal/handlers/train_handler.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/internal/handlers/train_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/handlers/user_handler.go b/internal/handlers/user_handler.go new file mode 100644 index 0000000..5ac8282 --- /dev/null +++ b/internal/handlers/user_handler.go @@ -0,0 +1 @@ +package handlers diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 4b4b1f2..58a5e49 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -5,7 +5,7 @@ import ( "log" "github.com/jackc/pgx/v5/pgxpool" - "theAesthetics.ru/base/config" + "theaesthetics.ru/base/config" ) func InitPostgres(cfg *config.Config) *pgxpool.Pool { From bda5821d6c552a97c489e45b256457258cc70423 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Wed, 19 Mar 2025 16:48:40 +0400 Subject: [PATCH 08/26] feat(storage): add models & repos --- internal/models/achievement.go | 1 + internal/models/equipment.go | 1 + internal/models/exercise.go | 1 + internal/models/muscle.go | 1 + internal/models/train.go | 1 + internal/models/user.go | 1 + internal/repository/achievement_repository.go | 1 + internal/repository/equipment_repository.go | 1 + internal/repository/exercise_repository.go | 1 + internal/repository/muscle_repository.go | 1 + internal/repository/train_repository.go | 1 + internal/repository/user_repository.go | 1 + 12 files changed, 12 insertions(+) create mode 100644 internal/models/achievement.go create mode 100644 internal/models/equipment.go create mode 100644 internal/models/exercise.go create mode 100644 internal/models/muscle.go create mode 100644 internal/models/train.go create mode 100644 internal/models/user.go create mode 100644 internal/repository/achievement_repository.go create mode 100644 internal/repository/equipment_repository.go create mode 100644 internal/repository/exercise_repository.go create mode 100644 internal/repository/muscle_repository.go create mode 100644 internal/repository/train_repository.go create mode 100644 internal/repository/user_repository.go diff --git a/internal/models/achievement.go b/internal/models/achievement.go new file mode 100644 index 0000000..2640e7f --- /dev/null +++ b/internal/models/achievement.go @@ -0,0 +1 @@ +package models diff --git a/internal/models/equipment.go b/internal/models/equipment.go new file mode 100644 index 0000000..2640e7f --- /dev/null +++ b/internal/models/equipment.go @@ -0,0 +1 @@ +package models diff --git a/internal/models/exercise.go b/internal/models/exercise.go new file mode 100644 index 0000000..2640e7f --- /dev/null +++ b/internal/models/exercise.go @@ -0,0 +1 @@ +package models diff --git a/internal/models/muscle.go b/internal/models/muscle.go new file mode 100644 index 0000000..2640e7f --- /dev/null +++ b/internal/models/muscle.go @@ -0,0 +1 @@ +package models diff --git a/internal/models/train.go b/internal/models/train.go new file mode 100644 index 0000000..2640e7f --- /dev/null +++ b/internal/models/train.go @@ -0,0 +1 @@ +package models diff --git a/internal/models/user.go b/internal/models/user.go new file mode 100644 index 0000000..2640e7f --- /dev/null +++ b/internal/models/user.go @@ -0,0 +1 @@ +package models diff --git a/internal/repository/achievement_repository.go b/internal/repository/achievement_repository.go new file mode 100644 index 0000000..50a4378 --- /dev/null +++ b/internal/repository/achievement_repository.go @@ -0,0 +1 @@ +package repository diff --git a/internal/repository/equipment_repository.go b/internal/repository/equipment_repository.go new file mode 100644 index 0000000..50a4378 --- /dev/null +++ b/internal/repository/equipment_repository.go @@ -0,0 +1 @@ +package repository diff --git a/internal/repository/exercise_repository.go b/internal/repository/exercise_repository.go new file mode 100644 index 0000000..50a4378 --- /dev/null +++ b/internal/repository/exercise_repository.go @@ -0,0 +1 @@ +package repository diff --git a/internal/repository/muscle_repository.go b/internal/repository/muscle_repository.go new file mode 100644 index 0000000..50a4378 --- /dev/null +++ b/internal/repository/muscle_repository.go @@ -0,0 +1 @@ +package repository diff --git a/internal/repository/train_repository.go b/internal/repository/train_repository.go new file mode 100644 index 0000000..50a4378 --- /dev/null +++ b/internal/repository/train_repository.go @@ -0,0 +1 @@ +package repository diff --git a/internal/repository/user_repository.go b/internal/repository/user_repository.go new file mode 100644 index 0000000..50a4378 --- /dev/null +++ b/internal/repository/user_repository.go @@ -0,0 +1 @@ +package repository From 586c90d6dd00953379c99bba8051b1bb4c7485f2 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Wed, 19 Mar 2025 16:50:32 +0400 Subject: [PATCH 09/26] feat(storage): add services --- internal/services/achievement_service.go | 1 + internal/services/equipment_service.go | 1 + internal/services/exercise_service.go | 1 + internal/services/muscle_service.go | 1 + internal/services/train_service.go | 1 + internal/services/user_service.go | 1 + 6 files changed, 6 insertions(+) create mode 100644 internal/services/achievement_service.go create mode 100644 internal/services/equipment_service.go create mode 100644 internal/services/exercise_service.go create mode 100644 internal/services/muscle_service.go create mode 100644 internal/services/train_service.go create mode 100644 internal/services/user_service.go diff --git a/internal/services/achievement_service.go b/internal/services/achievement_service.go new file mode 100644 index 0000000..5e568ea --- /dev/null +++ b/internal/services/achievement_service.go @@ -0,0 +1 @@ +package services diff --git a/internal/services/equipment_service.go b/internal/services/equipment_service.go new file mode 100644 index 0000000..5e568ea --- /dev/null +++ b/internal/services/equipment_service.go @@ -0,0 +1 @@ +package services diff --git a/internal/services/exercise_service.go b/internal/services/exercise_service.go new file mode 100644 index 0000000..5e568ea --- /dev/null +++ b/internal/services/exercise_service.go @@ -0,0 +1 @@ +package services diff --git a/internal/services/muscle_service.go b/internal/services/muscle_service.go new file mode 100644 index 0000000..5e568ea --- /dev/null +++ b/internal/services/muscle_service.go @@ -0,0 +1 @@ +package services diff --git a/internal/services/train_service.go b/internal/services/train_service.go new file mode 100644 index 0000000..5e568ea --- /dev/null +++ b/internal/services/train_service.go @@ -0,0 +1 @@ +package services diff --git a/internal/services/user_service.go b/internal/services/user_service.go new file mode 100644 index 0000000..5e568ea --- /dev/null +++ b/internal/services/user_service.go @@ -0,0 +1 @@ +package services From b9d13a1fc553d03687b24bea4a7331b23edb28fe Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Thu, 20 Mar 2025 05:05:34 +0400 Subject: [PATCH 10/26] feat(storage): add equipment getAll handler --- cmd/base/main.go | 17 +++++++---- config/config.go | 15 +++------ internal/handlers/equipment_handler.go | 26 ++++++++++++++++ internal/models/equipment.go | 6 ++++ internal/repository/equipment_repository.go | 34 +++++++++++++++++++++ internal/services/equipment_service.go | 19 ++++++++++++ 6 files changed, 100 insertions(+), 17 deletions(-) diff --git a/cmd/base/main.go b/cmd/base/main.go index 5120532..ac5048f 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -3,10 +3,12 @@ package main import ( "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" - "github.com/sirupsen/logrus" "theaesthetics.ru/base/config" + "theaesthetics.ru/base/internal/handlers" "theaesthetics.ru/base/internal/logger" + "theaesthetics.ru/base/internal/repository" + "theaesthetics.ru/base/internal/services" "theaesthetics.ru/base/internal/storage" ) @@ -19,7 +21,6 @@ func main() { // init logger logger.InitLogger(cfg.LogLevel) - logrus.Info("test log") // init pool pool := storage.InitPostgres(cfg) @@ -32,8 +33,12 @@ func main() { })) api := e.Group("/api/v1") - api.GET("/", func(c echo.Context) error { - return c.String(200, "Hello, World!") - }) - e.Logger.Fatal(e.Start(":1323")) + + eqRepo := repository.NewEquipmentRepository(pool) + eqService := services.NewEquipmentService(eqRepo) + eqHandler := handlers.NewEquipmentHandler(eqService) + + api.GET("/equipment", eqHandler.GetAllEquipments) + + e.Logger.Fatal(e.Start(":" + cfg.Port)) } diff --git a/config/config.go b/config/config.go index 77a4217..0edd38e 100644 --- a/config/config.go +++ b/config/config.go @@ -1,26 +1,19 @@ package config import ( - "log" - "github.com/caarlos0/env" - "github.com/joho/godotenv" ) type Config struct { - Host string `env:"HOST" envDefault:"9090"` - Port string `env:"PORT" envDefault:"debug"` //info - POSTGRES_DSN string `env:"POSTGRES_DSN" envDefault:"postgresql://user3:password1@localhost:5433/pool1?sslmode=disable"` - LogLevel string `env:"LOG_LEVEL" envDefault:debug` + Host string `env:"HOST" envDefault:"localhost"` + Port string `env:"PORT" envDefault:"8080"` + POSTGRES_DSN string `env:"POSTGRES_DSN" envDefault:"postgresql://root:123@localhost:5432/base?sslmode=disable"` + LogLevel string `env:"LOG_LEVEL" envDefault:"debug"` } func NewConfig() (*Config, error) { cfg := Config{} - if err := godotenv.Load(".env"); err != nil { - log.Fatalf("Ошибка загрузки .env файла: %v", err) - } - if err := env.Parse(&cfg); err != nil { return nil, err } diff --git a/internal/handlers/equipment_handler.go b/internal/handlers/equipment_handler.go index 5ac8282..c4e5d1f 100644 --- a/internal/handlers/equipment_handler.go +++ b/internal/handlers/equipment_handler.go @@ -1 +1,27 @@ package handlers + +import ( + "net/http" + + "github.com/labstack/echo/v4" + "theaesthetics.ru/base/internal/services" +) + +type EquipmentHandler struct { + service *services.EquipmentService +} + +func NewEquipmentHandler(service *services.EquipmentService) *EquipmentHandler { + return &EquipmentHandler{service: service} +} + +func (h *EquipmentHandler) GetAllEquipments(c echo.Context) error { + equipments, err := h.service.GetAll(c.Request().Context()) + if err != nil { + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, equipments) +} diff --git a/internal/models/equipment.go b/internal/models/equipment.go index 2640e7f..dc06b75 100644 --- a/internal/models/equipment.go +++ b/internal/models/equipment.go @@ -1 +1,7 @@ package models + +type Equipment struct { + Id uint8 `json:"id"` + Title string `json:"title"` + Image string `json:"image"` +} diff --git a/internal/repository/equipment_repository.go b/internal/repository/equipment_repository.go index 50a4378..943c7c4 100644 --- a/internal/repository/equipment_repository.go +++ b/internal/repository/equipment_repository.go @@ -1 +1,35 @@ package repository + +import ( + "context" + + "github.com/jackc/pgx/v5/pgxpool" + "theaesthetics.ru/base/internal/models" +) + +type EquipmentRepository struct { + db *pgxpool.Pool +} + +func NewEquipmentRepository(db *pgxpool.Pool) *EquipmentRepository { + return &EquipmentRepository{db: db} +} + +func (r *EquipmentRepository) GetAll(ctx context.Context) ([]models.Equipment, error) { + query := `SELECT id, title, image FROM equipments` + rows, err := r.db.Query(ctx, query) + if err != nil { + return nil, err + } + defer rows.Close() + var equipments []models.Equipment + for rows.Next() { + var equipment models.Equipment + err := rows.Scan(&equipment.Id, &equipment.Title, &equipment.Image) + if err != nil { + return nil, err + } + equipments = append(equipments, equipment) + } + return equipments, nil +} diff --git a/internal/services/equipment_service.go b/internal/services/equipment_service.go index 5e568ea..10d7e61 100644 --- a/internal/services/equipment_service.go +++ b/internal/services/equipment_service.go @@ -1 +1,20 @@ package services + +import ( + "context" + + "theaesthetics.ru/base/internal/models" + "theaesthetics.ru/base/internal/repository" +) + +type EquipmentService struct { + repo *repository.EquipmentRepository +} + +func NewEquipmentService(repo *repository.EquipmentRepository) *EquipmentService { + return &EquipmentService{repo: repo} +} + +func (s *EquipmentService) GetAll(ctx context.Context) ([]models.Equipment, error) { + return s.repo.GetAll(ctx) +} From 10754cd442996115b200f8e855f77dbe699350c2 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Thu, 20 Mar 2025 08:45:30 +0400 Subject: [PATCH 11/26] feat(storage): add equipment get by id handler --- cmd/base/main.go | 1 + internal/handlers/equipment_handler.go | 30 ++++++++++++++++++++- internal/repository/equipment_repository.go | 18 ++++++++++++- internal/services/equipment_service.go | 8 ++++-- 4 files changed, 53 insertions(+), 4 deletions(-) diff --git a/cmd/base/main.go b/cmd/base/main.go index ac5048f..f8e1581 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -39,6 +39,7 @@ func main() { eqHandler := handlers.NewEquipmentHandler(eqService) api.GET("/equipment", eqHandler.GetAllEquipments) + api.GET("/equipment/:id", eqHandler.GetEquipmentById) e.Logger.Fatal(e.Start(":" + cfg.Port)) } diff --git a/internal/handlers/equipment_handler.go b/internal/handlers/equipment_handler.go index c4e5d1f..5d89973 100644 --- a/internal/handlers/equipment_handler.go +++ b/internal/handlers/equipment_handler.go @@ -2,8 +2,10 @@ package handlers import ( "net/http" + "strconv" "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" "theaesthetics.ru/base/internal/services" ) @@ -16,8 +18,10 @@ func NewEquipmentHandler(service *services.EquipmentService) *EquipmentHandler { } func (h *EquipmentHandler) GetAllEquipments(c echo.Context) error { - equipments, err := h.service.GetAll(c.Request().Context()) + equipments, err := h.service.GetAllEquipments(c.Request().Context()) if err != nil { + logrus.Error(err) + return c.JSON(http.StatusInternalServerError, map[string]string{ "error": err.Error(), }) @@ -25,3 +29,27 @@ func (h *EquipmentHandler) GetAllEquipments(c echo.Context) error { return c.JSON(http.StatusOK, equipments) } + +func (h *EquipmentHandler) GetEquipmentById(c echo.Context) error { + id, err := strconv.Atoi(c.Param("id")) + ctx := c.Request().Context() + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + var idu8 uint8 = uint8(id) + equipment, err := h.service.GetEquipmentById(ctx, idu8) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + return c.JSON(http.StatusOK, equipment) +} diff --git a/internal/repository/equipment_repository.go b/internal/repository/equipment_repository.go index 943c7c4..4060085 100644 --- a/internal/repository/equipment_repository.go +++ b/internal/repository/equipment_repository.go @@ -4,6 +4,7 @@ import ( "context" "github.com/jackc/pgx/v5/pgxpool" + "github.com/sirupsen/logrus" "theaesthetics.ru/base/internal/models" ) @@ -15,7 +16,7 @@ func NewEquipmentRepository(db *pgxpool.Pool) *EquipmentRepository { return &EquipmentRepository{db: db} } -func (r *EquipmentRepository) GetAll(ctx context.Context) ([]models.Equipment, error) { +func (r *EquipmentRepository) GetAllEquipments(ctx context.Context) ([]models.Equipment, error) { query := `SELECT id, title, image FROM equipments` rows, err := r.db.Query(ctx, query) if err != nil { @@ -33,3 +34,18 @@ func (r *EquipmentRepository) GetAll(ctx context.Context) ([]models.Equipment, e } return equipments, nil } + +func (r *EquipmentRepository) GetEquipmentById(ctx context.Context, id uint8) (*models.Equipment, error) { + query := `SELECT id, title, image FROM equipments WHERE id = $1` + row := r.db.QueryRow(ctx, query, id) + + equipment := &models.Equipment{} + err := row.Scan(&equipment.Id, &equipment.Title, &equipment.Image) + + if err != nil { + logrus.Debug(err) + return nil, err + } + + return equipment, nil +} diff --git a/internal/services/equipment_service.go b/internal/services/equipment_service.go index 10d7e61..c7f166d 100644 --- a/internal/services/equipment_service.go +++ b/internal/services/equipment_service.go @@ -15,6 +15,10 @@ func NewEquipmentService(repo *repository.EquipmentRepository) *EquipmentService return &EquipmentService{repo: repo} } -func (s *EquipmentService) GetAll(ctx context.Context) ([]models.Equipment, error) { - return s.repo.GetAll(ctx) +func (s *EquipmentService) GetAllEquipments(ctx context.Context) ([]models.Equipment, error) { + return s.repo.GetAllEquipments(ctx) +} + +func (s *EquipmentService) GetEquipmentById(ctx context.Context, id uint8) (*models.Equipment, error) { + return s.repo.GetEquipmentById(ctx, id) } From b19282187643a9cad88a1268fddb7f5233f3e221 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Fri, 21 Mar 2025 03:18:29 +0400 Subject: [PATCH 12/26] feat(equipment): add update,create,delete methods --- cmd/base/main.go | 3 + internal/handlers/equipment_handler.go | 96 +++++++++++++++++++ internal/repository/equipment_repository.go | 35 ++++++- internal/services/equipment_service.go | 12 +++ .../migrations/20250319015139_init_schema.sql | 15 +-- 5 files changed, 153 insertions(+), 8 deletions(-) diff --git a/cmd/base/main.go b/cmd/base/main.go index f8e1581..f19df97 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -39,7 +39,10 @@ func main() { eqHandler := handlers.NewEquipmentHandler(eqService) api.GET("/equipment", eqHandler.GetAllEquipments) + api.POST("/equipment", eqHandler.CreateEqipment) api.GET("/equipment/:id", eqHandler.GetEquipmentById) + api.PUT("/equipment/:id", eqHandler.UpdateEqipment) + api.DELETE("/equipment/:id", eqHandler.RemoveEquipment) e.Logger.Fatal(e.Start(":" + cfg.Port)) } diff --git a/internal/handlers/equipment_handler.go b/internal/handlers/equipment_handler.go index 5d89973..61ea8e5 100644 --- a/internal/handlers/equipment_handler.go +++ b/internal/handlers/equipment_handler.go @@ -1,11 +1,13 @@ package handlers import ( + "context" "net/http" "strconv" "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" + "theaesthetics.ru/base/internal/models" "theaesthetics.ru/base/internal/services" ) @@ -53,3 +55,97 @@ func (h *EquipmentHandler) GetEquipmentById(c echo.Context) error { } return c.JSON(http.StatusOK, equipment) } + +func (h *EquipmentHandler) CreateEqipment(c echo.Context) error { + ctx := context.Background() + var equipment models.Equipment + err := c.Bind(&equipment) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + err = h.service.CreateEqipment(ctx, equipment.Title, equipment.Image) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusCreated, map[string]string{ + "message": "created", + }) +} + +func (h *EquipmentHandler) RemoveEquipment(c echo.Context) error { + ctx := context.Background() + id, err := strconv.Atoi(c.Param("id")) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + uid := uint8(id) + err = h.service.RemoveEquipment(ctx, uid) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, map[string]string{ + "message": "deleted", + }) +} + +func (h *EquipmentHandler) UpdateEqipment(c echo.Context) error { + ctx := context.Background() + id, err := strconv.Atoi(c.Param("id")) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + uid := uint8(id) + + equipment := models.Equipment{Id: uid} + err = c.Bind(&equipment) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + err = h.service.UpdateEquipment(ctx, equipment) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusCreated, map[string]string{ + "message": "updated", + }) +} diff --git a/internal/repository/equipment_repository.go b/internal/repository/equipment_repository.go index 4060085..c8ccebf 100644 --- a/internal/repository/equipment_repository.go +++ b/internal/repository/equipment_repository.go @@ -43,9 +43,42 @@ func (r *EquipmentRepository) GetEquipmentById(ctx context.Context, id uint8) (* err := row.Scan(&equipment.Id, &equipment.Title, &equipment.Image) if err != nil { - logrus.Debug(err) + logrus.Error(err) return nil, err } return equipment, nil } + +func (r *EquipmentRepository) CreateEqipment(ctx context.Context, title, image string) error { + query := `INSERT INTO equipments (title, image) VALUES ($1, $2)` + _, err := r.db.Exec(ctx, query, title, image) + if err != nil { + logrus.Error(err) + return err + } + + return nil +} + +func (r *EquipmentRepository) RemoveEquipment(ctx context.Context, id uint8) error { + query := `DELETE FROM equipments WHERE id = $1` + _, err := r.db.Exec(ctx, query, id) + if err != nil { + logrus.Error(err) + return err + } + + return nil +} + +func (r *EquipmentRepository) UpdateEquipment(ctx context.Context, equipment models.Equipment) error { + query := `UPDATE equipments SET title = $1, image = $2 WHERE id = $3` + _, err := r.db.Exec(ctx, query, equipment.Title, equipment.Image, equipment.Id) + if err != nil { + logrus.Error(err) + return err + } + + return nil +} diff --git a/internal/services/equipment_service.go b/internal/services/equipment_service.go index c7f166d..7badd4c 100644 --- a/internal/services/equipment_service.go +++ b/internal/services/equipment_service.go @@ -22,3 +22,15 @@ func (s *EquipmentService) GetAllEquipments(ctx context.Context) ([]models.Equip func (s *EquipmentService) GetEquipmentById(ctx context.Context, id uint8) (*models.Equipment, error) { return s.repo.GetEquipmentById(ctx, id) } + +func (s *EquipmentService) CreateEqipment(ctx context.Context, title, image string) error { + return s.repo.CreateEqipment(ctx, title, image) +} + +func (s *EquipmentService) RemoveEquipment(ctx context.Context, id uint8) error { + return s.repo.RemoveEquipment(ctx, id) +} + +func (s *EquipmentService) UpdateEquipment(ctx context.Context, equipment models.Equipment) error { + return s.repo.UpdateEquipment(ctx, equipment) +} diff --git a/storage/migrations/20250319015139_init_schema.sql b/storage/migrations/20250319015139_init_schema.sql index 561985b..5262bdb 100644 --- a/storage/migrations/20250319015139_init_schema.sql +++ b/storage/migrations/20250319015139_init_schema.sql @@ -67,7 +67,7 @@ CREATE TABLE IF NOT EXISTS exercises ( CREATE TABLE IF NOT EXISTS equipments ( id SERIAL PRIMARY KEY, title VARCHAR UNIQUE NOT NULL, - image VARCHAR UNIQUE NOT NULL + image VARCHAR ); -- +goose StatementEnd @@ -109,28 +109,29 @@ CREATE TABLE IF NOT EXISTS train_help_muscle ( -- +goose StatementEnd -- +goose Down + -- +goose StatementBegin -DROP TABLE IF EXISTS users; +DROP TABLE IF EXISTS equipments; -- +goose StatementEnd -- +goose StatementBegin -DROP TABLE IF EXISTS achievements; +DROP TABLE IF EXISTS exercises; -- +goose StatementEnd -- +goose StatementBegin -DROP TABLE IF EXISTS users_achievements; +DROP TABLE IF EXISTS users; -- +goose StatementEnd -- +goose StatementBegin -DROP TABLE IF EXISTS muscles; +DROP TABLE IF EXISTS achievements; -- +goose StatementEnd -- +goose StatementBegin -DROP TABLE IF EXISTS exercises; +DROP TABLE IF EXISTS users_achievements; -- +goose StatementEnd -- +goose StatementBegin -DROP TABLE IF EXISTS equipments; +DROP TABLE IF EXISTS muscles; -- +goose StatementEnd -- +goose StatementBegin From d1e98e07ef74d1a70ab66e1e9e671f523937a7b9 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Fri, 21 Mar 2025 03:32:03 +0400 Subject: [PATCH 13/26] feat(equipment): add router folder --- cmd/base/main.go | 18 +++--------------- internal/router/router.go | 23 +++++++++++++++++++++++ 2 files changed, 26 insertions(+), 15 deletions(-) create mode 100644 internal/router/router.go diff --git a/cmd/base/main.go b/cmd/base/main.go index f19df97..cfd0f4f 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -5,10 +5,8 @@ import ( "github.com/labstack/echo/v4/middleware" "theaesthetics.ru/base/config" - "theaesthetics.ru/base/internal/handlers" "theaesthetics.ru/base/internal/logger" - "theaesthetics.ru/base/internal/repository" - "theaesthetics.ru/base/internal/services" + "theaesthetics.ru/base/internal/router" "theaesthetics.ru/base/internal/storage" ) @@ -24,7 +22,6 @@ func main() { // init pool pool := storage.InitPostgres(cfg) - _ = pool // run echo server e := echo.New() @@ -32,17 +29,8 @@ func main() { Format: "method=${method}, uri=${uri}, status=${status}\n", })) - api := e.Group("/api/v1") - - eqRepo := repository.NewEquipmentRepository(pool) - eqService := services.NewEquipmentService(eqRepo) - eqHandler := handlers.NewEquipmentHandler(eqService) - - api.GET("/equipment", eqHandler.GetAllEquipments) - api.POST("/equipment", eqHandler.CreateEqipment) - api.GET("/equipment/:id", eqHandler.GetEquipmentById) - api.PUT("/equipment/:id", eqHandler.UpdateEqipment) - api.DELETE("/equipment/:id", eqHandler.RemoveEquipment) + // init router + router.InitRouter(e, pool) e.Logger.Fatal(e.Start(":" + cfg.Port)) } diff --git a/internal/router/router.go b/internal/router/router.go new file mode 100644 index 0000000..7f67342 --- /dev/null +++ b/internal/router/router.go @@ -0,0 +1,23 @@ +package router + +import ( + "github.com/jackc/pgx/v5/pgxpool" + "github.com/labstack/echo/v4" + "theaesthetics.ru/base/internal/handlers" + "theaesthetics.ru/base/internal/repository" + "theaesthetics.ru/base/internal/services" +) + +func InitRouter(e *echo.Echo, db *pgxpool.Pool) { + api := e.Group("/api/v1") + + eqRepo := repository.NewEquipmentRepository(db) + eqService := services.NewEquipmentService(eqRepo) + eqHandler := handlers.NewEquipmentHandler(eqService) + + api.GET("/equipment", eqHandler.GetAllEquipments) + api.POST("/equipment", eqHandler.CreateEqipment) + api.GET("/equipment/:id", eqHandler.GetEquipmentById) + api.PUT("/equipment/:id", eqHandler.UpdateEqipment) + api.DELETE("/equipment/:id", eqHandler.RemoveEquipment) +} From 04518dd750f01f56691b696d911e81f01f3fc969 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Fri, 21 Mar 2025 03:56:03 +0400 Subject: [PATCH 14/26] add workflow --- .github/workflows/deploy.yml | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .github/workflows/deploy.yml diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..a20c5fe --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,59 @@ +name: Deploy Go App + +on: + pull_request: + types: [opened, synchronize, reopened] + + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + # Проверяем код + - name: Checkout code + uses: actions/checkout@v3 + + # Устанавливаем Go + - name: Set up Go + uses: actions/setup-go@v4 + with: + go-version: "1.21" + + # Сборка бинарника + - name: Build binary + run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main ./cmd/app + + # Подключение к серверу по SSH и деплой + - name: Deploy to Server + uses: appleboy/ssh-action@v0.1.8 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USER }} + port: ${{ secrets.PORT }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + mkdir -p /var/www/myapp + rm -f /var/www/myapp/main + exit || true + - name: Upload binary + uses: appleboy/scp-action@v0.1.4 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USER }} + port: ${{ secrets.PORT }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + source: main + target: /var/www/myapp/main + + # Перезапуск сервиса + - name: Restart service + uses: appleboy/ssh-action@v0.1.8 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USER }} + port: ${{ secrets.PORT }} + key: ${{ secrets.SSH_PRIVATE_KEY }} + script: | + sudo systemctl restart myapp + sudo systemctl status myapp From dcce2cbbd5a775f3d4845879ee98aabaf12d0094 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Fri, 21 Mar 2025 03:57:27 +0400 Subject: [PATCH 15/26] fix workflow --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a20c5fe..3c04d47 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,7 +22,7 @@ jobs: # Сборка бинарника - name: Build binary - run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main ./cmd/app + run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main ./cmd/base/main # Подключение к серверу по SSH и деплой - name: Deploy to Server From e7d58d57a2b50f220a966b500a29cc37e1e9f30c Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Fri, 21 Mar 2025 04:01:32 +0400 Subject: [PATCH 16/26] fix workflow --- .github/workflows/deploy.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 3c04d47..a22bd3d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -22,7 +22,11 @@ jobs: # Сборка бинарника - name: Build binary - run: CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main ./cmd/base/main + run: | + pwd + ls -la + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main ./cmd/base + # Подключение к серверу по SSH и деплой - name: Deploy to Server From 84beec5ccf76fc87c1547a7551317448a2818f55 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Fri, 21 Mar 2025 04:08:11 +0400 Subject: [PATCH 17/26] fix workflow --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a22bd3d..215eb35 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -35,7 +35,7 @@ jobs: host: ${{ secrets.HOST }} username: ${{ secrets.USER }} port: ${{ secrets.PORT }} - key: ${{ secrets.SSH_PRIVATE_KEY }} + password: ${{ secrets.SSH_PASSWORD }} script: | mkdir -p /var/www/myapp rm -f /var/www/myapp/main From 09d8369babacefb035872490c6aa459e8de6e3d5 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Fri, 21 Mar 2025 04:10:50 +0400 Subject: [PATCH 18/26] fix workflow --- .github/workflows/deploy.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 215eb35..dbdaa74 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,12 +41,12 @@ jobs: rm -f /var/www/myapp/main exit || true - name: Upload binary - uses: appleboy/scp-action@v0.1.4 + uses: appleboy/ssh-action@v0.1.8 with: host: ${{ secrets.HOST }} username: ${{ secrets.USER }} port: ${{ secrets.PORT }} - key: ${{ secrets.SSH_PRIVATE_KEY }} + password: ${{ secrets.SSH_PASSWORD }} source: main target: /var/www/myapp/main @@ -57,7 +57,7 @@ jobs: host: ${{ secrets.HOST }} username: ${{ secrets.USER }} port: ${{ secrets.PORT }} - key: ${{ secrets.SSH_PRIVATE_KEY }} + password: ${{ secrets.SSH_PASSWORD }} script: | sudo systemctl restart myapp sudo systemctl status myapp From b914d9f2186d334d460c9caa8e9301a1f7043d97 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Fri, 21 Mar 2025 04:13:00 +0400 Subject: [PATCH 19/26] fix workflow --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index dbdaa74..6e1f6b0 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -41,7 +41,7 @@ jobs: rm -f /var/www/myapp/main exit || true - name: Upload binary - uses: appleboy/ssh-action@v0.1.8 + uses: appleboy/scp-action@v0.1.4 with: host: ${{ secrets.HOST }} username: ${{ secrets.USER }} From 81c3e33af642a9d72b3066b3c16073d03fdc97fc Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov <105169805+rLukoyanov@users.noreply.github.com> Date: Fri, 21 Mar 2025 04:44:04 +0400 Subject: [PATCH 20/26] Update deploy.yml --- .github/workflows/deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 6e1f6b0..2d4c936 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -38,7 +38,7 @@ jobs: password: ${{ secrets.SSH_PASSWORD }} script: | mkdir -p /var/www/myapp - rm -f /var/www/myapp/main + rm -f /var/www/myapp/main/main exit || true - name: Upload binary uses: appleboy/scp-action@v0.1.4 From bda7283ac79504789d46918c8d8ee3dd1c971cec Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Sun, 23 Mar 2025 06:42:42 +0400 Subject: [PATCH 21/26] feat(equipment): add unic checks and transaktions --- .env.example | 2 - cmd/base/main.go | 13 +++---- internal/handlers/equipment_handler.go | 7 ++-- internal/handlers/errors.go | 7 ++++ internal/logger/logger.go | 11 ++++-- internal/repository/checks.go | 27 ++++++++++++++ internal/repository/equipment_repository.go | 41 ++++++++++++++++++++- internal/services/equipment_service.go | 4 +- internal/storage/storage.go | 9 ++--- 9 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 internal/handlers/errors.go create mode 100644 internal/repository/checks.go diff --git a/.env.example b/.env.example index 452da38..8ac2d54 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,3 @@ -HOST="localhost" -PORT=5432 POSTGRES_DSN="postgres://root:123@localhost:5432/base?sslmode=disable" LOG_LEVEL="debug" GOOSE_DRIVER=postgres diff --git a/cmd/base/main.go b/cmd/base/main.go index cfd0f4f..8c89ee4 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -1,6 +1,8 @@ package main import ( + "context" + "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" @@ -11,20 +13,17 @@ import ( ) func main() { + ctx := context.Background() // init config cfg, err := config.NewConfig() if err != nil { panic(err.Error()) } - // init logger - logger.InitLogger(cfg.LogLevel) - - // init pool - pool := storage.InitPostgres(cfg) - - // run echo server + log := logger.InitLogger(cfg.LogLevel) + pool := storage.InitPostgres(cfg, ctx, log) e := echo.New() + e.Use(middleware.LoggerWithConfig(middleware.LoggerConfig{ Format: "method=${method}, uri=${uri}, status=${status}\n", })) diff --git a/internal/handlers/equipment_handler.go b/internal/handlers/equipment_handler.go index 61ea8e5..5ad48cb 100644 --- a/internal/handlers/equipment_handler.go +++ b/internal/handlers/equipment_handler.go @@ -20,7 +20,8 @@ func NewEquipmentHandler(service *services.EquipmentService) *EquipmentHandler { } func (h *EquipmentHandler) GetAllEquipments(c echo.Context) error { - equipments, err := h.service.GetAllEquipments(c.Request().Context()) + ctx := c.Request().Context() + equipments, err := h.service.GetAllEquipments(ctx) if err != nil { logrus.Error(err) @@ -33,8 +34,8 @@ func (h *EquipmentHandler) GetAllEquipments(c echo.Context) error { } func (h *EquipmentHandler) GetEquipmentById(c echo.Context) error { - id, err := strconv.Atoi(c.Param("id")) ctx := c.Request().Context() + id, err := strconv.Atoi(c.Param("id")) if err != nil { logrus.Error(err) @@ -96,7 +97,7 @@ func (h *EquipmentHandler) RemoveEquipment(c echo.Context) error { }) } uid := uint8(id) - err = h.service.RemoveEquipment(ctx, uid) + err = h.service.DeleteEquipment(ctx, uid) if err != nil { logrus.Error(err) diff --git a/internal/handlers/errors.go b/internal/handlers/errors.go new file mode 100644 index 0000000..1cd16cb --- /dev/null +++ b/internal/handlers/errors.go @@ -0,0 +1,7 @@ +package handlers + +func returnError(err error) *map[string]string { + return &map[string]string{ + "error": err.Error(), + } +} diff --git a/internal/logger/logger.go b/internal/logger/logger.go index c1c5a18..3f5bee6 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -4,19 +4,22 @@ import ( "fmt" "runtime" - log "github.com/sirupsen/logrus" + "github.com/sirupsen/logrus" ) -func InitLogger(logLevel string) { - level, err := log.ParseLevel(logLevel) +func InitLogger(logLevel string) *logrus.Logger { + log := logrus.New() + level, err := logrus.ParseLevel(logLevel) if err != nil { log.Fatalf("Ошибка при парсинге уровня логирования: %v", err) } log.SetLevel(level) log.SetReportCaller(true) - log.SetFormatter(&log.JSONFormatter{ + log.SetFormatter(&logrus.JSONFormatter{ CallerPrettyfier: func(frame *runtime.Frame) (function string, file string) { return fmt.Sprintf("%s:%d", frame.Function, frame.Line), "" }, }) + + return log } diff --git a/internal/repository/checks.go b/internal/repository/checks.go new file mode 100644 index 0000000..c894685 --- /dev/null +++ b/internal/repository/checks.go @@ -0,0 +1,27 @@ +package repository + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5" +) + +func checkTitle(tx pgx.Tx, ctx context.Context, title string) error { + var id int + err := tx.QueryRow(ctx, ` + SELECT id FROM equipments WHERE title = $1 FOR UPDATE + `, title).Scan(&id) + + if err == nil { + err = fmt.Errorf("equipment with title '%s' already exists", title) + return err + } + + if err != pgx.ErrNoRows { + + return err + } + + return nil +} diff --git a/internal/repository/equipment_repository.go b/internal/repository/equipment_repository.go index c8ccebf..b65ef5d 100644 --- a/internal/repository/equipment_repository.go +++ b/internal/repository/equipment_repository.go @@ -2,6 +2,7 @@ package repository import ( "context" + "fmt" "github.com/jackc/pgx/v5/pgxpool" "github.com/sirupsen/logrus" @@ -51,13 +52,31 @@ func (r *EquipmentRepository) GetEquipmentById(ctx context.Context, id uint8) (* } func (r *EquipmentRepository) CreateEqipment(ctx context.Context, title, image string) error { + tx, err := r.db.Begin(ctx) + if err != nil { + logrus.Error() + return err + } + defer tx.Rollback(ctx) + + err = checkTitle(tx, ctx, title) + if err != nil { + logrus.Error(err) + return err + } + query := `INSERT INTO equipments (title, image) VALUES ($1, $2)` - _, err := r.db.Exec(ctx, query, title, image) + _, err = r.db.Exec(ctx, query, title, image) if err != nil { logrus.Error(err) return err } + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("transaction commit failed: %w", err) + } + return nil } @@ -73,12 +92,30 @@ func (r *EquipmentRepository) RemoveEquipment(ctx context.Context, id uint8) err } func (r *EquipmentRepository) UpdateEquipment(ctx context.Context, equipment models.Equipment) error { + tx, err := r.db.Begin(ctx) + if err != nil { + logrus.Error() + return err + } + defer tx.Rollback(ctx) + + err = checkTitle(tx, ctx, equipment.Title) + if err != nil { + logrus.Error(err) + return err + } + query := `UPDATE equipments SET title = $1, image = $2 WHERE id = $3` - _, err := r.db.Exec(ctx, query, equipment.Title, equipment.Image, equipment.Id) + _, err = r.db.Exec(ctx, query, equipment.Title, equipment.Image, equipment.Id) if err != nil { logrus.Error(err) return err } + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("transaction commit failed: %w", err) + } + return nil } diff --git a/internal/services/equipment_service.go b/internal/services/equipment_service.go index 7badd4c..7fcc1f2 100644 --- a/internal/services/equipment_service.go +++ b/internal/services/equipment_service.go @@ -27,8 +27,8 @@ func (s *EquipmentService) CreateEqipment(ctx context.Context, title, image stri return s.repo.CreateEqipment(ctx, title, image) } -func (s *EquipmentService) RemoveEquipment(ctx context.Context, id uint8) error { - return s.repo.RemoveEquipment(ctx, id) +func (s *EquipmentService) DeleteEquipment(ctx context.Context, id uint8) error { + return s.repo.DeleteEquipment(ctx, id) } func (s *EquipmentService) UpdateEquipment(ctx context.Context, equipment models.Equipment) error { diff --git a/internal/storage/storage.go b/internal/storage/storage.go index 58a5e49..1405a31 100644 --- a/internal/storage/storage.go +++ b/internal/storage/storage.go @@ -2,25 +2,24 @@ package storage import ( "context" - "log" "github.com/jackc/pgx/v5/pgxpool" + "github.com/sirupsen/logrus" "theaesthetics.ru/base/config" ) -func InitPostgres(cfg *config.Config) *pgxpool.Pool { - ctx := context.Background() +func InitPostgres(cfg *config.Config, ctx context.Context, log *logrus.Logger) *pgxpool.Pool { pool, err := pgxpool.New(ctx, cfg.POSTGRES_DSN) if err != nil { - log.Fatalf("ошибка подключения к базе данных: %s", err.Error()) + log.Errorf("ошибка подключения к базе данных: %s", err.Error()) return nil } err = pool.Ping(ctx) if err != nil { - log.Fatalf("ошибка подключения к базе данных: %s", err.Error()) + log.Errorf("ошибка подключения к базе данных: %s", err.Error()) return nil } From 661ee8cc770792f7c4f8f951013d896afd87f852 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Sun, 23 Mar 2025 06:50:03 +0400 Subject: [PATCH 22/26] feat(equipment): add gracful shitdown --- cmd/base/main.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/base/main.go b/cmd/base/main.go index 8c89ee4..abb1219 100644 --- a/cmd/base/main.go +++ b/cmd/base/main.go @@ -2,9 +2,13 @@ package main import ( "context" + "os" + "os/signal" + "syscall" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" + "github.com/sirupsen/logrus" "theaesthetics.ru/base/config" "theaesthetics.ru/base/internal/logger" @@ -14,7 +18,6 @@ import ( func main() { ctx := context.Background() - // init config cfg, err := config.NewConfig() if err != nil { panic(err.Error()) @@ -28,8 +31,21 @@ func main() { Format: "method=${method}, uri=${uri}, status=${status}\n", })) - // init router router.InitRouter(e, pool) - e.Logger.Fatal(e.Start(":" + cfg.Port)) + go func() { + log.Error(e.Start(":" + cfg.Port)) + }() + + log.Info("start service") + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGTERM, syscall.SIGINT) + <-quit + log.Info("stop service") + + if err := e.Shutdown(ctx); err != nil { + logrus.Error("errors on stoping server", err) + } + + pool.Close() } From 033458031b70cbba39513795362319bcceb01f49 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Sun, 23 Mar 2025 06:55:02 +0400 Subject: [PATCH 23/26] add dokerfile --- base-service.dockerfile | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 base-service.dockerfile diff --git a/base-service.dockerfile b/base-service.dockerfile new file mode 100644 index 0000000..1e15346 --- /dev/null +++ b/base-service.dockerfile @@ -0,0 +1,7 @@ +FROM alpine:latest + +RUN mkdir /app + +COPY baseService /app + +CMD [ "/app/baseService"] \ No newline at end of file From 2b4fba1bfb226f434d2bfb09e32f627f75c17a44 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Sun, 23 Mar 2025 08:34:33 +0400 Subject: [PATCH 24/26] feat(muscles): add muscles --- internal/handlers/equipment_handler.go | 2 +- internal/handlers/muscle_handler.go | 151 ++++++++++++++++++++ internal/models/muscle.go | 6 + internal/repository/checks.go | 15 +- internal/repository/equipment_repository.go | 15 +- internal/repository/muscle_repository.go | 115 +++++++++++++++ internal/router/router.go | 12 +- internal/services/exercise_service.go | 35 +++++ 8 files changed, 332 insertions(+), 19 deletions(-) diff --git a/internal/handlers/equipment_handler.go b/internal/handlers/equipment_handler.go index 5ad48cb..edfb261 100644 --- a/internal/handlers/equipment_handler.go +++ b/internal/handlers/equipment_handler.go @@ -85,7 +85,7 @@ func (h *EquipmentHandler) CreateEqipment(c echo.Context) error { }) } -func (h *EquipmentHandler) RemoveEquipment(c echo.Context) error { +func (h *EquipmentHandler) DeleteEquipment(c echo.Context) error { ctx := context.Background() id, err := strconv.Atoi(c.Param("id")) diff --git a/internal/handlers/muscle_handler.go b/internal/handlers/muscle_handler.go index 5ac8282..e0fc877 100644 --- a/internal/handlers/muscle_handler.go +++ b/internal/handlers/muscle_handler.go @@ -1 +1,152 @@ package handlers + +import ( + "context" + "net/http" + "strconv" + + "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" + "theaesthetics.ru/base/internal/models" + "theaesthetics.ru/base/internal/services" +) + +type MusclesHandler struct { + service *services.MusclesService +} + +func NewMusclesHandler(service *services.MusclesService) *MusclesHandler { + return &MusclesHandler{service: service} +} + +func (h *MusclesHandler) GetAllEMuscles(c echo.Context) error { + ctx := c.Request().Context() + Muscless, err := h.service.GetAllMuscless(ctx) + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, Muscless) +} + +func (h *MusclesHandler) GetMusclesById(c echo.Context) error { + ctx := c.Request().Context() + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + var idu8 uint8 = uint8(id) + Muscles, err := h.service.GetMusclesById(ctx, idu8) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + return c.JSON(http.StatusOK, Muscles) +} + +func (h *MusclesHandler) CreateMuscles(c echo.Context) error { + ctx := context.Background() + var Muscles models.Muscles + err := c.Bind(&Muscles) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + err = h.service.CreateEqipment(ctx, Muscles.Title, Muscles.Image) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusCreated, map[string]string{ + "message": "created", + }) +} + +func (h *MusclesHandler) DeleteMuscles(c echo.Context) error { + ctx := context.Background() + id, err := strconv.Atoi(c.Param("id")) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + uid := uint8(id) + err = h.service.DeleteMuscles(ctx, uid) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, map[string]string{ + "message": "deleted", + }) +} + +func (h *MusclesHandler) UpdateMuscles(c echo.Context) error { + ctx := context.Background() + id, err := strconv.Atoi(c.Param("id")) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + uid := uint8(id) + + Muscles := models.Muscles{Id: uid} + err = c.Bind(&Muscles) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusBadRequest, map[string]string{ + "error": err.Error(), + }) + } + + err = h.service.UpdateMuscles(ctx, Muscles) + + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusCreated, map[string]string{ + "message": "updated", + }) +} diff --git a/internal/models/muscle.go b/internal/models/muscle.go index 2640e7f..5a5be9e 100644 --- a/internal/models/muscle.go +++ b/internal/models/muscle.go @@ -1 +1,7 @@ package models + +type Muscles struct { + Id uint8 `json:"id"` + Title string `json:"title"` + Image string `json:"image"` +} diff --git a/internal/repository/checks.go b/internal/repository/checks.go index c894685..b058571 100644 --- a/internal/repository/checks.go +++ b/internal/repository/checks.go @@ -5,21 +5,22 @@ import ( "fmt" "github.com/jackc/pgx/v5" + "github.com/sirupsen/logrus" ) -func checkTitle(tx pgx.Tx, ctx context.Context, title string) error { +// checkTitle проверяет уникальность названия в переданной таблице +func checkTitle(tx pgx.Tx, ctx context.Context, title string, table string) error { + // Безопасно подставляем имя таблицы + query := fmt.Sprintf(`SELECT id FROM %s WHERE title = $1 FOR UPDATE`, table) var id int - err := tx.QueryRow(ctx, ` - SELECT id FROM equipments WHERE title = $1 FOR UPDATE - `, title).Scan(&id) + logrus.Infof("Проверяем уникальность в таблице: %s", table) + err := tx.QueryRow(ctx, query, title).Scan(&id) if err == nil { - err = fmt.Errorf("equipment with title '%s' already exists", title) - return err + return fmt.Errorf("'%s' with title '%s' already exists", table, title) } if err != pgx.ErrNoRows { - return err } diff --git a/internal/repository/equipment_repository.go b/internal/repository/equipment_repository.go index b65ef5d..802c758 100644 --- a/internal/repository/equipment_repository.go +++ b/internal/repository/equipment_repository.go @@ -9,6 +9,8 @@ import ( "theaesthetics.ru/base/internal/models" ) +const tableNameEquipments = "equipments" + type EquipmentRepository struct { db *pgxpool.Pool } @@ -44,7 +46,6 @@ func (r *EquipmentRepository) GetEquipmentById(ctx context.Context, id uint8) (* err := row.Scan(&equipment.Id, &equipment.Title, &equipment.Image) if err != nil { - logrus.Error(err) return nil, err } @@ -54,21 +55,18 @@ func (r *EquipmentRepository) GetEquipmentById(ctx context.Context, id uint8) (* func (r *EquipmentRepository) CreateEqipment(ctx context.Context, title, image string) error { tx, err := r.db.Begin(ctx) if err != nil { - logrus.Error() return err } defer tx.Rollback(ctx) - err = checkTitle(tx, ctx, title) + err = checkTitle(tx, ctx, title, tableNameEquipments) if err != nil { - logrus.Error(err) return err } query := `INSERT INTO equipments (title, image) VALUES ($1, $2)` _, err = r.db.Exec(ctx, query, title, image) if err != nil { - logrus.Error(err) return err } @@ -80,11 +78,10 @@ func (r *EquipmentRepository) CreateEqipment(ctx context.Context, title, image s return nil } -func (r *EquipmentRepository) RemoveEquipment(ctx context.Context, id uint8) error { +func (r *EquipmentRepository) DeleteEquipment(ctx context.Context, id uint8) error { query := `DELETE FROM equipments WHERE id = $1` _, err := r.db.Exec(ctx, query, id) if err != nil { - logrus.Error(err) return err } @@ -99,16 +96,14 @@ func (r *EquipmentRepository) UpdateEquipment(ctx context.Context, equipment mod } defer tx.Rollback(ctx) - err = checkTitle(tx, ctx, equipment.Title) + err = checkTitle(tx, ctx, equipment.Title, tableNameEquipments) if err != nil { - logrus.Error(err) return err } query := `UPDATE equipments SET title = $1, image = $2 WHERE id = $3` _, err = r.db.Exec(ctx, query, equipment.Title, equipment.Image, equipment.Id) if err != nil { - logrus.Error(err) return err } diff --git a/internal/repository/muscle_repository.go b/internal/repository/muscle_repository.go index 50a4378..4453fb3 100644 --- a/internal/repository/muscle_repository.go +++ b/internal/repository/muscle_repository.go @@ -1 +1,116 @@ package repository + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/sirupsen/logrus" + "theaesthetics.ru/base/internal/models" +) + +const tableNameMuscles = "muscles" + +type MusclesRepository struct { + db *pgxpool.Pool +} + +func NewMusclesRepository(db *pgxpool.Pool) *MusclesRepository { + return &MusclesRepository{db: db} +} + +func (r *MusclesRepository) GetAllMuscles(ctx context.Context) ([]models.Muscles, error) { + query := `SELECT id, title, image FROM muscles` + rows, err := r.db.Query(ctx, query) + if err != nil { + return nil, err + } + defer rows.Close() + var Muscless []models.Muscles + for rows.Next() { + var Muscles models.Muscles + err := rows.Scan(&Muscles.Id, &Muscles.Title, &Muscles.Image) + if err != nil { + return nil, err + } + Muscless = append(Muscless, Muscles) + } + return Muscless, nil +} + +func (r *MusclesRepository) GetMusclesById(ctx context.Context, id uint8) (*models.Muscles, error) { + query := `SELECT id, title, image FROM muscles WHERE id = $1` + row := r.db.QueryRow(ctx, query, id) + + Muscles := &models.Muscles{} + err := row.Scan(&Muscles.Id, &Muscles.Title, &Muscles.Image) + + if err != nil { + return nil, err + } + + return Muscles, nil +} + +func (r *MusclesRepository) CreateMuscles(ctx context.Context, title, image string) error { + tx, err := r.db.Begin(ctx) + if err != nil { + logrus.Error() + return err + } + defer tx.Rollback(ctx) + + err = checkTitle(tx, ctx, title, tableNameMuscles) + if err != nil { + return err + } + + query := `INSERT INTO muscles (title, image) VALUES ($1, $2)` + _, err = r.db.Exec(ctx, query, title, image) + if err != nil { + return err + } + + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("transaction commit failed: %w", err) + } + + return nil +} + +func (r *MusclesRepository) DeleteMuscles(ctx context.Context, id uint8) error { + query := `DELETE FROM muscles WHERE id = $1` + _, err := r.db.Exec(ctx, query, id) + if err != nil { + return err + } + + return nil +} + +func (r *MusclesRepository) UpdateMuscles(ctx context.Context, Muscles models.Muscles) error { + tx, err := r.db.Begin(ctx) + if err != nil { + logrus.Error() + return err + } + defer tx.Rollback(ctx) + + err = checkTitle(tx, ctx, Muscles.Title, tableNameMuscles) + if err != nil { + return err + } + + _, err = r.db.Exec(ctx, `UPDATE muscles SET title = $1, image = $2 WHERE id = $3`, Muscles.Title, Muscles.Image, Muscles.Id) + if err != nil { + return err + } + + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("transaction commit failed: %w", err) + } + + return nil +} diff --git a/internal/router/router.go b/internal/router/router.go index 7f67342..1664229 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -19,5 +19,15 @@ func InitRouter(e *echo.Echo, db *pgxpool.Pool) { api.POST("/equipment", eqHandler.CreateEqipment) api.GET("/equipment/:id", eqHandler.GetEquipmentById) api.PUT("/equipment/:id", eqHandler.UpdateEqipment) - api.DELETE("/equipment/:id", eqHandler.RemoveEquipment) + api.DELETE("/equipment/:id", eqHandler.DeleteEquipment) + + msRepo := repository.NewMusclesRepository(db) + msService := services.NewMusclesService(msRepo) + msHandler := handlers.NewMusclesHandler(msService) + + api.GET("/muscles", msHandler.GetAllEMuscles) + api.POST("/muscles", msHandler.CreateMuscles) + api.GET("/muscles/:id", msHandler.GetMusclesById) + api.PUT("/muscles/:id", msHandler.UpdateMuscles) + api.DELETE("/muscles/:id", msHandler.DeleteMuscles) } diff --git a/internal/services/exercise_service.go b/internal/services/exercise_service.go index 5e568ea..5dfbc86 100644 --- a/internal/services/exercise_service.go +++ b/internal/services/exercise_service.go @@ -1 +1,36 @@ package services + +import ( + "context" + + "theaesthetics.ru/base/internal/models" + "theaesthetics.ru/base/internal/repository" +) + +type MusclesService struct { + repo *repository.MusclesRepository +} + +func NewMusclesService(repo *repository.MusclesRepository) *MusclesService { + return &MusclesService{repo: repo} +} + +func (s *MusclesService) GetAllMuscless(ctx context.Context) ([]models.Muscles, error) { + return s.repo.GetAllMuscles(ctx) +} + +func (s *MusclesService) GetMusclesById(ctx context.Context, id uint8) (*models.Muscles, error) { + return s.repo.GetMusclesById(ctx, id) +} + +func (s *MusclesService) CreateEqipment(ctx context.Context, title, image string) error { + return s.repo.CreateMuscles(ctx, title, image) +} + +func (s *MusclesService) DeleteMuscles(ctx context.Context, id uint8) error { + return s.repo.DeleteMuscles(ctx, id) +} + +func (s *MusclesService) UpdateMuscles(ctx context.Context, Muscles models.Muscles) error { + return s.repo.UpdateMuscles(ctx, Muscles) +} From c953951acc45019a2f0a7685f995f57321bf6711 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Sun, 23 Mar 2025 11:11:00 +0400 Subject: [PATCH 25/26] feat(trains) add trains --- internal/handlers/errors.go | 7 -- internal/handlers/train_handler.go | 54 +++++++++++++ internal/models/train.go | 28 +++++++ internal/repository/checks.go | 15 ++++ internal/repository/muscle_repository.go | 3 - internal/repository/train_repository.go | 78 +++++++++++++++++++ internal/router/router.go | 7 ++ internal/services/exercise_service.go | 35 --------- internal/services/muscle_service.go | 35 +++++++++ internal/services/train_service.go | 23 ++++++ .../migrations/20250319015139_init_schema.sql | 6 +- 11 files changed, 243 insertions(+), 48 deletions(-) delete mode 100644 internal/handlers/errors.go diff --git a/internal/handlers/errors.go b/internal/handlers/errors.go deleted file mode 100644 index 1cd16cb..0000000 --- a/internal/handlers/errors.go +++ /dev/null @@ -1,7 +0,0 @@ -package handlers - -func returnError(err error) *map[string]string { - return &map[string]string{ - "error": err.Error(), - } -} diff --git a/internal/handlers/train_handler.go b/internal/handlers/train_handler.go index 5ac8282..7f0368d 100644 --- a/internal/handlers/train_handler.go +++ b/internal/handlers/train_handler.go @@ -1 +1,55 @@ package handlers + +import ( + "context" + "net/http" + + "github.com/labstack/echo/v4" + "github.com/sirupsen/logrus" + "theaesthetics.ru/base/internal/models" + "theaesthetics.ru/base/internal/services" +) + +type TrainHandler struct { + service *services.TrainService +} + +func NewTrainHandler(service *services.TrainService) *TrainHandler { + return &TrainHandler{service: service} +} + +func (h *TrainHandler) GetAllTrains(c echo.Context) error { + ctx := context.Background() + trains, err := h.service.GetAllTrains(ctx) + if err != nil { + logrus.WithError(err).Error("Failed to get trains") + return respondWithError(c, http.StatusInternalServerError, err) + } + + return c.JSON(http.StatusOK, trains) +} + +func (h *TrainHandler) CreateTrain(c echo.Context) error { + ctx := context.Background() + + var train models.Train + if err := c.Bind(&train); err != nil { + logrus.WithError(err).Error("Failed to bind request data") + return respondWithError(c, http.StatusBadRequest, err) + } + + if err := h.service.CreateTrain(ctx, train); err != nil { + logrus.WithError(err).Error("Failed to create train") + return respondWithError(c, http.StatusInternalServerError, err) + } + + return c.JSON(http.StatusCreated, map[string]string{ + "message": "Train created successfully", + }) +} + +func respondWithError(c echo.Context, statusCode int, err error) error { + return c.JSON(statusCode, map[string]string{ + "error": err.Error(), + }) +} diff --git a/internal/models/train.go b/internal/models/train.go index 2640e7f..d806117 100644 --- a/internal/models/train.go +++ b/internal/models/train.go @@ -1 +1,29 @@ package models + +type Train struct { + Id uint8 `json:"id"` + + Title string `json:"title"` + Description string `json:"description"` + + Image string `json:"image"` + Video_url string `json:"video_url"` + Difficult uint8 `json:"difficult"` + Duration_time int64 `json:"duration_time"` + + Lead_muscle_id uint8 `json:"lead_muscle_id"` +} + +type TrainWithMuscle struct { + Id uint8 `json:"id"` + + Title string `json:"title,omitempty"` + Description string `json:"description,omitempty"` + + Image string `json:"image,omitempty"` + Video_url string `json:"video_url,omitempty"` + Difficult uint8 `json:"difficult,omitempty"` + Duration_time int64 `json:"duration_time,omitempty"` + + Muscles Muscles `json:"muscles"` +} diff --git a/internal/repository/checks.go b/internal/repository/checks.go index b058571..7455ae9 100644 --- a/internal/repository/checks.go +++ b/internal/repository/checks.go @@ -6,6 +6,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/sirupsen/logrus" + "theaesthetics.ru/base/internal/models" ) // checkTitle проверяет уникальность названия в переданной таблице @@ -26,3 +27,17 @@ func checkTitle(tx pgx.Tx, ctx context.Context, title string, table string) erro return nil } + +func checkMuscles(tx pgx.Tx, ctx context.Context, id uint8) error { + // Безопасно подставляем имя таблицы + query := `SELECT * FROM muscles WHERE id = $1` + logrus.Info("Проверяем нахождение в таблице:", id) + + var muscle models.Muscles + err := tx.QueryRow(ctx, query, id).Scan(&muscle) + if err == pgx.ErrNoRows { + return fmt.Errorf("muscle with id: '%v' not exists", id) + } + + return nil +} diff --git a/internal/repository/muscle_repository.go b/internal/repository/muscle_repository.go index 4453fb3..d41f339 100644 --- a/internal/repository/muscle_repository.go +++ b/internal/repository/muscle_repository.go @@ -5,7 +5,6 @@ import ( "fmt" "github.com/jackc/pgx/v5/pgxpool" - "github.com/sirupsen/logrus" "theaesthetics.ru/base/internal/models" ) @@ -55,7 +54,6 @@ func (r *MusclesRepository) GetMusclesById(ctx context.Context, id uint8) (*mode func (r *MusclesRepository) CreateMuscles(ctx context.Context, title, image string) error { tx, err := r.db.Begin(ctx) if err != nil { - logrus.Error() return err } defer tx.Rollback(ctx) @@ -92,7 +90,6 @@ func (r *MusclesRepository) DeleteMuscles(ctx context.Context, id uint8) error { func (r *MusclesRepository) UpdateMuscles(ctx context.Context, Muscles models.Muscles) error { tx, err := r.db.Begin(ctx) if err != nil { - logrus.Error() return err } defer tx.Rollback(ctx) diff --git a/internal/repository/train_repository.go b/internal/repository/train_repository.go index 50a4378..8c5291f 100644 --- a/internal/repository/train_repository.go +++ b/internal/repository/train_repository.go @@ -1 +1,79 @@ package repository + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" + "theaesthetics.ru/base/internal/models" +) + +const tableNameTrains = "trains" + +type TrainsRepository struct { + db *pgxpool.Pool +} + +func NewTrainsRepository(db *pgxpool.Pool) *TrainsRepository { + return &TrainsRepository{db: db} +} +func (r *TrainsRepository) GetAllTrains(ctx context.Context) ([]models.TrainWithMuscle, error) { + query := ` + SELECT + trains.id, trains.title, description, trains.image, + Video_url, difficult, duration_time, + muscles.id, muscles.title,muscles.image + FROM trains + JOIN muscles ON muscles.id = trains.lead_muscle_id + ` + var trains []models.TrainWithMuscle + + rows, err := r.db.Query(ctx, query) + if err != nil { + return nil, err + } + defer rows.Close() + + for rows.Next() { + var train models.TrainWithMuscle + err := rows.Scan(&train.Id, &train.Title, &train.Description, &train.Image, + &train.Video_url, &train.Difficult, &train.Duration_time, &train.Muscles.Id, + &train.Muscles.Title, &train.Muscles.Image) + if err != nil { + return nil, err + } + + trains = append(trains, train) + } + + return trains, nil +} + +func (r *TrainsRepository) CreateTrain(ctx context.Context, train models.Train) error { + tx, err := r.db.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + if err = checkTitle(tx, ctx, train.Title, tableNameTrains); err != nil { + return err + } + + if err = checkMuscles(tx, ctx, train.Lead_muscle_id); err != nil { + return err + } + + query := `INSERT INTO trains (title, description, image, video_url, difficult, duration_time, lead_muscle_id) VALUES ($1, $2, $3, $4, $5, $6, $7)` + _, err = r.db.Exec(ctx, query, train.Title, train.Description, train.Image, train.Video_url, train.Difficult, train.Duration_time, train.Lead_muscle_id) + if err != nil { + return err + } + + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("transaction commit failed: %w", err) + } + + return nil +} diff --git a/internal/router/router.go b/internal/router/router.go index 1664229..7bb959d 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -30,4 +30,11 @@ func InitRouter(e *echo.Echo, db *pgxpool.Pool) { api.GET("/muscles/:id", msHandler.GetMusclesById) api.PUT("/muscles/:id", msHandler.UpdateMuscles) api.DELETE("/muscles/:id", msHandler.DeleteMuscles) + + trRepo := repository.NewTrainsRepository(db) + trService := services.NewTrainService(trRepo) + trHandler := handlers.NewTrainHandler(trService) + + api.GET("/trains", trHandler.GetAllTrains) + api.POST("/train", trHandler.CreateTrain) } diff --git a/internal/services/exercise_service.go b/internal/services/exercise_service.go index 5dfbc86..5e568ea 100644 --- a/internal/services/exercise_service.go +++ b/internal/services/exercise_service.go @@ -1,36 +1 @@ package services - -import ( - "context" - - "theaesthetics.ru/base/internal/models" - "theaesthetics.ru/base/internal/repository" -) - -type MusclesService struct { - repo *repository.MusclesRepository -} - -func NewMusclesService(repo *repository.MusclesRepository) *MusclesService { - return &MusclesService{repo: repo} -} - -func (s *MusclesService) GetAllMuscless(ctx context.Context) ([]models.Muscles, error) { - return s.repo.GetAllMuscles(ctx) -} - -func (s *MusclesService) GetMusclesById(ctx context.Context, id uint8) (*models.Muscles, error) { - return s.repo.GetMusclesById(ctx, id) -} - -func (s *MusclesService) CreateEqipment(ctx context.Context, title, image string) error { - return s.repo.CreateMuscles(ctx, title, image) -} - -func (s *MusclesService) DeleteMuscles(ctx context.Context, id uint8) error { - return s.repo.DeleteMuscles(ctx, id) -} - -func (s *MusclesService) UpdateMuscles(ctx context.Context, Muscles models.Muscles) error { - return s.repo.UpdateMuscles(ctx, Muscles) -} diff --git a/internal/services/muscle_service.go b/internal/services/muscle_service.go index 5e568ea..5dfbc86 100644 --- a/internal/services/muscle_service.go +++ b/internal/services/muscle_service.go @@ -1 +1,36 @@ package services + +import ( + "context" + + "theaesthetics.ru/base/internal/models" + "theaesthetics.ru/base/internal/repository" +) + +type MusclesService struct { + repo *repository.MusclesRepository +} + +func NewMusclesService(repo *repository.MusclesRepository) *MusclesService { + return &MusclesService{repo: repo} +} + +func (s *MusclesService) GetAllMuscless(ctx context.Context) ([]models.Muscles, error) { + return s.repo.GetAllMuscles(ctx) +} + +func (s *MusclesService) GetMusclesById(ctx context.Context, id uint8) (*models.Muscles, error) { + return s.repo.GetMusclesById(ctx, id) +} + +func (s *MusclesService) CreateEqipment(ctx context.Context, title, image string) error { + return s.repo.CreateMuscles(ctx, title, image) +} + +func (s *MusclesService) DeleteMuscles(ctx context.Context, id uint8) error { + return s.repo.DeleteMuscles(ctx, id) +} + +func (s *MusclesService) UpdateMuscles(ctx context.Context, Muscles models.Muscles) error { + return s.repo.UpdateMuscles(ctx, Muscles) +} diff --git a/internal/services/train_service.go b/internal/services/train_service.go index 5e568ea..66e7775 100644 --- a/internal/services/train_service.go +++ b/internal/services/train_service.go @@ -1 +1,24 @@ package services + +import ( + "context" + + "theaesthetics.ru/base/internal/models" + "theaesthetics.ru/base/internal/repository" +) + +type TrainService struct { + repo *repository.TrainsRepository +} + +func NewTrainService(repo *repository.TrainsRepository) *TrainService { + return &TrainService{repo: repo} +} + +func (s *TrainService) CreateTrain(ctx context.Context, train models.Train) error { + return s.repo.CreateTrain(ctx, train) +} + +func (s *TrainService) GetAllTrains(ctx context.Context) ([]models.TrainWithMuscle, error) { + return s.repo.GetAllTrains(ctx) +} diff --git a/storage/migrations/20250319015139_init_schema.sql b/storage/migrations/20250319015139_init_schema.sql index 5262bdb..e14517b 100644 --- a/storage/migrations/20250319015139_init_schema.sql +++ b/storage/migrations/20250319015139_init_schema.sql @@ -27,7 +27,7 @@ CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS achievements ( id SERIAL PRIMARY KEY, title VARCHAR UNIQUE NOT NULL, - description VARCHAR UNIQUE NOT NULL, + description VARCHAR NOT NULL, image VARCHAR NOT NULL ); -- +goose StatementEnd @@ -44,7 +44,7 @@ CREATE TABLE IF NOT EXISTS users_achievements ( CREATE TABLE IF NOT EXISTS muscles ( id SERIAL PRIMARY KEY, title VARCHAR UNIQUE NOT NULL, - image VARCHAR UNIQUE NOT NULL + image VARCHAR NOT NULL ); -- +goose StatementEnd @@ -79,7 +79,7 @@ CREATE TABLE IF NOT EXISTS trains ( image VARCHAR, video_url VARCHAR, difficult INT, - duration_train INT, + duration_time INT, lead_muscle_id INT NOT NULL ); -- +goose StatementEnd From 3aa8268943feca2a8b66202d77deaeb0f8d10948 Mon Sep 17 00:00:00 2001 From: Ruslan Lukoyanov Date: Sun, 23 Mar 2025 11:25:28 +0400 Subject: [PATCH 26/26] feat(trains) add get by id --- internal/handlers/train_handler.go | 19 +++++++++++++++++++ internal/repository/train_repository.go | 24 ++++++++++++++++++++++++ internal/router/router.go | 1 + internal/services/train_service.go | 4 ++++ 4 files changed, 48 insertions(+) diff --git a/internal/handlers/train_handler.go b/internal/handlers/train_handler.go index 7f0368d..a7f6ff0 100644 --- a/internal/handlers/train_handler.go +++ b/internal/handlers/train_handler.go @@ -3,6 +3,7 @@ package handlers import ( "context" "net/http" + "strconv" "github.com/labstack/echo/v4" "github.com/sirupsen/logrus" @@ -29,6 +30,24 @@ func (h *TrainHandler) GetAllTrains(c echo.Context) error { return c.JSON(http.StatusOK, trains) } +func (h *TrainHandler) GetTrainById(c echo.Context) error { + id, err := strconv.Atoi(c.Param("id")) + if err != nil { + logrus.WithError(err).Error("Failed to get trains") + return respondWithError(c, http.StatusBadRequest, err) + } + uid := uint8(id) + + ctx := context.Background() + trains, err := h.service.GetTrainById(ctx, uid) + if err != nil { + logrus.WithError(err).Error("Failed to get trains") + return respondWithError(c, http.StatusInternalServerError, err) + } + + return c.JSON(http.StatusOK, trains) +} + func (h *TrainHandler) CreateTrain(c echo.Context) error { ctx := context.Background() diff --git a/internal/repository/train_repository.go b/internal/repository/train_repository.go index 8c5291f..83a583b 100644 --- a/internal/repository/train_repository.go +++ b/internal/repository/train_repository.go @@ -49,6 +49,30 @@ func (r *TrainsRepository) GetAllTrains(ctx context.Context) ([]models.TrainWith return trains, nil } +func (r *TrainsRepository) GetTrainById(ctx context.Context, id uint8) (models.TrainWithMuscle, error) { + query := ` + SELECT + trains.id, trains.title, description, trains.image, + Video_url, difficult, duration_time, + muscles.id, muscles.title,muscles.image + FROM trains + JOIN muscles ON muscles.id = trains.lead_muscle_id + WHERE trains.id = $1 + ` + var train models.TrainWithMuscle + + row := r.db.QueryRow(ctx, query, id) + err := row.Scan(&train.Id, &train.Title, &train.Description, &train.Image, + &train.Video_url, &train.Difficult, &train.Duration_time, &train.Muscles.Id, + &train.Muscles.Title, &train.Muscles.Image) + + if err != nil { + return models.TrainWithMuscle{}, err + } + + return train, nil +} + func (r *TrainsRepository) CreateTrain(ctx context.Context, train models.Train) error { tx, err := r.db.Begin(ctx) if err != nil { diff --git a/internal/router/router.go b/internal/router/router.go index 7bb959d..795af8d 100644 --- a/internal/router/router.go +++ b/internal/router/router.go @@ -37,4 +37,5 @@ func InitRouter(e *echo.Echo, db *pgxpool.Pool) { api.GET("/trains", trHandler.GetAllTrains) api.POST("/train", trHandler.CreateTrain) + api.GET("/trains/:id", trHandler.GetTrainById) } diff --git a/internal/services/train_service.go b/internal/services/train_service.go index 66e7775..ed31979 100644 --- a/internal/services/train_service.go +++ b/internal/services/train_service.go @@ -22,3 +22,7 @@ func (s *TrainService) CreateTrain(ctx context.Context, train models.Train) erro func (s *TrainService) GetAllTrains(ctx context.Context) ([]models.TrainWithMuscle, error) { return s.repo.GetAllTrains(ctx) } + +func (s *TrainService) GetTrainById(ctx context.Context, id uint8) (models.TrainWithMuscle, error) { + return s.repo.GetTrainById(ctx, id) +}