From 85c1c488555c579ab151b0a5d8b3027d94ed38c1 Mon Sep 17 00:00:00 2001 From: Ahmed Hemdan Date: Tue, 16 Apr 2024 14:21:12 +0200 Subject: [PATCH] Remove qa stage from pipeline entirely --- .codeclimate.yml | 6 - .gitlab-ci.yml | 17 +- ci/.gitlab-ci-golang.yml | 30 - ci/.gitlab-ci-node.yml | 27 - ci/.gitlab-ci-php.yml | 41 - ci/.gitlab-ci-python.yml | 41 - ci/.gitlab-ci-ruby.yml | 55 - .../.github/.golangci.yml | 112 - .../.github/ISSUE_TEMPLATE/bug_report.md | 28 - .../.github/ISSUE_TEMPLATE/feature_request.md | 19 - .../.github/dependabot.yml | 10 - .../.github/pull_request_template.md | 9 - .../.github/workflows/check.yml | 39 - .../.github/workflows/release.yml | 24 - src/sample-golang-echo-app/.gitignore | 23 - src/sample-golang-echo-app/.goreleaser.yml | 52 - src/sample-golang-echo-app/CONTRIBUTING.md | 31 - src/sample-golang-echo-app/LICENSE | 21 - src/sample-golang-echo-app/README.md | 182 - src/sample-golang-echo-app/config/config.go | 95 - src/sample-golang-echo-app/config/const.go | 47 - .../container/container.go | 65 - .../controller/account.go | 108 - .../controller/account_test.go | 106 - src/sample-golang-echo-app/controller/book.go | 135 - .../controller/book_test.go | 310 - .../controller/category.go | 37 - .../controller/category_test.go | 34 - .../controller/error.go | 51 - .../controller/error_test.go | 25 - .../controller/format.go | 37 - .../controller/format_test.go | 33 - .../controller/health.go | 35 - .../controller/health_test.go | 27 - .../controller/logger_test.go | 44 - .../controller/swagger_test.go | 25 - src/sample-golang-echo-app/docs/docs.go | 671 - src/sample-golang-echo-app/go.mod | 65 - src/sample-golang-echo-app/go.sum | 165 - .../logger/gormlogger.go | 60 - src/sample-golang-echo-app/logger/logger.go | 71 - .../logger/zaplogger.go | 88 - src/sample-golang-echo-app/main.go | 66 - .../middleware/middleware.go | 183 - .../migration/db_generator.go | 25 - .../migration/master_generator.go | 32 - src/sample-golang-echo-app/model/account.go | 74 - src/sample-golang-echo-app/model/authority.go | 34 - src/sample-golang-echo-app/model/base.go | 18 - src/sample-golang-echo-app/model/book.go | 197 - src/sample-golang-echo-app/model/category.go | 65 - .../model/dto/account.go | 20 - src/sample-golang-echo-app/model/dto/book.go | 77 - .../model/dto/book_test.go | 216 - src/sample-golang-echo-app/model/format.go | 53 - src/sample-golang-echo-app/model/page.go | 17 - .../repository/repository.go | 192 - .../resources/config/application.develop.yml | 36 - .../resources/config/application.docker.yml | 28 - .../resources/config/application.k8s.yml | 34 - .../resources/config/messages.properties | 3 - .../resources/config/zaplogger.develop.yml | 24 - .../resources/config/zaplogger.docker.yml | 26 - .../resources/config/zaplogger.k8s.yml | 26 - .../resources/public/css/app.750b60b0.css | 3 - .../resources/public/favicon.ico | Bin 4286 -> 0 bytes .../img/icons/android-chrome-192x192.png | Bin 9416 -> 0 bytes .../img/icons/android-chrome-512x512.png | Bin 29808 -> 0 bytes .../img/icons/apple-touch-icon-120x120.png | Bin 3369 -> 0 bytes .../img/icons/apple-touch-icon-152x152.png | Bin 4046 -> 0 bytes .../img/icons/apple-touch-icon-180x180.png | Bin 4678 -> 0 bytes .../img/icons/apple-touch-icon-60x60.png | Bin 1491 -> 0 bytes .../img/icons/apple-touch-icon-76x76.png | Bin 1823 -> 0 bytes .../public/img/icons/apple-touch-icon.png | Bin 4678 -> 0 bytes .../public/img/icons/favicon-16x16.png | Bin 799 -> 0 bytes .../public/img/icons/favicon-32x32.png | Bin 1271 -> 0 bytes .../img/icons/msapplication-icon-144x144.png | Bin 1169 -> 0 bytes .../public/img/icons/mstile-150x150.png | Bin 4282 -> 0 bytes .../public/img/icons/safari-pinned-tab.svg | 149 - .../resources/public/index.html | 1 - .../resources/public/js/app.b3c19b4f.js | 1 - .../public/js/chunk-vendors.2abeaede.js | 39 - .../resources/public/manifest.json | 1 - ...nifest.58c5972fecd8f435b235873557cb99ac.js | 30 - .../resources/public/robots.txt | 2 - .../resources/public/service-worker.js | 38 - src/sample-golang-echo-app/router/router.go | 98 - src/sample-golang-echo-app/service/account.go | 40 - .../service/account_test.go | 42 - src/sample-golang-echo-app/service/book.go | 208 - .../service/book_test.go | 270 - .../service/category.go | 32 - .../service/category_test.go | 17 - src/sample-golang-echo-app/service/format.go | 32 - .../service/format_test.go | 17 - src/sample-golang-echo-app/session/session.go | 118 - .../test/unittest_util.go | 158 - src/sample-golang-echo-app/util/props.go | 60 - src/sample-golang-echo-app/util/props_test.go | 32 - .../util/request_builder.go | 90 - .../util/request_builder_test.go | 24 - src/sample-golang-echo-app/util/strings.go | 23 - .../util/strings_test.go | 32 - .../util/test.properties | 7 - src/sample-node-remix-app/.dockerignore | 7 - src/sample-node-remix-app/.env.example | 2 - src/sample-node-remix-app/.eslintrc.js | 22 - .../.github/workflows/deploy.yml | 145 - src/sample-node-remix-app/.gitignore | 10 - src/sample-node-remix-app/.gitpod.Dockerfile | 9 - src/sample-node-remix-app/.gitpod.yml | 48 - src/sample-node-remix-app/.npmrc | 1 - src/sample-node-remix-app/.prettierignore | 7 - src/sample-node-remix-app/Dockerfile | 61 - src/sample-node-remix-app/README.md | 174 - src/sample-node-remix-app/app/db.server.js | 19 - .../app/entry.client.jsx | 18 - .../app/entry.server.jsx | 121 - .../app/models/note.server.js | 36 - .../app/models/user.server.js | 56 - src/sample-node-remix-app/app/root.jsx | 42 - .../app/routes/_index.jsx | 141 - .../app/routes/healthcheck.jsx | 24 - src/sample-node-remix-app/app/routes/join.jsx | 167 - .../app/routes/login.jsx | 177 - .../app/routes/logout.jsx | 7 - .../app/routes/notes.$noteId.jsx | 69 - .../app/routes/notes._index.jsx | 12 - .../app/routes/notes.jsx | 69 - .../app/routes/notes.new.jsx | 108 - .../app/session.server.js | 89 - src/sample-node-remix-app/app/tailwind.css | 3 - src/sample-node-remix-app/app/utils.js | 64 - src/sample-node-remix-app/app/utils.test.js | 13 - src/sample-node-remix-app/cypress.config.ts | 27 - .../cypress/.eslintrc.js | 6 - .../cypress/e2e/smoke.cy.ts | 51 - .../cypress/fixtures/example.json | 5 - .../cypress/support/commands.ts | 95 - .../cypress/support/create-user.ts | 48 - .../cypress/support/delete-user.ts | 37 - .../cypress/support/e2e.ts | 15 - .../cypress/tsconfig.json | 29 - src/sample-node-remix-app/fly.toml | 50 - src/sample-node-remix-app/mocks/README.md | 7 - src/sample-node-remix-app/mocks/index.js | 9 - src/sample-node-remix-app/package-lock.json | 16461 ---------------- src/sample-node-remix-app/package.json | 84 - src/sample-node-remix-app/postcss.config.js | 6 - .../20220713162558_init/migration.sql | 31 - .../prisma/migrations/migration_lock.toml | 3 - .../prisma/schema.prisma | 38 - src/sample-node-remix-app/prisma/seed.ts | 53 - src/sample-node-remix-app/public/favicon.ico | Bin 16958 -> 0 bytes src/sample-node-remix-app/remix.config.js | 14 - src/sample-node-remix-app/start.sh | 17 - src/sample-node-remix-app/tailwind.config.ts | 9 - .../test/setup-test-env.ts | 4 - src/sample-node-remix-app/tsconfig.json | 26 - src/sample-node-remix-app/vitest.config.ts | 15 - src/sample-php-laravel-app/.editorconfig | 18 - src/sample-php-laravel-app/.env.example | 58 - src/sample-php-laravel-app/.gitattributes | 11 - src/sample-php-laravel-app/.gitignore | 19 - src/sample-php-laravel-app/README.md | 66 - .../app/Console/Kernel.php | 27 - .../app/Exceptions/Handler.php | 30 - .../app/Http/Controllers/Controller.php | 12 - .../app/Http/Kernel.php | 67 - .../app/Http/Middleware/Authenticate.php | 17 - .../app/Http/Middleware/EncryptCookies.php | 17 - .../PreventRequestsDuringMaintenance.php | 17 - .../Middleware/RedirectIfAuthenticated.php | 30 - .../app/Http/Middleware/TrimStrings.php | 19 - .../app/Http/Middleware/TrustHosts.php | 20 - .../app/Http/Middleware/TrustProxies.php | 28 - .../app/Http/Middleware/ValidateSignature.php | 22 - .../app/Http/Middleware/VerifyCsrfToken.php | 17 - .../app/Models/User.php | 45 - .../app/Providers/AppServiceProvider.php | 24 - .../app/Providers/AuthServiceProvider.php | 26 - .../Providers/BroadcastServiceProvider.php | 19 - .../app/Providers/EventServiceProvider.php | 38 - .../app/Providers/RouteServiceProvider.php | 40 - src/sample-php-laravel-app/artisan | 53 - src/sample-php-laravel-app/bootstrap/app.php | 55 - .../bootstrap/cache/.gitignore | 2 - src/sample-php-laravel-app/composer.json | 66 - src/sample-php-laravel-app/composer.lock | 7894 -------- src/sample-php-laravel-app/config/app.php | 188 - src/sample-php-laravel-app/config/auth.php | 115 - .../config/broadcasting.php | 71 - src/sample-php-laravel-app/config/cache.php | 110 - src/sample-php-laravel-app/config/cors.php | 34 - .../config/database.php | 151 - .../config/filesystems.php | 76 - src/sample-php-laravel-app/config/hashing.php | 52 - src/sample-php-laravel-app/config/logging.php | 131 - src/sample-php-laravel-app/config/mail.php | 125 - src/sample-php-laravel-app/config/queue.php | 109 - src/sample-php-laravel-app/config/sanctum.php | 67 - .../config/services.php | 34 - src/sample-php-laravel-app/config/session.php | 201 - src/sample-php-laravel-app/config/view.php | 36 - .../database/.gitignore | 1 - .../database/factories/UserFactory.php | 38 - .../2014_10_12_000000_create_users_table.php | 32 - ...000_create_password_reset_tokens_table.php | 28 - ..._08_19_000000_create_failed_jobs_table.php | 32 - ...01_create_personal_access_tokens_table.php | 33 - .../database/seeders/DatabaseSeeder.php | 22 - src/sample-php-laravel-app/docker-compose.yml | 111 - src/sample-php-laravel-app/package.json | 13 - src/sample-php-laravel-app/phpunit.xml | 30 - src/sample-php-laravel-app/public/.htaccess | 21 - src/sample-php-laravel-app/public/favicon.ico | 0 src/sample-php-laravel-app/public/index.php | 55 - src/sample-php-laravel-app/public/robots.txt | 2 - .../resources/css/app.css | 0 .../resources/js/app.js | 1 - .../resources/js/bootstrap.js | 32 - .../resources/views/welcome.blade.php | 140 - src/sample-php-laravel-app/routes/api.php | 19 - .../routes/channels.php | 18 - src/sample-php-laravel-app/routes/console.php | 19 - src/sample-php-laravel-app/routes/web.php | 18 - .../storage/app/.gitignore | 3 - .../storage/app/public/.gitignore | 2 - .../storage/framework/.gitignore | 9 - .../storage/framework/cache/.gitignore | 3 - .../storage/framework/cache/data/.gitignore | 2 - .../storage/framework/sessions/.gitignore | 2 - .../storage/framework/testing/.gitignore | 2 - .../storage/framework/views/.gitignore | 2 - .../storage/logs/.gitignore | 2 - .../tests/CreatesApplication.php | 21 - .../tests/Feature/ExampleTest.php | 19 - src/sample-php-laravel-app/tests/TestCase.php | 10 - .../tests/Unit/ExampleTest.php | 16 - src/sample-php-laravel-app/vite.config.js | 11 - src/sample-python-django-app/main/__init__.py | 0 src/sample-python-django-app/main/admin.py | 3 - src/sample-python-django-app/main/apps.py | 12 - .../main/migrations/__init__.py | 0 src/sample-python-django-app/main/models.py | 3 - src/sample-python-django-app/main/tests.py | 3 - src/sample-python-django-app/main/views.py | 3 - src/sample-python-django-app/manage.py | 22 - .../python_django_app/__init__.py | 0 .../__pycache__/__init__.cpython-311.pyc | Bin 226 -> 0 bytes .../__pycache__/settings.cpython-311.pyc | Bin 2684 -> 0 bytes .../python_django_app/asgi.py | 16 - .../python_django_app/settings.py | 127 - .../python_django_app/urls.py | 22 - .../python_django_app/wsgi.py | 16 - src/sample-ruby-rails-app/.gitattributes | 10 - src/sample-ruby-rails-app/.gitignore | 33 - src/sample-ruby-rails-app/.ruby-version | 1 - src/sample-ruby-rails-app/Gemfile | 56 - src/sample-ruby-rails-app/Gemfile.lock | 244 - src/sample-ruby-rails-app/README.md | 24 - src/sample-ruby-rails-app/Rakefile | 6 - .../app/assets/config/manifest.js | 2 - .../app/assets/images/.keep | 0 .../app/assets/stylesheets/application.css | 15 - .../app/channels/application_cable/channel.rb | 4 - .../channels/application_cable/connection.rb | 4 - .../app/controllers/application_controller.rb | 2 - .../app/controllers/concerns/.keep | 0 .../app/helpers/application_helper.rb | 2 - .../app/javascript/channels/consumer.js | 6 - .../app/javascript/channels/index.js | 5 - .../app/javascript/packs/application.js | 13 - .../app/jobs/application_job.rb | 7 - .../app/mailers/application_mailer.rb | 4 - .../app/models/application_record.rb | 3 - .../app/models/concerns/.keep | 0 .../app/views/layouts/application.html.erb | 16 - .../app/views/layouts/mailer.html.erb | 13 - .../app/views/layouts/mailer.text.erb | 1 - src/sample-ruby-rails-app/bin/bundle | 109 - src/sample-ruby-rails-app/bin/rails | 5 - src/sample-ruby-rails-app/bin/rake | 5 - src/sample-ruby-rails-app/bin/setup | 36 - src/sample-ruby-rails-app/bin/spring | 14 - src/sample-ruby-rails-app/bin/yarn | 17 - src/sample-ruby-rails-app/config.ru | 6 - .../config/application.rb | 22 - src/sample-ruby-rails-app/config/boot.rb | 4 - src/sample-ruby-rails-app/config/cable.yml | 10 - .../config/credentials.yml.enc | 1 - src/sample-ruby-rails-app/config/database.yml | 25 - .../config/environment.rb | 5 - .../config/environments/development.rb | 76 - .../config/environments/production.rb | 120 - .../config/environments/test.rb | 60 - .../application_controller_renderer.rb | 8 - .../config/initializers/assets.rb | 14 - .../initializers/backtrace_silencers.rb | 8 - .../initializers/content_security_policy.rb | 30 - .../config/initializers/cookies_serializer.rb | 5 - .../initializers/filter_parameter_logging.rb | 6 - .../config/initializers/inflections.rb | 16 - .../config/initializers/mime_types.rb | 4 - .../config/initializers/permissions_policy.rb | 11 - .../config/initializers/wrap_parameters.rb | 14 - .../config/locales/en.yml | 33 - src/sample-ruby-rails-app/config/puma.rb | 43 - src/sample-ruby-rails-app/config/routes.rb | 3 - src/sample-ruby-rails-app/config/spring.rb | 6 - src/sample-ruby-rails-app/config/storage.yml | 34 - src/sample-ruby-rails-app/db/seeds.rb | 7 - src/sample-ruby-rails-app/lib/assets/.keep | 0 src/sample-ruby-rails-app/lib/tasks/.keep | 0 src/sample-ruby-rails-app/log/.keep | 0 src/sample-ruby-rails-app/package.json | 11 - src/sample-ruby-rails-app/public/404.html | 67 - src/sample-ruby-rails-app/public/422.html | 67 - src/sample-ruby-rails-app/public/500.html | 66 - .../public/apple-touch-icon-precomposed.png | 0 .../public/apple-touch-icon.png | 0 src/sample-ruby-rails-app/public/favicon.ico | 0 src/sample-ruby-rails-app/public/robots.txt | 1 - src/sample-ruby-rails-app/storage/.keep | 0 .../test/application_system_test_case.rb | 5 - .../application_cable/connection_test.rb | 11 - .../test/controllers/.keep | 0 .../test/fixtures/files/.keep | 0 src/sample-ruby-rails-app/test/helpers/.keep | 0 .../test/integration/.keep | 0 src/sample-ruby-rails-app/test/mailers/.keep | 0 src/sample-ruby-rails-app/test/models/.keep | 0 src/sample-ruby-rails-app/test/system/.keep | 0 src/sample-ruby-rails-app/test/test_helper.rb | 13 - src/sample-ruby-rails-app/tmp/.keep | 0 src/sample-ruby-rails-app/tmp/pids/.keep | 0 src/sample-ruby-rails-app/vendor/.keep | 0 337 files changed, 2 insertions(+), 37692 deletions(-) delete mode 100644 ci/.gitlab-ci-golang.yml delete mode 100644 ci/.gitlab-ci-node.yml delete mode 100644 ci/.gitlab-ci-php.yml delete mode 100644 ci/.gitlab-ci-python.yml delete mode 100644 ci/.gitlab-ci-ruby.yml delete mode 100644 src/sample-golang-echo-app/.github/.golangci.yml delete mode 100644 src/sample-golang-echo-app/.github/ISSUE_TEMPLATE/bug_report.md delete mode 100644 src/sample-golang-echo-app/.github/ISSUE_TEMPLATE/feature_request.md delete mode 100644 src/sample-golang-echo-app/.github/dependabot.yml delete mode 100644 src/sample-golang-echo-app/.github/pull_request_template.md delete mode 100644 src/sample-golang-echo-app/.github/workflows/check.yml delete mode 100644 src/sample-golang-echo-app/.github/workflows/release.yml delete mode 100644 src/sample-golang-echo-app/.gitignore delete mode 100644 src/sample-golang-echo-app/.goreleaser.yml delete mode 100644 src/sample-golang-echo-app/CONTRIBUTING.md delete mode 100644 src/sample-golang-echo-app/LICENSE delete mode 100644 src/sample-golang-echo-app/README.md delete mode 100644 src/sample-golang-echo-app/config/config.go delete mode 100644 src/sample-golang-echo-app/config/const.go delete mode 100644 src/sample-golang-echo-app/container/container.go delete mode 100644 src/sample-golang-echo-app/controller/account.go delete mode 100644 src/sample-golang-echo-app/controller/account_test.go delete mode 100644 src/sample-golang-echo-app/controller/book.go delete mode 100644 src/sample-golang-echo-app/controller/book_test.go delete mode 100644 src/sample-golang-echo-app/controller/category.go delete mode 100644 src/sample-golang-echo-app/controller/category_test.go delete mode 100644 src/sample-golang-echo-app/controller/error.go delete mode 100644 src/sample-golang-echo-app/controller/error_test.go delete mode 100644 src/sample-golang-echo-app/controller/format.go delete mode 100644 src/sample-golang-echo-app/controller/format_test.go delete mode 100644 src/sample-golang-echo-app/controller/health.go delete mode 100644 src/sample-golang-echo-app/controller/health_test.go delete mode 100644 src/sample-golang-echo-app/controller/logger_test.go delete mode 100644 src/sample-golang-echo-app/controller/swagger_test.go delete mode 100644 src/sample-golang-echo-app/docs/docs.go delete mode 100644 src/sample-golang-echo-app/go.mod delete mode 100644 src/sample-golang-echo-app/go.sum delete mode 100644 src/sample-golang-echo-app/logger/gormlogger.go delete mode 100644 src/sample-golang-echo-app/logger/logger.go delete mode 100644 src/sample-golang-echo-app/logger/zaplogger.go delete mode 100644 src/sample-golang-echo-app/main.go delete mode 100644 src/sample-golang-echo-app/middleware/middleware.go delete mode 100644 src/sample-golang-echo-app/migration/db_generator.go delete mode 100644 src/sample-golang-echo-app/migration/master_generator.go delete mode 100644 src/sample-golang-echo-app/model/account.go delete mode 100644 src/sample-golang-echo-app/model/authority.go delete mode 100644 src/sample-golang-echo-app/model/base.go delete mode 100644 src/sample-golang-echo-app/model/book.go delete mode 100644 src/sample-golang-echo-app/model/category.go delete mode 100644 src/sample-golang-echo-app/model/dto/account.go delete mode 100644 src/sample-golang-echo-app/model/dto/book.go delete mode 100644 src/sample-golang-echo-app/model/dto/book_test.go delete mode 100644 src/sample-golang-echo-app/model/format.go delete mode 100644 src/sample-golang-echo-app/model/page.go delete mode 100644 src/sample-golang-echo-app/repository/repository.go delete mode 100644 src/sample-golang-echo-app/resources/config/application.develop.yml delete mode 100644 src/sample-golang-echo-app/resources/config/application.docker.yml delete mode 100644 src/sample-golang-echo-app/resources/config/application.k8s.yml delete mode 100644 src/sample-golang-echo-app/resources/config/messages.properties delete mode 100644 src/sample-golang-echo-app/resources/config/zaplogger.develop.yml delete mode 100644 src/sample-golang-echo-app/resources/config/zaplogger.docker.yml delete mode 100644 src/sample-golang-echo-app/resources/config/zaplogger.k8s.yml delete mode 100644 src/sample-golang-echo-app/resources/public/css/app.750b60b0.css delete mode 100644 src/sample-golang-echo-app/resources/public/favicon.ico delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/android-chrome-192x192.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/android-chrome-512x512.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-120x120.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-152x152.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-180x180.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-60x60.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-76x76.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/favicon-16x16.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/favicon-32x32.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/msapplication-icon-144x144.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/mstile-150x150.png delete mode 100644 src/sample-golang-echo-app/resources/public/img/icons/safari-pinned-tab.svg delete mode 100644 src/sample-golang-echo-app/resources/public/index.html delete mode 100644 src/sample-golang-echo-app/resources/public/js/app.b3c19b4f.js delete mode 100644 src/sample-golang-echo-app/resources/public/js/chunk-vendors.2abeaede.js delete mode 100644 src/sample-golang-echo-app/resources/public/manifest.json delete mode 100644 src/sample-golang-echo-app/resources/public/precache-manifest.58c5972fecd8f435b235873557cb99ac.js delete mode 100644 src/sample-golang-echo-app/resources/public/robots.txt delete mode 100644 src/sample-golang-echo-app/resources/public/service-worker.js delete mode 100644 src/sample-golang-echo-app/router/router.go delete mode 100644 src/sample-golang-echo-app/service/account.go delete mode 100644 src/sample-golang-echo-app/service/account_test.go delete mode 100644 src/sample-golang-echo-app/service/book.go delete mode 100644 src/sample-golang-echo-app/service/book_test.go delete mode 100644 src/sample-golang-echo-app/service/category.go delete mode 100644 src/sample-golang-echo-app/service/category_test.go delete mode 100644 src/sample-golang-echo-app/service/format.go delete mode 100644 src/sample-golang-echo-app/service/format_test.go delete mode 100644 src/sample-golang-echo-app/session/session.go delete mode 100644 src/sample-golang-echo-app/test/unittest_util.go delete mode 100644 src/sample-golang-echo-app/util/props.go delete mode 100644 src/sample-golang-echo-app/util/props_test.go delete mode 100644 src/sample-golang-echo-app/util/request_builder.go delete mode 100644 src/sample-golang-echo-app/util/request_builder_test.go delete mode 100644 src/sample-golang-echo-app/util/strings.go delete mode 100644 src/sample-golang-echo-app/util/strings_test.go delete mode 100644 src/sample-golang-echo-app/util/test.properties delete mode 100644 src/sample-node-remix-app/.dockerignore delete mode 100644 src/sample-node-remix-app/.env.example delete mode 100644 src/sample-node-remix-app/.eslintrc.js delete mode 100644 src/sample-node-remix-app/.github/workflows/deploy.yml delete mode 100644 src/sample-node-remix-app/.gitignore delete mode 100644 src/sample-node-remix-app/.gitpod.Dockerfile delete mode 100644 src/sample-node-remix-app/.gitpod.yml delete mode 100644 src/sample-node-remix-app/.npmrc delete mode 100644 src/sample-node-remix-app/.prettierignore delete mode 100644 src/sample-node-remix-app/Dockerfile delete mode 100644 src/sample-node-remix-app/README.md delete mode 100644 src/sample-node-remix-app/app/db.server.js delete mode 100644 src/sample-node-remix-app/app/entry.client.jsx delete mode 100644 src/sample-node-remix-app/app/entry.server.jsx delete mode 100644 src/sample-node-remix-app/app/models/note.server.js delete mode 100644 src/sample-node-remix-app/app/models/user.server.js delete mode 100644 src/sample-node-remix-app/app/root.jsx delete mode 100644 src/sample-node-remix-app/app/routes/_index.jsx delete mode 100644 src/sample-node-remix-app/app/routes/healthcheck.jsx delete mode 100644 src/sample-node-remix-app/app/routes/join.jsx delete mode 100644 src/sample-node-remix-app/app/routes/login.jsx delete mode 100644 src/sample-node-remix-app/app/routes/logout.jsx delete mode 100644 src/sample-node-remix-app/app/routes/notes.$noteId.jsx delete mode 100644 src/sample-node-remix-app/app/routes/notes._index.jsx delete mode 100644 src/sample-node-remix-app/app/routes/notes.jsx delete mode 100644 src/sample-node-remix-app/app/routes/notes.new.jsx delete mode 100644 src/sample-node-remix-app/app/session.server.js delete mode 100644 src/sample-node-remix-app/app/tailwind.css delete mode 100644 src/sample-node-remix-app/app/utils.js delete mode 100644 src/sample-node-remix-app/app/utils.test.js delete mode 100644 src/sample-node-remix-app/cypress.config.ts delete mode 100644 src/sample-node-remix-app/cypress/.eslintrc.js delete mode 100644 src/sample-node-remix-app/cypress/e2e/smoke.cy.ts delete mode 100644 src/sample-node-remix-app/cypress/fixtures/example.json delete mode 100644 src/sample-node-remix-app/cypress/support/commands.ts delete mode 100644 src/sample-node-remix-app/cypress/support/create-user.ts delete mode 100644 src/sample-node-remix-app/cypress/support/delete-user.ts delete mode 100644 src/sample-node-remix-app/cypress/support/e2e.ts delete mode 100644 src/sample-node-remix-app/cypress/tsconfig.json delete mode 100644 src/sample-node-remix-app/fly.toml delete mode 100644 src/sample-node-remix-app/mocks/README.md delete mode 100644 src/sample-node-remix-app/mocks/index.js delete mode 100644 src/sample-node-remix-app/package-lock.json delete mode 100644 src/sample-node-remix-app/package.json delete mode 100644 src/sample-node-remix-app/postcss.config.js delete mode 100644 src/sample-node-remix-app/prisma/migrations/20220713162558_init/migration.sql delete mode 100644 src/sample-node-remix-app/prisma/migrations/migration_lock.toml delete mode 100644 src/sample-node-remix-app/prisma/schema.prisma delete mode 100644 src/sample-node-remix-app/prisma/seed.ts delete mode 100644 src/sample-node-remix-app/public/favicon.ico delete mode 100644 src/sample-node-remix-app/remix.config.js delete mode 100755 src/sample-node-remix-app/start.sh delete mode 100644 src/sample-node-remix-app/tailwind.config.ts delete mode 100644 src/sample-node-remix-app/test/setup-test-env.ts delete mode 100644 src/sample-node-remix-app/tsconfig.json delete mode 100644 src/sample-node-remix-app/vitest.config.ts delete mode 100644 src/sample-php-laravel-app/.editorconfig delete mode 100644 src/sample-php-laravel-app/.env.example delete mode 100644 src/sample-php-laravel-app/.gitattributes delete mode 100644 src/sample-php-laravel-app/.gitignore delete mode 100644 src/sample-php-laravel-app/README.md delete mode 100644 src/sample-php-laravel-app/app/Console/Kernel.php delete mode 100644 src/sample-php-laravel-app/app/Exceptions/Handler.php delete mode 100644 src/sample-php-laravel-app/app/Http/Controllers/Controller.php delete mode 100644 src/sample-php-laravel-app/app/Http/Kernel.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/Authenticate.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/EncryptCookies.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/PreventRequestsDuringMaintenance.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/RedirectIfAuthenticated.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/TrimStrings.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/TrustHosts.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/TrustProxies.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/ValidateSignature.php delete mode 100644 src/sample-php-laravel-app/app/Http/Middleware/VerifyCsrfToken.php delete mode 100644 src/sample-php-laravel-app/app/Models/User.php delete mode 100644 src/sample-php-laravel-app/app/Providers/AppServiceProvider.php delete mode 100644 src/sample-php-laravel-app/app/Providers/AuthServiceProvider.php delete mode 100644 src/sample-php-laravel-app/app/Providers/BroadcastServiceProvider.php delete mode 100644 src/sample-php-laravel-app/app/Providers/EventServiceProvider.php delete mode 100644 src/sample-php-laravel-app/app/Providers/RouteServiceProvider.php delete mode 100755 src/sample-php-laravel-app/artisan delete mode 100644 src/sample-php-laravel-app/bootstrap/app.php delete mode 100644 src/sample-php-laravel-app/bootstrap/cache/.gitignore delete mode 100644 src/sample-php-laravel-app/composer.json delete mode 100644 src/sample-php-laravel-app/composer.lock delete mode 100644 src/sample-php-laravel-app/config/app.php delete mode 100644 src/sample-php-laravel-app/config/auth.php delete mode 100644 src/sample-php-laravel-app/config/broadcasting.php delete mode 100644 src/sample-php-laravel-app/config/cache.php delete mode 100644 src/sample-php-laravel-app/config/cors.php delete mode 100644 src/sample-php-laravel-app/config/database.php delete mode 100644 src/sample-php-laravel-app/config/filesystems.php delete mode 100644 src/sample-php-laravel-app/config/hashing.php delete mode 100644 src/sample-php-laravel-app/config/logging.php delete mode 100644 src/sample-php-laravel-app/config/mail.php delete mode 100644 src/sample-php-laravel-app/config/queue.php delete mode 100644 src/sample-php-laravel-app/config/sanctum.php delete mode 100644 src/sample-php-laravel-app/config/services.php delete mode 100644 src/sample-php-laravel-app/config/session.php delete mode 100644 src/sample-php-laravel-app/config/view.php delete mode 100644 src/sample-php-laravel-app/database/.gitignore delete mode 100644 src/sample-php-laravel-app/database/factories/UserFactory.php delete mode 100644 src/sample-php-laravel-app/database/migrations/2014_10_12_000000_create_users_table.php delete mode 100644 src/sample-php-laravel-app/database/migrations/2014_10_12_100000_create_password_reset_tokens_table.php delete mode 100644 src/sample-php-laravel-app/database/migrations/2019_08_19_000000_create_failed_jobs_table.php delete mode 100644 src/sample-php-laravel-app/database/migrations/2019_12_14_000001_create_personal_access_tokens_table.php delete mode 100644 src/sample-php-laravel-app/database/seeders/DatabaseSeeder.php delete mode 100644 src/sample-php-laravel-app/docker-compose.yml delete mode 100644 src/sample-php-laravel-app/package.json delete mode 100644 src/sample-php-laravel-app/phpunit.xml delete mode 100644 src/sample-php-laravel-app/public/.htaccess delete mode 100644 src/sample-php-laravel-app/public/favicon.ico delete mode 100644 src/sample-php-laravel-app/public/index.php delete mode 100644 src/sample-php-laravel-app/public/robots.txt delete mode 100644 src/sample-php-laravel-app/resources/css/app.css delete mode 100644 src/sample-php-laravel-app/resources/js/app.js delete mode 100644 src/sample-php-laravel-app/resources/js/bootstrap.js delete mode 100644 src/sample-php-laravel-app/resources/views/welcome.blade.php delete mode 100644 src/sample-php-laravel-app/routes/api.php delete mode 100644 src/sample-php-laravel-app/routes/channels.php delete mode 100644 src/sample-php-laravel-app/routes/console.php delete mode 100644 src/sample-php-laravel-app/routes/web.php delete mode 100644 src/sample-php-laravel-app/storage/app/.gitignore delete mode 100644 src/sample-php-laravel-app/storage/app/public/.gitignore delete mode 100644 src/sample-php-laravel-app/storage/framework/.gitignore delete mode 100644 src/sample-php-laravel-app/storage/framework/cache/.gitignore delete mode 100644 src/sample-php-laravel-app/storage/framework/cache/data/.gitignore delete mode 100644 src/sample-php-laravel-app/storage/framework/sessions/.gitignore delete mode 100644 src/sample-php-laravel-app/storage/framework/testing/.gitignore delete mode 100644 src/sample-php-laravel-app/storage/framework/views/.gitignore delete mode 100644 src/sample-php-laravel-app/storage/logs/.gitignore delete mode 100644 src/sample-php-laravel-app/tests/CreatesApplication.php delete mode 100644 src/sample-php-laravel-app/tests/Feature/ExampleTest.php delete mode 100644 src/sample-php-laravel-app/tests/TestCase.php delete mode 100644 src/sample-php-laravel-app/tests/Unit/ExampleTest.php delete mode 100644 src/sample-php-laravel-app/vite.config.js delete mode 100644 src/sample-python-django-app/main/__init__.py delete mode 100644 src/sample-python-django-app/main/admin.py delete mode 100644 src/sample-python-django-app/main/apps.py delete mode 100644 src/sample-python-django-app/main/migrations/__init__.py delete mode 100644 src/sample-python-django-app/main/models.py delete mode 100644 src/sample-python-django-app/main/tests.py delete mode 100644 src/sample-python-django-app/main/views.py delete mode 100755 src/sample-python-django-app/manage.py delete mode 100644 src/sample-python-django-app/python_django_app/__init__.py delete mode 100644 src/sample-python-django-app/python_django_app/__pycache__/__init__.cpython-311.pyc delete mode 100644 src/sample-python-django-app/python_django_app/__pycache__/settings.cpython-311.pyc delete mode 100644 src/sample-python-django-app/python_django_app/asgi.py delete mode 100644 src/sample-python-django-app/python_django_app/settings.py delete mode 100644 src/sample-python-django-app/python_django_app/urls.py delete mode 100644 src/sample-python-django-app/python_django_app/wsgi.py delete mode 100644 src/sample-ruby-rails-app/.gitattributes delete mode 100644 src/sample-ruby-rails-app/.gitignore delete mode 100644 src/sample-ruby-rails-app/.ruby-version delete mode 100644 src/sample-ruby-rails-app/Gemfile delete mode 100644 src/sample-ruby-rails-app/Gemfile.lock delete mode 100644 src/sample-ruby-rails-app/README.md delete mode 100644 src/sample-ruby-rails-app/Rakefile delete mode 100644 src/sample-ruby-rails-app/app/assets/config/manifest.js delete mode 100644 src/sample-ruby-rails-app/app/assets/images/.keep delete mode 100644 src/sample-ruby-rails-app/app/assets/stylesheets/application.css delete mode 100644 src/sample-ruby-rails-app/app/channels/application_cable/channel.rb delete mode 100644 src/sample-ruby-rails-app/app/channels/application_cable/connection.rb delete mode 100644 src/sample-ruby-rails-app/app/controllers/application_controller.rb delete mode 100644 src/sample-ruby-rails-app/app/controllers/concerns/.keep delete mode 100644 src/sample-ruby-rails-app/app/helpers/application_helper.rb delete mode 100644 src/sample-ruby-rails-app/app/javascript/channels/consumer.js delete mode 100644 src/sample-ruby-rails-app/app/javascript/channels/index.js delete mode 100644 src/sample-ruby-rails-app/app/javascript/packs/application.js delete mode 100644 src/sample-ruby-rails-app/app/jobs/application_job.rb delete mode 100644 src/sample-ruby-rails-app/app/mailers/application_mailer.rb delete mode 100644 src/sample-ruby-rails-app/app/models/application_record.rb delete mode 100644 src/sample-ruby-rails-app/app/models/concerns/.keep delete mode 100644 src/sample-ruby-rails-app/app/views/layouts/application.html.erb delete mode 100644 src/sample-ruby-rails-app/app/views/layouts/mailer.html.erb delete mode 100644 src/sample-ruby-rails-app/app/views/layouts/mailer.text.erb delete mode 100755 src/sample-ruby-rails-app/bin/bundle delete mode 100755 src/sample-ruby-rails-app/bin/rails delete mode 100755 src/sample-ruby-rails-app/bin/rake delete mode 100755 src/sample-ruby-rails-app/bin/setup delete mode 100755 src/sample-ruby-rails-app/bin/spring delete mode 100755 src/sample-ruby-rails-app/bin/yarn delete mode 100644 src/sample-ruby-rails-app/config.ru delete mode 100644 src/sample-ruby-rails-app/config/application.rb delete mode 100644 src/sample-ruby-rails-app/config/boot.rb delete mode 100644 src/sample-ruby-rails-app/config/cable.yml delete mode 100644 src/sample-ruby-rails-app/config/credentials.yml.enc delete mode 100644 src/sample-ruby-rails-app/config/database.yml delete mode 100644 src/sample-ruby-rails-app/config/environment.rb delete mode 100644 src/sample-ruby-rails-app/config/environments/development.rb delete mode 100644 src/sample-ruby-rails-app/config/environments/production.rb delete mode 100644 src/sample-ruby-rails-app/config/environments/test.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/application_controller_renderer.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/assets.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/backtrace_silencers.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/content_security_policy.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/cookies_serializer.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/filter_parameter_logging.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/inflections.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/mime_types.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/permissions_policy.rb delete mode 100644 src/sample-ruby-rails-app/config/initializers/wrap_parameters.rb delete mode 100644 src/sample-ruby-rails-app/config/locales/en.yml delete mode 100644 src/sample-ruby-rails-app/config/puma.rb delete mode 100644 src/sample-ruby-rails-app/config/routes.rb delete mode 100644 src/sample-ruby-rails-app/config/spring.rb delete mode 100644 src/sample-ruby-rails-app/config/storage.yml delete mode 100644 src/sample-ruby-rails-app/db/seeds.rb delete mode 100644 src/sample-ruby-rails-app/lib/assets/.keep delete mode 100644 src/sample-ruby-rails-app/lib/tasks/.keep delete mode 100644 src/sample-ruby-rails-app/log/.keep delete mode 100644 src/sample-ruby-rails-app/package.json delete mode 100644 src/sample-ruby-rails-app/public/404.html delete mode 100644 src/sample-ruby-rails-app/public/422.html delete mode 100644 src/sample-ruby-rails-app/public/500.html delete mode 100644 src/sample-ruby-rails-app/public/apple-touch-icon-precomposed.png delete mode 100644 src/sample-ruby-rails-app/public/apple-touch-icon.png delete mode 100644 src/sample-ruby-rails-app/public/favicon.ico delete mode 100644 src/sample-ruby-rails-app/public/robots.txt delete mode 100644 src/sample-ruby-rails-app/storage/.keep delete mode 100644 src/sample-ruby-rails-app/test/application_system_test_case.rb delete mode 100644 src/sample-ruby-rails-app/test/channels/application_cable/connection_test.rb delete mode 100644 src/sample-ruby-rails-app/test/controllers/.keep delete mode 100644 src/sample-ruby-rails-app/test/fixtures/files/.keep delete mode 100644 src/sample-ruby-rails-app/test/helpers/.keep delete mode 100644 src/sample-ruby-rails-app/test/integration/.keep delete mode 100644 src/sample-ruby-rails-app/test/mailers/.keep delete mode 100644 src/sample-ruby-rails-app/test/models/.keep delete mode 100644 src/sample-ruby-rails-app/test/system/.keep delete mode 100644 src/sample-ruby-rails-app/test/test_helper.rb delete mode 100644 src/sample-ruby-rails-app/tmp/.keep delete mode 100644 src/sample-ruby-rails-app/tmp/pids/.keep delete mode 100644 src/sample-ruby-rails-app/vendor/.keep diff --git a/.codeclimate.yml b/.codeclimate.yml index a3b9601..1a58b3e 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -1,12 +1,6 @@ --- version: "2" plugins: - govet: - enabled: true - sonar-php: - enabled: true - sonar-python: - enabled: true csslint: enabled: true coffeelint: diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f68dd78..21cbb61 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,29 +4,16 @@ workflow: - if: $CI_COMMIT_TAG - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH -stages: [test, qa, release] +stages: [test, release] include: - - local: 'ci/.gitlab-ci-golang.yml' - - local: 'ci/.gitlab-ci-node.yml' - - local: 'ci/.gitlab-ci-python.yml' - - local: 'ci/.gitlab-ci-php.yml' - - local: 'ci/.gitlab-ci-ruby.yml' - component: $CI_SERVER_FQDN/$CI_PROJECT_PATH/code-quality@$CI_COMMIT_SHA inputs: - stage: qa - debug: "1" + stage: test -# We customize the `code_quality` job to enable verbose logs (for debugging), and to also save the -# code quality report so it can be inspected by other jobs to verify it works correctly. code_quality: - artifacts: - paths: - - gl-code-quality-report.json rules: - if: $CI_MERGE_REQUEST_ID || $CI_COMMIT_TAG || $CI_COMMIT_BRANCH - tags: - - saas-linux-medium-amd64 ensure-job-added: stage: test diff --git a/ci/.gitlab-ci-golang.yml b/ci/.gitlab-ci-golang.yml deleted file mode 100644 index 57f91f6..0000000 --- a/ci/.gitlab-ci-golang.yml +++ /dev/null @@ -1,30 +0,0 @@ -run-golang-test: - image: golang:latest - stage: test - variables: - GO_ENV: test - before_script: - - go version # Print out golang version for debugging - - apt-get update - - apt-get install sqlite3 - - mkdir -p $GOPATH/src/gitlab.com/test-code-quality-ci-component/sample-golang-echo-app - - mv $CI_PROJECT_DIR/src/sample-golang-echo-app/* $GOPATH/src/gitlab.com/test-code-quality-ci-component/sample-golang-echo-app - - cd $GOPATH/src/gitlab.com/test-code-quality-ci-component/sample-golang-echo-app - - go install github.com/ybkuroki/go-webapp-sample@latest - - go mod download - - export PATH="$PATH:$GOPATH/bin" - script: - - go test -v ./... - -verify-golang-findings: - image: badouralix/curl-jq - stage: qa - needs: - - code_quality - script: - - echo "Expect code_quality job will have at least 1 govet offense" - - | - count=`jq 'map(select(.engine_name=="govet")) | length' gl-code-quality-report.json` - if [ "$count" == "0" ]; then - exit 1 - fi diff --git a/ci/.gitlab-ci-node.yml b/ci/.gitlab-ci-node.yml deleted file mode 100644 index 1d44085..0000000 --- a/ci/.gitlab-ci-node.yml +++ /dev/null @@ -1,27 +0,0 @@ -run-node-test: - image: node:latest - stage: test - cache: - paths: - - node_modules/ - before_script: - - node -v # Print out node version for debugging - - cd src/sample-node-remix-app - - npm install - script: - - npm run test - -verify-node-findings: - image: badouralix/curl-jq - stage: qa - needs: - - code_quality - script: - - echo "Expect code_quality job will have at least 1 eslint, 1 structure and 1 duplication findings" - - | - eslint_count=`jq 'map(select(.engine_name=="eslint")) | length' gl-code-quality-report.json` - structure_count=`jq 'map(select(.engine_name=="structure")) | length' gl-code-quality-report.json` - duplication_count=`jq 'map(select(.engine_name=="duplication")) | length' gl-code-quality-report.json` - if [ "$eslint_count" == "0" ] || [ "$structure_count" == "0" ] || [ "$duplication_count" == "0" ]; then - exit 1 - fi diff --git a/ci/.gitlab-ci-php.yml b/ci/.gitlab-ci-php.yml deleted file mode 100644 index 0cc46cf..0000000 --- a/ci/.gitlab-ci-php.yml +++ /dev/null @@ -1,41 +0,0 @@ -run-php-laravel-test: - image: php:8.2-cli - stage: test - services: - - mysql:latest - variables: - MYSQL_DATABASE: php_laravel_app - MYSQL_ROOT_PASSWORD: secret - DB_HOST: mysql - DB_USERNAME: root - cache: - paths: - - vendor/ - before_script: - - php -v # Print out php version for debugging - - cd src/sample-php-laravel-app - - cp .env.example .env - - apt-get update - - apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev libzip-dev - - apt-get clean - - docker-php-ext-install pdo_mysql zip - - curl --silent --show-error "https://getcomposer.org/installer" | php -- --install-dir=/usr/local/bin --filename=composer - - composer install - - php artisan key:generate - - php artisan migrate - script: - - vendor/bin/phpunit - -verify-php-findings: - image: badouralix/curl-jq - stage: qa - needs: - - code_quality - script: - - echo "Expect code_quality job will have at least 1 sonar-php offense" - - | - count=`jq 'map(select(.engine_name=="sonar-php")) | length' gl-code-quality-report.json` - if [ "$count" == "0" ]; then - exit 1 - fi - diff --git a/ci/.gitlab-ci-python.yml b/ci/.gitlab-ci-python.yml deleted file mode 100644 index f71a5ba..0000000 --- a/ci/.gitlab-ci-python.yml +++ /dev/null @@ -1,41 +0,0 @@ -run-python-test: - image: ubuntu:20.04 - stage: test - services: - - mysql:latest - variables: - MYSQL_DATABASE: python_django_app - MYSQL_ROOT_PASSWORD: secret - cache: - paths: - - ~/.cache/pip/ - before_script: - - cd src/sample-python-django-app - - apt -y update - - apt -y install apt-utils - - DEBIAN_FRONTEND=noninteractive apt-get install -y tzdata - - apt -y install net-tools python3.8 python3-pip python3-venv mysql-client libmysqlclient-dev pkg-config - - apt -y upgrade - - python3 -m venv env - - source env/bin/activate - - env/bin/python -V # Print out python version for debugging - - env/bin/pip install Django mysqlclient - - env/bin/pip freeze > requirements.txt - - env/bin/pip install -r requirements.txt - script: - # The MYSQL user only gets permissions for MYSQL_DB, so Django can't create a test database. - - echo "GRANT ALL on *.* to 'root';"| mysql -u root --password="secret" -h mysql - - env/bin/python manage.py test - -verify-python-findings: - image: badouralix/curl-jq - stage: qa - needs: - - code_quality - script: - - echo "Expect code_quality job will have at least 1 sonar-python finding" - - | - count=`jq 'map(select(.engine_name=="sonar-python")) | length' gl-code-quality-report.json` - if [ "$count" == "0" ]; then - exit 1 - fi diff --git a/ci/.gitlab-ci-ruby.yml b/ci/.gitlab-ci-ruby.yml deleted file mode 100644 index 1f52e20..0000000 --- a/ci/.gitlab-ci-ruby.yml +++ /dev/null @@ -1,55 +0,0 @@ -run-ruby-rails-test: - image: ruby:3.2.2 - stage: test - cache: - paths: - - vendor/ruby - before_script: - - ruby -v # Print out ruby version for debugging - - apt-get update -q && apt-get install nodejs -yqq - - cd src/sample-ruby-rails-app - - bundle lock --add-platform aarch64-linux - - bundle config set --local deployment true - - bundle install -j $(nproc) - script: - - bin/rails test - -run-ruby-gem-test: - image: ruby:latest - stage: test - cache: - paths: - - vendor/ruby - before_script: - - ruby -v # Print out ruby version for debugging - - apt-get update -q && apt-get install nodejs -yqq - - cd src/sample-ruby-gem - - bin/setup - script: - - rake spec - -verify-ruby-rails-findings: - image: badouralix/curl-jq - stage: qa - needs: - - code_quality - script: - - echo "Expect code_quality job will have at least 1 rubocop offense" - - | - count=`jq 'map(select(.engine_name=="rubocop")) | length' gl-code-quality-report.json` - if [ "$count" == "0" ]; then - exit 1 - fi - -verify-ruby-gem-findings: - image: badouralix/curl-jq - stage: qa - needs: - - code_quality - script: - - echo "Expect code_quality job will have at least 1 fixme offense" - - | - count=`jq 'map(select(.engine_name=="fixme")) | length' gl-code-quality-report.json` - if [ "$count" == "0" ]; then - exit 1 - fi diff --git a/src/sample-golang-echo-app/.github/.golangci.yml b/src/sample-golang-echo-app/.github/.golangci.yml deleted file mode 100644 index 1801df1..0000000 --- a/src/sample-golang-echo-app/.github/.golangci.yml +++ /dev/null @@ -1,112 +0,0 @@ -# ref: https://github.com/golangci/golangci-lint/blob/master/.golangci.yml -# all available settings of specific linters -run: - concurrency: 4 - timeout: 3m - -linters-settings: - dupl: - threshold: 150 - funlen: - lines: 100 - statements: 50 - goconst: - min-len: 3 - min-occurrences: 2 - gocyclo: - min-complexity: 15 - gomnd: - # don't include the "operation" and "assign" - checks: - - argument - - case - - condition - - return - gofmt: - simplify: true - govet: - check-shadowing: true - settings: - printf: - funcs: - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf - - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf - lll: - line-length: 120 - misspell: - locale: US - nakedret: - max-func-lines: 20 - revive: - ignore-generated-header: true - severity: warning - confidence: 0.8 - errorCode: 0 - warningCode: 0 - rules: - - name: blank-imports - - name: context-as-argument - - name: context-keys-type - - name: dot-imports - - name: error-return - - name: error-strings - - name: error-naming - - name: exported - - name: if-return - - name: increment-decrement - - name: var-naming - - name: var-declaration - - name: package-comments - - name: range - - name: receiver-naming - - name: time-naming - - name: unexported-return - - name: indent-error-flow - - name: errorf - - name: empty-block - - name: superfluous-else - - name: unused-parameter - - name: unreachable-code - - name: redefines-builtin-id - -linters: - disable-all: true - enable: - # default - # ref: https://golangci-lint.run/usage/linters/ - #- deadcode - #- errcheck - #- gosimple - #- govet - #- ineffassign - #- staticcheck - #- structcheck - #- typecheck - #- unused - #- varcheck - # add - - dupl - - errcheck - - funlen - - goconst - - gocyclo - - gofmt - - goimports - - gomnd - - govet - - lll - - misspell - - nakedret - - staticcheck - - stylecheck - - typecheck - - unconvert - - unparam - - revive - -# https://github.com/golangci/golangci/wiki/Configuration -# latest version: https://github.com/golangci/golangci-lint -service: - golangci-lint-version: 1.51.2 diff --git a/src/sample-golang-echo-app/.github/ISSUE_TEMPLATE/bug_report.md b/src/sample-golang-echo-app/.github/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 69fdb01..0000000 --- a/src/sample-golang-echo-app/.github/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: "[BUG]" -labels: bug -assignees: '' - ---- - -## Summary -> Write the actual behavior and the purpose to modify. - -## Cause -> Why such a problem happened? - -### To reproduce -Perform the follwing steps to reproduce the problem. - -1. … -1. … - -## Expected behavior -Expected the follwing behavior. - -## How to deal with this issue -> How do you fix it? - -## Notes diff --git a/src/sample-golang-echo-app/.github/ISSUE_TEMPLATE/feature_request.md b/src/sample-golang-echo-app/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 792495b..0000000 --- a/src/sample-golang-echo-app/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -title: "[NEW]" -labels: enhancement -assignees: '' - ---- - -## Purpose -> What is it necessary for? - -## Expected behavior -Expected the follwing behavior. - -## How to deal with this issue -> How do you fix it? - -## Notes diff --git a/src/sample-golang-echo-app/.github/dependabot.yml b/src/sample-golang-echo-app/.github/dependabot.yml deleted file mode 100644 index 459b7f1..0000000 --- a/src/sample-golang-echo-app/.github/dependabot.yml +++ /dev/null @@ -1,10 +0,0 @@ -version: 2 -updates: - - package-ecosystem: gomod - directory: "/" - schedule: - interval: daily - - package-ecosystem: github-actions - directory: "/" - schedule: - interval: daily \ No newline at end of file diff --git a/src/sample-golang-echo-app/.github/pull_request_template.md b/src/sample-golang-echo-app/.github/pull_request_template.md deleted file mode 100644 index 151065d..0000000 --- a/src/sample-golang-echo-app/.github/pull_request_template.md +++ /dev/null @@ -1,9 +0,0 @@ -### Fixes #[ISSUE NUMBER]. - -Changes proposed in this pull request: - -- ... -- ... -- ... - -by [YOUR NAME] diff --git a/src/sample-golang-echo-app/.github/workflows/check.yml b/src/sample-golang-echo-app/.github/workflows/check.yml deleted file mode 100644 index e279b3b..0000000 --- a/src/sample-golang-echo-app/.github/workflows/check.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: check - -on: - pull_request: - branches: [ master ] - -jobs: - check: - name: check - runs-on: ubuntu-latest - steps: - # Set up GOPATH - - name: set up - uses: actions/setup-go@v4 - with: - go-version: '1.20' - id: go - # Check out this repository - - name: checkout - uses: actions/checkout@v3 - # Store cache - - name: cache - uses: actions/cache@v3.3.1 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go- - # Run golangci-lint using reviewdog - - name: golangci-lint - uses: reviewdog/action-golangci-lint@v2 - with: - github_token: ${{ secrets.github_token }} - level: warning - golangci_lint_flags: "--config=.github/.golangci.yml" - reporter: github-pr-review - # Run test - - name: test - run: go test -cover ./... diff --git a/src/sample-golang-echo-app/.github/workflows/release.yml b/src/sample-golang-echo-app/.github/workflows/release.yml deleted file mode 100644 index 926252c..0000000 --- a/src/sample-golang-echo-app/.github/workflows/release.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: release -on: - push: - tags: - - "v[0-9]+.[0-9]+.[0-9]+" -jobs: - goreleaser: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - fetch-depth: 0 - - name: Setup Go - uses: actions/setup-go@v4 - with: - go-version: '1.20' - - name: Run GoReleaser - uses: goreleaser/goreleaser-action@v4.2.0 - with: - version: latest - args: release --clean - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/src/sample-golang-echo-app/.gitignore b/src/sample-golang-echo-app/.gitignore deleted file mode 100644 index b851cf9..0000000 --- a/src/sample-golang-echo-app/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib -*.db - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool, specifically when used with LiteIDE -*.out - -# Dependency directories (remove the comment below to include it) -#vendor/ - -# Output file of the swaggo/swag -swagger.json -swagger.yaml - -# Debug folder -.vscode diff --git a/src/sample-golang-echo-app/.goreleaser.yml b/src/sample-golang-echo-app/.goreleaser.yml deleted file mode 100644 index 5269a1a..0000000 --- a/src/sample-golang-echo-app/.goreleaser.yml +++ /dev/null @@ -1,52 +0,0 @@ -# This is an example .goreleaser.yml file with some sensible defaults. -# Make sure to check the documentation at https://goreleaser.com -project_name: go-webapp-sample -env: - - GO111MODULE=on -before: - hooks: - # You may remove this if you don't use go modules. - - go mod tidy -builds: - - main: . - binary: go-webapp-sample - ldflags: - - -s -w - - -X main.Version={{.Version}} - - -X main.Revision={{.ShortCommit}} - env: - - CGO_ENABLED=0 - goos: - - linux - - windows - - darwin - -archives: - - format: tar.gz - # this name template makes the OS and Arch compatible with the results of uname. - name_template: >- - {{ .ProjectName }}_ - {{- title .Os }}_ - {{- if eq .Arch "amd64" }}x86_64 - {{- else if eq .Arch "386" }}i386 - {{- else }}{{ .Arch }}{{ end }} - {{- if .Arm }}v{{ .Arm }}{{ end }} - # use zip for windows archives - format_overrides: - - goos: windows - format: zip -checksum: - name_template: 'checksums.txt' -snapshot: - name_template: "{{ incpatch .Version }}-next" -changelog: - filters: - exclude: - - polish - - bump - - typo - -# The lines beneath this are called `modelines`. See `:help modeline` -# Feel free to remove those if you don't want/use them. -# yaml-language-server: $schema=https://goreleaser.com/static/schema.json -# vim: set ts=2 sw=2 tw=0 fo=cnqoj diff --git a/src/sample-golang-echo-app/CONTRIBUTING.md b/src/sample-golang-echo-app/CONTRIBUTING.md deleted file mode 100644 index 88ab1cc..0000000 --- a/src/sample-golang-echo-app/CONTRIBUTING.md +++ /dev/null @@ -1,31 +0,0 @@ -# Contribution -## Preface -Thank you for considering to contribute to this sample application. -I have created this sample application to be helpful as possible whenever you choose to develop a web application using golang. - -## How to report a bug -Please create an issue which highlights the following points when you report a bug: - -1. Version of golang you are using. -1. Operating system you are using. -1. Reproduce the bug. -1. Expected behavior (if any). - -Also, please write about it when you know the causes and how to fix the bug. - -1. Cause -1. How to fix - -## How to suggest a feature or enhancement -Please create an issue which highlights the purpose (feature or enhancement) and expected behavior, and also the functions which you would like to implement as a sample. - -1. Purpose. -1. Expected behavoir (if any), What kind of function do you propose? -1. How to develop - -## How to create a pull request -You can feel free to fork this repository, fix the source code and create a pull request when you are certain that you can resolve an issue. -And after that, please check passed the check workflow of GitHub Action. - -I will check your pull request, and I may suggest some improvements or alternatives. -If there was no problem, I will merge your changes. diff --git a/src/sample-golang-echo-app/LICENSE b/src/sample-golang-echo-app/LICENSE deleted file mode 100644 index 5b3af98..0000000 --- a/src/sample-golang-echo-app/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2023 Yuta Kuroki - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/src/sample-golang-echo-app/README.md b/src/sample-golang-echo-app/README.md deleted file mode 100644 index 68b95b1..0000000 --- a/src/sample-golang-echo-app/README.md +++ /dev/null @@ -1,182 +0,0 @@ -# go-webapp-sample - -[![license](https://img.shields.io/github/license/ybkuroki/go-webapp-sample?style=for-the-badge)](https://github.com/ybkuroki/go-webapp-sample/blob/master/LICENSE) -[![report](https://goreportcard.com/badge/github.com/ybkuroki/go-webapp-sample?style=for-the-badge)](https://goreportcard.com/report/github.com/ybkuroki/go-webapp-sample) -[![workflow](https://img.shields.io/github/actions/workflow/status/ybkuroki/go-webapp-sample/check.yml?label=check&logo=github&style=for-the-badge)](https://github.com/ybkuroki/go-webapp-sample/actions?query=workflow%3Acheck) -[![release](https://img.shields.io/github/release/ybkuroki/go-webapp-sample?style=for-the-badge&logo=github)](https://github.com/ybkuroki/go-webapp-sample/releases) - -## Preface -This repository is the sample of web application using golang. -This sample uses [Echo](https://echo.labstack.com/) as web application framework, [Gorm](https://gorm.io/) as OR mapper and [Zap logger](https://pkg.go.dev/go.uber.org/zap) as logger. -This sample application provides only several functions as Web APIs. -Please refer to the 'Service' section about the detail of those functions. - -Also, this application contains the static contents such as html file, css file and javascript file which built [vuejs-webapp-sample](https://github.com/ybkuroki/vuejs-webapp-sample) project to easily check the behavior of those functions. -So, you can check this application without starting a web server for front end. -Please refer to the 'Starting Server' section about checking the behavior of this application. - -If you would like to develop a web application using golang, please feel free to use this sample. - -## Install -Perform the following steps: -1. Download(`x86_64-8.1.0-release-posix-seh-rt_v6-rev0.7z`) and install [MinGW(gcc)](https://sourceforge.net/projects/mingw-w64/files/?source=navbar). -1. Download and install [Visual Studio Code(VS Code)](https://code.visualstudio.com/). -1. Download and install [Golang](https://golang.org/). -1. Get the source code of this repository by the following command. - ```bash - go install github.com/ybkuroki/go-webapp-sample@latest - ``` - -## Starting Server -There are 2 methods for starting server. - -### Without Web Server -1. Starting this web application by the following command. - ```bash - go run main.go - ``` -1. When startup is complete, the console shows the following message: - ``` - http server started on [::]:8080 - ``` -1. Access [http://localhost:8080](http://localhost:8080) in your browser. -1. Login with the following username and password. - - username : ``test`` - - password : ``test`` - -### With Web Server -#### Starting Application Server -1. Starting this web application by the following command. - ```bash - go run main.go - ``` -1. When startup is complete, the console shows the following message: - ``` - http server started on [::]:8080 - ``` -1. Access [http://localhost:8080/api/health](http://localhost:8080/api/health) in your browser and confirm that this application has started. - ``` - healthy - ``` -#### Starting Web Server -1. Clone [vuejs-webapp-sample](https://github.com/ybkuroki/vuejs-webapp-sample) project and install some tools. -1. Start by the following command. - ```bash - npm run serve - ``` -1. When startup is complete, the console shows the following message: - ``` - DONE Compiled successfully in *****ms - - App running at: - - Local: http://localhost:3000/ - - Network: http://192.168.***.***:3000/ - - Note that the development build is not optimized. - To create a production build, run npm run build. - ``` -1. Access [http://localhost:3000](http://localhost:3000) in your browser. -1. Login with the following username and password. - - username : ``test`` - - password : ``test`` - -## Using Swagger -In this sample, Swagger is enabled only when executed this application on the development environment. -Swagger isn't enabled on the another environments in default. - -### Accessing to Swagger -1. Start this application according to the 'Starting Application Server' section. -2. Access [http://localhost:8080/swagger/index.html](http://localhost:8080/swagger/index.html) in your browser. - -### Updating the existing Swagger document -1. Update some comments of some controllers. -2. Download Swag library. (Only first time) - ```bash - go install github.com/swaggo/swag/cmd/swag@latest - ``` -3. Update ``docs/docs.go``. - ```bash - swag init - ``` - -## Build executable file -Build this source code by the following command. -```bash -go build main.go -``` - -## Project Map -The following figure is the map of this sample project. - -``` -- go-webapp-sample - + config … Define configurations of this system. - + logger … Provide loggers. - + middleware … Define custom middleware. - + migration … Provide database migration service for development. - + router … Define routing. - + controller … Define controllers. - + model … Define models. - + repository … Provide a service of database access. - + service … Provide a service of book management. - + session … Provide session management. - + test … for unit test - - main.go … Entry Point. -``` - -## Services -This sample provides 3 services: book management, account management, and master management. -Regarding the detail of the API specification, please refer to the 'Using Swagger' section. - -### Book Management -There are the following services in the book management. - -|Service Name|HTTP Method|URL|Parameter|Summary| -|:---|:---:|:---|:---|:---| -|Get Service|GET|``/api/books/[BOOK_ID]``|Book ID|Get a book data.| -|List/Search Service|GET|``/api/books?query=[KEYWORD]&page=[PAGE_NUMBER]&size=[PAGE_SIZE]``|Page, Keyword(Optional)|Get a list of books.| -|Regist Service|POST|``/api/books``|Book|Regist a book data.| -|Edit Service|PUT|``/api/books``|Book|Edit a book data.| -|Delete Service|DELETE|``/api/books``|Book|Delete a book data.| - -### Account Management -There are the following services in the Account management. - -|Service Name|HTTP Method|URL|Parameter|Summary| -|:---|:---:|:---|:---|:---| -|Login Service|POST|``/api/auth/login``|Session ID, User Name, Password|Session authentication with username and password.| -|Logout Service|POST|``/api/auth/logout``|Session ID|Logout a user.| -|Login Status Check Service|GET|``/api/auth/loginStatus``|Session ID|Check if the user is logged in.| -|Login Username Service|GET|``/api/auth/loginAccount``|Session ID|Get the login user's username.| - -### Master Management -There are the following services in the Master management. - -|Service Name|HTTP Method|URL|Parameter|Summary| -|:---|:---:|:---|:---|:---| -|Category List Service|GET|``/api/categories``|Nothing|Get a list of categories.| -|Format List Service|GET|``/api/formats``|Nothing|Get a list of formats.| - -## Tests -Create the unit tests only for the packages such as controller, service, model/dto and util. The test cases is included the regular cases and irregular cases. Please refer to the source code in each packages for more detail. - -The command for testing is the following: -```bash -go test ./... -v -``` - -## Libraries -This sample uses the following libraries. - -|Library Name|Version| -|:---|:---:| -|echo|4.10.2| -|gorm|1.24.6| -|go-playground/validator.v9|9.31.0| -|zap|1.24.0| - -## Contribution -Please read [CONTRIBUTING.md](https://github.com/ybkuroki/go-webapp-sample/blob/master/CONTRIBUTING.md) for proposing new functions, reporting bugs and submitting pull requests before contributing to this repository. - -## License -The License of this sample is *MIT License*. diff --git a/src/sample-golang-echo-app/config/config.go b/src/sample-golang-echo-app/config/config.go deleted file mode 100644 index afee25e..0000000 --- a/src/sample-golang-echo-app/config/config.go +++ /dev/null @@ -1,95 +0,0 @@ -package config - -import ( - "embed" - "flag" - "fmt" - "os" - - "github.com/ybkuroki/go-webapp-sample/util" - "gopkg.in/yaml.v3" -) - -// Config represents the composition of yml settings. -type Config struct { - Database struct { - Dialect string `default:"sqlite3"` - Host string `default:"book.db"` - Port string - Dbname string - Username string - Password string - Migration bool `default:"false"` - } - Redis struct { - Enabled bool `default:"false"` - ConnectionPoolSize int `yaml:"connection_pool_size" default:"10"` - Host string - Port string - } - Extension struct { - MasterGenerator bool `yaml:"master_generator" default:"false"` - CorsEnabled bool `yaml:"cors_enabled" default:"false"` - SecurityEnabled bool `yaml:"security_enabled" default:"false"` - } - Log struct { - RequestLogFormat string `yaml:"request_log_format" default:"${remote_ip} ${account_name} ${uri} ${method} ${status}"` - } - StaticContents struct { - Enabled bool `default:"false"` - } - Swagger struct { - Enabled bool `default:"false"` - Path string - } - Security struct { - AuthPath []string `yaml:"auth_path"` - ExculdePath []string `yaml:"exclude_path"` - UserPath []string `yaml:"user_path"` - AdminPath []string `yaml:"admin_path"` - } -} - -const ( - // DEV represents development environment - DEV = "develop" - // PRD represents production environment - PRD = "production" - // DOC represents docker container - DOC = "docker" -) - -// LoadAppConfig reads the settings written to the yml file -func LoadAppConfig(yamlFile embed.FS) (*Config, string) { - var env *string - if value := os.Getenv("WEB_APP_ENV"); value != "" { - env = &value - } else { - env = flag.String("env", "develop", "To switch configurations.") - flag.Parse() - } - - file, err := yamlFile.ReadFile(fmt.Sprintf(AppConfigPath, *env)) - if err != nil { - fmt.Printf("Failed to read application.%s.yml: %s", *env, err) - os.Exit(ErrExitStatus) - } - - config := &Config{} - if err := yaml.Unmarshal(file, config); err != nil { - fmt.Printf("Failed to read application.%s.yml: %s", *env, err) - os.Exit(ErrExitStatus) - } - - return config, *env -} - -// LoadMessagesConfig loads the messages.properties. -func LoadMessagesConfig(propsFile embed.FS) map[string]string { - messages := util.ReadPropertiesFile(propsFile, MessagesConfigPath) - if messages == nil { - fmt.Printf("Failed to load the messages.properties.") - os.Exit(ErrExitStatus) - } - return messages -} diff --git a/src/sample-golang-echo-app/config/const.go b/src/sample-golang-echo-app/config/const.go deleted file mode 100644 index f4c1ef2..0000000 --- a/src/sample-golang-echo-app/config/const.go +++ /dev/null @@ -1,47 +0,0 @@ -package config - -// ErrExitStatus represents the error status in this application. -const ErrExitStatus int = 2 - -const ( - // AppConfigPath is the path of application.yml. - AppConfigPath = "resources/config/application.%s.yml" - // MessagesConfigPath is the path of messages.properties. - MessagesConfigPath = "resources/config/messages.properties" - // LoggerConfigPath is the path of zaplogger.yml. - LoggerConfigPath = "resources/config/zaplogger.%s.yml" -) - -// PasswordHashCost is hash cost for a password. -const PasswordHashCost int = 10 - -const ( - // API represents the group of API. - API = "/api" - // APIBooks represents the group of book management API. - APIBooks = API + "/books" - // APIBooksID represents the API to get book data using id. - APIBooksID = APIBooks + "/:id" - // APICategories represents the group of category management API. - APICategories = API + "/categories" - // APIFormats represents the group of format management API. - APIFormats = API + "/formats" -) - -const ( - // APIAccount represents the group of auth management API. - APIAccount = API + "/auth" - // APIAccountLoginStatus represents the API to get the status of logged in account. - APIAccountLoginStatus = APIAccount + "/loginStatus" - // APIAccountLoginAccount represents the API to get the logged in account. - APIAccountLoginAccount = APIAccount + "/loginAccount" - // APIAccountLogin represents the API to login by session authentication. - APIAccountLogin = APIAccount + "/login" - // APIAccountLogout represents the API to logout. - APIAccountLogout = APIAccount + "/logout" -) - -const ( - // APIHealth represents the API to get the status of this application. - APIHealth = API + "/health" -) diff --git a/src/sample-golang-echo-app/container/container.go b/src/sample-golang-echo-app/container/container.go deleted file mode 100644 index e7a12d8..0000000 --- a/src/sample-golang-echo-app/container/container.go +++ /dev/null @@ -1,65 +0,0 @@ -package container - -import ( - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/logger" - "github.com/ybkuroki/go-webapp-sample/repository" - "github.com/ybkuroki/go-webapp-sample/session" -) - -// Container represents a interface for accessing the data which sharing in overall application. -type Container interface { - GetRepository() repository.Repository - GetSession() session.Session - GetConfig() *config.Config - GetMessages() map[string]string - GetLogger() logger.Logger - GetEnv() string -} - -// container struct is for sharing data which such as database setting, the setting of application and logger in overall this application. -type container struct { - rep repository.Repository - session session.Session - config *config.Config - messages map[string]string - logger logger.Logger - env string -} - -// NewContainer is constructor. -func NewContainer(rep repository.Repository, s session.Session, config *config.Config, - messages map[string]string, logger logger.Logger, env string) Container { - return &container{rep: rep, session: s, config: config, - messages: messages, logger: logger, env: env} -} - -// GetRepository returns the object of repository. -func (c *container) GetRepository() repository.Repository { - return c.rep -} - -// GetSession returns the object of session. -func (c *container) GetSession() session.Session { - return c.session -} - -// GetConfig returns the object of configuration. -func (c *container) GetConfig() *config.Config { - return c.config -} - -// GetMessages returns the map has key and message. -func (c *container) GetMessages() map[string]string { - return c.messages -} - -// GetLogger returns the object of logger. -func (c *container) GetLogger() logger.Logger { - return c.logger -} - -// GetEnv returns the running environment. -func (c *container) GetEnv() string { - return c.env -} diff --git a/src/sample-golang-echo-app/controller/account.go b/src/sample-golang-echo-app/controller/account.go deleted file mode 100644 index a1db16c..0000000 --- a/src/sample-golang-echo-app/controller/account.go +++ /dev/null @@ -1,108 +0,0 @@ -package controller - -import ( - "net/http" - - "github.com/labstack/echo/v4" - "github.com/ybkuroki/go-webapp-sample/container" - "github.com/ybkuroki/go-webapp-sample/model" - "github.com/ybkuroki/go-webapp-sample/model/dto" - "github.com/ybkuroki/go-webapp-sample/service" -) - -// AccountController is a controller for managing user account. -type AccountController interface { - GetLoginStatus(c echo.Context) error - GetLoginAccount(c echo.Context) error - Login(c echo.Context) error - Logout(c echo.Context) error -} - -type accountController struct { - context container.Container - service service.AccountService - dummyAccount *model.Account -} - -// NewAccountController is constructor. -func NewAccountController(container container.Container) AccountController { - return &accountController{ - context: container, - service: service.NewAccountService(container), - dummyAccount: model.NewAccountWithPlainPassword("test", "test", 1), - } -} - -// GetLoginStatus returns the status of login. -// @Summary Get the login status. -// @Description Get the login status of current logged-in user. -// @Tags Auth -// @Accept json -// @Produce json -// @Success 200 {boolean} bool "The current user have already logged-in. Returns true." -// @Failure 401 {boolean} bool "The current user haven't logged-in yet. Returns false." -// @Router /auth/loginStatus [get] -func (controller *accountController) GetLoginStatus(c echo.Context) error { - return c.JSON(http.StatusOK, true) -} - -// GetLoginAccount returns the account data of logged in user. -// @Summary Get the account data of logged-in user. -// @Description Get the account data of logged-in user. -// @Tags Auth -// @Accept json -// @Produce json -// @Success 200 {object} model.Account "Success to fetch the account data. If the security function is disable, it returns the dummy data." -// @Failure 401 {boolean} bool "The current user haven't logged-in yet. Returns false." -// @Router /auth/loginAccount [get] -func (controller *accountController) GetLoginAccount(c echo.Context) error { - if !controller.context.GetConfig().Extension.SecurityEnabled { - return c.JSON(http.StatusOK, controller.dummyAccount) - } - return c.JSON(http.StatusOK, controller.context.GetSession().GetAccount()) -} - -// Login is the method to login using username and password by http post. -// @Summary Login using username and password. -// @Description Login using username and password. -// @Tags Auth -// @Accept json -// @Produce json -// @Param data body dto.LoginDto true "User name and Password for logged-in." -// @Success 200 {object} model.Account "Success to the authentication." -// @Failure 401 {boolean} bool "Failed to the authentication." -// @Router /auth/login [post] -func (controller *accountController) Login(c echo.Context) error { - dto := dto.NewLoginDto() - if err := c.Bind(dto); err != nil { - return c.JSON(http.StatusBadRequest, dto) - } - - sess := controller.context.GetSession() - if account := sess.GetAccount(); account != nil { - return c.JSON(http.StatusOK, account) - } - - authenticate, a := controller.service.AuthenticateByUsernameAndPassword(dto.UserName, dto.Password) - if authenticate { - _ = sess.SetAccount(a) - _ = sess.Save() - return c.JSON(http.StatusOK, a) - } - return c.NoContent(http.StatusUnauthorized) -} - -// Logout is the method to logout by http post. -// @Summary Logout. -// @Description Logout. -// @Tags Auth -// @Accept json -// @Produce json -// @Success 200 -// @Router /auth/logout [post] -func (controller *accountController) Logout(c echo.Context) error { - sess := controller.context.GetSession() - _ = sess.SetAccount(nil) - _ = sess.Delete() - return c.NoContent(http.StatusOK) -} diff --git a/src/sample-golang-echo-app/controller/account_test.go b/src/sample-golang-echo-app/controller/account_test.go deleted file mode 100644 index 2aab835..0000000 --- a/src/sample-golang-echo-app/controller/account_test.go +++ /dev/null @@ -1,106 +0,0 @@ -package controller - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/model" - "github.com/ybkuroki/go-webapp-sample/model/dto" - "github.com/ybkuroki/go-webapp-sample/test" -) - -func TestGetLoginStatus_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - account := NewAccountController(container) - router.GET(config.APIAccountLoginStatus, func(c echo.Context) error { return account.GetLoginStatus(c) }) - - req := httptest.NewRequest("GET", config.APIAccountLoginStatus, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, "true", rec.Body.String()) -} - -func TestGetLoginAccount_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - account := NewAccountController(container) - router.GET(config.APIAccountLoginAccount, func(c echo.Context) error { return account.GetLoginAccount(c) }) - - req := httptest.NewRequest("GET", config.APIAccountLoginAccount, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - entity := model.NewAccountWithPlainPassword("test", "test", 1) - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, test.ConvertToString(entity), rec.Body.String()) -} - -func TestLogin_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(true) - - account := NewAccountController(container) - router.POST(config.APIAccountLogin, func(c echo.Context) error { return account.Login(c) }) - - param := createLoginSuccessAccount() - req := test.NewJSONRequest("POST", config.APIAccountLogin, param) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code) - assert.NotEmpty(t, test.GetCookie(rec, "GSESSION")) -} - -func TestLogin_AuthenticationFailure(t *testing.T) { - router, container := test.PrepareForControllerTest(true) - - account := NewAccountController(container) - router.POST(config.APIAccountLogin, func(c echo.Context) error { return account.Login(c) }) - - param := createLoginFailureAccount() - req := test.NewJSONRequest("POST", config.APIAccountLogin, param) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusUnauthorized, rec.Code) - assert.Empty(t, test.GetCookie(rec, "GSESSION")) -} - -func TestLogout_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(true) - - account := NewAccountController(container) - router.POST(config.APIAccountLogout, func(c echo.Context) error { return account.Logout(c) }) - - req := test.NewJSONRequest("POST", config.APIAccountLogout, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code) - assert.NotEmpty(t, test.GetCookie(rec, "GSESSION")) -} - -func createLoginSuccessAccount() *dto.LoginDto { - return &dto.LoginDto{ - UserName: "test", - Password: "test", - } -} - -func createLoginFailureAccount() *dto.LoginDto { - return &dto.LoginDto{ - UserName: "test", - Password: "abcde", - } -} diff --git a/src/sample-golang-echo-app/controller/book.go b/src/sample-golang-echo-app/controller/book.go deleted file mode 100644 index 2b6de4e..0000000 --- a/src/sample-golang-echo-app/controller/book.go +++ /dev/null @@ -1,135 +0,0 @@ -package controller - -import ( - "net/http" - - "github.com/labstack/echo/v4" - "github.com/ybkuroki/go-webapp-sample/container" - "github.com/ybkuroki/go-webapp-sample/model/dto" - "github.com/ybkuroki/go-webapp-sample/service" -) - -// BookController is a controller for managing books. -type BookController interface { - GetBook(c echo.Context) error - GetBookList(c echo.Context) error - CreateBook(c echo.Context) error - UpdateBook(c echo.Context) error - DeleteBook(c echo.Context) error -} - -type bookController struct { - container container.Container - service service.BookService -} - -// NewBookController is constructor. -func NewBookController(container container.Container) BookController { - return &bookController{container: container, service: service.NewBookService(container)} -} - -// GetBook returns one record matched book's id. -// @Summary Get a book -// @Description Get a book -// @Tags Books -// @Accept json -// @Produce json -// @Param book_id path int true "Book ID" -// @Success 200 {object} model.Book "Success to fetch data." -// @Failure 400 {string} message "Failed to fetch data." -// @Failure 401 {boolean} bool "Failed to the authentication. Returns false." -// @Router /books/{book_id} [get] -func (controller *bookController) GetBook(c echo.Context) error { - book, err := controller.service.FindByID(c.Param("id")) - if err != nil { - return c.JSON(http.StatusBadRequest, err.Error()) - } - return c.JSON(http.StatusOK, book) -} - -// GetBookList returns the list of matched books by searching. -// @Summary Get a book list -// @Description Get the list of matched books by searching -// @Tags Books -// @Accept json -// @Produce json -// @Param query query string false "Keyword" -// @Param page query int false "Page number" -// @Param size query int false "Item size per page" -// @Success 200 {object} model.Page "Success to fetch a book list." -// @Failure 400 {string} message "Failed to fetch data." -// @Failure 401 {boolean} bool "Failed to the authentication. Returns false." -// @Router /books [get] -func (controller *bookController) GetBookList(c echo.Context) error { - book, err := controller.service.FindBooksByTitle(c.QueryParam("query"), c.QueryParam("page"), c.QueryParam("size")) - if err != nil { - return c.JSON(http.StatusBadRequest, err.Error()) - } - return c.JSON(http.StatusOK, book) -} - -// CreateBook create a new book by http post. -// @Summary Create a new book -// @Description Create a new book -// @Tags Books -// @Accept json -// @Produce json -// @Param data body dto.BookDto true "a new book data for creating" -// @Success 200 {object} model.Book "Success to create a new book." -// @Failure 400 {string} message "Failed to the registration." -// @Failure 401 {boolean} bool "Failed to the authentication. Returns false." -// @Router /books [post] -func (controller *bookController) CreateBook(c echo.Context) error { - dto := dto.NewBookDto(controller.container.GetMessages()) - if err := c.Bind(dto); err != nil { - return c.JSON(http.StatusBadRequest, dto) - } - book, result := controller.service.CreateBook(dto) - if result != nil { - return c.JSON(http.StatusBadRequest, result) - } - return c.JSON(http.StatusOK, book) -} - -// UpdateBook update the existing book by http put. -// @Summary Update the existing book -// @Description Update the existing book -// @Tags Books -// @Accept json -// @Produce json -// @Param book_id path int true "Book ID" -// @Param data body dto.BookDto true "the book data for updating" -// @Success 200 {object} model.Book "Success to update the existing book." -// @Failure 400 {string} message "Failed to the update." -// @Failure 401 {boolean} bool "Failed to the authentication. Returns false." -// @Router /books/{book_id} [put] -func (controller *bookController) UpdateBook(c echo.Context) error { - dto := dto.NewBookDto(controller.container.GetMessages()) - if err := c.Bind(dto); err != nil { - return c.JSON(http.StatusBadRequest, dto) - } - book, result := controller.service.UpdateBook(dto, c.Param("id")) - if result != nil { - return c.JSON(http.StatusBadRequest, result) - } - return c.JSON(http.StatusOK, book) -} - -// DeleteBook deletes the existing book by http delete. -// @Summary Delete the existing book -// @Description Delete the existing book -// @Tags Books -// @Accept json -// @Produce json -// @Param book_id path int true "Book ID" -// @Success 200 {object} model.Book "Success to delete the existing book." -// @Failure 400 {string} message "Failed to the delete." -// @Failure 401 {boolean} bool "Failed to the authentication. Returns false." -// @Router /books/{book_id} [delete] -func (controller *bookController) DeleteBook(c echo.Context) error { - book, result := controller.service.DeleteBook(c.Param("id")) - if result != nil { - return c.JSON(http.StatusBadRequest, result) - } - return c.JSON(http.StatusOK, book) -} diff --git a/src/sample-golang-echo-app/controller/book_test.go b/src/sample-golang-echo-app/controller/book_test.go deleted file mode 100644 index 0f65e86..0000000 --- a/src/sample-golang-echo-app/controller/book_test.go +++ /dev/null @@ -1,310 +0,0 @@ -package controller - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/container" - "github.com/ybkuroki/go-webapp-sample/model" - "github.com/ybkuroki/go-webapp-sample/model/dto" - "github.com/ybkuroki/go-webapp-sample/test" - "github.com/ybkuroki/go-webapp-sample/util" -) - -type BookDtoForBindError struct { - Title string - Isbn string - CategoryID string - FormatID string -} - -const ( - ValidationErrMessageBookTitle string = "Please enter the title with 3 to 50 characters." - ValidationErrMessageBookISBN string = "Please enter the ISBN with 10 to 20 characters." -) - -func TestGetBook_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.GET(config.APIBooksID, func(c echo.Context) error { return book.GetBook(c) }) - - setUpTestData(container) - - uri := util.NewRequestBuilder().URL(config.APIBooks).PathParams("1").Build().GetRequestURL() - req := httptest.NewRequest("GET", uri, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - entity := &model.Book{} - opt := entity.FindByID(container.GetRepository(), 1) - data, _ := opt.Take() - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, test.ConvertToString(data), rec.Body.String()) -} - -func TestGetBook_Failure(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.GET(config.APIBooksID, func(c echo.Context) error { return book.GetBook(c) }) - - setUpTestData(container) - - uri := util.NewRequestBuilder().URL(config.APIBooks).PathParams("9999").Build().GetRequestURL() - req := httptest.NewRequest("GET", uri, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.Equal(t, "\"none value taken\"\n", rec.Body.String()) -} - -func TestGetBookList_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.GET(config.APIBooks, func(c echo.Context) error { return book.GetBookList(c) }) - - setUpTestData(container) - - uri := util.NewRequestBuilder().URL(config.APIBooks). - RequestParams("query", "Test").RequestParams("page", "0").RequestParams("size", "5"). - Build().GetRequestURL() - req := httptest.NewRequest("GET", uri, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - entity := &model.Book{} - data, _ := entity.FindByTitle(container.GetRepository(), "Test", "0", "5") - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, test.ConvertToString(data), rec.Body.String()) -} - -func TestCreateBook_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.POST(config.APIBooks, func(c echo.Context) error { return book.CreateBook(c) }) - - param := createBookForCreate() - req := test.NewJSONRequest("POST", config.APIBooks, param) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - entity := &model.Book{} - opt := entity.FindByID(container.GetRepository(), 1) - data, _ := opt.Take() - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, test.ConvertToString(data), rec.Body.String()) -} - -func TestCreateBook_BindError(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.POST(config.APIBooks, func(c echo.Context) error { return book.CreateBook(c) }) - - param := createBookForBindError() - req := test.NewJSONRequest("POST", config.APIBooks, param) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - result := createResultForBindError() - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.JSONEq(t, test.ConvertToString(result), rec.Body.String()) -} - -func TestCreateBook_ValidationError(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.POST(config.APIBooks, func(c echo.Context) error { return book.CreateBook(c) }) - - param := createBookForValidationError() - req := test.NewJSONRequest("POST", config.APIBooks, param) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - result := createResultForValidationError() - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.JSONEq(t, test.ConvertToString(result), rec.Body.String()) -} - -func TestUpdateBook_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.PUT(config.APIBooksID, func(c echo.Context) error { return book.UpdateBook(c) }) - - setUpTestData(container) - - param := createBookForUpdate() - uri := util.NewRequestBuilder().URL(config.APIBooks).PathParams("1").Build().GetRequestURL() - req := test.NewJSONRequest("PUT", uri, param) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - entity := &model.Book{} - opt := entity.FindByID(container.GetRepository(), 1) - data, _ := opt.Take() - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, test.ConvertToString(data), rec.Body.String()) -} - -func TestUpdateBook_BindError(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.PUT(config.APIBooksID, func(c echo.Context) error { return book.UpdateBook(c) }) - - setUpTestData(container) - - param := createBookForBindError() - uri := util.NewRequestBuilder().URL(config.APIBooks).PathParams("1").Build().GetRequestURL() - req := test.NewJSONRequest("PUT", uri, param) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - result := createResultForBindError() - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.JSONEq(t, test.ConvertToString(result), rec.Body.String()) -} - -func TestUpdateBook_ValidationError(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.PUT(config.APIBooksID, func(c echo.Context) error { return book.UpdateBook(c) }) - - setUpTestData(container) - - param := createBookForValidationError() - uri := util.NewRequestBuilder().URL(config.APIBooks).PathParams("1").Build().GetRequestURL() - req := test.NewJSONRequest("PUT", uri, param) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - result := createResultForValidationError() - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.JSONEq(t, test.ConvertToString(result), rec.Body.String()) -} - -func TestDeleteBook_Success(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.DELETE(config.APIBooksID, func(c echo.Context) error { return book.DeleteBook(c) }) - - setUpTestData(container) - - entity := &model.Book{} - opt := entity.FindByID(container.GetRepository(), 1) - data, _ := opt.Take() - - uri := util.NewRequestBuilder().URL(config.APIBooks).PathParams("1").Build().GetRequestURL() - req := test.NewJSONRequest("DELETE", uri, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, test.ConvertToString(data), rec.Body.String()) -} - -func TestDeleteBook_Failure(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - book := NewBookController(container) - router.DELETE(config.APIBooksID, func(c echo.Context) error { return book.DeleteBook(c) }) - - setUpTestData(container) - - uri := util.NewRequestBuilder().URL(config.APIBooks).PathParams("9999").Build().GetRequestURL() - req := test.NewJSONRequest("DELETE", uri, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusBadRequest, rec.Code) - assert.JSONEq(t, test.ConvertToString(createResultForDeleteError()), rec.Body.String()) -} - -func setUpTestData(container container.Container) { - model := model.NewBook("Test1", "123-123-123-1", 1, 1) - repo := container.GetRepository() - _, _ = model.Create(repo) -} - -func createBookForCreate() *dto.BookDto { - return &dto.BookDto{ - Title: "Test1", - Isbn: "123-123-123-1", - CategoryID: 1, - FormatID: 1, - } -} - -func createBookForValidationError() *dto.BookDto { - return &dto.BookDto{ - Title: "T", - Isbn: "123", - CategoryID: 1, - FormatID: 1, - } -} - -func createBookForBindError() *BookDtoForBindError { - return &BookDtoForBindError{ - Title: "Test1", - Isbn: "123-123-123-1", - CategoryID: "Test", - FormatID: "Test", - } -} - -func createResultForBindError() *dto.BookDto { - return &dto.BookDto{ - Title: "Test1", - Isbn: "123-123-123-1", - CategoryID: 0, - FormatID: 0, - } -} - -func createResultForValidationError() map[string]string { - return map[string]string{ - "isbn": ValidationErrMessageBookISBN, - "title": ValidationErrMessageBookTitle, - } -} - -func createResultForDeleteError() map[string]string { - return map[string]string{"error": "Failed to the delete"} -} - -func createBookForUpdate() *dto.BookDto { - return &dto.BookDto{ - Title: "Test2", - Isbn: "123-123-123-2", - CategoryID: 2, - FormatID: 2, - } -} diff --git a/src/sample-golang-echo-app/controller/category.go b/src/sample-golang-echo-app/controller/category.go deleted file mode 100644 index 9f39850..0000000 --- a/src/sample-golang-echo-app/controller/category.go +++ /dev/null @@ -1,37 +0,0 @@ -package controller - -import ( - "net/http" - - "github.com/labstack/echo/v4" - "github.com/ybkuroki/go-webapp-sample/container" - "github.com/ybkuroki/go-webapp-sample/service" -) - -// CategoryController is a controller for managing category data. -type CategoryController interface { - GetCategoryList(c echo.Context) error -} - -type categoryController struct { - container container.Container - service service.CategoryService -} - -// NewCategoryController is constructor. -func NewCategoryController(container container.Container) CategoryController { - return &categoryController{container: container, service: service.NewCategoryService(container)} -} - -// GetCategoryList returns the list of all categories. -// @Summary Get a category list -// @Description Get a category list -// @Tags Categories -// @Accept json -// @Produce json -// @Success 200 {array} model.Category "Success to fetch a category list." -// @Failure 401 {string} false "Failed to the authentication." -// @Router /categories [get] -func (controller *categoryController) GetCategoryList(c echo.Context) error { - return c.JSON(http.StatusOK, controller.service.FindAllCategories()) -} diff --git a/src/sample-golang-echo-app/controller/category_test.go b/src/sample-golang-echo-app/controller/category_test.go deleted file mode 100644 index a764fec..0000000 --- a/src/sample-golang-echo-app/controller/category_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package controller - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/model" - "github.com/ybkuroki/go-webapp-sample/test" -) - -func TestGetCategoryList(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - category := NewCategoryController(container) - router.GET(config.APICategories, func(c echo.Context) error { return category.GetCategoryList(c) }) - - req := httptest.NewRequest("GET", config.APICategories, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - data := [...]*model.Category{ - {ID: 1, Name: "Technical Book"}, - {ID: 2, Name: "Magazine"}, - {ID: 3, Name: "Novel"}, - } - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, test.ConvertToString(data), rec.Body.String()) -} diff --git a/src/sample-golang-echo-app/controller/error.go b/src/sample-golang-echo-app/controller/error.go deleted file mode 100644 index 0b0e90d..0000000 --- a/src/sample-golang-echo-app/controller/error.go +++ /dev/null @@ -1,51 +0,0 @@ -package controller - -import ( - "net/http" - - "github.com/labstack/echo/v4" - "github.com/ybkuroki/go-webapp-sample/container" -) - -// APIError has a error code and a message. -type APIError struct { - Code int - Message string -} - -// ErrorController is a controller for handling errors. -type ErrorController interface { - JSONError(err error, c echo.Context) -} - -type errorController struct { - container container.Container -} - -// NewErrorController is constructor. -func NewErrorController(container container.Container) ErrorController { - return &errorController{container: container} -} - -// JSONError is cumstomize error handler -func (controller *errorController) JSONError(err error, c echo.Context) { - logger := controller.container.GetLogger() - code := http.StatusInternalServerError - msg := http.StatusText(code) - - if he, ok := err.(*echo.HTTPError); ok { - code = he.Code - msg = he.Message.(string) - } - - var apierr APIError - apierr.Code = code - apierr.Message = msg - - if !c.Response().Committed { - if reserr := c.JSON(code, apierr); reserr != nil { - logger.GetZapLogger().Errorf(reserr.Error()) - } - } - logger.GetZapLogger().Debugf(err.Error()) -} diff --git a/src/sample-golang-echo-app/controller/error_test.go b/src/sample-golang-echo-app/controller/error_test.go deleted file mode 100644 index 1fa2f5a..0000000 --- a/src/sample-golang-echo-app/controller/error_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package controller - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/ybkuroki/go-webapp-sample/test" -) - -func TestJSONError(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - errorHandler := NewErrorController(container) - router.HTTPErrorHandler = errorHandler.JSONError - - req := httptest.NewRequest("GET", "/api/movies/1", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusNotFound, rec.Code) - assert.JSONEq(t, `{"Code":404,"Message":"Not Found"}`, rec.Body.String()) -} diff --git a/src/sample-golang-echo-app/controller/format.go b/src/sample-golang-echo-app/controller/format.go deleted file mode 100644 index dd5d3dd..0000000 --- a/src/sample-golang-echo-app/controller/format.go +++ /dev/null @@ -1,37 +0,0 @@ -package controller - -import ( - "net/http" - - "github.com/labstack/echo/v4" - "github.com/ybkuroki/go-webapp-sample/container" - "github.com/ybkuroki/go-webapp-sample/service" -) - -// FormatController is a controller for managing format data. -type FormatController interface { - GetFormatList(c echo.Context) error -} - -type formatController struct { - container container.Container - service service.FormatService -} - -// NewFormatController is constructor. -func NewFormatController(container container.Container) FormatController { - return &formatController{container: container, service: service.NewFormatService(container)} -} - -// GetFormatList returns the list of all formats. -// @Summary Get a format list -// @Description Get a format list -// @Tags Formats -// @Accept json -// @Produce json -// @Success 200 {array} model.Format "Success to fetch a format list." -// @Failure 401 {string} false "Failed to the authentication." -// @Router /formats [get] -func (controller *formatController) GetFormatList(c echo.Context) error { - return c.JSON(http.StatusOK, controller.service.FindAllFormats()) -} diff --git a/src/sample-golang-echo-app/controller/format_test.go b/src/sample-golang-echo-app/controller/format_test.go deleted file mode 100644 index 88ea18a..0000000 --- a/src/sample-golang-echo-app/controller/format_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package controller - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/model" - "github.com/ybkuroki/go-webapp-sample/test" -) - -func TestGetFormatList(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - format := NewFormatController(container) - router.GET(config.APIFormats, func(c echo.Context) error { return format.GetFormatList(c) }) - - req := httptest.NewRequest("GET", config.APIFormats, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - data := [...]*model.Format{ - {ID: 1, Name: "Paper Book"}, - {ID: 2, Name: "e-Book"}, - } - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, test.ConvertToString(data), rec.Body.String()) -} diff --git a/src/sample-golang-echo-app/controller/health.go b/src/sample-golang-echo-app/controller/health.go deleted file mode 100644 index f6fac2a..0000000 --- a/src/sample-golang-echo-app/controller/health.go +++ /dev/null @@ -1,35 +0,0 @@ -package controller - -import ( - "net/http" - - "github.com/labstack/echo/v4" - "github.com/ybkuroki/go-webapp-sample/container" -) - -// HealthController is a controller returns the current status of this application. -type HealthController interface { - GetHealthCheck(c echo.Context) error -} - -type healthController struct { - container container.Container -} - -// NewHealthController is constructor. -func NewHealthController(container container.Container) HealthController { - return &healthController{container: container} -} - -// GetHealthCheck returns whether this application is alive or not. -// @Summary Get the status of this application -// @Description Get the status of this application -// @Tags Health -// @Accept json -// @Produce json -// @Success 200 {string} message "healthy: This application is started." -// @Failure 404 {string} message "None: This application is stopped." -// @Router /health [get] -func (controller *healthController) GetHealthCheck(c echo.Context) error { - return c.JSON(http.StatusOK, "healthy") -} diff --git a/src/sample-golang-echo-app/controller/health_test.go b/src/sample-golang-echo-app/controller/health_test.go deleted file mode 100644 index 3f49c4c..0000000 --- a/src/sample-golang-echo-app/controller/health_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package controller - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/test" -) - -func TestGetHealthCheck(t *testing.T) { - router, container := test.PrepareForControllerTest(false) - - health := NewHealthController(container) - router.GET(config.APIHealth, func(c echo.Context) error { return health.GetHealthCheck(c) }) - - req := httptest.NewRequest("GET", config.APIHealth, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code) - assert.JSONEq(t, `"healthy"`, rec.Body.String()) -} diff --git a/src/sample-golang-echo-app/controller/logger_test.go b/src/sample-golang-echo-app/controller/logger_test.go deleted file mode 100644 index 8db6e99..0000000 --- a/src/sample-golang-echo-app/controller/logger_test.go +++ /dev/null @@ -1,44 +0,0 @@ -package controller - -import ( - "net/http/httptest" - "strings" - "testing" - - "github.com/labstack/echo/v4" - "github.com/stretchr/testify/assert" - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/test" - "github.com/ybkuroki/go-webapp-sample/util" - "go.uber.org/zap/zaptest/observer" -) - -func TestLogging(t *testing.T) { - router, container, logs := test.PrepareForLoggerTest() - - book := NewBookController(container) - router.GET(config.APIBooksID, func(c echo.Context) error { return book.GetBook(c) }) - - setUpTestData(container) - - uri := util.NewRequestBuilder().URL(config.APIBooks).PathParams("1").Build().GetRequestURL() - req := httptest.NewRequest("GET", uri, nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - allLogs := logs.All() - assert.True(t, assertLogger("/api/books/:id Action Start", allLogs)) - assert.True(t, assertLogger("/api/books/:id Action End", allLogs)) - assert.True(t, assertLogger("/api/books/1 GET 200", allLogs)) - assert.True(t, assertLogger("[gorm] ", allLogs)) -} - -func assertLogger(message string, logs []observer.LoggedEntry) bool { - for _, l := range logs { - if strings.Contains(l.Message, message) { - return true - } - } - return false -} diff --git a/src/sample-golang-echo-app/controller/swagger_test.go b/src/sample-golang-echo-app/controller/swagger_test.go deleted file mode 100644 index 4397a5b..0000000 --- a/src/sample-golang-echo-app/controller/swagger_test.go +++ /dev/null @@ -1,25 +0,0 @@ -package controller - -import ( - "net/http" - "net/http/httptest" - "testing" - - "github.com/stretchr/testify/assert" - echoSwagger "github.com/swaggo/echo-swagger" - _ "github.com/ybkuroki/go-webapp-sample/docs" // for using echo-swagger - "github.com/ybkuroki/go-webapp-sample/test" -) - -func TestSwagger(t *testing.T) { - router, _ := test.PrepareForControllerTest(false) - router.GET("/swagger/*", echoSwagger.WrapHandler) - - req := httptest.NewRequest("GET", "/swagger/index.html", nil) - rec := httptest.NewRecorder() - - router.ServeHTTP(rec, req) - - assert.Equal(t, http.StatusOK, rec.Code) - assert.Regexp(t, "Swagger UI", rec.Body.String()) -} diff --git a/src/sample-golang-echo-app/docs/docs.go b/src/sample-golang-echo-app/docs/docs.go deleted file mode 100644 index abce2a9..0000000 --- a/src/sample-golang-echo-app/docs/docs.go +++ /dev/null @@ -1,671 +0,0 @@ -// GENERATED BY THE COMMAND ABOVE; DO NOT EDIT -// This file was generated by swaggo/swag - -package docs - -import ( - "bytes" - "encoding/json" - "strings" - - "github.com/alecthomas/template" - "github.com/swaggo/swag" -) - -var doc = `{ - "schemes": {{ marshal .Schemes }}, - "swagger": "2.0", - "info": { - "description": "{{.Description}}", - "title": "{{.Title}}", - "contact": {}, - "license": { - "name": "MIT", - "url": "https://opensource.org/licenses/mit-license.php" - }, - "version": "{{.Version}}" - }, - "host": "{{.Host}}", - "basePath": "{{.BasePath}}", - "paths": { - "/auth/login": { - "post": { - "description": "Login using username and password.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Auth" - ], - "summary": "Login using username and password.", - "parameters": [ - { - "description": "User name and Password for logged-in.", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/dto.LoginDto" - } - } - ], - "responses": { - "200": { - "description": "Success to the authentication.", - "schema": { - "$ref": "#/definitions/model.Account" - } - }, - "401": { - "description": "Failed to the authentication.", - "schema": { - "type": "boolean" - } - } - } - } - }, - "/auth/loginAccount": { - "get": { - "description": "Get the account data of logged-in user.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Auth" - ], - "summary": "Get the account data of logged-in user.", - "responses": { - "200": { - "description": "Success to fetch the account data. If the security function is disable, it returns the dummy data.", - "schema": { - "$ref": "#/definitions/model.Account" - } - }, - "401": { - "description": "The current user haven't logged-in yet. Returns false.", - "schema": { - "type": "boolean" - } - } - } - } - }, - "/auth/loginStatus": { - "get": { - "description": "Get the login status of current logged-in user.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Auth" - ], - "summary": "Get the login status.", - "responses": { - "200": { - "description": "The current user have already logged-in. Returns true.", - "schema": { - "type": "boolean" - } - }, - "401": { - "description": "The current user haven't logged-in yet. Returns false.", - "schema": { - "type": "boolean" - } - } - } - } - }, - "/auth/logout": { - "post": { - "description": "Logout.", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Auth" - ], - "summary": "Logout.", - "responses": { - "200": { - "description": "" - } - } - } - }, - "/books": { - "get": { - "description": "Get the list of matched books by searching", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Books" - ], - "summary": "Get a book list", - "parameters": [ - { - "type": "string", - "description": "Keyword", - "name": "query", - "in": "query" - }, - { - "type": "integer", - "description": "Page number", - "name": "page", - "in": "query" - }, - { - "type": "integer", - "description": "Item size per page", - "name": "size", - "in": "query" - } - ], - "responses": { - "200": { - "description": "Success to fetch a book list.", - "schema": { - "$ref": "#/definitions/model.Page" - } - }, - "400": { - "description": "Failed to fetch data.", - "schema": { - "type": "string" - } - }, - "401": { - "description": "Failed to the authentication. Returns false.", - "schema": { - "type": "boolean" - } - } - } - }, - "post": { - "description": "Create a new book", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Books" - ], - "summary": "Create a new book", - "parameters": [ - { - "description": "a new book data for creating", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/dto.BookDto" - } - } - ], - "responses": { - "200": { - "description": "Success to create a new book.", - "schema": { - "$ref": "#/definitions/model.Book" - } - }, - "400": { - "description": "Failed to the registration.", - "schema": { - "type": "string" - } - }, - "401": { - "description": "Failed to the authentication. Returns false.", - "schema": { - "type": "boolean" - } - } - } - } - }, - "/books/{book_id}": { - "get": { - "description": "Get a book", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Books" - ], - "summary": "Get a book", - "parameters": [ - { - "type": "integer", - "description": "Book ID", - "name": "book_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Success to fetch data.", - "schema": { - "$ref": "#/definitions/model.Book" - } - }, - "400": { - "description": "Failed to fetch data.", - "schema": { - "type": "string" - } - }, - "401": { - "description": "Failed to the authentication. Returns false.", - "schema": { - "type": "boolean" - } - } - } - }, - "put": { - "description": "Update the existing book", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Books" - ], - "summary": "Update the existing book", - "parameters": [ - { - "type": "integer", - "description": "Book ID", - "name": "book_id", - "in": "path", - "required": true - }, - { - "description": "the book data for updating", - "name": "data", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/dto.BookDto" - } - } - ], - "responses": { - "200": { - "description": "Success to update the existing book.", - "schema": { - "$ref": "#/definitions/model.Book" - } - }, - "400": { - "description": "Failed to the update.", - "schema": { - "type": "string" - } - }, - "401": { - "description": "Failed to the authentication. Returns false.", - "schema": { - "type": "boolean" - } - } - } - }, - "delete": { - "description": "Delete the existing book", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Books" - ], - "summary": "Delete the existing book", - "parameters": [ - { - "type": "integer", - "description": "Book ID", - "name": "book_id", - "in": "path", - "required": true - } - ], - "responses": { - "200": { - "description": "Success to delete the existing book.", - "schema": { - "$ref": "#/definitions/model.Book" - } - }, - "400": { - "description": "Failed to the delete.", - "schema": { - "type": "string" - } - }, - "401": { - "description": "Failed to the authentication. Returns false.", - "schema": { - "type": "boolean" - } - } - } - } - }, - "/categories": { - "get": { - "description": "Get a category list", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Categories" - ], - "summary": "Get a category list", - "responses": { - "200": { - "description": "Success to fetch a category list.", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/model.Category" - } - } - }, - "401": { - "description": "Failed to the authentication.", - "schema": { - "type": "string" - } - } - } - } - }, - "/formats": { - "get": { - "description": "Get a format list", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Formats" - ], - "summary": "Get a format list", - "responses": { - "200": { - "description": "Success to fetch a format list.", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/model.Format" - } - } - }, - "401": { - "description": "Failed to the authentication.", - "schema": { - "type": "string" - } - } - } - } - }, - "/health": { - "get": { - "description": "Get the status of this application", - "consumes": [ - "application/json" - ], - "produces": [ - "application/json" - ], - "tags": [ - "Health" - ], - "summary": "Get the status of this application", - "responses": { - "200": { - "description": "healthy: This application is started.", - "schema": { - "type": "string" - } - }, - "404": { - "description": "None: This application is stopped.", - "schema": { - "type": "string" - } - } - } - } - } - }, - "definitions": { - "dto.BookDto": { - "type": "object", - "required": [ - "isbn", - "title" - ], - "properties": { - "categoryId": { - "type": "integer" - }, - "formatId": { - "type": "integer" - }, - "isbn": { - "type": "string" - }, - "title": { - "type": "string" - } - } - }, - "dto.LoginDto": { - "type": "object", - "properties": { - "password": { - "type": "string" - }, - "username": { - "type": "string" - } - } - }, - "model.Account": { - "type": "object", - "properties": { - "authority": { - "$ref": "#/definitions/model.Authority" - }, - "authority_id": { - "type": "integer" - }, - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, - "model.Authority": { - "type": "object", - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, - "model.Book": { - "type": "object", - "properties": { - "category": { - "$ref": "#/definitions/model.Category" - }, - "categoryId": { - "type": "integer" - }, - "format": { - "$ref": "#/definitions/model.Format" - }, - "formatId": { - "type": "integer" - }, - "id": { - "type": "integer" - }, - "isbn": { - "type": "string" - }, - "title": { - "type": "string" - } - } - }, - "model.Category": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, - "model.Format": { - "type": "object", - "required": [ - "name" - ], - "properties": { - "id": { - "type": "integer" - }, - "name": { - "type": "string" - } - } - }, - "model.Page": { - "type": "object", - "properties": { - "content": { - "type": "array", - "items": { - "$ref": "#/definitions/model.Book" - } - }, - "last": { - "type": "boolean" - }, - "numberOfElements": { - "type": "integer" - }, - "page": { - "type": "integer" - }, - "size": { - "type": "integer" - }, - "totalElements": { - "type": "integer" - }, - "totalPages": { - "type": "integer" - } - } - } - } -}` - -type swaggerInfo struct { - Version string - Host string - BasePath string - Schemes []string - Title string - Description string -} - -// SwaggerInfo holds exported Swagger Info so clients can modify it -var SwaggerInfo = swaggerInfo{ - Version: "1.5.1", - Host: "localhost:8080", - BasePath: "/api", - Schemes: []string{}, - Title: "go-webapp-sample API", - Description: "This is API specification for go-webapp-sample project.", -} - -type s struct{} - -func (s *s) ReadDoc() string { - sInfo := SwaggerInfo - sInfo.Description = strings.Replace(sInfo.Description, "\n", "\\n", -1) - - t, err := template.New("swagger_info").Funcs(template.FuncMap{ - "marshal": func(v interface{}) string { - a, _ := json.Marshal(v) - return string(a) - }, - }).Parse(doc) - if err != nil { - return doc - } - - var tpl bytes.Buffer - if err := t.Execute(&tpl, sInfo); err != nil { - return doc - } - - return tpl.String() -} - -func init() { - swag.Register(swag.Name, &s{}) -} diff --git a/src/sample-golang-echo-app/go.mod b/src/sample-golang-echo-app/go.mod deleted file mode 100644 index 1232327..0000000 --- a/src/sample-golang-echo-app/go.mod +++ /dev/null @@ -1,65 +0,0 @@ -module github.com/ybkuroki/go-webapp-sample - -go 1.20 - -require ( - github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 - github.com/garyburd/redigo v1.6.4 // indirect - github.com/go-openapi/jsonreference v0.20.2 // indirect - github.com/go-openapi/swag v0.22.3 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/gorilla/sessions v1.2.1 - github.com/labstack/echo-contrib v0.15.0 - github.com/labstack/echo/v4 v4.10.2 - github.com/leodido/go-urn v1.2.4 // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-isatty v0.0.19 // indirect - github.com/mattn/go-sqlite3 v1.14.17 // indirect - github.com/moznion/go-optional v0.10.0 - github.com/stretchr/testify v1.8.4 - github.com/swaggo/echo-swagger v1.4.0 - github.com/swaggo/swag v1.16.1 - github.com/valyala/fasttemplate v1.2.2 - go.uber.org/zap v1.24.0 - golang.org/x/crypto v0.9.0 - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/tools v0.9.3 // indirect - gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b - gopkg.in/go-playground/assert.v1 v1.2.1 // indirect - gopkg.in/go-playground/validator.v9 v9.31.0 - gopkg.in/natefinch/lumberjack.v2 v2.2.1 - gopkg.in/yaml.v3 v3.0.1 - gorm.io/driver/mysql v1.5.1 - gorm.io/driver/postgres v1.5.2 - gorm.io/driver/sqlite v1.5.1 - gorm.io/gorm v1.25.1 -) - -require ( - github.com/KyleBanks/depth v1.2.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/go-openapi/jsonpointer v0.19.6 // indirect - github.com/go-openapi/spec v0.20.9 // indirect - github.com/go-playground/locales v0.14.1 // indirect - github.com/go-sql-driver/mysql v1.7.1 // indirect - github.com/golang-jwt/jwt v3.2.2+incompatible // indirect - github.com/gorilla/context v1.1.1 // indirect - github.com/gorilla/securecookie v1.1.1 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect - github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect - github.com/jackc/pgx/v5 v5.3.1 // indirect - github.com/jinzhu/inflection v1.0.0 // indirect - github.com/jinzhu/now v1.1.5 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/labstack/gommon v0.4.0 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/rogpeppe/go-internal v1.10.0 // indirect - github.com/swaggo/files/v2 v2.0.0 // indirect - github.com/valyala/bytebufferpool v1.0.0 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - golang.org/x/text v0.9.0 // indirect - golang.org/x/time v0.3.0 // indirect -) diff --git a/src/sample-golang-echo-app/go.sum b/src/sample-golang-echo-app/go.sum deleted file mode 100644 index 5d16da5..0000000 --- a/src/sample-golang-echo-app/go.sum +++ /dev/null @@ -1,165 +0,0 @@ -github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= -github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751 h1:JYp7IbQjafoB+tBA3gMyHYHrpOtNuDiK/uB5uXxq5wM= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -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/garyburd/redigo v1.6.4 h1:LFu2R3+ZOPgSMWMOL+saa/zXRjw0ID2G8FepO53BGlg= -github.com/garyburd/redigo v1.6.4/go.mod h1:rTb6epsqigu3kYKBnaF028A7Tf/Aw5s0cqA47doKKqw= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE= -github.com/go-openapi/jsonpointer v0.19.6/go.mod h1:osyAmYz/mB/C3I+WsTTSgw1ONzaLJoLCyoi6/zppojs= -github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= -github.com/go-openapi/jsonreference v0.20.2 h1:3sVjiK66+uXK/6oQ8xgcRKcFgQ5KXa2KvnJRumpMGbE= -github.com/go-openapi/jsonreference v0.20.2/go.mod h1:Bl1zwGIM8/wsvqjsOQLJ/SH+En5Ap4rVB5KVcIDZG2k= -github.com/go-openapi/spec v0.20.9 h1:xnlYNQAwKd2VQRRfwTEI0DcK+2cbuvI/0c7jx3gA8/8= -github.com/go-openapi/spec v0.20.9/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= -github.com/go-openapi/swag v0.22.3 h1:yMBqmnQ0gyZvEb/+KzuWZOXgllrXT4SADYbvDaXHv/g= -github.com/go-openapi/swag v0.22.3/go.mod h1:UzaqsxGiab7freDnrUUra0MwWfN/q7tE4j+VcZ0yl14= -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-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= -github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= -github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= -github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= -github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= -github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= -github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= -github.com/gorilla/sessions v1.2.1 h1:DHd3rPN5lE3Ts3D8rKkQ8x/0kqfeNmBAaiSi+o7FsgI= -github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= -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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= -github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= -github.com/jackc/pgx/v5 v5.3.1/go.mod h1:t3JDKnCBlYIc0ewLF0Q7B8MXmoIaBOZj/ic7iHozM/8= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= -github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/labstack/echo-contrib v0.15.0 h1:9K+oRU265y4Mu9zpRDv3X+DGTqUALY6oRHCSZZKCRVU= -github.com/labstack/echo-contrib v0.15.0/go.mod h1:lei+qt5CLB4oa7VHTE0yEfQSEB9XTJI1LUqko9UWvo4= -github.com/labstack/echo/v4 v4.10.2 h1:n1jAhnq/elIFTHr1EYpiYtyKgx4RW9ccVgkqByZaN2M= -github.com/labstack/echo/v4 v4.10.2/go.mod h1:OEyqf2//K1DFdE57vw2DRgWY0M7s65IVQO2FzvI4J5k= -github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8= -github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= -github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= -github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -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.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= -github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= -github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= -github.com/moznion/go-optional v0.10.0 h1:YE42pzLDp6vc9zi/2hyaHYJesjahZEgFXEN1u5DMwMA= -github.com/moznion/go-optional v0.10.0/go.mod h1:l3mLmsyp2bWTvWKjEm5MT7lo3g5MRlNIflxFB0XTASA= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -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/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= -github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= -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/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -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.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= -github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/swaggo/echo-swagger v1.4.0 h1:RCxLKySw1SceHLqnmc41pKyiIeE+OiD7NSI7FUOBlLo= -github.com/swaggo/echo-swagger v1.4.0/go.mod h1:Wh3VlwjZGZf/LH0s81tz916JokuPG7y/ZqaqnckYqoQ= -github.com/swaggo/files/v2 v2.0.0 h1:hmAt8Dkynw7Ssz46F6pn8ok6YmGZqHSVLZ+HQM7i0kw= -github.com/swaggo/files/v2 v2.0.0/go.mod h1:24kk2Y9NYEJ5lHuCra6iVwkMjIekMCaFq/0JQj66kyM= -github.com/swaggo/swag v1.16.1 h1:fTNRhKstPKxcnoKsytm4sahr8FaYzUcT7i1/3nd/fBg= -github.com/swaggo/swag v1.16.1/go.mod h1:9/LMvHycG3NFHfR6LwvikHv5iFvmPADQ359cKikGxto= -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.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= -github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= -go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.24.0 h1:FiJd5l1UOLj0wCgbSE0rwwXHzEdAZS6hiiSnxJN/D60= -go.uber.org/zap v1.24.0/go.mod h1:2kMP+WWQ8aoFoedH3T2sq6iJ2yDWpHbP0f6MQbS9Gkg= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/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.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= -golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= -gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b h1:U/Uqd1232+wrnHOvWNaxrNqn/kFnr4yu4blgPtQt0N8= -gopkg.in/boj/redistore.v1 v1.0.0-20160128113310-fc113767cd6b/go.mod h1:fgfIZMlsafAHpspcks2Bul+MWUNw/2dyQmjC2faKjtg= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= -gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= -gopkg.in/go-playground/validator.v9 v9.31.0 h1:bmXmP2RSNtFES+bn4uYuHT7iJFJv7Vj+an+ZQdDaD1M= -gopkg.in/go-playground/validator.v9 v9.31.0/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/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= -gorm.io/driver/mysql v1.5.1 h1:WUEH5VF9obL/lTtzjmML/5e6VfFR/788coz2uaVCAZw= -gorm.io/driver/mysql v1.5.1/go.mod h1:Jo3Xu7mMhCyj8dlrb3WoCaRd1FhsVh+yMXb1jUInf5o= -gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= -gorm.io/driver/postgres v1.5.2/go.mod h1:fmpX0m2I1PKuR7mKZiEluwrP3hbs+ps7JIGMUBpCgl8= -gorm.io/driver/sqlite v1.5.1 h1:hYyrLkAWE71bcarJDPdZNTLWtr8XrSjOWyjUYI6xdL4= -gorm.io/driver/sqlite v1.5.1/go.mod h1:7MZZ2Z8bqyfSQA1gYEV6MagQWj3cpUkJj9Z+d1HEMEQ= -gorm.io/gorm v1.25.1 h1:nsSALe5Pr+cM3V1qwwQ7rOkw+6UeLrX5O4v3llhHa64= -gorm.io/gorm v1.25.1/go.mod h1:L4uxeKpfBml98NYqVqwAdmV1a2nBtAec/cf3fpucW/k= diff --git a/src/sample-golang-echo-app/logger/gormlogger.go b/src/sample-golang-echo-app/logger/gormlogger.go deleted file mode 100644 index e1cf331..0000000 --- a/src/sample-golang-echo-app/logger/gormlogger.go +++ /dev/null @@ -1,60 +0,0 @@ -package logger - -import ( - "context" - "fmt" - "time" - - gormLogger "gorm.io/gorm/logger" - gormUtils "gorm.io/gorm/utils" -) - -// Customize SQL Logger for gorm library -// ref: https://github.com/wantedly/gorm-zap -// ref: https://github.com/go-gorm/gorm/blob/master/logger/logger.go - -const ( - logTitle = "[gorm] " - sqlFormat = logTitle + "%s" - messageFormat = logTitle + "%s, %s" - errorFormat = logTitle + "%s, %s, %s" - slowThreshold = 200 -) - -// LogMode The log level of gorm logger is overwrited by the log level of Zap logger. -func (log *logger) LogMode(_ gormLogger.LogLevel) gormLogger.Interface { - return log -} - -// Info prints a information log. -func (log *logger) Info(_ context.Context, msg string, data ...interface{}) { - log.Zap.Infof(messageFormat, append([]interface{}{msg, gormUtils.FileWithLineNum()}, data...)...) -} - -// Warn prints a warning log. -func (log *logger) Warn(_ context.Context, msg string, data ...interface{}) { - log.Zap.Warnf(messageFormat, append([]interface{}{msg, gormUtils.FileWithLineNum()}, data...)...) -} - -// Error prints a error log. -func (log *logger) Error(_ context.Context, msg string, data ...interface{}) { - log.Zap.Errorf(messageFormat, append([]interface{}{msg, gormUtils.FileWithLineNum()}, data...)...) -} - -// Trace prints a trace log such as sql, source file and error. -func (log *logger) Trace(_ context.Context, begin time.Time, fc func() (string, int64), err error) { - elapsed := time.Since(begin) - - switch { - case err != nil: - sql, _ := fc() - log.GetZapLogger().Errorf(errorFormat, gormUtils.FileWithLineNum(), err, sql) - case elapsed > slowThreshold*time.Millisecond && slowThreshold*time.Millisecond != 0: - sql, _ := fc() - slowLog := fmt.Sprintf("SLOW SQL >= %v", slowThreshold) - log.GetZapLogger().Warnf(errorFormat, gormUtils.FileWithLineNum(), slowLog, sql) - default: - sql, _ := fc() - log.GetZapLogger().Debugf(sqlFormat, sql) - } -} diff --git a/src/sample-golang-echo-app/logger/logger.go b/src/sample-golang-echo-app/logger/logger.go deleted file mode 100644 index 6c30202..0000000 --- a/src/sample-golang-echo-app/logger/logger.go +++ /dev/null @@ -1,71 +0,0 @@ -package logger - -import ( - "context" - "embed" - "fmt" - "os" - "time" - - "github.com/ybkuroki/go-webapp-sample/config" - "go.uber.org/zap" - "gopkg.in/natefinch/lumberjack.v2" - "gopkg.in/yaml.v3" - gormLogger "gorm.io/gorm/logger" -) - -// Config represents the setting for zap logger. -type Config struct { - ZapConfig zap.Config `json:"zap_config" yaml:"zap_config"` - LogRotate lumberjack.Logger `json:"log_rotate" yaml:"log_rotate"` -} - -// Logger is an alternative implementation of *gorm.Logger -type Logger interface { - GetZapLogger() *zap.SugaredLogger - LogMode(level gormLogger.LogLevel) gormLogger.Interface - Info(ctx context.Context, msg string, data ...interface{}) - Warn(ctx context.Context, msg string, data ...interface{}) - Error(ctx context.Context, msg string, data ...interface{}) - Trace(ctx context.Context, begin time.Time, fc func() (string, int64), err error) -} - -type logger struct { - Zap *zap.SugaredLogger -} - -// NewLogger is constructor for logger -func NewLogger(sugar *zap.SugaredLogger) Logger { - return &logger{Zap: sugar} -} - -// InitLogger create logger object for *gorm.DB from *echo.Logger -func InitLogger(env string, yamlFile embed.FS) Logger { - configYaml, err := yamlFile.ReadFile(fmt.Sprintf(config.LoggerConfigPath, env)) - if err != nil { - fmt.Printf("Failed to read logger configuration: %s", err) - os.Exit(config.ErrExitStatus) - } - var myConfig *Config - if err = yaml.Unmarshal(configYaml, &myConfig); err != nil { - fmt.Printf("Failed to read zap logger configuration: %s", err) - os.Exit(config.ErrExitStatus) - } - var zap *zap.Logger - zap, err = build(myConfig) - if err != nil { - fmt.Printf("Failed to compose zap logger : %s", err) - os.Exit(config.ErrExitStatus) - } - sugar := zap.Sugar() - // set package varriable logger. - log := NewLogger(sugar) - log.GetZapLogger().Infof("Success to read zap logger configuration: zaplogger." + env + ".yml") - _ = zap.Sync() - return log -} - -// GetZapLogger returns zapSugaredLogger -func (log *logger) GetZapLogger() *zap.SugaredLogger { - return log.Zap -} diff --git a/src/sample-golang-echo-app/logger/zaplogger.go b/src/sample-golang-echo-app/logger/zaplogger.go deleted file mode 100644 index 417f788..0000000 --- a/src/sample-golang-echo-app/logger/zaplogger.go +++ /dev/null @@ -1,88 +0,0 @@ -package logger - -import ( - "errors" - "os" - - "go.uber.org/zap" - "go.uber.org/zap/zapcore" - "gopkg.in/natefinch/lumberjack.v2" -) - -func build(cfg *Config) (*zap.Logger, error) { - var zapCfg = cfg.ZapConfig - enc, _ := newEncoder(zapCfg) - writer, errWriter := openWriters(cfg) - - if zapCfg.Level == (zap.AtomicLevel{}) { - return nil, errors.New("missing Level") - } - - log := zap.New(zapcore.NewCore(enc, writer, zapCfg.Level), buildOptions(zapCfg, errWriter)...) - return log, nil -} - -func newEncoder(cfg zap.Config) (zapcore.Encoder, error) { - switch cfg.Encoding { - case "console": - return zapcore.NewConsoleEncoder(cfg.EncoderConfig), nil - case "json": - return zapcore.NewJSONEncoder(cfg.EncoderConfig), nil - } - return nil, errors.New("failed to set encoder") -} - -func openWriters(cfg *Config) (zapcore.WriteSyncer, zapcore.WriteSyncer) { - writer := open(cfg.ZapConfig.OutputPaths, &cfg.LogRotate) - errWriter := open(cfg.ZapConfig.ErrorOutputPaths, &cfg.LogRotate) - return writer, errWriter -} - -func open(paths []string, rotateCfg *lumberjack.Logger) zapcore.WriteSyncer { - writers := make([]zapcore.WriteSyncer, 0, len(paths)) - for _, path := range paths { - writer := newWriter(path, rotateCfg) - writers = append(writers, writer) - } - writer := zap.CombineWriteSyncers(writers...) - return writer -} - -func newWriter(path string, rotateCfg *lumberjack.Logger) zapcore.WriteSyncer { - switch path { - case "stdout": - return os.Stdout - case "stderr": - return os.Stderr - } - sink := zapcore.AddSync( - &lumberjack.Logger{ - Filename: path, - MaxSize: rotateCfg.MaxSize, - MaxBackups: rotateCfg.MaxBackups, - MaxAge: rotateCfg.MaxAge, - Compress: rotateCfg.Compress, - }, - ) - return sink -} - -func buildOptions(cfg zap.Config, errWriter zapcore.WriteSyncer) []zap.Option { - opts := []zap.Option{zap.ErrorOutput(errWriter)} - if cfg.Development { - opts = append(opts, zap.Development()) - } - - if !cfg.DisableCaller { - opts = append(opts, zap.AddCaller()) - } - - stackLevel := zap.ErrorLevel - if cfg.Development { - stackLevel = zap.WarnLevel - } - if !cfg.DisableStacktrace { - opts = append(opts, zap.AddStacktrace(stackLevel)) - } - return opts -} diff --git a/src/sample-golang-echo-app/main.go b/src/sample-golang-echo-app/main.go deleted file mode 100644 index 973e5c7..0000000 --- a/src/sample-golang-echo-app/main.go +++ /dev/null @@ -1,66 +0,0 @@ -package main - -import ( - "embed" - - "github.com/labstack/echo/v4" - - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/container" - "github.com/ybkuroki/go-webapp-sample/logger" - "github.com/ybkuroki/go-webapp-sample/middleware" - "github.com/ybkuroki/go-webapp-sample/migration" - "github.com/ybkuroki/go-webapp-sample/repository" - "github.com/ybkuroki/go-webapp-sample/router" - "github.com/ybkuroki/go-webapp-sample/session" -) - -//go:embed resources/config/application.*.yml -var yamlFile embed.FS - -//go:embed resources/config/zaplogger.*.yml -var zapYamlFile embed.FS - -//go:embed resources/public/* -var staticFile embed.FS - -//go:embed resources/config/messages.properties -var propsFile embed.FS - -// @title go-webapp-sample API -// @version 1.5.1 -// @description This is API specification for go-webapp-sample project. - -// @license.name MIT -// @license.url https://opensource.org/licenses/mit-license.php - -// @host localhost:8080 -// @BasePath /api -func main() { - e := echo.New() - - conf, env := config.LoadAppConfig(yamlFile) - logger := logger.InitLogger(env, zapYamlFile) - logger.GetZapLogger().Infof("Loaded this configuration : application." + env + ".yml") - - messages := config.LoadMessagesConfig(propsFile) - logger.GetZapLogger().Infof("Loaded messages.properties") - - rep := repository.NewBookRepository(logger, conf) - sess := session.NewSession() - container := container.NewContainer(rep, sess, conf, messages, logger, env) - - migration.CreateDatabase(container) - migration.InitMasterData(container) - - router.Init(e, container) - middleware.InitLoggerMiddleware(e, container) - middleware.InitSessionMiddleware(e, container) - middleware.StaticContentsMiddleware(e, container, staticFile) - - if err := e.Start(":8080"); err != nil { - logger.GetZapLogger().Errorf(err.Error()) - } - - defer rep.Close() -} diff --git a/src/sample-golang-echo-app/middleware/middleware.go b/src/sample-golang-echo-app/middleware/middleware.go deleted file mode 100644 index 6333345..0000000 --- a/src/sample-golang-echo-app/middleware/middleware.go +++ /dev/null @@ -1,183 +0,0 @@ -package middleware - -import ( - "embed" - "fmt" - "io" - "net/http" - "regexp" - "strconv" - - "github.com/gorilla/sessions" - "github.com/labstack/echo-contrib/session" - "github.com/labstack/echo/v4" - echomd "github.com/labstack/echo/v4/middleware" - "github.com/valyala/fasttemplate" - "github.com/ybkuroki/go-webapp-sample/container" - "gopkg.in/boj/redistore.v1" -) - -// InitLoggerMiddleware initialize a middleware for logger. -func InitLoggerMiddleware(e *echo.Echo, container container.Container) { - e.Use(RequestLoggerMiddleware(container)) - e.Use(ActionLoggerMiddleware(container)) -} - -// InitSessionMiddleware initialize a middleware for session management. -func InitSessionMiddleware(e *echo.Echo, container container.Container) { - conf := container.GetConfig() - logger := container.GetLogger() - - e.Use(SessionMiddleware(container)) - - if conf.Extension.SecurityEnabled { - if conf.Redis.Enabled { - logger.GetZapLogger().Infof("Try redis connection") - address := fmt.Sprintf("%s:%s", conf.Redis.Host, conf.Redis.Port) - store, err := redistore.NewRediStore(conf.Redis.ConnectionPoolSize, "tcp", address, "", []byte("secret")) - if err != nil { - logger.GetZapLogger().Errorf("Failure redis connection") - } - e.Use(session.Middleware(store)) - logger.GetZapLogger().Infof(fmt.Sprintf("Success redis connection, %s", address)) - } else { - e.Use(session.Middleware(sessions.NewCookieStore([]byte("secret")))) - } - e.Use(AuthenticationMiddleware(container)) - } -} - -// RequestLoggerMiddleware is middleware for logging the contents of requests. -func RequestLoggerMiddleware(container container.Container) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - req := c.Request() - res := c.Response() - if err := next(c); err != nil { - c.Error(err) - } - - template := fasttemplate.New(container.GetConfig().Log.RequestLogFormat, "${", "}") - logstr := template.ExecuteFuncString(func(w io.Writer, tag string) (int, error) { - switch tag { - case "remote_ip": - return w.Write([]byte(c.RealIP())) - case "account_name": - if account := container.GetSession().GetAccount(); account != nil { - return w.Write([]byte(account.Name)) - } - return w.Write([]byte("None")) - case "uri": - return w.Write([]byte(req.RequestURI)) - case "method": - return w.Write([]byte(req.Method)) - case "status": - return w.Write([]byte(strconv.Itoa(res.Status))) - default: - return w.Write([]byte("")) - } - }) - container.GetLogger().GetZapLogger().Infof(logstr) - return nil - } - } -} - -// ActionLoggerMiddleware is middleware for logging the start and end of controller processes. -// ref: https://echo.labstack.com/cookbook/middleware -func ActionLoggerMiddleware(container container.Container) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - logger := container.GetLogger() - logger.GetZapLogger().Debugf(c.Path() + " Action Start") - if err := next(c); err != nil { - c.Error(err) - } - logger.GetZapLogger().Debugf(c.Path() + " Action End") - return nil - } - } -} - -// SessionMiddleware is a middleware for setting a context to a session. -func SessionMiddleware(container container.Container) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - container.GetSession().SetContext(c) - if err := next(c); err != nil { - c.Error(err) - } - return nil - } - } -} - -// StaticContentsMiddleware is the middleware for loading the static files. -func StaticContentsMiddleware(e *echo.Echo, container container.Container, staticFile embed.FS) { - conf := container.GetConfig() - if conf.StaticContents.Enabled { - staticConfig := echomd.StaticConfig{ - Root: "resources/public", - Index: "index.html", - Browse: false, - HTML5: true, - Filesystem: http.FS(staticFile), - } - if conf.Swagger.Enabled { - staticConfig.Skipper = func(c echo.Context) bool { - return equalPath(c.Path(), []string{conf.Swagger.Path}) - } - } - e.Use(echomd.StaticWithConfig(staticConfig)) - container.GetLogger().GetZapLogger().Infof("Served the static contents.") - } -} - -// AuthenticationMiddleware is the middleware of session authentication for echo. -func AuthenticationMiddleware(container container.Container) echo.MiddlewareFunc { - return func(next echo.HandlerFunc) echo.HandlerFunc { - return func(c echo.Context) error { - if !hasAuthorization(c, container) { - return c.JSON(http.StatusUnauthorized, false) - } - if err := next(c); err != nil { - c.Error(err) - } - return nil - } - } -} - -// hasAuthorization judges whether the user has the right to access the path. -func hasAuthorization(c echo.Context, container container.Container) bool { - currentPath := c.Path() - if equalPath(currentPath, container.GetConfig().Security.AuthPath) { - if equalPath(currentPath, container.GetConfig().Security.ExculdePath) { - return true - } - account := container.GetSession().GetAccount() - if account == nil { - return false - } - if account.Authority.Name == "Admin" && equalPath(currentPath, container.GetConfig().Security.AdminPath) { - _ = container.GetSession().Save() - return true - } - if account.Authority.Name == "User" && equalPath(currentPath, container.GetConfig().Security.UserPath) { - _ = container.GetSession().Save() - return true - } - return false - } - return true -} - -// equalPath judges whether a given path contains in the path list. -func equalPath(cpath string, paths []string) bool { - for i := range paths { - if regexp.MustCompile(paths[i]).Match([]byte(cpath)) { - return true - } - } - return false -} diff --git a/src/sample-golang-echo-app/migration/db_generator.go b/src/sample-golang-echo-app/migration/db_generator.go deleted file mode 100644 index 2e4e3ac..0000000 --- a/src/sample-golang-echo-app/migration/db_generator.go +++ /dev/null @@ -1,25 +0,0 @@ -package migration - -import ( - "github.com/ybkuroki/go-webapp-sample/container" - "github.com/ybkuroki/go-webapp-sample/model" -) - -// CreateDatabase creates the tables used in this application. -func CreateDatabase(container container.Container) { - if container.GetConfig().Database.Migration { - db := container.GetRepository() - - _ = db.DropTableIfExists(&model.Book{}) - _ = db.DropTableIfExists(&model.Category{}) - _ = db.DropTableIfExists(&model.Format{}) - _ = db.DropTableIfExists(&model.Account{}) - _ = db.DropTableIfExists(&model.Authority{}) - - _ = db.AutoMigrate(&model.Book{}) - _ = db.AutoMigrate(&model.Category{}) - _ = db.AutoMigrate(&model.Format{}) - _ = db.AutoMigrate(&model.Account{}) - _ = db.AutoMigrate(&model.Authority{}) - } -} diff --git a/src/sample-golang-echo-app/migration/master_generator.go b/src/sample-golang-echo-app/migration/master_generator.go deleted file mode 100644 index 007decd..0000000 --- a/src/sample-golang-echo-app/migration/master_generator.go +++ /dev/null @@ -1,32 +0,0 @@ -package migration - -import ( - "github.com/ybkuroki/go-webapp-sample/container" - "github.com/ybkuroki/go-webapp-sample/model" -) - -// InitMasterData creates the master data used in this application. -func InitMasterData(container container.Container) { - if container.GetConfig().Extension.MasterGenerator { - rep := container.GetRepository() - - r := model.NewAuthority("Admin") - _, _ = r.Create(rep) - a := model.NewAccountWithPlainPassword("test", "test", r.ID) - _, _ = a.Create(rep) - a = model.NewAccountWithPlainPassword("test2", "test2", r.ID) - _, _ = a.Create(rep) - - c := model.NewCategory("Technical Book") - _, _ = c.Create(rep) - c = model.NewCategory("Magazine") - _, _ = c.Create(rep) - c = model.NewCategory("Novel") - _, _ = c.Create(rep) - - f := model.NewFormat("Paper Book") - _, _ = f.Create(rep) - f = model.NewFormat("e-Book") - _, _ = f.Create(rep) - } -} diff --git a/src/sample-golang-echo-app/model/account.go b/src/sample-golang-echo-app/model/account.go deleted file mode 100644 index 6f9d8b9..0000000 --- a/src/sample-golang-echo-app/model/account.go +++ /dev/null @@ -1,74 +0,0 @@ -package model - -import ( - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/repository" - "golang.org/x/crypto/bcrypt" -) - -// Account defines struct of account data. -type Account struct { - ID uint `gorm:"primary_key" json:"id"` - Name string `json:"name"` - Password string `json:"-"` - AuthorityID uint `json:"authority_id"` - Authority *Authority `json:"authority"` -} - -// RecordAccount defines struct represents the record of the database. -type RecordAccount struct { - ID uint - Name string - Password string - AuthorityID uint - AuthorityName string -} - -const selectAccount = "select a.id as id, a.name as name, a.password as password," + - " r.id as authority_id, r.name as authority_name " + - " from account_master a inner join authority_master r on a.authority_id = r.id " - -// TableName returns the table name of account struct and it is used by gorm. -func (Account) TableName() string { - return "account_master" -} - -// NewAccount is constructor. -func NewAccount(name string, password string, authorityID uint) *Account { - return &Account{Name: name, Password: password, AuthorityID: authorityID} -} - -// NewAccountWithPlainPassword is constructor. And it is encoded plain text password by using bcrypt. -func NewAccountWithPlainPassword(name string, password string, authorityID uint) *Account { - hashed, _ := bcrypt.GenerateFromPassword([]byte(password), config.PasswordHashCost) - return &Account{Name: name, Password: string(hashed), AuthorityID: authorityID} -} - -// FindByName returns accounts full matched given account name. -func (a *Account) FindByName(rep repository.Repository, name string) (*Account, error) { - var account *Account - - var rec RecordAccount - rep.Raw(selectAccount+" where a.name = ?", name).Scan(&rec) - account = convertToAccount(&rec) - - return account, nil -} - -// Create persists this account data. -func (a *Account) Create(rep repository.Repository) (*Account, error) { - if err := rep.Select("name", "password", "authority_id").Create(a).Error; err != nil { - return nil, err - } - return a, nil -} - -func convertToAccount(rec *RecordAccount) *Account { - r := &Authority{ID: rec.AuthorityID, Name: rec.AuthorityName} - return &Account{ID: rec.ID, Name: rec.Name, Password: rec.Password, AuthorityID: rec.AuthorityID, Authority: r} -} - -// ToString is return string of object -func (a *Account) ToString() string { - return toString(a) -} diff --git a/src/sample-golang-echo-app/model/authority.go b/src/sample-golang-echo-app/model/authority.go deleted file mode 100644 index 3097c7d..0000000 --- a/src/sample-golang-echo-app/model/authority.go +++ /dev/null @@ -1,34 +0,0 @@ -package model - -import ( - "github.com/ybkuroki/go-webapp-sample/repository" -) - -// Authority defines struct of authority data. -type Authority struct { - ID uint `gorm:"primary_key" json:"id"` - Name string `json:"name"` -} - -// TableName returns the table name of authority struct and it is used by gorm. -func (Authority) TableName() string { - return "authority_master" -} - -// NewAuthority is constructor. -func NewAuthority(name string) *Authority { - return &Authority{Name: name} -} - -// Create persists this authority data. -func (a *Authority) Create(rep repository.Repository) (*Authority, error) { - if err := rep.Create(a).Error; err != nil { - return nil, err - } - return a, nil -} - -// ToString is return string of object -func (a *Authority) ToString() string { - return toString(a) -} diff --git a/src/sample-golang-echo-app/model/base.go b/src/sample-golang-echo-app/model/base.go deleted file mode 100644 index f4c48f6..0000000 --- a/src/sample-golang-echo-app/model/base.go +++ /dev/null @@ -1,18 +0,0 @@ -package model - -import "encoding/json" - -// DomainObject defines the common interface for domain models. -type DomainObject interface { - Account | Authority | Book | Category | Format -} - -// toString returns the JSON data of the domain models. -func toString[T DomainObject](o *T) string { - var bytes []byte - var err error - if bytes, err = json.Marshal(o); err != nil { - return "" - } - return string(bytes) -} diff --git a/src/sample-golang-echo-app/model/book.go b/src/sample-golang-echo-app/model/book.go deleted file mode 100644 index f6a8358..0000000 --- a/src/sample-golang-echo-app/model/book.go +++ /dev/null @@ -1,197 +0,0 @@ -package model - -import ( - "database/sql" - "errors" - "math" - - "github.com/moznion/go-optional" - "github.com/ybkuroki/go-webapp-sample/repository" - "github.com/ybkuroki/go-webapp-sample/util" - "gorm.io/gorm" -) - -// Book defines struct of book data. -type Book struct { - ID uint `gorm:"primary_key" json:"id"` - Title string `json:"title"` - Isbn string `json:"isbn"` - CategoryID uint `json:"categoryId"` - Category *Category `json:"category"` - FormatID uint `json:"formatId"` - Format *Format `json:"format"` -} - -// RecordBook defines struct represents the record of the database. -type RecordBook struct { - ID uint - Title string - Isbn string - CategoryID uint - CategoryName string - FormatID uint - FormatName string -} - -const ( - selectBook = "select b.id as id, b.title as title, b.isbn as isbn, " + - "c.id as category_id, c.name as category_name, f.id as format_id, f.name as format_name " + - "from book b inner join category_master c on c.id = b.category_id inner join format_master f on f.id = b.format_id " - findByID = " where b.id = ?" - findByTitle = " where title like ? " -) - -// TableName returns the table name of book struct and it is used by gorm. -func (Book) TableName() string { - return "book" -} - -// NewBook is constructor -func NewBook(title string, isbn string, categoryID uint, formatID uint) *Book { - return &Book{Title: title, Isbn: isbn, CategoryID: categoryID, FormatID: formatID} -} - -// FindByID returns a book full matched given book's ID. -func (b *Book) FindByID(rep repository.Repository, id uint) optional.Option[*Book] { - var rec RecordBook - args := []interface{}{id} - - createRaw(rep, selectBook+findByID, "", "", args).Scan(&rec) - return convertToBook(&rec) -} - -// FindAll returns all books of the book table. -func (b *Book) FindAll(rep repository.Repository) (*[]Book, error) { - var books []Book - var err error - - if books, err = findRows(rep, selectBook, "", "", []interface{}{}); err != nil { - return nil, err - } - return &books, nil -} - -// FindAllByPage returns the page object of all books. -func (b *Book) FindAllByPage(rep repository.Repository, page string, size string) (*Page, error) { - var books []Book - var err error - - if books, err = findRows(rep, selectBook, page, size, []interface{}{}); err != nil { - return nil, err - } - p := createPage(&books, page, size) - return p, nil -} - -// FindByTitle returns the page object of books partially matched given book title. -func (b *Book) FindByTitle(rep repository.Repository, title string, page string, size string) (*Page, error) { - var books []Book - var err error - args := []interface{}{"%" + title + "%"} - - if books, err = findRows(rep, selectBook+findByTitle, page, size, args); err != nil { - return nil, err - } - p := createPage(&books, page, size) - return p, nil -} - -func findRows(rep repository.Repository, sqlquery string, page string, size string, args []interface{}) ([]Book, error) { - var books []Book - - var rec RecordBook - var rows *sql.Rows - var err error - - if rows, err = createRaw(rep, sqlquery, page, size, args).Rows(); err != nil { - return nil, err - } - for rows.Next() { - if err = rep.ScanRows(rows, &rec); err != nil { - return nil, err - } - - opt := convertToBook(&rec) - if opt.IsNone() { - return nil, errors.New("failed to fetch data") - } - book, _ := opt.Take() - books = append(books, *book) - } - return books, nil -} - -func createRaw(rep repository.Repository, sql string, pageNum string, pageSize string, args []interface{}) *gorm.DB { - if util.IsNumeric(pageNum) && util.IsNumeric(pageSize) { - page := util.ConvertToInt(pageNum) - size := util.ConvertToInt(pageSize) - args = append(args, size) - args = append(args, page*size) - sql += " limit ? offset ? " - } - if len(args) > 0 { - return rep.Raw(sql, args...) - } - return rep.Raw(sql) -} - -func createPage(books *[]Book, page string, size string) *Page { - p := NewPage() - p.Page = util.ConvertToInt(page) - p.Size = util.ConvertToInt(size) - p.NumberOfElements = p.Size - p.TotalElements = len(*books) - if p.TotalPages = int(math.Ceil(float64(p.TotalElements) / float64(p.Size))); p.TotalPages < 0 { - p.TotalPages = 0 - } - p.Content = books - - return p -} - -// Save persists this book data. -func (b *Book) Save(rep repository.Repository) (*Book, error) { - if err := rep.Save(b).Error; err != nil { - return nil, err - } - return b, nil -} - -// Update updates this book data. -func (b *Book) Update(rep repository.Repository) (*Book, error) { - if err := rep.Model(Book{}).Where("id = ?", b.ID).Select("title", "isbn", "category_id", "format_id").Updates(b).Error; err != nil { - return nil, err - } - return b, nil -} - -// Create persists this book data. -func (b *Book) Create(rep repository.Repository) (*Book, error) { - if err := rep.Select("title", "isbn", "category_id", "format_id").Create(b).Error; err != nil { - return nil, err - } - return b, nil -} - -// Delete deletes this book data. -func (b *Book) Delete(rep repository.Repository) (*Book, error) { - if err := rep.Delete(b).Error; err != nil { - return nil, err - } - return b, nil -} - -func convertToBook(rec *RecordBook) optional.Option[*Book] { - if rec.ID == 0 { - return optional.None[*Book]() - } - c := &Category{ID: rec.CategoryID, Name: rec.CategoryName} - f := &Format{ID: rec.FormatID, Name: rec.FormatName} - return optional.Some( - &Book{ID: rec.ID, Title: rec.Title, Isbn: rec.Isbn, CategoryID: rec.CategoryID, Category: c, FormatID: rec.FormatID, Format: f}) -} - -// ToString is return string of object -func (b *Book) ToString() string { - return toString(b) -} diff --git a/src/sample-golang-echo-app/model/category.go b/src/sample-golang-echo-app/model/category.go deleted file mode 100644 index 4f4a42a..0000000 --- a/src/sample-golang-echo-app/model/category.go +++ /dev/null @@ -1,65 +0,0 @@ -package model - -import ( - "github.com/moznion/go-optional" - "github.com/ybkuroki/go-webapp-sample/repository" -) - -// Category defines struct of category data. -type Category struct { - ID uint `gorm:"primary_key" json:"id"` - Name string `validate:"required" json:"name"` -} - -// TableName returns the table name of category struct and it is used by gorm. -func (Category) TableName() string { - return "category_master" -} - -// NewCategory is constructor -func NewCategory(name string) *Category { - return &Category{Name: name} -} - -// Exist returns true if a given category exits. -func (c *Category) Exist(rep repository.Repository, id uint) (bool, error) { - var count int64 - if err := rep.Where("id = ?", id).Count(&count).Error; err != nil { - return false, err - } - if count > 0 { - return true, nil - } - return false, nil -} - -// FindByID returns a category full matched given category's ID. -func (c *Category) FindByID(rep repository.Repository, id uint) optional.Option[*Category] { - var category Category - if err := rep.Where("id = ?", id).First(&category).Error; err != nil { - return optional.None[*Category]() - } - return optional.Some(&category) -} - -// FindAll returns all categories of the category table. -func (c *Category) FindAll(rep repository.Repository) (*[]Category, error) { - var categories []Category - if err := rep.Find(&categories).Error; err != nil { - return nil, err - } - return &categories, nil -} - -// Create persists this category data. -func (c *Category) Create(rep repository.Repository) (*Category, error) { - if err := rep.Create(c).Error; err != nil { - return nil, err - } - return c, nil -} - -// ToString is return string of object -func (c *Category) ToString() string { - return toString(c) -} diff --git a/src/sample-golang-echo-app/model/dto/account.go b/src/sample-golang-echo-app/model/dto/account.go deleted file mode 100644 index 986d664..0000000 --- a/src/sample-golang-echo-app/model/dto/account.go +++ /dev/null @@ -1,20 +0,0 @@ -package dto - -import "encoding/json" - -// LoginDto defines a data transfer object for login. -type LoginDto struct { - UserName string `json:"username"` - Password string `json:"password"` -} - -// NewLoginDto is constructor. -func NewLoginDto() *LoginDto { - return &LoginDto{} -} - -// ToString is return string of object -func (l *LoginDto) ToString() (string, error) { - bytes, err := json.Marshal(l) - return string(bytes), err -} diff --git a/src/sample-golang-echo-app/model/dto/book.go b/src/sample-golang-echo-app/model/dto/book.go deleted file mode 100644 index 3cf141b..0000000 --- a/src/sample-golang-echo-app/model/dto/book.go +++ /dev/null @@ -1,77 +0,0 @@ -package dto - -import ( - "encoding/json" - - "github.com/ybkuroki/go-webapp-sample/model" - "gopkg.in/go-playground/validator.v9" -) - -const ( - required string = "required" - max string = "max" - min string = "min" -) - -// BookDto defines a data transfer object for book. -type BookDto struct { - Title string `validate:"required,min=3,max=50" json:"title"` - Isbn string `validate:"required,min=10,max=20" json:"isbn"` - CategoryID uint `json:"categoryId"` - FormatID uint `json:"formatId"` - messages map[string]string -} - -// NewBookDto is constructor. -func NewBookDto(messages map[string]string) *BookDto { - return &BookDto{messages: messages} -} - -// Create creates a book model from this DTO. -func (b *BookDto) Create() *model.Book { - return model.NewBook(b.Title, b.Isbn, b.CategoryID, b.FormatID) -} - -// Validate performs validation check for the each item. -func (b *BookDto) Validate() map[string]string { - return validateDto(b) -} - -func validateDto(b *BookDto) map[string]string { - err := validator.New().Struct(b) - if err == nil { - return nil - } - - errors := err.(validator.ValidationErrors) - if len(errors) == 0 { - return nil - } - - return createErrorMessages(b, errors) -} - -func createErrorMessages(b *BookDto, errors validator.ValidationErrors) map[string]string { - result := make(map[string]string) - for i := range errors { - switch errors[i].StructField() { - case "Title": - switch errors[i].Tag() { - case required, min, max: - result["title"] = b.messages["ValidationErrMessageBookTitle"] - } - case "Isbn": - switch errors[i].Tag() { - case required, min, max: - result["isbn"] = b.messages["ValidationErrMessageBookISBN"] - } - } - } - return result -} - -// ToString is return string of object -func (b *BookDto) ToString() (string, error) { - bytes, err := json.Marshal(b) - return string(bytes), err -} diff --git a/src/sample-golang-echo-app/model/dto/book_test.go b/src/sample-golang-echo-app/model/dto/book_test.go deleted file mode 100644 index 25ae466..0000000 --- a/src/sample-golang-echo-app/model/dto/book_test.go +++ /dev/null @@ -1,216 +0,0 @@ -package dto - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -const ( - ValidationErrMessageBookTitle string = "Please enter the title with 3 to 50 characters." - ValidationErrMessageBookISBN string = "Please enter the ISBN with 10 to 20 characters." -) - -func TestValidate_Title2Error(t *testing.T) { - dto := createBookForTitle2() - result := dto.Validate() - assert.Equal(t, ValidationErrMessageBookTitle, result["title"]) -} - -func TestValidate_Title3Success(t *testing.T) { - dto := createBookForTitle3() - result := dto.Validate() - assert.Empty(t, result) -} - -func TestValidate_Title4Success(t *testing.T) { - dto := createBookForTitle4() - result := dto.Validate() - assert.Empty(t, result) -} - -func TestValidate_Title49Success(t *testing.T) { - dto := createBookForTitle49() - result := dto.Validate() - assert.Empty(t, result) -} - -func TestValidate_Title50Success(t *testing.T) { - dto := createBookForTitle50() - result := dto.Validate() - assert.Empty(t, result) -} - -func TestValidate_Title51Error(t *testing.T) { - dto := createBookForTitle51() - result := dto.Validate() - assert.Equal(t, ValidationErrMessageBookTitle, result["title"]) -} - -func TestValidate_Isbn9Error(t *testing.T) { - dto := createBookForIsbn9() - result := dto.Validate() - assert.Equal(t, ValidationErrMessageBookISBN, result["isbn"]) -} - -func TestValidate_Isbn10Success(t *testing.T) { - dto := createBookForIsbn10() - result := dto.Validate() - assert.Empty(t, result) -} - -func TestValidate_Isbn11Success(t *testing.T) { - dto := createBookForIsbn11() - result := dto.Validate() - assert.Empty(t, result) -} - -func TestValidate_Isbn19Success(t *testing.T) { - dto := createBookForIsbn19() - result := dto.Validate() - assert.Empty(t, result) -} - -func TestValidate_Isbn20Success(t *testing.T) { - dto := createBookForIsbn20() - result := dto.Validate() - assert.Empty(t, result) -} - -func TestValidate_Isbn21Error(t *testing.T) { - dto := createBookForIsbn21() - result := dto.Validate() - assert.Equal(t, ValidationErrMessageBookISBN, result["isbn"]) -} - -func TestToString(t *testing.T) { - dto := createBookForTitle4() - result, _ := dto.ToString() - assert.Equal(t, "{\"title\":\"Test\",\"isbn\":\"123-123-123-1\",\"categoryId\":1,\"formatId\":1}", result) -} - -func createValidationMessages() map[string]string { - return map[string]string{ - "ValidationErrMessageBookTitle": "Please enter the title with 3 to 50 characters.", - "ValidationErrMessageBookISBN": "Please enter the ISBN with 10 to 20 characters."} -} - -func createBookForTitle2() *BookDto { - return &BookDto{ - Title: "Te", - Isbn: "123-123-123-1", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForTitle3() *BookDto { - return &BookDto{ - Title: "Tes", - Isbn: "123-123-123-1", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForTitle4() *BookDto { - return &BookDto{ - Title: "Test", - Isbn: "123-123-123-1", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForTitle49() *BookDto { - return &BookDto{ - Title: "Test012345Test012345Test012345Test012345Test01234", - Isbn: "123-123-123-1", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForTitle50() *BookDto { - return &BookDto{ - Title: "Test012345Test012345Test012345Test012345Test012345", - Isbn: "123-123-123-1", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForTitle51() *BookDto { - return &BookDto{ - Title: "Test012345Test012345Test012345Test012345Test012345T", - Isbn: "123-123-123-1", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForIsbn9() *BookDto { - return &BookDto{ - Title: "Test", - Isbn: "123456789", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForIsbn10() *BookDto { - return &BookDto{ - Title: "Test", - Isbn: "1234567890", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForIsbn19() *BookDto { - return &BookDto{ - Title: "Test", - Isbn: "1234567890123456789", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForIsbn20() *BookDto { - return &BookDto{ - Title: "Test", - Isbn: "12345678901234567890", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForIsbn21() *BookDto { - return &BookDto{ - Title: "Test", - Isbn: "123456789012345678901", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} - -func createBookForIsbn11() *BookDto { - return &BookDto{ - Title: "Test", - Isbn: "12345678901", - CategoryID: 1, - FormatID: 1, - messages: createValidationMessages(), - } -} diff --git a/src/sample-golang-echo-app/model/format.go b/src/sample-golang-echo-app/model/format.go deleted file mode 100644 index 74371a2..0000000 --- a/src/sample-golang-echo-app/model/format.go +++ /dev/null @@ -1,53 +0,0 @@ -package model - -import ( - "github.com/moznion/go-optional" - "github.com/ybkuroki/go-webapp-sample/repository" -) - -// Format defines struct of format data. -type Format struct { - ID uint `gorm:"primary_key" json:"id"` - Name string `validate:"required" json:"name"` -} - -// TableName returns the table name of format struct and it is used by gorm. -func (Format) TableName() string { - return "format_master" -} - -// NewFormat is constructor -func NewFormat(name string) *Format { - return &Format{Name: name} -} - -// FindByID returns a format full matched given format's ID. -func (f *Format) FindByID(rep repository.Repository, id uint) optional.Option[*Format] { - var format Format - if err := rep.Where("id = ?", id).First(&format).Error; err != nil { - return optional.None[*Format]() - } - return optional.Some(&format) -} - -// FindAll returns all formats of the format table. -func (f *Format) FindAll(rep repository.Repository) (*[]Format, error) { - var formats []Format - if err := rep.Find(&formats).Error; err != nil { - return nil, err - } - return &formats, nil -} - -// Create persists this category data. -func (f *Format) Create(rep repository.Repository) (*Format, error) { - if err := rep.Create(f).Error; err != nil { - return nil, err - } - return f, nil -} - -// ToString is return string of object -func (f *Format) ToString() string { - return toString(f) -} diff --git a/src/sample-golang-echo-app/model/page.go b/src/sample-golang-echo-app/model/page.go deleted file mode 100644 index 5d5317d..0000000 --- a/src/sample-golang-echo-app/model/page.go +++ /dev/null @@ -1,17 +0,0 @@ -package model - -// Page defines struct of pagination data. -type Page struct { - Content *[]Book `json:"content"` - Last bool `json:"last"` - TotalElements int `json:"totalElements"` - TotalPages int `json:"totalPages"` - Size int `json:"size"` - Page int `json:"page"` - NumberOfElements int `json:"numberOfElements"` -} - -// NewPage is constructor -func NewPage() *Page { - return &Page{} -} diff --git a/src/sample-golang-echo-app/repository/repository.go b/src/sample-golang-echo-app/repository/repository.go deleted file mode 100644 index da970dd..0000000 --- a/src/sample-golang-echo-app/repository/repository.go +++ /dev/null @@ -1,192 +0,0 @@ -package repository - -import ( - "database/sql" - "fmt" - "os" - - "github.com/ybkuroki/go-webapp-sample/config" - "github.com/ybkuroki/go-webapp-sample/logger" - "gorm.io/driver/mysql" - "gorm.io/driver/postgres" - "gorm.io/driver/sqlite" - "gorm.io/gorm" -) - -// Repository defines a interface for access the database. -type Repository interface { - Model(value interface{}) *gorm.DB - Select(query interface{}, args ...interface{}) *gorm.DB - Find(out interface{}, where ...interface{}) *gorm.DB - Exec(sql string, values ...interface{}) *gorm.DB - First(out interface{}, where ...interface{}) *gorm.DB - Raw(sql string, values ...interface{}) *gorm.DB - Create(value interface{}) *gorm.DB - Save(value interface{}) *gorm.DB - Updates(value interface{}) *gorm.DB - Delete(value interface{}) *gorm.DB - Where(query interface{}, args ...interface{}) *gorm.DB - Preload(column string, conditions ...interface{}) *gorm.DB - Scopes(funcs ...func(*gorm.DB) *gorm.DB) *gorm.DB - ScanRows(rows *sql.Rows, result interface{}) error - Transaction(fc func(tx Repository) error) (err error) - Close() error - DropTableIfExists(value interface{}) error - AutoMigrate(value interface{}) error -} - -// repository defines a repository for access the database. -type repository struct { - db *gorm.DB -} - -// bookRepository is a concrete repository that implements repository. -type bookRepository struct { - *repository -} - -// NewBookRepository is constructor for bookRepository. -func NewBookRepository(logger logger.Logger, conf *config.Config) Repository { - logger.GetZapLogger().Infof("Try database connection") - db, err := connectDatabase(logger, conf) - if err != nil { - logger.GetZapLogger().Errorf("Failure database connection") - os.Exit(config.ErrExitStatus) - } - logger.GetZapLogger().Infof("Success database connection, %s:%s", conf.Database.Host, conf.Database.Port) - return &bookRepository{&repository{db: db}} -} - -const ( - // SQLITE represents SQLite3 - SQLITE = "sqlite3" - // POSTGRES represents PostgreSQL - POSTGRES = "postgres" - // MYSQL represents MySQL - MYSQL = "mysql" -) - -func connectDatabase(logger logger.Logger, config *config.Config) (*gorm.DB, error) { - var dsn string - gormConfig := &gorm.Config{Logger: logger} - - if config.Database.Dialect == POSTGRES { - dsn = fmt.Sprintf("host=%s port=%s user=%s dbname=%s password=%s sslmode=disable", config.Database.Host, config.Database.Port, config.Database.Username, config.Database.Dbname, config.Database.Password) - return gorm.Open(postgres.Open(dsn), gormConfig) - } else if config.Database.Dialect == MYSQL { - dsn = fmt.Sprintf("%s:%s@(%s)/%s?charset=utf8&parseTime=True&loc=Local", config.Database.Username, config.Database.Password, config.Database.Host, config.Database.Dbname) - return gorm.Open(mysql.Open(dsn), gormConfig) - } - return gorm.Open(sqlite.Open(config.Database.Host), gormConfig) -} - -// Model specify the model you would like to run db operations -func (rep *repository) Model(value interface{}) *gorm.DB { - return rep.db.Model(value) -} - -// Select specify fields that you want to retrieve from database when querying, by default, will select all fields; -func (rep *repository) Select(query interface{}, args ...interface{}) *gorm.DB { - return rep.db.Select(query, args...) -} - -// Find find records that match given conditions. -func (rep *repository) Find(out interface{}, where ...interface{}) *gorm.DB { - return rep.db.Find(out, where...) -} - -// Exec exec given SQL using by gorm.DB. -func (rep *repository) Exec(sql string, values ...interface{}) *gorm.DB { - return rep.db.Exec(sql, values...) -} - -// First returns first record that match given conditions, order by primary key. -func (rep *repository) First(out interface{}, where ...interface{}) *gorm.DB { - return rep.db.First(out, where...) -} - -// Raw returns the record that executed the given SQL using gorm.DB. -func (rep *repository) Raw(sql string, values ...interface{}) *gorm.DB { - return rep.db.Raw(sql, values...) -} - -// Create insert the value into database. -func (rep *repository) Create(value interface{}) *gorm.DB { - return rep.db.Create(value) -} - -// Save update value in database, if the value doesn't have primary key, will insert it. -func (rep *repository) Save(value interface{}) *gorm.DB { - return rep.db.Save(value) -} - -// Update update value in database -func (rep *repository) Updates(value interface{}) *gorm.DB { - return rep.db.Updates(value) -} - -// Delete delete value match given conditions. -func (rep *repository) Delete(value interface{}) *gorm.DB { - return rep.db.Delete(value) -} - -// Where returns a new relation. -func (rep *repository) Where(query interface{}, args ...interface{}) *gorm.DB { - return rep.db.Where(query, args...) -} - -// Preload preload associations with given conditions. -func (rep *repository) Preload(column string, conditions ...interface{}) *gorm.DB { - return rep.db.Preload(column, conditions...) -} - -// Scopes pass current database connection to arguments `func(*DB) *DB`, which could be used to add conditions dynamically -func (rep *repository) Scopes(funcs ...func(*gorm.DB) *gorm.DB) *gorm.DB { - return rep.db.Scopes(funcs...) -} - -// ScanRows scan `*sql.Rows` to give struct -func (rep *repository) ScanRows(rows *sql.Rows, result interface{}) error { - return rep.db.ScanRows(rows, result) -} - -// Close close current db connection. If database connection is not an io.Closer, returns an error. -func (rep *repository) Close() error { - sqlDB, _ := rep.db.DB() - return sqlDB.Close() -} - -// DropTableIfExists drop table if it is exist -func (rep *repository) DropTableIfExists(value interface{}) error { - return rep.db.Migrator().DropTable(value) -} - -// AutoMigrate run auto migration for given models, will only add missing fields, won't delete/change current data -func (rep *repository) AutoMigrate(value interface{}) error { - return rep.db.AutoMigrate(value) -} - -// Transaction start a transaction as a block. -// If it is failed, will rollback and return error. -// If it is sccuessed, will commit. -// ref: https://github.com/jinzhu/gorm/blob/master/main.go#L533 -func (rep *repository) Transaction(fc func(tx Repository) error) (err error) { - panicked := true - tx := rep.db.Begin() - defer func() { - if panicked || err != nil { - tx.Rollback() - } - }() - - txrep := &repository{} - txrep.db = tx - err = fc(txrep) - - if err == nil { - err = tx.Commit().Error - } - - panicked = false - return -} diff --git a/src/sample-golang-echo-app/resources/config/application.develop.yml b/src/sample-golang-echo-app/resources/config/application.develop.yml deleted file mode 100644 index 1f4c5af..0000000 --- a/src/sample-golang-echo-app/resources/config/application.develop.yml +++ /dev/null @@ -1,36 +0,0 @@ -database: - dialect: sqlite3 - host: book.db - port: - dbname: - username: - password: - migration: true - -extension: - master_generator: true - cors_enabled: true - security_enabled: true - -log: - request_log_format: ${remote_ip} ${account_name} ${uri} ${method} ${status} - -staticcontents: - enabled: true - -swagger: - enabled: true - path: /swagger/.* - -security: - auth_path: - - /api/.* - exclude_path: - - /swagger/.* - - /api/auth/login$ - - /api/auth/logout$ - - /api/health$ - user_path: - - /api/.* - admin_path: - - /api/.* \ No newline at end of file diff --git a/src/sample-golang-echo-app/resources/config/application.docker.yml b/src/sample-golang-echo-app/resources/config/application.docker.yml deleted file mode 100644 index 62b8af7..0000000 --- a/src/sample-golang-echo-app/resources/config/application.docker.yml +++ /dev/null @@ -1,28 +0,0 @@ -database: - dialect: postgres - host: postgres-db - port: 5432 - dbname: testdb - username: testusr - password: testusr - migration: false - -extension: - master_generator: false - cors_enabled: false - security_enabled: true - -log: - request_log_format: ${remote_ip} ${account_name} ${uri} ${method} ${status} - -security: - auth_path: - - /api/.* - exclude_path: - - /api/auth/login$ - - /api/auth/logout$ - - /api/health$ - user_path: - - /api/.* - admin_path: - - /api/.* \ No newline at end of file diff --git a/src/sample-golang-echo-app/resources/config/application.k8s.yml b/src/sample-golang-echo-app/resources/config/application.k8s.yml deleted file mode 100644 index 378e432..0000000 --- a/src/sample-golang-echo-app/resources/config/application.k8s.yml +++ /dev/null @@ -1,34 +0,0 @@ -database: - dialect: postgres - host: dbserver-k8s-service - port: 5432 - dbname: testdb - username: testusr - password: testusr - migration: false - -redis: - enabled: true - connection_pool_size: 10 - host: redis-k8s-service - port: 6379 - -extension: - master_generator: false - cors_enabled: false - security_enabled: true - -log: - request_log_format: ${remote_ip} ${account_name} ${uri} ${method} ${status} - -security: - auth_path: - - /api/.* - exclude_path: - - /api/auth/login$ - - /api/auth/logout$ - - /api/health$ - user_path: - - /api/.* - admin_path: - - /api/.* \ No newline at end of file diff --git a/src/sample-golang-echo-app/resources/config/messages.properties b/src/sample-golang-echo-app/resources/config/messages.properties deleted file mode 100644 index 475ad0e..0000000 --- a/src/sample-golang-echo-app/resources/config/messages.properties +++ /dev/null @@ -1,3 +0,0 @@ -# validation messages for book model -ValidationErrMessageBookTitle = Please enter the title with 3 to 50 characters. -ValidationErrMessageBookISBN = Please enter the ISBN with 10 to 20 characters. diff --git a/src/sample-golang-echo-app/resources/config/zaplogger.develop.yml b/src/sample-golang-echo-app/resources/config/zaplogger.develop.yml deleted file mode 100644 index b21670a..0000000 --- a/src/sample-golang-echo-app/resources/config/zaplogger.develop.yml +++ /dev/null @@ -1,24 +0,0 @@ -zap_config: - level: "debug" - encoding: "console" - development: true - encoderConfig: - messageKey: "Msg" - levelKey: "Level" - timeKey: "Time" - nameKey: "Name" - callerKey: "Caller" - stacktraceKey: "St" - levelEncoder: "capital" - timeEncoder: "iso8601" - durationEncoder: "string" - callerEncoder: "short" - outputPaths: - - "stdout" - errorOutputPaths: - - "stdout" - -log_rotate: - maxsize: 3 - maxage: 7 - maxbackups: 7 \ No newline at end of file diff --git a/src/sample-golang-echo-app/resources/config/zaplogger.docker.yml b/src/sample-golang-echo-app/resources/config/zaplogger.docker.yml deleted file mode 100644 index 6aebbf9..0000000 --- a/src/sample-golang-echo-app/resources/config/zaplogger.docker.yml +++ /dev/null @@ -1,26 +0,0 @@ -zap_config: - level: "info" - encoding: "console" - development: true - encoderConfig: - messageKey: "Msg" - levelKey: "Level" - timeKey: "Time" - nameKey: "Name" - callerKey: "Caller" - stacktraceKey: "St" - levelEncoder: "capital" - timeEncoder: "iso8601" - durationEncoder: "string" - callerEncoder: "short" - outputPaths: - - "stdout" - - "./application.log" - errorOutputPaths: - - "stderr" - - "./error.log" - -log_rotate: - maxsize: 3 - maxage: 7 - maxbackups: 7 \ No newline at end of file diff --git a/src/sample-golang-echo-app/resources/config/zaplogger.k8s.yml b/src/sample-golang-echo-app/resources/config/zaplogger.k8s.yml deleted file mode 100644 index 6aebbf9..0000000 --- a/src/sample-golang-echo-app/resources/config/zaplogger.k8s.yml +++ /dev/null @@ -1,26 +0,0 @@ -zap_config: - level: "info" - encoding: "console" - development: true - encoderConfig: - messageKey: "Msg" - levelKey: "Level" - timeKey: "Time" - nameKey: "Name" - callerKey: "Caller" - stacktraceKey: "St" - levelEncoder: "capital" - timeEncoder: "iso8601" - durationEncoder: "string" - callerEncoder: "short" - outputPaths: - - "stdout" - - "./application.log" - errorOutputPaths: - - "stderr" - - "./error.log" - -log_rotate: - maxsize: 3 - maxage: 7 - maxbackups: 7 \ No newline at end of file diff --git a/src/sample-golang-echo-app/resources/public/css/app.750b60b0.css b/src/sample-golang-echo-app/resources/public/css/app.750b60b0.css deleted file mode 100644 index 85aa344..0000000 --- a/src/sample-golang-echo-app/resources/public/css/app.750b60b0.css +++ /dev/null @@ -1,3 +0,0 @@ -.margin { - height: 70px -} diff --git a/src/sample-golang-echo-app/resources/public/favicon.ico b/src/sample-golang-echo-app/resources/public/favicon.ico deleted file mode 100644 index df36fcfb72584e00488330b560ebcf34a41c64c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4286 zcmZQzU}RuqP*4ET3Jfa*7#PGD7#K7d7#I{77#JKFAmR)lAOIqU2X5Zs$bgLL=_@3A zjhlBkf-u-E^l}5#e(vTSjvJvE#HNe&P`g3?jcVTE_#KKtY>*hu-2k;;s(FXw$>tr7 z|DhPf28q$seyH6be^xc`aQp|g8{`HM8zcsjqlp`k?AB}E;dmd(Zjk*T3=#v$(Zmf< z`&pZJIL^XiH^_bv2FZccP&Evoc7y!o(Y(X)10MT9av(JzwN!Hh)P8~H9gaKj*bVYO z2!qss)KbNMsNEp{q&4qw{6&QQAT=PhAUzbj0cyWO^A5*L7iKh$o<<{gf0*zB%ZV*ek6akv4b2c(xQH$d$Mg`s)#4##Kc_BU;D{GXL$3C18c zx;#`5NH53?lHCBcpR;*~<1!4hcRKzrpKSX--p3S-L2Mjh0MZLGgCzT*c7xm<)V#y- z3yS?a9sf71bNHW@Xz@SJ!xW4`Y>*fhH-Pkl%mA51v>TxIi#G3YJcMF5D4p$e{9n{+ z^FPkh6a|CCu-Feuiy$*VW)WpS)NYV_3!8U1{z0*Sr{n+HW%mD*!_C3|hP%PT6f6dk z!{P>z86dMjW)gG*)P9ZT9geq9><0OLyW{`dGAmTOVd3Cm3YKf$4zCkIeurU@Ss*j< z+7Gpxxp{}*uwyWnns+ArO_!|@D?-JmqL!|{JXy){Z+gZlMPypL%f2*-Jv z{(*|Y)q(V2GYe`5$S$z`P`g3ysYPp3f$NrjCM-5(c2Q8ptk?oiME5yuZM-3=hU zAT!X-h1vzO6J$So^A5*3=xSPaIsUJhX8S+E&kP=>F!STRO_wBvm~$isnlXSdhz$~h z$-`)nUXU3ev(U|l+6l5d9HUJID&yBX{7+ATmhrH(1){x7pC(=HT=LB0y}A7)UP8)AS^=9*`Lzvp{CT%!kq-J3)5yHScf)ByT?WW^if zV!|8eX^Mgqe9c%vaSpN<8H2>Ya%k#7X5$(!Tt{e|LXt$|6>oq zKji=a2jLI=|NlQ=hu{Ou|Nnz<1LOby3=ClWkAa~cg#R!w*n{v71_t>L3=I4r{D6Uh a9fS`sFffB~17iat17ib}cK|F4vKj!X^7qaF diff --git a/src/sample-golang-echo-app/resources/public/img/icons/android-chrome-192x192.png b/src/sample-golang-echo-app/resources/public/img/icons/android-chrome-192x192.png deleted file mode 100644 index b02aa64d97167ad649e496908b35f14c603d9249..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 9416 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 e9t*tm7+#4BmV9zDoyx$#z~JfX=d#Wzp$P!+W| zgW!U_%O^81FtC?+`ns||WE5uS)(gokJkG$teah3tF{I+w+qt)kMMC9{*FV2xY`gv4 z&il*Nr=Lt!n|xAba;KXki=n8;37a=|??3-D{=Mh@?_DdFO1s`{Xb2MFif|J@ct}V* zTr)5v;*hTAd*{RJCKz0PZf2ogdGeMu>as1b8jiI+|f|!*6xno}Z`H=f~c< zaYI0)+2E{qjL4B<#XPmQeqyul?tZGP_xIQOr`gX9J7<*s{dZDYI{Y1jp!WCONB`FU zIsBeeQ0JRO?DhY@w(sA+ZI4m}PsG9J_k@}C{=_()NLqI-=Eb#GcjwjoY(g`{yj`Ti zJEwQRq6C zv8mt851&gH6q?I_>z?-e>$SIsPiudFci(->&wGD=w@(kR+glpFMM3`Cr_)o?AC_<-r0oyCB~0inDlGDm)h^dTwEqkNxrI%+Amw%;)Ds|9x@jc~WZ0g2c}0 zA{X_}Es%OQySIPKo-(Uy1oDIpQqCTR?gmk zr|R$9WPUj=zPlnnW;luTRm}Z6>CTDkws%q=DL&I^-*UHe(aG-l4(wqcHcxTiA~}6s zjBVAYo`8vq)z8Q5Dhl0ErOB`TLr{zPz&n?XGMCQ8WQJw9y>MDL?E{P8R>izi+FU!1 z+{=o+KD~e5uBx}zlds3`{dV`$#o$AmmBpWmw(c-|-PxkPW5Q~~q*ZGUFq_nu*hL@x zl5DkMP5bw1*ZZ?x+yD9TZ1?;8oSbcYijtE*pDn+?|KH*MMWT=Y#!VCbm(R?=WH5hi z*1r`eUmvYIS^M&%qN#3-CPP9?|LP<9Hdn0+f8ALwuD^ZHo6FtP{pZ-Jmu1U#%>P&@ zD)H>8WYFc`mmWV`FD`8Lv1 zd)cRt=iQGK-fhZgC$&IO{84FH;Jpj46sMoB5q!8WQ!Q*pA0Jov+TOOg@q51IK zESq)X<&}xa%eQk?J$;?+-Zxiloqg7>%n2V``BRn)FzTn+SiR+yJ>wVX(%{QJ?b`;& z`7ds7+1j0PZ>h=Z0}J|>8U|Z=ojCX=|a0ci3%~T_}3;@`YEM9?VGCGObNDPK58#n!9_;D=Lr9ICgj6 z=CU1Ex8zH6d{9z5!OCDXSt=@aqK(DQTVAQYuI%%=c^{toBtkCqv7s;o2(Qg|SogpT1VyUUBumbcVQ3dQMKxzOVOA`rGaP za;;kZtj^i_4a&lT3<5>Jr+->8^;YsN-*aM9u4pT1U;Mw2`E+%pUq#>7v)k|ge|f(C z=d;`ITji&J-egnc_1&v#&w?el1Xsl_q0 ziPvkGKL)H^^uQ>zvOdSIxog>9HtsiUZtG5`Z27SA+vS!sZA@>kFI3Gr;V*3Ts5Gr* z*2$2qN2KD9uY2;M;zr1y>eru_1~sr4US`<*!l7wWh|sxD5cB=M`-EB9ocv0uN0Z0vP?z&(vGHR-<KFskoG?(94wCcD!lS!MQyyq0%tjmJir%v5iw_$!S zufN+bLsiZXK@Dw&o_m&GY;23&({d)n*X>t&^5JWzlJA@g)@!hIj^l?p5nA~`Xp7KlV*!P`QKrg8f{Ra8L8D;G%fp*;6llT?8RGE z?p<3V6f*DSMcJ4s;URN=YP{r>JJKo~b#WzYYGd6m@$M)}hT z)9hO&{CtygPW)wyg6y5co6JMy0yk`1wyEhOlTDF{hQ;TL9h2@&*DLqv<>Qhq`8nZW z(c!y6BJ+&}9=PuLnV}ia-!-GZ<;9zIYpO~%=)HJR*^-#u)#b5_x%bgo_pcEP`-&50 zF5c|4d*+o38@8#*3vV`+e5Nd)mb-G-9$w!YH7YL0zf1P5c)3?IMO{SHeiQ*H7GNW$kQ@x#0r*F*Wb6K`$(W5i&k?~I)#7rH94))F6teKyo*uK?t zX^Pnmt668*{_3>OycFmpRurr`<)_CR$GLS42fa^TiV)a($4Kn5Z;o&O8TS*dzas>8 zcZlY->$0f$E5}ctS;64QE|s;*{OpZH@k2BFV|r_B8%*Un6Ynqkee%SC92rTW>SuZ0 zZ{`Iy-~90*g(crk=#bq))l+<#mlLO(ycKcmFx>xM0Z@-9J$xWYYRa zm&DrEdR7;GTgH6(!?VTV##0?*j5u1?Ite%I^x1m8-|L8^{`%)1Pp@YFX!F?A+;P6W z-SurhJvesryLFyCwWw{I>CT(^yOPb`Ea&uD)+M-W#X_NPxffc6Gs;xm)Hg)`SQ^y8 zrh4N_X27HkGgoyM)QMkfYFftaRK0We#P!O#zN?NL7kMzHU(@C2to~0L!J*Y9&wZA9 zS@hpI#D9RxZsn>?Oh)r|S_iI)NU;-IWbGuqHzh`Fd&Vn=6g6Mg&Yqc+SoUvlJ=KYYgE6b3*Yj3n!4l@~ z*_oZ96YN&`?Kxu)`*7k4``ABagyI`Cz>CmR#zlbc-IE?jnU zPfZOpE=^)vo)fs*^({!*WhdqX+|Ty;1o3{~+aP6=yZCymS&r{nf74Hel9{!eCdP?N z_2^GFv0a{BUUcP&xz(-{d;+(A7@gegH?O-oGi**EX8H=95Aj+BxT!{?hP z^Jez9+>mr)KA`>bqvF?>8=g<-vX#7AR@QT2p>AW1a-hPO-afre$t`RRGj9aEHCMN| zzKGfQ#^u#-)73e3jWb_^m5H+A7$eV&rexRbH7T7-<| zudS>9{+#*d$#t{)TNGxj|5hd%dp+OoNBNDX?^jMq@GhHo!Fr19HTU;1G7EDL3Eom8 zp8Cfec|9~BJz8)N0|SFRdP{kVo554k%6JPu7RPhp=F4Hxs{=zm5G_Q zfuWUw!MwIpo+uh}^HVa@DsgLgf4k@_0|P^NRY*ihP-3}4K~a8MW=^U?No7H*LS{ip zG6O@!oX4MdI10lwG*0=SKI8c`h=Ey|TQ8YgSXtP6vIw)Vf=h$R;S^@&%^?b>Z(KQX mt~c6VX; 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 diff --git a/src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-180x180.png b/src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-180x180.png deleted file mode 100644 index 404e192a95ccccbede087203c42b1f25f6bc6e67..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4678 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 k5t$?GryD#Lc*Wx?~8uXj8?AF{W{ zcUQYdTbXl6+N{NE|NZ;-{m-wCi|c)NxBIMbaZS&04o(Y6F1dE=?zQ)iV-8I8oKo!^ zmfqYq>&MSub8qhT-P-C_RNxhzbLjZlXJ6lC9-Za2pw1;KBd@0O!^ckw@BjbBw-5SmYjZ0teEI!D&apY( zi|SosGIu;VAF#XAy|MJ`?;mw%SNN=Ha!JlU`{rKwzCO>sO6SnD$yc}eY;JYU&Ab2k zWzwOkUbAYQBhpvgJ?gis-FJJNdqvUPAD;_P%=cc};1ZX)@7d*`JzXBHW#9k&^jX*J znwE3v-NUH;6FetY{r&fE#`Rr3TUy-;@|^=yAAfn1c4UUv+&Y)|to090`t9!Unp6AX z=hxCxi;ldw{`2qe&Wr1>e|QpmVAA|s`<{P$pLKNBmPcnl{rX;YX4%PCx5M`J_Fvwd zaB#|!JBMC<|Co1t?ye^n0{3(^oLhPB&AsHq)67Z?8U$k4h9_|FAfC**T{pyUMJS%Z?kgS?xC1 zxZ67I+`DssHwRnTvkAAHK7ICcSM|BifB(FDTB#?n_j-MZnZ|AYn6j#1Ym?m4*Izd7 zeX@9)jLclSy;C-Kug-h%>d~5)mX=l@v^w|Nl@(7fsywm7=Bdr(>P1f*UEk&Xx^a5; zZM%JSO=U~#8O}5P*~)wH{Pp(m%4$iQk_3U00*>b`ZJBC`3u|PBc%(%#e)OI^T<9%0 zQASpD?MxTOMGVpw8U7*x4UU3ww)>7Oh2e|DZeo!~w77=MMU>bFI!q9(kY9uV6a znA+NTeep7}i=|(GMXgxxT#(y)a{B6pf{eawJ2>ASW1BoJ(f=>MPb8KP|a+boCy(O%JbIqw_c^^P-T?>gXKn zEn71eE`2L|N4sdL_3dd%x32zeEZF%zDkn4lzy6;6>6XiyotdjE6b?=LbAmzsxbmI2=dB8@OP6l$Jc1ICAEQ%n|m}4IT@; b^cY@=3zmFxGM&o6z`)??>gTe~DWM4fG;ZGz diff --git a/src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-76x76.png b/src/sample-golang-echo-app/resources/public/img/icons/apple-touch-icon-76x76.png deleted file mode 100644 index c500769e3df9d6a6f1977ace8be4e63a8095e36a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1823 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 diff --git a/src/sample-golang-echo-app/resources/public/img/icons/msapplication-icon-144x144.png b/src/sample-golang-echo-app/resources/public/img/icons/msapplication-icon-144x144.png deleted file mode 100644 index 7808237a18d4009501f950044f8388d13c5e1044..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1169 zcmeAS@N?(olHy`uVBq!ia0y~yV3+{H9Lx+13>Rhybuln7a29w(7Bet#3xhBt!>lgInkXZSz!s8(Mc{LX=zERY3XVHn_I$WSNm>k@tRfR7o8Sd zQ{e2D=)0@kIV8>dOt1H$E=R`%d%slItX%h|(%wt!4l^(?2$Tf*1v4bHt<`XNA;JCX z;-7!pPP~kE-PORrzD#!<~XTyY{Tz=`BAr)?No}KmU7tV zV!oc!9R24RHxDPS-}~)W_WHd+s|)9J#ZE1~`0V{k-Jp2&1zLONb6LHA-E`;ew;9J3 z9ZYNb>ozVEwo)#iHSN6g{)w|?^VM&cy=au$#ku2C%7!+cl51BEe$QIFJM%;X``5$# zCQ9*#|3>g%`O$pm)EyI({uexQVpaU5C9TrCob=C}7jOAx8C`z-49kxl+!rRycZdxa zHd(;^=gC961(hzQMvE9;?_#WTPjFS{uUmKbvVP#zLbsGlB5&1G9Acl&o40;tf5(jl zf)%*UoT=@!q{WQdGFweLwFX zlk5n!Fv}8Up(T-WpN^Tn*w(N-QlwVwQ~GNqZ)N?n=Uzl-<~?1|A2G{jyVm0MeS1vK z&f8=5X_E=JXl7{dUK74wRy(J*)&;pQW~o&CB{@NTA^&TsACJ2kDjzECUXt-?m6qfD zb=iJ-XFLD()F`Z9RTz8PeM#e)JwO^ZTJ^?y~J zI4u@=ah2A_EjKzUR%cB!Sv@g2^zs_7yiniGT02E01TS9_DO#O3>#B+2RM#-&?ODqr z=iXV>UMKcM^~H0?dHr>2Pi|(KNq>s@@>%h`_vXcEc~?C)@7ftUFKuhFw)WY5CQIf- zFU<_I-(Ga-4fEg3S6jnE=emkX2N?L}I$Sw#H2Hi+l8pE1?4*}3raR7S`3kvXGH=Oifi5kpeJ2i0b-mbPGTUiweK6Z$8G|d^u<0ommb4v`!?ySl?tbDQ)sU z=?yHuv=#ZIZe57lo@OsUKlNJ37olI;O8YZ1wP#AqN%MYwP2$(luu!dE*~>N4Jn zv3+r diff --git a/src/sample-golang-echo-app/resources/public/img/icons/mstile-150x150.png b/src/sample-golang-echo-app/resources/public/img/icons/mstile-150x150.png deleted file mode 100644 index 3b37a43ae2fdef53050291d95da2e49f78cf398e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4282 zcmeAS@N?(olHy`uVBq!ia0y~yVB`Z~4mJh`hKh|pmohLgu%tWsIx;Y9?C1WI$jZRL zppfhlpez*ZhxXVxSpNU=8J8a5;DP=U4U9w**bv{3`iZJ^#xi$?rW9A5K>eS-{?O zu;72}|DEaAKG~S&?oD!kVK?KZ-g0kM(c9101n;TzVV-Y&{*wTc=lXQdU&^QbcE!eg zW!UAQ@>0vwbn-Q3&8Xcj#^?8Fo9BMGS@vu9?0J7zFLBrLzjX55vh?FmzbJcVcjZ}2 zbcw8uePn+tEivY}>fiRelCO9ZuQI=2s#dw1d~Mg{MOP1Z>B(*UWw2)A`=!k?}8l5aWlD-_IeowlT1<*&=7DUs?6Me);R3E6GP^zq~sI*siN@3w_P{n|n?mOil* zPb=M*Ql9!z--Or82kt1k@aphK*0Z}fHy`}HZGS+;Za=PYKCL+WB(pDfx+kqWx!&|# z(}9bNbUfv!ZMnXNS)J8e>FTlLOKv>bwpeZXdowk)>tc5cl9P(o&e^Bk&H2P4Ah*Wn z;G`43@{;rGdM`3D9Y6GDYRdO#ML}OSs&-w|+4yTh&a~qD_kOK?FCsQ`Tit8x#yP*Y zEjg?9Jo&7UtGuzbyzjPnF58X+8yO-e?Ni8{uCn{~&8P*pDq;_AxM;rSviaRBAN|Fz z-MRA7|Mbh*Y>z_>WLBERP0w0(yLXxD+f(axqg)|mbs&M5n?=fj`QUDsy*UAEuh z(s$|UzI^6Ae{uP+^P7x&ugTgsaJ?DYEIPuHu8bsX5p5jknz zge}pRil_Z`dF>`LFTZT{zevmSpNszA`u{eaTYltnOND&A6fUY4`>tm*`xLE3!d63JIO3UCVx-a%0>ON?E618 z{MmX~Nzmff1l5asAnIfAYD+ z^Cd!e9LhOed-?6B>pZy%1zR+|FD?G$e|}%$HBW`3(|fdR$`8C+_xSXz(%a{L?TXy) z>lbhKBL3k{<}Q87t$w;++x}c$@9taul9{D1>5hKp{K(gSTyDunXP=#x-v0DU^RXjO ze*S;O+S_^Z^V2WPo`+k%%Xxi#wyP;{@fN1#Zzk^#%Mfl{%n&*0oX4-3OO~IpDh&G4 zxpZ1*j^5Pb`#-M#-JS2T>&lZIVf@;^T5hrwTn?U8H!WU!OXq>W2|Awhr}4|B<)29~ z2(htSmT=(7Cf1@^IX*slR)6bF)YQyRW(Kj@t28zKnZf3JzsvZOs^2YeOUY~TA{!{)P z`!a8O?pHir`SIO^lGRJi-@NkNyE!&z7u>&L_PWf=YWat_HQ`-;J464? zTHtV}P(;W3Qu3$ShvE!ht4w(0-=`Im_if?tMQL}#O<(WRTCOj4Z`b5QPno;yGxz=Z z)%7P$e>ZPcZP$Us$Bv%nGq%GAKUr7ltxu+T~MtctKZyC61eSDNS!!_RMTz13VVbzAUfNkjW;yC-pO4>o+S_B5Zp z_1%&WGMBXqj&jsAaGy8yXRp}&$Fg0KsePkm=mVe2?~lKB7g=XiuTon$M`WGWQmNbA zi=?78Ib>8%O!^||d3|~G=(U}}FswXFXQT-{qRDMQj&^`0&>{Gjie>v2(zwi!~FWRqAFy*zFC;Qnb z{d~QJsj6r9%1p7}uEE*3*d=n3-%0)R`%*!9LfSiW?~M4$)wcO3_4oUQR&1$lKM=UD zTJ>(qwOx~&HU@q3KC^z$Usdyy^Mj{!90=TG8Tw$});H?AZfxPY^Jmu88*j?D*lDlY z7}|6ouu;cz{)8>=m)3Z)pVcaeKC|k;+4W*8wjKQ=IV;k$`crY|BteT+pS?Znjk5Rt z6nW+P%zxp(;Qb$6mxd(&p8Utsg_T1_^yH*3raw)Z-_I@;k_>OOzI!JtXWL7L`_ui| ze?)P*w;C%HguJd-`Fm_lse0>a_S$7@C;d2+^6d2b{S)oWS8y~gc8Z*|Prd&7$6b-? zXHA-)OFsWr5>yeV`H~@i`u^J$=eLNT={^v+_y6QC+MBOE4{O}~=cfQ`YH0HBsede; zTRCJzPfscdt80Dgx!G@4=mR~IsClJ9B@Y86uilt9CEjymWUtd{4jEYm?@Q?^v8;It zA$yK4ONug>u{}39aox(u-nWvc{Vqr`xj+A*?^$mGDjQo59}REJ4olvBq2}k;{-2gk z>zE zb6@XI`j>F6$~gVluGZ&gI<8Hf^!hRXPfG`OrGksw9_UgT`Mzsrwn`qhxzHy(Y;QMhW>rxlYn@BEc-dEB|m1tlAF zJo`_dFP-u4T>rIxjhNDD^X2@w!g)0}onP?J{O3;I-9Kfw^d7kQaFa@HNZr(<&#d=F z{!a0({de+Q)Ph^nc9otLQ{Uf}w0%*FlYqsp=C^Td>aN+(R&Kkt=GeAld!%N+l@k+b zJ8+S8lS*x5YG`A0(Jk55$xW(D!;+2fnsz4%SnN8!KK19Gw#kR4rmtfX2Q_9c*~mV~ zPyIQEkIVJJ3!%tK`?Tk$U3kZN@$%{eQ^R}TPWW?qeVISo;sBPucS*A+AM$jxt@p8; za{u|po$D6l&EvaZCeF#_{@{gDlK;yWAhV zFp8XHr+Hed;L}Qm?D_KevN zWz#rhVl})kU4N?XwR`8b6V;!D7nPZ&CbS*6xOdmySu=A*t!F3|l&sY8ygz;a>lep< ztz3Wde(sH{XInLy+{<5I>w3HY%<(=w*9R|>idP-54g4A*_JA|6ckOFIi(N6(mYnyx zrTfz5YI5d5PS4$+4HtI{T2vkOo%C*Pm4NOJb9wDuGL4JbQ#^l7tC@9h<*MUG{hB{Z z8uCMJ1-A4bxM*aq;mK&tFYTvQ>P;ZKu`nTz``6#IIQJ zW!Y@izx!S}K1trO^OfU*3YI>(DXK38e?Gl=Sd{PahCiQPPh54L9TcFk<=(gA6aWAI zY`B13(4s0Njx%@rlIPkl{#E{5`!po^bs$Th+%(mf$&=PiE|pu)Up#5u>ECYsN(Eo` zNvqgKB-Qgjt6kJCXi*h2ZOQr*?=@`0oXU?sDeu@9^tJG?SmR>*J6bRMgnsQ*e$Q>& zciTPFAaJNz%%9sc(HY3%}j0gGQp=4ci8OPl=P zvf|jaa1NPT74J*SpESEql>Z?4bHT@utH;|v30V9(Z|C`4{Kmb%oBndP@VPvAVH`PW zpQ3$W)De|ufj>Dfny<3co6>#Y;{3enCDDv;?@Zg?YUljmg>&R2xykoMzGX?D-J`Yt z=IWl_R8WTf_qAb(^z9$Nr9YcpXy=fr)$$Hfe;MW3um9-0{PdFCdu+c18W;Dcdj6Ut z7RS0SviI%IRmb~Z30Q2n&|+P_`WB1h3kH@xyXmSgZ-2^Ui}w6j?qVnV;C5)g^i`z- zhe_`yl`LqzE_))r+s^$#!zPuv)AUyw#7j1dtGqo05mV9hzO+B%pH@NjW|i6