diff --git a/.example.env b/.example.env deleted file mode 100644 index 775f70b..0000000 --- a/.example.env +++ /dev/null @@ -1 +0,0 @@ -DB_PASSWORD=password \ No newline at end of file diff --git a/.gitignore b/.gitignore index 3bf780b..3c7c7fb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .idea +.storage .env \ No newline at end of file diff --git a/.storage/.gitignore b/.storage/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/.storage/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 104a154..0000000 --- a/README.md +++ /dev/null @@ -1,31 +0,0 @@ -# pink_fox - -## Развертывание - -Скопировать `cp environment/example.config.yml environment/config.yml` и поправить пароль. - -Скопировать `cp .example.env .env` и поправить пароль. - -## Библиотеки - -- [Веб framework](https://github.com/gin-gonic/gin) -- [Framework консольных приложений](https://github.com/spf13/cobra) -- [Шаблонизатор](https://github.com/CloudyKit/jet) - -## Планы - -- модели -- кастомизация страницы html ошибок -- добавить api -- реализовать миграции -- горячая перезагрузка конфига -- валидация -- добавить образцы middleware -- модуль авторизации -- загрузку assets, таких как js, css, jpg, png на страницы html -- /robot.txt -- настройка timeout через конфиг -- транзакции -- timeout для базы данных -- запуск задач по cron -- заполнение базы данных тестовыми значениями консольной командой diff --git a/pink_fox_app/.gitignore b/application/.gitignore similarity index 57% rename from pink_fox_app/.gitignore rename to application/.gitignore index d2cfe19..09f5f58 100644 --- a/pink_fox_app/.gitignore +++ b/application/.gitignore @@ -1,5 +1,5 @@ -out.log -err.log -pink_fox dlv.log - +err.log +local_app +out.log +pink_fox \ No newline at end of file diff --git a/application/README.md b/application/README.md new file mode 100644 index 0000000..5416643 --- /dev/null +++ b/application/README.md @@ -0,0 +1,15 @@ +[Best Practices of Building Web Apps with Gin & Golang](https://www.squash.io/optimizing-gin-in-golang-project-structuring-error-handling-and-testing/) + +Папки + +- cmd +- internal +- pkg + +# План + +- создать консольную команду для создания пользователя + - она должна создать нового пользователя, так же к этой команде нужно добавить di container + - нужно сделать отдельную команду `exec` которая и будет запускать пользовательские команды с di контейнером, но есть вопросы + +Пока делаем endpoint для этого \ No newline at end of file diff --git a/application/config-test.yml b/application/config-test.yml new file mode 100644 index 0000000..fbf92b8 --- /dev/null +++ b/application/config-test.yml @@ -0,0 +1,6 @@ +host: postgres_test +user: pink_fox +password: pink_fox_pass +port: 5432 +database: pink_fox_db_test +migrations: /app/database/migrations \ No newline at end of file diff --git a/application/config.yml b/application/config.yml new file mode 100644 index 0000000..af23f41 --- /dev/null +++ b/application/config.yml @@ -0,0 +1,12 @@ +debug: true + +db: + host: postgres + user: pink_fox + password: pink_fox_pass + port: 5432 + database: pink_fox_db + migrations: /app/database/migrations + +# Путь к файлу лога ошибок +#logFile: /app/logs/errors.log diff --git a/application/database/migrations/20250420230013_create_users.sql b/application/database/migrations/20250420230013_create_users.sql new file mode 100644 index 0000000..96f12ea --- /dev/null +++ b/application/database/migrations/20250420230013_create_users.sql @@ -0,0 +1,21 @@ +-- +goose Up +-- +goose StatementBegin +DROP TABLE IF EXISTS users; + +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + email VARCHAR(128) NOT NULL, + password VARCHAR(128) NOT NULL, + token VARCHAR(32) NOT NULL, + email_confirmed BOOLEAN NOT NULL, + created_at timestamptz NOT NULL, + updated_at timestamptz NOT NULL +); + +CREATE UNIQUE INDEX idx_users_email ON users (email); +-- +goose StatementEnd + +-- +goose Down +-- +goose StatementBegin +DROP TABLE IF EXISTS users; +-- +goose StatementEnd diff --git a/application/database/seeders/users.sql b/application/database/seeders/users.sql new file mode 100644 index 0000000..137061a --- /dev/null +++ b/application/database/seeders/users.sql @@ -0,0 +1,5 @@ +TRUNCATE TABLE users RESTART IDENTITY CASCADE; + +INSERT INTO users (email, password, token, email_confirmed, created_at, updated_at) +VALUES + ('admin@admin.ru', 'password', 'token', true, '2000-10-15 10:00:00 +04:00', '2000-10-15 10:00:00 +04:00'); \ No newline at end of file diff --git a/pink_fox_app/go.mod b/application/go.mod similarity index 57% rename from pink_fox_app/go.mod rename to application/go.mod index fb51b26..20486a6 100644 --- a/pink_fox_app/go.mod +++ b/application/go.mod @@ -2,19 +2,26 @@ module pink_fox go 1.24.1 +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/spf13/cobra v1.9.1 + github.com/stretchr/testify v1.10.0 +) + require ( github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect github.com/CloudyKit/jet/v6 v6.3.1 // indirect - github.com/bytedance/sonic v1.13.1 // indirect + github.com/bytedance/sonic v1.13.2 // 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/davecgh/go-spew v1.1.1 // 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-chi/chi/v5 v5.2.1 // 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/go-playground/validator/v10 v10.26.0 // indirect github.com/goccy/go-json v0.10.5 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -22,18 +29,23 @@ require ( 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/mfridman/interpolate v0.0.2 // 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/spf13/cobra v1.9.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pressly/goose/v3 v3.24.2 // indirect + github.com/sethvargo/go-retry v0.3.0 // indirect github.com/spf13/pflag v1.0.6 // 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 - 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/text v0.23.0 // indirect - google.golang.org/protobuf v1.36.5 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/arch v0.16.0 // indirect + golang.org/x/crypto v0.37.0 // indirect + golang.org/x/net v0.39.0 // indirect + golang.org/x/sync v0.13.0 // indirect + golang.org/x/sys v0.32.0 // indirect + golang.org/x/text v0.24.0 // indirect + google.golang.org/protobuf v1.36.6 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/pink_fox_app/go.sum b/application/go.sum similarity index 71% rename from pink_fox_app/go.sum rename to application/go.sum index f7fa444..6de402e 100644 --- a/pink_fox_app/go.sum +++ b/application/go.sum @@ -2,8 +2,8 @@ github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4s github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno= github.com/CloudyKit/jet/v6 v6.3.1 h1:6IAo5Cx21xrHVaR8zzXN5gJatKV/wO7Nf6bfCnCSbUw= github.com/CloudyKit/jet/v6 v6.3.1/go.mod h1:lf8ksdNsxZt7/yH/3n4vJQWA9RUq4wpaHtArHhGVMOw= -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 v1.13.2 h1:8/H1FempDZqC4VqjptGo14QQlJx8VdZJegxs6wwfqpQ= +github.com/bytedance/sonic v1.13.2/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= @@ -13,6 +13,7 @@ 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/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= 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= @@ -20,12 +21,14 @@ 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-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8= +github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= 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/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= 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= @@ -43,15 +46,22 @@ 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/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= +github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= 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/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +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/v3 v3.24.2 h1:c/ie0Gm8rnIVKvnDQ/scHErv46jrDv9b4I0WRcFJzYU= +github.com/pressly/goose/v3 v3.24.2/go.mod h1:kjefwFB0eR4w30Td2Gj2Mznyw94vSP+2jJYkOVNbD1k= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +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/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= @@ -66,24 +76,29 @@ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ 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= -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= +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/arch v0.16.0 h1:foMtLTdyOmIniqWCHjY6+JxuC54XP1fDwx4N0ASyW+U= +golang.org/x/arch v0.16.0/go.mod h1:JmwW7aLIoRUKgaTzhkiEFxvcEiQGyOg9BMonBJUS7EE= +golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= +golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= +golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= +golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= +golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= 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/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= +golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= +golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= +google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= 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/application/inner/commands/create_new_user_command.go b/application/inner/commands/create_new_user_command.go new file mode 100644 index 0000000..903146a --- /dev/null +++ b/application/inner/commands/create_new_user_command.go @@ -0,0 +1,23 @@ +package commands + +import ( + "pink_fox/inner/repositories" + "pink_fox/packages/fw" +) + +type CreateUserCommand struct { + usersRepository repositories.UserRepository +} + +// TODO нужно ли мне создавать access, я думаю пока нет, но вообще надо + +func NewCreateUserCommand(usersRepository repositories.UserRepository) *CreateUserCommand { + return &CreateUserCommand{ + usersRepository: usersRepository, + } +} + +func (it *CreateUserCommand) Exec(email, password string, confirmEmail bool) fw.Error { + + return nil +} diff --git a/application/inner/commands/user_command.go b/application/inner/commands/user_command.go new file mode 100644 index 0000000..9d2815c --- /dev/null +++ b/application/inner/commands/user_command.go @@ -0,0 +1,20 @@ +package commands + +import "pink_fox/packages/fw" + +type UserCommand struct { +} + +func NewUserCommand() *UserCommand { + return &UserCommand{} +} + +func (it *UserCommand) CheckAndFind(id int) (func(int) ([]string, fw.Error), fw.Error) { + // тут мы получаем пользователя или ошибку + // получает сущность по id + return it.getUsersList, nil +} + +func (it *UserCommand) getUsersList(page int) ([]string, fw.Error) { + return nil, nil +} diff --git a/application/inner/config/config.org.go b/application/inner/config/config.org.go new file mode 100644 index 0000000..b7aca2f --- /dev/null +++ b/application/inner/config/config.org.go @@ -0,0 +1,71 @@ +package config + +import ( + "gopkg.in/yaml.v3" + "os" + "pink_fox/packages/fw" +) + +type Config struct { + Debug bool `yaml:"debug"` + LogFile string `yaml:"logFile"` + DB DB `yaml:"db"` +} + +type DB struct { + Host string `yaml:"host"` + User string `yaml:"user"` + Password string `yaml:"password"` + Port string `yaml:"port"` + Database string `yaml:"database"` + Migrations string `yaml:"migrations"` +} + +func LoadConfig(path string) (*Config, fw.Error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fw.Err(err) + } + var conf Config + err = yaml.Unmarshal(data, &conf) + if err != nil { + return nil, fw.Err(err) + } + + return setDefaultValues(&conf), nil +} + +func LoadTestConfig(path string) (*DB, fw.Error) { + data, err := os.ReadFile(path) + if err != nil { + return nil, fw.Err(err) + } + var db DB + err = yaml.Unmarshal(data, &db) + if err != nil { + return nil, fw.Err(err) + } + return &db, nil +} + +func setDefaultValues(conf *Config) *Config { + if conf.LogFile == "" { + conf.LogFile = "/app/logs/errors.log" + } + return conf +} + +func GetDatabaseConfig(path string) (*fw.DatabaseConfig, fw.Error) { + conf, err := LoadConfig(path) + if err != nil { + return nil, err.Tap() + } + return &fw.DatabaseConfig{ + Host: conf.DB.Host, + User: conf.DB.User, + Password: conf.DB.Password, + Port: conf.DB.Port, + Database: conf.DB.Database, + Migrations: conf.DB.Migrations, + }, nil +} diff --git a/application/inner/controllers/http_error_controller.go b/application/inner/controllers/http_error_controller.go new file mode 100644 index 0000000..46fda2a --- /dev/null +++ b/application/inner/controllers/http_error_controller.go @@ -0,0 +1,29 @@ +package controllers + +import "pink_fox/packages/fw" + +type HttpErrorController struct { + app HttpErrorControllerServices +} + +type HttpErrorControllerServices interface { + fw.BaseServices +} + +func NewHttpErrorController(services HttpErrorControllerServices) *HttpErrorController { + return &HttpErrorController{ + app: services, + } +} + +func (it *HttpErrorController) Page404() (fw.Response, fw.Error) { + return it.app.ResponseFactory().HtmlError(404, "general"), nil +} + +func (it *HttpErrorController) Api404() (fw.Response, fw.Error) { + return it.app.ResponseFactory().HtmlError(404, "api"), nil +} + +func (it *HttpErrorController) Back404() (fw.Response, fw.Error) { + return it.app.ResponseFactory().HtmlError(404, "back"), nil +} diff --git a/application/inner/controllers/ping_controller.go b/application/inner/controllers/ping_controller.go new file mode 100644 index 0000000..6d532a8 --- /dev/null +++ b/application/inner/controllers/ping_controller.go @@ -0,0 +1,21 @@ +package controllers + +import "pink_fox/packages/fw" + +type PingController struct { + services PingControllerService +} + +type PingControllerService interface { + ResponseFactory() *fw.ResponseFactory +} + +func NewPingController(services PingControllerService) *PingController { + return &PingController{ + services: services, + } +} + +func (it *PingController) Index() (fw.Response, fw.Error) { + return it.services.ResponseFactory().String("ok"), nil +} diff --git a/application/inner/controllers/site_controller.go b/application/inner/controllers/site_controller.go new file mode 100644 index 0000000..09ac469 --- /dev/null +++ b/application/inner/controllers/site_controller.go @@ -0,0 +1,48 @@ +package controllers + +import ( + "pink_fox/inner/commands" + "pink_fox/packages/fw" + "strings" +) + +type SiteController struct { + services SiteControllerService +} + +type SiteControllerService interface { + fw.BaseServices +} + +func NewSiteController(services SiteControllerService) *SiteController { + return &SiteController{ + services: services, + } +} + +func (it *SiteController) Index() (fw.Response, fw.Error) { + //return it.services.ResponseFactory().HtmlError(404, "test"), nil + //return nil, fw.ErrStr("site controller not yet implemented") + + return it.services.ResponseFactory().View("index.html", map[string]any{ + "text": "Hello world", + }), nil +} + +type TestActionServices interface { + UserCommand() *commands.UserCommand +} + +func (it *SiteController) Test(services TestActionServices) (fw.Response, fw.Error) { + getUserList, err := services.UserCommand().CheckAndFind(100) + if err != nil { + return nil, err.Tap() + } + + list, err := getUserList(1000) + if err != nil { + return nil, err.Tap() + } + + return it.services.ResponseFactory().String(strings.Join(list, ", ")), nil +} diff --git a/application/inner/di/di.go b/application/inner/di/di.go new file mode 100644 index 0000000..b61f445 --- /dev/null +++ b/application/inner/di/di.go @@ -0,0 +1,43 @@ +package di + +import ( + "net/http" + "pink_fox/inner/storage" + "pink_fox/inner/view" + "pink_fox/packages/fw" +) + +type Container struct { + storage *storage.Storage + writer http.ResponseWriter + request *http.Request +} + +func MakeContainer(storage *storage.Storage, writer http.ResponseWriter, request *http.Request) *Container { + return &Container{ + storage: storage, + writer: writer, + request: request, + } +} + +func CloseContainer(_ *Container) { +} + +func (it *Container) Debug() bool { + return it.storage.Debug +} + +func (it *Container) Logger() fw.Logger { + return nil // FIXME +} + +func (it *Container) ResponseFactory() *fw.ResponseFactory { + return fw.NewResponseFactory(it.writer, it.storage.Template, func() any { + return view.NewView() + }) +} + +func (it *Container) GetWriter() http.ResponseWriter { + return it.writer +} diff --git a/application/inner/middlewares/ErrorMiddleware.go b/application/inner/middlewares/ErrorMiddleware.go new file mode 100644 index 0000000..4f8012a --- /dev/null +++ b/application/inner/middlewares/ErrorMiddleware.go @@ -0,0 +1,56 @@ +package middlewares + +import ( + "fmt" + "net/http" + "pink_fox/packages/fw" + "runtime/debug" +) + +type ErrorMiddlewareServices interface { + Debug() bool + Logger() fw.Logger + ResponseFactory() *fw.ResponseFactory +} + +func ErrorMiddleware(services ErrorMiddlewareServices, next fw.ActionFunc) (response fw.Response, err fw.Error) { + defer func() { + if err_ := recover(); err_ != nil { + err = fw.ErrPanic(fmt.Errorf("panic: %v", err_), debug.Stack()) + err = errorsHandler(services.ResponseFactory(), err, services.Debug(), services.Logger()) + response = nil + } + }() + + response, err = next() + if err != nil { + err = errorsHandler(services.ResponseFactory(), err, services.Debug(), services.Logger()) + return nil, err // ошибку отобразили + } + + return nil, nil +} + +func errorsHandler(responseFactory *fw.ResponseFactory, errIn fw.Error, debugOn bool, logger fw.Logger) fw.Error { + if logger != nil { + logger.Error(errIn.Message(), errIn.Trace()) + } + + textError := "" + if debugOn { + textError = errIn.Error() + } + + response := responseFactory.View("errors/500.html", map[string]any{ + "title": fmt.Sprintf("%d %s", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)), + "message": "We have a problem", + "error": textError, + }) + response.WriteHeader(http.StatusInternalServerError) + err := response.Render() + if err != nil { + return err.Add("ошибка при создании страницы ошибки") + } + + return nil +} diff --git a/application/inner/middlewares/ResponseMiddlware.go b/application/inner/middlewares/ResponseMiddlware.go new file mode 100644 index 0000000..c00989c --- /dev/null +++ b/application/inner/middlewares/ResponseMiddlware.go @@ -0,0 +1,18 @@ +package middlewares + +import "pink_fox/packages/fw" + +func ResponseMiddleware(next fw.ActionFunc) (fw.Response, fw.Error) { + response, err := next() + if err != nil { + return nil, err + } + + // нет ошибок, пробуем отобразить результат запроса + err = response.Render() + if err != nil { + return nil, err.Tap() + } + + return nil, nil +} diff --git a/application/inner/registration_routes.go b/application/inner/registration_routes.go new file mode 100644 index 0000000..2656be2 --- /dev/null +++ b/application/inner/registration_routes.go @@ -0,0 +1,57 @@ +package inner + +import ( + "pink_fox/inner/controllers" + "pink_fox/inner/di" + "pink_fox/inner/middlewares" + "pink_fox/inner/storage" + "pink_fox/packages/fw" +) + +func RegistrationRoutes(rm *fw.RouterManager[*storage.Storage, *di.Container]) { + rm.Use(errorMiddleware, responseMiddleware) + rm.NotFound(noRoute2) + + rm.Get("/ping", ping) + rm.Get("/", siteIndex) + + api := rm.Group("/api") + { + api.NotFound(noRouteApi) + api.Get("/ping", ping) + + sub := api.Group("/sub") + { + sub.NotFound(noRouteBack) + sub.Get("/ping", ping) + } + } +} + +func siteIndex(di *di.Container) (fw.Response, fw.Error) { + return controllers.NewSiteController(di).Index() +} + +func ping(di *di.Container) (fw.Response, fw.Error) { + return controllers.NewPingController(di).Index() +} + +func errorMiddleware(di *di.Container, next fw.ActionFunc) (fw.Response, fw.Error) { + return middlewares.ErrorMiddleware(di, next) +} + +func responseMiddleware(_ *di.Container, next fw.ActionFunc) (fw.Response, fw.Error) { + return middlewares.ResponseMiddleware(next) +} + +func noRoute2(di *di.Container) (fw.Response, fw.Error) { + return controllers.NewHttpErrorController(di).Page404() +} + +func noRouteApi(di *di.Container) (fw.Response, fw.Error) { + return controllers.NewHttpErrorController(di).Api404() +} + +func noRouteBack(di *di.Container) (fw.Response, fw.Error) { + return controllers.NewHttpErrorController(di).Back404() +} diff --git a/application/inner/repositories/pg/users_repository.go b/application/inner/repositories/pg/users_repository.go new file mode 100644 index 0000000..b99652f --- /dev/null +++ b/application/inner/repositories/pg/users_repository.go @@ -0,0 +1,31 @@ +package pg + +import ( + "context" + "database/sql" + "pink_fox/inner/repositories" + "pink_fox/packages/fw" +) + +type UsersRepository struct { + ctx context.Context + db *sql.DB +} + +func NewUsersRepository(context context.Context, db *sql.DB) *UsersRepository { + return &UsersRepository{ + ctx: context, + db: db, + } +} + +// FIXME делаем запись в базу данных +// FIXME делаю тест этой функции + +func (it *UsersRepository) CreateNewUser(email, password string, emailConfirm bool) (id int64, err fw.Error) { + return 0, nil +} + +func (it *UsersRepository) GetByID(id int64) (user *repositories.User, ok bool, err fw.Error) { + return nil, false, nil +} diff --git a/application/inner/repositories/pg/users_repository_test.go b/application/inner/repositories/pg/users_repository_test.go new file mode 100644 index 0000000..f19a0ee --- /dev/null +++ b/application/inner/repositories/pg/users_repository_test.go @@ -0,0 +1,15 @@ +package pg + +import ( + "context" + "pink_fox/inner/test" + "testing" +) + +func TestGetByID(t *testing.T) { + tool := test.NewDBTest().Seed("users.sql") + ctx := context.Background() + + _ = NewUsersRepository(ctx, tool.GetDB()) + // FIXME +} diff --git a/application/inner/repositories/repositories.go b/application/inner/repositories/repositories.go new file mode 100644 index 0000000..c63f211 --- /dev/null +++ b/application/inner/repositories/repositories.go @@ -0,0 +1,21 @@ +package repositories + +import ( + "pink_fox/packages/fw" + "time" +) + +type User struct { + ID int64 + Email string + Password string + Token string + EmailConfirmed bool + CreatedAt time.Time + UpdatedAt time.Time +} + +type UserRepository interface { + CreateNewUser(email, password string, emailConfirm bool) (id int64, err fw.Error) + GetByID(id int64) (user *User, ok bool, err fw.Error) +} diff --git a/application/inner/storage/storage.go b/application/inner/storage/storage.go new file mode 100644 index 0000000..3175ccd --- /dev/null +++ b/application/inner/storage/storage.go @@ -0,0 +1,27 @@ +package storage + +import ( + "pink_fox/inner/config" + "pink_fox/packages/fw" +) + +type Storage struct { + Config *config.Config + Port int + Debug bool + Template *fw.JetTemplate +} + +func New(port int, configPath string) (*Storage, fw.Error) { + conf, err := config.LoadConfig(configPath) + if err != nil { + return nil, err.Tap() + } + + return &Storage{ + Config: conf, + Port: port, + Debug: conf.Debug, + Template: fw.NewJetTemplate("/app/templates"), + }, nil +} diff --git a/application/inner/test/test.go b/application/inner/test/test.go new file mode 100644 index 0000000..4930b18 --- /dev/null +++ b/application/inner/test/test.go @@ -0,0 +1,149 @@ +package test + +import ( + "database/sql" + "fmt" + "os" + "pink_fox/inner/config" + "pink_fox/packages/fw" + "regexp" + "strings" +) + +var ( + dbTest *DBTest = nil +) + +type DBTest struct { + db *sql.DB + seedersPath string +} + +func NewDBTest() *DBTest { + if dbTest == nil { + path := "/app/config-test.yml" + dbConf, err := config.LoadTestConfig(path) + if err != nil { + panic(err.Add("ошибка загрузки конфига")) + } + + db, err := fw.CreateConnection(dbConf.Host, dbConf.Port, dbConf.User, dbConf.Password, dbConf.Database) + if err != nil { + panic(err.Add("ошибка соединение с базой данной")) + } + + err = refreshDatabase(db, dbConf.Migrations, dbConf.Database, true) + if err != nil { + panic(err.Add("ошибка обновление базы данных")) + } + + dbTest = &DBTest{ + db: db, + seedersPath: "/app/database/seeders", + } + } + return dbTest +} + +func (it *DBTest) GetDB() *sql.DB { + return it.db +} + +func (it *DBTest) Seed(filename string) *DBTest { + path := it.seedersPath + "/" + filename + queryByte, err := os.ReadFile(path) + if err != nil { + panic(fw.ErrStr(fmt.Sprintf("ошибка загрузки файла для %s: %s", path, err))) + } + + tx, err := it.db.Begin() + if err != nil { + panic(fw.ErrStr(fmt.Sprintf("ошибка запуска транзакции: %s", err))) + } + defer func() { + _ = tx.Rollback() + }() + + cleanedData := it.removeSQLComments(string(queryByte)) + queries := it.splitSQLQueries(cleanedData) + for _, query := range queries { + query = strings.TrimSpace(query) + if query == "" { + continue + } + + if _, err = tx.Exec(query); err != nil { + panic(fmt.Errorf("ошибка в запросе `%s`: %s", query, err)) + } + } + + err = tx.Commit() + if err != nil { + panic(fmt.Sprintf("ошибка подтверждения транзакции: %s", err)) + } + + return it +} + +func refreshDatabase(db *sql.DB, migrationsPath, dbName string, disableLogger bool) fw.Error { + err := fw.DropAppTable(db, dbName) + if err != nil { + return err.Tap() + } + + return fw.MigrateUp(db, migrationsPath, disableLogger) +} + +func (it *DBTest) removeSQLComments(sql string) string { + // Удаляем однострочные комментарии (-- до конца строки) + reSingleLine := regexp.MustCompile(`--.*`) + sql = reSingleLine.ReplaceAllString(sql, "") + + // Удаляем многострочные комментарии (/* ... */) + reMultiLine := regexp.MustCompile(`/\*.*?\*/`) + sql = reMultiLine.ReplaceAllString(sql, "") + + return sql +} + +// splitSQLQueries разбивает SQL-код на отдельные запросы +func (it *DBTest) splitSQLQueries(sql string) []string { + var queries []string + var buffer strings.Builder + inString := false + quoteChar := byte(0) + + for i := 0; i < len(sql); i++ { + char := sql[i] + + switch { + case char == '\'' || char == '"': + if !inString { + inString = true + quoteChar = char + } else if char == quoteChar { + // Проверяем, не экранирована ли кавычка + if i > 0 && sql[i-1] == '\\' { + continue + } + inString = false + quoteChar = 0 + } + buffer.WriteByte(char) + + case char == ';' && !inString: + queries = append(queries, buffer.String()) + buffer.Reset() + + default: + buffer.WriteByte(char) + } + } + + // Добавляем последний запрос (если есть) + if buffer.Len() > 0 { + queries = append(queries, buffer.String()) + } + + return queries +} diff --git a/application/inner/view/view.go b/application/inner/view/view.go new file mode 100644 index 0000000..d85e751 --- /dev/null +++ b/application/inner/view/view.go @@ -0,0 +1,11 @@ +package view + +type View struct { + SiteTitle string +} + +func NewView() *View { + return &View{ + SiteTitle: "FW - framework on golang", + } +} diff --git a/application/main.go b/application/main.go new file mode 100644 index 0000000..fd67a55 --- /dev/null +++ b/application/main.go @@ -0,0 +1,42 @@ +package main + +import ( + "fmt" + "os" + "pink_fox/inner" + "pink_fox/inner/config" + "pink_fox/inner/di" + "pink_fox/inner/storage" + "pink_fox/packages/fw" +) + +func main() { + serverConfig := fw.ServerConfig[*storage.Storage, *di.Container]{ + InitStorage: storage.New, + GetDebugMode: func(s *storage.Storage) bool { + return s.Debug + }, + RegistrationRoutes: inner.RegistrationRoutes, + MakeContainer: di.MakeContainer, + CloseContainer: di.CloseContainer, + } + + getConfig := func() (*fw.DatabaseConfig, fw.Error) { + return config.GetDatabaseConfig("/app/config.yml") + } + + cli := fw.CLI{ + Use: "pink_fox", + Short: "todo example project", + Commands: []fw.Command{ + fw.GetCmdServer(serverConfig, 12001, "/app/config.yml"), + fw.GetCmdMigration(getConfig), + }, + } + + err := fw.Start(&cli) + if err != nil { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/application/packages/fw/base.go b/application/packages/fw/base.go new file mode 100644 index 0000000..fe82e77 --- /dev/null +++ b/application/packages/fw/base.go @@ -0,0 +1,5 @@ +package fw + +type BaseServices interface { + ResponseFactory() *ResponseFactory +} diff --git a/application/packages/fw/cmd.go b/application/packages/fw/cmd.go new file mode 100644 index 0000000..dea0444 --- /dev/null +++ b/application/packages/fw/cmd.go @@ -0,0 +1,43 @@ +package fw + +import "github.com/spf13/cobra" + +type CLI struct { + Use string + Short string + Long string + Commands []Command +} + +type Command struct { + Use string + Short string + Long string + Args cobra.PositionalArgs + Run func(cmd *cobra.Command, args []string) + Init func(cmd *cobra.Command) +} + +func cmdInit(cli *CLI) *cobra.Command { + rootCmd := &cobra.Command{ + Use: cli.Use, + Short: cli.Short, + Long: cli.Long, + } + + for _, command := range cli.Commands { + subCmd := &cobra.Command{ + Use: command.Use, + Short: command.Short, + Long: command.Long, + Args: command.Args, + Run: command.Run, + } + if command.Init != nil { + command.Init(subCmd) + } + rootCmd.AddCommand(subCmd) + } + + return rootCmd +} diff --git a/application/packages/fw/cmd_migrate.go b/application/packages/fw/cmd_migrate.go new file mode 100644 index 0000000..699e651 --- /dev/null +++ b/application/packages/fw/cmd_migrate.go @@ -0,0 +1,103 @@ +package fw + +import ( + "database/sql" + _ "github.com/lib/pq" + "github.com/spf13/cobra" +) + +type DatabaseConfig struct { + Host string + User string + Password string + Port string + Database string + Migrations string +} + +var ( + step = 0 +) + +func GetCmdMigration(getConfig func() (*DatabaseConfig, Error)) Command { + return Command{ + Use: "migrate", + Short: "Выполнить миграции. Подкоманды [down, create]", + Run: func(cmd *cobra.Command, args []string) { + conf, db, err := getConfigAndDatabase(getConfig) + if err != nil { + Exit(err) + } + + err = MigrateUp(db, conf.Migrations, false) + if err != nil { + Exit(err) + } + }, + Init: func(cmd *cobra.Command) { + downCmd := &cobra.Command{ + Use: "down", + Short: "Откатить одну миграцию", + Run: downCmdHandler(getConfig), + } + downCmd.Flags().IntVar(&step, "step", 1, "Количество отката миграции") + + createCmd := &cobra.Command{ + Use: "create", + Short: "Новая миграция", + Run: createCmdHandler(getConfig), + Args: cobra.ExactArgs(1), + } + + cmd.AddCommand(downCmd, createCmd) + }, + } +} + +func downCmdHandler(getConfig func() (*DatabaseConfig, Error)) func(*cobra.Command, []string) { + return func(cmd *cobra.Command, args []string) { + conf, db, err := getConfigAndDatabase(getConfig) + if err != nil { + Exit(err) + } + + if step == 0 { + step = 1 + } + + for i := 0; i < step; i++ { + err = MigrateDown(db, conf.Migrations) + if err != nil { + Exit(err) + } + } + } +} + +func createCmdHandler(getConfig func() (*DatabaseConfig, Error)) func(*cobra.Command, []string) { + return func(cmd *cobra.Command, args []string) { + conf, err := getConfig() + if err != nil { + Exit(err) + } + + err = MigrateCreate(conf.Migrations, args[0]) + if err != nil { + Exit(err) + } + } +} + +func getConfigAndDatabase(getConfig func() (*DatabaseConfig, Error)) (*DatabaseConfig, *sql.DB, Error) { + conf, err := getConfig() + if err != nil { + return nil, nil, err.Tap() + } + + db, err := CreateConnection(conf.Host, conf.Port, conf.User, conf.Password, conf.Database) + if err != nil { + return nil, nil, err.Tap() + } + + return conf, db, nil +} diff --git a/application/packages/fw/cmd_server.go b/application/packages/fw/cmd_server.go new file mode 100644 index 0000000..94547b0 --- /dev/null +++ b/application/packages/fw/cmd_server.go @@ -0,0 +1,77 @@ +package fw + +import ( + "fmt" + "github.com/go-chi/chi/v5" + "github.com/spf13/cobra" + "net/http" +) + +type ServerConfig[T, U any] struct { + InitStorage func(port int, pathConfig string) (storage T, err Error) + GetDebugMode func(T) bool + GetLogger func(T) Logger + InitServerEngine func(T) (*chi.Mux, Error) + RegistrationRoutes func(*RouterManager[T, U]) + MakeContainer func(T, http.ResponseWriter, *http.Request) U + CloseContainer func(U) +} + +func GetCmdServer[T, U any](config ServerConfig[T, U], portDefault int, configPathDefault string) Command { + port := portDefault + configPath := configPathDefault + return Command{ + Use: "server", + Short: "start the server", + Run: func(cmd *cobra.Command, args []string) { + storage, err := config.InitStorage(port, configPath) + if err != nil { + Exit(err) + } + + var engine *chi.Mux + if config.InitServerEngine != nil { + engine, err = config.InitServerEngine(storage) + if err != nil { + Exit(err) + } + } else { + engine = DefaultServerEngine() + } + + debugMode := false + if config.GetDebugMode != nil { + debugMode = config.GetDebugMode(storage) + } + + var logger Logger = nil + if config.GetLogger != nil { + logger = config.GetLogger(storage) + } + + routesManager := NewRouterManager[T, U]( + engine, + storage, + config.MakeContainer, + config.CloseContainer, + debugMode, + logger, + ) + config.RegistrationRoutes(routesManager) + + fmt.Printf("Starting http_server at port %d...", port) + err_ := http.ListenAndServe(":12001", engine) + if err_ != nil { + Exit(Err(fmt.Errorf("error starting http server: %v", err))) + } + }, + Init: func(cmd *cobra.Command) { + cmd.Flags().IntVarP(&port, "port", "p", port, "start http server on port") + cmd.Flags().StringVarP(&configPath, "config", "c", configPath, "config file path") + }, + } +} + +func DefaultServerEngine() *chi.Mux { + return chi.NewRouter() +} diff --git a/application/packages/fw/error.go b/application/packages/fw/error.go new file mode 100644 index 0000000..c51f298 --- /dev/null +++ b/application/packages/fw/error.go @@ -0,0 +1,115 @@ +package fw + +import ( + "fmt" + "runtime" + "strings" +) + +type Error interface { + Add(string) Error + Tap() Error + Message() string + Trace() []string + Error() string +} + +type LogError struct { + err error + messages []string + trace []string + stack []byte +} + +func Err(err error) *LogError { + return &LogError{ + err: err, + trace: []string{trace()}, + } +} + +func ErrStr(msg string) *LogError { + return &LogError{ + messages: []string{msg}, + trace: []string{trace()}, + } +} + +func (it *LogError) Add(msg string) Error { + it.trace = append(it.trace, trace()) + it.messages = append(it.messages, msg) + return it +} + +func (it *LogError) Tap() Error { + it.trace = append(it.trace, trace()) + return it +} + +func (it *LogError) Message() string { + result := "" + for i := len(it.messages) - 1; i >= 0; i-- { + if result == "" { + result += fmt.Sprintf("%v", it.messages[i]) + } else { + result += fmt.Sprintf(": %v", it.messages[i]) + } + } + if it.err != nil { + if result == "" { + result += fmt.Sprintf("%v", it.err) + } else { + result += fmt.Sprintf(": %v", it.err) + } + } + return result +} + +func (it *LogError) Error() string { + result := "" + if len(it.trace) > 0 { + result = "\n\t" + strings.Join(it.trace, "\n\t") + } + return it.Message() + result +} + +func (it *LogError) Trace() []string { + return it.trace +} + +type PanicError struct { + *LogError +} + +func ErrPanic(err error, stack []byte) *PanicError { + e := PanicError{ + LogError: Err(err), + } + e.trace = strings.Split(string(stack), "\n") + return &e +} + +var ( + basePath = "packages/fw/error.go" +) + +func init() { + _, file, _, ok := runtime.Caller(0) + if !ok { + return + } + basePath = file[:len(file)-len(basePath)] +} + +func trace() string { + _, file, line, ok := runtime.Caller(2) + if !ok { + return "it was not possible to recover the information" + } + + if strings.HasPrefix(file, basePath) { + return fmt.Sprintf("%s:%d", file[len(basePath):], line) + } else { + return fmt.Sprintf("%s:%d", file, line) + } +} diff --git a/application/packages/fw/error_test.go b/application/packages/fw/error_test.go new file mode 100644 index 0000000..54289a1 --- /dev/null +++ b/application/packages/fw/error_test.go @@ -0,0 +1,49 @@ +package fw + +import ( + "fmt" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestTrace(t *testing.T) { + test := ErrStr("error 1") + _ = test.Add("message 2") + _ = test.Tap() + + assert.Equal(t, []string{"packages/fw/error_test.go:10", "packages/fw/error_test.go:11", "packages/fw/error_test.go:12"}, test.Trace()) +} + +func TestErr(t *testing.T) { + test := Err(fmt.Errorf("test error")) + assert.Equal(t, "test error", test.Message()) + assert.Equal(t, 1, len(test.Trace())) +} + +func TestErrAddMsq(t *testing.T) { + test := Err(fmt.Errorf("test error")) + _ = test.Add("message 2") + assert.Equal(t, "message 2: test error", test.Message()) + assert.Equal(t, 2, len(test.Trace())) +} + +func TestErrAddMsq2(t *testing.T) { + test := Err(fmt.Errorf("test error")) + _ = test.Add("message 2") + _ = test.Add("message 3") + assert.Equal(t, "message 3: message 2: test error", test.Message()) + assert.Equal(t, 3, len(test.Trace())) +} + +func TestStr(t *testing.T) { + test := ErrStr("error 1") + assert.Equal(t, "error 1", test.Message()) + assert.Equal(t, 1, len(test.Trace())) +} + +func TestStrAddMsg(t *testing.T) { + test := ErrStr("error 1") + _ = test.Add("message 2") + assert.Equal(t, "message 2: error 1", test.Message()) + assert.Equal(t, 2, len(test.Trace())) +} diff --git a/application/packages/fw/fw.go b/application/packages/fw/fw.go new file mode 100644 index 0000000..6a44c60 --- /dev/null +++ b/application/packages/fw/fw.go @@ -0,0 +1,18 @@ +package fw + +import ( + "fmt" + "os" +) + +func Start(cli *CLI) Error { + if err := cmdInit(cli).Execute(); err != nil { + return Err(err) + } + return nil +} + +func Exit(err Error) { + _, _ = fmt.Fprintln(os.Stderr, err) + os.Exit(1) +} diff --git a/application/packages/fw/logger.go b/application/packages/fw/logger.go new file mode 100644 index 0000000..ea18777 --- /dev/null +++ b/application/packages/fw/logger.go @@ -0,0 +1,8 @@ +package fw + +type Logger interface { + Info(msg string, args ...any) + Error(msg string, args ...any) + Debug(msg string, args ...any) + Warn(msg string, args ...any) +} diff --git a/application/packages/fw/migration.go b/application/packages/fw/migration.go new file mode 100644 index 0000000..4cfc234 --- /dev/null +++ b/application/packages/fw/migration.go @@ -0,0 +1,75 @@ +package fw + +import ( + "context" + "database/sql" + "fmt" + "github.com/pressly/goose/v3" +) + +func MigrateUp(db *sql.DB, dir string, disableLogger bool) Error { + if disableLogger { + goose.SetLogger(goose.NopLogger()) + } + err := goose.Up(db, dir) + if err != nil { + return Err(err) + } + return nil +} + +func MigrateDown(conn *sql.DB, migrationPath string) Error { + err := goose.Down(conn, migrationPath) + if err != nil { + return Err(err) + } + return nil +} + +func MigrateCreate(migrationPath, migrationName string) Error { + ctx := context.Background() + err := goose.RunContext(ctx, "create", nil, migrationPath, migrationName, "sql") + if err != nil { + return Err(err) + } + return nil +} + +func DropAppTable(db *sql.DB, dbName string) Error { + rows, err := db.Query(` + SELECT table_name + FROM information_schema.tables + WHERE table_schema = 'public' AND table_type = 'BASE TABLE' AND table_catalog = $1 + `, dbName) + if err != nil { + return Err(err) + } + defer func() { + _ = rows.Close() + }() + + var tables []string + for rows.Next() { + var tableName string + if err = rows.Scan(&tableName); err != nil { + return Err(err) + } + tables = append(tables, tableName) + } + defer func() { + _ = rows.Close() + }() + + if err = rows.Err(); err != nil { + return Err(err) + } + + for _, table := range tables { + _, err = db.Exec(fmt.Sprintf("DROP TABLE IF EXISTS %s", table)) + if err != nil { + return Err(fmt.Errorf("error drop %s: %v", table, err)) + } + } + + return nil +} diff --git a/application/packages/fw/pg.go b/application/packages/fw/pg.go new file mode 100644 index 0000000..896cfc5 --- /dev/null +++ b/application/packages/fw/pg.go @@ -0,0 +1,21 @@ +package fw + +import ( + "database/sql" + "fmt" +) + +func CreateConnection(host, port, user, password, database string) (*sql.DB, Error) { + psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", + host, port, user, password, database) + var db *sql.DB + var err error + db, err = sql.Open("postgres", psqlInfo) + if err == nil { + err = db.Ping() + if err == nil { + return db, nil + } + } + return nil, Err(err) +} diff --git a/application/packages/fw/render.go b/application/packages/fw/render.go new file mode 100644 index 0000000..65a0161 --- /dev/null +++ b/application/packages/fw/render.go @@ -0,0 +1,49 @@ +package fw + +import ( + "bytes" + "github.com/CloudyKit/jet/v6" + "io" + "net/http" +) + +type JetTemplate struct { + set *jet.Set +} + +func NewJetTemplate(path string) *JetTemplate { + return &JetTemplate{ + set: jet.NewSet(jet.NewOSFileSystemLoader(path)), + } +} + +type JetRender struct { + engine *JetTemplate + Writer http.ResponseWriter + Variables jet.VarMap +} + +func NewJetRender(engine *JetTemplate, writer http.ResponseWriter, variables jet.VarMap) *JetRender { + return &JetRender{ + engine: engine, + Writer: writer, + Variables: variables, + } +} + +func (it *JetRender) Render(template string, data any) Error { + tpl, err := it.engine.set.GetTemplate(template) + if err != nil { + return Err(err) + } + var buf bytes.Buffer + err = tpl.Execute(&buf, it.Variables, data) + if err != nil { + return Err(err) + } + _, err = io.Copy(it.Writer, &buf) + if err != nil { + return Err(err) + } + return nil +} diff --git a/application/packages/fw/response.go b/application/packages/fw/response.go new file mode 100644 index 0000000..d97b525 --- /dev/null +++ b/application/packages/fw/response.go @@ -0,0 +1,132 @@ +package fw + +import ( + "fmt" + "github.com/CloudyKit/jet/v6" + "net/http" +) + +type Response interface { + Render() Error + WriteHeader(statusCode int) +} + +type ResponseFactory struct { + writer http.ResponseWriter + template *JetTemplate + GetView func() any +} + +func NewResponseFactory(writer http.ResponseWriter, template *JetTemplate, getView func() any) *ResponseFactory { + return &ResponseFactory{ + writer: writer, + template: template, + GetView: getView, + } +} + +func (it *ResponseFactory) String(str string) *StringResponse { + return NewStringResponse(it.writer, str) +} + +func (it *ResponseFactory) HtmlError(code int, message string) *HtmlErrorResponse { + return NewHtmlErrorResponse(it.makeJetRender(), code, message) +} + +func (it *ResponseFactory) View(template string, data any) *ViewResponse { + return NewViewResponse(it.makeJetRender(), template, data) +} + +func (it *ResponseFactory) makeJetRender() *JetRender { + variables := make(jet.VarMap) + if it.GetView != nil { + variables.Set("vi", it.GetView()) + } + return NewJetRender(it.template, it.writer, variables) +} + +type BaseResponse struct { + statusCode int +} + +func (it *BaseResponse) WriteHeader(statusCode int) { + it.statusCode = statusCode +} + +type StringResponse struct { + BaseResponse + str string + writer http.ResponseWriter +} + +func NewStringResponse(writer http.ResponseWriter, str string) *StringResponse { + return &StringResponse{ + writer: writer, + str: str, + } +} + +func (it *StringResponse) Render() Error { + it.writer.WriteHeader(it.statusCode) + _, err := it.writer.Write([]byte(it.str)) + if err != nil { + return Err(err) + } + return nil +} + +type HtmlErrorResponse struct { + ViewResponse + statusCode int + message string +} + +func NewHtmlErrorResponse(render *JetRender, code int, message string) *HtmlErrorResponse { + return &HtmlErrorResponse{ + ViewResponse: *NewViewResponse(render, "errors/error.html", nil), + statusCode: code, + message: message, + } +} + +func (it *HtmlErrorResponse) Render() Error { + fmt.Println("status", it.statusCode) + it.render.Writer.WriteHeader(it.statusCode) + err := it.render.Render(it.template, map[string]any{ + "title": fmt.Sprintf("%d %s", it.statusCode, http.StatusText(it.statusCode)), + "message": it.message, + }) + if err != nil { + return err.Tap() + } + return nil +} + +type ViewResponse struct { + BaseResponse + render *JetRender + template string + data any +} + +func NewViewResponse(render *JetRender, template string, data any) *ViewResponse { + return &ViewResponse{ + render: render, + template: template, + data: data, + } +} + +func (it *ViewResponse) Set(key string, value interface{}) *ViewResponse { + it.render.Variables.Set(key, value) + return it +} + +func (it *ViewResponse) Render() Error { + it.render.Writer.WriteHeader(it.statusCode) + err := it.render.Render(it.template, it.data) + if err != nil { + return err.Tap() + } + return nil +} diff --git a/application/packages/fw/router_manager.go b/application/packages/fw/router_manager.go new file mode 100644 index 0000000..0428ee7 --- /dev/null +++ b/application/packages/fw/router_manager.go @@ -0,0 +1,224 @@ +package fw + +import ( + "fmt" + "github.com/go-chi/chi/v5" + "net/http" + "runtime/debug" +) + +type MiddlewareFunc[U any] func(U, ActionFunc) (Response, Error) + +type HandlerFunc[U any] func(U) (Response, Error) + +type ActionFunc func() (Response, Error) + +type RouterManager[S, D any] struct { + mux *chi.Mux + storage S + fnMakeContainer func(S, http.ResponseWriter, *http.Request) D + fnCloseContainer func(D) + debug bool + logger Logger + middlewares []MiddlewareFunc[D] +} + +func NewRouterManager[S, D any](mux *chi.Mux, storage S, fnMakeContainer func(S, http.ResponseWriter, *http.Request) D, + fnCloseContainer func(D), debug bool, logger Logger) *RouterManager[S, D] { + return &RouterManager[S, D]{ + mux: mux, + storage: storage, + fnMakeContainer: fnMakeContainer, + fnCloseContainer: fnCloseContainer, + debug: debug, + logger: logger, + middlewares: make([]MiddlewareFunc[D], 0), + } +} + +func (it *RouterManager[S, D]) Use(middlewares ...MiddlewareFunc[D]) { + it.middlewares = append(it.middlewares, middlewares...) +} + +func (it *RouterManager[S, D]) Group(relativePath string) *Group[S, D] { + return NewGroup(it, relativePath, it.middlewares...) +} + +func (it *RouterManager[S, D]) NotFound(handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.mux.NotFound(it.makeHandler(handler, append(it.middlewares, middlewares...))) +} + +func (it *RouterManager[S, D]) NoMethod(handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.mux.MethodNotAllowed(it.makeHandler(handler, append(it.middlewares, middlewares...))) +} + +func (it *RouterManager[S, D]) Get(relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.mux.Get(relativePath, it.makeHandler(handler, append(it.middlewares, middlewares...))) +} + +func (it *RouterManager[S, D]) Post(relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.mux.Post(relativePath, it.makeHandler(handler, append(it.middlewares, middlewares...))) +} + +func (it *RouterManager[S, D]) List(methods []string, relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + for _, m := range methods { + it.mux.Method(m, relativePath, it.makeHandler(handler, append(it.middlewares, middlewares...))) + } +} + +func (it *RouterManager[S, D]) Common(relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.List([]string{http.MethodGet, http.MethodPost, http.MethodOptions}, relativePath, handler, middlewares...) +} + +func (it *RouterManager[S, D]) All(relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.List([]string{ + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + http.MethodConnect, + http.MethodOptions, + http.MethodTrace, + }, + relativePath, + handler, + middlewares..., + ) +} + +type Group[S, D any] struct { + routerManager *RouterManager[S, D] + mux *chi.Mux + middlewares []MiddlewareFunc[D] +} + +func NewGroup[S, D any](routerManager *RouterManager[S, D], relativePath string, middlewares ...MiddlewareFunc[D]) *Group[S, D] { + mux := chi.NewRouter() + routerManager.mux.Mount(relativePath, mux) + return &Group[S, D]{ + routerManager: routerManager, + mux: mux, + middlewares: middlewares, + } +} + +func (it *Group[S, D]) Use(middlewares ...MiddlewareFunc[D]) { + it.middlewares = append(it.middlewares, middlewares...) +} + +func (it *Group[S, D]) Group(relativePath string, middlewares ...MiddlewareFunc[D]) *Group[S, D] { + mux := chi.NewRouter() + it.mux.Mount(relativePath, mux) + return &Group[S, D]{ + routerManager: it.routerManager, + mux: mux, + middlewares: append(it.middlewares, middlewares...), + } +} + +func (it *Group[S, D]) NotFound(handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.mux.NotFound(it.routerManager.makeHandler(handler, append(it.middlewares, middlewares...))) +} + +func (it *Group[S, D]) NoMethod(handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.mux.MethodNotAllowed(it.routerManager.makeHandler(handler, append(it.middlewares, middlewares...))) +} + +func (it *Group[S, D]) Get(relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.mux.Get(relativePath, it.routerManager.makeHandler(handler, append(it.middlewares, middlewares...))) +} + +func (it *Group[S, D]) Post(relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.mux.Post(relativePath, it.routerManager.makeHandler(handler, append(it.middlewares, middlewares...))) +} + +func (it *Group[S, D]) List(methods []string, relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + for _, m := range methods { + it.mux.Method(m, relativePath, it.routerManager.makeHandler(handler, append(it.middlewares, middlewares...))) + } +} + +func (it *Group[S, D]) Common(relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.List([]string{http.MethodGet, http.MethodPost, http.MethodOptions}, relativePath, handler, middlewares...) +} + +func (it *Group[S, D]) All(relativePath string, handler HandlerFunc[D], middlewares ...MiddlewareFunc[D]) { + it.List([]string{ + http.MethodGet, + http.MethodHead, + http.MethodPost, + http.MethodPut, + http.MethodPatch, + http.MethodDelete, + http.MethodConnect, + http.MethodOptions, + http.MethodTrace, + }, + relativePath, + handler, + middlewares..., + ) +} + +func (it *RouterManager[S, D]) makeHandler(handler HandlerFunc[D], middlewares []MiddlewareFunc[D]) http.HandlerFunc { + return func(writer http.ResponseWriter, request *http.Request) { + var err Error + defer func() { + if err_ := recover(); err_ != nil { + err = ErrPanic(fmt.Errorf("panic: %v", err_), debug.Stack()) + it.errorsHandler(writer, err, it.debug, it.logger) + } + }() + + container := it.fnMakeContainer(it.storage, writer, request) + defer it.fnCloseContainer(container) + + err = it.pipeline(handler, middlewares, container) + if err != nil { + it.errorsHandler(writer, err, it.debug, it.logger) + } + } +} + +func (it *RouterManager[S, D]) pipeline(endpoint HandlerFunc[D], middlewares []MiddlewareFunc[D], container D) (err Error) { + handler := func() (Response, Error) { + return endpoint(container) + } + for i := len(middlewares) - 1; i >= 0; i-- { + handler = it.createMiddleware(handler, middlewares[i], container) + } + resp, err := handler() + if err != nil { + _ = err.Tap() + } + if resp != nil { + err = ErrStr("response должен быть отображен в middleware, по всей видимости этот шаг был пропущен") + } + return +} + +func (it *RouterManager[T, U]) createMiddleware(next ActionFunc, middleware MiddlewareFunc[U], container U) ActionFunc { + return func() (Response, Error) { + return middleware(container, next) + } +} + +func (it *RouterManager[T, U]) errorsHandler(writer http.ResponseWriter, err Error, debugOn bool, logger Logger) { + if logger != nil { + logger.Error(err.Message(), err.Trace()) + } + + message := "" + if debugOn { + message = fmt.Sprintf("
%s", err.Error()) + } + message = fmt.Sprintf("
{{ .message }}
+ +{{ if .error != "" }} +{{ .error }}
+{{ end }}
\ No newline at end of file
diff --git a/application/templates/errors/error.html b/application/templates/errors/error.html
new file mode 100644
index 0000000..86552c1
--- /dev/null
+++ b/application/templates/errors/error.html
@@ -0,0 +1,5 @@
+{{ .message }}
+{{ end }} diff --git a/application/templates/index.html b/application/templates/index.html new file mode 100644 index 0000000..f6fa641 --- /dev/null +++ b/application/templates/index.html @@ -0,0 +1,3 @@ +{{ .text }}
\ No newline at end of file diff --git a/application/ttt.go b/application/ttt.go new file mode 100644 index 0000000..e2d66d5 --- /dev/null +++ b/application/ttt.go @@ -0,0 +1,77 @@ +package main + +import "fmt" + +func main1() { + name := Map(getUser(100), getName).OrElse("-") + fmt.Println(name) + + value := Map(Map(getUser(200), getCard), getValue).OrElse(0) + fmt.Println(value) +} + +func getUser(id int) Maybe[User] { + if id == 100 { + return None[User]() + } + user := User{ + Name: "Test", + Card: &Card{Value: 100}, + } + return Some[User](user) +} + +func getCard(user User) Maybe[Card] { + if user.Card != nil { + return Some[Card](*user.Card) + } + return None[Card]() +} + +func getValue(card Card) Maybe[int] { + return Some[int](card.Value) +} + +func getName(user User) Maybe[string] { + return Some[string](user.Name) +} + +type User struct { + Name string + Card *Card +} + +type Card struct { + Value int +} + +type Maybe[T any] struct { + value T + present bool +} + +func Some[T any](value T) Maybe[T] { + return Maybe[T]{value: value, present: true} +} + +func None[T any]() Maybe[T] { + return Maybe[T]{present: false} +} + +func Map[T, R any](m Maybe[T], fn func(T) Maybe[R]) Maybe[R] { + if m.present { + return fn(m.value) + } + return None[R]() +} + +func (m Maybe[T]) IsPresent() bool { + return m.present +} + +func (m Maybe[T]) OrElse(defaultValue T) T { + if m.present { + return m.value + } + return defaultValue +} diff --git a/dist/.gitignore b/dist/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/dist/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 64386f9..80448e1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,9 +9,8 @@ services: - "12001:12001" - "12002:2345" volumes: - - ./pink_fox_app:/app - - ./pink_fox_app/log:/var/log/pink_fox - - ./dist:/dist + - ./application:/app + - ./application/log:/var/log/pink_fox - ./environment:/var/environment/pink_fox - ./.storage/go:/go environment: @@ -31,3 +30,17 @@ services: - ./services/postgres/init:/docker-entrypoint-initdb.d ports: - "12003:5432" + + postgres_test: + image: postgres:16 + environment: + - POSTGRES_USER=pink_fox + - POSTGRES_PASSWORD=${DB_PASSWORD} + - TZ=Europe/Moscow + - PGTZ=Europe/Moscow + volumes: + - ./services/postgres/init-test:/docker-entrypoint-initdb.d + tmpfs: + - /var/lib/postgresql/data + ports: + - "12004:5432" diff --git a/environment/example.config.yml b/environment/example.config.yml deleted file mode 100644 index c15a689..0000000 --- a/environment/example.config.yml +++ /dev/null @@ -1,15 +0,0 @@ -# Порт для приложения -port: 12001 - -# Соединение с postgres -db: - host: postgres - user: pink_fox - password: password - port: 5432 - database: pink_fox_db - -debug: true - -# Путь к файлу лога -#logFile: /var/log/pink_fox/app.log diff --git a/pink_fox_app/database/links.postgres.sql b/pink_fox_app/database/links.postgres.sql deleted file mode 100644 index 0be15da..0000000 --- a/pink_fox_app/database/links.postgres.sql +++ /dev/null @@ -1,11 +0,0 @@ -DROP TABLE IF EXISTS links; - -CREATE TABLE links ( - id SERIAL PRIMARY KEY, - token VARCHAR(24) NOT NULL UNIQUE, - url VARCHAR(512) NOT NULL, - created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP, - expires_at TIMESTAMP WITH TIME ZONE NOT NULL -); - -CREATE INDEX idx_links_token ON links (token); diff --git a/pink_fox_app/log/.gitignore b/pink_fox_app/log/.gitignore deleted file mode 100644 index c96a04f..0000000 --- a/pink_fox_app/log/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -* -!.gitignore \ No newline at end of file diff --git a/pink_fox_app/main.go b/pink_fox_app/main.go deleted file mode 100644 index 15ad3ab..0000000 --- a/pink_fox_app/main.go +++ /dev/null @@ -1,20 +0,0 @@ -package main - -import ( - "fmt" - "os" - "pink_fox/src/app/cmd" -) - -// FIXME -// перегрузка конфига -// отображение html страницы -// вынести перехват ошибок в middleware - -func main() { - err := cmd.Execute() - if err != nil { - _, _ = fmt.Fprintln(os.Stderr, err) - os.Exit(1) - } -} diff --git a/pink_fox_app/src/app/app.go b/pink_fox_app/src/app/app.go deleted file mode 100644 index e81d7fb..0000000 --- a/pink_fox_app/src/app/app.go +++ /dev/null @@ -1,21 +0,0 @@ -package app - -import ( - "database/sql" - "pink_fox/src/app/config" - "pink_fox/src/app/logger" -) - -type Application struct { - Conf *config.Config - Conn *sql.DB - Logger *logger.Logger -} - -func New(conf *config.Config, conn *sql.DB, logger *logger.Logger) *Application { - return &Application{ - Conf: conf, - Conn: conn, - Logger: logger, - } -} diff --git a/pink_fox_app/src/app/cmd/cmd.go b/pink_fox_app/src/app/cmd/cmd.go deleted file mode 100644 index beb8a23..0000000 --- a/pink_fox_app/src/app/cmd/cmd.go +++ /dev/null @@ -1,47 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/spf13/cobra" - "os" -) - -var ( - port = 12001 - config = "/var/environment/pink_fox/config.yml" - - rootCmd = &cobra.Command{ - Use: "pink_fox", - Short: "Розовый лис\nСквозь ссылок паутину —\nСвет в ночи горит.", - } - - serverCmd = &cobra.Command{ - Use: "server", - Short: "Запуск сервера", - Run: func(cmd *cobra.Command, args []string) { - err := NewServer().Execute(config, port) - if err != nil { - exit(err) - } - }, - } -) - -func init() { - serverCmd.Flags().IntVarP(&port, "port", "p", port, "Порт доступ к приложению") - serverCmd.Flags().StringVarP(&config, "config", "c", config, "Путь к файлу конфигурации") - - rootCmd.AddCommand(serverCmd) -} - -func Execute() error { - if err := rootCmd.Execute(); err != nil { - return fmt.Errorf("cmd: %s", err) - } - return nil -} - -func exit(err error) { - _, _ = fmt.Fprintln(os.Stderr, err) - os.Exit(1) -} diff --git a/pink_fox_app/src/app/cmd/server.go b/pink_fox_app/src/app/cmd/server.go deleted file mode 100644 index c6647a9..0000000 --- a/pink_fox_app/src/app/cmd/server.go +++ /dev/null @@ -1,151 +0,0 @@ -package cmd - -import ( - "bytes" - "database/sql" - "fmt" - "github.com/CloudyKit/jet/v6" - "github.com/gin-gonic/gin" - "github.com/gin-gonic/gin/render" - "io" - "net/http" - "pink_fox/src/app" - c "pink_fox/src/app/config" - "pink_fox/src/app/db" - l "pink_fox/src/app/logger" - "pink_fox/src/app/routes_manager" - "pink_fox/src/app/views_manager" - "pink_fox/src/routes" - "pink_fox/src/views" - "runtime/debug" -) - -type Server struct { -} - -func NewServer() *Server { - return &Server{} -} - -func (it *Server) Execute(defaultConfig string, defaultPort int) error { - conf, err := c.LoadConfig(defaultConfig, defaultPort) - if err != nil { - exit(err) - } - - conn, err := db.CreateConnection(&conf.Db) - if err != nil { - exit(err) - } - defer func(conn *sql.DB) { - _ = conn.Close() - }(conn) - - logger, err := l.NewLogger(conf.LogFile) - if err != nil { - exit(err) - } - defer func(logger *l.Logger) { - _ = logger.Close() - }(logger) - - return it.StartServer(app.New(conf, conn, logger)) -} - -func (it *Server) StartServer(application *app.Application) error { - r := gin.Default() - _ = r.SetTrustedProxies(nil) - r.GET("/ping", func(c *gin.Context) { - c.String(http.StatusOK, "ok") - }) - - r.Use(it.errorHandler(application.Logger)) - - err := it.loadTemplates(r) - if err != nil { - return err - } - - tm := views_manager.NewManager() - views.RegistrationViews(tm) - - routes.RegistrationRoutes(routes_manager.NewManager(r, application, tm)) - - fmt.Printf("Starting http_server at port %d...", application.Conf.Port) - err = r.Run(fmt.Sprintf(":%d", application.Conf.Port)) - if err != nil { - return err - } - - return nil -} - -func (it *Server) loadTemplates(e *gin.Engine) error { - set := jet.NewSet(jet.NewOSFileSystemLoader("views")) - renderer := &JetRenderer{set: set} - e.HTMLRender = renderer - - return nil -} - -func (it *Server) errorHandler(logger *l.Logger) gin.HandlerFunc { - return func(ctx *gin.Context) { - it.errorHandlerSetRecovery(ctx) - if len(ctx.Errors) > 0 { - logger.Error(ctx.Errors[0].Error()) - ctx.JSON(http.StatusInternalServerError, gin.H{ - "errors": "Internal Server Error", - }) - } - } -} - -func (it *Server) errorHandlerSetRecovery(ctx *gin.Context) { - defer func() { - if err := recover(); err != nil { - _ = ctx.Error(fmt.Errorf("panic: %v\n%s", err, debug.Stack())) - } - }() - ctx.Next() -} - -type JetRenderer struct { - set *jet.Set - template string - data any -} - -func (it *JetRenderer) Instance(tpl string, data any) render.Render { - it.template = tpl - it.data = data - return it -} - -func (it *JetRenderer) Render(w http.ResponseWriter) error { - tpl, err := it.set.GetTemplate(it.template) - if err != nil { - return err - } - - vars := make(jet.VarMap) - if it.data != nil { - if v, ok := it.data.(jet.VarMap); ok { - vars = v - } else { - vars.Set("data", it.data) - } - } - - var buf bytes.Buffer - err = tpl.Execute(&buf, vars, nil) - if err != nil { - return err - } - _, err = io.Copy(w, &buf) - return err -} - -func (it *JetRenderer) WriteContentType(http.ResponseWriter) { - // не знаю в каких случаях будет вызван этот метод - panic("implement me") // TODO -} diff --git a/pink_fox_app/src/app/config/config.go b/pink_fox_app/src/app/config/config.go deleted file mode 100644 index 7e62f72..0000000 --- a/pink_fox_app/src/app/config/config.go +++ /dev/null @@ -1,45 +0,0 @@ -package config - -import ( - "fmt" - "gopkg.in/yaml.v3" - "os" -) - -type DB struct { - Host string `yaml:"host"` - User string `yaml:"user"` - Password string `yaml:"password"` - Port string `yaml:"port"` - Database string `yaml:"database"` -} - -type Config struct { - Port int `yaml:"port"` - Db DB `yaml:"db"` - LogFile string `yaml:"logFile"` - Debug bool `yaml:"debug"` -} - -func LoadConfig(defaultConfig string, defaultPort int) (*Config, error) { - data, err := os.ReadFile(defaultConfig) - if err != nil { - return nil, fmt.Errorf("ошибка чтения файла конфига: %s", err) - } - var config Config - err = yaml.Unmarshal(data, &config) - if err != nil { - return nil, fmt.Errorf("не получилось распарсить конфиг: %s", err) - } - return setDefaultValue(&config, defaultPort), nil -} - -func setDefaultValue(conf *Config, defaultPort int) *Config { - if conf.Port == 0 { - conf.Port = defaultPort - } - if conf.LogFile == "" { - conf.LogFile = "/var/log/pink_fox/app.log" - } - return conf -} diff --git a/pink_fox_app/src/app/db/db.go b/pink_fox_app/src/app/db/db.go deleted file mode 100644 index 2f363c2..0000000 --- a/pink_fox_app/src/app/db/db.go +++ /dev/null @@ -1,30 +0,0 @@ -package db - -import ( - "database/sql" - "fmt" - _ "github.com/lib/pq" - "pink_fox/src/app/config" - "time" -) - -func CreateConnection(dbConf *config.DB) (*sql.DB, error) { - psqlInfo := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable", - dbConf.Host, dbConf.Port, dbConf.User, dbConf.Password, dbConf.Database) - - var err error - var db *sql.DB - - for i := 0; i < 10; i++ { - db, err = sql.Open("postgres", psqlInfo) - if err == nil { - err = db.Ping() - if err == nil { - return db, nil - } - } - time.Sleep(200 * time.Millisecond) - } - - return nil, fmt.Errorf("не удалось подключиться к базе данных: %v", err) -} diff --git a/pink_fox_app/src/app/http_server/request.go b/pink_fox_app/src/app/http_server/request.go deleted file mode 100644 index ce44eab..0000000 --- a/pink_fox_app/src/app/http_server/request.go +++ /dev/null @@ -1,17 +0,0 @@ -package http_server - -import "github.com/gin-gonic/gin" - -type Request struct { - ctx *gin.Context -} - -func NewRequest(ctx *gin.Context) *Request { - return &Request{ - ctx: ctx, - } -} - -func (it *Request) Param(key string) string { - return it.ctx.Param(key) -} diff --git a/pink_fox_app/src/app/http_server/response-factory.go b/pink_fox_app/src/app/http_server/response-factory.go deleted file mode 100644 index 65ecc34..0000000 --- a/pink_fox_app/src/app/http_server/response-factory.go +++ /dev/null @@ -1,34 +0,0 @@ -package http_server - -import ( - "github.com/gin-gonic/gin" - "pink_fox/src/app/types" -) - -type ResponseFactory struct { - ctx *gin.Context - makeViewObject types.MakeViewObject -} - -func NewResponseFactory(ctx *gin.Context, makeViewObject types.MakeViewObject) *ResponseFactory { - return &ResponseFactory{ - ctx: ctx, - makeViewObject: makeViewObject, - } -} - -func (it *ResponseFactory) String(s string) *StringResponse { - return NewStringResponse(s, it.ctx) -} - -func (it *ResponseFactory) HtmlError(code int) *HtmlErrorResponse { - return NewHtmlErrorResponse(code, it.ctx) -} - -func (it *ResponseFactory) Redirect(url string) *RedirectResponse { - return NewRedirectResponse(url, it.ctx) -} - -func (it *ResponseFactory) View(id int16, file string, data any) *ViewResponse { - return NewViewResponse(it.ctx, it.makeViewObject, id, file, data) -} diff --git a/pink_fox_app/src/app/http_server/response.go b/pink_fox_app/src/app/http_server/response.go deleted file mode 100644 index 93d5a3e..0000000 --- a/pink_fox_app/src/app/http_server/response.go +++ /dev/null @@ -1,95 +0,0 @@ -package http_server - -import ( - "fmt" - "github.com/CloudyKit/jet/v6" - "github.com/gin-gonic/gin" - "net/http" - "pink_fox/src/app/types" -) - -type Response interface { - Render() -} - -type StringResponse struct { - str string - ctx *gin.Context -} - -func NewStringResponse(s string, ctx *gin.Context) *StringResponse { - return &StringResponse{str: s, ctx: ctx} -} - -func (it *StringResponse) Render() { - it.ctx.String(http.StatusOK, it.str) -} - -type HtmlErrorResponse struct { - code int - msg string - ctx *gin.Context -} - -func NewHtmlErrorResponse(code int, ctx *gin.Context) *HtmlErrorResponse { - return &HtmlErrorResponse{code: code, ctx: ctx} -} - -func (it *HtmlErrorResponse) Msg(msg string) *HtmlErrorResponse { - it.msg = msg - return it -} - -func (it *HtmlErrorResponse) Render() { - message := "" - if it.msg != "" { - message = fmt.Sprintf("%s
", it.msg) - } - it.ctx.Header("Content-Type", "text/html; charset=utf-8") - it.ctx.String(it.code, fmt.Sprintf("%s\n\n%s\n%s\n", err.String(), debugStack, strings.Join(err.GetTrace(), "\n"))
- } else {
- message = fmt.Sprintf("%s\n\n%s\n", err.String(), strings.Join(err.GetTrace(), "\n"))
- }
- }
- ctx.Header("Content-Type", "text/html; charset=utf-8")
- ctx.String(http.StatusInternalServerError, fmt.Sprintf("%d %s
\n%s\n", http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError), message))
-}
diff --git a/pink_fox_app/src/app/timer/timer.go b/pink_fox_app/src/app/timer/timer.go
deleted file mode 100644
index 2193db2..0000000
--- a/pink_fox_app/src/app/timer/timer.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package timer
-
-// TODO
diff --git a/pink_fox_app/src/app/types/types.go b/pink_fox_app/src/app/types/types.go
deleted file mode 100644
index 24fe370..0000000
--- a/pink_fox_app/src/app/types/types.go
+++ /dev/null
@@ -1,3 +0,0 @@
-package types
-
-type MakeViewObject func(int16) any
diff --git a/pink_fox_app/src/app/views_manager/views_manager.go b/pink_fox_app/src/app/views_manager/views_manager.go
deleted file mode 100644
index 9bc3e55..0000000
--- a/pink_fox_app/src/app/views_manager/views_manager.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package views_manager
-
-import (
- "fmt"
- "pink_fox/src/dependence"
-)
-
-type ViewsManager struct {
- list map[int16]ViewsFunc
-}
-
-func NewManager() *ViewsManager {
- return &ViewsManager{
- list: make(map[int16]ViewsFunc),
- }
-}
-
-func (it *ViewsManager) Add(id int16, tplFunc ViewsFunc) {
- it.list[id] = tplFunc
-}
-
-func (it *ViewsManager) GetViewObject(id int16, depend *dependence.Container) (any, error) {
- tplFunc, ok := it.list[id]
- if !ok {
- return nil, fmt.Errorf("GetViewObject: template id %v not found", id)
- }
- return tplFunc(depend), nil
-}
-
-type ViewsFunc func(*dependence.Container) any
diff --git a/pink_fox_app/src/controllers/Go.go b/pink_fox_app/src/controllers/Go.go
deleted file mode 100644
index 0827838..0000000
--- a/pink_fox_app/src/controllers/Go.go
+++ /dev/null
@@ -1,33 +0,0 @@
-package controllers
-
-import (
- "fmt"
- "pink_fox/src/app/http_server"
- le "pink_fox/src/app/lerror"
- "pink_fox/src/repositories"
-)
-
-type GoController struct {
-}
-
-func NewGoController() *GoController {
- return &GoController{}
-}
-
-type IndexActionDependence interface {
- MakeRequest() *http_server.Request
- MakeResponseFactory() *http_server.ResponseFactory
- MakeLinksRepository() repositories.LinksRepository
-}
-
-func (it *GoController) IndexAction(depend IndexActionDependence) (http_server.Response, *le.Error) {
- token := depend.MakeRequest().Param("token")
- link, err := depend.MakeLinksRepository().ByToken(token)
- if err != nil {
- return nil, err.Tap()
- } else if link == nil {
- return depend.MakeResponseFactory().HtmlError(404), nil
- }
-
- return depend.MakeResponseFactory().String(fmt.Sprintf("Ссылка %d, token \"%s\" to \"%v\", дата %v", link.ID, link.Token, link.Url, link.CreatedAt)), nil
-}
diff --git a/pink_fox_app/src/controllers/Html.go b/pink_fox_app/src/controllers/Html.go
deleted file mode 100644
index 797dae8..0000000
--- a/pink_fox_app/src/controllers/Html.go
+++ /dev/null
@@ -1,25 +0,0 @@
-package controllers
-
-import (
- "pink_fox/src/app/http_server"
- le "pink_fox/src/app/lerror"
- "pink_fox/src/views"
-)
-
-type HtmlController struct {
- responseFactory *http_server.ResponseFactory
-}
-
-type HtmlControllerDependence interface {
- MakeResponseFactory() *http_server.ResponseFactory
-}
-
-func NewHtmlController(depend HtmlControllerDependence) *HtmlController {
- return &HtmlController{
- responseFactory: depend.MakeResponseFactory(),
- }
-}
-
-func (it *HtmlController) IndexAction() (http_server.Response, *le.Error) {
- return it.responseFactory.View(views.Base, "test.jet", "hello"), nil
-}
diff --git a/pink_fox_app/src/controllers/Index.go b/pink_fox_app/src/controllers/Index.go
deleted file mode 100644
index 3dbd1a0..0000000
--- a/pink_fox_app/src/controllers/Index.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package controllers
-
-import (
- "pink_fox/src/app/http_server"
- le "pink_fox/src/app/lerror"
-)
-
-type IndexController struct {
- responseFactory *http_server.ResponseFactory
-}
-
-type IndexControllerDependence interface {
- MakeResponseFactory() *http_server.ResponseFactory
-}
-
-func NewIndexController(depend IndexControllerDependence) *IndexController {
- return &IndexController{
- responseFactory: depend.MakeResponseFactory(),
- }
-}
-
-func (it *IndexController) IndexAction() (http_server.Response, *le.Error) {
- return it.responseFactory.String("Hello world!"), nil
-}
diff --git a/pink_fox_app/src/dependence/container.go b/pink_fox_app/src/dependence/container.go
deleted file mode 100644
index 2c0bf51..0000000
--- a/pink_fox_app/src/dependence/container.go
+++ /dev/null
@@ -1,41 +0,0 @@
-package dependence
-
-import (
- "github.com/gin-gonic/gin"
- "pink_fox/src/app"
- "pink_fox/src/app/http_server"
- "pink_fox/src/app/types"
- repo "pink_fox/src/repositories"
-)
-
-type Container struct {
- application *app.Application
- context *gin.Context
- makeViewObject types.MakeViewObject
-}
-
-func NewContainer(application *app.Application, context *gin.Context) *Container {
- return &Container{
- application: application,
- context: context,
- }
-}
-
-func (it *Container) SetViewManager(makeViewObject types.MakeViewObject) {
- it.makeViewObject = makeViewObject
-}
-
-func (it *Container) Close() {
-}
-
-func (it *Container) MakeRequest() *http_server.Request {
- return http_server.NewRequest(it.context)
-}
-
-func (it *Container) MakeResponseFactory() *http_server.ResponseFactory {
- return http_server.NewResponseFactory(it.context, it.makeViewObject)
-}
-
-func (it *Container) MakeLinksRepository() repo.LinksRepository {
- return repo.NewLinksRepository(it.application.Conn)
-}
diff --git a/pink_fox_app/src/repositories/links.go b/pink_fox_app/src/repositories/links.go
deleted file mode 100644
index e35aeaf..0000000
--- a/pink_fox_app/src/repositories/links.go
+++ /dev/null
@@ -1,48 +0,0 @@
-package repositories
-
-import (
- "database/sql"
- "errors"
- le "pink_fox/src/app/lerror"
- "time"
-)
-
-type LinkData struct {
- ID int
- Token string
- Url string
- CreatedAt time.Time
- ExpiresAt time.Time
-}
-
-type LinksRepository interface {
- ByToken(string) (*LinkData, *le.Error)
-}
-
-type LinksRepositoryImpl struct {
- db *sql.DB
-}
-
-func NewLinksRepository(conn *sql.DB) *LinksRepositoryImpl {
- return &LinksRepositoryImpl{
- db: conn,
- }
-}
-
-// FIXME нужно настроить время
-// время для го
-// время для postgres
-
-func (it *LinksRepositoryImpl) ByToken(token string) (*LinkData, *le.Error) {
- var link LinkData
- query := `SELECT id, token, url, created_at, expires_at FROM links WHERE token = $1`
- err := it.db.QueryRow(query, token).Scan(&link.ID, &link.Token, &link.Url, &link.CreatedAt, &link.ExpiresAt)
- if err != nil {
- if errors.Is(err, sql.ErrNoRows) {
- return nil, nil
- } else {
- return nil, le.New(err)
- }
- }
- return &link, nil
-}
diff --git a/pink_fox_app/src/routes/registration.go b/pink_fox_app/src/routes/registration.go
deleted file mode 100644
index 09f3233..0000000
--- a/pink_fox_app/src/routes/registration.go
+++ /dev/null
@@ -1,29 +0,0 @@
-package routes
-
-import (
- hs "pink_fox/src/app/http_server"
- le "pink_fox/src/app/lerror"
- "pink_fox/src/app/routes_manager"
- "pink_fox/src/controllers"
- di "pink_fox/src/dependence"
-)
-
-func RegistrationRoutes(r *routes_manager.RoutesManager) {
- r.GET("/", IndexController)
-
- r.GET("/link/:token", GoController)
-
- r.GET("/html", HtmlController)
-}
-
-func HtmlController(depend *di.Container) (hs.Response, *le.Error) {
- return controllers.NewHtmlController(depend).IndexAction()
-}
-
-func IndexController(depend *di.Container) (hs.Response, *le.Error) {
- return controllers.NewIndexController(depend).IndexAction()
-}
-
-func GoController(depend *di.Container) (hs.Response, *le.Error) {
- return controllers.NewGoController().IndexAction(depend)
-}
diff --git a/pink_fox_app/src/views/base.go b/pink_fox_app/src/views/base.go
deleted file mode 100644
index c8280ea..0000000
--- a/pink_fox_app/src/views/base.go
+++ /dev/null
@@ -1,21 +0,0 @@
-package views
-
-import (
- repo "pink_fox/src/repositories"
-)
-
-const Base int16 = 1
-
-type BaseView struct {
- Title string
-}
-
-type BaseViewInterface interface {
- MakeLinksRepository() repo.LinksRepository
-}
-
-func NewBase(_ BaseViewInterface) *BaseView {
- return &BaseView{
- Title: "Hello World!",
- }
-}
diff --git a/pink_fox_app/src/views/registration.go b/pink_fox_app/src/views/registration.go
deleted file mode 100644
index fdd1767..0000000
--- a/pink_fox_app/src/views/registration.go
+++ /dev/null
@@ -1,16 +0,0 @@
-package views
-
-import (
- "pink_fox/src/app/views_manager"
- "pink_fox/src/dependence"
-)
-
-func RegistrationViews(tm *views_manager.ViewsManager) {
- tm.Add(Base, MakeBaseView())
-}
-
-func MakeBaseView() func(container *dependence.Container) any {
- return func(depend *dependence.Container) any {
- return NewBase(depend)
- }
-}
diff --git a/pink_fox_app/views/test.jet b/pink_fox_app/views/test.jet
deleted file mode 100644
index c87d395..0000000
--- a/pink_fox_app/views/test.jet
+++ /dev/null
@@ -1,2 +0,0 @@
-{{ vi.Title }}
-{{ it }}
\ No newline at end of file
diff --git a/services/postgres/init-test/01-databases.sql b/services/postgres/init-test/01-databases.sql
new file mode 100644
index 0000000..e48fe37
--- /dev/null
+++ b/services/postgres/init-test/01-databases.sql
@@ -0,0 +1 @@
+CREATE DATABASE pink_fox_db_test with owner pink_fox;
diff --git a/services/site/build.sh b/services/site/build.sh
index 29689b4..3bf37ad 100755
--- a/services/site/build.sh
+++ b/services/site/build.sh
@@ -16,30 +16,30 @@ fi
if [ "$ARG1" = "debug" ]; then
# Собираем приложение для дебага
cd /app || exit 1
- rm -f "/dist/local_app_name"
+ rm -f "/app/local_app"
echo "" > /app/out.log
echo "" > /app/err.log
echo "" > /app/dlv.log
- pkill -f /dist/local_app_name
+ pkill -f /app/local_app
- go build -gcflags "all=-N -l" -o /dist/local_app_name > /app/out.log 2> /app/err.log
+ go build -gcflags "all=-N -l" -o /app/local_app > /app/out.log 2> /app/err.log
if [ ! -s "/app/err.log" ]; then
- dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec /dist/local_app_name server > /app/out.log 2> /app/dlv.log
+ dlv --listen=:2345 --headless=true --api-version=2 --accept-multiclient exec /app/local_app server > /app/out.log 2> /app/dlv.log
fi
else
# Собираем приложение
cd /app || exit 1
- rm -f "/dist/local_app_name"
+ rm -f "/app/local_app"
echo "" > /app/out.log
echo "" > /app/err.log
- pkill -f /dist/local_app_name
+ pkill -f /app/local_app
- go build -o "/dist/local_app_name" > /app/out.log 2> /app/err.log
+ go build -o "/app/local_app" > /app/out.log 2> /app/err.log
if [ ! -s "/app/err.log" ]; then
- /dist/local_app_name server > /app/out.log 2> /app/err.log
+ /app/local_app server > /app/out.log 2> /app/err.log
fi
fi
diff --git a/services/site/rerun.py b/services/site/rerun.py
index 41601c6..14aa3be 100755
--- a/services/site/rerun.py
+++ b/services/site/rerun.py
@@ -19,7 +19,7 @@ if len(sys.argv) > 1 and sys.argv[1] == "debug":
env["ARG1"] = "debug"
# Запускаем сервис через Docker compose
-subprocess.run(["docker", "compose", "up", "site", "-d", "--build"], env=env)
+subprocess.run(["docker", "compose", "up", "site", "-d"], env=env)
attempt_count = 0