├── .gitattributes
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .rubocop.yml
├── .rubocop_todo.yml
├── .ruby-version
├── Gemfile
├── Gemfile.lock
├── Procfile
├── Procfile.dev
├── README.md
├── Rakefile
├── app
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── contracts
│ ├── application_contract.rb
│ └── users
│ │ ├── passwords
│ │ ├── send_instructions_contract.rb
│ │ └── update_contract.rb
│ │ └── registrations
│ │ └── register_contract.rb
├── controllers
│ ├── application_controller.rb
│ ├── authenticated_controller.rb
│ ├── concerns
│ │ ├── doorkeeper
│ │ │ └── authorize.rb
│ │ └── parameter_objects.rb
│ ├── swagger_controller.rb
│ └── v1
│ │ └── users
│ │ ├── passwords_controller.rb
│ │ ├── registrations_controller.rb
│ │ └── tokens_controller.rb
├── helpers
│ └── .keep
├── jobs
│ └── application_job.rb
├── lib
│ ├── custom_objects
│ │ ├── base.rb
│ │ └── parameter_object
│ │ │ ├── pagination.rb
│ │ │ └── query.rb
│ └── types.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── application_record.rb
│ ├── concerns
│ │ └── authenticable.rb
│ └── user.rb
├── operations
│ ├── application_operation.rb
│ └── users
│ │ ├── passwords
│ │ ├── create_operation.rb
│ │ └── update_operation.rb
│ │ └── registrations
│ │ └── create_operation.rb
├── services
│ ├── application_service.rb
│ ├── concerns
│ │ └── .keep
│ ├── doorkeeper
│ │ └── access_tokens
│ │ │ └── create_service.rb
│ └── users
│ │ ├── create_service.rb
│ │ ├── passwords
│ │ ├── send_instructions_service.rb
│ │ └── update_service.rb
│ │ └── registrations
│ │ └── register_service.rb
├── swagger_docs
│ ├── controllers
│ │ └── v1
│ │ │ └── users
│ │ │ ├── passwords_controller.rb
│ │ │ ├── registrations_controller.rb
│ │ │ └── tokens_controller.rb
│ ├── inputs
│ │ └── v1
│ │ │ └── user
│ │ │ ├── reset_password_input.rb
│ │ │ ├── revoke_input.rb
│ │ │ ├── sign_in_input.rb
│ │ │ ├── sign_up_input.rb
│ │ │ └── update_password_input.rb
│ ├── models
│ │ └── shared
│ │ │ ├── meta.rb
│ │ │ └── pagination.rb
│ ├── parameters
│ │ └── shared
│ │ │ └── .keep
│ ├── responses
│ │ ├── shared
│ │ │ └── error_response.rb
│ │ └── v1
│ │ │ └── user
│ │ │ ├── reset_password_response.rb
│ │ │ ├── sign_in_response.rb
│ │ │ ├── sign_up_response.rb
│ │ │ └── update_password_response.rb
│ └── swagger_docs.rb
└── views
│ ├── devise
│ └── mailer
│ │ ├── confirmation_instructions.html.erb
│ │ ├── email_changed.html.erb
│ │ ├── password_change.html.erb
│ │ ├── reset_password_instructions.html.erb
│ │ └── unlock_instructions.html.erb
│ ├── layouts
│ ├── mailer.html.erb
│ ├── mailer.text.erb
│ └── swagger.html.erb
│ └── shared
│ └── partials
│ ├── _meta.json.jbuilder
│ └── _pagination.json.jbuilder
├── bin
├── bundle
├── dev
├── rails
├── rake
└── setup
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cable.yml
├── credentials.yml.enc
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── cors.rb
│ ├── devise.rb
│ ├── doorkeeper.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── kaminari_config.rb
│ └── ransack.rb
├── locales
│ ├── contracts
│ │ └── tr.yml
│ ├── defaults
│ │ └── tr.yml
│ ├── en.yml
│ ├── gems
│ │ ├── devise
│ │ │ ├── en.yml
│ │ │ └── tr.yml
│ │ ├── doorkeeper
│ │ │ ├── en.yml
│ │ │ └── tr.yml
│ │ └── dry_validation
│ │ │ ├── en.yml
│ │ │ └── tr.yml
│ └── services
│ │ ├── en.yml
│ │ └── tr.yml
├── puma.rb
├── routes.rb
├── routes
│ ├── authentication.rb
│ └── swagger.rb
├── sidekiq.yml
└── storage.yml
├── db
├── migrate
│ ├── 20220212185845_devise_create_users.rb
│ └── 20220212195921_create_doorkeeper_tables.rb
├── schema.rb
└── seeds.rb
├── docs
├── CONTRACT.md
├── RANSACK.md
├── SERVICE.md
├── SWAGGER.md
└── cover.png
├── lib
├── generators
│ ├── contract
│ │ ├── USAGE
│ │ ├── contract_generator.rb
│ │ └── templates
│ │ │ ├── contract.rb.tt
│ │ │ └── contract_test.rb.tt
│ ├── operation
│ │ ├── USAGE
│ │ ├── operation_generator.rb
│ │ └── templates
│ │ │ ├── operation.rb.tt
│ │ │ └── operation_test.rb.tt
│ └── service
│ │ ├── USAGE
│ │ ├── service_generator.rb
│ │ └── templates
│ │ ├── service.rb.tt
│ │ └── service_test.rb.tt
├── supports
│ ├── application_contract
│ │ ├── error_parser.rb
│ │ └── i18n.rb
│ ├── application_operation
│ │ └── helper.rb
│ ├── application_service
│ │ └── helper.rb
│ ├── doorkeeper
│ │ ├── custom_error_response.rb
│ │ ├── custom_register_response.rb
│ │ └── custom_token_response.rb
│ └── sidekiq
│ │ └── helper.rb
└── tasks
│ └── .keep
├── log
└── .keep
├── public
├── favicon.ico
└── robots.txt
├── storage
└── .keep
├── test
├── channels
│ └── application_cable
│ │ └── connection_test.rb
├── contracts
│ └── users
│ │ ├── passwords
│ │ ├── send_instructions_contract_test.rb
│ │ └── update_contract_test.rb
│ │ └── registrations
│ │ └── register_contract_test.rb
├── controllers
│ ├── swagger_controller_test.rb
│ └── v1
│ │ └── users
│ │ ├── passwords_controller_test.rb
│ │ ├── registrations_controller_test.rb
│ │ └── tokens_controller_test.rb
├── factories
│ ├── doorkeeper
│ │ ├── doorkeeper_access_tokens_factory.rb
│ │ └── doorkeeper_applications_factory.rb
│ └── users_factory.rb
├── fixtures
│ └── files
│ │ └── .keep
├── helpers
│ └── .keep
├── integration
│ └── .keep
├── lib
│ ├── custom_objects
│ │ └── parameter_object
│ │ │ ├── pagination_test.rb
│ │ │ └── query_test.rb
│ └── generators
│ │ ├── contract_generator_test.rb
│ │ ├── operation_generator_test.rb
│ │ └── service_generator_test.rb
├── mailers
│ └── .keep
├── models
│ └── user_test.rb
├── operations
│ └── users
│ │ ├── passwords
│ │ ├── create_operation_test.rb
│ │ └── update_operation_test.rb
│ │ └── registrations
│ │ └── create_operation_test.rb
├── services
│ ├── doorkeeper
│ │ └── access_tokens
│ │ │ └── create_service_test.rb
│ └── users
│ │ ├── create_service_test.rb
│ │ ├── passwords
│ │ ├── send_instructions_service_test.rb
│ │ └── update_service_test.rb
│ │ └── registrations
│ │ └── register_service_test.rb
├── supports
│ ├── body_parser.rb
│ ├── contract_parser.rb
│ ├── contract_validator.rb
│ ├── doorkeeper_authenticator.rb
│ └── sidekiq_minitest_support.rb
└── test_helper.rb
├── tmp
├── .keep
├── pids
│ └── .keep
└── storage
│ └── .keep
└── vendor
└── .keep
/.gitattributes:
--------------------------------------------------------------------------------
1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files.
2 |
3 | # Mark the database schema as having been generated.
4 | db/schema.rb linguist-generated
5 |
6 | # Mark any vendored files as having been vendored.
7 | vendor/* linguist-vendored
8 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | # build & test & quality
2 |
3 | name: build & test & quality
4 |
5 | # Triggers the workflow on push or pull request events
6 | on: [push]
7 |
8 | concurrency:
9 | group: ${{ github.ref }}
10 | cancel-in-progress: true
11 |
12 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel
13 | jobs:
14 | minitest:
15 | env:
16 | POSTGRES_USER: postgres
17 | POSTGRES_PASSWORD: postgres
18 |
19 | strategy:
20 | fail-fast: false
21 | matrix:
22 | os: [ubuntu-latest]
23 | ruby-version: [3.1.2]
24 |
25 | # The type of runner that the job will run on
26 | runs-on: ${{ matrix.os }}
27 |
28 | services:
29 | redis:
30 | image: redis
31 | options: >-
32 | --health-cmd "redis-cli ping"
33 | --health-interval 10s
34 | --health-timeout 5s
35 | --health-retries 5
36 | ports:
37 | - 6379:6379
38 |
39 | # Label used to access the service container
40 | postgres:
41 | # Docker Hub image
42 | image: postgres:14
43 | # Provide the password for postgres
44 | env:
45 | POSTGRES_USER: postgres
46 | POSTGRES_PASSWORD: postgres
47 | # Set health checks to wait until postgres has started
48 | options: >-
49 | --health-cmd pg_isready
50 | --health-interval 10s
51 | --health-timeout 5s
52 | --health-retries 5
53 | ports:
54 | # Maps tcp port 5432 on service container to the host
55 | - 5432:5432
56 |
57 | # Steps represent a sequence of tasks that will be executed as part of the job
58 | steps:
59 | - run: echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf && sudo sysctl -p
60 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
61 | - uses: actions/checkout@v2
62 | with:
63 | persist-credentials: false
64 |
65 | - name: Set up Ruby ${{ matrix.ruby-version }}
66 | uses: ruby/setup-ruby@v1
67 | with:
68 | ruby-version: ${{ matrix.ruby-version }}
69 | bundler-cache: false
70 |
71 | - name: Install ruby dependencies
72 | run: bundle install
73 |
74 | - name: Create test database
75 | env:
76 | RAILS_ENV: test
77 | run: |
78 | bundle exec rails db:create
79 | bundle exec rails db:schema:load
80 |
81 | - name: Running rails tests
82 | run: bundle exec rails test
83 |
84 | rubocop:
85 | strategy:
86 | matrix:
87 | os: [ubuntu-latest]
88 | ruby-version: [3.1.2]
89 |
90 | runs-on: ${{ matrix.os }}
91 |
92 | steps:
93 | - uses: actions/checkout@v2
94 | with:
95 | persist-credentials: false
96 |
97 | - name: Set up Ruby ${{ matrix.ruby-version }}
98 | uses: ruby/setup-ruby@v1
99 | with:
100 | ruby-version: ${{ matrix.ruby-version }}
101 | bundler-cache: false
102 |
103 | - name: Install ruby dependencies
104 | run: bundle install
105 |
106 | - name: Rubocop
107 | run: bundle exec rubocop --config .rubocop.yml --parallel
108 |
109 | zeitwerk:
110 | env:
111 | RAILS_ENV: test
112 |
113 | strategy:
114 | matrix:
115 | os: [ ubuntu-latest ]
116 | ruby-version: [3.1.2]
117 |
118 | runs-on: ${{ matrix.os }}
119 |
120 | steps:
121 | - uses: actions/checkout@v2
122 | with:
123 | persist-credentials: false
124 |
125 | - name: Set up Ruby ${{ matrix.ruby-version }}
126 | uses: ruby/setup-ruby@v1
127 | with:
128 | ruby-version: ${{ matrix.ruby-version }}
129 | bundler-cache: false
130 |
131 | - name: Install ruby dependencies
132 | run: bundle install
133 |
134 | - name: Zeitwerk Check
135 | run: bundle exec rails zeitwerk:check
136 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files.
2 | #
3 | # If you find yourself ignoring temporary files generated by your text editor
4 | # or operating system, you probably want to add a global ignore instead:
5 | # git config --global core.excludesfile '~/.gitignore_global'
6 |
7 | # Ignore bundler config.
8 | /.bundle
9 |
10 | # Ignore all logfiles and tempfiles.
11 | /log/*
12 | /tmp/*
13 | !/log/.keep
14 | !/tmp/.keep
15 |
16 | # Ignore pidfiles, but keep the directory.
17 | /tmp/pids/*
18 | !/tmp/pids/
19 | !/tmp/pids/.keep
20 |
21 | # Ignore uploaded files in development.
22 | /storage/*
23 | !/storage/.keep
24 | /tmp/storage/*
25 | !/tmp/storage/
26 | !/tmp/storage/.keep
27 |
28 | # Ignore master key for decrypting credentials and more.
29 | /config/master.key
30 |
31 | # Ignore ds_store files.
32 | .DS_Store
33 |
34 | .byebug_history
35 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | inherit_from: .rubocop_todo.yml
2 |
3 | require:
4 | - rubocop-rails
5 | - rubocop-performance
6 | - rubocop-minitest
7 |
8 | Rails:
9 | Enabled: true
10 |
11 | AllCops:
12 | NewCops: disable
13 |
14 | Metrics/AbcSize:
15 | Max: 31
16 | Exclude:
17 | - 'test/**/*_test.rb'
18 |
19 | Metrics/MethodLength:
20 | Max: 30
21 | Exclude:
22 | - 'test/**/*_test.rb'
23 |
24 | Metrics/BlockLength:
25 | Max: 100
26 | Exclude:
27 | - 'test/**/*_test.rb'
28 |
29 | Metrics/ClassLength:
30 | Exclude:
31 | - 'test/**/*_test.rb'
32 |
33 | Style/Documentation:
34 | Enabled: false
35 |
36 | Style/HashEachMethods:
37 | Enabled: true
38 |
39 | Style/HashTransformKeys:
40 | Enabled: true
41 |
42 | Style/HashTransformValues:
43 | Enabled: true
44 |
45 | Layout/SpaceAroundMethodCallOperator:
46 | Enabled: true
47 |
48 | Lint/RaiseException:
49 | Enabled: true
50 |
51 | Lint/StructNewOverride:
52 | Enabled: true
53 |
54 | Style/ExponentialNotation:
55 | Enabled: true
56 |
57 | Style/FrozenStringLiteralComment:
58 | Enabled: true
59 |
60 | Style/StringLiterals:
61 | Enabled: true
62 | EnforcedStyle: single_quotes
63 |
64 | Style/StringLiteralsInInterpolation:
65 | Enabled: true
66 | EnforcedStyle: single_quotes
67 |
68 | Naming/MemoizedInstanceVariableName:
69 | Exclude:
70 | - 'lib/generators/**/*.rb'
--------------------------------------------------------------------------------
/.rubocop_todo.yml:
--------------------------------------------------------------------------------
1 | # This configuration was generated by
2 | # `rubocop --auto-gen-config`
3 | # on 2022-02-11 17:17:45 UTC using RuboCop version 1.25.1.
4 | # The point is for the user to remove these configuration records
5 | # one by one as the offenses are removed from the code base.
6 | # Note that changes in the inspected code, or installation of new
7 | # versions of RuboCop, may require this file to be generated again.
8 |
9 | # Offense count: 6
10 | # Cop supports --auto-correct.
11 | Layout/EmptyLineAfterGuardClause:
12 | Exclude:
13 | - 'bin/bundle'
14 |
15 | # Offense count: 1
16 | # Cop supports --auto-correct.
17 | Layout/EmptyLines:
18 | Exclude:
19 | - 'config/environments/development.rb'
20 |
21 | # Offense count: 1
22 | # Cop supports --auto-correct.
23 | # Configuration parameters: EnforcedStyle.
24 | # SupportedStyles: empty_lines, no_empty_lines
25 | Layout/EmptyLinesAroundBlockBody:
26 | Exclude:
27 | - 'db/schema.rb'
28 |
29 | # Offense count: 1
30 | # Cop supports --auto-correct.
31 | # Configuration parameters: AllowForAlignment, AllowBeforeTrailingComments, ForceEqualSignAlignment.
32 | Layout/ExtraSpacing:
33 | Exclude:
34 | - 'config/environments/production.rb'
35 |
36 | # Offense count: 1
37 | # Cop supports --auto-correct.
38 | # Configuration parameters: EnforcedStyle, IndentationWidth.
39 | # SupportedStyles: aligned, indented
40 | Layout/MultilineOperationIndentation:
41 | Exclude:
42 | - 'bin/bundle'
43 |
44 | # Offense count: 1
45 | # Cop supports --auto-correct.
46 | # Configuration parameters: AllowForAlignment, EnforcedStyleForExponentOperator.
47 | # SupportedStylesForExponentOperator: space, no_space
48 | Layout/SpaceAroundOperators:
49 | Exclude:
50 | - 'config/environments/production.rb'
51 |
52 | # Offense count: 2
53 | # Cop supports --auto-correct.
54 | # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets.
55 | # SupportedStyles: space, no_space, compact
56 | # SupportedStylesForEmptyBrackets: space, no_space
57 | Layout/SpaceInsideArrayLiteralBrackets:
58 | Exclude:
59 | - 'config/environments/production.rb'
60 |
61 | # Offense count: 4
62 | # Cop supports --auto-correct.
63 | Layout/SpaceInsidePercentLiteralDelimiters:
64 | Exclude:
65 | - 'Gemfile'
66 |
67 | # Offense count: 1
68 | # Configuration parameters: IgnoredMethods.
69 | Metrics/CyclomaticComplexity:
70 | Max: 9
71 |
72 | # Offense count: 1
73 | # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, IgnoredMethods.
74 | Metrics/MethodLength:
75 | Max: 13
76 |
77 | # Offense count: 1
78 | # Configuration parameters: IgnoredMethods.
79 | Metrics/PerceivedComplexity:
80 | Max: 9
81 |
82 | # Offense count: 2
83 | # Cop supports --auto-correct.
84 | # Configuration parameters: EnforcedStyle.
85 | # SupportedStyles: nested, compact
86 | Style/ClassAndModuleChildren:
87 | Exclude:
88 | - 'test/channels/application_cable/connection_test.rb'
89 | - 'test/test_helper.rb'
90 |
91 | # Offense count: 1
92 | # Configuration parameters: AllowedConstants.
93 | Style/Documentation:
94 | Exclude:
95 | - 'spec/**/*'
96 | - 'test/**/*'
97 | - 'config/application.rb'
98 |
99 | # Offense count: 1
100 | # Cop supports --auto-correct.
101 | Style/ExpandPathArguments:
102 | Exclude:
103 | - 'bin/bundle'
104 |
105 | # Offense count: 27
106 | # Cop supports --auto-correct.
107 | # Configuration parameters: EnforcedStyle.
108 | # SupportedStyles: always, always_true, never
109 | Style/FrozenStringLiteralComment:
110 | Enabled: false
111 |
112 | # Offense count: 1
113 | # Cop supports --auto-correct.
114 | Style/GlobalStdStream:
115 | Exclude:
116 | - 'config/environments/production.rb'
117 |
118 | # Offense count: 2
119 | # Cop supports --auto-correct.
120 | Style/IfUnlessModifier:
121 | Exclude:
122 | - 'bin/bundle'
123 |
124 | # Offense count: 1
125 | # Cop supports --auto-correct.
126 | Style/PerlBackrefs:
127 | Exclude:
128 | - 'bin/bundle'
129 |
130 | # Offense count: 2
131 | # Cop supports --auto-correct-all.
132 | # Configuration parameters: SafeForConstants.
133 | Style/RedundantFetchBlock:
134 | Exclude:
135 | - 'config/puma.rb'
136 |
137 | # Offense count: 1
138 | # Cop supports --auto-correct.
139 | # Configuration parameters: RequireEnglish.
140 | # SupportedStyles: use_perl_names, use_english_names
141 | Style/SpecialGlobalVars:
142 | EnforcedStyle: use_perl_names
143 |
144 | # Offense count: 79
145 | # Cop supports --auto-correct.
146 | # Configuration parameters: EnforcedStyle, ConsistentQuotesInMultiline.
147 | # SupportedStyles: single_quotes, double_quotes
148 | Style/StringLiterals:
149 | Enabled: false
150 |
151 | # Offense count: 1
152 | # Cop supports --auto-correct.
153 | # Configuration parameters: .
154 | # SupportedStyles: percent, brackets
155 | Style/SymbolArray:
156 | EnforcedStyle: percent
157 | MinSize: 10
158 |
159 | # Offense count: 2
160 | # Cop supports --auto-correct.
161 | # Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns.
162 | # URISchemes: http, https
163 | Layout/LineLength:
164 | Max: 198
165 |
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 3.1.2
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | source 'https://rubygems.org'
4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
5 |
6 | ruby '3.1.2'
7 |
8 | # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main"
9 | gem 'rails', '~> 7.0', '>= 7.0.4'
10 |
11 | # Use postgresql as the database for Active Record
12 | gem 'pg', '~> 1.1'
13 |
14 | # Use the Puma web server [https://github.com/puma/puma]
15 | gem 'puma', '~> 5.0'
16 |
17 | # Build JSON APIs with ease [https://github.com/rails/jbuilder]
18 | gem 'jbuilder'
19 |
20 | # Use Redis adapter to run Action Cable in production
21 | # gem "redis", "~> 4.0"
22 |
23 | # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis]
24 | # gem "kredis"
25 |
26 | # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword]
27 | # gem "bcrypt", "~> 3.1.7"
28 |
29 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
30 | gem 'tzinfo-data', platforms: %i[ mingw mswin x64_mingw jruby ]
31 |
32 | # Reduces boot times through caching; required in config/boot.rb
33 | gem 'bootsnap', require: false
34 |
35 | # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images]
36 | # gem "image_processing", "~> 1.2"
37 |
38 | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible
39 | gem 'rack-cors'
40 |
41 | group :development, :test do
42 | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem
43 | gem 'debug', platforms: %i[ mri mingw x64_mingw ]
44 |
45 | # A library for generating fake data [https://github.com/faker-ruby/faker]
46 | gem 'faker', git: 'https://github.com/faker-ruby/faker.git', branch: 'master'
47 |
48 | # Pretty print your Ruby objects [https://github.com/awesome-print/awesome_print]
49 | gem 'awesome_print'
50 |
51 | # A Ruby static code analyzer and formatter, based on the community Ruby style guide [https://github.com/rubocop/rubocop]
52 | gem 'rubocop', require: false
53 |
54 | # An extension of RuboCop focused on code performance checks [https://github.com/rubocop/rubocop-performance]
55 | gem 'rubocop-performance', require: false
56 |
57 | # A RuboCop extension focused on enforcing Rails best practices and coding conventions [https://github.com/rubocop/rubocop-rails]
58 | gem 'rubocop-rails', require: false
59 |
60 | # Code style checking for Minitest files [https://github.com/rubocop/rubocop-minitest]
61 | gem 'rubocop-minitest', require: false
62 |
63 | # Pry is a runtime developer console and IRB alternative with powerful introspection capabilities [https://github.com/pry/pry]
64 | gem 'pry', '~> 0.14.1'
65 |
66 | # Byebug is a Ruby debugger [https://github.com/deivid-rodriguez/byebug]
67 | gem 'byebug', '~> 11.1', '>= 11.1.3'
68 | end
69 |
70 | group :development do
71 | # Speed up commands on slow machines / big apps [https://github.com/rails/spring]
72 | # gem "spring"
73 |
74 | # E-mail opener [https://github.com/ryanb/letter_opener]
75 | gem 'letter_opener', '~> 1.4', '>= 1.4.1'
76 | end
77 |
78 | group :test do
79 | # Making tests easy on the fingers and eyes [https://github.com/thoughtbot/shoulda]
80 | gem 'shoulda', '~> 4.0'
81 |
82 | # Simple one-liner tests for common Rails functionality [https://github.com/thoughtbot/shoulda-matchers#minitest]
83 | gem 'shoulda-matchers', '~> 4.0'
84 |
85 | # A library for setting up Ruby objects as test data. [https://github.com/thoughtbot/factory_bot_rails]
86 | gem 'factory_bot_rails', '~> 6.2'
87 |
88 | # Mocha is a mocking and stubbing library for Ruby [https://github.com/freerange/mocha]
89 | gem 'mocha', '~> 1.13'
90 |
91 | # Allows you to focus on a few tests with ease without having to use command-line arguments [https://github.com/seattlerb/minitest-focus]
92 | gem 'minitest-focus'
93 | end
94 |
95 | # Define and serve live-updating Swagger JSON for Ruby apps [https://github.com/fotinakis/swagger-blocks]
96 | gem 'swagger-blocks', '~> 3.0'
97 |
98 | # Kaminari is a Scope & Engine based customizable and sophisticated paginator [https://github.com/kaminari/kaminari]
99 | gem 'kaminari', '~> 1.2', '>= 1.2.2'
100 |
101 | # Useful, common monads in idiomatic Ruby [https://github.com/dry-rb/dry-monads]
102 | gem 'dry-monads', '~> 1.4'
103 |
104 | # A simple validation library [https://github.com/dry-rb/dry-validation]
105 | gem 'dry-validation', '~> 1.8', '>= 1.8.1'
106 |
107 | # Typed structs and value objects [https://github.com/dry-rb/dry-struct]
108 | gem 'dry-struct', '~> 1.4'
109 |
110 | # Type system for Ruby supporting coercions, constraints and complex types like structs, value objects, enums etc [https://github.com/dry-rb/dry-types]
111 | gem 'dry-types', '~> 1.5', '>= 1.5.1'
112 |
113 | # DSL for declaring params and options of the initializer [https://github.com/dry-rb/dry-initializer]
114 | gem 'dry-initializer', '~> 3.1', '>= 3.1.1'
115 |
116 | # Flexible authentication solution for Rails with Warden [https://github.com/heartcombo/devise]
117 | gem 'devise', '~> 4.8', '>= 4.8.1'
118 |
119 | # Doorkeeper is an OAuth 2 provider for Rails and Grape [https://github.com/doorkeeper-gem/doorkeeper/]
120 | gem 'doorkeeper', '~> 5.5', '>= 5.5.4'
121 |
122 | # Simple, efficient background processing for Ruby [https://github.com/mperham/sidekiq]
123 | gem 'sidekiq', '~> 6.4', '>= 6.4.1'
124 |
125 | # Object-based searching [https://github.com/activerecord-hackery/ransack]
126 | gem 'ransack', github: 'activerecord-hackery/ransack'
127 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | web: bin/rails server
2 | worker: bundle exec sidekiq -C config/sidekiq.yml
3 | release: bundle exec rails db:migrate -t
4 |
--------------------------------------------------------------------------------
/Procfile.dev:
--------------------------------------------------------------------------------
1 | web: bin/rails server -p 3000
2 | worker: bundle exec sidekiq -C config/sidekiq.yml
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | [](https://github.com/rubocop/rubocop)
3 | 
4 | 
5 | [](docs/SWAGGER.md)
6 |
7 | # Rails API Boilerplate
8 | 
9 |
10 | # How to Works?
11 | ```mermaid
12 | flowchart TD
13 | R[Request] --> C[Application Controller]
14 | C --> O[Application Operation]
15 | O -- Validate Params --> AC[Application Contract]
16 | O -- Application Contract returned success?--> S[Application Service]
17 | AC -- Validation success? --> O[Application Operation]
18 | AC -- Validation failed? --> E[Contract Errors]
19 | E --> RE
20 | S -- Process successful? --> RS[Resource]
21 | S -- Process failed? --> OE[Resource Errors]
22 | OE --> RE[Response]
23 | RS --> RE
24 | RE -- Returns --> C
25 | ```
26 |
27 | # Documentations
28 | - [Swagger](docs/SWAGGER.md)
29 | - [Service generator](docs/SERVICE.md)
30 | - [Contract generator](docs/CONTRACT.md)
31 | - [Search & Filter & Sort](docs/RANSACK.md)
32 |
33 | # Installation
34 | ## Prerequisites
35 | - [Ruby](https://rvm.io/)
36 | - [PostgreSQL](https://www.postgresql.org/)
37 | - [Redis](https://redis.io/)
38 |
39 | ## Installation
40 | - Install GEM dependencies:
41 | ```bash
42 | bundle install
43 | ```
44 |
45 | - Create database, migrate tables and run the seed data:
46 | ```bash
47 | rails db:create
48 | rails db:migrate
49 | rails db:seed
50 | ```
51 |
52 | - If you are setting up again, when you already have previous databases:
53 | ```bash
54 | rails db:reset
55 | ```
56 | `reset` is equivalent of `rails db:drop & rails db:setup`.
57 |
58 | - Run the server
59 | ```bash
60 | ./bin/dev
61 | ```
62 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Add your own tasks in files placed in lib/tasks ending in .rake,
4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
5 |
6 | require_relative 'config/application'
7 |
8 | Rails.application.load_tasks
9 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Channel < ActionCable::Channel::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ApplicationCable
4 | class Connection < ActionCable::Connection::Base
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/contracts/application_contract.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'dry-validation'
4 |
5 | class ApplicationContract < Dry::Validation::Contract
6 | config.messages.backend = :i18n
7 |
8 | register_macro(:password_confirmation) do
9 | key.failure(:same_password?) unless values[:password].eql?(values[:password_confirmation])
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/contracts/users/passwords/send_instructions_contract.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Passwords
5 | class SendInstructionsContract < ApplicationContract
6 | params do
7 | required(:email).filled(Types::Email)
8 | end
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/app/contracts/users/passwords/update_contract.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Passwords
5 | class UpdateContract < ApplicationContract
6 | params do
7 | required(:reset_password_token).filled(:string)
8 | required(:password) { filled? & str? & min_size?(Devise.password_length.min) }
9 | required(:password_confirmation) { filled? & str? & min_size?(Devise.password_length.min) }
10 | end
11 |
12 | rule(:password).validate(:password_confirmation)
13 | rule(:password_confirmation).validate(:password_confirmation)
14 | end
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/app/contracts/users/registrations/register_contract.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Registrations
5 | class RegisterContract < ApplicationContract
6 | params do
7 | required(:email).filled(Types::Email)
8 | required(:password).filled(:str?, min_size?: Devise.password_length.min)
9 | end
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationController < ActionController::API
4 | include ParameterObjects
5 | end
6 |
--------------------------------------------------------------------------------
/app/controllers/authenticated_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class AuthenticatedController < ApplicationController
4 | # equivalent of authenticate_user! on devise, but this one will check the oauth token
5 | before_action :doorkeeper_authorize!
6 |
7 | around_action :switch_locale
8 |
9 | private
10 |
11 | # helper method to access the current user from the token
12 | def current_user
13 | @current_user ||= doorkeeper_token.resource_owner
14 | end
15 |
16 | def switch_locale(&action)
17 | locale = current_user.try(:locale) || I18n.default_locale
18 | I18n.with_locale(locale, &action)
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/controllers/concerns/doorkeeper/authorize.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module Authorize
5 | extend ActiveSupport::Concern
6 |
7 | included do
8 | before_action :find_doorkeeper_application!
9 |
10 | attr_reader :current_doorkeeper_application
11 | end
12 |
13 | private
14 |
15 | def find_doorkeeper_application!
16 | @current_doorkeeper_application = Doorkeeper::Application.find_by(uid: params[:client_id],
17 | secret: params[:client_secret])
18 |
19 | render json: invalid_client_response, status: :unauthorized if @current_doorkeeper_application.blank?
20 | end
21 |
22 | def invalid_client_response
23 | { errors: [I18n.t('doorkeeper.errors.messages.invalid_client')] }
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/controllers/concerns/parameter_objects.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module ParameterObjects
4 | extend ActiveSupport::Concern
5 |
6 | included do
7 | def pagination_object
8 | page = params.fetch(:page, 1)
9 | per_page = params.fetch(:per_page, Kaminari.config.default_per_page)
10 |
11 | CustomObjects::ParameterObject::Pagination.new(page:, per_page:)
12 | end
13 |
14 | def query_object
15 | query = params.fetch(:query, {})
16 |
17 | CustomObjects::ParameterObject::Query.new(query:)
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/controllers/swagger_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SwaggerController < ApplicationController
4 | def index
5 | render html: nil, layout: 'layouts/swagger'
6 | end
7 |
8 | def v1_data
9 | swagger_data = SwaggerDocs.v1_swagger_root
10 |
11 | render json: swagger_data, status: :ok
12 | end
13 |
14 | def v2_data
15 | swagger_data = SwaggerDocs.v2_swagger_root
16 |
17 | render json: swagger_data, status: :ok
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/app/controllers/v1/users/passwords_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module V1
4 | module Users
5 | class PasswordsController < ApplicationController
6 | include Doorkeeper::Authorize
7 |
8 | def create
9 | operation = ::Users::Passwords::CreateOperation.new(params: password_params).call
10 |
11 | if operation.success?
12 | render json: operation.success, status: :ok
13 | else
14 | render json: operation.failure, status: :unprocessable_entity
15 | end
16 | end
17 |
18 | def update
19 | operation = ::Users::Passwords::UpdateOperation.new(params: password_params).call
20 |
21 | if operation.success?
22 | render json: operation.success, status: :ok
23 | else
24 | render json: operation.failure, status: :unprocessable_entity
25 | end
26 | end
27 |
28 | private
29 |
30 | def password_params
31 | params.permit(:email, :reset_password_token, :password, :password_confirmation)
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/controllers/v1/users/registrations_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module V1
4 | module Users
5 | class RegistrationsController < ApplicationController
6 | include Doorkeeper::Authorize
7 |
8 | def create
9 | operation = ::Users::Registrations::CreateOperation.new(params: registration_params,
10 | doorkeeper_application: current_doorkeeper_application).call
11 |
12 | if operation.success?
13 | render json: operation.success, status: :created
14 | else
15 | render json: operation.failure, status: :unprocessable_entity
16 | end
17 | end
18 |
19 | private
20 |
21 | def registration_params
22 | params.permit(:email, :password)
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/app/controllers/v1/users/tokens_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module V1
4 | module Users
5 | class TokensController < Doorkeeper::TokensController
6 | private
7 |
8 | def revocation_error_response
9 | error_description = I18n.t('doorkeeper.errors.messages.revoke.unauthorized')
10 |
11 | { errors: [error_description] }
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/app/helpers/.keep
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationJob < ActiveJob::Base
4 | # Automatically retry jobs that encountered a deadlock
5 | # retry_on ActiveRecord::Deadlocked
6 |
7 | # Most jobs are safe to ignore if the underlying records are no longer available
8 | # discard_on ActiveJob::DeserializationError
9 | end
10 |
--------------------------------------------------------------------------------
/app/lib/custom_objects/base.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'dry-struct'
4 |
5 | module CustomObjects
6 | class Base < Dry::Struct
7 | transform_keys(&:to_sym)
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/app/lib/custom_objects/parameter_object/pagination.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module CustomObjects
4 | module ParameterObject
5 | class Pagination < Base
6 | attribute :page, Types::Coercible::Integer.default(1)
7 | attribute :per_page, Types::Coercible::Integer.default(Kaminari.config.default_per_page)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/lib/custom_objects/parameter_object/query.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module CustomObjects
4 | module ParameterObject
5 | class Query < Base
6 | SUPPORTED_CONDITIONS = %w[
7 | eq not_eq matches does_not_match lt gteq lteq gt in
8 | not_in cont cont_any cont_all not_cont_all i_cont
9 | not_i_cont i_cont_any not_i_cont_any i_cont_all
10 | not_i_cont_all start not_start end not_end true
11 | not_true false not_false present blank null not_null
12 | ].freeze
13 |
14 | DEFAULT = {}.freeze
15 |
16 | attribute :query, Types::Params::Hash.default(DEFAULT)
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/app/lib/types.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'dry-types'
4 |
5 | module Types
6 | include Dry.Types()
7 |
8 | Email = String.constrained(format: URI::MailTo::EMAIL_REGEXP)
9 | end
10 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationMailer < ActionMailer::Base
4 | default from: 'from@example.com'
5 | layout 'mailer'
6 | end
7 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ApplicationRecord < ActiveRecord::Base
4 | primary_abstract_class
5 | end
6 |
--------------------------------------------------------------------------------
/app/models/concerns/authenticable.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Authenticable
4 | extend ActiveSupport::Concern
5 |
6 | included do
7 | # Include default devise modules. Others available are:
8 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable
9 | devise :database_authenticatable, :registerable,
10 | :recoverable, :rememberable, :validatable,
11 | :trackable
12 | end
13 |
14 | module ClassMethods
15 | # rubocop:disable Style/RedundantSelf
16 | def authenticate(email, password)
17 | user = self.find_for_authentication(email:)
18 | user&.valid_password?(password) ? user : nil
19 | end
20 | # rubocop:enable Style/RedundantSelf
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class User < ApplicationRecord
4 | include Authenticable
5 | end
6 |
--------------------------------------------------------------------------------
/app/operations/application_operation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'dry/monads/all'
4 | require 'dry-initializer'
5 |
6 | class ApplicationOperation
7 | extend Dry::Initializer
8 |
9 | include Dry::Monads
10 | include Dry::Monads::Do
11 | include Supports::ApplicationOperation::Helper
12 | include Supports::ApplicationContract::ErrorParser
13 | end
14 |
--------------------------------------------------------------------------------
/app/operations/users/passwords/create_operation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Passwords
5 | class CreateOperation < ApplicationOperation
6 | option :params
7 | option :contract, default: proc { Users::Passwords::SendInstructionsContract.new }
8 |
9 | def call
10 | contract_params = yield validate(contract)
11 | result = yield call_service(contract_params)
12 |
13 | Success(result)
14 | end
15 |
16 | private
17 |
18 | def call_service(contract_params)
19 | Users::Passwords::SendInstructionsService.new(params: contract_params).call
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/operations/users/passwords/update_operation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Passwords
5 | class UpdateOperation < ApplicationOperation
6 | option :params
7 | option :contract, default: proc { Users::Passwords::UpdateContract.new }
8 |
9 | def call
10 | contract_params = yield validate(contract)
11 | result = yield call_service(contract_params)
12 |
13 | Success(result)
14 | end
15 |
16 | private
17 |
18 | def call_service(contract_params)
19 | Users::Passwords::UpdateService.new(params: contract_params).call
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/operations/users/registrations/create_operation.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Registrations
5 | class CreateOperation < ApplicationOperation
6 | option :params
7 | option :doorkeeper_application, type: Types.Instance(Doorkeeper::Application)
8 | option :contract, default: proc { Users::Registrations::RegisterContract.new }
9 |
10 | def call
11 | contract_params = yield validate(contract)
12 | user = yield call_service(contract_params)
13 |
14 | Success(user)
15 | end
16 |
17 | private
18 |
19 | def call_service(contract_params)
20 | Users::Registrations::RegisterService.new(params: contract_params, doorkeeper_application:).call
21 | end
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/app/services/application_service.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'dry/monads/all'
4 | require 'dry-initializer'
5 |
6 | class ApplicationService
7 | extend Dry::Initializer
8 |
9 | include Dry::Monads
10 | include Dry::Monads::Do
11 | include Supports::Sidekiq::Helper
12 | include Supports::ApplicationService::Helper
13 | end
14 |
--------------------------------------------------------------------------------
/app/services/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/app/services/concerns/.keep
--------------------------------------------------------------------------------
/app/services/doorkeeper/access_tokens/create_service.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Doorkeeper
4 | module AccessTokens
5 | class CreateService < ApplicationService
6 | option :doorkeeper_application, type: Types.Instance(Doorkeeper::Application)
7 | option :user, type: Types.Instance(User)
8 |
9 | def call
10 | access_token = yield create_resource(Doorkeeper::AccessToken)
11 |
12 | Success(access_token)
13 | end
14 |
15 | private
16 |
17 | def params
18 | {
19 | resource_owner_id: user.id,
20 | resource_owner_type: user.class.name,
21 | application_id: doorkeeper_application.id,
22 | refresh_token: generate_refresh_token,
23 | expires_in: Doorkeeper.configuration.access_token_expires_in.to_i
24 | }
25 | end
26 |
27 | def generate_refresh_token
28 | loop do
29 | token = SecureRandom.hex(32)
30 | break token unless Doorkeeper::AccessToken.exists?(refresh_token: token)
31 | end
32 | end
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/services/users/create_service.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | class CreateService < ApplicationService
5 | option :params, type: Types::Hash
6 |
7 | def call
8 | user = yield create_resource(User)
9 |
10 | Success(user)
11 | end
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/app/services/users/passwords/send_instructions_service.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Passwords
5 | class SendInstructionsService < ApplicationService
6 | option :params, type: Types::Hash
7 |
8 | def call
9 | user = yield send_password_instructions
10 | result = yield check_result(user)
11 |
12 | Success(result)
13 | end
14 |
15 | private
16 |
17 | def send_password_instructions
18 | user = User.send_reset_password_instructions(params)
19 |
20 | Success(user)
21 | end
22 |
23 | def check_result(user)
24 | return Success(success_message) if successfully_sent?(user)
25 |
26 | resource_failure(user)
27 | end
28 |
29 | def successfully_sent?(user)
30 | Devise.paranoid || user.errors.empty?
31 | end
32 |
33 | def success_message
34 | { message: I18n.t('devise.passwords.send_instructions') }
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/services/users/passwords/update_service.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Passwords
5 | class UpdateService < ApplicationService
6 | option :params, type: Types::Hash
7 |
8 | def call
9 | user = yield update_password
10 | result = yield check_result(user)
11 |
12 | Success(result)
13 | end
14 |
15 | private
16 |
17 | def update_password
18 | user = User.reset_password_by_token(params)
19 |
20 | Success(user)
21 | end
22 |
23 | def check_result(user)
24 | return Success(success_message) if successfully_updated?(user)
25 |
26 | resource_failure(user)
27 | end
28 |
29 | def successfully_updated?(user)
30 | user.errors.empty?
31 | end
32 |
33 | def success_message
34 | { message: I18n.t('devise.passwords.updated_not_active') }
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/app/services/users/registrations/register_service.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Users
4 | module Registrations
5 | class RegisterService < ApplicationService
6 | include Supports::Doorkeeper::CustomRegisterResponse
7 |
8 | option :params, type: Types::Hash
9 | option :doorkeeper_application, type: Types.Instance(Doorkeeper::Application)
10 |
11 | def call
12 | ActiveRecord::Base.transaction(requires_new: true) do
13 | user = yield create_user
14 | access_token = yield create_access_token(user)
15 | response = body(user, access_token)
16 |
17 | Success(response)
18 | end
19 | end
20 |
21 | private
22 |
23 | def create_user
24 | Users::CreateService.new(params:).call
25 | end
26 |
27 | def create_access_token(user)
28 | Doorkeeper::AccessTokens::CreateService.new(user:, doorkeeper_application:).call
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/app/swagger_docs/controllers/v1/users/passwords_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Controllers
4 | module V1
5 | module Users
6 | class PasswordsController
7 | include Swagger::Blocks
8 |
9 | swagger_path '/v1/users/password' do
10 | operation :post do
11 | key :summary, 'Reset password'
12 | key :description, 'Send an email about resetting password'
13 | key :operationId, 'sendResetPasswordInformation'
14 | key :tags, [
15 | 'User Passwords'
16 | ]
17 |
18 | request_body do
19 | key :description, 'User email'
20 | key :required, true
21 | content :'application/json' do
22 | schema do
23 | key :'$ref', :UserResetPasswordInput
24 | end
25 | end
26 | end
27 |
28 | response 200 do
29 | key :description, 'Successful response'
30 | content :'application/json' do
31 | schema do
32 | key :'$ref', :UserResetPasswordSuccessResponse
33 | end
34 | end
35 | end
36 |
37 | response 422 do
38 | key :description, 'Somethins goes wrong'
39 | content :'application/json' do
40 | schema do
41 | key :'$ref', :ErrorResponse
42 | end
43 | end
44 | end
45 |
46 | response 401 do
47 | key :description, 'Invalid client credentials passed'
48 | content :'application/json' do
49 | schema do
50 | key :'$ref', :ErrorResponse
51 | end
52 | end
53 | end
54 | end
55 |
56 | operation :patch do
57 | key :summary, 'Update password'
58 | key :description, 'Update user password with token'
59 | key :operationId, 'updateUserPassword'
60 | key :tags, [
61 | 'User Passwords'
62 | ]
63 |
64 | request_body do
65 | key :description, 'Password credentials'
66 | key :required, true
67 | content :'application/json' do
68 | schema do
69 | key :'$ref', :UserUpdatePasswordInput
70 | end
71 | end
72 | end
73 |
74 | response 200 do
75 | key :description, 'Successful response'
76 | content :'application/json' do
77 | schema do
78 | key :'$ref', :UserUpdatePasswordSuccessResponse
79 | end
80 | end
81 | end
82 |
83 | response 422 do
84 | key :description, 'Something goes wrong'
85 | content :'application/json' do
86 | schema do
87 | key :'$ref', :ErrorResponse
88 | end
89 | end
90 | end
91 |
92 | response 401 do
93 | key :description, 'Invalid client credentials passed'
94 | content :'application/json' do
95 | schema do
96 | key :'$ref', :ErrorResponse
97 | end
98 | end
99 | end
100 | end
101 | end
102 | end
103 | end
104 | end
105 | end
106 |
--------------------------------------------------------------------------------
/app/swagger_docs/controllers/v1/users/registrations_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Controllers
4 | module V1
5 | module Users
6 | class RegistrationsController
7 | include Swagger::Blocks
8 |
9 | swagger_path '/v1/users/sign_up' do
10 | operation :post do
11 | key :summary, 'Sign up'
12 | key :description, 'Create a new user and generate access and refresh tokens'
13 | key :operationId, 'userSignUp'
14 | key :tags, [
15 | 'User Registrations'
16 | ]
17 |
18 | request_body do
19 | key :description, 'User credentials'
20 | key :required, true
21 | content :'application/json' do
22 | schema do
23 | key :'$ref', :UserSignUpInput
24 | end
25 | end
26 | end
27 |
28 | response 201 do
29 | key :description, 'Successful response'
30 | content :'application/json' do
31 | schema do
32 | key :'$ref', :UserSignUpSuccessResponse
33 | end
34 | end
35 | end
36 |
37 | response 422 do
38 | key :description, 'Something goes wrong'
39 | content :'application/json' do
40 | schema do
41 | key :'$ref', :ErrorResponse
42 | end
43 | end
44 | end
45 |
46 | response 401 do
47 | key :description, 'Invalid client credentials passed'
48 | content :'application/json' do
49 | schema do
50 | key :'$ref', :ErrorResponse
51 | end
52 | end
53 | end
54 | end
55 | end
56 | end
57 | end
58 | end
59 | end
60 |
--------------------------------------------------------------------------------
/app/swagger_docs/controllers/v1/users/tokens_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Controllers
4 | module V1
5 | module Users
6 | class TokensController
7 | include Swagger::Blocks
8 |
9 | swagger_path '/v1/users/oauth/token' do
10 | operation :post do
11 | key :summary, 'Generate access and refresh tokens for authentication'
12 | key :description, 'Generate access and refresh tokens for authentication'
13 | key :operationId, 'userSignIn'
14 | key :tags, [
15 | 'User Sessions'
16 | ]
17 |
18 | request_body do
19 | key :description, 'User credentials'
20 | key :required, true
21 | content :'application/json' do
22 | schema do
23 | key :'$ref', :UserSignInInput
24 | end
25 | end
26 | end
27 |
28 | response 200 do
29 | key :description, 'Successful response'
30 | content :'application/json' do
31 | schema do
32 | key :'$ref', :UserSignInSuccessResponse
33 | end
34 | end
35 | end
36 |
37 | response 400 do
38 | key :description, 'Invalid authentication credentials passed'
39 | content :'application/json' do
40 | schema do
41 | key :'$ref', :ErrorResponse
42 | end
43 | end
44 | end
45 |
46 | response 401 do
47 | key :description, 'Invalid client credentials passed'
48 | content :'application/json' do
49 | schema do
50 | key :'$ref', :ErrorResponse
51 | end
52 | end
53 | end
54 | end
55 | end
56 |
57 | swagger_path '/v1/users/oauth/revoke' do
58 | operation :post do
59 | key :summary, 'Revoke access or refresh tokens'
60 | key :description, 'Revoke access or refresh token'
61 | key :operationId, 'userLogout'
62 | key :tags, [
63 | 'User Sessions'
64 | ]
65 |
66 | request_body do
67 | key :description, 'Token credentials'
68 | key :required, true
69 | content :'application/json' do
70 | schema do
71 | key :'$ref', :UserRevokeInput
72 | end
73 | end
74 | end
75 |
76 | response 200 do
77 | key :description, 'Default response'
78 | content :'application/json' do
79 | schema do
80 | key :type, :object
81 | end
82 | end
83 | end
84 |
85 | response 403 do
86 | key :description, 'Invalid client credentials passed'
87 | content :'application/json' do
88 | schema do
89 | key :'$ref', :ErrorResponse
90 | end
91 | end
92 | end
93 | end
94 | end
95 | end
96 | end
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/app/swagger_docs/inputs/v1/user/reset_password_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Inputs
4 | module V1
5 | module User
6 | class ResetPasswordInput
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserResetPasswordInput do
11 | key :required, %i[email client_id client_secret]
12 |
13 | property :email do
14 | key :type, :string
15 | key :example, 'test@test.com'
16 | end
17 |
18 | property :client_id do
19 | key :type, :string
20 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
21 | end
22 |
23 | property :client_secret do
24 | key :type, :string
25 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
26 | end
27 | end
28 | end
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/app/swagger_docs/inputs/v1/user/revoke_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Inputs
4 | module V1
5 | module User
6 | class RevokeInput
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserRevokeInput do
11 | key :required, %i[token client_id client_secret]
12 |
13 | property :token do
14 | key :type, :string
15 | key :example, '1qaz2wsx3edc'
16 | end
17 |
18 | property :client_id do
19 | key :type, :string
20 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
21 | end
22 |
23 | property :client_secret do
24 | key :type, :string
25 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
26 | end
27 | end
28 | end
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/app/swagger_docs/inputs/v1/user/sign_in_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Inputs
4 | module V1
5 | module User
6 | class SignInInput
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserSignInInput do
11 | key :required, %i[grant_type email password client_id client_secret]
12 |
13 | property :grant_type do
14 | key :type, :string
15 | key :example, 'password'
16 | end
17 |
18 | property :email do
19 | key :type, :string
20 | key :example, 'test@test.com'
21 | end
22 |
23 | property :password do
24 | key :type, :string
25 | key :example, 'password'
26 | end
27 |
28 | property :client_id do
29 | key :type, :string
30 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
31 | end
32 |
33 | property :client_secret do
34 | key :type, :string
35 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
36 | end
37 | end
38 | end
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/swagger_docs/inputs/v1/user/sign_up_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Inputs
4 | module V1
5 | module User
6 | class SignUpInput
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserSignUpInput do
11 | key :required, %i[email password client_id client_secret]
12 |
13 | property :email do
14 | key :type, :string
15 | key :example, 'test@test.com'
16 | end
17 |
18 | property :password do
19 | key :type, :string
20 | key :example, 'password'
21 | end
22 |
23 | property :client_id do
24 | key :type, :string
25 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
26 | end
27 |
28 | property :client_secret do
29 | key :type, :string
30 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
31 | end
32 | end
33 | end
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/swagger_docs/inputs/v1/user/update_password_input.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Inputs
4 | module V1
5 | module User
6 | class UpdatePasswordInput
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserUpdatePasswordInput do
11 | key :required, %i[reset_password_token password password_confirmation client_id client_secret]
12 |
13 | property :reset_password_token do
14 | key :type, :string
15 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
16 | end
17 |
18 | property :password do
19 | key :type, :string
20 | key :example, 'newpassword'
21 | end
22 |
23 | property :password_confirmation do
24 | key :type, :string
25 | key :example, 'newpassword'
26 | end
27 |
28 | property :client_id do
29 | key :type, :string
30 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
31 | end
32 |
33 | property :client_secret do
34 | key :type, :string
35 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
36 | end
37 | end
38 | end
39 | end
40 | end
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/swagger_docs/models/shared/meta.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Models
4 | module Shared
5 | class Meta
6 | include Swagger::Blocks
7 |
8 | swagger_component do
9 | schema :Meta do
10 | key :type, :object
11 | key :required, %i[pagination]
12 |
13 | property :pagination do
14 | key :'$ref', :Pagination
15 | end
16 | end
17 | end
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/app/swagger_docs/models/shared/pagination.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Models
4 | module Shared
5 | class Pagination
6 | include Swagger::Blocks
7 |
8 | swagger_component do
9 | schema :Pagination do
10 | key :type, :object
11 | key :required, %i[current limit total_pages total_count]
12 |
13 | property :current do
14 | key :type, :integer
15 | key :format, :int32
16 | end
17 |
18 | property :previous do
19 | key :type, :integer
20 | key :format, :int32
21 | end
22 |
23 | property :next do
24 | key :type, :integer
25 | key :format, :int32
26 | end
27 |
28 | property :limit do
29 | key :type, :integer
30 | key :format, :int32
31 | end
32 |
33 | property :total_pages do
34 | key :type, :integer
35 | key :format, :int32
36 | end
37 |
38 | property :total_count do
39 | key :type, :integer
40 | key :format, :int64
41 | end
42 | end
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/app/swagger_docs/parameters/shared/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/app/swagger_docs/parameters/shared/.keep
--------------------------------------------------------------------------------
/app/swagger_docs/responses/shared/error_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Responses
4 | module Shared
5 | class ErrorResponse
6 | include Swagger::Blocks
7 |
8 | swagger_component do
9 | schema :ErrorResponse do
10 | key :type, :object
11 |
12 | property :errors do
13 | key :type, :array
14 | items do
15 | key :type, :string
16 | key :example, 'Something goes wrong while executing request'
17 | end
18 | end
19 | end
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/swagger_docs/responses/v1/user/reset_password_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Responses
4 | module V1
5 | module User
6 | class ResetPasswordResponse
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserResetPasswordSuccessResponse do
11 | key :type, :object
12 | key :required, %i[message]
13 |
14 | property :message do
15 | key :type, :string
16 | key :example, 'You will receive an email with instructions on how to reset your password in a few minutes.'
17 | end
18 | end
19 | end
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/swagger_docs/responses/v1/user/sign_in_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Responses
4 | module V1
5 | module User
6 | class SignInResponse
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserSignInSuccessResponse do
11 | key :type, :object
12 | key :required, %i[access_token token_type expires_in refresh_token created_at user]
13 |
14 | property :access_token do
15 | key :type, :string
16 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
17 | end
18 |
19 | property :token_type do
20 | key :type, :string
21 | key :example, 'Bearer'
22 | end
23 |
24 | property :expires_in do
25 | key :type, :integer
26 | key :example, 7200
27 | end
28 |
29 | property :refresh_token do
30 | key :type, :string
31 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
32 | end
33 |
34 | property :created_at do
35 | key :type, :integer
36 | key :example, 1_661_719_659
37 | end
38 |
39 | property :user do
40 | key :type, :object
41 | key :required, %i[id type email]
42 |
43 | property :id do
44 | key :type, :string
45 | key :format, :uuid
46 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
47 | end
48 |
49 | property :type do
50 | key :type, :string
51 | key :example, 'User'
52 | end
53 |
54 | property :email do
55 | key :type, :string
56 | key :example, 'test@test.com'
57 | end
58 | end
59 | end
60 | end
61 | end
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/app/swagger_docs/responses/v1/user/sign_up_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Responses
4 | module V1
5 | module User
6 | class SignUpResponse
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserSignUpSuccessResponse do
11 | key :type, :object
12 | key :required, %i[user access_token token_type expires_in refresh_token created_at]
13 |
14 | property :user do
15 | key :type, :object
16 | key :required, %i[id type email]
17 |
18 | property :id do
19 | key :type, :string
20 | key :format, :uuid
21 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
22 | end
23 |
24 | property :type do
25 | key :type, :string
26 | key :example, 'User'
27 | end
28 |
29 | property :email do
30 | key :type, :string
31 | key :example, 'test@test.com'
32 | end
33 | end
34 |
35 | property :access_token do
36 | key :type, :string
37 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
38 | end
39 |
40 | property :token_type do
41 | key :type, :string
42 | key :example, 'Bearer'
43 | end
44 |
45 | property :expires_in do
46 | key :type, :integer
47 | key :example, 7200
48 | end
49 |
50 | property :refresh_token do
51 | key :type, :string
52 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
53 | end
54 |
55 | property :created_at do
56 | key :type, :integer
57 | key :example, 1_661_719_307
58 | end
59 | end
60 | end
61 | end
62 | end
63 | end
64 | end
65 |
--------------------------------------------------------------------------------
/app/swagger_docs/responses/v1/user/update_password_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Responses
4 | module V1
5 | module User
6 | class UpdatePasswordResponse
7 | include Swagger::Blocks
8 |
9 | swagger_component do
10 | schema :UserUpdatePasswordSuccessResponse do
11 | key :type, :object
12 | key :required, %i[message]
13 |
14 | property :message do
15 | key :type, :string
16 | key :example, 'Your password has been changed successfully.'
17 | end
18 | end
19 | end
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/swagger_docs/swagger_docs.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SwaggerDocs
4 | include Swagger::Blocks
5 |
6 | COMMON_SWAGGERED_CLASSES = [
7 | ## Models ##
8 | Models::Shared::Pagination,
9 | Models::Shared::Meta,
10 | ## Responses ##
11 | Responses::Shared::ErrorResponse
12 | ].freeze
13 |
14 | V1_SWAGGERED_CLASSES = [
15 | ## Controllers ##
16 | Controllers::V1::Users::TokensController,
17 | Controllers::V1::Users::RegistrationsController,
18 | Controllers::V1::Users::PasswordsController,
19 | ## Inputs ##
20 | Inputs::V1::User::SignInInput,
21 | Inputs::V1::User::SignUpInput,
22 | Inputs::V1::User::ResetPasswordInput,
23 | Inputs::V1::User::UpdatePasswordInput,
24 | Inputs::V1::User::RevokeInput,
25 | ## Responses ##
26 | Responses::V1::User::SignInResponse,
27 | Responses::V1::User::SignUpResponse,
28 | Responses::V1::User::ResetPasswordResponse,
29 | Responses::V1::User::UpdatePasswordResponse,
30 | ## Models ##
31 | self
32 | ].concat(COMMON_SWAGGERED_CLASSES)
33 |
34 | V2_SWAGGERED_CLASSES = [
35 | self
36 | ].concat(COMMON_SWAGGERED_CLASSES)
37 |
38 | swagger_root do
39 | key :openapi, '3.0.0'
40 |
41 | info do
42 | key :version, '1.0.0'
43 | key :title, 'Rails API Boilerplate'
44 | key :description, 'Rails API Boilerplate'
45 |
46 | contact do
47 | key :name, 'SHFT'
48 | key :url, 'https://shft.co'
49 | key :email, 'contact@shft.co'
50 | end
51 | end
52 |
53 | server do
54 | url_options = Rails.application.routes.default_url_options
55 | key :url, "#{url_options[:protocol]}://#{url_options[:host]}"
56 | key :description, 'Rails API Boilerplate'
57 | end
58 | end
59 |
60 | class << self
61 | def v1_swagger_root
62 | build_swagger_root(V1_SWAGGERED_CLASSES)
63 | end
64 |
65 | def v2_swagger_root
66 | build_swagger_root(V2_SWAGGERED_CLASSES)
67 | end
68 |
69 | private
70 |
71 | def build_swagger_root(classes)
72 | swagger_data = Swagger::Blocks.build_root_json(classes)
73 |
74 | swagger_data[:components][:securitySchemes] = {
75 | bearerAuth: {
76 | type: 'http',
77 | scheme: 'bearer',
78 | bearerFormat: 'JWT'
79 | }
80 | }
81 |
82 | swagger_data[:security] = [{ bearerAuth: [] }]
83 |
84 | swagger_data
85 | end
86 | end
87 | end
88 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/confirmation_instructions.html.erb:
--------------------------------------------------------------------------------
1 |
Welcome <%= @email %>!
2 |
3 | You can confirm your account email through the link below:
4 |
5 | '>Confirm my account
6 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/email_changed.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @email %>!
2 |
3 | <% if @resource.try(:unconfirmed_email?) %>
4 | We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.
5 | <% else %>
6 | We're contacting you to notify you that your email has been changed to <%= @resource.email %>.
7 | <% end %>
8 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/password_change.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | We're contacting you to notify you that your password has been changed.
4 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/reset_password_instructions.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | Someone has requested a link to change your password. You can do this through the link below.
4 |
5 | '>Change my password
6 |
7 | If you didn't request this, please ignore this email.
8 | Your password won't change until you access the link above and create a new one.
9 |
--------------------------------------------------------------------------------
/app/views/devise/mailer/unlock_instructions.html.erb:
--------------------------------------------------------------------------------
1 | Hello <%= @resource.email %>!
2 |
3 | Your account has been locked due to an excessive number of unsuccessful sign in attempts.
4 |
5 | Click the link below to unlock your account:
6 |
7 | '>Unlock my account
8 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/views/layouts/swagger.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Swagger UI
5 |
6 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
68 |
69 |
--------------------------------------------------------------------------------
/app/views/shared/partials/_meta.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.meta do
4 | json.partial! 'shared/partials/pagination', locals: { collection: }
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/shared/partials/_pagination.json.jbuilder:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | json.pagination do
4 | json.current collection.current_page
5 | json.previous collection.prev_page
6 | json.next collection.next_page
7 | json.limit collection.limit_value
8 | json.total_pages collection.total_pages
9 | json.total_count collection.total_count
10 | end
11 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | # frozen_string_literal: true
3 |
4 | #
5 | # This file was generated by Bundler.
6 | #
7 | # The application 'bundle' is installed as part of a gem, and
8 | # this file is here to facilitate running it.
9 | #
10 |
11 | require "rubygems"
12 |
13 | m = Module.new do
14 | module_function
15 |
16 | def invoked_as_script?
17 | File.expand_path($0) == File.expand_path(__FILE__)
18 | end
19 |
20 | def env_var_version
21 | ENV["BUNDLER_VERSION"]
22 | end
23 |
24 | def cli_arg_version
25 | return unless invoked_as_script? # don't want to hijack other binstubs
26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update`
27 | bundler_version = nil
28 | update_index = nil
29 | ARGV.each_with_index do |a, i|
30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN
31 | bundler_version = a
32 | end
33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/
34 | bundler_version = $1
35 | update_index = i
36 | end
37 | bundler_version
38 | end
39 |
40 | def gemfile
41 | gemfile = ENV["BUNDLE_GEMFILE"]
42 | return gemfile if gemfile && !gemfile.empty?
43 |
44 | File.expand_path("../../Gemfile", __FILE__)
45 | end
46 |
47 | def lockfile
48 | lockfile =
49 | case File.basename(gemfile)
50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile)
51 | else "#{gemfile}.lock"
52 | end
53 | File.expand_path(lockfile)
54 | end
55 |
56 | def lockfile_version
57 | return unless File.file?(lockfile)
58 | lockfile_contents = File.read(lockfile)
59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/
60 | Regexp.last_match(1)
61 | end
62 |
63 | def bundler_requirement
64 | @bundler_requirement ||=
65 | env_var_version || cli_arg_version ||
66 | bundler_requirement_for(lockfile_version)
67 | end
68 |
69 | def bundler_requirement_for(version)
70 | return "#{Gem::Requirement.default}.a" unless version
71 |
72 | bundler_gem_version = Gem::Version.new(version)
73 |
74 | requirement = bundler_gem_version.approximate_recommendation
75 |
76 | return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0")
77 |
78 | requirement += ".a" if bundler_gem_version.prerelease?
79 |
80 | requirement
81 | end
82 |
83 | def load_bundler!
84 | ENV["BUNDLE_GEMFILE"] ||= gemfile
85 |
86 | activate_bundler
87 | end
88 |
89 | def activate_bundler
90 | gem_error = activation_error_handling do
91 | gem "bundler", bundler_requirement
92 | end
93 | return if gem_error.nil?
94 | require_error = activation_error_handling do
95 | require "bundler/version"
96 | end
97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION))
98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`"
99 | exit 42
100 | end
101 |
102 | def activation_error_handling
103 | yield
104 | nil
105 | rescue StandardError, LoadError => e
106 | e
107 | end
108 | end
109 |
110 | m.load_bundler!
111 |
112 | if m.invoked_as_script?
113 | load Gem.bin_path("bundler", "bundle")
114 | end
115 |
--------------------------------------------------------------------------------
/bin/dev:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | if ! command -v foreman &> /dev/null
4 | then
5 | echo "Installing foreman..."
6 | gem install foreman
7 | fi
8 |
9 | foreman start -p 3000 -f Procfile.dev "$@"
10 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_PATH = File.expand_path("../config/application", __dir__)
3 | require_relative "../config/boot"
4 | require "rails/commands"
5 |
--------------------------------------------------------------------------------
/bin/rake:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require_relative "../config/boot"
3 | require "rake"
4 | Rake.application.run
5 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require "fileutils"
3 |
4 | # path to your application root.
5 | APP_ROOT = File.expand_path("..", __dir__)
6 |
7 | def system!(*args)
8 | system(*args) || abort("\n== Command #{args} failed ==")
9 | end
10 |
11 | FileUtils.chdir APP_ROOT do
12 | # This script is a way to set up or update your development environment automatically.
13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome.
14 | # Add necessary setup steps to this file.
15 |
16 | puts "== Installing dependencies =="
17 | system! "gem install bundler --conservative"
18 | system("bundle check") || system!("bundle install")
19 |
20 | # puts "\n== Copying sample files =="
21 | # unless File.exist?("config/database.yml")
22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml"
23 | # end
24 |
25 | puts "\n== Preparing database =="
26 | system! "bin/rails db:prepare"
27 |
28 | puts "\n== Removing old logs and tempfiles =="
29 | system! "bin/rails log:clear tmp:clear"
30 |
31 | puts "\n== Restarting application server =="
32 | system! "bin/rails restart"
33 | end
34 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # This file is used by Rack-based servers to start the application.
4 |
5 | require_relative 'config/environment'
6 |
7 | run Rails.application
8 | Rails.application.load_server
9 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require_relative 'boot'
4 |
5 | require 'rails/all'
6 |
7 | # Require the gems listed in Gemfile, including any gems
8 | # you've limited to :test, :development, or :production.
9 | Bundler.require(*Rails.groups)
10 |
11 | module RailsApiBoilerplate
12 | class Application < Rails::Application
13 | # Initialize configuration defaults for originally generated Rails version.
14 | config.load_defaults 7.0
15 |
16 | I18n.load_path += Dir[Rails.root.join('config/locales/**/*.yml').to_s]
17 | I18n.available_locales = %i[en tr]
18 |
19 | config.i18n.default_locale = :en
20 |
21 | # Configuration for the application, engines, and railties goes here.
22 | #
23 | # These settings can be overridden in specific environments using the files
24 | # in config/environments, which are processed later.
25 | #
26 | # config.time_zone = "Central Time (US & Canada)"
27 | # config.eager_load_paths << Rails.root.join("extras")
28 |
29 | # Require supports
30 | Dir[Rails.root.join('lib/supports/**/*.rb')].each { |file| require file }
31 |
32 | config.generators do |g|
33 | g.test_framework :test_unit, fixture: false
34 | g.orm :active_record, primary_key_type: :uuid
35 | end
36 |
37 | # Only loads a smaller set of middleware suitable for API only apps.
38 | # Middleware like session, flash, cookies can be added back manually.
39 | # Skip views, helpers and assets when generating a new resource.
40 | config.api_only = true
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
4 |
5 | require 'bundler/setup' # Set up gems listed in the Gemfile.
6 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
7 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: test
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %>
10 | channel_prefix: rails_api_boilerplate_production
11 |
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | lqNhiRbjsLdDi8GtVcTjsJpN7zm52vq0Uvwt1Rch95CKVoywdGXoiVRv3qSqL6IFxtaRYpURuaoseqMDvgc5z5lKEQeOY2kr4gPpxBwRQBPV1aV0zfTBtxdcLDg3MAdnzQ9JEdMkMIXejJcEbjB+CWRa2RcplfMFziNUzkg1X4T5vASjK7c0efnVLQsskHVDqCJhWBNyrnUUi9yO7ZFPoNJQ3AB5UCgoI2+uiDajorL8yu6HauDpwUVGSVi5R4XLoMMRpNJnRPQOW1U2Dlrs4pFY2urEjfboEuZOuBLeDUvkzkHkiAGMFfEEZIPGypOvCi2paROtW10nUPGUurdT+UNnw2I4Ahxpp46xU0pOVFyJbxFH6SBdf7Pttkp824LRDemnyPPoGZHusjvX4CFdCaeVuiIq6zikAH14--5nFC+VevtiVbgT+m--keYGK6wSKq9SUMc8eAE4iw==
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # PostgreSQL. Versions 9.3 and up are supported.
2 | #
3 | # Install the pg driver:
4 | # gem install pg
5 | # On macOS with Homebrew:
6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config
7 | # On macOS with MacPorts:
8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
9 | # On Windows:
10 | # gem install pg
11 | # Choose the win32 build.
12 | # Install PostgreSQL and put its /bin directory on your path.
13 | #
14 | # Configure Using Gemfile
15 | # gem "pg"
16 | #
17 | default: &default
18 | adapter: postgresql
19 | encoding: unicode
20 | # For details on connection pooling, see Rails configuration guide
21 | # https://guides.rubyonrails.org/configuring.html#database-pooling
22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
23 |
24 | development:
25 | <<: *default
26 | database: rails_api_boilerplate_development
27 |
28 | # The specified database role being used to connect to postgres.
29 | # To create additional roles in postgres see `$ createuser --help`.
30 | # When left blank, postgres will use the default role. This is
31 | # the same name as the operating system user running Rails.
32 | #username: rails_api_boilerplate
33 |
34 | # The password associated with the postgres role (username).
35 | #password:
36 |
37 | # Connect on a TCP socket. Omitted by default since the client uses a
38 | # domain socket that doesn't need configuration. Windows does not have
39 | # domain sockets, so uncomment these lines.
40 | #host: localhost
41 |
42 | # The TCP port the server listens on. Defaults to 5432.
43 | # If your server runs on a different port number, change accordingly.
44 | #port: 5432
45 |
46 | # Schema search path. The server defaults to $user,public
47 | #schema_search_path: myapp,sharedapp,public
48 |
49 | # Minimum log levels, in increasing order:
50 | # debug5, debug4, debug3, debug2, debug1,
51 | # log, notice, warning, error, fatal, and panic
52 | # Defaults to warning.
53 | #min_messages: notice
54 |
55 | # Warning: The database defined as "test" will be erased and
56 | # re-generated from your development database when you run "rake".
57 | # Do not set this db to the same as development or production.
58 | test:
59 | <<: *default
60 | database: rails_api_boilerplate_test
61 | host: localhost
62 | # ENV POSTGRES_USER and POSTGRES_PASSWORD set in CI, usually empty
63 | # in local tests, where pg is usually running with no authentication required
64 | username: <%= ENV['POSTGRES_USER'] %>
65 | password: <%= ENV['POSTGRES_PASSWORD'] %>
66 |
67 | # As with config/credentials.yml, you never want to store sensitive information,
68 | # like your database password, in your source code. If your source code is
69 | # ever seen by anyone, they now have access to your database.
70 | #
71 | # Instead, provide the password or a full connection URL as an environment
72 | # variable when you boot the app. For example:
73 | #
74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
75 | #
76 | # If the connection URL is provided in the special DATABASE_URL environment
77 | # variable, Rails will automatically merge its configuration values on top of
78 | # the values provided in this file. Alternatively, you can specify a connection
79 | # URL environment variable explicitly:
80 | #
81 | # production:
82 | # url: <%= ENV["MY_APP_DATABASE_URL"] %>
83 | #
84 | # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database
85 | # for a full overview on how database connection configuration can be specified.
86 | #
87 | production:
88 | <<: *default
89 | database: rails_api_boilerplate_production
90 | username: rails_api_boilerplate
91 | password: <%= ENV["RAILS_API_BOILERPLATE_DATABASE_PASSWORD"] %>
92 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Load the Rails application.
4 | require_relative 'application'
5 |
6 | # Initialize the Rails application.
7 | Rails.application.initialize!
8 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'active_support/core_ext/integer/time'
4 |
5 | APP_HOST = "lvh.me:#{ENV.fetch('PORT', 3000)}".freeze
6 | URL_PROTOCOL = ENV['URL_PROTOCOL'] || 'http'
7 |
8 | Rails.application.routes.default_url_options[:host] = APP_HOST
9 | Rails.application.routes.default_url_options[:protocol] = URL_PROTOCOL
10 |
11 | Rails.application.configure do
12 | # Settings specified here will take precedence over those in config/application.rb.
13 |
14 | config.hosts << '.lvh.me'
15 |
16 | # In the development environment your application's code is reloaded any time
17 | # it changes. This slows down response time but is perfect for development
18 | # since you don't have to restart the web server when you make code changes.
19 | config.cache_classes = false
20 |
21 | # Do not eager load code on boot.
22 | config.eager_load = false
23 |
24 | # Show full error reports.
25 | config.consider_all_requests_local = true
26 |
27 | # Enable server timing
28 | config.server_timing = true
29 |
30 | # Enable/disable caching. By default caching is disabled.
31 | # Run rails dev:cache to toggle caching.
32 | if Rails.root.join('tmp/caching-dev.txt').exist?
33 | config.cache_store = :memory_store
34 | config.public_file_server.headers = {
35 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
36 | }
37 | else
38 | config.action_controller.perform_caching = false
39 |
40 | config.cache_store = :null_store
41 | end
42 |
43 | # Store uploaded files on the local file system (see config/storage.yml for options).
44 | config.active_storage.service = :local
45 |
46 | # Don't care if the mailer can't send.
47 | config.action_mailer.raise_delivery_errors = false
48 |
49 | config.action_mailer.perform_caching = false
50 |
51 | # letter opener
52 | config.action_mailer.delivery_method = :letter_opener
53 |
54 | config.action_mailer.perform_deliveries = true
55 |
56 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
57 |
58 | # Print deprecation notices to the Rails logger.
59 | config.active_support.deprecation = :log
60 |
61 | # Raise exceptions for disallowed deprecations.
62 | config.active_support.disallowed_deprecation = :raise
63 |
64 | # Tell Active Support which deprecation messages to disallow.
65 | config.active_support.disallowed_deprecation_warnings = []
66 |
67 | # Raise an error on page load if there are pending migrations.
68 | config.active_record.migration_error = :page_load
69 |
70 | # Highlight code that triggered database queries in logs.
71 | config.active_record.verbose_query_logs = true
72 |
73 |
74 | # Raises error for missing translations.
75 | # config.i18n.raise_on_missing_translations = true
76 |
77 | # Annotate rendered view with file names.
78 | # config.action_view.annotate_rendered_view_with_filenames = true
79 |
80 | # Uncomment if you wish to allow Action Cable access from any origin.
81 | # config.action_cable.disable_request_forgery_protection = true
82 | end
83 |
--------------------------------------------------------------------------------
/config/environments/production.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'active_support/core_ext/integer/time'
4 |
5 | Rails.application.configure do
6 | # Settings specified here will take precedence over those in config/application.rb.
7 |
8 | # Code is not reloaded between requests.
9 | config.cache_classes = true
10 |
11 | # Eager load code on boot. This eager loads most of Rails and
12 | # your application in memory, allowing both threaded web servers
13 | # and those relying on copy on write to perform better.
14 | # Rake tasks automatically ignore this option for performance.
15 | config.eager_load = true
16 |
17 | # Full error reports are disabled and caching is turned on.
18 | config.consider_all_requests_local = false
19 |
20 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"]
21 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files).
22 | # config.require_master_key = true
23 |
24 | # Disable serving static files from the `/public` folder by default since
25 | # Apache or NGINX already handles this.
26 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
27 |
28 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
29 | # config.asset_host = "http://assets.example.com"
30 |
31 | # Specifies the header that your server uses for sending files.
32 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache
33 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX
34 |
35 | # Store uploaded files on the local file system (see config/storage.yml for options).
36 | config.active_storage.service = :local
37 |
38 | # Mount Action Cable outside main process or domain.
39 | # config.action_cable.mount_path = nil
40 | # config.action_cable.url = "wss://example.com/cable"
41 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ]
42 |
43 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
44 | # config.force_ssl = true
45 |
46 | # Include generic and useful information about system operation, but avoid logging too much
47 | # information to avoid inadvertent exposure of personally identifiable information (PII).
48 | config.log_level = :info
49 |
50 | # Prepend all log lines with the following tags.
51 | config.log_tags = [ :request_id ]
52 |
53 | # Use a different cache store in production.
54 | # config.cache_store = :mem_cache_store
55 |
56 | # Use a real queuing backend for Active Job (and separate queues per environment).
57 | # config.active_job.queue_adapter = :resque
58 | # config.active_job.queue_name_prefix = "rails_api_boilerplate_production"
59 |
60 | config.action_mailer.perform_caching = false
61 |
62 | # Ignore bad email addresses and do not raise email delivery errors.
63 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
64 | # config.action_mailer.raise_delivery_errors = false
65 |
66 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
67 | # the I18n.default_locale when a translation cannot be found).
68 | config.i18n.fallbacks = true
69 |
70 | # Don't log any deprecations.
71 | config.active_support.report_deprecations = false
72 |
73 | # Use default logging formatter so that PID and timestamp are not suppressed.
74 | config.log_formatter = ::Logger::Formatter.new
75 |
76 | # Use a different logger for distributed setups.
77 | # require "syslog/logger"
78 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name")
79 |
80 | if ENV['RAILS_LOG_TO_STDOUT'].present?
81 | logger = ActiveSupport::Logger.new(STDOUT)
82 | logger.formatter = config.log_formatter
83 | config.logger = ActiveSupport::TaggedLogging.new(logger)
84 | end
85 |
86 | # Do not dump schema after migrations.
87 | config.active_record.dump_schema_after_migration = false
88 | end
89 |
--------------------------------------------------------------------------------
/config/environments/test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'active_support/core_ext/integer/time'
4 |
5 | # The test environment is used exclusively to run your application's
6 | # test suite. You never need to work with it otherwise. Remember that
7 | # your test database is "scratch space" for the test suite and is wiped
8 | # and recreated between test runs. Don't rely on the data there!
9 |
10 | Rails.application.configure do
11 | # Settings specified here will take precedence over those in config/application.rb.
12 |
13 | # Turn false under Spring and add config.action_view.cache_template_loading = true.
14 | config.cache_classes = true
15 |
16 | # Eager loading loads your whole application. When running a single test locally,
17 | # this probably isn't necessary. It's a good idea to do in a continuous integration
18 | # system, or in some way before deploying your code.
19 | config.eager_load = ENV['CI'].present?
20 |
21 | # Configure public file server for tests with Cache-Control for performance.
22 | config.public_file_server.enabled = true
23 | config.public_file_server.headers = {
24 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}"
25 | }
26 |
27 | # Show full error reports and disable caching.
28 | config.consider_all_requests_local = true
29 | config.action_controller.perform_caching = false
30 | config.cache_store = :null_store
31 |
32 | # Raise exceptions instead of rendering exception templates.
33 | config.action_dispatch.show_exceptions = false
34 |
35 | # Disable request forgery protection in test environment.
36 | config.action_controller.allow_forgery_protection = false
37 |
38 | # Store uploaded files on the local file system in a temporary directory.
39 | config.active_storage.service = :test
40 |
41 | config.action_mailer.perform_caching = false
42 |
43 | # Tell Action Mailer not to deliver emails to the real world.
44 | # The :test delivery method accumulates sent emails in the
45 | # ActionMailer::Base.deliveries array.
46 | config.action_mailer.delivery_method = :test
47 |
48 | # Print deprecation notices to the stderr.
49 | config.active_support.deprecation = :stderr
50 |
51 | # Raise exceptions for disallowed deprecations.
52 | config.active_support.disallowed_deprecation = :raise
53 |
54 | # Tell Active Support which deprecation messages to disallow.
55 | config.active_support.disallowed_deprecation_warnings = []
56 |
57 | # Raises error for missing translations.
58 | # config.i18n.raise_on_missing_translations = true
59 |
60 | # Annotate rendered view with file names.
61 | # config.action_view.annotate_rendered_view_with_filenames = true
62 | end
63 |
--------------------------------------------------------------------------------
/config/initializers/cors.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Avoid CORS issues when API is called from the frontend app.
6 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests.
7 |
8 | # Read more: https://github.com/cyu/rack-cors
9 |
10 | Rails.application.config.middleware.insert_before 0, Rack::Cors do
11 | allow do
12 | origins '*'
13 |
14 | resource '*',
15 | headers: :any,
16 | methods: [:get, :post, :put, :patch, :delete, :options, :head]
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/config/initializers/filter_parameter_logging.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Be sure to restart your server when you modify this file.
4 |
5 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of
6 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported
7 | # notations and behaviors.
8 | Rails.application.config.filter_parameters += [
9 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn
10 | ]
11 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # Be sure to restart your server when you modify this file.
3 |
4 | # Add new inflection rules using the following format. Inflections
5 | # are locale specific, and you may define rules for as many different
6 | # locales as you wish. All of these examples are active by default:
7 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
8 | # inflect.plural /^(ox)$/i, "\\1en"
9 | # inflect.singular /^(ox)en/i, "\\1"
10 | # inflect.irregular "person", "people"
11 | # inflect.uncountable %w( fish sheep )
12 | # end
13 |
14 | # These inflection rules are supported but not enabled by default:
15 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
16 | # inflect.acronym "RESTful"
17 | # end
18 |
--------------------------------------------------------------------------------
/config/initializers/kaminari_config.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Kaminari.configure do |config|
4 | # config.default_per_page = 25
5 | # config.max_per_page = nil
6 | # config.window = 4
7 | # config.outer_window = 0
8 | # config.left = 0
9 | # config.right = 0
10 | # config.page_method_name = :page
11 | # config.param_name = :page
12 | # config.max_pages = nil
13 | # config.params_on_first_page = false
14 | end
15 |
--------------------------------------------------------------------------------
/config/initializers/ransack.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Ransack.configure do |config|
4 | # Change default search parameter key name.
5 | # Default key name is :q
6 | config.search_key = :query
7 | end
8 |
--------------------------------------------------------------------------------
/config/locales/contracts/tr.yml:
--------------------------------------------------------------------------------
1 | ---
2 | tr:
3 | contracts:
4 | params:
5 | email: E-posta
6 | password: Şifre
7 | password_confirmation: Şifre onayı
8 | reset_password_token: Şifre sıfırlama jetonu
9 |
--------------------------------------------------------------------------------
/config/locales/defaults/tr.yml:
--------------------------------------------------------------------------------
1 | ---
2 | tr:
3 | datetime:
4 | distance_in_words:
5 | about_x_hours:
6 | one: yaklaşık 1 saat
7 | other: yaklaşık %{count} saat
8 | about_x_months:
9 | one: yaklaşık 1 ay
10 | other: yaklaşık %{count} ay
11 | about_x_years:
12 | one: yaklaşık 1 yıl
13 | other: yaklaşık %{count} yıl
14 | almost_x_years:
15 | one: neredeyse 1 yıl
16 | other: neredeyse %{count} yıl
17 | half_a_minute: yarım dakika
18 | less_than_x_minutes:
19 | one: 1 dakikadan az
20 | other: "%{count} dakikadan az"
21 | less_than_x_seconds:
22 | one: 1 saniyeden az
23 | other: "%{count} saniyeden az"
24 | over_x_years:
25 | one: 1 yıldan fazla
26 | other: "%{count} yıldan fazla"
27 | x_days:
28 | one: 1 gün
29 | other: "%{count} gün"
30 | x_minutes:
31 | one: 1 dakika
32 | other: "%{count} dakika"
33 | x_months:
34 | one: 1 ay
35 | other: "%{count} ay"
36 | x_seconds:
37 | one: 1 saniye
38 | other: "%{count} saniye"
39 | x_years:
40 | one: 1 yıl
41 | other: "%{count} yıl"
42 | prompts:
43 | day: Gün
44 | hour: Saat
45 | minute: Dakika
46 | month: Ay
47 | second: Saniye
48 | year: Yıl
49 | time_helper:
50 | ago: 'önce'
51 | time:
52 | am: 'öğleden önce'
53 | formats:
54 | default: "%a, %d %b %Y %H:%M:%S %z"
55 | short: "%d %b %H:%M"
56 | long: "%d %B, %Y %H:%M"
57 | pm: 'öğleden sonra'
58 | date:
59 | abbr_day_names:
60 | - Paz
61 | - Pzt
62 | - Sal
63 | - Çar
64 | - Per
65 | - Cum
66 | - Cts
67 | abbr_month_names:
68 | -
69 | - Ocak
70 | - Şubat
71 | - Mart
72 | - Nisan
73 | - Mayıs
74 | - Haziran
75 | - Temmuz
76 | - Ağustos
77 | - Eylül
78 | - Ekim
79 | - Kasım
80 | - Aralık
81 | day_names:
82 | - Pazar
83 | - Pazartesi
84 | - Salı
85 | - Çarşamba
86 | - Perşembe
87 | - Cuma
88 | - Cumartesi
89 | formats:
90 | default: "%m-%d-%Y"
91 | long: "%B %d, %Y"
92 | short: "%b %d"
93 | month_names:
94 | -
95 | - Ocak
96 | - Şubat
97 | - Mart
98 | - Nisan
99 | - Mayıs
100 | - Haziran
101 | - Temmuz
102 | - Ağustos
103 | - Eylül
104 | - Ekim
105 | - Kasım
106 | - Aralık
107 | order:
108 | - :month
109 | - :day
110 | - :year
111 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t "hello"
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t("hello") %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # "true": "foo"
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at https://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/config/locales/gems/devise/en.yml:
--------------------------------------------------------------------------------
1 | # Additional translations at https://github.com/heartcombo/devise/wiki/I18n
2 |
3 | en:
4 | devise:
5 | confirmations:
6 | confirmed: "Your email address has been successfully confirmed."
7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes."
8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes."
9 | failure:
10 | already_authenticated: "You are already signed in."
11 | inactive: "Your account is not activated yet."
12 | invalid: "Invalid %{authentication_keys} or password."
13 | locked: "Your account is locked."
14 | last_attempt: "You have one more attempt before your account is locked."
15 | not_found_in_database: "Invalid %{authentication_keys} or password."
16 | timeout: "Your session expired. Please sign in again to continue."
17 | unauthenticated: "You need to sign in or sign up before continuing."
18 | unconfirmed: "You have to confirm your email address before continuing."
19 | mailer:
20 | confirmation_instructions:
21 | subject: "Confirmation instructions"
22 | reset_password_instructions:
23 | subject: "Reset password instructions"
24 | unlock_instructions:
25 | subject: "Unlock instructions"
26 | email_changed:
27 | subject: "Email Changed"
28 | password_change:
29 | subject: "Password Changed"
30 | omniauth_callbacks:
31 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"."
32 | success: "Successfully authenticated from %{kind} account."
33 | passwords:
34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided."
35 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes."
36 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes."
37 | updated: "Your password has been changed successfully. You are now signed in."
38 | updated_not_active: "Your password has been changed successfully."
39 | registrations:
40 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon."
41 | signed_up: "Welcome! You have signed up successfully."
42 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated."
43 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked."
44 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account."
45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address."
46 | updated: "Your account has been updated successfully."
47 | updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again."
48 | sessions:
49 | signed_in: "Signed in successfully."
50 | signed_out: "Signed out successfully."
51 | already_signed_out: "Signed out successfully."
52 | unlocks:
53 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes."
54 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes."
55 | unlocked: "Your account has been unlocked successfully. Please sign in to continue."
56 | errors:
57 | messages:
58 | already_confirmed: "was already confirmed, please try signing in"
59 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one"
60 | expired: "has expired, please request a new one"
61 | not_found: "not found"
62 | not_locked: "was not locked"
63 | not_saved:
64 | one: "1 error prohibited this %{resource} from being saved:"
65 | other: "%{count} errors prohibited this %{resource} from being saved:"
66 |
--------------------------------------------------------------------------------
/config/locales/gems/doorkeeper/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | activerecord:
3 | attributes:
4 | doorkeeper/application:
5 | name: 'Name'
6 | redirect_uri: 'Redirect URI'
7 | errors:
8 | models:
9 | doorkeeper/application:
10 | attributes:
11 | redirect_uri:
12 | fragment_present: 'cannot contain a fragment.'
13 | invalid_uri: 'must be a valid URI.'
14 | unspecified_scheme: 'must specify a scheme.'
15 | relative_uri: 'must be an absolute URI.'
16 | secured_uri: 'must be an HTTPS/SSL URI.'
17 | forbidden_uri: 'is forbidden by the server.'
18 | scopes:
19 | not_match_configured: "doesn't match configured on the server."
20 |
21 | doorkeeper:
22 | applications:
23 | confirmations:
24 | destroy: 'Are you sure?'
25 | buttons:
26 | edit: 'Edit'
27 | destroy: 'Destroy'
28 | submit: 'Submit'
29 | cancel: 'Cancel'
30 | authorize: 'Authorize'
31 | form:
32 | error: 'Whoops! Check your form for possible errors'
33 | help:
34 | confidential: 'Application will be used where the client secret can be kept confidential. Native mobile apps and Single Page Apps are considered non-confidential.'
35 | redirect_uri: 'Use one line per URI'
36 | blank_redirect_uri: "Leave it blank if you configured your provider to use Client Credentials, Resource Owner Password Credentials or any other grant type that doesn't require redirect URI."
37 | scopes: 'Separate scopes with spaces. Leave blank to use the default scopes.'
38 | edit:
39 | title: 'Edit application'
40 | index:
41 | title: 'Your applications'
42 | new: 'New Application'
43 | name: 'Name'
44 | callback_url: 'Callback URL'
45 | confidential: 'Confidential?'
46 | actions: 'Actions'
47 | confidentiality:
48 | 'yes': 'Yes'
49 | 'no': 'No'
50 | new:
51 | title: 'New Application'
52 | show:
53 | title: 'Application: %{name}'
54 | application_id: 'UID'
55 | secret: 'Secret'
56 | secret_hashed: 'Secret hashed'
57 | scopes: 'Scopes'
58 | confidential: 'Confidential'
59 | callback_urls: 'Callback urls'
60 | actions: 'Actions'
61 | not_defined: 'Not defined'
62 |
63 | authorizations:
64 | buttons:
65 | authorize: 'Authorize'
66 | deny: 'Deny'
67 | error:
68 | title: 'An error has occurred'
69 | new:
70 | title: 'Authorization required'
71 | prompt: 'Authorize %{client_name} to use your account?'
72 | able_to: 'This application will be able to'
73 | show:
74 | title: 'Authorization code'
75 | form_post:
76 | title: 'Submit this form'
77 |
78 | authorized_applications:
79 | confirmations:
80 | revoke: 'Are you sure?'
81 | buttons:
82 | revoke: 'Revoke'
83 | index:
84 | title: 'Your authorized applications'
85 | application: 'Application'
86 | created_at: 'Created At'
87 | date_format: '%Y-%m-%d %H:%M:%S'
88 |
89 | pre_authorization:
90 | status: 'Pre-authorization'
91 |
92 | errors:
93 | messages:
94 | # Common error messages
95 | invalid_request:
96 | unknown: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.'
97 | missing_param: 'Missing required parameter: %{value}.'
98 | request_not_authorized: 'Request need to be authorized. Required parameter for authorizing request is missing or invalid.'
99 | invalid_redirect_uri: "The requested redirect uri is malformed or doesn't match client redirect URI."
100 | unauthorized_client: 'The client is not authorized to perform this request using this method.'
101 | access_denied: 'The resource owner or authorization server denied the request.'
102 | invalid_scope: 'The requested scope is invalid, unknown, or malformed.'
103 | invalid_code_challenge_method: 'The code challenge method must be plain or S256.'
104 | server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.'
105 | temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.'
106 |
107 | # Configuration error messages
108 | credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
109 | resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfigured.'
110 | admin_authenticator_not_configured: 'Access to admin panel is forbidden due to Doorkeeper.configure.admin_authenticator being unconfigured.'
111 |
112 | # Access grant errors
113 | unsupported_response_type: 'The authorization server does not support this response type.'
114 | unsupported_response_mode: 'The authorization server does not support this response mode.'
115 |
116 | # Access token errors
117 | invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.'
118 | invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.'
119 | unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.'
120 |
121 | invalid_token:
122 | revoked: "The access token was revoked"
123 | expired: "The access token expired"
124 | unknown: "The access token is invalid"
125 | revoke:
126 | unauthorized: "You are not authorized to revoke this token"
127 |
128 | forbidden_token:
129 | missing_scope: 'Access to this resource requires scope "%{oauth_scopes}".'
130 |
131 | flash:
132 | applications:
133 | create:
134 | notice: 'Application created.'
135 | destroy:
136 | notice: 'Application deleted.'
137 | update:
138 | notice: 'Application updated.'
139 | authorized_applications:
140 | destroy:
141 | notice: 'Application revoked.'
142 |
143 | layouts:
144 | admin:
145 | title: 'Doorkeeper'
146 | nav:
147 | oauth2_provider: 'OAuth2 Provider'
148 | applications: 'Applications'
149 | home: 'Home'
150 | application:
151 | title: 'OAuth authorization required'
152 |
--------------------------------------------------------------------------------
/config/locales/gems/doorkeeper/tr.yml:
--------------------------------------------------------------------------------
1 | tr:
2 | activerecord:
3 | attributes:
4 | doorkeeper/application:
5 | name: 'Ad'
6 | redirect_uri: 'Yönlendirme URL'
7 | errors:
8 | models:
9 | doorkeeper/application:
10 | attributes:
11 | redirect_uri:
12 | fragment_present: 'Yönlendirme URL için fragment kullanılamaz'
13 | invalid_uri: 'Geçerli bir URI olmalıdır'
14 | unspecified_scheme: 'Şema belirtilmelidir'
15 | relative_uri: 'Mutlak URI olmalıdır'
16 | secured_uri: 'HTTPS/SSL URI olmalıdır'
17 | forbidden_uri: 'Sunucu tarafından yasaklanmıştır'
18 | scopes:
19 | not_match_configured: "Sunucu tarafında yapılandırılmış olan ile eşleşmiyor"
20 | doorkeeper:
21 | applications:
22 | confirmations:
23 | destroy: 'Silmek istediğinizden emin misiniz?'
24 | buttons:
25 | edit: 'Düzenle'
26 | destroy: 'Sil'
27 | submit: 'Gönder'
28 | cancel: 'İptal'
29 | authorize: 'Yetkilendir'
30 | form:
31 | error: 'Hata! Formunuzda hata olabilir'
32 | help:
33 | confidential: 'Gizli olarak kullanılacak bir uygulama olarak işaretlenir. Mobil uygulamalar veya tek sayfalık uygulamalar gibi.'
34 | redirect_uri: 'Her URI için bir satır kullanın'
35 | blank_redirect_uri: "Eğer uygulama için Client Credentials, Resource Owner Password Credentials veya başka bir yetki türü kullanılıyorsa, yönlendirme URI'lerini boş bırakın."
36 | scopes: 'Yetkileri aralarında boşluk bırakarak ayırın'
37 | edit:
38 | title: 'Uygulamayı düzenle'
39 | index:
40 | title: 'Uygulamalarınız'
41 | new: 'Yeni Uygulama'
42 | name: 'Ad'
43 | callback_url: 'Yönlendirme URL'
44 | confidential: 'Gizli?'
45 | actions: 'Eylemler'
46 | confidentiality:
47 | 'yes': 'Evet'
48 | 'no': 'Hayır'
49 | new:
50 | title: 'Yeni Uygulama'
51 | show:
52 | title: "Uygulama: %{name}"
53 | application_id: 'UID'
54 | secret: 'Gizli'
55 | secret_hashed: 'Gizli hashed'
56 | scopes: 'Yetkiler'
57 | confidential: 'Gizli'
58 | callback_urls: 'Yönlendirme URLleri'
59 | actions: 'Eylemler'
60 | not_defined: 'Belirtilmemiş'
61 |
62 | authorizations:
63 | buttons:
64 | authorize: 'Yetkilendir'
65 | deny: 'Reddet'
66 | error:
67 | title: 'Bir hata oluştu'
68 | new:
69 | title: 'Oturum zorunlu '
70 | prompt: "%{client_name} kullanıcı hesabınıza yetkilendirmek istiyor musunuz?"
71 | able_to: 'Bu uygulama şunu yapabilir'
72 | show:
73 | title: 'Yetkilendirme kodu'
74 | form_post:
75 | title: 'Bu formu gönderin'
76 |
77 | authorized_applications:
78 | confirmations:
79 | revoke: 'Emin misiniz?'
80 | buttons:
81 | revoke: 'İptal et'
82 | index:
83 | title: 'Yetkilendirilmiş uygulamalar'
84 | application: 'Uygulama'
85 | created_at: 'Oluşturulma tarihi'
86 | date_format: '%Y-%m-%d %H:%M:%S'
87 |
88 | pre_authorization:
89 | status: 'Yetkilendirme durumu'
90 |
91 | errors:
92 | messages:
93 | # Common error messages
94 | invalid_request:
95 | unknown: 'İstekte gerekli bir parametre eksik, desteklenmeyen bir parametre değeri içeriyor veya başka bir şekilde hatalı biçimlendirilmiş.'
96 | missing_param: "%{value} parametresi eksik"
97 | request_not_authorized: 'Talebin yetkilendirilmesi gerekiyor. Yetkilendirme isteği için gerekli parametre eksik veya geçersiz.'
98 | invalid_redirect_uri: "İstenen yönlendirme URI'si hatalı biçimlendirilmiş veya istemci yönlendirme URI'si ile eşleşmiyor."
99 | unauthorized_client: 'İstemci, bu yöntemi kullanarak bu isteği gerçekleştirme yetkisine sahip değil.'
100 | access_denied: 'Kaynak sahibi veya yetkilendirme sunucusu, isteği reddetti.'
101 | invalid_scope: 'İstenen kapsam geçersiz, bilinmiyor veya hatalı biçimlendirilmiş.'
102 | invalid_code_challenge_method: 'Kod sorgulama yöntemi düz veya S256 olmalıdır.'
103 | server_error: 'Yetkilendirme sunucusu, isteği yerine getirmesini engelleyen beklenmeyen bir koşulla karşılaştı.'
104 | temporarily_unavailable: 'Yetkilendirme sunucusu, sunucunun geçici olarak aşırı yüklenmesi veya bakımı nedeniyle şu anda isteği işleyemiyor.'
105 |
106 | # Configuration error messages
107 | credential_flow_not_configured: 'Doorkeeper.configure.resource_owner_from_credentials yapılandırılmadığından Kaynak Sahibi Parolası Kimlik Bilgileri akışı başarısız oldu.'
108 | resource_owner_authenticator_not_configured: 'Doorkeeper.configure.resource_owner_authenticator yapılandırılmadığından Kaynak Sahibi bulma başarısız oldu.'
109 | admin_authenticator_not_configured: 'Doorkeeper.configure.admin_authenticator yapılandırılmamış olduğundan yönetici paneline erişim yasaktır.'
110 |
111 | # Access grant errors
112 | unsupported_response_type: 'Yetkilendirme sunucusu bu yanıt türünü desteklemiyor.'
113 | unsupported_response_mode: 'Yetkilendirme sunucusu bu yanıt modunu desteklemiyor.'
114 |
115 | # Access token errors
116 | invalid_client: 'Bilinmeyen istemci, istemci kimlik doğrulamasının dahil edilmemesi veya desteklenmeyen kimlik doğrulama yöntemi nedeniyle istemci kimlik doğrulaması başarısız oldu.'
117 | invalid_grant: "Sağlanan yetkilendirme yetkisi geçersiz, süresi doldu, iptal edildi, yetkilendirme talebinde kullanılan yeniden yönlendirme URI'si ile eşleşmiyor veya başka bir istemciye verildi."
118 | unsupported_grant_type: 'Yetki verme türü, yetkilendirme sunucusu tarafından desteklenmiyor.'
119 |
120 | invalid_token:
121 | revoked: "Erişim tokeni geri alındı."
122 | expired: "Erişim tokeni zaman aşımına uğradı ve kullanılmaya devam edemez."
123 | unknown: "Erişim tokeni geçersiz."
124 | revoke:
125 | unauthorized: "Bu erişim tokeni için yetkilendirme yapılamadı."
126 |
127 | forbidden_token:
128 | missing_scope: "Bu kaynağa erişim için, %{oauth_scopes} yetkileri gerekli."
129 |
130 | flash:
131 | applications:
132 | create:
133 | notice: 'Uygulama oluşturuldu'
134 | destroy:
135 | notice: 'Uygulama silindi'
136 | update:
137 | notice: 'Uygulama güncellendi'
138 | authorized_applications:
139 | destroy:
140 | notice: 'Yetkilendirilmiş uygulama silindi'
141 |
142 | layouts:
143 | admin:
144 | title: 'Doorkeeper'
145 | nav:
146 | oauth2_provider: 'OAuth2 Sağlayıcı'
147 | applications: 'Uygulamalar'
148 | home: 'Anasayfa'
149 | application:
150 | title: 'OAuth yetkilendirmesi zorunlu'
151 |
--------------------------------------------------------------------------------
/config/locales/gems/dry_validation/en.yml:
--------------------------------------------------------------------------------
1 | ---
2 | en:
3 | dry_validation:
4 | keys_splitter: ' of '
5 | errors:
6 | same_password?: does not match
7 |
--------------------------------------------------------------------------------
/config/locales/gems/dry_validation/tr.yml:
--------------------------------------------------------------------------------
1 | ---
2 | tr:
3 | dry_validation:
4 | keys_splitter: ' içindeki '
5 | errors:
6 | same_password?: eşleşmiyor
7 | array?: dizi olmalı
8 | attr?: eksik
9 | bool?: true veya false olmalı
10 | date?: tarih olmalı
11 | date_time?: saat - tarih olmalı
12 | decimal?: ondalık sayı olmalı
13 | empty?: boş olmalı
14 | eql?: '%{left} ile eşit olmalı'
15 | even?: çift sayı olmalı
16 | excluded_from?:
17 | arg:
18 | default: '%{list} değerlerinden biri olmalı'
19 | range: '%{list_left} - %{list_right} arasında olmalı'
20 | excludes?: '%{value} değerini içermemeli'
21 | exclusion?: '%{list} değerlerinden biri olmamalı'
22 | false?: false olmalı
23 | filled?: doldurulmalı
24 | float?: ondalık sayı olmalı
25 | format?: geçersiz formata sahip
26 | gt?: "%{num}'dan büyük olmalı"
27 | gteq?: "%{num}'dan büyük ya da eşit olmalı"
28 | hash?: must be a hash
29 | included_in?:
30 | arg:
31 | default: "%{list} seçeneklerinden biri olmalı"
32 | range: "%{list_left} - %{list_right} seçeneklerinden biri olmalı"
33 | includes?: "%{value} içermeli"
34 | inclusion?: "%{list} seçeneklerinden biri olmalı"
35 | int?: sayı olmalı
36 | invalid_permission: geçersiz yetki
37 | key?: eksik
38 | lt?: "%{num} sayısından küçük olmalı"
39 | lteq?: "%{num} sayısından küçük ya da eşit olmalı"
40 | max_size?: "%{num} karakterden büyük olamaz"
41 | min_size?: "%{num} karakterden küçük olamaz"
42 | must_have_same_currency: aynı döviz cinsi seçilmeli ya da Türk Lirası olarak hesapla seçeneği kullanılmalı
43 | must_have_valid_email: geçersiz
44 | must_have_valid_url: geçersiz
45 | none?: boş olamaz
46 | not_eql?: '%{left} ile eşit olmamalı'
47 | number?: sayı olmalı
48 | odd?: tek sayı olmalı
49 | or: veya
50 | password_not_matched: uyuşmuyor
51 | size?:
52 | arg:
53 | default: "%{size} hane olmalı"
54 | range: "%{size_left} - %{size_right} karakter aralığında olmalı"
55 | value:
56 | string:
57 | arg:
58 | default: uzunluğu %{size} karakter olmalı
59 | range: uzunluğu %{size_left} - %{size_right} karakter aralığında olmalı
60 | str?: alfabetik karakter olmalı
61 | time?: saat olmalı
62 | true?: true olmalı
63 | type?: "%{type} tipinde olmalı"
64 | unit_required: boş olmamalı
65 |
--------------------------------------------------------------------------------
/config/locales/services/en.yml:
--------------------------------------------------------------------------------
1 | ---
2 | en:
3 | application_service:
4 | default:
5 | find_resource:
6 | error: '%{klass} could not be found'
7 |
--------------------------------------------------------------------------------
/config/locales/services/tr.yml:
--------------------------------------------------------------------------------
1 | ---
2 | tr:
3 | application_service:
4 | default:
5 | find_resource:
6 | error: '%{klass} bulunamadı'
7 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # Puma can serve each request in a thread from an internal thread pool.
4 | # The `threads` method setting takes two numbers: a minimum and maximum.
5 | # Any libraries that use thread pools should be configured to match
6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
7 | # and maximum; this matches the default thread size of Active Record.
8 | #
9 | max_threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 }
10 | min_threads_count = ENV.fetch('RAILS_MIN_THREADS') { max_threads_count }
11 | threads min_threads_count, max_threads_count
12 |
13 | # Specifies the `worker_timeout` threshold that Puma will use to wait before
14 | # terminating a worker in development environments.
15 | #
16 | worker_timeout 3600 if ENV.fetch('RAILS_ENV', 'development') == 'development'
17 |
18 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
19 | #
20 | port ENV.fetch('PORT') { 3000 }
21 |
22 | # Specifies the `environment` that Puma will run in.
23 | #
24 | environment ENV.fetch('RAILS_ENV') { 'development' }
25 |
26 | # Specifies the `pidfile` that Puma will use.
27 | pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' }
28 |
29 | # Specifies the number of `workers` to boot in clustered mode.
30 | # Workers are forked web server processes. If using threads and workers together
31 | # the concurrency of the application would be max `threads` * `workers`.
32 | # Workers do not work on JRuby or Windows (both of which do not support
33 | # processes).
34 | #
35 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
36 |
37 | # Use the `preload_app!` method when specifying a `workers` number.
38 | # This directive tells Puma to first boot the application and load code
39 | # before forking the application. This takes advantage of Copy On Write
40 | # process behavior so workers use less memory.
41 | #
42 | # preload_app!
43 |
44 | # Allow puma to be restarted by `bin/rails restart` command.
45 | plugin :tmp_restart
46 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | Rails.application.routes.draw do
4 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html
5 |
6 | # Defines the root path route ("/")
7 | root 'swagger#index'
8 |
9 | # Swagger documentation
10 | draw :swagger
11 |
12 | # API routes
13 | defaults format: :json do
14 | # V1
15 | namespace :v1 do
16 | draw :authentication
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/config/routes/authentication.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | devise_for :users, skip: :all
4 |
5 | namespace :users do
6 | use_doorkeeper do
7 | controllers tokens: 'tokens'
8 | skip_controllers :applications, :authorized_applications, :authorizations
9 | end
10 |
11 | post 'sign_up', to: 'registrations#create', as: :registration
12 | post 'password', to: 'passwords#create', as: :password
13 | patch 'password', to: 'passwords#update', as: nil
14 | end
15 |
--------------------------------------------------------------------------------
/config/routes/swagger.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | scope :swagger, as: 'swagger' do
4 | get '/', to: 'swagger#index', as: :root
5 | get '/v1_data', to: 'swagger#v1_data', as: :v1_data
6 | get '/v2_data', to: 'swagger#v2_data', as: :v2_data
7 | end
8 |
--------------------------------------------------------------------------------
/config/sidekiq.yml:
--------------------------------------------------------------------------------
1 | # Sample configuration file for Sidekiq.
2 | # Options here can still be overridden by cmd line args.
3 | # Place this file at config/sidekiq.yml and Sidekiq will
4 | # pick it up automatically.
5 | ---
6 | :verbose: false
7 | :concurrency: <%= ENV['SIDEKIQ_CONCURRENCY'] || 10 %>
8 |
9 | # Set timeout to 8 on Heroku, longer if you manage your own systems.
10 | :timeout: 8
11 |
12 | # Sidekiq will run this file through ERB when reading it so you can
13 | # even put in dynamic logic, like a host-specific queue.
14 | # http://www.mikeperham.com/2013/11/13/advanced-sidekiq-host-specific-queues/
15 | :queues:
16 | - [critical, 5]
17 | - [default, 2]
18 | - [low, 1]
19 | # you can override concurrency based on environment
20 | # production:
21 | # :concurrency: 25
22 | # staging:
23 | # :concurrency: 15
24 |
--------------------------------------------------------------------------------
/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | # amazon:
11 | # service: S3
12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %>
13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %>
14 | # region: us-east-1
15 | # bucket: your_own_bucket-<%= Rails.env %>
16 |
17 | # Remember not to checkin your GCS keyfile to a repository
18 | # google:
19 | # service: GCS
20 | # project: your_project
21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
22 | # bucket: your_own_bucket-<%= Rails.env %>
23 |
24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
25 | # microsoft:
26 | # service: AzureStorage
27 | # storage_account_name: your_account_name
28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
29 | # container: your_container_name-<%= Rails.env %>
30 |
31 | # mirror:
32 | # service: Mirror
33 | # primary: local
34 | # mirrors: [ amazon, google, microsoft ]
35 |
--------------------------------------------------------------------------------
/db/migrate/20220212185845_devise_create_users.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class DeviseCreateUsers < ActiveRecord::Migration[7.0]
4 | def change
5 | create_table :users, id: :uuid do |t|
6 | ## Database authenticatable
7 | t.string :email, null: false, default: ''
8 | t.string :encrypted_password, null: false, default: ''
9 |
10 | ## Recoverable
11 | t.string :reset_password_token
12 | t.datetime :reset_password_sent_at
13 |
14 | ## Rememberable
15 | t.datetime :remember_created_at
16 |
17 | ## Trackable
18 | t.integer :sign_in_count, default: 0, null: false
19 | t.datetime :current_sign_in_at
20 | t.datetime :last_sign_in_at
21 | t.string :current_sign_in_ip
22 | t.string :last_sign_in_ip
23 |
24 | ## Confirmable
25 | # t.string :confirmation_token
26 | # t.datetime :confirmed_at
27 | # t.datetime :confirmation_sent_at
28 | # t.string :unconfirmed_email # Only if using reconfirmable
29 |
30 | ## Lockable
31 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts
32 | # t.string :unlock_token # Only if unlock strategy is :email or :both
33 | # t.datetime :locked_at
34 |
35 | t.timestamps null: false
36 | end
37 |
38 | add_index :users, :email, unique: true
39 | add_index :users, :reset_password_token, unique: true
40 | # add_index :users, :confirmation_token, unique: true
41 | # add_index :users, :unlock_token, unique: true
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/db/migrate/20220212195921_create_doorkeeper_tables.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class CreateDoorkeeperTables < ActiveRecord::Migration[7.0]
4 | def change
5 | create_table :oauth_applications, id: :uuid do |t|
6 | t.string :name, null: false
7 | t.string :uid, null: false
8 | t.string :secret, null: false
9 |
10 | # Remove `null: false` if you are planning to use grant flows
11 | # that doesn't require redirect URI to be used during authorization
12 | # like Client Credentials flow or Resource Owner Password.
13 | t.text :redirect_uri
14 | t.string :scopes, null: false, default: ''
15 | t.boolean :confidential, null: false, default: true
16 | t.timestamps null: false
17 | end
18 |
19 | add_index :oauth_applications, :uid, unique: true
20 |
21 | create_table :oauth_access_tokens, id: :uuid do |t|
22 | t.references :resource_owner, index: true, polymorphic: true, type: :uuid
23 |
24 | # Remove `null: false` if you are planning to use Password
25 | # Credentials Grant flow that doesn't require an application.
26 | t.references :application, null: false, type: :uuid
27 |
28 | # If you use a custom token generator you may need to change this column
29 | # from string to text, so that it accepts tokens larger than 255
30 | # characters. More info on custom token generators in:
31 | # https://github.com/doorkeeper-gem/doorkeeper/tree/v3.0.0.rc1#custom-access-token-generator
32 | #
33 | # t.text :token, null: false
34 | t.string :token, null: false
35 |
36 | t.string :refresh_token
37 | t.integer :expires_in
38 | t.datetime :revoked_at
39 | t.datetime :created_at, null: false
40 | t.string :scopes
41 |
42 | # The authorization server MAY issue a new refresh token, in which case
43 | # *the client MUST discard the old refresh token* and replace it with the
44 | # new refresh token. The authorization server MAY revoke the old
45 | # refresh token after issuing a new refresh token to the client.
46 | # @see https://datatracker.ietf.org/doc/html/rfc6749#section-6
47 | #
48 | # Doorkeeper implementation: if there is a `previous_refresh_token` column,
49 | # refresh tokens will be revoked after a related access token is used.
50 | # If there is no `previous_refresh_token` column, previous tokens are
51 | # revoked as soon as a new access token is created.
52 | #
53 | # Comment out this line if you want refresh tokens to be instantly
54 | # revoked after use.
55 | t.string :previous_refresh_token, null: false, default: ''
56 | end
57 |
58 | add_index :oauth_access_tokens, :token, unique: true
59 | add_index :oauth_access_tokens, :refresh_token, unique: true
60 |
61 | add_foreign_key(
62 | :oauth_access_tokens,
63 | :oauth_applications,
64 | column: :application_id
65 | )
66 |
67 | # Uncomment below to ensure a valid reference to the resource owner's table
68 | # add_foreign_key :oauth_access_grants, , column: :resource_owner_id
69 | # add_foreign_key :oauth_access_tokens, , column: :resource_owner_id
70 | end
71 | end
72 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # This file is the source Rails uses to define your schema when running `bin/rails
6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
7 | # be faster and is potentially less error prone than running all of your
8 | # migrations from scratch. Old migrations may fail to apply correctly if those
9 | # migrations use external dependencies or application code.
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema[7.0].define(version: 2022_02_12_195921) do
14 | # These are extensions that must be enabled in order to support this database
15 | enable_extension "plpgsql"
16 |
17 | create_table "oauth_access_tokens", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
18 | t.string "resource_owner_type"
19 | t.uuid "resource_owner_id"
20 | t.uuid "application_id", null: false
21 | t.string "token", null: false
22 | t.string "refresh_token"
23 | t.integer "expires_in"
24 | t.datetime "revoked_at"
25 | t.datetime "created_at", null: false
26 | t.string "scopes"
27 | t.string "previous_refresh_token", default: "", null: false
28 | t.index ["application_id"], name: "index_oauth_access_tokens_on_application_id"
29 | t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
30 | t.index ["resource_owner_type", "resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner"
31 | t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
32 | end
33 |
34 | create_table "oauth_applications", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
35 | t.string "name", null: false
36 | t.string "uid", null: false
37 | t.string "secret", null: false
38 | t.text "redirect_uri"
39 | t.string "scopes", default: "", null: false
40 | t.boolean "confidential", default: true, null: false
41 | t.datetime "created_at", null: false
42 | t.datetime "updated_at", null: false
43 | t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
44 | end
45 |
46 | create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t|
47 | t.string "email", default: "", null: false
48 | t.string "encrypted_password", default: "", null: false
49 | t.string "reset_password_token"
50 | t.datetime "reset_password_sent_at"
51 | t.datetime "remember_created_at"
52 | t.integer "sign_in_count", default: 0, null: false
53 | t.datetime "current_sign_in_at"
54 | t.datetime "last_sign_in_at"
55 | t.string "current_sign_in_ip"
56 | t.string "last_sign_in_ip"
57 | t.datetime "created_at", null: false
58 | t.datetime "updated_at", null: false
59 | t.index ["email"], name: "index_users_on_email", unique: true
60 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
61 | end
62 |
63 | add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"
64 | end
65 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | # This file should contain all the record creation needed to seed the database with its default values.
3 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup).
4 | #
5 | # Examples:
6 | #
7 | # movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }])
8 | # Character.create(name: "Luke", movie: movies.first)
9 |
--------------------------------------------------------------------------------
/docs/CONTRACT.md:
--------------------------------------------------------------------------------
1 | # Contract Generator Documentation
2 | This boilerplate is using `dry-validation` gem for contract structure. If you need contract, you can use custom contract generator like that:
3 | ```bash
4 | $ rails generate contract Users::Registration
5 | ```
6 |
7 | Then, it will create some service classes for your operations.
8 | ```
9 | create app/contracts/users/registration_contract.rb
10 | create test/contracts/users/registration_contract_test.rb
11 | ```
12 |
13 | You can access service templates from `/lib/generators/contract` folder.
14 |
--------------------------------------------------------------------------------
/docs/RANSACK.md:
--------------------------------------------------------------------------------
1 | # Search & Filter & Sort (RANSACK)
2 |
3 | The search parameters are passed to ransack as a hash. The URL representation of this hash uses the bracket notation: ```hash_name[key]=value```. The hash_name is the parameter which is defined in the controller, for instance ```query```. The key is the attribute and search predicate compound, for instance ```first_name_cont```, the value is the search parameter. When searching without using the search form helpers this URL structure needs to be created manually.
4 |
5 | For example, the URL layout for searching and sorting users could looks like this:
6 |
7 | ```
8 | /users?query[first_name_cont]=pete&query[last_name_cont]=jack&query[s]=created_at+desc
9 | ```
10 |
11 | _Note that the sorting parameter ```s``` is nested within the ```query``` hash._
12 |
13 | When using JavaScript to create such a URL, a matching axios request could look like this:
14 |
15 | ```javascript
16 | import axios from 'axios';
17 |
18 | const response = await axios.get('/users', {
19 | params: {
20 | query: {
21 | first_name_cont: "pete",
22 | last_name_cont: "jack",
23 | s: "created_at desc"
24 | }
25 | }
26 | })
27 | ```
28 | ---
29 |
30 | ## List of all possible predicates
31 |
32 | | Predicate | Description | Notes |
33 | | ------------- | ------------- |-------- |
34 | | `*_eq` | equal | |
35 | | `*_not_eq` | not equal | |
36 | | `*_matches` | matches with `LIKE` | e.g. `q[email_matches]=%@gmail.com`|
37 | | `*_does_not_match` | does not match with `LIKE` | |
38 | | `*_matches_any` | Matches any | |
39 | | `*_matches_all` | Matches all | |
40 | | `*_does_not_match_any` | Does not match any | |
41 | | `*_does_not_match_all` | Does not match all | |
42 | | `*_lt` | less than | |
43 | | `*_lteq` | less than or equal | |
44 | | `*_gt` | greater than | |
45 | | `*_gteq` | greater than or equal | |
46 | | `*_present` | not null and not empty | Only compatible with string columns. Example: `q[name_present]=1` (SQL: `col is not null AND col != ''`) |
47 | | `*_blank` | is null or empty. | (SQL: `col is null OR col = ''`) |
48 | | `*_null` | is null | |
49 | | `*_not_null` | is not null | |
50 | | `*_in` | match any values in array | e.g. `q[name_in][]=Alice&q[name_in][]=Bob` |
51 | | `*_not_in` | match none of values in array | |
52 | | `*_lt_any` | Less than any | SQL: `col < value1 OR col < value2` |
53 | | `*_lteq_any` | Less than or equal to any | |
54 | | `*_gt_any` | Greater than any | |
55 | | `*_gteq_any` | Greater than or equal to any | |
56 | | `*_lt_all` | Less than all | SQL: `col < value1 AND col < value2` |
57 | | `*_lteq_all` | Less than or equal to all | |
58 | | `*_gt_all` | Greater than all | |
59 | | `*_gteq_all` | Greater than or equal to all | |
60 | | `*_not_eq_all` | none of values in a set | |
61 | | `*_start` | Starts with | SQL: `col LIKE 'value%'` |
62 | | `*_not_start` | Does not start with | |
63 | | `*_start_any` | Starts with any of | |
64 | | `*_start_all` | Starts with all of | |
65 | | `*_not_start_any` | Does not start with any of | |
66 | | `*_not_start_all` | Does not start with all of | |
67 | | `*_end` | Ends with | SQL: `col LIKE '%value'` |
68 | | `*_not_end` | Does not end with | |
69 | | `*_end_any` | Ends with any of | |
70 | | `*_end_all` | Ends with all of | |
71 | | `*_not_end_any` | | |
72 | | `*_not_end_all` | | |
73 | | `*_cont` | Contains value | uses `LIKE` |
74 | | `*_cont_any` | Contains any of | |
75 | | `*_cont_all` | Contains all of | |
76 | | `*_not_cont` | Does not contain |
77 | | `*_not_cont_any` | Does not contain any of | |
78 | | `*_not_cont_all` | Does not contain all of | |
79 | | `*_i_cont` | Contains value with case insensitive | uses `ILIKE` |
80 | | `*_i_cont_any` | Contains any of values with case insensitive | |
81 | | `*_i_cont_all` | Contains all of values with case insensitive | |
82 | | `*_not_i_cont` | Does not contain with case insensitive |
83 | | `*_not_i_cont_any` | Does not contain any of values with case insensitive | |
84 | | `*_not_i_cont_all` | Does not contain all of values with case insensitive | |
85 | | `*_true` | is true | |
86 | | `*_false` | is false | |
87 |
--------------------------------------------------------------------------------
/docs/SERVICE.md:
--------------------------------------------------------------------------------
1 | # Service Generator Documentation
2 | This boilerplate is using `dry-monads` gem for service structure. If you need service, you can use custom service generator like that:
3 | ```bash
4 | $ rails generate service Facebook::Adset
5 | ```
6 |
7 | Then, it will create some service classes for your operations.
8 | ```
9 | create app/services/facebook/adset_service.rb
10 | create test/services/facebook/adset_service_test.rb
11 | ```
12 |
13 | You can access service templates from `/lib/generators/service` folder.
14 |
15 | # Sample list service usage
16 | ```ruby
17 | class UsersController < ApplicationController
18 | def index
19 | service = UserService::List.new(query: query_object,
20 | pagination: pagination_object).call
21 |
22 | @users = service.success
23 | end
24 | end
25 | ```
26 |
--------------------------------------------------------------------------------
/docs/SWAGGER.md:
--------------------------------------------------------------------------------
1 | # Swagger Documentation
2 | This boilerplate is using `swagger-blocks` gem for creating swagger documentation with Ruby on Rails application.
3 |
4 | [TUTORIAL](https://duetcode.io/rails-api-only-course/api-documentation-with-swagger)
5 |
6 | ## Folder Structure
7 | .
8 | ├── ...
9 | ├── app
10 | │ ├── ...
11 | │ ├── controllers
12 | │ ├── swagger_docs
13 | │ │ ├── controllers
14 | │ │ ├── inputs
15 | │ │ ├── models
16 | │ │ ├── parameters
17 | │ │ ├── responses
18 |
19 | ---
20 | ### Sample controller
21 | ```ruby
22 | # frozen_string_literal: true
23 |
24 | module Controllers
25 | class CitiesController
26 | include Swagger::Blocks
27 |
28 | swagger_path '/cities/{city_id}' do
29 | operation :get do
30 | key :summary, 'Return city with by id'
31 | key :description, 'Return city with by id'
32 | key :operationId, 'showCity'
33 | key :tags, [
34 | 'cities'
35 | ]
36 |
37 | parameter do
38 | key :'$ref', :city_id
39 | end
40 |
41 | response 200 do
42 | key :description, 'Successful response'
43 | content :'application/json' do
44 | schema do
45 | key :'$ref', :CityShowResponse
46 | end
47 | end
48 | end
49 | end
50 | end
51 | end
52 | end
53 | ```
54 | ---
55 | ### Sample input
56 | ```ruby
57 | # frozen_string_literal: true
58 |
59 | module Inputs
60 | module City
61 | class CreateInput
62 | include Swagger::Blocks
63 |
64 | swagger_component do
65 | schema :CityCreateInput do
66 | key :required, %i[city]
67 |
68 | property :city do
69 | key :required, %i[name alpha_2_code]
70 |
71 | property :name do
72 | key :type, :string
73 | key :example, 'Ankara'
74 | end
75 |
76 | property :alpha_2_code do
77 | key :type, :string
78 | key :example, 'TR-06'
79 | end
80 | end
81 | end
82 | end
83 | end
84 | end
85 | end
86 | ```
87 | ---
88 | ### Sample model
89 | ```ruby
90 | # frozen_string_literal: true
91 |
92 | module Models
93 | class City
94 | include Swagger::Blocks
95 |
96 | swagger_component do
97 | schema :City do
98 | key :type, :object
99 | key :required, %i[id name alpha_2_code]
100 |
101 | property :id do
102 | key :type, :string
103 | key :format, :uuid
104 | key :example, 'e7c8f8f0-e8e0-4b0f-b8b1-f8f8f8f8f8f8'
105 | end
106 |
107 | property :name do
108 | key :type, :string
109 | key :example, 'Ankara'
110 | end
111 |
112 | property :alpha_2_code do
113 | key :type, :string
114 | key :example, 'TR-06'
115 | end
116 | end
117 | end
118 | end
119 | end
120 | ```
121 | ---
122 | ### Sample parameter
123 | ```ruby
124 | # frozen_string_literal: true
125 |
126 | module Parameters
127 | class CityId
128 | include Swagger::Blocks
129 |
130 | swagger_component do
131 | parameter :city_id do
132 | key :name, :city_id
133 | key :in, :path
134 | key :description, 'The id of the city'
135 | key :required, true
136 | schema do
137 | key :type, :string
138 | key :format, :uuid
139 | end
140 | end
141 | end
142 | end
143 | end
144 | ```
145 | ---
146 | ### Sample response
147 | ```ruby
148 | # frozen_string_literal: true
149 |
150 | module Responses
151 | module City
152 | class Show
153 | include Swagger::Blocks
154 |
155 | swagger_component do
156 | schema :CityShowResponse do
157 | key :type, :object
158 | key :required, %i[city meta]
159 |
160 | property :city do
161 | key :'$ref', :City
162 | end
163 | end
164 | end
165 | end
166 | end
167 | end
168 | ```
169 |
--------------------------------------------------------------------------------
/docs/cover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/docs/cover.png
--------------------------------------------------------------------------------
/lib/generators/contract/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Creates a new contract
3 |
4 | Example:
5 | bin/rails generate contract Customer::Create email fullname
6 |
7 | This will create:
8 | app/contracts/customers/create_contract.rb
9 | app/test/contracts/customers/create_contract_test.rb
10 |
--------------------------------------------------------------------------------
/lib/generators/contract/contract_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ContractGenerator < Rails::Generators::NamedBase
4 | argument :params, type: :array, default: [], banner: 'name:o fullname:required'
5 | class_option :parent, type: :string, default: 'ApplicationContract', desc: 'The parent class for the generated contract'
6 |
7 | check_class_collision suffix: 'Contract'
8 |
9 | source_root File.expand_path('templates', __dir__)
10 |
11 | def create_contract_files
12 | template 'contract.rb', File.join('app/contracts/', class_path, "#{file_name}_contract.rb")
13 | template 'contract_test.rb', File.join('test/contracts/', class_path, "#{file_name}_contract_test.rb")
14 |
15 | return unless Rails.env.development?
16 |
17 | system("rubocop -A #{contract_file_path}")
18 | system("rubocop -A #{contract_test_file_path}")
19 | end
20 |
21 | private
22 |
23 | def contract_file_path
24 | "app/contracts/#{class_path.join('/')}/#{file_name}_contract.rb"
25 | end
26 |
27 | def contract_test_file_path
28 | "test/contracts/#{class_path.join('/')}/#{file_name}_contract_test.rb"
29 | end
30 |
31 | def parent_class_name
32 | options[:parent]
33 | end
34 |
35 | def formatted_params
36 | params.map do |current_param|
37 | type = type_defined_for_param?(current_param) ? defined_type_for_param(current_param) : 'optional'
38 |
39 | [type, parse_param(current_param).first]
40 | end
41 | end
42 |
43 | def parse_param(current_param)
44 | current_param.split(':')
45 | end
46 |
47 | def type_defined_for_param?(current_param)
48 | param = parse_param(current_param)
49 |
50 | return false if param.size <= 1
51 |
52 | valid_types = %w[r o required optional]
53 |
54 | valid_types.include?(param.second)
55 | end
56 |
57 | def defined_type_for_param(current_param)
58 | param = parse_param(current_param)
59 |
60 | case param.last
61 | when 'o', 'optional'
62 | 'optional'
63 | when 'r', 'required'
64 | 'required'
65 | end
66 | end
67 |
68 | def file_name
69 | @_file_name ||= remove_possible_suffix(super)
70 | end
71 |
72 | def remove_possible_suffix(name)
73 | name.sub(/_?contract$/i, '')
74 | end
75 | end
76 |
--------------------------------------------------------------------------------
/lib/generators/contract/templates/contract.rb.tt:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | <% module_namespacing do -%>
4 | class <%= class_name %>Contract < <%= parent_class_name.classify %>
5 | params do
6 | <% formatted_params.each do |param| -%>
7 | <%= param.first %>(:<%= param.last %>) {}
8 | <% end -%>
9 | end
10 | end
11 | <% end -%>
12 |
--------------------------------------------------------------------------------
/lib/generators/contract/templates/contract_test.rb.tt:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | <% module_namespacing do -%>
6 | class <%= class_name %>ContractTest < <%= ActiveSupport::TestCase %>
7 | CONTRACT = <%= class_name %>Contract.new
8 |
9 | def validate(payload = {})
10 | CONTRACT.call(payload)
11 | end
12 |
13 | <% formatted_params.each do |param| -%>
14 | test 'validate#<%= param.last %>' do
15 | end
16 |
17 | <% end -%>
18 | end
19 | <% end -%>
20 |
--------------------------------------------------------------------------------
/lib/generators/operation/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Creates a new operation
3 |
4 | Example:
5 | bin/rails generate operation Customer::Create current_user params
6 |
7 | This will create:
8 | app/operations/customer/create_operation.rb
9 | test/operations/customer/create_operation_test.rb
10 |
--------------------------------------------------------------------------------
/lib/generators/operation/operation_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class OperationGenerator < Rails::Generators::NamedBase
4 | argument :params, type: :array, default: [], banner: 'action action'
5 | class_option :parent, type: :string, default: 'ApplicationOperation', desc: 'The parent class for the generated operation'
6 |
7 | check_class_collision suffix: 'Operation'
8 |
9 | source_root File.expand_path('templates', __dir__)
10 |
11 | def create_operation_files
12 | template 'operation.rb', File.join('app/operations/', class_path, "#{file_name}_operation.rb")
13 | template 'operation_test.rb', File.join('test/operations/', class_path, "#{file_name}_operation_test.rb")
14 |
15 | return unless Rails.env.development?
16 |
17 | system("rubocop -A #{operation_file_path}")
18 | system("rubocop -A #{operation_test_file_path}")
19 | end
20 |
21 | private
22 |
23 | def operation_file_path
24 | "app/operations/#{class_path.join('/')}/#{file_name}_operation.rb"
25 | end
26 |
27 | def operation_test_file_path
28 | "test/operations/#{class_path.join('/')}/#{file_name}_operation_test.rb"
29 | end
30 |
31 | def parent_class_name
32 | options[:parent]
33 | end
34 |
35 | def file_name
36 | @_file_name ||= remove_possible_suffix(super)
37 | end
38 |
39 | def remove_possible_suffix(name)
40 | name.sub(/_?operation$/i, '')
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/generators/operation/templates/operation.rb.tt:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | <% module_namespacing do -%>
4 | class <%= class_name %>Service < <%= parent_class_name.classify %>
5 | <% params.each do |param| -%>
6 | option :<%= param %>, type: Types<%= param == 'params' ? '.Instance(ActionController::Parameters) | Types::Hash' : '::GiveMeValidType' %>
7 | <% end -%>
8 | option :contract, default: proc { <%= class_name %>Contract.new }
9 |
10 | def call
11 | contract_params = yield validate(contract)
12 | result = yield call_service(contract_params)
13 |
14 | Success(result)
15 | end
16 |
17 | private
18 |
19 | def call_service(contract_params)
20 | <%= class_name %>Service.new(params: contract_params).call
21 | end
22 | end
23 | <% end -%>
24 |
--------------------------------------------------------------------------------
/lib/generators/operation/templates/operation_test.rb.tt:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | <% module_namespacing do -%>
6 | class <%= class_name %>OperationTest < ActiveSupport::TestCase
7 | test 'should pass contract validation then calling the service' do
8 | params_mock = mock
9 | params_mock.expects(:to_h).returns(params_mock)
10 |
11 | contract_mock = mock
12 | <%= class_name %>Contract.expects(:new).returns(contract_mock)
13 | contract_mock.expects(:call).with(params_mock).returns(contract_mock)
14 | contract_mock.expects(:success?).returns(true)
15 | contract_mock.expects(:to_h).returns(contract_mock)
16 |
17 | service_mock = mock
18 | <%= class_name %>Service.expects(:new).returns(service_mock)
19 | service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true))
20 |
21 | operation = <%= class_name %>Operation.new(params: params_mock).call
22 |
23 | assert operation.success?
24 | end
25 |
26 | test 'should return errors if something goes wrong while validating params' do
27 | service_mock = mock
28 | <%= class_name %>Service.expects(:new).returns(service_mock).never
29 |
30 | operation = <%= class_name %>Operation.new(params: {}).call
31 |
32 | errors = contract_errors_parser(operation.failure)
33 |
34 | assert operation.failure?
35 | assert errors.keys.any?
36 | end
37 |
38 | test 'should return errors if something goes wrong while executing service' do
39 | params_mock = mock
40 | params_mock.expects(:to_h).returns(params_mock)
41 |
42 | contract_mock = mock
43 | <%= class_name %>Contract.expects(:new).returns(contract_mock)
44 | contract_mock.expects(:call).with(params_mock).returns(contract_mock)
45 | contract_mock.expects(:success?).returns(true)
46 | contract_mock.expects(:to_h).returns(contract_mock)
47 |
48 | service_mock = mock
49 | <%= class_name %>Service.expects(:new).returns(service_mock)
50 | service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me))
51 |
52 | operation = <%= class_name %>Operation.new(params: params_mock).call
53 |
54 | assert operation.failure?
55 | assert_equal :failed_because_of_me, operation.failure
56 | end
57 | end
58 | <% end -%>
59 |
--------------------------------------------------------------------------------
/lib/generators/service/USAGE:
--------------------------------------------------------------------------------
1 | Description:
2 | Creates a new service
3 |
4 | Example:
5 | bin/rails generate service Facebook::Ad current_user params
6 |
7 | This will create:
8 | app/services/facebook/ad/create_service.rb
9 | test/services/facebook/ad/create_service_test.rb
10 |
--------------------------------------------------------------------------------
/lib/generators/service/service_generator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class ServiceGenerator < Rails::Generators::NamedBase
4 | argument :params, type: :array, default: [], banner: 'action action'
5 | class_option :parent, type: :string, default: 'ApplicationService', desc: 'The parent class for the generated service'
6 |
7 | check_class_collision suffix: 'Service'
8 |
9 | source_root File.expand_path('templates', __dir__)
10 |
11 | def create_service_files
12 | template 'service.rb', File.join('app/services/', class_path, "#{file_name}_service.rb")
13 | template 'service_test.rb', File.join('test/services/', class_path, "#{file_name}_service_test.rb")
14 |
15 | return unless Rails.env.development?
16 |
17 | system("rubocop -A #{service_file_path}")
18 | system("rubocop -A #{service_test_file_path}")
19 | end
20 |
21 | private
22 |
23 | def service_file_path
24 | "app/services/#{class_path.join('/')}/#{file_name}_service.rb"
25 | end
26 |
27 | def service_test_file_path
28 | "test/services/#{class_path.join('/')}/#{file_name}_service_test.rb"
29 | end
30 |
31 | def parent_class_name
32 | options[:parent]
33 | end
34 |
35 | def file_name
36 | @_file_name ||= remove_possible_suffix(super)
37 | end
38 |
39 | def remove_possible_suffix(name)
40 | name.sub(/_?service$/i, '')
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/lib/generators/service/templates/service.rb.tt:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | <% module_namespacing do -%>
4 | class <%= class_name %>Service < <%= parent_class_name.classify %>
5 | <% params.each do |param| -%>
6 | option :<%= param %>, type: <%= param == 'params' ? 'Types::Hash' : 'Types::GiveMeValidType' %>
7 | <% end -%>
8 |
9 | def call
10 | result = yield call_something
11 |
12 | Success(result)
13 | end
14 |
15 | private
16 |
17 | def call_something
18 | # must be return Success or Failure
19 |
20 | # call some service ex: Foo::Bar.new(params).call
21 | # or manual ex: Success(Record.all)
22 | end
23 | end
24 | <% end -%>
25 |
--------------------------------------------------------------------------------
/lib/generators/service/templates/service_test.rb.tt:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | <% module_namespacing do -%>
6 | class <%= class_name %>ServiceTest < ActiveSupport::TestCase
7 | def setup; end
8 |
9 | test 'should ...' do
10 | assert_changes -> { something } do
11 | service = <%= class_name %>Service.new.call
12 |
13 | assert service.success?
14 | end
15 | end
16 |
17 | test 'should ...' do
18 | assert_no_changes -> { something } do
19 | service = <%= class_name %>Service.new.call
20 |
21 | assert service.failure?
22 | end
23 | end
24 | end
25 | <% end -%>
26 |
--------------------------------------------------------------------------------
/lib/supports/application_contract/error_parser.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Supports
4 | module ApplicationContract
5 | module ErrorParser
6 | TEST_KEYS_SPLITTER = '_of_'
7 |
8 | def contract_error_parser(contract_result, contract)
9 | errors = contract_result.errors.to_h
10 | total_errors = []
11 |
12 | errors.each do |key, val|
13 | total_errors.push(extracts_errors(contract, key, val))
14 | end
15 |
16 | { errors: total_errors.flatten }
17 | end
18 |
19 | private
20 |
21 | def extracts_errors(contract, key, val, top_keys = [])
22 | return handle_error(top_keys.concat([key]), val.first) if val.is_a?(Array)
23 |
24 | val.map do |k, v|
25 | extracts_errors(contract, k, v, top_keys.concat([key]))
26 | end
27 | end
28 |
29 | def handle_error(keys, val)
30 | if Rails.env.test?
31 | {
32 | "#{keys.uniq.reverse.join(TEST_KEYS_SPLITTER).to_sym}": val
33 | }
34 | else
35 | localize_error_message(keys, val)
36 | end
37 | end
38 |
39 | def localize_error_message(keys, val)
40 | case I18n.locale
41 | when :tr
42 | "#{localize_params(keys).reverse.join(keys_splitter)} #{val}"
43 | when :en
44 | "#{localize_params(keys).join(keys_splitter)} #{val}"
45 | else
46 | raise "Unsupported locale #{I18n.locale}"
47 | end
48 | end
49 |
50 | def localize_params(keys)
51 | case I18n.locale
52 | when :tr
53 | keys.map { |k| I18n.t("contracts.params.#{k}").capitalize }
54 | when :en
55 | keys.map(&:capitalize)
56 | else
57 | raise "Unsupported locale #{I18n.locale}"
58 | end
59 | end
60 |
61 | def format_contract_name(contract)
62 | contract.class.name.parameterize.tr('-', '.')
63 | end
64 |
65 | def keys_splitter(locale = I18n.locale)
66 | I18n.t('dry_validation.keys_splitter', locale:)
67 | end
68 | end
69 | end
70 | end
71 |
--------------------------------------------------------------------------------
/lib/supports/application_contract/i18n.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Supports
4 | module ApplicationContract
5 | module I18n
6 | # rubocop:disable Rails/Output
7 | def check_i18n_translations_for(errors, locale)
8 | params = errors.keys.map { |k| k.to_s.split(Supports::ApplicationContract::ErrorParser::TEST_KEYS_SPLITTER) }.flatten
9 |
10 | params.each do |param|
11 | translation = ::I18n.t("contracts.params.#{param}", locale:)
12 | ap(translation, { color: { string: :red } }) if translation.include?('translation missing')
13 | end
14 | end
15 | # rubocop:enable Rails/Output
16 |
17 | def check_i18n_translations(errors)
18 | ::I18n.available_locales.each do |locale|
19 | next if locale == :en
20 |
21 | check_i18n_translations_for(errors, locale)
22 | end
23 | end
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/supports/application_operation/helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Supports
4 | module ApplicationOperation
5 | module Helper
6 | def validate(contract)
7 | result = contract.call(params.to_h)
8 |
9 | return contract_success(result) if result.success?
10 |
11 | contract_failure(result, contract)
12 | end
13 |
14 | def contract_failure(result, contract)
15 | Failure(contract_error_parser(result, contract))
16 | end
17 |
18 | def contract_success(result)
19 | Success(result.to_h)
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/supports/application_service/helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Supports
4 | module ApplicationService
5 | module Helper
6 | def resource_failure(resource)
7 | custom_resource_failure(resource.errors.full_messages)
8 | end
9 |
10 | def custom_resource_failure(errors)
11 | Failure({ errors: })
12 | end
13 |
14 | def create_resource(klass)
15 | record = klass.new(params)
16 |
17 | return Success(record) if record.save
18 |
19 | resource_failure(record)
20 | end
21 |
22 | def update_resource(record)
23 | return Success(record) if record.update(params)
24 |
25 | resource_failure(resource)
26 | end
27 |
28 | def destroy_resource(record)
29 | return Success(record) if record.destroy
30 |
31 | resource_failure(resource)
32 | end
33 |
34 | def find_resource(klass:, error_message: nil, **args)
35 | record = klass.find_by(args)
36 |
37 | return Success(record) if record.present?
38 |
39 | message = error_message || I18n.t('application_service.default.find_resource.error', klass: klass.to_s)
40 |
41 | custom_resource_failure([message])
42 | end
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/supports/doorkeeper/custom_error_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # https://github.com/doorkeeper-gem/doorkeeper/blob/main/lib/doorkeeper/oauth/error_response.rb
4 | module Supports
5 | module Doorkeeper
6 | module CustomErrorResponse
7 | def body
8 | {
9 | errors: custom_errors
10 | }
11 | end
12 |
13 | def status
14 | if @error.name == :invalid_client || @error.name == :unauthorized_client
15 | :unauthorized
16 | else
17 | :bad_request
18 | end
19 | end
20 |
21 | private
22 |
23 | def custom_errors
24 | [
25 | if @error.name == :invalid_client || @error.name == :unauthorized_client
26 | I18n.t('doorkeeper.errors.messages.invalid_client')
27 | else
28 | I18n.t('devise.failure.invalid', authentication_keys: User.authentication_keys.join('/'))
29 | end
30 | ]
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/lib/supports/doorkeeper/custom_register_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # https://github.com/doorkeeper-gem/doorkeeper/blob/main/lib/doorkeeper/oauth/token_response.rb
4 | module Supports
5 | module Doorkeeper
6 | module CustomRegisterResponse
7 | def body(user, access_token, token_type = 'Bearer')
8 | {
9 | user: {
10 | id: user.id,
11 | type: user.class.name,
12 | email: user.email
13 | },
14 | access_token: access_token.token,
15 | token_type:,
16 | expires_in: access_token.expires_in,
17 | refresh_token: access_token.refresh_token,
18 | created_at: access_token.created_at.to_time.to_i
19 | }
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/supports/doorkeeper/custom_token_response.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # https://github.com/doorkeeper-gem/doorkeeper/blob/main/lib/doorkeeper/oauth/token_response.rb
4 | module Supports
5 | module Doorkeeper
6 | module CustomTokenResponse
7 | def body
8 | current_user = @token.resource_owner
9 |
10 | additional_data = {
11 | user: {
12 | id: current_user.id,
13 | type: current_user.class.name,
14 | email: current_user.email
15 | }
16 | }
17 |
18 | super.merge(additional_data)
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/supports/sidekiq/helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | # https://github.com/doorkeeper-gem/doorkeeper/blob/main/lib/doorkeeper/oauth/token_response.rb
4 | module Supports
5 | module Sidekiq
6 | module Helper
7 | def job_params_handler(**params)
8 | data = (params || {}).to_json
9 |
10 | JSON.parse(data)
11 | end
12 |
13 | def updated_resource_payload(newest, oldest)
14 | old_attributes = newest.previous_changes.except(:updated_at).transform_values(&:first)
15 | new_attributes = oldest.attributes.slice(*old_attributes.keys)
16 |
17 | job_params_handler(new_attributes:, old_attributes:)
18 | end
19 | end
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/lib/tasks/.keep
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/log/.keep
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/storage/.keep
--------------------------------------------------------------------------------
/test/channels/application_cable/connection_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase
6 | # test "connects with cookies" do
7 | # cookies.signed[:user_id] = 42
8 | #
9 | # connect
10 | #
11 | # assert_equal connection.user_id, "42"
12 | # end
13 | end
14 |
--------------------------------------------------------------------------------
/test/contracts/users/passwords/send_instructions_contract_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Passwords
7 | class SendInstructionsContractTest < ActiveSupport::TestCase
8 | CONTRACT = Users::Passwords::SendInstructionsContract.new
9 |
10 | def validate(payload = {})
11 | CONTRACT.call(payload)
12 | end
13 |
14 | test 'validate#email' do
15 | success?(validate({ email: 'test@test.com' }), :email, CONTRACT)
16 | filled?(validate({ email: nil }), :email, CONTRACT)
17 | filled?(validate({ email: '' }), :email, CONTRACT)
18 | format?(validate({ email: 'invalid email' }), :email, CONTRACT)
19 | end
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/contracts/users/passwords/update_contract_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Passwords
7 | class UpdateContractTest < ActiveSupport::TestCase
8 | CONTRACT = Users::Passwords::UpdateContract.new
9 |
10 | def validate(payload = {})
11 | CONTRACT.call(payload)
12 | end
13 |
14 | test 'validate#reset_password_token' do
15 | success?(validate({ reset_password_token: 'token' }), :reset_password_token, CONTRACT)
16 | filled?(validate({ reset_password_token: nil }), :reset_password_token, CONTRACT)
17 | filled?(validate({ reset_password_token: '' }), :reset_password_token, CONTRACT)
18 | str?(validate({ reset_password_token: 1 }), :reset_password_token, CONTRACT)
19 | end
20 |
21 | test 'validate#password' do
22 | success?(validate({ password: '123456', password_confirmation: '123456' }), :password, CONTRACT)
23 | filled?(validate({ password: nil }), :password, CONTRACT)
24 | filled?(validate({ password: '' }), :password, CONTRACT)
25 | min_size?(validate({ password: '12345' }), :password, 6, CONTRACT)
26 | same_password?(validate({ password: '1234566', password_confirmation: '123456' }), :password, CONTRACT)
27 | end
28 |
29 | test 'validate#password_confirmation' do
30 | success?(validate({ password_confirmation: '123456', password: '123456' }), :password_confirmation, CONTRACT)
31 | filled?(validate({ password_confirmation: nil }), :password_confirmation, CONTRACT)
32 | filled?(validate({ password_confirmation: '' }), :password_confirmation, CONTRACT)
33 | min_size?(validate({ password_confirmation: '12345' }), :password_confirmation, 6, CONTRACT)
34 | same_password?(validate({ password_confirmation: '1234566', password: '123456' }), :password_confirmation, CONTRACT)
35 | end
36 | end
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/test/contracts/users/registrations/register_contract_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Registrations
7 | class RegisterContractTest < ActiveSupport::TestCase
8 | CONTRACT = Users::Registrations::RegisterContract.new
9 |
10 | def validate(payload = {})
11 | CONTRACT.call(payload)
12 | end
13 |
14 | test 'validate#email' do
15 | success?(validate({ email: 'test@test.com' }), :email, CONTRACT)
16 | filled?(validate({ email: nil }), :email, CONTRACT)
17 | filled?(validate({ email: '' }), :email, CONTRACT)
18 | format?(validate({ email: 'invalid email' }), :email, CONTRACT)
19 | end
20 |
21 | test 'validate#password' do
22 | success?(validate({ password: '123456' }), :password, CONTRACT)
23 | filled?(validate({ password: nil }), :password, CONTRACT)
24 | filled?(validate({ password: '' }), :password, CONTRACT)
25 | min_size?(validate({ password: '12345' }), :password, 6, CONTRACT)
26 | end
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/test/controllers/swagger_controller_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class SwaggerControllerTest < ActionDispatch::IntegrationTest
4 | test 'should get swagger ui' do
5 | get swagger_root_url
6 | assert_response :success
7 | end
8 |
9 | test 'should get swagger data [V1]' do
10 | get swagger_v1_data_url
11 | assert_response :success
12 | end
13 |
14 | test 'should get swagger data [V2]' do
15 | get swagger_v2_data_url
16 | assert_response :success
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/test/controllers/v1/users/passwords_controller_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module V1
6 | module Users
7 | class PasswordsControllerTest < ActionDispatch::IntegrationTest
8 | attr_reader :doorkeeper_application, :user
9 |
10 | def setup
11 | @doorkeeper_application = create(:doorkeeper_application)
12 | @user = create(:user)
13 | end
14 |
15 | test 'create#should send an email then returns an information message' do
16 | params = { email: user.email,
17 | client_id: doorkeeper_application.uid,
18 | client_secret: doorkeeper_application.secret }
19 |
20 | assert_difference('Devise.mailer.deliveries.count') do
21 | post(v1_users_password_url, params:, as: :json)
22 | end
23 |
24 | body = load_body(response)
25 |
26 | assert_equal I18n.t('devise.passwords.send_instructions'), body.message
27 | assert_response :ok
28 | end
29 |
30 | test 'create#should return errors if oauth client id is invalid' do
31 | params = { email: user.email,
32 | client_id: 'invalid',
33 | client_secret: doorkeeper_application.secret }
34 |
35 | assert_no_difference('Devise.mailer.deliveries.count') do
36 | post(v1_users_password_url, params:, as: :json)
37 | end
38 |
39 | assert_equal 1, errors_count(response)
40 | error_message?(response, I18n.t('doorkeeper.errors.messages.invalid_client'))
41 | assert_response :unauthorized
42 | end
43 |
44 | test 'create#should return errors if oauth client secret is invalid' do
45 | params = { email: user.email,
46 | client_id: doorkeeper_application.uid,
47 | client_secret: 'invalid' }
48 |
49 | assert_no_difference('Devise.mailer.deliveries.count') do
50 | post(v1_users_password_url, params:, as: :json)
51 | end
52 |
53 | assert_equal 1, errors_count(response)
54 | error_message?(response, I18n.t('doorkeeper.errors.messages.invalid_client'))
55 | assert_response :unauthorized
56 | end
57 |
58 | test 'create#should not send an email then returns errors if email was not found' do
59 | params = { email: 'not@registered.com',
60 | client_id: doorkeeper_application.uid,
61 | client_secret: doorkeeper_application.secret }
62 |
63 | assert_no_difference('Devise.mailer.deliveries.count') do
64 | post(v1_users_password_url, params:, as: :json)
65 | end
66 |
67 | error_message?(response, 'Email not found')
68 | assert_equal 1, errors_count(response)
69 | assert_response :unprocessable_entity
70 | end
71 |
72 | test 'update#should update password' do
73 | token = user.send_reset_password_instructions
74 | params = { reset_password_token: token,
75 | password: user.password,
76 | password_confirmation: user.password,
77 | client_id: doorkeeper_application.uid,
78 | client_secret: doorkeeper_application.secret }
79 |
80 | assert_changes -> { user.updated_at } do
81 | patch(v1_users_password_url, params:, as: :json)
82 |
83 | user.reload
84 | end
85 |
86 | body = load_body(response)
87 |
88 | assert_equal I18n.t('devise.passwords.updated_not_active'), body.message
89 | assert_response :ok
90 | end
91 |
92 | test 'update#should return errors if oauth client id is invalid' do
93 | token = user.send_reset_password_instructions
94 | params = { reset_password_token: token,
95 | password: user.password,
96 | password_confirmation: user.password,
97 | client_id: 'invalid',
98 | client_secret: doorkeeper_application.secret }
99 |
100 | assert_no_difference('Devise.mailer.deliveries.count') do
101 | patch(v1_users_password_url, params:, as: :json)
102 | end
103 |
104 | assert_equal 1, errors_count(response)
105 | error_message?(response, I18n.t('doorkeeper.errors.messages.invalid_client'))
106 | assert_response :unauthorized
107 | end
108 |
109 | test 'update#should return errors if oauth client secret is invalid' do
110 | token = user.send_reset_password_instructions
111 | params = { reset_password_token: token,
112 | password: user.password,
113 | password_confirmation: user.password,
114 | client_id: doorkeeper_application.uid,
115 | client_secret: 'invalid' }
116 |
117 | assert_no_difference('Devise.mailer.deliveries.count') do
118 | patch(v1_users_password_url, params:, as: :json)
119 | end
120 |
121 | assert_equal 1, errors_count(response)
122 | error_message?(response, I18n.t('doorkeeper.errors.messages.invalid_client'))
123 | assert_response :unauthorized
124 | end
125 |
126 | test 'update#should not update password with invalid token' do
127 | user.send_reset_password_instructions
128 | params = { reset_password_token: 'so_secret_token',
129 | password: user.password,
130 | password_confirmation: user.password,
131 | client_id: doorkeeper_application.uid,
132 | client_secret: doorkeeper_application.secret }
133 |
134 | assert_no_changes -> { user.updated_at } do
135 | patch(v1_users_password_url, params:, as: :json)
136 |
137 | user.reload
138 | end
139 |
140 | assert error_message?(response, 'Reset password token is invalid')
141 | assert_equal 1, errors_count(response)
142 | assert_response :unprocessable_entity
143 | end
144 | end
145 | end
146 | end
147 |
--------------------------------------------------------------------------------
/test/controllers/v1/users/registrations_controller_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module V1
6 | module Users
7 | class RegistrationsControllerTest < ActionDispatch::IntegrationTest
8 | attr_reader :doorkeeper_application
9 |
10 | def setup
11 | @doorkeeper_application = create(:doorkeeper_application)
12 | end
13 |
14 | test 'create#should register and then generate access token' do
15 | params = { email: 'tester@mail.com',
16 | password: '123456',
17 | client_id: doorkeeper_application.uid,
18 | client_secret: doorkeeper_application.secret }
19 |
20 | assert_difference(['Doorkeeper::AccessToken.count', 'User.count'], 1) do
21 | post(v1_users_registration_url, params:, as: :json)
22 | end
23 |
24 | body = load_body(response)
25 |
26 | assert_equal params[:email], body.user.email
27 | assert_response :created
28 | end
29 |
30 | test 'create#should not register if email has already been taken' do
31 | old_user = create(:user)
32 | params = { email: old_user.email,
33 | password: '123456',
34 | client_id: doorkeeper_application.uid,
35 | client_secret: doorkeeper_application.secret }
36 |
37 | assert_no_difference(['Doorkeeper::AccessToken.count', 'User.count'], 1) do
38 | post(v1_users_registration_url, params:, as: :json)
39 | end
40 |
41 | assert_equal 1, errors_count(response)
42 | assert error_message?(response, 'Email has already been taken')
43 | assert_response :unprocessable_entity
44 | end
45 |
46 | test 'create#should return errors if oauth client id is invalid' do
47 | params = { email: 'tester@mail.com',
48 | password: '123456',
49 | client_id: 'invalid',
50 | client_secret: doorkeeper_application.secret }
51 |
52 | assert_no_difference(['Doorkeeper::AccessToken.count', 'User.count'], 1) do
53 | post(v1_users_registration_url, params:, as: :json)
54 | end
55 |
56 | assert error_message?(response, I18n.t('doorkeeper.errors.messages.invalid_client'))
57 | assert_equal 1, errors_count(response)
58 | assert_response :unauthorized
59 | end
60 |
61 | test 'create#should return errors if oauth client secret is invalid' do
62 | params = { email: 'tester@mail.com',
63 | password: '123456',
64 | client_id: doorkeeper_application.uid,
65 | client_secret: 'invalid' }
66 |
67 | assert_no_difference(['Doorkeeper::AccessToken.count', 'User.count'], 1) do
68 | post(v1_users_registration_url, params:, as: :json)
69 | end
70 |
71 | assert error_message?(response, I18n.t('doorkeeper.errors.messages.invalid_client'))
72 | assert_equal 1, errors_count(response)
73 | assert_response :unauthorized
74 | end
75 | end
76 | end
77 | end
78 |
--------------------------------------------------------------------------------
/test/controllers/v1/users/tokens_controller_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module V1
6 | module Users
7 | class TokensControllerTest < ActionDispatch::IntegrationTest
8 | attr_reader :user, :doorkeeper_application, :token
9 |
10 | def setup
11 | @user = create(:user)
12 | @doorkeeper_application = create(:doorkeeper_application)
13 | @token = create(:doorkeeper_access_token, application: @doorkeeper_application, resource_owner: @user)
14 | end
15 |
16 | test 'create#should generate new access and refresh tokens' do
17 | assert_difference('Doorkeeper::AccessToken.count') do
18 | post(v1_users_oauth_token_url,
19 | params: oauth_token_params(user, doorkeeper_application),
20 | as: :json)
21 | end
22 |
23 | body = load_body(response)
24 |
25 | assert_respond_to body, :access_token
26 | assert_respond_to body, :refresh_token
27 | assert_respond_to body, :token_type
28 | assert_respond_to body, :expires_in
29 | assert_respond_to body, :created_at
30 | assert_respond_to body.user, :email
31 | assert_respond_to body.user, :type
32 | assert_respond_to body.user, :id
33 | assert_response :success
34 | end
35 |
36 | test 'create#should fail client id is invalid' do
37 | params = { grant_type: 'password',
38 | email: user.email,
39 | password: user.password,
40 | client_id: 'invalid',
41 | client_secret: doorkeeper_application.secret }
42 |
43 | assert_no_difference('Doorkeeper::AccessToken.count') do
44 | post(v1_users_oauth_token_url, params:, as: :json)
45 | end
46 |
47 | assert error_message?(response, I18n.t('doorkeeper.errors.messages.invalid_client'))
48 | assert_equal 1, errors_count(response)
49 | assert_response :unauthorized
50 | end
51 |
52 | test 'create#should fail client secret is invalid' do
53 | params = { grant_type: 'password',
54 | email: user.email,
55 | password: user.password,
56 | client_id: doorkeeper_application.uid,
57 | client_secret: 'invalid' }
58 |
59 | assert_no_difference('Doorkeeper::AccessToken.count') do
60 | post(v1_users_oauth_token_url, params:, as: :json)
61 | end
62 |
63 | assert error_message?(response, I18n.t('doorkeeper.errors.messages.invalid_client'))
64 | assert_equal 1, errors_count(response)
65 | assert_response :unauthorized
66 | end
67 |
68 | test 'create#should not generate new access and refresh tokens if user credentials is invalid' do
69 | invalid_user = build(:user)
70 |
71 | assert_no_difference('Doorkeeper::AccessToken.count') do
72 | post(v1_users_oauth_token_url,
73 | params: oauth_token_params(invalid_user, doorkeeper_application),
74 | as: :json)
75 | end
76 |
77 | assert error_message?(response, I18n.t('devise.failure.invalid', authentication_keys: User.authentication_keys.join('/')))
78 | assert_equal 1, errors_count(response)
79 | assert_response :bad_request
80 | end
81 |
82 | test 'create#should generate new access and refresh tokens with refresh token' do
83 | assert_difference('Doorkeeper::AccessToken.count') do
84 | post(v1_users_oauth_token_url,
85 | params: oauth_refresh_token_params(token),
86 | as: :json)
87 | end
88 |
89 | body = load_body(response)
90 |
91 | assert_respond_to body, :access_token
92 | assert_respond_to body, :refresh_token
93 | assert_respond_to body, :token_type
94 | assert_respond_to body, :expires_in
95 | assert_respond_to body, :created_at
96 | assert_response :success
97 | end
98 |
99 | test 'create#should not generate new access and refresh tokens if refresh token is invalid' do
100 | assert_no_difference('Doorkeeper::AccessToken.count') do
101 | post(v1_users_oauth_token_url,
102 | params: oauth_refresh_token_params(token, 'token'))
103 | end
104 |
105 | assert error_message?(response, I18n.t('devise.failure.invalid', authentication_keys: User.authentication_keys.join('/')))
106 | assert_equal 1, errors_count(response)
107 | assert_response :bad_request
108 | end
109 |
110 | test 'revoke#should revoke access token' do
111 | assert_changes -> { token.revoked_at } do
112 | post(v1_users_oauth_revoke_url,
113 | params: oauth_revoke_params(token),
114 | as: :json)
115 |
116 | token.reload
117 | end
118 |
119 | assert_response :success
120 | end
121 |
122 | test 'revoke#should not revoke access token if token has already revoked' do
123 | token.revoke
124 |
125 | assert_no_changes -> { token.revoked_at } do
126 | post(v1_users_oauth_revoke_url, params: oauth_revoke_params(token), as: :json)
127 |
128 | token.reload
129 | end
130 |
131 | assert_response :success
132 | end
133 |
134 | test 'revoke#should fail client id is invalid' do
135 | params = { token: token.token,
136 | client_id: 'invalid',
137 | client_secret: doorkeeper_application.secret }
138 |
139 | assert_no_changes -> { token.revoked_at } do
140 | post(v1_users_oauth_revoke_url, params:, as: :json)
141 |
142 | token.reload
143 | end
144 |
145 | assert error_message?(response, I18n.t('doorkeeper.errors.messages.revoke.unauthorized'))
146 | assert_equal 1, errors_count(response)
147 | assert_response :forbidden
148 | end
149 |
150 | test 'revoke#should fail client secret is invalid' do
151 | params = { token: token.token,
152 | client_id: doorkeeper_application.uid,
153 | client_secret: 'invalid' }
154 |
155 | assert_no_changes -> { token.revoked_at } do
156 | post(v1_users_oauth_revoke_url, params:, as: :json)
157 |
158 | token.reload
159 | end
160 |
161 | assert error_message?(response, I18n.t('doorkeeper.errors.messages.revoke.unauthorized'))
162 | assert_equal 1, errors_count(response)
163 | assert_response :forbidden
164 | end
165 | end
166 | end
167 | end
168 |
--------------------------------------------------------------------------------
/test/factories/doorkeeper/doorkeeper_access_tokens_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :doorkeeper_access_token, class: 'Doorkeeper::AccessToken' do
5 | association :application, factory: :doorkeeper_application
6 | expires_in { 1.hour }
7 | association :resource_owner, factory: :user
8 | sequence :refresh_token do |n|
9 | SecureRandom.hex(32) + n.to_s
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/test/factories/doorkeeper/doorkeeper_applications_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :doorkeeper_application, class: 'Doorkeeper::Application' do
5 | name { Faker::App.name }
6 | redirect_uri { '' }
7 | scopes { '' }
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/test/factories/users_factory.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | FactoryBot.define do
4 | factory :user do
5 | email { Faker::Internet.email }
6 | password { Faker::Internet.password(min_length: 6) }
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/test/integration/.keep
--------------------------------------------------------------------------------
/test/lib/custom_objects/parameter_object/pagination_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module CustomObjects
6 | module ParameterObject
7 | class PaginationTest < ActiveSupport::TestCase
8 | test 'default values' do
9 | pagination = CustomObjects::ParameterObject::Pagination.new
10 |
11 | assert_equal 1, pagination.page
12 | assert_equal 25, pagination.per_page
13 | end
14 |
15 | test 'page' do
16 | pagination = CustomObjects::ParameterObject::Pagination.new(page: 2)
17 | pagination_coercible = CustomObjects::ParameterObject::Pagination.new(page: '2')
18 |
19 | assert_equal 2, pagination.page
20 | assert_equal 2, pagination_coercible.page
21 | end
22 |
23 | test 'per page' do
24 | pagination = CustomObjects::ParameterObject::Pagination.new(per_page: 2)
25 | pagination_coercible = CustomObjects::ParameterObject::Pagination.new(per_page: '2')
26 |
27 | assert_equal 2, pagination.per_page
28 | assert_equal 2, pagination_coercible.per_page
29 | end
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/test/lib/custom_objects/parameter_object/query_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module CustomObjects
6 | module ParameterObject
7 | class QueryTest < ActiveSupport::TestCase
8 | test 'default values' do
9 | query = CustomObjects::ParameterObject::Query.new
10 |
11 | assert_empty query.query
12 | end
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/test/lib/generators/contract_generator_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 | require 'generators/contract/contract_generator'
5 |
6 | class ContractGeneratorTest < Rails::Generators::TestCase
7 | tests ContractGenerator
8 | destination Rails.root.join('tmp/generators')
9 | setup :prepare_destination
10 |
11 | test 'generator runs without errors' do
12 | assert_nothing_raised do
13 | run_generator ['Customer']
14 | end
15 | end
16 |
17 | test 'generator creates contract' do
18 | run_generator ['Customer::Create']
19 |
20 | assert_file 'app/contracts/customer/create_contract.rb'
21 | assert_file 'test/contracts/customer/create_contract_test.rb'
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/test/lib/generators/operation_generator_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 | require 'generators/operation/operation_generator'
5 |
6 | class OperationGeneratorTest < Rails::Generators::TestCase
7 | tests OperationGenerator
8 | destination Rails.root.join('tmp/generators')
9 | setup :prepare_destination
10 |
11 | test 'generator runs without errors' do
12 | assert_nothing_raised do
13 | run_generator ['Seller']
14 | end
15 | end
16 |
17 | test 'generator creates operation files' do
18 | run_generator ['Seller::Create']
19 |
20 | assert_file 'app/operations/seller/create_operation.rb'
21 | assert_file 'test/operations/seller/create_operation_test.rb'
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/test/lib/generators/service_generator_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 | require 'generators/service/service_generator'
5 |
6 | class ServiceGeneratorTest < Rails::Generators::TestCase
7 | tests ServiceGenerator
8 | destination Rails.root.join('tmp/generators')
9 | setup :prepare_destination
10 |
11 | test 'generator runs without errors' do
12 | assert_nothing_raised do
13 | run_generator ['Seller']
14 | end
15 | end
16 |
17 | test 'generator creates service files' do
18 | run_generator ['Seller::Create']
19 |
20 | assert_file 'app/services/seller/create_service.rb'
21 | assert_file 'test/services/seller/create_service_test.rb'
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/test/mailers/.keep
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | class UserTest < ActiveSupport::TestCase
6 | test 'should have a valid factory' do
7 | assert create(:user).persisted?
8 | end
9 |
10 | context '#indexes' do
11 | should have_db_index(:email).unique(true)
12 | should have_db_index(:reset_password_token).unique(true)
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/test/operations/users/passwords/create_operation_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Passwords
7 | class CreateOperationTest < ActiveSupport::TestCase
8 | test 'should pass contract validation then calling the service' do
9 | params_mock = mock
10 | params_mock.expects(:to_h).returns(params_mock)
11 |
12 | contract_mock = mock
13 | Users::Passwords::SendInstructionsContract.expects(:new).returns(contract_mock)
14 | contract_mock.expects(:call).with(params_mock).returns(contract_mock)
15 | contract_mock.expects(:success?).returns(true)
16 | contract_mock.expects(:to_h).returns(contract_mock)
17 |
18 | service_mock = mock
19 | Users::Passwords::SendInstructionsService.expects(:new).returns(service_mock)
20 | service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true))
21 |
22 | operation = Users::Passwords::CreateOperation.new(params: params_mock).call
23 |
24 | assert operation.success?
25 | end
26 |
27 | test 'should return errors if something goes wrong while validating params' do
28 | service_mock = mock
29 | Users::Passwords::SendInstructionsService.expects(:new).returns(service_mock).never
30 |
31 | operation = Users::Passwords::CreateOperation.new(params: {}).call
32 |
33 | errors = contract_errors_parser(operation.failure)
34 |
35 | assert operation.failure?
36 | assert_equal errors[:email], 'is missing'
37 | end
38 |
39 | test 'should return errors if something goes wrong while executing service' do
40 | params_mock = mock
41 | params_mock.expects(:to_h).returns(params_mock)
42 |
43 | contract_mock = mock
44 | Users::Passwords::SendInstructionsContract.expects(:new).returns(contract_mock)
45 | contract_mock.expects(:call).with(params_mock).returns(contract_mock)
46 | contract_mock.expects(:success?).returns(true)
47 | contract_mock.expects(:to_h).returns(contract_mock)
48 |
49 | service_mock = mock
50 | Users::Passwords::SendInstructionsService.expects(:new).returns(service_mock)
51 | service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me))
52 |
53 | operation = Users::Passwords::CreateOperation.new(params: params_mock).call
54 |
55 | assert operation.failure?
56 | assert_equal :failed_because_of_me, operation.failure
57 | end
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/test/operations/users/passwords/update_operation_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Passwords
7 | class UpdateOperationTest < ActiveSupport::TestCase
8 | test 'should pass contract validation then calling the service' do
9 | params_mock = mock
10 | params_mock.expects(:to_h).returns(params_mock)
11 |
12 | contract_mock = mock
13 | Users::Passwords::UpdateContract.expects(:new).returns(contract_mock)
14 | contract_mock.expects(:call).with(params_mock).returns(contract_mock)
15 | contract_mock.expects(:success?).returns(true)
16 | contract_mock.expects(:to_h).returns(contract_mock)
17 |
18 | service_mock = mock
19 | Users::Passwords::UpdateService.expects(:new).returns(service_mock)
20 | service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true))
21 |
22 | operation = Users::Passwords::UpdateOperation.new(params: params_mock).call
23 |
24 | assert operation.success?
25 | end
26 |
27 | test 'should return errors if something goes wrong while validating params' do
28 | service_mock = mock
29 | Users::Passwords::UpdateService.expects(:new).returns(service_mock).never
30 |
31 | operation = Users::Passwords::UpdateOperation.new(params: {}).call
32 |
33 | errors = contract_errors_parser(operation.failure)
34 |
35 | assert operation.failure?
36 | assert_equal errors[:password], 'is missing'
37 | assert_equal errors[:password_confirmation], 'is missing'
38 | end
39 |
40 | test 'should return errors if something goes wrong while executing service' do
41 | params_mock = mock
42 | params_mock.expects(:to_h).returns(params_mock)
43 |
44 | contract_mock = mock
45 | Users::Passwords::UpdateContract.expects(:new).returns(contract_mock)
46 | contract_mock.expects(:call).with(params_mock).returns(contract_mock)
47 | contract_mock.expects(:success?).returns(true)
48 | contract_mock.expects(:to_h).returns(contract_mock)
49 |
50 | service_mock = mock
51 | Users::Passwords::UpdateService.expects(:new).returns(service_mock)
52 | service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me))
53 |
54 | operation = Users::Passwords::UpdateOperation.new(params: params_mock).call
55 |
56 | assert operation.failure?
57 | assert_equal :failed_because_of_me, operation.failure
58 | end
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/test/operations/users/registrations/create_operation_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Registrations
7 | class CreateOperationTest < ActiveSupport::TestCase
8 | attr_reader :doorkeeper_application
9 |
10 | def setup
11 | @doorkeeper_application = create(:doorkeeper_application)
12 | end
13 |
14 | test 'should pass contract validation then calling the service' do
15 | params_mock = mock
16 | params_mock.expects(:to_h).returns(params_mock)
17 |
18 | contract_mock = mock
19 | Users::Registrations::RegisterContract.expects(:new).returns(contract_mock)
20 | contract_mock.expects(:call).with(params_mock).returns(contract_mock)
21 | contract_mock.expects(:success?).returns(true)
22 | contract_mock.expects(:to_h).returns(contract_mock)
23 |
24 | service_mock = mock
25 | Users::Registrations::RegisterService.expects(:new).returns(service_mock)
26 | service_mock.expects(:call).returns(Dry::Monads::Result::Success.new(true))
27 |
28 | operation = Users::Registrations::CreateOperation.new(params: params_mock, doorkeeper_application:).call
29 |
30 | assert operation.success?
31 | end
32 |
33 | test 'should return errors if something goes wrong while validating params' do
34 | service_mock = mock
35 | Users::Registrations::RegisterService.expects(:new).returns(service_mock).never
36 |
37 | operation = Users::Registrations::CreateOperation.new(params: {}, doorkeeper_application:).call
38 |
39 | errors = contract_errors_parser(operation.failure)
40 |
41 | assert operation.failure?
42 | assert_equal errors[:email], 'is missing'
43 | assert_equal errors[:password], 'is missing'
44 | end
45 |
46 | test 'should return errors if something goes wrong while executing service' do
47 | params_mock = mock
48 | params_mock.expects(:to_h).returns(params_mock)
49 |
50 | contract_mock = mock
51 | Users::Registrations::RegisterContract.expects(:new).returns(contract_mock)
52 | contract_mock.expects(:call).with(params_mock).returns(contract_mock)
53 | contract_mock.expects(:success?).returns(true)
54 | contract_mock.expects(:to_h).returns(contract_mock)
55 |
56 | service_mock = mock
57 | Users::Registrations::RegisterService.expects(:new).returns(service_mock)
58 | service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me))
59 |
60 | operation = Users::Registrations::CreateOperation.new(params: params_mock, doorkeeper_application:).call
61 |
62 | assert operation.failure?
63 | assert_equal :failed_because_of_me, operation.failure
64 | end
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/test/services/doorkeeper/access_tokens/create_service_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Doorkeeper
6 | module AccessTokens
7 | class CreateServiceTest < ActiveSupport::TestCase
8 | attr_reader :doorkeeper_application, :user
9 |
10 | def setup
11 | @doorkeeper_application = create(:doorkeeper_application)
12 | @user = create(:user)
13 | end
14 |
15 | test 'should create doorkeeper access token' do
16 | assert_difference 'Doorkeeper::AccessToken.count', 1 do
17 | service = Doorkeeper::AccessTokens::CreateService.new(doorkeeper_application:, user:).call
18 |
19 | assert service.success?
20 | end
21 | end
22 |
23 | test 'should fail if something goes wrong' do
24 | Doorkeeper::AccessToken.any_instance.expects(:save).returns(false)
25 |
26 | assert_no_difference 'Doorkeeper::AccessToken.count' do
27 | service = Doorkeeper::AccessTokens::CreateService.new(doorkeeper_application:, user:).call
28 |
29 | assert service.failure?
30 | end
31 | end
32 | end
33 | end
34 | end
35 |
--------------------------------------------------------------------------------
/test/services/users/create_service_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | class CreateServiceTest < ActiveSupport::TestCase
7 | attr_reader :params
8 |
9 | def setup
10 | @params = attributes_for(:user)
11 | end
12 |
13 | test 'should create user' do
14 | assert_difference 'User.count', 1 do
15 | service = Users::CreateService.new(params:).call
16 |
17 | assert service.success?
18 | end
19 | end
20 |
21 | test 'should fail if something goes wrong' do
22 | User.any_instance.expects(:save).returns(false)
23 |
24 | assert_no_difference 'User.count' do
25 | service = Users::CreateService.new(params:).call
26 |
27 | assert service.failure?
28 | end
29 | end
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/test/services/users/passwords/send_instructions_service_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Passwords
7 | class SendInstructionsServiceTest < ActiveSupport::TestCase
8 | attr_reader :user
9 |
10 | def setup
11 | @user = create(:user)
12 | end
13 |
14 | test 'should send instructions to user' do
15 | params = { email: user.email }
16 |
17 | assert_changes -> { user.updated_at } do
18 | service = Users::Passwords::SendInstructionsService.new(params:).call
19 |
20 | user.reload
21 |
22 | assert service.success?
23 | assert_equal I18n.t('devise.passwords.send_instructions'), service.success[:message]
24 | end
25 | end
26 |
27 | test 'should not update password if token is invalid' do
28 | params = { email: 'test@development.com' }
29 |
30 | assert_no_changes -> { user.updated_at } do
31 | service = Users::Passwords::SendInstructionsService.new(params:).call
32 |
33 | user.reload
34 |
35 | assert service.failure?
36 | end
37 | end
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/test/services/users/passwords/update_service_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Passwords
7 | class UpdateServiceTest < ActiveSupport::TestCase
8 | attr_reader :user
9 |
10 | def setup
11 | @user = create(:user)
12 | end
13 |
14 | test 'should update password successfully' do
15 | reset_password_token = user.send_reset_password_instructions
16 |
17 | params = { reset_password_token:,
18 | password: '123456',
19 | password_confirmation: '123456' }
20 |
21 | assert_changes -> { user.updated_at } do
22 | service = Users::Passwords::UpdateService.new(params:).call
23 |
24 | user.reload
25 |
26 | assert service.success?
27 | assert_equal I18n.t('devise.passwords.updated_not_active'), service.success[:message]
28 | end
29 | end
30 |
31 | test 'should not update password if token is invalid' do
32 | params = { reset_password_token: 'invalid',
33 | password: '123456',
34 | password_confirmation: '123456' }
35 |
36 | assert_no_changes -> { user.updated_at } do
37 | service = Users::Passwords::UpdateService.new(params:).call
38 |
39 | user.reload
40 |
41 | assert service.failure?
42 | end
43 | end
44 | end
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/test/services/users/registrations/register_service_test.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require 'test_helper'
4 |
5 | module Users
6 | module Registrations
7 | class RegisterServiceTest < ActiveSupport::TestCase
8 | attr_reader :params, :doorkeeper_application
9 |
10 | def setup
11 | @params = attributes_for(:user)
12 | @doorkeeper_application = create(:doorkeeper_application)
13 | end
14 |
15 | test 'should register user' do
16 | assert_difference(['User.count', 'Doorkeeper::AccessToken.count'], 1) do
17 | service = Users::Registrations::RegisterService.new(params:, doorkeeper_application:).call
18 |
19 | user = service.success[:user]
20 |
21 | assert service.success?
22 | assert_equal params[:email], user[:email]
23 | end
24 | end
25 |
26 | test 'should return error if something goes wrong while creating doorkeeper access token' do
27 | access_token_service_mock = mock
28 | Doorkeeper::AccessTokens::CreateService.expects(:new).returns(access_token_service_mock)
29 | access_token_service_mock.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me))
30 |
31 | assert_no_difference(['User.count', 'Doorkeeper::AccessToken.count']) do
32 | service = Users::Registrations::RegisterService.new(params:, doorkeeper_application:).call
33 |
34 | assert service.failure?
35 | assert_equal :failed_because_of_me, service.failure
36 | end
37 | end
38 |
39 | test 'should fail if user service returns failure' do
40 | user_service = mock
41 | Users::CreateService.expects(:new).returns(user_service)
42 | user_service.expects(:call).returns(Dry::Monads::Result::Failure.new(:failed_because_of_me))
43 |
44 | assert_no_difference(['User.count', 'Doorkeeper::AccessToken.count']) do
45 | service = Users::Registrations::RegisterService.new(params:, doorkeeper_application:).call
46 |
47 | assert service.failure?
48 | assert_equal :failed_because_of_me, service.failure
49 | end
50 | end
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/test/supports/body_parser.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Supports
4 | module BodyParser
5 | def load_body(response)
6 | JSON.parse(response.body, object_class: OpenStruct)
7 | end
8 |
9 | def errors_count(response)
10 | load_body(response).errors.count
11 | end
12 |
13 | def error_message?(response, message)
14 | load_body(response).errors.include?(message)
15 | end
16 |
17 | def parse_meta(response)
18 | body = load_body(response)
19 |
20 | body.meta
21 | end
22 |
23 | def pagination_present?(response)
24 | pagination = parse_meta(response).pagination
25 | expected_keys = %i[current previous next limit total_pages total_count]
26 |
27 | expected_keys == pagination.keys
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/test/supports/contract_parser.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Supports
4 | module ContractParser
5 | def contract_errors_parser(contract_result)
6 | errors = {}
7 |
8 | contract_result.fetch(:errors, []).each do |err|
9 | k = err.keys.first
10 | v = err.values.first
11 | errors[k] = v
12 | end
13 |
14 | errors
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/test/supports/doorkeeper_authenticator.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Supports
4 | module DoorkeeperAuthenticator
5 | def oauth_token_params(user, application, grant_type = ::Doorkeeper.config.grant_flows.first)
6 | {
7 | grant_type:,
8 | email: user.email,
9 | password: user.password,
10 | client_id: application.uid,
11 | client_secret: application.secret
12 | }
13 | end
14 |
15 | def oauth_revoke_params(token, token_type = 'token', grant_type = 'refresh_token')
16 | {
17 | grant_type:,
18 | token: token.try(token_type),
19 | client_id: token.application.uid,
20 | client_secret: token.application.secret
21 | }
22 | end
23 |
24 | def oauth_refresh_token_params(token, token_type = 'refresh_token')
25 | {
26 | grant_type: 'refresh_token',
27 | refresh_token: token.try(token_type),
28 | client_id: token.application.uid,
29 | client_secret: token.application.secret
30 | }
31 | end
32 |
33 | def authorization_header(token)
34 | { 'Authorization' => "Bearer #{token.token}" }
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/test/supports/sidekiq_minitest_support.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Supports
4 | module SidekiqMinitestSupport
5 | def after_teardown
6 | ::Sidekiq::Worker.clear_all
7 | super
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | ENV['RAILS_ENV'] ||= 'test'
4 | require_relative '../config/environment'
5 | require 'rails/test_help'
6 | require 'shoulda/matchers'
7 | require 'minitest/unit'
8 | require 'mocha/minitest'
9 | require 'minitest/focus'
10 | require 'supports/contract_validator'
11 | require 'supports/body_parser'
12 | require 'supports/contract_parser'
13 | require 'supports/doorkeeper_authenticator'
14 | require 'supports/sidekiq_minitest_support'
15 | require 'sidekiq/testing'
16 |
17 | class ActiveSupport::TestCase
18 | # Run tests in parallel with specified workers
19 | # parallelize(workers: :number_of_processors)
20 |
21 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order.
22 | # fixtures :all
23 |
24 | include FactoryBot::Syntax::Methods
25 | include Supports::BodyParser
26 | include Supports::ContractParser
27 | include Supports::ContractValidator
28 | include Supports::DoorkeeperAuthenticator
29 | include Supports::SidekiqMinitestSupport
30 | end
31 |
32 | Shoulda::Matchers.configure do |config|
33 | config.integrate do |with|
34 | with.test_framework :minitest
35 | with.library :rails
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/tmp/.keep
--------------------------------------------------------------------------------
/tmp/pids/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/tmp/pids/.keep
--------------------------------------------------------------------------------
/tmp/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/tmp/storage/.keep
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shftco/rails-api-boilerplate/dcaa654b4b40c0a38a6989213cc22ea575ece65a/vendor/.keep
--------------------------------------------------------------------------------