diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8ac2d54 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +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/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a20c5fe..2d4c936 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/app + run: | + pwd + ls -la + CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o main ./cmd/base + # Подключение к серверу по SSH и деплой - name: Deploy to Server @@ -31,10 +35,10 @@ 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 + rm -f /var/www/myapp/main/main exit || true - name: Upload binary uses: appleboy/scp-action@v0.1.4 @@ -42,7 +46,7 @@ jobs: 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 @@ -53,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 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/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 diff --git a/cmd/base/main.go b/cmd/base/main.go new file mode 100644 index 0000000..abb1219 --- /dev/null +++ b/cmd/base/main.go @@ -0,0 +1,51 @@ +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" + "theaesthetics.ru/base/internal/router" + "theaesthetics.ru/base/internal/storage" +) + +func main() { + ctx := context.Background() + cfg, err := config.NewConfig() + if err != nil { + panic(err.Error()) + } + + 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", + })) + + router.InitRouter(e, pool) + + 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() +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..0edd38e --- /dev/null +++ b/config/config.go @@ -0,0 +1,22 @@ +package config + +import ( + "github.com/caarlos0/env" +) + +type Config struct { + 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 := env.Parse(&cfg); err != nil { + return nil, err + } + + return &cfg, nil +} diff --git a/go.mod b/go.mod index f922980..7827406 100644 --- a/go.mod +++ b/go.mod @@ -1,35 +1,28 @@ -module theAesthetics.ru/base +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/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/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/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/valyala/bytebufferpool v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.2 // indirect golang.org/x/crypto v0.36.0 // indirect - golang.org/x/net v0.37.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 - google.golang.org/protobuf v1.36.5 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect + golang.org/x/time v0.8.0 // indirect ) diff --git a/go.sum b/go.sum index abb76fc..e49199b 100644 --- a/go.sum +++ b/go.sum @@ -1,82 +1,56 @@ -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/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/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/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/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 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= 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/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= 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/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= 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= -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= +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= 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/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..edfb261 --- /dev/null +++ b/internal/handlers/equipment_handler.go @@ -0,0 +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 EquipmentHandler struct { + service *services.EquipmentService +} + +func NewEquipmentHandler(service *services.EquipmentService) *EquipmentHandler { + return &EquipmentHandler{service: service} +} + +func (h *EquipmentHandler) GetAllEquipments(c echo.Context) error { + ctx := c.Request().Context() + equipments, err := h.service.GetAllEquipments(ctx) + if err != nil { + logrus.Error(err) + + return c.JSON(http.StatusInternalServerError, map[string]string{ + "error": err.Error(), + }) + } + + return c.JSON(http.StatusOK, equipments) +} + +func (h *EquipmentHandler) GetEquipmentById(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) + 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) +} + +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) DeleteEquipment(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.DeleteEquipment(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/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..e0fc877 --- /dev/null +++ b/internal/handlers/muscle_handler.go @@ -0,0 +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/handlers/train_handler.go b/internal/handlers/train_handler.go new file mode 100644 index 0000000..a7f6ff0 --- /dev/null +++ b/internal/handlers/train_handler.go @@ -0,0 +1,74 @@ +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 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) 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() + + 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/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/logger/logger.go b/internal/logger/logger.go new file mode 100644 index 0000000..3f5bee6 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,25 @@ +package logger + +import ( + "fmt" + "runtime" + + "github.com/sirupsen/logrus" +) + +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(&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/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..dc06b75 --- /dev/null +++ b/internal/models/equipment.go @@ -0,0 +1,7 @@ +package models + +type Equipment struct { + Id uint8 `json:"id"` + Title string `json:"title"` + Image string `json:"image"` +} 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..5a5be9e --- /dev/null +++ b/internal/models/muscle.go @@ -0,0 +1,7 @@ +package models + +type Muscles struct { + Id uint8 `json:"id"` + Title string `json:"title"` + Image string `json:"image"` +} diff --git a/internal/models/train.go b/internal/models/train.go new file mode 100644 index 0000000..d806117 --- /dev/null +++ b/internal/models/train.go @@ -0,0 +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/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/checks.go b/internal/repository/checks.go new file mode 100644 index 0000000..7455ae9 --- /dev/null +++ b/internal/repository/checks.go @@ -0,0 +1,43 @@ +package repository + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5" + "github.com/sirupsen/logrus" + "theaesthetics.ru/base/internal/models" +) + +// 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 + logrus.Infof("Проверяем уникальность в таблице: %s", table) + + err := tx.QueryRow(ctx, query, title).Scan(&id) + if err == nil { + return fmt.Errorf("'%s' with title '%s' already exists", table, title) + } + + if err != pgx.ErrNoRows { + return err + } + + 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/equipment_repository.go b/internal/repository/equipment_repository.go new file mode 100644 index 0000000..802c758 --- /dev/null +++ b/internal/repository/equipment_repository.go @@ -0,0 +1,116 @@ +package repository + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/sirupsen/logrus" + "theaesthetics.ru/base/internal/models" +) + +const tableNameEquipments = "equipments" + +type EquipmentRepository struct { + db *pgxpool.Pool +} + +func NewEquipmentRepository(db *pgxpool.Pool) *EquipmentRepository { + return &EquipmentRepository{db: db} +} + +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 { + 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 +} + +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 { + return nil, err + } + + return equipment, nil +} + +func (r *EquipmentRepository) CreateEqipment(ctx context.Context, title, image string) error { + tx, err := r.db.Begin(ctx) + if err != nil { + return err + } + defer tx.Rollback(ctx) + + err = checkTitle(tx, ctx, title, tableNameEquipments) + if err != nil { + return err + } + + query := `INSERT INTO equipments (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 *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 { + return err + } + + return nil +} + +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, tableNameEquipments) + if err != nil { + 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 { + return err + } + + err = tx.Commit(ctx) + if err != nil { + return fmt.Errorf("transaction commit failed: %w", err) + } + + return nil +} 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..d41f339 --- /dev/null +++ b/internal/repository/muscle_repository.go @@ -0,0 +1,113 @@ +package repository + +import ( + "context" + "fmt" + + "github.com/jackc/pgx/v5/pgxpool" + "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 { + 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 { + 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/repository/train_repository.go b/internal/repository/train_repository.go new file mode 100644 index 0000000..83a583b --- /dev/null +++ b/internal/repository/train_repository.go @@ -0,0 +1,103 @@ +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) 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 { + 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/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 diff --git a/internal/router/router.go b/internal/router/router.go new file mode 100644 index 0000000..795af8d --- /dev/null +++ b/internal/router/router.go @@ -0,0 +1,41 @@ +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.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) + + trRepo := repository.NewTrainsRepository(db) + trService := services.NewTrainService(trRepo) + trHandler := handlers.NewTrainHandler(trService) + + api.GET("/trains", trHandler.GetAllTrains) + api.POST("/train", trHandler.CreateTrain) + api.GET("/trains/:id", trHandler.GetTrainById) +} 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..7fcc1f2 --- /dev/null +++ b/internal/services/equipment_service.go @@ -0,0 +1,36 @@ +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) 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) +} + +func (s *EquipmentService) CreateEqipment(ctx context.Context, title, image string) error { + return s.repo.CreateEqipment(ctx, title, image) +} + +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 { + return s.repo.UpdateEquipment(ctx, equipment) +} 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..5dfbc86 --- /dev/null +++ b/internal/services/muscle_service.go @@ -0,0 +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 new file mode 100644 index 0000000..ed31979 --- /dev/null +++ b/internal/services/train_service.go @@ -0,0 +1,28 @@ +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) +} + +func (s *TrainService) GetTrainById(ctx context.Context, id uint8) (models.TrainWithMuscle, error) { + return s.repo.GetTrainById(ctx, id) +} 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 diff --git a/internal/storage/storage.go b/internal/storage/storage.go new file mode 100644 index 0000000..1405a31 --- /dev/null +++ b/internal/storage/storage.go @@ -0,0 +1,27 @@ +package storage + +import ( + "context" + + "github.com/jackc/pgx/v5/pgxpool" + "github.com/sirupsen/logrus" + "theaesthetics.ru/base/config" +) + +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.Errorf("ошибка подключения к базе данных: %s", err.Error()) + return nil + } + + err = pool.Ping(ctx) + + if err != nil { + log.Errorf("ошибка подключения к базе данных: %s", err.Error()) + return nil + } + + return pool +} diff --git a/storage/migrations/20250319015139_init_schema.sql b/storage/migrations/20250319015139_init_schema.sql new file mode 100644 index 0000000..e14517b --- /dev/null +++ b/storage/migrations/20250319015139_init_schema.sql @@ -0,0 +1,151 @@ +-- +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 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 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 +); +-- +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_time 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 equipments; +-- +goose StatementEnd + +-- +goose StatementBegin +DROP TABLE IF EXISTS exercises; +-- +goose StatementEnd + +-- +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 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