├── .gitignore ├── .rubocop.yml ├── .ruby-version ├── CODE_OF_CONDUCT.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── README.md ├── Rakefile ├── app.json ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── logo.svg │ ├── javascripts │ │ └── .keep │ └── stylesheets │ │ ├── application.css │ │ ├── frontend │ │ ├── _vars.scss │ │ ├── auth.scss │ │ ├── main.scss │ │ ├── normalize.css │ │ ├── typo.scss │ │ └── util.scss │ │ └── rails_admin │ │ └── custom │ │ └── theming.scss ├── controllers │ ├── application_controller.rb │ ├── auth │ │ ├── confirmations_controller.rb │ │ ├── invitations_controller.rb │ │ └── passwords_controller.rb │ ├── concerns │ │ ├── http_auth.rb │ │ └── locale_wrapper.rb │ ├── graphql_controller.rb │ └── pages_controller.rb ├── graphql │ ├── graphql_schema.rb │ ├── mutations │ │ ├── base_mutation.rb │ │ ├── companies │ │ │ └── update_company.rb │ │ └── users │ │ │ ├── accept_invite.rb │ │ │ ├── delete_user.rb │ │ │ ├── invite_user.rb │ │ │ ├── update_user.rb │ │ │ └── update_user_role.rb │ ├── resolvers │ │ ├── base_resolver.rb │ │ ├── companies │ │ │ └── company.rb │ │ └── users │ │ │ ├── me.rb │ │ │ ├── user.rb │ │ │ └── users.rb │ └── types │ │ ├── base_input_object.rb │ │ ├── base_model.rb │ │ ├── base_object.rb │ │ ├── companies │ │ ├── company_input_type.rb │ │ └── company_type.rb │ │ ├── item_order_type.rb │ │ ├── mutation_type.rb │ │ ├── query_type.rb │ │ └── users │ │ ├── user_input_type.rb │ │ └── user_type.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── ability.rb │ ├── application_record.rb │ ├── companies │ │ └── company.rb │ ├── concerns │ │ └── .keep │ └── user.rb └── views │ ├── devise │ ├── confirmations │ │ └── new.html.erb │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── email_changed.html.erb │ │ ├── password_change.html.erb │ │ ├── reset_password_instructions.html.erb │ │ └── unlock_instructions.html.erb │ ├── passwords │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── registrations │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── sessions │ │ └── new.html.erb │ ├── shared │ │ ├── _error_messages.html.erb │ │ └── _links.html.erb │ └── unlocks │ │ └── new.html.erb │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ ├── mailer.text.erb │ └── rails_admin │ │ ├── _navigation.html.erb │ │ └── _secondary_navigation.html.erb │ └── shared │ └── _favicon.html.erb ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring └── update ├── bitbucket-pipelines.yml ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.ci.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── backtrace_silencers.rb │ ├── cors.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── friendly_id.rb │ ├── generators.rb │ ├── graphql_auth.rb │ ├── graphql_errors.rb │ ├── inflections.rb │ ├── locale.rb │ ├── mime_types.rb │ ├── new_framework_defaults_6_0.rb │ ├── rack_attack.rb │ ├── rails_admin.rb │ └── wrap_parameters.rb ├── locales │ ├── activerecord │ │ ├── de.yml │ │ └── en.yml │ ├── de.yml │ ├── devise │ │ ├── invitable │ │ │ └── en.yml │ │ └── views │ │ │ ├── de.yml │ │ │ └── en.yml │ ├── en.yml │ └── errors │ │ ├── de.yml │ │ └── en.yml ├── puma.rb ├── routes.rb ├── spring.rb └── storage.yml ├── db ├── migrate │ ├── 20190209162712_enable_extension_for_uuid.rb │ ├── 20190209163712_devise_create_users.rb │ ├── 20200911092211_create_active_storage_tables.active_storage.rb │ ├── 20200912120337_create_companies.rb │ ├── 20200912153138_add_slug_to_companies.rb │ ├── 20200912153858_create_friendly_id_slugs.rb │ └── 20200920102035_devise_invitable_add_to_users.rb ├── schema.rb └── seeds.rb ├── env_sample ├── lib ├── regex.rb └── tasks │ ├── .keep │ └── auto_annotate_models.rake ├── log └── .keep ├── public ├── android-icon-144x144.png ├── android-icon-192x192.png ├── android-icon-36x36.png ├── android-icon-48x48.png ├── android-icon-72x72.png ├── android-icon-96x96.png ├── apple-icon-114x114.png ├── apple-icon-120x120.png ├── apple-icon-144x144.png ├── apple-icon-152x152.png ├── apple-icon-180x180.png ├── apple-icon-57x57.png ├── apple-icon-60x60.png ├── apple-icon-72x72.png ├── apple-icon-76x76.png ├── apple-icon-precomposed.png ├── apple-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.ico ├── manifest.json ├── ms-icon-144x144.png ├── ms-icon-150x150.png ├── ms-icon-310x310.png ├── ms-icon-70x70.png └── robots.txt ├── spec ├── config │ └── initializers │ │ └── rack │ │ └── attack_spec.rb ├── controllers │ ├── .keep │ └── graphql_controller_spec.rb ├── factories │ ├── companies │ │ └── companies.rb │ └── users.rb ├── fixtures │ └── files │ │ └── .keep ├── graphql │ ├── mutations │ │ ├── companies │ │ │ └── update_company_spec.rb │ │ └── users │ │ │ ├── delete_user_spec.rb │ │ │ ├── update_user_role_spec.rb │ │ │ └── update_user_spec.rb │ ├── resolvers │ │ ├── companies │ │ │ └── company_spec.rb │ │ └── users │ │ │ ├── me_spec.rb │ │ │ ├── user_spec.rb │ │ │ └── users_spec.rb │ └── types │ │ └── .keep ├── locales │ └── locale_spec.rb ├── mailers │ └── .keep ├── models │ ├── companies │ │ └── company_spec.rb │ └── user_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── support │ ├── database_cleaner.rb │ ├── devise_helper.rb │ └── request_spec_helper.rb └── tmp └── .keep /.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 the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | coverage 21 | 22 | # Ignore uploaded files in development 23 | /storage/* 24 | !/storage/.keep 25 | 26 | .byebug_history 27 | 28 | # Ignore master key for decrypting credentials and more. 29 | /config/master.key 30 | 31 | .env 32 | .env.* 33 | 34 | dump.rdb -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | require: 2 | - rubocop-performance 3 | - rubocop-rails 4 | - rubocop-rspec 5 | AllCops: 6 | TargetRubyVersion: 2.6 7 | NewCops: enable 8 | Exclude: 9 | - "db/**/*" 10 | - "script/**/*" 11 | - "bin/**/*" 12 | - "vendor/**/*" 13 | - "app/helpers/ui_helper*" 14 | - "spec/spec_helper*" 15 | - "spec/rails_helper*" 16 | - Gemfile* 17 | Metrics/MethodLength: 18 | Max: 15 19 | Metrics/AbcSize: 20 | Max: 25 21 | Style/MethodCallWithoutArgsParentheses: 22 | Enabled: false 23 | Metrics/BlockLength: 24 | Exclude: 25 | - "config/**/*" 26 | - "spec/**/*" 27 | - "lib/tasks/**/*" 28 | Layout/LineLength: 29 | Exclude: 30 | - "config/**/*" 31 | RSpec/MultipleExpectations: 32 | Enabled: false 33 | RSpec/InstanceVariable: 34 | Enabled: false 35 | RSpec/UnspecifiedException: 36 | Enabled: false 37 | RSpec/ExampleLength: 38 | Max: 10 39 | RSpec/MultipleMemoizedHelpers: 40 | Max: 10 41 | Naming/PredicateName: 42 | Exclude: 43 | - "app/graphql/**/*" 44 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.6.5 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 '2.6.5' 7 | 8 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 9 | gem 'rails', '~> 6.0.3' 10 | 11 | # Use postgresql as the database for Active Record 12 | gem 'pg' 13 | 14 | gem 'bcrypt', '~> 3.1.7' # Use ActiveModel has_secure_password 15 | gem 'devise' # Use devise as authentication module 16 | gem 'devise_invitable', '~> 2.0.0' # Used to invite users. Allows setting passwords by invited user 17 | gem 'graphql', '~> 1.11.4' # GraphQL as API 18 | gem 'graphql-auth', git: 'https://github.com/simonfranzen/graphql-auth.git', branch: 'rails6' 19 | gem 'graphql-errors' # GrapqhQL error handling 20 | gem 'search_object_graphql' # Search Object for graphql-ruby 21 | gem 'rack-cors' # Rack CORS settings 22 | gem 'rails_admin', '~> 2.0.2' # Admin interface 23 | gem 'cancancan' # Defining abilities 24 | gem 'image_processing', '~> 1.2' # Image processing 25 | gem 'mini_magick' # Image manipulation with rmagick 26 | gem 'friendly_id', '5.3.0' # Auto generate slugs for resources 27 | gem 'foreman' 28 | gem 'rack-attack' # request limiter and ip blocker 29 | 30 | # I18n 31 | gem 'rails-i18n', '~> 6.0.0' 32 | gem 'devise-i18n' # Install default translations 33 | gem 'rails_admin-i18n' # Use default rails_admin translations 34 | 35 | # gem 'graphiql-rails', group: :development 36 | 37 | # Use Puma as the app server 38 | gem 'puma', '~> 3.12' 39 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 40 | # gem 'jbuilder', '~> 2.5' 41 | # Use Redis adapter to run Action Cable in production 42 | # gem 'redis', '~> 4.0' 43 | # Use ActiveModel has_secure_password 44 | # gem 'bcrypt', '~> 3.1.7' 45 | 46 | # Use ActiveStorage variant 47 | # gem 'mini_magick', '~> 4.8' 48 | 49 | # Use Capistrano for deployment 50 | # gem 'capistrano-rails', group: :development 51 | 52 | # Reduces boot times through caching; required in config/boot.rb 53 | gem 'bootsnap', '>= 1.1.0', require: false 54 | 55 | # Use Rack CORS for handling Cross-Origin Resource Sharing (CORS), making cross-origin AJAX possible 56 | # gem 'rack-cors' 57 | 58 | # gem 'env' 59 | 60 | group :development, :test do 61 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 62 | gem 'awesome_print' # better console ouput for objects -> ap object.inspect 63 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 64 | gem 'dotenv-rails' # craate a .env file to set local environment variables 65 | gem 'factory_bot_rails' # model mocks with factory bot 66 | gem 'rspec-rails', '~> 3.8' # used testframework 67 | gem 'faker', '~> 1.8' 68 | end 69 | 70 | group :test do 71 | gem 'database_cleaner', '~> 1.6' 72 | gem 'rails-controller-testing' 73 | gem 'shoulda-matchers', '4.0.0.rc1' 74 | gem 'simplecov', require: false 75 | gem 'i18n-spec' 76 | gem 'timecop' 77 | end 78 | 79 | group :development do 80 | gem 'annotate' 81 | gem 'listen', '>= 3.0.5', '< 3.2' 82 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 83 | gem 'rubocop-performance' # speed up rubocop 84 | gem 'rubocop-rails' # rubocop for rails 85 | gem 'rubocop-rspec' # rubocop for rspec 86 | gem 'rubocop' # rubocop for linting ruby code 87 | gem 'spring' 88 | gem 'spring-watcher-listen', '~> 2.0.0' 89 | end 90 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 zauberware technologies 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C config/puma.rb 2 | release: bundle exec rake db:migrate -------------------------------------------------------------------------------- /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.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Rails 6 boilerplate with devise, JWT, graphQL, CanCanCan and RailsAdmin", 3 | "description": "This is a boilerplate to build your next SaaS product. It's a RubyOnRails 6 backend with Authentication and GraphQL API. It works nicely together with clients made with React.js & React.Native or any other frontend which implements the JSON Web Tokens philosophy.", 4 | "keywords": [ 5 | "saas", 6 | "saas-boilerplate", 7 | "ruby-on-rails", 8 | "rails5", 9 | "devise", 10 | "graphql", 11 | "apollo-server", 12 | "ruby-graphql", 13 | "rails-boilerplate", 14 | "rails-api-only", 15 | "api-only", 16 | "gatsby-backend", 17 | "reactjs-backend", 18 | "reactnative-backend", 19 | "vue.js-backend", 20 | "angular-backend", 21 | "graphql-api" 22 | ], 23 | "website": "https://www.zauberware.com/", 24 | "repository": "https://github.com/zauberware/rails-devise-graphql", 25 | "logo": "https://avatars3.githubusercontent.com/u/1753330?s=200&v=4", 26 | "success_url": "/", 27 | "scripts": { 28 | "postdeploy": "rake db:migrate && rake db:seed" 29 | }, 30 | "env": { 31 | "DEVISE_SECRET_KEY": { 32 | "description": "A secret key for verifying the integrity of signed JWT tokens.", 33 | "value": "replace-this-key-with-a-secret" 34 | }, 35 | "DEFAULT_URL": { 36 | "description": "Enter the url of this application. If you deploy to heroku set your heroku address.", 37 | "value": "http://...herokuapp.com" 38 | }, 39 | "CLIENT_URL": { 40 | "description": "Enter the client domain for CORS.", 41 | "value": "http://0.0.0.0:8000" 42 | }, 43 | "DEVISE_MAILER_FROM": { 44 | "description": "Set the default from address for mailers.", 45 | "value": "demo@domain.com" 46 | }, 47 | "ADMIN_EMAIL": { 48 | "description": "Admin default login.", 49 | "value": "demo@zauberware.com" 50 | }, 51 | "ADMIN_PASSWORD": { 52 | "description": "Admin default password.", 53 | "value": "demo1234" 54 | }, 55 | "ADMIN_FIRST_NAME": { 56 | "description": "Admin default firstname.", 57 | "value": "John" 58 | }, 59 | "ADMIN_LAST_NAME": { 60 | "description": "Admin default lastname.", 61 | "value": "Doe" 62 | }, 63 | "IS_HTTP_AUTH_PROTECTED": { 64 | "description": "Enable http auth protection with setting this to true.", 65 | "value": "false" 66 | }, 67 | "HTTP_AUTH_USER": { 68 | "description": "Set user for HTTP Auth protection.", 69 | "value": "demo" 70 | }, 71 | "HTTP_AUTH_PASSWORD": { 72 | "description": "Set password for HTTP Auth protection.", 73 | "value": "demo1234" 74 | }, 75 | "WEB_CONCURRENCY": { 76 | "description": "Specifies the number of `workers` to boot in clustered mode.", 77 | "value": "2" 78 | }, 79 | "RAILS_MAX_THREADS": { 80 | "description": "Puma can serve each request in a thread from an internal thread pool.", 81 | "value": "5" 82 | }, 83 | "SMTP_ADDRESS": { 84 | "description": "Set a smtp server for sending emails.", 85 | "value": "smtp.DOMAIN.COM" 86 | }, 87 | "SMTP_PORT": { 88 | "description": "Set the port for sending mails.", 89 | "value": "587" 90 | }, 91 | "SMTP_DOMAIN": { 92 | "description": "Set domain", 93 | "value": "" 94 | }, 95 | "SMTP_USERNAME": { 96 | "description": "Set SMTP username", 97 | "value": "" 98 | }, 99 | "SMTP_PASSWORD": { 100 | "description": "Set SMTP password", 101 | "value": "" 102 | }, 103 | "SMTP_AUTH": { 104 | "description": "Set SMTP authentication method.", 105 | "value": "plain" 106 | }, 107 | "SMTP_ENABLE_STARTTLS_AUTO": { 108 | "description": "Enable / disable auto starttls.", 109 | "value": "true" 110 | } 111 | }, 112 | "image": "heroku/ruby", 113 | "addons": [ 114 | { 115 | "plan": "heroku-postgresql:hobby-dev" 116 | } 117 | ] 118 | } 119 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css -------------------------------------------------------------------------------- /app/assets/images/logo.svg: -------------------------------------------------------------------------------- 1 | New Project -------------------------------------------------------------------------------- /app/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/app/assets/javascripts/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require frontend/normalize 14 | *= require frontend/util 15 | *= require frontend/typo 16 | *= require frontend/main 17 | *= require frontend/auth 18 | *= require_self 19 | */ -------------------------------------------------------------------------------- /app/assets/stylesheets/frontend/_vars.scss: -------------------------------------------------------------------------------- 1 | // 2 | /* SETTINGS ===================================================== */ 3 | /* Global variables, anything that is global for the project ==== */ 4 | $color--border: rgb(203, 219, 238); 5 | 6 | $gray-base: #080604; 7 | $gray-darker: #221710; // #222 8 | $gray-dark: #3f2b1e; // #333 9 | $gray: #3d6372; // #555 10 | $gray-light: #859fb8; // #999 11 | $gray-lighter: #eaeff5; // #eee 12 | 13 | $brand-info: #80bcdc; // default $brand-primary 14 | $brand-success: #c9db8a; 15 | $brand-primary: #0079ba; // default $brand-info 16 | $brand-warning: #e1c345; 17 | $brand-danger: #e5734b; 18 | 19 | 20 | $font-family-sans-serif: "Roboto",-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif; 21 | $font-family-serif: "Roboto Slab",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif; 22 | //** Default monospace fonts for ``, ``, and `
`.
23 | $font-family-monospace:   Menlo, Monaco, Consolas, "Courier New", monospace;
24 | $font-family-base:        $font-family-sans-serif;
25 | 
26 | 


--------------------------------------------------------------------------------
/app/assets/stylesheets/frontend/auth.scss:
--------------------------------------------------------------------------------
 1 | @import "_vars";
 2 | 
 3 | .authWrapper{
 4 |   width: 100%;
 5 |   max-width: 420px;
 6 |   margin: 100px auto;
 7 |   padding: 20px 30px;
 8 |   border-radius: 6px;
 9 |   background-color: white;
10 |   text-align: center;
11 |   border: 1px solid $color--border;
12 |   box-shadow: 1px 1px 3px rgba(0,0,0,.1);
13 | }


--------------------------------------------------------------------------------
/app/assets/stylesheets/frontend/main.scss:
--------------------------------------------------------------------------------
  1 | @import "_vars";
  2 | 
  3 | /* TOOLS ======================================================== */
  4 | /* External tools, mixins, functions ============================ */
  5 | 
  6 | 
  7 | /* GENERIC ====================================================== */
  8 | /* Project-agnostic, normalizers, box-sizing, etc. ============== */
  9 | 
 10 | * { box-sizing: border-box; }
 11 | 
 12 | /* ELEMENTS ===================================================== */
 13 | /* Basic styling for any HTML-tag without classes =============== */
 14 | 
 15 | body {
 16 |   font-size: 17px;
 17 |   font-family: $font-family-base;
 18 |   -webkit-font-smoothing: antialiased;
 19 |   font-weight: 400;
 20 |   overflow-x: hidden;
 21 |   width: 100%;
 22 |   margin-left: auto;
 23 |   margin-right: auto;
 24 |   background-color: $gray-lighter;
 25 | }
 26 | 
 27 | a{
 28 |   color: $brand-primary;
 29 |   text-decoration: none;
 30 | 
 31 |   &:hover{
 32 |     text-decoration: underline;
 33 |   }
 34 | }
 35 | 
 36 | code{
 37 |   font-size: 0.95rem;
 38 |   font-weight: 300;
 39 |   padding: 3px;
 40 |   margin: 0 3px;
 41 |   border-radius: 4px;
 42 |   line-height: 1;
 43 |   background-color: $gray-lighter;
 44 | }
 45 | 
 46 | input{
 47 |   display: inline-block;
 48 |   margin: 5px 0;
 49 |   border-radius: 6px;
 50 |   width: 100%;
 51 |   padding: 8px 16px;
 52 |   background-color: rgb(255,255,255);
 53 |   border: 1px solid $color--border;
 54 | }
 55 | 
 56 | input[type=submit],
 57 | .btn {
 58 |   margin: 20px 0;
 59 |   display: inline-block;
 60 |   box-sizing: border-box;
 61 |   padding: 12px 24px;
 62 |   -moz-appearance: none;
 63 |   cursor: pointer;
 64 |   border-radius: 6px;
 65 |   background-color: $brand-primary;
 66 |   color: #ffffff;
 67 |   text-transform: uppercase;
 68 |   font-weight: 700;
 69 |   width: 100%;
 70 |   border: medium none;
 71 |   line-height: 18px;
 72 |   -moz-box-align: center;
 73 |   align-items: center;
 74 |   margin-left: auto;
 75 |   margin-right: auto;
 76 | }
 77 | 
 78 | h1,h2,h3,h4,h5,h6{
 79 |   font-family: $font-family-serif;
 80 |   color: $gray;
 81 | }
 82 | 
 83 | .field{
 84 |   text-align: left;
 85 | }
 86 | 
 87 | .logo-wrapper{
 88 |   background-color: white;
 89 |   display: inline-block;
 90 |   padding: 15px 10px 10px 10px;
 91 | }
 92 | 
 93 | .flash{
 94 |   padding: 10px;
 95 |   text-align: center;
 96 |   background: $brand-info;
 97 |   margin: 10px 0;
 98 |   color: white;
 99 |   border-radius: 3px;
100 |   font-weight: 700;
101 | 
102 |   &--error{
103 |     background: $brand-danger;
104 |   }
105 | 
106 |   &--alert{
107 |     background: $brand-warning;
108 |   }
109 | }


--------------------------------------------------------------------------------
/app/assets/stylesheets/frontend/typo.scss:
--------------------------------------------------------------------------------
1 | @import "_vars";
2 | 
3 | h1,h2,h3,h4,h5,h6{
4 |   font-family: $font-family-serif;
5 |   font-weight: 600;
6 |   line-height: 1.2;
7 | }


--------------------------------------------------------------------------------
/app/assets/stylesheets/frontend/util.scss:
--------------------------------------------------------------------------------
1 | .clearfix{
2 |   float: none;
3 |   clear: both;
4 |   display: block;
5 | }


--------------------------------------------------------------------------------
/app/assets/stylesheets/rails_admin/custom/theming.scss:
--------------------------------------------------------------------------------
 1 | 
 2 | @import url('https://fonts.googleapis.com/css?family=Roboto:400,400i,700|Roboto+Slab:400,700&display=swap&subset=latin-ext');
 3 | 
 4 | body.rails_admin{
 5 | 
 6 |   h1,h2,h3,h4,h5,h6{
 7 |     font-family: $font-family-serif;
 8 |     color: $gray-dark;
 9 |   }
10 | 
11 |   .navbar{
12 |     border-bottom: 3px solid white;
13 |     box-shadow: 1px 2px 5px rgba(0,0,0,.1);
14 |     max-height: 53px;
15 |     overflow: visible;
16 |     
17 |   }
18 | 
19 |   .nav {
20 |     .icon, .fa{
21 |       margin-right: 12px;
22 |     }
23 |   }
24 | 
25 |   .logo-wrapper{
26 |     display: inline-block;
27 |     padding: 15px 10px 10px 10px;
28 |   }
29 | 
30 |   .fieldset .label.label-info{
31 |     border-bottom-left-radius: 0;
32 |     border-bottom-right-radius: 0;
33 |   }
34 | 
35 |   .fieldset .well{
36 |     border-top-left-radius: 0;
37 |   }
38 | 
39 | }
40 | 


--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | # Base controlller for application
 4 | class ApplicationController < ActionController::Base
 5 |   include HttpAuth
 6 |   include LocaleWrapper
 7 | 
 8 |   rescue_from CanCan::AccessDenied do |exception|
 9 |     respond_to do |format|
10 |       format.js   { render 'errors/unauthorized', status: 403 }
11 |       format.html { redirect_to '/', alert: exception.message }
12 |       format.json { render json: { errors: { permission: [exception.message] } }, status: 403 }
13 |     end
14 |   end
15 | 
16 |   protected
17 | 
18 |   def current_superadmin
19 |     return nil if current_user.nil?
20 |     return nil unless current_user.superadmin?
21 | 
22 |     current_user
23 |   end
24 | 
25 |   private
26 | 
27 |   def after_sign_in_path_for(resource)
28 |     if resource.superadmin? || resource.admin?
29 |       rails_admin_url
30 |     else
31 |       root_url
32 |     end
33 |   end
34 | end
35 | 


--------------------------------------------------------------------------------
/app/controllers/auth/confirmations_controller.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Auth
 4 |   # Custom confirmations controller
 5 |   class ConfirmationsController < Devise::ConfirmationsController
 6 |     # GET /resource/confirmation?confirmation_token=abcdef
 7 |     def show
 8 |       self.resource = resource_class.confirm_by_token(params[:confirmation_token])
 9 |       yield resource if block_given?
10 | 
11 |       if resource.errors.empty?
12 |         respond_with_navigational(resource) { redirect_to after_confirmation_path_for(resource_name, resource) }
13 |       else
14 |         redirect_to "http://#{ENV['CLIENT_URL']}?error=#{I18n.t('errors.messages.already_confirmed')}"
15 |       end
16 |     end
17 | 
18 |     private
19 | 
20 |     # redirect user to front end app after confirming the email adress.
21 |     def after_confirmation_path_for(_resource_name, _resource)
22 |       "http://#{ENV['CLIENT_URL']}?notice=#{I18n.t('devise.confirmations.confirmed')}"
23 |     end
24 |   end
25 | end
26 | 


--------------------------------------------------------------------------------
/app/controllers/auth/invitations_controller.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Auth
 4 |   # Custom passwords controller
 5 |   class InvitationsController < Devise::InvitationsController
 6 |     # GET /resource/invitation/accept?invitation_token=abcdef
 7 |     # redirect user to front end to finish invitation
 8 |     def edit
 9 |       redirect_to "http://#{ENV['CLIENT_URL']}/users/invitation/accept?invitation_token=#{params[:invitation_token]}"
10 |     end
11 |   end
12 | end
13 | 


--------------------------------------------------------------------------------
/app/controllers/auth/passwords_controller.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | # rubocop:disable Layout/LineLength
 4 | module Auth
 5 |   # Custom passwords controller
 6 |   class PasswordsController < Devise::PasswordsController
 7 |     # GET /resource/password/edit?reset_password_token=abcdef
 8 |     # redirect user to front end to reset the passwords there
 9 |     def edit
10 |       redirect_to "http://#{ENV['CLIENT_URL']}/users/password/edit?reset_password_token=#{params[:reset_password_token]}"
11 |     end
12 |   end
13 | end
14 | # rubocop:enable Layout/LineLength
15 | 


--------------------------------------------------------------------------------
/app/controllers/concerns/http_auth.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | # Injects http authentication to a controller.
 4 | module HttpAuth
 5 |   extend ActiveSupport::Concern
 6 | 
 7 |   included do
 8 |     before_action :http_authenticate
 9 |   end
10 | 
11 |   protected
12 | 
13 |   def http_authenticate
14 |     return true unless ENV['IS_HTTP_AUTH_PROTECTED'] == 'true'
15 | 
16 |     authenticate_or_request_with_http_basic do |username, password|
17 |       username == ENV['HTTP_AUTH_USER'] && password == ENV['HTTP_AUTH_PASSWORD']
18 |     end
19 |   end
20 | end
21 | 


--------------------------------------------------------------------------------
/app/controllers/concerns/locale_wrapper.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | # Handles setting of locale in your controller.
 4 | module LocaleWrapper
 5 |   extend ActiveSupport::Concern
 6 | 
 7 |   included do
 8 |     around_action :switch_locale
 9 |   end
10 | 
11 |   protected
12 | 
13 |   def switch_locale(&action)
14 |     locale = params[:locale] || locale_from_http_accept_lang || I18n.default_locale
15 |     I18n.with_locale(locale, &action)
16 |   end
17 | 
18 |   def locale_from_http_accept_lang
19 |     return nil if !request || !request.env['HTTP_ACCEPT_LANGUAGE']
20 | 
21 |     locale = request.env['HTTP_ACCEPT_LANGUAGE'].scan(/^[a-z]{2}/).first
22 |     return nil unless locale
23 | 
24 |     # only if match
25 |     I18n.available_locales.map(&:to_s).include?(locale) ? locale : nil
26 |   end
27 | end
28 | 


--------------------------------------------------------------------------------
/app/controllers/graphql_controller.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | # GraphQL API entry point
 4 | class GraphqlController < ApplicationController
 5 |   include Graphql::AuthHelper
 6 | 
 7 |   # disable forgery protection for API
 8 |   skip_before_action :verify_authenticity_token
 9 | 
10 |   # Executes the graphql request
11 |   def execute
12 |     query, variables, operation_name = read_query_params()
13 |     result = GraphqlSchema.execute(
14 |       query,
15 |       variables: variables,
16 |       context: context,
17 |       operation_name: operation_name
18 |     )
19 |     render json: result
20 |   rescue StandardError => e
21 |     handle_error e
22 |   end
23 | 
24 |   private
25 | 
26 |   def read_query_params
27 |     [
28 |       params[:query],
29 |       ensure_hash(params[:variables]),
30 |       params[:operationName]
31 |     ]
32 |   end
33 | 
34 |   # Handle form data, JSON body, or a blank value
35 |   def ensure_hash(ambiguous_param)
36 |     case ambiguous_param
37 |     when String
38 |       ambiguous_param.present? ? ensure_hash(JSON.parse(ambiguous_param)) : {}
39 |     when Hash, ActionController::Parameters
40 |       ambiguous_param
41 |     when nil
42 |       {}
43 |     else
44 |       raise ArgumentError, "Unexpected parameter: #{ambiguous_param}"
45 |     end
46 |   end
47 | 
48 |   def handle_error(err)
49 |     logger.error err.message
50 |     logger.error err.backtrace.join("\n")
51 | 
52 |     render json: {
53 |       errors: [
54 |         { message: err.message }
55 |       ],
56 |       data: {}
57 |     }, status: 500
58 |   end
59 | end
60 | 


--------------------------------------------------------------------------------
/app/controllers/pages_controller.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | 
3 | # A controller to handle static pages served from backend
4 | class PagesController < ApplicationController
5 |   def blank
6 |     head 200, content_type: 'text/html'
7 |   end
8 | end
9 | 


--------------------------------------------------------------------------------
/app/graphql/graphql_schema.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | 
3 | # Entry point for graphql schema
4 | class GraphqlSchema < GraphQL::Schema
5 |   disable_introspection_entry_points if Rails.env.production?
6 |   query(Types::QueryType)
7 |   mutation(Types::MutationType)
8 | end
9 | 


--------------------------------------------------------------------------------
/app/graphql/mutations/base_mutation.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Mutations
 4 |   # Base mutation class for app
 5 |   class BaseMutation < GraphQL::Schema::Mutation
 6 |     def current_ability
 7 |       Ability.new(context[:current_user])
 8 |     end
 9 |   end
10 | end
11 | 


--------------------------------------------------------------------------------
/app/graphql/mutations/companies/update_company.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Mutations
 4 |   module Companies
 5 |     # Updates an existing company.
 6 |     class UpdateCompany < Mutations::BaseMutation
 7 |       description 'Updates an existing company.'
 8 |       argument :id, ID, required: true
 9 |       argument :attributes, Types::Companies::CompanyInputType, required: true
10 |       payload_type Types::Companies::CompanyType
11 | 
12 |       def resolve(id:, attributes:)
13 |         # find company
14 |         company = ::Companies::Company.accessible_by(current_ability).find_by(id: id)
15 |         if company.nil?
16 |           raise ActiveRecord::RecordNotFound,
17 |                 I18n.t('errors.messages.resource_not_found', resource: ::Companies::Company.model_name.human)
18 |         end
19 | 
20 |         company.attributes = attributes.to_h
21 |         current_ability.authorize! :update, company
22 |         return company if company.save!
23 |       end
24 |     end
25 |   end
26 | end
27 | 


--------------------------------------------------------------------------------
/app/graphql/mutations/users/accept_invite.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Mutations
 4 |   module Users
 5 |     # Accepts an invitation for a user
 6 |     class AcceptInvite < Mutations::BaseMutation
 7 |       description 'Accepts an invitation for a user'
 8 |       argument :invitation_token, String, required: true
 9 |       argument :attributes, Types::Users::UserInputType, required: true
10 |       payload_type Boolean
11 | 
12 |       def resolve(invitation_token:, attributes:)
13 |         user = User.accept_invitation!(attributes.to_h.merge(invitation_token: invitation_token))
14 |         raise ActiveRecord::RecordInvalid, user unless user.errors.empty?
15 | 
16 |         true
17 |       end
18 |     end
19 |   end
20 | end
21 | 


--------------------------------------------------------------------------------
/app/graphql/mutations/users/delete_user.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Mutations
 4 |   module Users
 5 |     # Deletes an user as an admin.
 6 |     class DeleteUser < Mutations::BaseMutation
 7 |       description 'Deletes an user as an admin.'
 8 |       argument :id, ID, required: true
 9 |       payload_type Boolean
10 | 
11 |       def resolve(id:)
12 |         user = ::User.accessible_by(current_ability).find_by(id: id)
13 |         if user.nil?
14 |           raise ActiveRecord::RecordNotFound,
15 |                 I18n.t('errors.messages.resource_not_found', resource: ::User.model_name.human)
16 |         end
17 | 
18 |         current_ability.authorize! :destroy, user
19 |         return true if user.destroy!
20 | 
21 |         false
22 |       end
23 |     end
24 |   end
25 | end
26 | 


--------------------------------------------------------------------------------
/app/graphql/mutations/users/invite_user.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Mutations
 4 |   module Users
 5 |     # Invites an user to your account.
 6 |     class InviteUser < Mutations::BaseMutation
 7 |       description 'Invites an user to your account.'
 8 |       argument :attributes, Types::Users::UserInputType, required: true
 9 |       payload_type Types::Users::UserType
10 | 
11 |       def resolve(attributes:)
12 |         # create a dummy user object to check ability against create
13 |         user = ::User.new(attributes.to_h.merge(company_id: context[:current_user].company_id))
14 |         current_ability.authorize! :create, user
15 | 
16 |         user = User.invite!(user.attributes, context[:current_user])
17 |         raise ActiveRecord::RecordInvalid, user unless user.errors.empty?
18 | 
19 |         user
20 |       end
21 |     end
22 |   end
23 | end
24 | 


--------------------------------------------------------------------------------
/app/graphql/mutations/users/update_user.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Mutations
 4 |   module Users
 5 |     # Updates an existing user as an admin.
 6 |     class UpdateUser < Mutations::BaseMutation
 7 |       description 'Updates an existing user as an admin.'
 8 |       argument :id, ID, required: true
 9 |       argument :attributes, Types::Users::UserInputType, required: true
10 |       payload_type Types::Users::UserType
11 | 
12 |       def resolve(id:, attributes:)
13 |         user = ::User.accessible_by(current_ability).find_by(id: id)
14 |         if user.nil?
15 |           raise ActiveRecord::RecordNotFound,
16 |                 I18n.t('errors.messages.resource_not_found', resource: ::User.model_name.human)
17 |         end
18 | 
19 |         user.attributes = attributes.to_h
20 |         current_ability.authorize! :update, user
21 |         return user if user.save!
22 |       end
23 |     end
24 |   end
25 | end
26 | 


--------------------------------------------------------------------------------
/app/graphql/mutations/users/update_user_role.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Mutations
 4 |   module Users
 5 |     # Updates the role for an user as an admin.
 6 |     class UpdateUserRole < Mutations::BaseMutation
 7 |       description 'Updates the role for an user as an admin.'
 8 |       argument :id, ID, required: true
 9 |       argument :role, String, required: true, description: '"user" or "admin"'
10 |       payload_type Boolean
11 | 
12 |       def resolve(id:, role:)
13 |         user = ::User.accessible_by(current_ability).find_by(id: id)
14 |         if user.nil?
15 |           raise ActiveRecord::RecordNotFound,
16 |                 I18n.t('errors.messages.resource_not_found', resource: ::User.model_name.human)
17 |         end
18 | 
19 |         if %w[admin user].include?(role)
20 |           user.role = role
21 |           current_ability.authorize! :update, user
22 |           user.save!
23 |           return true
24 |         end
25 | 
26 |         false
27 |       end
28 |     end
29 |   end
30 | end
31 | 


--------------------------------------------------------------------------------
/app/graphql/resolvers/base_resolver.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Resolvers
 4 |   # Base resolver class include everything you need for sorting and filtering entities
 5 |   class BaseResolver < GraphQL::Schema::Resolver
 6 |     # override in your resolver to allow order by attributes
 7 |     def allowed_filter_attributes
 8 |       raise 'Return an array with your allowed filter attributes.'
 9 |     end
10 | 
11 |     # apply_filter recursively loops through "OR" branches
12 |     def apply_filter(scope, value)
13 |       branches = normalize_filters(value).reduce { |a, b| a.or(b) }
14 |       scope.merge branches
15 |     end
16 | 
17 |     def normalize_filters(value, branches = [])
18 |       scope = resources
19 |       allowed_filter_attributes.each do |filter_attr|
20 |         if value[filter_attr.to_sym].present?
21 |           scope = scope.where("#{filter_attr} LIKE ?", "%#{value[filter_attr.to_sym]}%")
22 |         end
23 |       end
24 |       branches << scope
25 |       value[:OR].reduce(branches) { |s, v| normalize_filters(v, s) } if value[:OR].present?
26 |       branches
27 |     end
28 | 
29 |     # override in your resolver to allow order by attributes
30 |     def allowed_order_attributes
31 |       raise 'Return an array with your allowed order attributes.'
32 |     end
33 | 
34 |     # apply order_by
35 |     def apply_order_by(scope, value)
36 |       direction = 'asc'
37 |       if value[:attribute].present? &&
38 |          allowed_order_attributes.include?(value[:attribute])
39 |         direction = value[:direction] if value[:direction].present? && %w[asc desc].include?(value[:direction].downcase)
40 |         scope = scope.order("#{value[:attribute]} #{direction}")
41 |       end
42 |       scope
43 |     end
44 | 
45 |     def current_ability
46 |       Ability.new(context[:current_user])
47 |     end
48 |   end
49 | end
50 | 


--------------------------------------------------------------------------------
/app/graphql/resolvers/companies/company.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Resolvers
 4 |   module Companies
 5 |     # Resolver to return a user
 6 |     class Company < ::Resolvers::BaseResolver
 7 |       type Types::Companies::CompanyType, null: true
 8 |       description 'Returns the company for the user.'
 9 | 
10 |       def resolve
11 |         context[:current_user] ? context[:current_user].company : nil
12 |       end
13 |     end
14 |   end
15 | end
16 | 


--------------------------------------------------------------------------------
/app/graphql/resolvers/users/me.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Resolvers
 4 |   module Users
 5 |     # Get current user object
 6 |     class Me < Resolvers::BaseResolver
 7 |       type Types::Users::UserType, null: true
 8 |       description 'Returns the current user'
 9 | 
10 |       def resolve
11 |         context[:current_user]
12 |       end
13 |     end
14 |   end
15 | end
16 | 


--------------------------------------------------------------------------------
/app/graphql/resolvers/users/user.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Resolvers
 4 |   module Users
 5 |     # Resolver to return a user
 6 |     class User < Resolvers::BaseResolver
 7 |       type Types::Users::UserType, null: true
 8 |       description 'Returns the user for a requested id'
 9 | 
10 |       argument :id, ID, required: true
11 | 
12 |       def resolve(id:)
13 |         ::User.accessible_by(current_ability).find_by(id: id)
14 |       end
15 |     end
16 |   end
17 | end
18 | 


--------------------------------------------------------------------------------
/app/graphql/resolvers/users/users.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Resolvers
 4 |   module Users
 5 |     # Resolver to return a user
 6 |     class Users < Resolvers::BaseResolver
 7 |       include ::SearchObject.module(:graphql)
 8 | 
 9 |       type Types::Users::UserType.connection_type, null: false
10 |       description 'Returns all user for the current user company'
11 |       scope { resources }
12 | 
13 |       def resources
14 |         ::User.accessible_by(current_ability).includes(:company)
15 |       end
16 | 
17 |       option :order_by, type: Types::ItemOrderType, with: :apply_order_by
18 |       def allowed_order_attributes
19 |         %w[email first_name last_name created_at updated_at]
20 |       end
21 | 
22 |       # inline input type definition for the advanced filter
23 |       class UserFilterType < ::Types::BaseInputObject
24 |         argument :OR, [self], required: false
25 |         argument :email, String, required: false
26 |         argument :first_name, String, required: false
27 |         argument :last_name, String, required: false
28 |       end
29 |       option :filter, type: UserFilterType, with: :apply_filter
30 |       def allowed_filter_attributes
31 |         %w[email first_name last_name]
32 |       end
33 |     end
34 |   end
35 | end
36 | 


--------------------------------------------------------------------------------
/app/graphql/types/base_input_object.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | 
3 | module Types
4 |   # Base class for all graphql input objects
5 |   class BaseInputObject < GraphQL::Schema::InputObject
6 |   end
7 | end
8 | 


--------------------------------------------------------------------------------
/app/graphql/types/base_model.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Types
 4 |   # Base class for all graphql model objects
 5 |   class BaseModel < GraphQL::Schema::Object
 6 |     field :id, ID, null: false
 7 |     field :created_at, GraphQL::Types::ISO8601DateTime, null: false
 8 |     field :updated_at, GraphQL::Types::ISO8601DateTime, null: false
 9 |   end
10 | end
11 | 


--------------------------------------------------------------------------------
/app/graphql/types/base_object.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | 
3 | module Types
4 |   # Base class for all graphql objects
5 |   class BaseObject < GraphQL::Schema::Object
6 |   end
7 | end
8 | 


--------------------------------------------------------------------------------
/app/graphql/types/companies/company_input_type.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Types
 4 |   module Companies
 5 |     # Attributes to update a company.
 6 |     class CompanyInputType < Types::BaseInputObject
 7 |       description 'Attributes to update a company.'
 8 |       argument :name, String, 'Name of company', required: true
 9 |     end
10 |   end
11 | end
12 | 


--------------------------------------------------------------------------------
/app/graphql/types/companies/company_type.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Types
 4 |   module Companies
 5 |     # GraphQL type for a company
 6 |     class CompanyType < Types::BaseModel
 7 |       field :name, String, null: false
 8 |       field :slug, String, null: true
 9 |       # field :users, [::Types::UserType], null: true
10 |     end
11 |   end
12 | end
13 | 


--------------------------------------------------------------------------------
/app/graphql/types/item_order_type.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Types
 4 |   # Base type for sort objects
 5 |   class ItemOrderType < BaseInputObject
 6 |     argument :attribute, String, required: true, description: 'The attribute you want to order by.'
 7 |     argument :direction, String, required: true, description: 'Set a direction with "asc" or "desc".'
 8 |   end
 9 | end
10 | 


--------------------------------------------------------------------------------
/app/graphql/types/mutation_type.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Types
 4 |   # Loads mutations into schema
 5 |   # Include ither mutations here
 6 |   class MutationType < Types::BaseObject
 7 |     implements ::Types::GraphqlAuth
 8 | 
 9 |     # Mutations
10 |     field :update_user, mutation: Mutations::Users::UpdateUser
11 |     field :update_user_role, mutation: Mutations::Users::UpdateUserRole
12 |     field :delete_user, mutation: Mutations::Users::DeleteUser
13 |     field :invite_user, mutation: Mutations::Users::InviteUser
14 |     field :accept_invite, mutation: Mutations::Users::AcceptInvite
15 | 
16 |     # Company
17 |     field :update_company, mutation: Mutations::Companies::UpdateCompany
18 |   end
19 | end
20 | 


--------------------------------------------------------------------------------
/app/graphql/types/query_type.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Types
 4 |   # Loads queries into schema
 5 |   # include other queries and resolvers here
 6 |   class QueryType < BaseObject
 7 |     field :me, resolver: Resolvers::Users::Me
 8 | 
 9 |     field :users, resolver: Resolvers::Users::Users
10 |     field :user, resolver: Resolvers::Users::User
11 | 
12 |     field :company, resolver: Resolvers::Companies::Company
13 |   end
14 | end
15 | 


--------------------------------------------------------------------------------
/app/graphql/types/users/user_input_type.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Types
 4 |   module Users
 5 |     # Input type for user
 6 |     class UserInputType < Types::BaseInputObject
 7 |       description 'Attributes to create a user.'
 8 |       argument :email, String, 'Email of user', required: true
 9 |       argument :first_name, String, 'Firstname of user', required: true
10 |       argument :last_name, String, 'Lastname of user', required: true
11 |       argument :password, String, 'Password of user', required: false
12 |       argument :password_confirmation, String, 'Password confirmation', required: false
13 |     end
14 |   end
15 | end
16 | 


--------------------------------------------------------------------------------
/app/graphql/types/users/user_type.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | module Types
 4 |   module Users
 5 |     # GraphQL type for a user
 6 |     class UserType < Types::BaseModel
 7 |       field :name, String, null: false
 8 |       field :first_name, String, null: false
 9 |       field :last_name, String, null: false
10 |       field :email, String, null: true
11 |       field :role, String, null: false
12 |       field :company, Types::Companies::CompanyType, null: false
13 | 
14 |       field :is_confirmed, Boolean, null: false
15 |       def is_confirmed
16 |         object.confirmed?
17 |       end
18 | 
19 |       field :is_locked, Boolean, null: false
20 |       def is_locked
21 |         object.access_locked?
22 |       end
23 |     end
24 |   end
25 | end
26 | 


--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | 
3 | # Base class for all background jobs
4 | # :nocov:
5 | class ApplicationJob < ActiveJob::Base
6 | end
7 | # :nocov:
8 | 


--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | # Base class for all application mailer
 4 | # :nocov:
 5 | class ApplicationMailer < ActionMailer::Base
 6 |   default from: ENV['DEVISE_MAILER_FROM']
 7 |   layout 'mailer'
 8 | end
 9 | # :nocov:
10 | 


--------------------------------------------------------------------------------
/app/models/ability.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | # Defines abilities for user
 4 | class Ability
 5 |   include CanCan::Ability
 6 | 
 7 |   def initialize(user)
 8 |     user ||= User.new # guest user (not logged in)
 9 | 
10 |     alias_action :create, :read, :update, :destroy, to: :crud
11 | 
12 |     return if user.new_record?
13 | 
14 |     if user.superadmin?
15 |       can :access, :rails_admin         # grant access to rails_admin
16 |       can :manage, :all                 # admins can manage all objects
17 |     elsif user.admin?
18 |       can :crud, User, company_id: user.company_id
19 |       can :update, Companies::Company, id: user.company_id
20 |     end
21 |     can :read, Companies::Company, id: user.company_id
22 |     can :read, User, company_id: user.company_id
23 | 
24 |     # See the wiki for details:
25 |     # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
26 |   end
27 | end
28 | 


--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 | 
3 | # Base class for all application records
4 | class ApplicationRecord < ActiveRecord::Base
5 |   self.abstract_class = true
6 | end
7 | 


--------------------------------------------------------------------------------
/app/models/companies/company.rb:
--------------------------------------------------------------------------------
 1 | # frozen_string_literal: true
 2 | 
 3 | # == Schema Information
 4 | #
 5 | # Table name: companies
 6 | #
 7 | #  id          :uuid             not null, primary key
 8 | #  name        :string
 9 | #  slug        :string
10 | #  users_count :integer
11 | #  created_at  :datetime         not null
12 | #  updated_at  :datetime         not null
13 | #
14 | # Indexes
15 | #
16 | #  index_companies_on_slug  (slug) UNIQUE
17 | #
18 | module Companies
19 |   # A company to scope a bunch of users
20 |   class Company < ApplicationRecord
21 |     extend FriendlyId
22 | 
23 |     self.table_name = 'companies'
24 | 
25 |     # - EXTENSIONS
26 |     friendly_id :name, use: :slugged
27 | 
28 |     # - VALIDATIONS
29 |     validates :name, presence: true
30 |     validates :name, length: { maximum: 255 }
31 |     validates :slug, length: { maximum: 255 }
32 | 
33 |     # - RELATIONS
34 |     has_many :users, dependent: :destroy
35 | 
36 |     # override friendly id checker for categories
37 |     def should_generate_new_friendly_id?
38 |       (slug.nil? || slug.blank?) || (name_changed? && !slug_changed?)
39 |     end
40 | 
41 |     # :nocov:
42 |     rails_admin do
43 |       weight 10
44 |       navigation_icon 'fa fa-building'
45 | 
46 |       list do
47 |         field :id
48 |         field :name
49 |         field :slug
50 |         field :users_count
51 |       end
52 | 
53 |       edit do
54 |         field :name
55 |         field :slug
56 |       end
57 | 
58 |       show do
59 |         field :id
60 |         field :name
61 |         field :slug
62 |         field :users_count
63 |       end
64 |     end
65 | 
66 |     # :nocov:
67 |   end
68 | end
69 | 


--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/app/models/concerns/.keep


--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
  1 | # frozen_string_literal: true
  2 | 
  3 | # == Schema Information
  4 | #
  5 | # Table name: users
  6 | #
  7 | #  id                     :uuid             not null, primary key
  8 | #  confirmation_sent_at   :datetime
  9 | #  confirmation_token     :string
 10 | #  confirmed_at           :datetime
 11 | #  current_sign_in_at     :datetime
 12 | #  current_sign_in_ip     :inet
 13 | #  email                  :string           default(""), not null
 14 | #  encrypted_password     :string           default(""), not null
 15 | #  failed_attempts        :integer          default(0), not null
 16 | #  first_name             :string           default(""), not null
 17 | #  invitation_accepted_at :datetime
 18 | #  invitation_created_at  :datetime
 19 | #  invitation_limit       :integer
 20 | #  invitation_sent_at     :datetime
 21 | #  invitation_token       :string
 22 | #  invitations_count      :integer          default(0)
 23 | #  invited_by_type        :string
 24 | #  last_name              :string           default(""), not null
 25 | #  last_seen_at           :datetime
 26 | #  last_sign_in_at        :datetime
 27 | #  last_sign_in_ip        :inet
 28 | #  locked_at              :datetime
 29 | #  refresh_token          :string
 30 | #  remember_created_at    :datetime
 31 | #  reset_password_sent_at :datetime
 32 | #  reset_password_token   :string
 33 | #  role                   :integer          default("user"), not null
 34 | #  sign_in_count          :integer          default(0), not null
 35 | #  unconfirmed_email      :string
 36 | #  unlock_token           :string
 37 | #  created_at             :datetime         not null
 38 | #  updated_at             :datetime         not null
 39 | #  company_id             :uuid
 40 | #  invited_by_id          :bigint
 41 | #
 42 | # Indexes
 43 | #
 44 | #  index_users_on_confirmation_token                 (confirmation_token) UNIQUE
 45 | #  index_users_on_email                              (email) UNIQUE
 46 | #  index_users_on_invitation_token                   (invitation_token) UNIQUE
 47 | #  index_users_on_invitations_count                  (invitations_count)
 48 | #  index_users_on_invited_by_id                      (invited_by_id)
 49 | #  index_users_on_invited_by_type_and_invited_by_id  (invited_by_type,invited_by_id)
 50 | #  index_users_on_refresh_token                      (refresh_token) UNIQUE
 51 | #  index_users_on_reset_password_token               (reset_password_token) UNIQUE
 52 | #  index_users_on_unlock_token                       (unlock_token) UNIQUE
 53 | #
 54 | class User < ApplicationRecord
 55 |   # Include default devise modules. Others available are:
 56 |   # :timeoutable, :trackable and :omniauthable
 57 |   devise :invitable, :database_authenticatable,
 58 |          :registerable,
 59 |          :recoverable,
 60 |          :confirmable,
 61 |          :devise,
 62 |          :validatable,
 63 |          :lockable,
 64 |          :trackable,
 65 |          :invitable
 66 | 
 67 |   # add new roles to the end
 68 |   enum role: { user: 0, admin: 1, superadmin: 2 }
 69 | 
 70 |   # - VALIDATIONS
 71 |   validates :email, presence: true
 72 |   validates :email, length: { maximum: 255 }
 73 |   validates :email, format: { with: Regex::Email::VALIDATE }
 74 |   validates :first_name, length: { maximum: 255 }
 75 |   validates :last_name, length: { maximum: 255 }
 76 | 
 77 |   # - RELATIONS
 78 |   belongs_to :company, counter_cache: true, class_name: 'Companies::Company'
 79 | 
 80 |   # - CALLBACKS
 81 |   after_initialize :setup_new_user, if: :new_record?
 82 |   before_validation :setup_company
 83 | 
 84 |   # return first and lastname
 85 |   def name
 86 |     [first_name, last_name].join(' ').strip
 87 |   end
 88 | 
 89 |   def status_color
 90 |     return 'warning' if role == 'admin'
 91 |     return 'danger' if role == 'superadmin'
 92 | 
 93 |     'primary'
 94 |   end
 95 | 
 96 |   private
 97 | 
 98 |   def setup_company
 99 |     return unless company.nil?
100 | 
101 |     self.company = Companies::Company.create!(name: 'My company')
102 |     self.role = :admin # make this user the admin
103 |   end
104 | 
105 |   def setup_new_user
106 |     self.role ||= :user
107 |   end
108 | 
109 |   # :nocov:
110 |   # rubocop:disable Metrics/BlockLength
111 |   rails_admin do
112 |     weight 10
113 |     navigation_icon 'fa fa-user-circle'
114 | 
115 |     configure :role do
116 |       pretty_value do # used in list view columns and show views, defaults to formatted_value for non-association fields
117 |         bindings[:view].tag.span(
118 |           User.human_attribute_name(value), class: "label label-#{bindings[:object].status_color}"
119 |         )
120 |       end
121 |       export_value do
122 |         User.human_attribute_name(value)
123 |       end
124 |     end
125 | 
126 |     configure :email do
127 |       pretty_value do
128 |         bindings[:view].link_to(value, "mailto:#{value}")
129 |       end
130 |       export_value do
131 |         value
132 |       end
133 |     end
134 | 
135 |     list do
136 |       field :id
137 |       field :first_name
138 |       field :last_name
139 |       field :email
140 |       field :role
141 |       field :last_sign_in_at
142 |       field :company
143 |     end
144 | 
145 |     edit do
146 |       field :first_name
147 |       field :last_name
148 |       field :email
149 |       field :password
150 |       field :password_confirmation
151 |       field :role
152 |       field :company
153 |     end
154 | 
155 |     show do
156 |       field :id
157 |       field :first_name
158 |       field :last_name
159 |       field :email
160 |       field :role
161 |       field :last_sign_in_at
162 |       field :company
163 |     end
164 |   end
165 |   # rubocop:enable Metrics/BlockLength
166 |   # :nocov:
167 | end
168 | 


--------------------------------------------------------------------------------
/app/views/devise/confirmations/new.html.erb:
--------------------------------------------------------------------------------
 1 | 
2 |
3 | <%= image_tag('logo.svg', width: '200px', alt: 'Logo' ) %> 4 |
5 |
6 |

<%= t('.resend_confirmation_instructions') %>

7 | 8 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 9 | <%= render "devise/shared/error_messages", resource: resource %> 10 | 11 |
12 | <%= f.label :email %>
13 | <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) %> 14 |
15 | 16 |
17 | <%= f.submit t('.resend_confirmation_instructions') %> 18 |
19 | <% end %> 20 | 21 | <%= render "devise/shared/links" %> 22 | 23 |
-------------------------------------------------------------------------------- /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 |

<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

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 |

Hallo <%= @resource.email %>!

2 | 3 |

Ihr Passwort wurde geändert.

4 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Hallo <%= @resource.email %>!

2 | 3 |

Jemand hat einen Link angefordert, um dein Passwort zu ändern. Du kannst dies über den untenstehenden Link tun.

4 | 5 |

<%= link_to 'Passwort ändern', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

Wenn Sie dies nicht initiiert haben, ignorieren Sie bitte diese E-Mail.

8 |

Ihr Passwort wird sich erst ändern, wenn Sie auf den obigen Link zugreifen und ein neues erstellen.

9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t('.greeting', recipient: @resource.email) %>

2 | 3 |

<%= t('.message') %>

4 | 5 |

<%= t('.instruction') %>

6 | 7 |

<%= link_to t('.action'), unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= image_tag('logo.svg', width: '200px', alt: 'Logo' ) %> 4 |
5 |
6 |

<%= t('.change_your_password') %>

7 | 8 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 9 | <%= render "devise/shared/error_messages", resource: resource %> 10 | <%= f.hidden_field :reset_password_token %> 11 | 12 |
13 | <%= f.label :password, t('.new_password') %>
14 | <% if @minimum_password_length %> 15 | <%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %>
16 | <% end %> 17 | <%= f.password_field :password, autofocus: true, autocomplete: "new-password" %> 18 |
19 | 20 |
21 | <%= f.label :password_confirmation, t('.confirm_new_password') %>
22 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 23 |
24 | 25 |
26 | <%= f.submit t('.change_my_password') %> 27 |
28 | <% end %> 29 | 30 | <%= render "devise/shared/links" %> 31 | 32 |
-------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= t('.forgot_your_password') %>

3 | 4 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 5 | <%= render "devise/shared/error_messages", resource: resource %> 6 | 7 |
8 | <%= f.label :email %>
9 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 10 |
11 | 12 |
13 | <%= f.submit t('.send_me_reset_password_instructions') %> 14 |
15 | <% end %> 16 | 17 | <%= render "devise/shared/links" %> 18 | 19 |
-------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%# Note that registration is currently not used #%> 2 |
3 |
4 | <%= image_tag('logo.svg', width: '200px', alt: 'Logo' ) %> 5 |
6 |
7 |

<%= t('.title', resource: resource.model_name.human) %>

8 | 9 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 10 | <%= render "devise/shared/error_messages", resource: resource %> 11 | 12 |
13 | <%= f.label :email %>
14 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 15 |
16 | 17 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 18 |
<%= t('.currently_waiting_confirmation_for_email', email: resource.unconfirmed_email) %>
19 | <% end %> 20 | 21 |
22 | <%= f.label :password %> (<%= t('.leave_blank_if_you_don_t_want_to_change_it') %>)
23 | <%= f.password_field :password, autocomplete: "new-password" %> 24 | <% if @minimum_password_length %> 25 |
26 | <%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %> 27 | <% end %> 28 |
29 | 30 |
31 | <%= f.label :password_confirmation %>
32 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 33 |
34 | 35 |
36 | <%= f.label :current_password %> (<%= t('.we_need_your_current_password_to_confirm_your_changes') %>)
37 | <%= f.password_field :current_password, autocomplete: "current-password" %> 38 |
39 | 40 |
41 | <%= f.submit t('.update') %> 42 |
43 | <% end %> 44 | 45 |

<%= t('.cancel_my_account') %>

46 | 47 |

<%= t('.unhappy') %> <%= button_to t('.cancel_my_account'), registration_path(resource_name), data: { confirm: t('.are_you_sure') }, method: :delete %>

48 | 49 | <%= link_to t('devise.shared.links.back'), :back %> 50 |
-------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%# Note that registration is currently not used #%> 2 |
3 |
4 | <%= image_tag('logo.svg', width: '200px', alt: 'Logo' ) %> 5 |
6 |
7 |

<%= t('.sign_up') %>

8 | 9 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 10 | <%= render "devise/shared/error_messages", resource: resource %> 11 | 12 |
13 | <%= f.label :email %>
14 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 15 |
16 | 17 |
18 | <%= f.label :password %> 19 | <% if @minimum_password_length %> 20 | <%= t('devise.shared.minimum_password_length', count: @minimum_password_length) %> 21 | <% end %>
22 | <%= f.password_field :password, autocomplete: "new-password" %> 23 |
24 | 25 |
26 | <%= f.label :password_confirmation %>
27 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 28 |
29 | 30 |
31 | <%= f.submit t('.sign_up') %> 32 |
33 | <% end %> 34 | 35 | <%= render "devise/shared/links" %> 36 |
-------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= image_tag('logo.svg', width: '200px', alt: 'Logo' ) %> 4 |
5 |
6 |

<%= t('.sign_in') %>

7 | 8 | <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 9 | <%= render "devise/shared/error_messages", resource: resource %> 10 | 11 |
12 | <%= f.label :email %>
13 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 14 |
15 | 16 |
17 | <%= f.label :password %>
18 | <%= f.password_field :password, autocomplete: "current-password" %> 19 |
20 | 21 | <% if devise_mapping.rememberable? %> 22 |
23 | <%= f.check_box :remember_me %> 24 | <%= f.label :remember_me %> 25 |
26 | <% end %> 27 | 28 |
29 | <%= f.submit t('.sign_in') %> 30 |
31 | <% end %> 32 | 33 | <%= render "devise/shared/links" %> 34 | 35 |
-------------------------------------------------------------------------------- /app/views/devise/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.any? %> 2 |
3 |

4 | <%= I18n.t("errors.messages.not_saved", 5 | count: resource.errors.count, 6 | resource: resource.class.model_name.human.downcase) 7 | %> 8 |

9 |
    10 | <% resource.errors.full_messages.each do |message| %> 11 |
  • <%= message %>
  • 12 | <% end %> 13 |
14 |
15 | <% end %> 16 | 17 | <% flash.each do |key, value| %> 18 | <% key = (key == 'alert' || key == 'error') ? 'error' : 'info' %> 19 | <%= content_tag(:div, value, class: "flash flash--#{key}") %> 20 | <% end %> -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to t(".sign_in"), new_session_path(resource_name) %>
3 | <% end %> 4 | 5 | <%# remove registration %> 6 | <%# if devise_mapping.registerable? && controller_name != 'registrations' %> 7 | <%# link_to t(".sign_up"), new_registration_path(resource_name) %>
8 | <%# end %> 9 | 10 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 11 | <%= link_to t(".forgot_your_password"), new_password_path(resource_name) %>
12 | <% end %> 13 | 14 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 15 | <%= link_to t('.didn_t_receive_confirmation_instructions'), new_confirmation_path(resource_name) %>
16 | <% end %> 17 | 18 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 19 | <%= link_to t('.didn_t_receive_unlock_instructions'), new_unlock_path(resource_name) %>
20 | <% end %> 21 | 22 | <%- if devise_mapping.omniauthable? %> 23 | <%- resource_class.omniauth_providers.each do |provider| %> 24 | <%= link_to t('.sign_in_with_provider', provider: OmniAuth::Utils.camelize(provider)), omniauth_authorize_path(resource_name, provider) %>
25 | <% end %> 26 | <% end %> 27 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= image_tag('logo.svg', width: '200px', alt: 'Logo' ) %> 4 |
5 |
6 |

<%= t('.resend_unlock_instructions') %>

7 | 8 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 9 | <%= render "devise/shared/error_messages", resource: resource %> 10 | 11 |
12 | <%= f.label :email %>
13 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 14 |
15 | 16 |
17 | <%= f.submit t('.resend_unlock_instructions') %> 18 |
19 | <% end %> 20 | 21 | <%= render "devise/shared/links" %> 22 | 23 |
-------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SAAS Boilerplate 5 | 6 | <%= csrf_meta_tags %> 7 | <%= action_cable_meta_tag %> 8 | 9 | <%= render 'shared/favicon' %> 10 | 11 | 12 | <%= stylesheet_link_tag 'application', media: 'all' %> 13 | <%# javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' %> 14 | 15 | 16 | 17 | <%= yield %> 18 | 19 | -------------------------------------------------------------------------------- /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/rails_admin/_navigation.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 13 | 16 |
-------------------------------------------------------------------------------- /app/views/layouts/rails_admin/_secondary_navigation.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/shared/_favicon.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 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 | # cp 'config/database.yml.sample', 'config/database.yml' 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! 'bin/rails db:setup' 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 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update 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== Updating database ==" 21 | system! 'bin/rails db:migrate' 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system! 'bin/rails log:clear tmp:clear' 25 | 26 | puts "\n== Restarting application server ==" 27 | system! 'bin/rails restart' 28 | end 29 | -------------------------------------------------------------------------------- /bitbucket-pipelines.yml: -------------------------------------------------------------------------------- 1 | # This is a sample build configuration for Ruby. 2 | # Check our guides at https://confluence.atlassian.com/x/8r-5Mw for more examples. 3 | # Only use spaces to indent your .yml configuration. 4 | # ----- 5 | # You can specify a custom docker image from Docker Hub as your build environment. 6 | image: ruby:2.6.5 7 | 8 | definitions: 9 | steps: 10 | - step: &run-audit 11 | name: Check Vulnerabilities 12 | script: 13 | - gem install bundler-audit 14 | - bundle audit --update 15 | - step: &run-tests 16 | name: Run tests 17 | caches: 18 | - bundler 19 | script: 20 | - export DATABASE_URL=postgresql://test_user:test_user_password@localhost/ci_test 21 | - cp config/database.ci.yml config/database.yml 22 | - bundle install --path vendor/bundle 23 | - bundle exec rake db:setup 24 | - bundle exec rake db:test:prepare 25 | - bundle exec rspec spec 26 | services: 27 | - postgres 28 | caches: 29 | bundler: vendor/bundle 30 | services: 31 | postgres: 32 | image: postgres 33 | environment: 34 | POSTGRES_DB: ci_test 35 | POSTGRES_USER: test_user 36 | POSTGRES_PASSWORD: test_user_password 37 | 38 | pipelines: 39 | pull-requests: 40 | "**": 41 | - step: *run-tests 42 | branches: 43 | develop: 44 | - step: *run-tests 45 | - step: 46 | name: Deploy to Staging 47 | deployment: staging 48 | script: 49 | - git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git develop:master 50 | master: 51 | - step: *run-tests 52 | - step: 53 | name: Deploy to Production 54 | deployment: production 55 | script: 56 | - git push https://heroku:$HEROKU_API_KEY@git.heroku.com/$HEROKU_APP_NAME.git master 57 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails/all' 6 | # Pick the frameworks you want: 7 | # require 'active_model/railtie' 8 | # require 'active_job/railtie' 9 | # require 'active_record/railtie' 10 | # require 'active_storage/engine' 11 | # require 'action_controller/railtie' 12 | # require 'action_mailer/railtie' 13 | # require 'action_view/railtie' 14 | # require 'action_cable/engine' 15 | # require 'rails/test_unit/railtie' 16 | 17 | # We had to require this module manually. No idea why... 18 | require 'search_object' 19 | require 'search_object/plugin/graphql' 20 | 21 | # Require the gems listed in Gemfile, including any gems 22 | # you've limited to :test, :development, or :production. 23 | Bundler.require(*Rails.groups) 24 | 25 | # Base Module for our App 26 | module RailsDeviseGraphql 27 | # Application entry point 28 | class Application < Rails::Application 29 | # Initialize configuration defaults for originally generated Rails version. 30 | config.load_defaults 6.0 31 | 32 | config.autoloader = :classic 33 | 34 | config.autoload_paths << Rails.root.join('lib') 35 | config.eager_load_paths << Rails.root.join('lib') 36 | 37 | # Settings in config/environments/* take precedence over those specified here. 38 | # Application configuration can go into files in config/initializers 39 | # -- all .rb files in that directory are automatically loaded after loading 40 | # the framework and any gems in your application. 41 | 42 | # Only loads a smaller set of middleware suitable for API only apps. 43 | # Middleware like session, flash, cookies can be added back manually. 44 | # Skip views, helpers and assets when generating a new resource. 45 | 46 | config.time_zone = 'Berlin' 47 | 48 | # devise uses this for default from options 49 | config.action_mailer.default_options = { from: ENV['DEVISE_MAILER_FROM'] } 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /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: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: rails-devise-graphql-production 11 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | QDH2QZpHpGTWG7FS0osN0ZNOwyRIbZoeTk/aT6iWIjFGKRy9w5mNpECufZyMG0LxydIOpA1elHvqcD+GhvzkkTzBt3W1o0a2RQlIfzN9Pc8dsOtXVcYRcNr4it9cMEr7OGkHJP6I/KZp9Xe8KF+NF7OmjRdTW0D39vjJUGwvKtA+zv174ema+hW9wFeku5d0V80mCVe8yL51vWG6ZnBGmaWrESJtRL5cFFT4q93ZzzYG39c7Zpn268h8XUgTvkd4teO0+RV4ofhgff7bV3le4vxSqt1Ocq2OFP66+wS67AH4G7TVTQ8MFZHcSATgaw3ajRkUfMIqN7wamiXj8fsz5iKPCrZNLDiIJ9rI65GgdDjuJozxTj+Tqyvs7S0qNNAfS0VA7ieIpqfXT3ogyPXQAjYzXH0CSH10nQ7H--iDeTJFe6e2H7K3Yz--nYtHPdraCkU6xN2U9KgMvQ== -------------------------------------------------------------------------------- /config/database.ci.yml: -------------------------------------------------------------------------------- 1 | # this file is used in the build process of bitbucket pipelines CI 2 | default: &default 3 | adapter: postgresql 4 | encoding: unicode 5 | # For details on connection pooling, see rails configuration guide 6 | # http://guides.rubyonrails.org/configuring.html#database-pooling 7 | pool: 5 8 | host: localhost 9 | 10 | development: 11 | <<: *default 12 | database: ci_development 13 | 14 | # Warning: The database defined as "test" will be erased and 15 | # re-generated from your development database when you run "rake". 16 | # Do not set this db to the same as development or production. 17 | test: 18 | <<: *default 19 | database: ci_test 20 | username: test_user 21 | password: test_user_password 22 | 23 | production: 24 | <<: *default 25 | database: ci_production -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: postgresql 9 | timeout: 5000 10 | 11 | development: 12 | <<: *default 13 | database: rails_devise_graphql_development 14 | 15 | # Warning: The database defined as "test" will be erased and 16 | # re-generated from your development database when you run "rake". 17 | # Do not set this db to the same as development or production. 18 | test: 19 | <<: *default 20 | database: db/rails_devise_graphql_test 21 | 22 | # production: 23 | # <<: *default 24 | # database: db/production.sqlite3 25 | -------------------------------------------------------------------------------- /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 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join('tmp/caching-dev.txt').exist? 20 | config.action_controller.perform_caching = true 21 | 22 | config.cache_store = :memory_store 23 | config.public_file_server.headers = { 24 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 25 | } 26 | else 27 | config.action_controller.perform_caching = false 28 | 29 | config.cache_store = :null_store 30 | end 31 | 32 | # Store uploaded files on the local file system (see config/storage.yml for options) 33 | config.active_storage.service = :local 34 | 35 | config.action_mailer.raise_delivery_errors = true 36 | config.action_mailer.perform_deliveries = false 37 | config.action_mailer.perform_caching = false 38 | 39 | # Print deprecation notices to the Rails logger. 40 | config.active_support.deprecation = :log 41 | 42 | # Raise an error on page load if there are pending migrations. 43 | config.active_record.migration_error = :page_load 44 | 45 | # Highlight code that triggered database queries in logs. 46 | config.active_record.verbose_query_logs = true 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | 51 | # Use an evented file watcher to asynchronously detect changes in source code, 52 | # routes, locales, etc. This feature depends on the listen gem. 53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 54 | 55 | config.action_mailer.default_url_options = { host: ENV['DEFAULT_URL'] } 56 | end 57 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 28 | # config.action_controller.asset_host = 'http://assets.example.com' 29 | 30 | # Specifies the header that your server uses for sending files. 31 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 32 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 33 | 34 | # Store uploaded files on the local file system (see config/storage.yml for options) 35 | config.active_storage.service = :local 36 | 37 | # Mount Action Cable outside main process or domain 38 | # config.action_cable.mount_path = nil 39 | # config.action_cable.url = 'wss://example.com/cable' 40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | config.force_ssl = true 44 | 45 | # Use the lowest log level to ensure availability of diagnostic information 46 | # when problems arise. 47 | config.log_level = :debug 48 | 49 | # Prepend all log lines with the following tags. 50 | config.log_tags = [:request_id] 51 | 52 | # Use a different cache store in production. 53 | # config.cache_store = :mem_cache_store 54 | 55 | # Use a real queuing backend for Active Job (and separate queues per environment) 56 | # config.active_job.queue_adapter = :resque 57 | # config.active_job.queue_name_prefix = "rails-devise-graphql_#{Rails.env}" 58 | 59 | config.action_mailer.perform_caching = false 60 | 61 | # Ignore bad email addresses and do not raise email delivery errors. 62 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 63 | config.action_mailer.raise_delivery_errors = false 64 | config.action_mailer.perform_deliveries = true 65 | config.action_mailer.delivery_method = :smtp 66 | config.action_mailer.smtp_settings = { 67 | address: ENV['SMTP_ADDRESS'], 68 | port: ENV['SMTP_PORT'].to_i, 69 | domain: ENV['SMTP_DOMAIN'], 70 | user_name: ENV['SMTP_USERNAME'], 71 | password: ENV['SMTP_PASSWORD'], 72 | authentication: ENV['SMTP_AUTH'], 73 | enable_starttls_auto: ENV['SMTP_ENABLE_STARTTLS_AUTO'] == 'true' 74 | } 75 | config.action_mailer.default_url_options = { host: ENV['DEFAULT_URL'] } 76 | 77 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 78 | # the I18n.default_locale when a translation cannot be found). 79 | config.i18n.fallbacks = true 80 | 81 | # Send deprecation notices to registered listeners. 82 | config.active_support.deprecation = :notify 83 | 84 | # Use default logging formatter so that PID and timestamp are not suppressed. 85 | config.log_formatter = ::Logger::Formatter.new 86 | 87 | # Use a different logger for distributed setups. 88 | # require 'syslog/logger' 89 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 90 | 91 | if ENV['RAILS_LOG_TO_STDOUT'].present? 92 | logger = ActiveSupport::Logger.new($stdout) 93 | logger.formatter = config.log_formatter 94 | config.logger = ActiveSupport::TaggedLogging.new(logger) 95 | end 96 | 97 | # Do not dump schema after migrations. 98 | config.active_record.dump_schema_after_migration = false 99 | end 100 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # The test environment is used exclusively to run your application's 7 | # test suite. You never need to work with it otherwise. Remember that 8 | # your test database is "scratch space" for the test suite and is wiped 9 | # and recreated between test runs. Don't rely on the data there! 10 | config.cache_classes = true 11 | 12 | # Do not eager load code on boot. This avoids loading your whole application 13 | # just for the purpose of running a single test. If you are using a tool that 14 | # preloads Rails for running tests, you may have to set it to true. 15 | config.eager_load = false 16 | 17 | # Configure public file server for tests with Cache-Control for performance. 18 | config.public_file_server.enabled = true 19 | config.public_file_server.headers = { 20 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 21 | } 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | 27 | # Raise exceptions instead of rendering exception templates. 28 | config.action_dispatch.show_exceptions = false 29 | 30 | # Disable request forgery protection in test environment. 31 | config.action_controller.allow_forgery_protection = false 32 | 33 | # Store uploaded files on the local file system in a temporary directory 34 | config.active_storage.service = :test 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | # Tell Action Mailer not to deliver emails to the real world. 39 | # The :test delivery method accumulates sent emails in the 40 | # ActionMailer::Base.deliveries array. 41 | config.action_mailer.delivery_method = :test 42 | 43 | # Print deprecation notices to the stderr. 44 | config.active_support.deprecation = :stderr 45 | 46 | # Raises error for missing translations 47 | # config.action_view.raise_on_missing_translations = true 48 | end 49 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # ActiveSupport::Reloader.to_prepare do 5 | # ApplicationController.renderer.defaults.merge!( 6 | # http_host: 'example.org', 7 | # https: false 8 | # ) 9 | # end 10 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 8 | # Rails.backtrace_cleaner.remove_silencers! 9 | -------------------------------------------------------------------------------- /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 ENV['CLIENT_URL'] ? ENV['CLIENT_URL'].split(',').map(&:strip) : '0.0.0.0:8000' 13 | 14 | # origins '*' 15 | resource '*', 16 | headers: :any, 17 | expose: ['Authorization'], 18 | methods: %i[get post options delete put] 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /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 sensitive parameters which will be filtered from the log file. 6 | Rails.application.config.filter_parameters += %i[password password_confirmation] 7 | -------------------------------------------------------------------------------- /config/initializers/friendly_id.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # FriendlyId Global Configuration 4 | # 5 | # Use this to set up shared configuration options for your entire application. 6 | # Any of the configuration options shown here can also be applied to single 7 | # models by passing arguments to the `friendly_id` class method or defining 8 | # methods in your model. 9 | # 10 | # To learn more, check out the guide: 11 | # 12 | # http://norman.github.io/friendly_id/file.Guide.html 13 | FriendlyId.defaults do |config| 14 | # ## Reserved Words 15 | # 16 | # Some words could conflict with Rails's routes when used as slugs, or are 17 | # undesirable to allow as slugs. Edit this list as needed for your app. 18 | config.use :reserved 19 | 20 | config.reserved_words = %w[new edit index session login logout users admin 21 | stylesheets assets javascripts images] 22 | 23 | # ## Friendly Finders 24 | # 25 | # Uncomment this to use friendly finders in all models. By default, if 26 | # you wish to find a record by its friendly id, you must do: 27 | # 28 | # MyModel.friendly.find('foo') 29 | # 30 | # If you uncomment this, you can do: 31 | # 32 | # MyModel.find('foo') 33 | # 34 | # This is significantly more convenient but may not be appropriate for 35 | # all applications, so you must explicity opt-in to this behavior. You can 36 | # always also configure it on a per-model basis if you prefer. 37 | # 38 | # Something else to consider is that using the :finders addon boosts 39 | # performance because it will avoid Rails-internal code that makes runtime 40 | # calls to `Module.extend`. 41 | # 42 | # config.use :finders 43 | # 44 | # ## Slugs 45 | # 46 | # Most applications will use the :slugged module everywhere. If you wish 47 | # to do so, uncomment the following line. 48 | # 49 | # config.use :slugged 50 | # 51 | # By default, FriendlyId's :slugged addon expects the slug column to be named 52 | # 'slug', but you can change it if you wish. 53 | # 54 | # config.slug_column = 'slug' 55 | # 56 | # When FriendlyId can not generate a unique ID from your base method, it appends 57 | # a UUID, separated by a single dash. You can configure the character used as the 58 | # separator. If you're upgrading from FriendlyId 4, you may wish to replace this 59 | # with two dashes. 60 | # 61 | # config.sequence_separator = '-' 62 | # 63 | # ## Tips and Tricks 64 | # 65 | # ### Controlling when slugs are generated 66 | # 67 | # As of FriendlyId 5.0, new slugs are generated only when the slug field is 68 | # nil, but if you're using a column as your base method can change this 69 | # behavior by overriding the `should_generate_new_friendly_id` method that 70 | # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave 71 | # more like 4.0. 72 | # 73 | # config.use Module.new { 74 | # def should_generate_new_friendly_id? 75 | # slug.blank? || _changed? 76 | # end 77 | # } 78 | # 79 | # FriendlyId uses Rails's `parameterize` method to generate slugs, but for 80 | # languages that don't use the Roman alphabet, that's not usually sufficient. 81 | # Here we use the Babosa library to transliterate Russian Cyrillic slugs to 82 | # ASCII. If you use this, don't forget to add "babosa" to your Gemfile. 83 | # 84 | # config.use Module.new { 85 | # def normalize_friendly_id(text) 86 | # text.to_slug.normalize! :transliterations => [:russian, :latin] 87 | # end 88 | # } 89 | end 90 | -------------------------------------------------------------------------------- /config/initializers/generators.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.config.generators do |g| 4 | g.orm :active_record, primary_key_type: :uuid 5 | g.orm :active_record, foreign_key_type: :uuid 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/graphql_auth.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | GraphQL::Auth.configure do |config| 4 | config.token_lifespan = 4.hours 5 | config.jwt_secret_key = ENV['DEVISE_SECRET_KEY'] 6 | config.app_url = ENV['CLIENT_URL'] 7 | 8 | config.user_type = '::Types::Users::UserType' 9 | 10 | # Devise allowed actions 11 | # Don't forget to enable the lockable setting in your Devise user model if you plan on using the lock_account feature 12 | config.allow_sign_up = true 13 | config.allow_lock_account = true 14 | config.allow_unlock_account = true 15 | config.allow_email_confirmable = true 16 | 17 | # Allow custom mutations for signup and update account 18 | # config.sign_up_mutation = '::Mutations::Auth::SignUp' 19 | # config.update_account_mutation = '::Mutations::Auth::UpdateAccount' 20 | end 21 | -------------------------------------------------------------------------------- /config/initializers/graphql_errors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | GraphQL::Errors.configure(GraphqlSchema) do 4 | rescue_from ActiveRecord::RecordNotFound do |_exception| 5 | nil 6 | end 7 | 8 | rescue_from CanCan::AccessDenied do |exception| 9 | GraphQL::ExecutionError.new(exception.message) 10 | end 11 | 12 | rescue_from ActiveRecord::RecordInvalid do |exception| 13 | GraphQL::ExecutionError.new(exception.record.errors.full_messages.join("\n")) 14 | end 15 | 16 | # rescue_from StandardError do |exception| 17 | # GraphQL::ExecutionError.new("Please try to execute the query for this field later") 18 | # end 19 | 20 | # rescue_from CustomError do |exception, object, arguments, context| 21 | # error = GraphQL::ExecutionError.new("Error found!") 22 | # firstError.path = context.path + ["myError"] 23 | # context.add_error(firstError) 24 | # end 25 | end 26 | -------------------------------------------------------------------------------- /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/locale.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Whitelist locales available for the application 4 | I18n.available_locales = %i[en de] 5 | 6 | # Set default locale to something other than :en 7 | I18n.default_locale = :en 8 | 9 | I18n.load_path += Dir[Rails.root.join('config/locales/**/*yml').to_s] 10 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new mime types for use in respond_to blocks: 5 | # Mime::Type.register "text/richtext", :rtf 6 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults_6_0.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | # 4 | # This file contains migration options to ease your Rails 6.0 upgrade. 5 | # 6 | # Once upgraded flip defaults one by one to migrate to the new default. 7 | # 8 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 9 | 10 | # Don't force requests from old versions of IE to be UTF-8 encoded. 11 | # Rails.application.config.action_view.default_enforce_utf8 = false 12 | 13 | # Embed purpose and expiry metadata inside signed and encrypted 14 | # cookies for increased security. 15 | # 16 | # This option is not backwards compatible with earlier Rails versions. 17 | # It's best enabled when your entire app is migrated and stable on 6.0. 18 | # Rails.application.config.action_dispatch.use_cookies_with_metadata = true 19 | 20 | # Change the return value of `ActionDispatch::Response#content_type` to Content-Type header without modification. 21 | # Rails.application.config.action_dispatch.return_only_media_type_on_content_type = false 22 | 23 | # Return false instead of self when enqueuing is aborted from a callback. 24 | # Rails.application.config.active_job.return_false_on_aborted_enqueue = true 25 | 26 | # Send Active Storage analysis and purge jobs to dedicated queues. 27 | # Rails.application.config.active_storage.queues.analysis = :active_storage_analysis 28 | # Rails.application.config.active_storage.queues.purge = :active_storage_purge 29 | 30 | # When assigning to a collection of attachments declared via `has_many_attached`, replace existing 31 | # attachments instead of appending. Use #attach to add new attachments without replacing existing ones. 32 | # Rails.application.config.active_storage.replace_on_assign_to_many = true 33 | 34 | # Use ActionMailer::MailDeliveryJob for sending parameterized and normal mail. 35 | # 36 | # The default delivery jobs (ActionMailer::Parameterized::DeliveryJob, ActionMailer::DeliveryJob), 37 | # will be removed in Rails 6.1. This setting is not backwards compatible with earlier Rails versions. 38 | # If you send mail in the background, job workers need to have a copy of 39 | # MailDeliveryJob to ensure all delivery jobs are processed properly. 40 | # Make sure your entire app is migrated and stable on 6.0 before using this setting. 41 | # Rails.application.config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob" 42 | 43 | # Enable the same cache key to be reused when the object being cached of type 44 | # `ActiveRecord::Relation` changes by moving the volatile information (max updated at and count) 45 | # of the relation's cache key into the cache version to support recycling cache key. 46 | # Rails.application.config.active_record.collection_cache_versioning = true 47 | -------------------------------------------------------------------------------- /config/initializers/rack_attack.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rack::Attack.enabled = false if ENV['RACK_ATTACK_ENABLED'] == 'false' 4 | # otherwise default ON 5 | 6 | # rubocop:disable Metrics/ClassLength 7 | module Rack 8 | # Define roles for blocking users 9 | class Attack 10 | def self.user_session?(req) 11 | # stored in cookies 12 | if req.env['rack.request.cookie_hash'] && req.env['rack.request.cookie_hash']['user'] 13 | user = JSON.parse(req.env['rack.request.cookie_hash']['user']) 14 | return true if user && user['email'].present? 15 | end 16 | 17 | # devise is storing user id in rack.session after a valid authentication 18 | req.env['rack.session'] && 19 | req.env['rack.session']['warden.user.user.key'] && 20 | req.env['rack.session']['warden.user.user.key'][0][0] 21 | end 22 | 23 | # Always allow requests from localhost 24 | # (blocklist & throttles are skipped) 25 | Rack::Attack.safelist('allow from localhost') do |req| 26 | # Requests are allowed if the return value is truthy 27 | req.ip == '127.0.0.1' || req.ip == '::1' 28 | end 29 | 30 | # Always allow requests from localhost 31 | # (blocklist & throttles are skipped) 32 | whitelist = ENV['RACK_WHITELIST'] ? ENV['RACK_WHITELIST'].split(',') : [] 33 | Rack::Attack.safelist('allow from ENV ATTACK_WHITELIST') do |req| 34 | # Requests are allowed if the return value is truthy 35 | whitelist.include?(req.ip) 36 | end 37 | 38 | # If any single client IP is making tons of requests, then they're 39 | # probably malicious or a poorly-configured scraper. Either way, they 40 | # don't deserve to hog all of the app server's CPU. Cut them off! 41 | # 42 | # Note: If you're serving assets through rack, those requests may be 43 | # counted by rack-attack and this throttle may be activated too 44 | # quickly. If so, enable the condition to exclude them from tracking. 45 | 46 | # Throttle all requests by IP 47 | request_limit = (ENV['ATTACK_REQUEST_LIMIT'] || 300).to_i 48 | request_period = (ENV['ATTACK_REQUEST_PERIOD_IN_MINUTES'] || 5).to_i 49 | ban_time = (ENV['ATTACK_REQUEST_BAN_TIME_IN_MINUTES'] || 30).to_i 50 | (1..3).each do |level| 51 | # level 1 -> 300 requests in 5 minutes (60rpm), ban for 30 minutes 52 | # level 2 -> 600 requests in 25 minutes (24rpm), ban for 60 minutes 53 | # level 3 -> 900 requests in 125 minutes (7.2rpm), ban for 90 minutes 54 | throttle( 55 | "request/ip/#{level}", 56 | limit: request_limit * level, 57 | period: (request_period**level).minutes, 58 | bantime: (ban_time * level).minutes 59 | ) do |req| 60 | req.ip if !req.path.start_with?('/assets') && !Rack::Attack.user_session?(req) 61 | end 62 | end 63 | 64 | # Throttle authenticated requests by IP 65 | request_limit = (ENV['ATTACK_AUTHENTICATED_REQUEST_LIMIT'] || 500).to_i 66 | request_period = (ENV['ATTACK_AUTHENTICATED_REQUEST_PERIOD_IN_MINUTES'] || 5).to_i 67 | ban_time = (ENV['ATTACK_AUTHENTICATED_REQUEST_BAN_TIME_IN_MINUTES'] || 10).to_i 68 | (1..3).each do |level| 69 | # level 1 -> 500 requests in 5 minutes (100rpm), ban for 10 minute 70 | # level 2 -> 1000 requests in 25 minutes (40rpm), ban for 20 minutes 71 | # level 3 -> 1500 requests in 125 minutes (12rpm), ban for 30 minutes 72 | throttle( 73 | "request/authenticated/ip/#{level}", 74 | limit: request_limit * level, 75 | period: (request_period**level).minutes, 76 | bantime: (ban_time * level).minutes 77 | ) do |req| 78 | req.ip if !req.path.start_with?('/assets') && Rack::Attack.user_session?(req) 79 | end 80 | end 81 | 82 | ### Prevent Brute-Force Attacks ### 83 | 84 | # The most common brute-force login attack is a brute-force password 85 | # attack where an attacker simply tries a large number of emails and 86 | # passwords to see if any credentials match. 87 | # 88 | # Another common method of attack is to use a swarm of computers with 89 | # different IPs to try brute-forcing a password for a specific account. 90 | 91 | # Key: "rack::attack:#{Time.now.to_i/:period}:logins/ip:#{req.ip}" 92 | # LOGIN / SIGN UP 93 | 94 | auth_limit = (ENV['ATTACK_AUTH_LIMIT'] || 30).to_i 95 | auth_period = (ENV['ATTACK_AUTH_PERIOD_IN_MINUTES'] || 10).to_i 96 | auth_ban_time = (ENV['ATTACK_AUTH_BAN_TIME_IN_MINUTES'] || 30).to_i 97 | 98 | (1..3).each do |level| 99 | # level 1 -> 30 auth requests in 10 minutes, ban for 30 minutes 100 | # level 2 -> 60 auth requests in 100 minutes, ban for 60 minutes 101 | # level 3 -> 90 auth requests per 1000 minutes (16,5 hours), ban for 120 minutes 102 | 103 | # Devise sign_in 104 | throttle( 105 | "request/devise/ip/#{level}", 106 | limit: auth_limit * level, 107 | period: (auth_period**level).minutes, 108 | bantime: (auth_ban_time * level).minutes 109 | ) do |req| 110 | req.ip if req.path == '/users/sign_in' && (req.post? || req.put?) 111 | end 112 | 113 | # Devise password reset 114 | throttle( 115 | "request/devise/password/ip/#{level}", 116 | limit: auth_limit * level, 117 | period: (auth_period**level).minutes, 118 | bantime: (auth_ban_time * level).minutes 119 | ) do |req| 120 | req.ip if req.path == '/users/password' && (req.post? || req.put?) 121 | end 122 | 123 | # GraphQL login & signup via api 124 | throttle( 125 | "request/graphql/auth/ip/#{level}", 126 | limit: auth_limit * level, 127 | period: (auth_period**level).minutes, 128 | bantime: (auth_ban_time * level).minutes 129 | ) do |req| 130 | if req.path == '/graphql' && req.post? && req.body 131 | params = JSON.parse(req.body.read) 132 | req.body.rewind # needed a rewind after parsing it to JSON 133 | if params['query'].include?('signIn') || 134 | params['query'].include?('signUp') 135 | req.ip 136 | end 137 | end 138 | end 139 | 140 | # GraphQL password reset 141 | throttle( 142 | "request/graphql/password_reset/ip/#{level}", 143 | limit: auth_limit * level, 144 | period: (auth_period**level).minutes, 145 | bantime: (auth_ban_time * level).minutes 146 | ) do |req| 147 | if req.path == '/graphql' && req.post? && req.body 148 | params = JSON.parse(req.body.read) 149 | req.body.rewind # needed a rewind after parsing it to JSON 150 | req.ip if params['query'].include?('resetPassword') 151 | end 152 | end 153 | end 154 | 155 | # Actions 156 | # Limits actions like locking a user or create a message 157 | public_action_limit = (ENV['ATTACK_PUBLIC_ACTION_LIMIT'] || 30).to_i 158 | public_action_period = (ENV['ATTACK_PUBLIC_ACTION_PERIOD_IN_MINUTES'] || 60).to_i 159 | public_action_ban_time = (ENV['ATTACK_PUBLIC_ACTION_BAN_TIME_IN_MINUTES'] || 30).to_i 160 | 161 | throttle( 162 | 'request/public/action/ip', 163 | limit: public_action_limit, 164 | period: public_action_period.minutes, 165 | bantime: public_action_ban_time.minutes 166 | ) do |req| 167 | if req.path == '/graphql' && req.post? && req.body 168 | params = JSON.parse(req.body.read) 169 | req.body.rewind # needed a rewind after parsing it to JSON 170 | if params['query'].include?('unlockAccount') || 171 | params['query'].include?('lockAccount') || 172 | params['query'].include?('createConversation') || 173 | params['query'].include?('createMessage') 174 | req.ip 175 | end 176 | end 177 | end 178 | 179 | ### Custom Throttle Response ### 180 | # Add a helpful response about the rate limit for clients 181 | # For responses that did not exceed a throttle limit, Rack::Attack annotates the env with match data: 182 | # request.env['rack.attack.throttle_data'][name] 183 | # => { discriminator: d, count: n, period: p, limit: l, epoch_time: t } 184 | self.throttled_response = lambda do |_env| 185 | # match_data = env['rack.attack.match_data'] 186 | # now = match_data[:epoch_time] 187 | 188 | headers = {} 189 | 190 | [429, headers, [{ 'errors': [{ 'message': 'Too many requests' }] }.to_json]] 191 | end 192 | end 193 | end 194 | 195 | # Error reporting 196 | ActiveSupport::Notifications.subscribe(/rack_attack/) do |name, start, _finish, _request_id, payload| 197 | # request object available in payload[:request] 198 | if %i[throttle blacklist].include? payload[:request].env['rack.attack.match_type'] 199 | error = [ 200 | payload[:request].env['rack.attack.match_type'], 201 | name, 202 | start, 203 | payload[:request].ip, 204 | payload[:request].request_method, 205 | payload[:request].fullpath 206 | ].join(' ') 207 | Rails.logger.warn error 208 | # Rollbar.warning(error, payload[:request].env) 209 | end 210 | end 211 | # rubocop:enable Metrics/ClassLength 212 | -------------------------------------------------------------------------------- /config/initializers/rails_admin.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RailsAdmin.config do |config| 4 | ### Popular gems integration 5 | 6 | ## == Devise == 7 | config.parent_controller = 'ApplicationController' 8 | 9 | config.authenticate_with do 10 | warden.authenticate! scope: :user 11 | end 12 | config.current_user_method(&:current_superadmin) 13 | 14 | ## == Cancan == 15 | config.authorize_with :cancancan 16 | 17 | ## == Pundit == 18 | # config.authorize_with :pundit 19 | 20 | ## == PaperTrail == 21 | # config.audit_with :paper_trail, 'User', 'PaperTrail::Version' # PaperTrail >= 3.0.0 22 | 23 | ### More at https://github.com/sferik/rails_admin/wiki/Base-configuration 24 | 25 | ## == Gravatar integration == 26 | ## To disable Gravatar integration in Navigation Bar set to false 27 | # config.show_gravatar = true 28 | 29 | # config.excluded_models << 'Place' 30 | 31 | config.actions do 32 | dashboard # mandatory 33 | index # mandatory 34 | new 35 | export 36 | bulk_delete 37 | show 38 | edit 39 | delete 40 | # show_in_app 41 | 42 | ## With an audit adapter, you can add: 43 | # history_index 44 | # history_show 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /config/locales/activerecord/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | attributes: 3 | slug: Pfad 4 | activerecord: 5 | attributes: 6 | company: 7 | name: Name des Unternehmens 8 | users_count: Anzahl Mitarbeiter 9 | user: 10 | confirmation_sent_at: Bestätigung gesendet am 11 | confirmation_token: Bestätigungs-Token 12 | confirmed_at: Bestätigt am 13 | created_at: Erstellt am 14 | current_password: Bisheriges Passwort 15 | current_sign_in_at: Aktuelle Anmeldung vom 16 | current_sign_in_ip: IP der aktuellen Anmeldung 17 | email: E-Mail 18 | encrypted_password: Verschlüsseltes Passwort 19 | failed_attempts: Fehlversuche 20 | last_sign_in_at: Letzte Anmeldung am 21 | last_sign_in_ip: IP der letzten Anmeldung 22 | locked_at: Gesperrt am 23 | password: Passwort 24 | password_confirmation: Passwortbestätigung 25 | remember_created_at: Angemeldet bleiben vom 26 | remember_me: Angemeldet bleiben 27 | reset_password_sent_at: Passwort-Zurücksetzen-E-Mail gesendet am 28 | reset_password_token: Passwort-Zurücksetzen-Token 29 | sign_in_count: Anzahl Anmeldungen 30 | unconfirmed_email: Unbestätigte E-Mail-Adresse 31 | unlock_token: Entsperrungs-Token 32 | updated_at: Aktualisiert am 33 | first_name: Vorname 34 | last_name: Nachname 35 | role: Rolle 36 | models: 37 | user: 38 | one: Benutzer 39 | other: Benutzer 40 | company: 41 | one: Unternehmen 42 | other: Unternehmen 43 | -------------------------------------------------------------------------------- /config/locales/activerecord/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | attributes: 3 | slug: Slug 4 | activerecord: 5 | attributes: 6 | company: 7 | name: Name of company 8 | users_count: Number of employees 9 | user: 10 | confirmation_sent_at: Confirmation sent at 11 | confirmation_token: Confirmation token 12 | confirmed_at: Confirmed at 13 | created_at: Created at 14 | current_password: Current password 15 | current_sign_in_at: Current sign in at 16 | current_sign_in_ip: Current sign in IP 17 | email: Email 18 | encrypted_password: Encrypted password 19 | failed_attempts: Failed attempts 20 | last_sign_in_at: Last sign in at 21 | last_sign_in_ip: Last sign in IP 22 | locked_at: Locked at 23 | password: Password 24 | password_confirmation: Password confirmation 25 | remember_created_at: Remember created at 26 | remember_me: Remember me 27 | reset_password_sent_at: Reset password sent at 28 | reset_password_token: Reset password token 29 | sign_in_count: Sign in count 30 | unconfirmed_email: Unconfirmed email 31 | unlock_token: Unlock token 32 | updated_at: Updated at 33 | first_name: Firstname 34 | last_name: Lastname 35 | role: Role 36 | models: 37 | user: 38 | one: User 39 | other: Users 40 | company: 41 | one: Company 42 | other: Companies 43 | -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | hello: "Hallo Welt" 3 | -------------------------------------------------------------------------------- /config/locales/devise/invitable/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | devise: 3 | failure: 4 | invited: "You have a pending invitation, accept it to finish creating your account." 5 | invitations: 6 | send_instructions: "An invitation email has been sent to %{email}." 7 | invitation_token_invalid: "The invitation token provided is not valid!" 8 | updated: "Your password was set successfully. You are now signed in." 9 | updated_not_active: "Your password was set successfully." 10 | no_invitations_remaining: "No invitations remaining" 11 | invitation_removed: "Your invitation was removed." 12 | new: 13 | header: "Send invitation" 14 | submit_button: "Send an invitation" 15 | edit: 16 | header: "Set your password" 17 | submit_button: "Set my password" 18 | mailer: 19 | invitation_instructions: 20 | subject: "Invitation instructions" 21 | hello: "Hello %{email}" 22 | someone_invited_you: "Someone has invited you to %{url}, you can accept it through the link below." 23 | accept: "Accept invitation" 24 | accept_until: "This invitation will be due in %{due_date}." 25 | ignore: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password." 26 | time: 27 | formats: 28 | devise: 29 | mailer: 30 | invitation_instructions: 31 | accept_until_format: "%B %d, %Y %I:%M %p" 32 | -------------------------------------------------------------------------------- /config/locales/devise/views/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | devise: 3 | confirmations: 4 | confirmed: Ihre E-Mail-Adresse wurde erfolgreich bestätigt. 5 | new: 6 | resend_confirmation_instructions: Anleitung zur Bestätigung noch mal schicken 7 | send_instructions: Sie erhalten in wenigen Minuten eine E-Mail, mit der Sie Ihre Registrierung bestätigen können. 8 | send_paranoid_instructions: Falls Ihre E-Mail-Adresse in unserer Datenbank existiert, erhalten Sie in wenigen Minuten eine E-Mail, mit der Sie Ihre Registrierung bestätigen können. 9 | failure: 10 | already_authenticated: Sie sind bereits angemeldet. 11 | inactive: Ihr Konto ist noch nicht aktiv. 12 | invalid: "%{authentication_keys} oder Passwort ungültig." 13 | last_attempt: Sie haben noch einen Versuch, bis Ihr Konto gesperrt wird. 14 | locked: Ihr Konto ist gesperrt. 15 | not_found_in_database: "%{authentication_keys} oder Passwort ungültig." 16 | timeout: Ihre Sitzung ist abgelaufen. Bitte melden Sie sich erneut an. 17 | unauthenticated: Sie müssen sich anmelden oder registrieren, bevor Sie fortfahren können. 18 | unconfirmed: Sie müssen Ihr Konto bestätigen, bevor Sie fortfahren können. 19 | invited: "Sie haben eine ausstehende Einladung, nehmen Sie diese an, um die Erstellung Ihres Kontos abzuschließen." 20 | mailer: 21 | confirmation_instructions: 22 | action: Mein Konto bestätigen 23 | greeting: Willkommen %{recipient}! 24 | instruction: "Folgen Sie dem untenstehenden Link, um Ihr Konto zu bestätigen:" 25 | subject: Anleitung zur Bestätigung Ihres Kontos 26 | email_changed: 27 | greeting: Hallo %{recipient}! 28 | message: Wir schreiben Ihnen, um Sie darüber zu informieren, dass Ihre E-Mail-Adresse zu %{email} geändert wurde. 29 | subject: E-Mail-Adresse geändert 30 | password_change: 31 | greeting: Hallo %{recipient}! 32 | message: Wir schreiben Ihnen, um Sie darüber zu informieren, dass Ihr Passwort geändert wurde. 33 | subject: Passwort geändert 34 | reset_password_instructions: 35 | action: Passwort ändern 36 | greeting: Hallo %{recipient}! 37 | instruction: Jemand hat einen Link angefordert, um Ihr Passwort zu ändern. Klicken Sie auf den unten aufgeführten Link um das Passwort zu ändern. 38 | instruction_2: Bitte ignorieren Sie diese E-Mail, wenn Sie kein neues Passwort angefordert haben. 39 | instruction_3: Das Passwort wird nicht geändert bis Sie den obenstehenden Link abrufen und ein neues Passwort bestimmen. 40 | subject: Anleitung für das Zurücksetzen Ihres Passworts 41 | unlock_instructions: 42 | action: Mein Konto entsperren 43 | greeting: Hallo %{recipient}! 44 | instruction: "Folgen Sie dem untenstehenden Link, um Ihr Konto zu entsperren:" 45 | message: Ihr Konto wurde aufgrund einer großen Anzahl von fehlgeschlagenen Anmeldeversuchen gesperrt. 46 | subject: Anleitung für die Konto-Freischaltung 47 | invitation_instructions: 48 | subject: "Ihre Einladung annehmen" 49 | hello: "Hallo %{email}" 50 | someone_invited_you: "Jemand hat Sie zu %{url} eingeladen, akzeptieren Sie die Einladung über den angefügten Link." 51 | accept: "Einladung annehmen" 52 | accept_until: "Diese Einladung ist gültig bis %{due_date}." 53 | ignore: "Wenn Sie die Einladung nicht annehmen möchten, ignorieren Sie diese E-Mail. Ihr Konto wird nicht erstellt, solange sie nicht über den Link Ihr Passwort vergeben." 54 | invitations: 55 | send_instructions: "Eine Einladung wurde versendet an %{email}." 56 | invitation_token_invalid: "Der Token zur Einladung ist nicht gültig!" 57 | updated: "Ihr Passwort wurde erfolgreich gestzt. Sie sind nun eingeloggt." 58 | updated_not_active: "Ihr Passwort wurde erfolgreich gesetzt." 59 | no_invitations_remaining: "Keine Einladungen mehr übrig." 60 | invitation_removed: "Ihre Einladung wurde entfernt." 61 | new: 62 | header: "Einladung versenden" 63 | submit_button: "Versende eine Einladung" 64 | edit: 65 | header: "Vergebe ein Passwort" 66 | submit_button: "Mein Passwort setzen" 67 | omniauth_callbacks: 68 | failure: Sie konnten nicht mit Ihrem %{kind}-Konto angemeldet werden, weil "%{reason}". 69 | success: Sie haben sich erfolgreich mit Ihrem %{kind}-Konto angemeldet. 70 | passwords: 71 | edit: 72 | change_my_password: Ändere mein Passwort 73 | change_your_password: Passwort ändern 74 | confirm_new_password: Neues Passwort bestätigen 75 | new_password: Neues Passwort 76 | new: 77 | forgot_your_password: Haben Sie Ihr Passwort vergessen? 78 | send_me_reset_password_instructions: Schicken Sie mir die Anleitung, mein Passwort zurückzusetzen 79 | no_token: Sie können sich nicht auf dieser Seite anmelden, wenn Sie nicht von einer Passwort-Zurücksetzen-E-Mail kommen. Wenn Sie von solch einer E-Mail kommen, überprüfen Sie bitte, ob Sie die gesamte URL verwendet haben. 80 | send_instructions: Sie erhalten in wenigen Minuten eine E-Mail mit der Anleitung, wie Sie Ihr Passwort zurücksetzen können. 81 | send_paranoid_instructions: Falls Ihre E-Mail-Adresse in unserer Datenbank existiert, erhalten Sie in wenigen Minuten eine E-Mail mit der Anleitung, wie Sie Ihr Passwort zurücksetzen können. 82 | updated: Ihr Passwort wurde geändert. Sie sind jetzt angemeldet. 83 | updated_not_active: Ihr Passwort wurde erfolgreich geändert. 84 | registrations: 85 | destroyed: Ihr Konto wurde gelöscht. Wir hoffen, dass wir Sie bald wiedersehen. 86 | edit: 87 | are_you_sure: Sind Sie sicher? 88 | cancel_my_account: Konto löschen 89 | currently_waiting_confirmation_for_email: Warte auf Bestätigung von %{email}. 90 | leave_blank_if_you_don_t_want_to_change_it: freilassen, wenn Sie das nicht ändern wollen 91 | title: "%{resource} bearbeiten" 92 | unhappy: Unzufrieden? 93 | update: Aktualisieren 94 | we_need_your_current_password_to_confirm_your_changes: wir benötigen Ihr aktuelles Passwort, um die Änderung zu bestätigen 95 | new: 96 | sign_up: Registrieren 97 | signed_up: Sie haben sich erfolgreich registriert. 98 | signed_up_but_inactive: Sie haben sich erfolgreich registriert. Wir konnten Sie jedoch nicht anmelden, weil Ihr Konto noch nicht aktiviert ist. 99 | signed_up_but_locked: Sie haben sich erfolgreich registriert. Wir konnten Sie jedoch nicht anmelden, weil Ihr Konto gesperrt ist. 100 | signed_up_but_unconfirmed: Sie erhalten in wenigen Minuten eine E-Mail mit einem Link für die Bestätigung der Registrierung. Klicken Sie auf den Link um Ihr Konto zu aktivieren. 101 | update_needs_confirmation: Ihre Daten wurden aktualisiert, aber Sie müssen Ihre neue E-Mail-Adresse bestätigen. Sie erhalten in wenigen Minuten eine E-Mail, mit der Sie die Änderung Ihrer E-Mail-Adresse abschließen können. 102 | updated: Ihre Daten wurden aktualisiert. 103 | updated_but_not_signed_in: Ihre Daten wurden aktualisiert, aber da sich Ihr Passwort geändert hat, müssen Sie sich erneut anmelden 104 | sessions: 105 | already_signed_out: Erfolgreich abgemeldet. 106 | new: 107 | sign_in: Anmelden 108 | signed_in: Erfolgreich angemeldet. 109 | signed_out: Erfolgreich abgemeldet. 110 | shared: 111 | links: 112 | back: Zurück 113 | didn_t_receive_confirmation_instructions: Keine Anleitung zur Bestätigung erhalten? 114 | didn_t_receive_unlock_instructions: Keine Anleitung zum Entsperren erhalten? 115 | forgot_your_password: Passwort vergessen? 116 | sign_in: Anmelden 117 | sign_in_with_provider: Mit %{provider} anmelden 118 | sign_up: Registrieren 119 | minimum_password_length: 120 | one: "(mindestens %{count} Zeichen)" 121 | other: "(mindestens %{count} Zeichen)" 122 | unlocks: 123 | new: 124 | resend_unlock_instructions: Anleitung zum Entsperren noch mal schicken 125 | send_instructions: Sie erhalten in wenigen Minuten eine E-Mail mit der Anleitung, wie Sie Ihr Konto entsperren können. 126 | send_paranoid_instructions: Falls Ihre E-Mail-Adresse in unserer Datenbank existiert, erhalten Sie in wenigen Minuten eine E-Mail, mit der Anleitung, wie Sie Ihr Konto entsperren können. 127 | unlocked: Ihr Konto wurde entsperrt. Bitte melden Sie sich an, um fortzufahren. 128 | time: 129 | formats: 130 | devise: 131 | mailer: 132 | invitation_instructions: 133 | accept_until_format: "%B %d, %Y %I:%M %p" 134 | -------------------------------------------------------------------------------- /config/locales/devise/views/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | devise: 3 | confirmations: 4 | confirmed: Your email address has been successfully confirmed. 5 | new: 6 | resend_confirmation_instructions: Resend confirmation instructions 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 | last_attempt: You have one more attempt before your account is locked. 14 | locked: 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 | invited: "You have a pending invitation, accept it to finish creating your account." 20 | mailer: 21 | confirmation_instructions: 22 | action: Confirm my account 23 | greeting: Welcome %{recipient}! 24 | instruction: "You can confirm your account email through the link below:" 25 | subject: Confirmation instructions 26 | email_changed: 27 | greeting: Hello %{recipient}! 28 | message: We're contacting you to notify you that your email has been changed to %{email}. 29 | subject: Email Changed 30 | password_change: 31 | greeting: Hello %{recipient}! 32 | message: We're contacting you to notify you that your password has been changed. 33 | subject: Password Changed 34 | reset_password_instructions: 35 | action: Change my password 36 | greeting: Hello %{recipient}! 37 | instruction: Someone has requested a link to change your password. You can do this through the link below. 38 | instruction_2: If you didn't request this, please ignore this email. 39 | instruction_3: Your password won't change until you access the link above and create a new one. 40 | subject: Reset password instructions 41 | unlock_instructions: 42 | action: Unlock my account 43 | greeting: Hello %{recipient}! 44 | instruction: "Click the link below to unlock your account:" 45 | message: Your account has been locked due to an excessive number of unsuccessful sign in attempts. 46 | subject: Unlock instructions 47 | invitation_instructions: 48 | subject: "Invitation instructions" 49 | hello: "Hello %{email}" 50 | someone_invited_you: "Someone has invited you to %{url}, you can accept it through the link below." 51 | accept: "Accept invitation" 52 | accept_until: "This invitation will be due in %{due_date}." 53 | ignore: "If you don't want to accept the invitation, please ignore this email. Your account won't be created until you access the link above and set your password." 54 | omniauth_callbacks: 55 | failure: Could not authenticate you from %{kind} because "%{reason}". 56 | success: Successfully authenticated from %{kind} account. 57 | invitations: 58 | send_instructions: "An invitation email has been sent to %{email}." 59 | invitation_token_invalid: "The invitation token provided is not valid!" 60 | updated: "Your password was set successfully. You are now signed in." 61 | updated_not_active: "Your password was set successfully." 62 | no_invitations_remaining: "No invitations remaining" 63 | invitation_removed: "Your invitation was removed." 64 | new: 65 | header: "Send invitation" 66 | submit_button: "Send an invitation" 67 | edit: 68 | header: "Set your password" 69 | submit_button: "Set my password" 70 | passwords: 71 | edit: 72 | change_my_password: Change my password 73 | change_your_password: Change your password 74 | confirm_new_password: Confirm new password 75 | new_password: New password 76 | new: 77 | forgot_your_password: Forgot your password? 78 | send_me_reset_password_instructions: Send me reset password instructions 79 | 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. 80 | send_instructions: You will receive an email with instructions on how to reset your password in a few minutes. 81 | 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. 82 | updated: Your password has been changed successfully. You are now signed in. 83 | updated_not_active: Your password has been changed successfully. 84 | registrations: 85 | destroyed: Bye! Your account has been successfully cancelled. We hope to see you again soon. 86 | edit: 87 | are_you_sure: Are you sure? 88 | cancel_my_account: Cancel my account 89 | currently_waiting_confirmation_for_email: "Currently waiting confirmation for: %{email}" 90 | leave_blank_if_you_don_t_want_to_change_it: leave blank if you don't want to change it 91 | title: Edit %{resource} 92 | unhappy: Unhappy? 93 | update: Update 94 | we_need_your_current_password_to_confirm_your_changes: we need your current password to confirm your changes 95 | new: 96 | sign_up: Sign up 97 | signed_up: Welcome! You have signed up successfully. 98 | signed_up_but_inactive: You have signed up successfully. However, we could not sign you in because your account is not yet activated. 99 | signed_up_but_locked: You have signed up successfully. However, we could not sign you in because your account is locked. 100 | 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. 101 | 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. 102 | updated: Your account has been updated successfully. 103 | updated_but_not_signed_in: Your account has been updated successfully, but since your password was changed, you need to sign in again 104 | sessions: 105 | already_signed_out: Signed out successfully. 106 | new: 107 | sign_in: Log in 108 | signed_in: Signed in successfully. 109 | signed_out: Signed out successfully. 110 | shared: 111 | links: 112 | back: Back 113 | didn_t_receive_confirmation_instructions: Didn't receive confirmation instructions? 114 | didn_t_receive_unlock_instructions: Didn't receive unlock instructions? 115 | forgot_your_password: Forgot your password? 116 | sign_in: Log in 117 | sign_in_with_provider: Sign in with %{provider} 118 | sign_up: Sign up 119 | minimum_password_length: 120 | one: "(%{count} character minimum)" 121 | other: "(%{count} characters minimum)" 122 | unlocks: 123 | new: 124 | resend_unlock_instructions: Resend unlock instructions 125 | send_instructions: You will receive an email with instructions for how to unlock your account in a few minutes. 126 | send_paranoid_instructions: If your account exists, you will receive an email with instructions for how to unlock it in a few minutes. 127 | unlocked: Your account has been unlocked successfully. Please sign in to continue. 128 | time: 129 | formats: 130 | devise: 131 | mailer: 132 | invitation_instructions: 133 | accept_until_format: "%B %d, %Y %I:%M %p" 134 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | hello: "Hello world" 3 | -------------------------------------------------------------------------------- /config/locales/errors/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | errors: 3 | messages: 4 | already_confirmed: wurde bereits bestätigt; bitte versuchen Sie, sich anzumelden 5 | confirmation_period_expired: musste innerhalb von %{period} bestätigt werden, bitte neu anfordern. 6 | expired: ist abgelaufen, bitte neu anfordern 7 | not_found: nicht gefunden 8 | not_locked: ist nicht gesperrt 9 | not_saved: 10 | one: "%{resource} konnte aufgrund eines Fehlers nicht gespeichert werden:" 11 | other: "%{count} Fehler verhinderten das Speichern von %{resource}:" 12 | resource_not_found: "%{resource} nicht gefunden." 13 | -------------------------------------------------------------------------------- /config/locales/errors/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | errors: 3 | messages: 4 | already_confirmed: was already confirmed, please try signing in 5 | confirmation_period_expired: needs to be confirmed within %{period}, please request a new one 6 | expired: has expired, please request a new one 7 | not_found: not found 8 | not_locked: was not locked 9 | not_saved: 10 | one: "1 error prohibited this %{resource} from being saved:" 11 | other: "%{count} errors prohibited this %{resource} from being saved:" 12 | resource_not_found: "%{resource} not found." 13 | -------------------------------------------------------------------------------- /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 | threads_count = ENV.fetch('RAILS_MAX_THREADS', 5) 10 | threads threads_count, threads_count 11 | 12 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 13 | # 14 | port ENV.fetch('PORT', 3000) 15 | 16 | # Specifies the `environment` that Puma will run in. 17 | # 18 | environment ENV.fetch('RAILS_ENV', 'development') 19 | 20 | # Specifies the number of `workers` to boot in clustered mode. 21 | # Workers are forked webserver processes. If using threads and workers together 22 | # the concurrency of the application would be max `threads` * `workers`. 23 | # Workers do not work on JRuby or Windows (both of which do not support 24 | # processes). 25 | # 26 | workers ENV.fetch('WEB_CONCURRENCY', 2) 27 | 28 | # Use the `preload_app!` method when specifying a `workers` number. 29 | # This directive tells Puma to first boot the application and load code 30 | # before forking the application. This takes advantage of Copy On Write 31 | # process behavior so workers use less memory. 32 | # 33 | # preload_app! 34 | 35 | # Allow puma to be restarted by `rails restart` command. 36 | plugin :tmp_restart 37 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | default_url_options host: ENV['DEFAULT_URL'] 5 | 6 | mount RailsAdmin::Engine => '/admin', as: 'rails_admin' 7 | post '/graphql', to: 'graphql#execute' 8 | devise_for :users, 9 | controllers: { 10 | confirmations: 'auth/confirmations', 11 | passwords: 'auth/passwords', 12 | invitations: 'auth/invitations' 13 | }, 14 | skip: :registrations # skip registration route 15 | 16 | # Just a blank root path 17 | root 'pages#blank' 18 | end 19 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Spring.watch( 4 | '.ruby-version', 5 | '.rbenv-vars', 6 | 'tmp/restart.txt', 7 | 'tmp/caching-dev.txt' 8 | ) 9 | -------------------------------------------------------------------------------- /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 | # amazon: 10 | # service: S3 11 | # access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %> 12 | # secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %> 13 | # bucket: <%= ENV['AWS_S3_BUCKET'] %> 14 | # region: "eu-west-1" # e.g. 'us-east-1' 15 | 16 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 17 | # amazon: 18 | # service: S3 19 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 20 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 21 | # region: us-east-1 22 | # bucket: your_own_bucket 23 | 24 | # Remember not to checkin your GCS keyfile to a repository 25 | # google: 26 | # service: GCS 27 | # project: your_project 28 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 29 | # bucket: your_own_bucket 30 | 31 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 32 | # microsoft: 33 | # service: AzureStorage 34 | # storage_account_name: your_account_name 35 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 36 | # container: your_container_name 37 | 38 | # mirror: 39 | # service: Mirror 40 | # primary: local 41 | # mirrors: [ amazon, google, microsoft ] 42 | -------------------------------------------------------------------------------- /db/migrate/20190209162712_enable_extension_for_uuid.rb: -------------------------------------------------------------------------------- 1 | class EnableExtensionForUuid < ActiveRecord::Migration[6.0] 2 | def change 3 | enable_extension 'pgcrypto' unless extension_enabled?('pgcrypto') 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20190209163712_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateUsers < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :users, id: :uuid do |t| 6 | 7 | ## General 8 | t.string 'first_name', null: false, default: '' 9 | t.string 'last_name', null: false, default: '' 10 | 11 | ## Database authenticatable 12 | t.string :email, null: false, default: '' 13 | t.string :encrypted_password, null: false, default: '' 14 | 15 | ## Recoverable 16 | t.string :reset_password_token 17 | t.datetime :reset_password_sent_at 18 | 19 | ## Rememberable 20 | t.datetime :remember_created_at 21 | 22 | ## Trackable 23 | t.integer :sign_in_count, default: 0, null: false 24 | t.datetime :current_sign_in_at 25 | t.datetime :last_sign_in_at 26 | t.inet :current_sign_in_ip 27 | t.inet :last_sign_in_ip 28 | 29 | ## Confirmable 30 | t.string :confirmation_token 31 | t.datetime :confirmed_at 32 | t.datetime :confirmation_sent_at 33 | t.string :unconfirmed_email # Only if using reconfirmable 34 | 35 | ## Lockable 36 | t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 37 | t.string :unlock_token # Only if unlock strategy is :email or :both 38 | t.datetime :locked_at 39 | 40 | ## Refresh token for JWT auth 41 | t.string :refresh_token 42 | 43 | # role attributes (used as enum in user model) 44 | t.integer :role, default: 0, null: false 45 | 46 | t.timestamps null: false 47 | 48 | t.uuid :company_id 49 | end 50 | 51 | add_index :users, :email, unique: true 52 | add_index :users, :reset_password_token, unique: true 53 | add_index :users, :confirmation_token, unique: true 54 | add_index :users, :unlock_token, unique: true 55 | add_index 'users', ['refresh_token'], name: 'index_users_on_refresh_token', unique: true, using: :btree 56 | 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /db/migrate/20200911092211_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateActiveStorageTables < ActiveRecord::Migration[6.0] 4 | def change 5 | create_table :active_storage_blobs do |t| 6 | t.string :key, null: false 7 | t.string :filename, null: false 8 | t.string :content_type 9 | t.text :metadata 10 | t.bigint :byte_size, null: false 11 | t.string :checksum, null: false 12 | t.datetime :created_at, null: false 13 | 14 | t.index [ :key ], unique: true 15 | end 16 | 17 | create_table :active_storage_attachments do |t| 18 | t.string :name, null: false 19 | t.references :record, null: false, polymorphic: true, index: false 20 | t.references :blob, null: false 21 | 22 | t.datetime :created_at, null: false 23 | 24 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true 25 | t.foreign_key :active_storage_blobs, column: :blob_id 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /db/migrate/20200912120337_create_companies.rb: -------------------------------------------------------------------------------- 1 | class CreateCompanies < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :companies, id: :uuid do |t| 4 | t.string :name 5 | t.integer :users_count 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20200912153138_add_slug_to_companies.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToCompanies < ActiveRecord::Migration[6.0] 2 | def change 3 | add_column :companies, :slug, :string 4 | add_index :companies, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20200912153858_create_friendly_id_slugs.rb: -------------------------------------------------------------------------------- 1 | class CreateFriendlyIdSlugs < ActiveRecord::Migration[6.0] 2 | def change 3 | create_table :friendly_id_slugs do |t| 4 | t.string :slug, :null => false 5 | t.integer :sluggable_id, :null => false 6 | t.string :sluggable_type, :limit => 50 7 | t.string :scope 8 | t.datetime :created_at 9 | end 10 | add_index :friendly_id_slugs, :sluggable_id 11 | add_index :friendly_id_slugs, [:slug, :sluggable_type] 12 | add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], :unique => true 13 | add_index :friendly_id_slugs, :sluggable_type 14 | 15 | Companies::Company.all.each(&:save) 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /db/migrate/20200920102035_devise_invitable_add_to_users.rb: -------------------------------------------------------------------------------- 1 | class DeviseInvitableAddToUsers < ActiveRecord::Migration[6.0] 2 | def up 3 | change_table :users do |t| 4 | t.string :invitation_token 5 | t.datetime :invitation_created_at 6 | t.datetime :invitation_sent_at 7 | t.datetime :invitation_accepted_at 8 | t.integer :invitation_limit 9 | t.references :invited_by, polymorphic: true 10 | t.integer :invitations_count, default: 0 11 | t.index :invitations_count 12 | t.index :invitation_token, unique: true # for invitable 13 | t.index :invited_by_id 14 | end 15 | end 16 | 17 | def down 18 | change_table :users do |t| 19 | t.remove_references :invited_by, polymorphic: true 20 | t.remove :invitations_count, :invitation_limit, :invitation_sent_at, :invitation_accepted_at, :invitation_token, :invitation_created_at 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /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 `rails 6 | # db:schema:load`. When creating a new database, `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.define(version: 2020_09_20_102035) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "pgcrypto" 17 | enable_extension "plpgsql" 18 | 19 | create_table "active_storage_attachments", force: :cascade do |t| 20 | t.string "name", null: false 21 | t.string "record_type", null: false 22 | t.bigint "record_id", null: false 23 | t.bigint "blob_id", null: false 24 | t.datetime "created_at", null: false 25 | t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" 26 | t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true 27 | end 28 | 29 | create_table "active_storage_blobs", force: :cascade do |t| 30 | t.string "key", null: false 31 | t.string "filename", null: false 32 | t.string "content_type" 33 | t.text "metadata" 34 | t.bigint "byte_size", null: false 35 | t.string "checksum", null: false 36 | t.datetime "created_at", null: false 37 | t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true 38 | end 39 | 40 | create_table "companies", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| 41 | t.string "name" 42 | t.integer "users_count" 43 | t.datetime "created_at", precision: 6, null: false 44 | t.datetime "updated_at", precision: 6, null: false 45 | t.string "slug" 46 | t.index ["slug"], name: "index_companies_on_slug", unique: true 47 | end 48 | 49 | create_table "conversation_memberships", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| 50 | t.uuid "user_id" 51 | t.uuid "conversation_id" 52 | t.uuid "last_read_message_id" 53 | t.datetime "created_at", precision: 6, null: false 54 | t.datetime "updated_at", precision: 6, null: false 55 | t.index ["user_id", "conversation_id"], name: "index_user_id_on_conversation_id", unique: true 56 | end 57 | 58 | create_table "conversations", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| 59 | t.string "name" 60 | t.uuid "owner_id" 61 | t.uuid "company_id" 62 | t.datetime "created_at", precision: 6, null: false 63 | t.datetime "updated_at", precision: 6, null: false 64 | end 65 | 66 | create_table "friendly_id_slugs", force: :cascade do |t| 67 | t.string "slug", null: false 68 | t.integer "sluggable_id", null: false 69 | t.string "sluggable_type", limit: 50 70 | t.string "scope" 71 | t.datetime "created_at" 72 | t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true 73 | t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type" 74 | t.index ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id" 75 | t.index ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type" 76 | end 77 | 78 | create_table "messages", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| 79 | t.uuid "conversation_id" 80 | t.uuid "user_id" 81 | t.text "body" 82 | t.datetime "created_at", precision: 6, null: false 83 | t.datetime "updated_at", precision: 6, null: false 84 | end 85 | 86 | create_table "notifications", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| 87 | t.string "notification_type" 88 | t.uuid "initiator_id" 89 | t.uuid "receiver_id" 90 | t.boolean "recent", default: true 91 | t.boolean "is_read", default: false 92 | t.boolean "is_delivered", default: false 93 | t.uuid "resource_id" 94 | t.string "resource_type" 95 | t.datetime "created_at", precision: 6, null: false 96 | t.datetime "updated_at", precision: 6, null: false 97 | t.index ["initiator_id"], name: "index_notifications_on_initiator_id" 98 | t.index ["notification_type"], name: "index_notifications_on_notification_type" 99 | t.index ["receiver_id"], name: "index_notifications_on_receiver_id" 100 | t.index ["resource_id", "resource_type"], name: "index_notifications_on_resource_id_and_resource_type" 101 | end 102 | 103 | create_table "users", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| 104 | t.string "first_name", default: "", null: false 105 | t.string "last_name", default: "", null: false 106 | t.string "email", default: "", null: false 107 | t.string "encrypted_password", default: "", null: false 108 | t.string "reset_password_token" 109 | t.datetime "reset_password_sent_at" 110 | t.datetime "remember_created_at" 111 | t.integer "sign_in_count", default: 0, null: false 112 | t.datetime "current_sign_in_at" 113 | t.datetime "last_sign_in_at" 114 | t.inet "current_sign_in_ip" 115 | t.inet "last_sign_in_ip" 116 | t.string "confirmation_token" 117 | t.datetime "confirmed_at" 118 | t.datetime "confirmation_sent_at" 119 | t.string "unconfirmed_email" 120 | t.integer "failed_attempts", default: 0, null: false 121 | t.string "unlock_token" 122 | t.datetime "locked_at" 123 | t.string "refresh_token" 124 | t.integer "role", default: 0, null: false 125 | t.datetime "created_at", precision: 6, null: false 126 | t.datetime "updated_at", precision: 6, null: false 127 | t.uuid "company_id" 128 | t.datetime "last_seen_at" 129 | t.string "invitation_token" 130 | t.datetime "invitation_created_at" 131 | t.datetime "invitation_sent_at" 132 | t.datetime "invitation_accepted_at" 133 | t.integer "invitation_limit" 134 | t.string "invited_by_type" 135 | t.bigint "invited_by_id" 136 | t.integer "invitations_count", default: 0 137 | t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true 138 | t.index ["email"], name: "index_users_on_email", unique: true 139 | t.index ["invitation_token"], name: "index_users_on_invitation_token", unique: true 140 | t.index ["invitations_count"], name: "index_users_on_invitations_count" 141 | t.index ["invited_by_id"], name: "index_users_on_invited_by_id" 142 | t.index ["invited_by_type", "invited_by_id"], name: "index_users_on_invited_by_type_and_invited_by_id" 143 | t.index ["refresh_token"], name: "index_users_on_refresh_token", unique: true 144 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 145 | t.index ["unlock_token"], name: "index_users_on_unlock_token", unique: true 146 | end 147 | 148 | add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" 149 | end 150 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | user = User.create( 2 | email: ENV['ADMIN_EMAIL'], 3 | password: ENV['ADMIN_PASSWORD'], 4 | password_confirmation: ENV['ADMIN_PASSWORD'], 5 | first_name: ENV['ADMIN_FIRST_NAME'], 6 | last_name: ENV['ADMIN_LAST_NAME'], 7 | role: 'superadmin' 8 | ) 9 | 10 | if user 11 | Rails.logger.info "Login with #{ENV['ADMIN_EMAIL']} and #{ENV['ADMIN_PASSWORD']}" 12 | end 13 | 14 | # Create a admin user 15 | admin = User.create( 16 | email: Faker::Internet.email, 17 | password: ENV['ADMIN_PASSWORD'], 18 | password_confirmation: ENV['ADMIN_PASSWORD'], 19 | first_name: Faker::Name.first_name, 20 | last_name: Faker::Name.last_name, 21 | role: 'admin' 22 | ) 23 | 24 | User.create( 25 | email: Faker::Internet.email, 26 | password: ENV['ADMIN_PASSWORD'], 27 | password_confirmation: ENV['ADMIN_PASSWORD'], 28 | first_name: Faker::Name.first_name, 29 | last_name: Faker::Name.last_name, 30 | role: 'user', 31 | company_id: admin.company_id 32 | ) 33 | 34 | User.create( 35 | email: Faker::Internet.email, 36 | password: ENV['ADMIN_PASSWORD'], 37 | password_confirmation: ENV['ADMIN_PASSWORD'], 38 | first_name: Faker::Name.first_name, 39 | last_name: Faker::Name.last_name, 40 | role: 'user', 41 | company_id: admin.company_id 42 | ) 43 | -------------------------------------------------------------------------------- /env_sample: -------------------------------------------------------------------------------- 1 | DEFAULT_URL=0.0.0.0:3000 2 | CLIENT_URL=0.0.0.0:8000 3 | WEB_CONCURRENCY=2 4 | RAILS_MAX_THREADS=5 5 | ADMIN_EMAIL=demo@zauberware.com 6 | ADMIN_PASSWORD=demo1234 7 | ADMIN_FIRST_NAME=John 8 | ADMIN_LAST_NAME=Doe 9 | DEVISE_SECRET_KEY=replace-this-key-with-a-secret 10 | DEVISE_MAILER_FROM=test@domain.com 11 | IS_HTTP_AUTH_PROTECTED=false 12 | HTTP_AUTH_USER=demo 13 | HTTP_AUTH_PASSWORD=demo1234 14 | SMTP_ADDRESS= 15 | SMTP_PORT= 16 | SMTP_DOMAIN= 17 | SMTP_USERNAME= 18 | SMTP_PASSWORD= 19 | SMTP_AUTH=login 20 | SMTP_ENABLE_STARTTLS_AUTO=true 21 | RACK_ATTACK_ENABLED=false -------------------------------------------------------------------------------- /lib/regex.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Regex 4 | class Email 5 | VALIDATE = /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i.freeze 6 | VALIDATE_OR_EMPTY = /(^$|#{VALIDATE})/i.freeze 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/lib/tasks/.keep -------------------------------------------------------------------------------- /lib/tasks/auto_annotate_models.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # NOTE: only doing this in development as some production environments (Heroku) 4 | # NOTE: are sensitive to local FS writes, and besides -- it's just not proper 5 | # NOTE: to have a dev-mode tool do its thing in production. 6 | if Rails.env.development? 7 | require 'annotate' 8 | task set_annotation_options: :environment do 9 | # You can override any of these by setting an environment variable of the 10 | # same name. 11 | Annotate.set_defaults( 12 | 'active_admin' => 'false', 13 | 'additional_file_patterns' => [], 14 | 'routes' => 'false', 15 | 'models' => 'true', 16 | 'position_in_routes' => 'before', 17 | 'position_in_class' => 'before', 18 | 'position_in_test' => 'before', 19 | 'position_in_fixture' => 'before', 20 | 'position_in_factory' => 'before', 21 | 'position_in_serializer' => 'before', 22 | 'show_foreign_keys' => 'true', 23 | 'show_complete_foreign_keys' => 'false', 24 | 'show_indexes' => 'true', 25 | 'simple_indexes' => 'false', 26 | 'model_dir' => 'app/models', 27 | 'root_dir' => '', 28 | 'include_version' => 'false', 29 | 'require' => '', 30 | 'exclude_tests' => 'false', 31 | 'exclude_fixtures' => 'false', 32 | 'exclude_factories' => 'false', 33 | 'exclude_serializers' => 'false', 34 | 'exclude_scaffolds' => 'true', 35 | 'exclude_controllers' => 'true', 36 | 'exclude_helpers' => 'true', 37 | 'exclude_sti_subclasses' => 'false', 38 | 'ignore_model_sub_dir' => 'false', 39 | 'ignore_columns' => nil, 40 | 'ignore_routes' => nil, 41 | 'ignore_unknown_models' => 'false', 42 | 'hide_limit_column_types' => 'integer,bigint,boolean', 43 | 'hide_default_column_types' => 'json,jsonb,hstore', 44 | 'skip_on_db_migrate' => 'false', 45 | 'format_bare' => 'true', 46 | 'format_rdoc' => 'false', 47 | 'format_yard' => 'false', 48 | 'format_markdown' => 'false', 49 | 'sort' => 'false', 50 | 'force' => 'false', 51 | 'frozen' => 'false', 52 | 'classified_sort' => 'true', 53 | 'trace' => 'false', 54 | 'wrapper_open' => nil, 55 | 'wrapper_close' => nil, 56 | 'with_comment' => 'true' 57 | ) 58 | end 59 | 60 | Annotate.load_tasks 61 | end 62 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/log/.keep -------------------------------------------------------------------------------- /public/android-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/android-icon-144x144.png -------------------------------------------------------------------------------- /public/android-icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/android-icon-192x192.png -------------------------------------------------------------------------------- /public/android-icon-36x36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/android-icon-36x36.png -------------------------------------------------------------------------------- /public/android-icon-48x48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/android-icon-48x48.png -------------------------------------------------------------------------------- /public/android-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/android-icon-72x72.png -------------------------------------------------------------------------------- /public/android-icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/android-icon-96x96.png -------------------------------------------------------------------------------- /public/apple-icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-114x114.png -------------------------------------------------------------------------------- /public/apple-icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-120x120.png -------------------------------------------------------------------------------- /public/apple-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-144x144.png -------------------------------------------------------------------------------- /public/apple-icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-152x152.png -------------------------------------------------------------------------------- /public/apple-icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-180x180.png -------------------------------------------------------------------------------- /public/apple-icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-57x57.png -------------------------------------------------------------------------------- /public/apple-icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-60x60.png -------------------------------------------------------------------------------- /public/apple-icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-72x72.png -------------------------------------------------------------------------------- /public/apple-icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-76x76.png -------------------------------------------------------------------------------- /public/apple-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/apple-icon.png -------------------------------------------------------------------------------- /public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | #ffffff -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/favicon.ico -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "App", 3 | "icons": [ 4 | { 5 | "src": "\/android-icon-36x36.png", 6 | "sizes": "36x36", 7 | "type": "image\/png", 8 | "density": "0.75" 9 | }, 10 | { 11 | "src": "\/android-icon-48x48.png", 12 | "sizes": "48x48", 13 | "type": "image\/png", 14 | "density": "1.0" 15 | }, 16 | { 17 | "src": "\/android-icon-72x72.png", 18 | "sizes": "72x72", 19 | "type": "image\/png", 20 | "density": "1.5" 21 | }, 22 | { 23 | "src": "\/android-icon-96x96.png", 24 | "sizes": "96x96", 25 | "type": "image\/png", 26 | "density": "2.0" 27 | }, 28 | { 29 | "src": "\/android-icon-144x144.png", 30 | "sizes": "144x144", 31 | "type": "image\/png", 32 | "density": "3.0" 33 | }, 34 | { 35 | "src": "\/android-icon-192x192.png", 36 | "sizes": "192x192", 37 | "type": "image\/png", 38 | "density": "4.0" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /public/ms-icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/ms-icon-144x144.png -------------------------------------------------------------------------------- /public/ms-icon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/ms-icon-150x150.png -------------------------------------------------------------------------------- /public/ms-icon-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/ms-icon-310x310.png -------------------------------------------------------------------------------- /public/ms-icon-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/public/ms-icon-70x70.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/config/initializers/rack/attack_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | describe Rack::Attack do 6 | include Rack::Test::Methods 7 | 8 | def app 9 | Rails.application 10 | end 11 | 12 | before do 13 | described_class.cache.store = ActiveSupport::Cache::MemoryStore.new 14 | end 15 | 16 | (1..3).each do |level| 17 | describe "Throttle all requests by IP (Level #{level})" do 18 | let(:limit) { (ENV['ATTACK_REQUEST_LIMIT'] || 300).to_i * level } 19 | let(:period) { (ENV['ATTACK_REQUEST_PERIOD_IN_MINUTES'] || 5).to_i**level } 20 | 21 | context "when requests are lower than level #{level} limit" do 22 | it 'does not change the request status' do 23 | ip = Faker::Internet.ip_v4_address 24 | limit.times do |i| 25 | Timecop.freeze(DateTime.now + ((i + 1) * (60 / (limit / period.to_f))).seconds) do 26 | get '/', session: { 'REMOTE_ADDR' => ip } 27 | expect(last_response.status).not_to eq(429) 28 | end 29 | end 30 | end 31 | end 32 | 33 | context "when requests are higher than level #{level} limit" do 34 | it 'changes the request status to 429' do 35 | ip = Faker::Internet.ip_v4_address 36 | (limit * 2.0).to_i.times do |i| 37 | Timecop.freeze(DateTime.now + (i * (60 / ((limit * 2.0) / period.to_f))).seconds) do 38 | get '/', session: { 'REMOTE_ADDR' => ip } 39 | expect(last_response.status).to eq(429) if i >= limit * 2.0 40 | end 41 | end 42 | end 43 | end 44 | end 45 | 46 | describe "Throttle all authenticated requests by IP (Level #{level})" do 47 | let(:limit) { (ENV['ATTACK_AUTHENTICATED_REQUEST_LIMIT'] || 300).to_i * level } 48 | let(:period) { (ENV['ATTACK_AUTHENTICATED_REQUEST_PERIOD_IN_MINUTES'] || 5).to_i**level } 49 | let(:user) { create(:user) } 50 | 51 | before { allow(described_class).to receive(:user_session?) { user } } 52 | 53 | context "when requests are lower than level #{level} limit" do 54 | it 'does not change the request status' do 55 | ip = Faker::Internet.ip_v4_address 56 | limit.times do |i| 57 | Timecop.freeze(DateTime.now + ((i + 1) * (60 / (limit / period.to_f))).seconds) do 58 | get '/', session: { 'REMOTE_ADDR' => ip } 59 | expect(last_response.status).not_to eq(429) 60 | end 61 | end 62 | end 63 | end 64 | 65 | context "when requests are higher than level #{level} limit" do 66 | it 'changes the request status to 429' do 67 | ip = Faker::Internet.ip_v4_address 68 | (limit * 2.0).to_i.times do |i| 69 | Timecop.freeze(DateTime.now + (i * (60 / ((limit * 2.0) / period.to_f))).seconds) do 70 | get '/', session: { 'REMOTE_ADDR' => ip } 71 | expect(last_response.status).to eq(429) if i >= limit * 2.0 72 | end 73 | end 74 | end 75 | end 76 | end 77 | 78 | describe "Throttle devise sign in requests (Level #{level})" do 79 | let(:limit) { (ENV['ATTACK_AUTH_LIMIT'] || 10).to_i * level } 80 | let(:period) { (ENV['ATTACK_AUTH_PERIOD_IN_MINUTES'] || 10).to_i**level } 81 | let(:user) { create(:user) } 82 | 83 | before { allow(described_class).to receive(:user_session?) { user } } 84 | 85 | context "when requests are lower than level #{level} limit" do 86 | it 'does not change the request status' do 87 | ip = Faker::Internet.ip_v4_address 88 | limit.times do |i| 89 | Timecop.freeze(DateTime.now + ((i + 1) * (60 / (limit / period.to_f))).seconds) do 90 | post '/users/sign_in', params: { user: { email: user.email, password: 'wrong' } }, 'REMOTE_ADDR' => ip 91 | expect(last_response.status).not_to eq(429) 92 | end 93 | end 94 | end 95 | end 96 | 97 | context "when requests are higher than level #{level} limit" do 98 | it 'changes the request status to 429' do 99 | ip = Faker::Internet.ip_v4_address 100 | (limit * 2.0).to_i.times do |i| 101 | Timecop.freeze(DateTime.now + (i * (60 / ((limit * 2.0) / period.to_f))).seconds) do 102 | post '/users/sign_in', params: { user: { email: user.email, password: 'wrong' } }, 'REMOTE_ADDR' => ip 103 | expect(last_response.status).to eq(429) if i >= limit * 2.0 104 | end 105 | end 106 | end 107 | end 108 | end 109 | 110 | describe "Throttle devise password requests (Level #{level})" do 111 | let(:limit) { (ENV['ATTACK_AUTH_LIMIT'] || 10).to_i * level } 112 | let(:period) { (ENV['ATTACK_AUTH_PERIOD_IN_MINUTES'] || 10).to_i**level } 113 | let(:user) { create(:user) } 114 | 115 | before { allow(described_class).to receive(:user_session?) { user } } 116 | 117 | context "when requests are lower than level #{level} limit" do 118 | it 'does not change the request status' do 119 | ip = Faker::Internet.ip_v4_address 120 | limit.times do |i| 121 | Timecop.freeze(DateTime.now + ((i + 1) * (60 / (limit / period.to_f))).seconds) do 122 | post '/users/password', params: { user: { email: user.email } }, 'REMOTE_ADDR' => ip 123 | expect(last_response.status).not_to eq(429) 124 | end 125 | end 126 | end 127 | end 128 | 129 | context "when requests are higher than level #{level} limit" do 130 | it 'changes the request status to 429' do 131 | ip = Faker::Internet.ip_v4_address 132 | (limit * 2.0).to_i.times do |i| 133 | Timecop.freeze(DateTime.now + (i * (60 / ((limit * 2.0) / period.to_f))).seconds) do 134 | post '/users/password', params: { user: { email: user.email } }, 'REMOTE_ADDR' => ip 135 | expect(last_response.status).to eq(429) if i >= limit * 2.0 136 | end 137 | end 138 | end 139 | end 140 | end 141 | 142 | describe "Throttle graphQL auth requests (Level #{level})" do 143 | let(:limit) { (ENV['ATTACK_AUTH_LIMIT'] || 10).to_i * level } 144 | let(:period) { (ENV['ATTACK_AUTH_PERIOD_IN_MINUTES'] || 10).to_i**level } 145 | let(:user) { create(:user) } 146 | 147 | before { allow(described_class).to receive(:user_session?) { user } } 148 | 149 | context "when requests are lower than level #{level} limit" do 150 | it 'does not change the request status' do 151 | ip = Faker::Internet.ip_v4_address 152 | limit.times do |i| 153 | Timecop.freeze(DateTime.now + ((i + 1) * (60 / (limit / period.to_f))).seconds) do 154 | post '/graphql', params: { query: 'signUp' }, 'REMOTE_ADDR' => ip 155 | expect(last_response.status).not_to eq(429) 156 | end 157 | end 158 | end 159 | end 160 | 161 | context "when requests are higher than level #{level} limit" do 162 | it 'changes the request status to 429' do 163 | ip = Faker::Internet.ip_v4_address 164 | (limit * 2.0).to_i.times do |i| 165 | Timecop.freeze(DateTime.now + (i * (60 / ((limit * 2.0) / period.to_f))).seconds) do 166 | post '/graphql', params: { query: 'signUp' }, 'REMOTE_ADDR' => ip 167 | expect(last_response.status).to eq(429) if i >= limit * 2.0 168 | end 169 | end 170 | end 171 | end 172 | end 173 | end 174 | 175 | describe 'Throttle graphQL actions' do 176 | let(:limit) { (ENV['ATTACK_PUBLIC_ACTION_LIMIT'] || 30).to_i } 177 | let(:period) { (ENV['ATTACK_PUBLIC_ACTION_PERIOD_IN_MINUTES'] || 60).to_i } 178 | let(:user) { create(:user) } 179 | 180 | before { allow(described_class).to receive(:user_session?) { user } } 181 | 182 | context 'when requests are lower than limit' do 183 | it 'does not change the request status' do 184 | ip = Faker::Internet.ip_v4_address 185 | limit.times do |i| 186 | Timecop.freeze(DateTime.now + ((i + 1) * (60 / (limit / period.to_f))).seconds) do 187 | post '/graphql', params: { query: 'createMessage' }, 'REMOTE_ADDR' => ip 188 | expect(last_response.status).not_to eq(429) 189 | end 190 | end 191 | end 192 | end 193 | 194 | context 'when requests are higher than limit' do 195 | it 'changes the request status to 429' do 196 | ip = Faker::Internet.ip_v4_address 197 | (limit * 2.0).to_i.times do |i| 198 | Timecop.freeze(DateTime.now + (i * (60 / ((limit * 2.0) / period.to_f))).seconds) do 199 | post '/graphql', params: { query: 'createMessage' }, 'REMOTE_ADDR' => ip 200 | expect(last_response.status).to eq(429) if i >= limit * 2.0 201 | end 202 | end 203 | end 204 | end 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /spec/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/spec/controllers/.keep -------------------------------------------------------------------------------- /spec/controllers/graphql_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe GraphqlController, type: :controller do 6 | login_user # access current user with @current_user 7 | 8 | describe 'execute' do 9 | let(:current_user) { @current_user } 10 | 11 | context 'when wrong query params given' do 12 | it 'returns with errors' do 13 | post :execute, params: { 'query' => "{\n wrong {\n email\n }\n}" } 14 | response_body = JSON.parse(response.body) 15 | expect(response_body['errors']).not_to be nil 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/factories/companies/companies.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: companies 6 | # 7 | # id :uuid not null, primary key 8 | # name :string 9 | # slug :string 10 | # users_count :integer 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | # Indexes 15 | # 16 | # index_companies_on_slug (slug) UNIQUE 17 | # 18 | FactoryBot.define do 19 | factory :company, class: 'Companies::Company' do 20 | name { 'My Company' } 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: users 6 | # 7 | # id :uuid not null, primary key 8 | # confirmation_sent_at :datetime 9 | # confirmation_token :string 10 | # confirmed_at :datetime 11 | # current_sign_in_at :datetime 12 | # current_sign_in_ip :inet 13 | # email :string default(""), not null 14 | # encrypted_password :string default(""), not null 15 | # failed_attempts :integer default(0), not null 16 | # first_name :string default(""), not null 17 | # invitation_accepted_at :datetime 18 | # invitation_created_at :datetime 19 | # invitation_limit :integer 20 | # invitation_sent_at :datetime 21 | # invitation_token :string 22 | # invitations_count :integer default(0) 23 | # invited_by_type :string 24 | # last_name :string default(""), not null 25 | # last_seen_at :datetime 26 | # last_sign_in_at :datetime 27 | # last_sign_in_ip :inet 28 | # locked_at :datetime 29 | # refresh_token :string 30 | # remember_created_at :datetime 31 | # reset_password_sent_at :datetime 32 | # reset_password_token :string 33 | # role :integer default("user"), not null 34 | # sign_in_count :integer default(0), not null 35 | # unconfirmed_email :string 36 | # unlock_token :string 37 | # created_at :datetime not null 38 | # updated_at :datetime not null 39 | # company_id :uuid 40 | # invited_by_id :bigint 41 | # 42 | # Indexes 43 | # 44 | # index_users_on_confirmation_token (confirmation_token) UNIQUE 45 | # index_users_on_email (email) UNIQUE 46 | # index_users_on_invitation_token (invitation_token) UNIQUE 47 | # index_users_on_invitations_count (invitations_count) 48 | # index_users_on_invited_by_id (invited_by_id) 49 | # index_users_on_invited_by_type_and_invited_by_id (invited_by_type,invited_by_id) 50 | # index_users_on_refresh_token (refresh_token) UNIQUE 51 | # index_users_on_reset_password_token (reset_password_token) UNIQUE 52 | # index_users_on_unlock_token (unlock_token) UNIQUE 53 | # 54 | FactoryBot.define do 55 | factory :user do 56 | first_name { Faker::Name.first_name } 57 | last_name { Faker::Name.last_name } 58 | email { Faker::Internet.email } 59 | password { 'password' } 60 | password_confirmation { 'password' } 61 | association :company 62 | 63 | trait :user do 64 | role { :user } 65 | end 66 | 67 | trait :admin do 68 | role { :admin } 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /spec/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/spec/fixtures/files/.keep -------------------------------------------------------------------------------- /spec/graphql/mutations/companies/update_company_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Mutations::Companies::UpdateCompany do 6 | subject(:graphql!) { result } 7 | 8 | let(:result) do 9 | GraphqlSchema.execute( 10 | query_string, 11 | variables: variables, 12 | context: context 13 | ) 14 | end 15 | 16 | let(:variables) do 17 | {} 18 | end 19 | 20 | let(:query_string) do 21 | <<-GRAPHQL 22 | mutation updateCompany($id: ID!, $attributes: CompanyInput!){ 23 | updateCompany(id: $id, attributes: $attributes) { 24 | id 25 | name 26 | } 27 | } 28 | GRAPHQL 29 | end 30 | 31 | describe 'updateCompany' do 32 | context 'with invalid company id' do 33 | let!(:user) do 34 | create(:user) 35 | end 36 | 37 | let(:context) do 38 | { 39 | current_user: user 40 | } 41 | end 42 | 43 | let(:variables) do 44 | { 45 | id: 'wrong', 46 | attributes: {} 47 | } 48 | end 49 | 50 | it 'returns errors' do 51 | message = result['errors'][0]['message'] 52 | expect(message).not_to be_nil 53 | end 54 | end 55 | 56 | context 'with invalid params' do 57 | let!(:user) do 58 | create(:user) 59 | end 60 | 61 | let(:context) do 62 | { 63 | current_user: user 64 | } 65 | end 66 | 67 | let(:variables) do 68 | { 69 | id: user.company_id, 70 | attributes: {} 71 | } 72 | end 73 | 74 | it 'returns errors' do 75 | graphql! 76 | message = result['errors'][0]['message'] 77 | expect(message).not_to be_nil 78 | end 79 | end 80 | 81 | context 'with valid params' do 82 | let!(:user) do 83 | create(:user, :admin) 84 | end 85 | 86 | let(:context) do 87 | { 88 | current_user: user 89 | } 90 | end 91 | 92 | let(:variables) do 93 | { 94 | id: user.company_id, 95 | attributes: { name: 'new name' } 96 | } 97 | end 98 | 99 | it 'changes name' do 100 | graphql! 101 | name = result['data']['updateCompany']['name'] 102 | expect(name).to eq('new name') 103 | end 104 | end 105 | end 106 | end 107 | -------------------------------------------------------------------------------- /spec/graphql/mutations/users/delete_user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Mutations::Users::DeleteUser do 6 | subject(:graphql!) { result } 7 | 8 | let!(:admin) do 9 | create(:user, :admin) 10 | end 11 | 12 | let(:result) do 13 | GraphqlSchema.execute( 14 | query_string, 15 | variables: variables, 16 | context: context 17 | ) 18 | end 19 | 20 | let(:variables) do 21 | {} 22 | end 23 | 24 | let(:query_string) do 25 | <<-GRAPHQL 26 | mutation deleteUser($id: ID!){ 27 | deleteUser(id: $id) 28 | } 29 | GRAPHQL 30 | end 31 | 32 | describe 'deleteUser' do 33 | context 'when not an admin' do 34 | let(:user) do 35 | create(:user, company_id: admin.company_id) 36 | end 37 | 38 | let(:context) do 39 | { 40 | current_user: user 41 | } 42 | end 43 | 44 | let(:variables) do 45 | { 46 | id: user.id 47 | } 48 | end 49 | 50 | it 'returns errors' do 51 | graphql! 52 | message = result['errors'][0]['message'] 53 | expect(message).not_to be_nil 54 | end 55 | end 56 | 57 | context 'with invalid id' do 58 | let(:user) do 59 | create(:user, company_id: admin.company_id) 60 | end 61 | 62 | let(:context) do 63 | { 64 | current_user: admin 65 | } 66 | end 67 | 68 | let(:variables) do 69 | { 70 | id: 'wrong' 71 | } 72 | end 73 | 74 | it 'returns nil' do 75 | graphql! 76 | success = result['data']['deleteUser'] 77 | expect(success).to be_nil 78 | end 79 | end 80 | 81 | context 'with valid params' do 82 | let!(:user) do 83 | create(:user, company_id: admin.company_id) 84 | end 85 | 86 | let(:context) do 87 | { 88 | current_user: admin 89 | } 90 | end 91 | 92 | let(:variables) do 93 | { 94 | id: user.id 95 | } 96 | end 97 | 98 | it 'changes name' do 99 | graphql! 100 | success = result['data']['deleteUser'] 101 | expect(success).to eq(true) 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /spec/graphql/mutations/users/update_user_role_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Mutations::Users::UpdateUserRole do 6 | subject(:graphql!) { result } 7 | 8 | let!(:admin) do 9 | create(:user, :admin) 10 | end 11 | 12 | let(:result) do 13 | GraphqlSchema.execute( 14 | query_string, 15 | variables: variables, 16 | context: context 17 | ) 18 | end 19 | 20 | let(:variables) do 21 | {} 22 | end 23 | 24 | let(:query_string) do 25 | <<-GRAPHQL 26 | mutation updateUserRole($id: ID!, $role: String!){ 27 | updateUserRole(id: $id, role: $role) 28 | } 29 | GRAPHQL 30 | end 31 | 32 | describe 'updateUser' do 33 | context 'when not an admin' do 34 | let(:user) do 35 | create(:user, company_id: admin.company_id) 36 | end 37 | 38 | let(:context) do 39 | { 40 | current_user: user 41 | } 42 | end 43 | 44 | let(:variables) do 45 | { 46 | id: user.id, 47 | role: 'admin' 48 | } 49 | end 50 | 51 | it 'returns errors' do 52 | graphql! 53 | message = result['errors'][0]['message'] 54 | expect(message).not_to be_nil 55 | end 56 | 57 | it 'not updates user role' do 58 | graphql! 59 | expect(user.role).to eq('user') 60 | end 61 | end 62 | 63 | context 'with invalid id' do 64 | let(:user) do 65 | create(:user, company_id: admin.company_id) 66 | end 67 | 68 | let(:context) do 69 | { 70 | current_user: admin 71 | } 72 | end 73 | 74 | let(:variables) do 75 | { 76 | id: 'wrong', 77 | role: 'admin' 78 | } 79 | end 80 | 81 | it 'returns errors' do 82 | graphql! 83 | message = result['data']['updateUserRolw'] 84 | expect(message).to be_nil 85 | end 86 | end 87 | 88 | context 'with invalid params' do 89 | let(:user) do 90 | create(:user, company_id: admin.company_id) 91 | end 92 | 93 | let(:context) do 94 | { 95 | current_user: admin 96 | } 97 | end 98 | 99 | let(:variables) do 100 | { 101 | id: user.id, 102 | role: 'superadmin' 103 | } 104 | end 105 | 106 | it 'returns false' do 107 | graphql! 108 | success = result['data']['updateUserRole'] 109 | expect(success).to eq(false) 110 | end 111 | 112 | it 'not updates user role' do 113 | graphql! 114 | expect(user.role).to eq('user') 115 | end 116 | end 117 | 118 | context 'with valid params' do 119 | let!(:user) do 120 | create(:user, company_id: admin.company_id) 121 | end 122 | 123 | let(:context) do 124 | { 125 | current_user: admin 126 | } 127 | end 128 | 129 | let(:variables) do 130 | { 131 | id: user.id, 132 | role: 'admin' 133 | } 134 | end 135 | 136 | it 'returns true' do 137 | graphql! 138 | success = result['data']['updateUserRole'] 139 | expect(success).to eq(true) 140 | end 141 | 142 | it 'updates user role' do 143 | graphql! 144 | expect(user.reload.role).to eq('admin') 145 | end 146 | end 147 | end 148 | end 149 | -------------------------------------------------------------------------------- /spec/graphql/mutations/users/update_user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Mutations::Users::UpdateUser do 6 | subject(:graphql!) { result } 7 | 8 | let!(:admin) do 9 | create(:user, :admin) 10 | end 11 | 12 | let(:result) do 13 | GraphqlSchema.execute( 14 | query_string, 15 | variables: variables, 16 | context: context 17 | ) 18 | end 19 | 20 | let(:variables) do 21 | {} 22 | end 23 | 24 | let(:query_string) do 25 | <<-GRAPHQL 26 | mutation updateUser($id: ID!, $attributes: UserInput!){ 27 | updateUser(id: $id, attributes: $attributes) { 28 | id 29 | firstName 30 | lastName 31 | email 32 | } 33 | } 34 | GRAPHQL 35 | end 36 | 37 | describe 'updateUser' do 38 | context 'when not an admin' do 39 | let(:user) do 40 | create(:user, company_id: admin.company_id) 41 | end 42 | 43 | let(:context) do 44 | { 45 | current_user: user 46 | } 47 | end 48 | 49 | let(:variables) do 50 | { 51 | id: user.id, 52 | attributes: { 53 | email: 'mail@pete.de', 54 | lastName: 'new last name', 55 | firstName: 'new first name' 56 | } 57 | } 58 | end 59 | 60 | it 'returns errors' do 61 | graphql! 62 | message = result['errors'][0]['message'] 63 | expect(message).not_to be_nil 64 | end 65 | end 66 | 67 | context 'with invalid id' do 68 | let(:user) do 69 | create(:user, company_id: admin.company_id) 70 | end 71 | 72 | let(:context) do 73 | { 74 | current_user: admin 75 | } 76 | end 77 | 78 | let(:variables) do 79 | { 80 | id: 'wrong', 81 | attributes: {} 82 | } 83 | end 84 | 85 | it 'returns errors' do 86 | graphql! 87 | message = result['errors'][0]['message'] 88 | expect(message).not_to be_nil 89 | end 90 | end 91 | 92 | context 'with invalid params' do 93 | let(:user) do 94 | create(:user, company_id: admin.company_id) 95 | end 96 | 97 | let(:context) do 98 | { 99 | current_user: admin 100 | } 101 | end 102 | 103 | let(:variables) do 104 | { 105 | id: user.id, 106 | attributes: {} 107 | } 108 | end 109 | 110 | it 'returns errors' do 111 | graphql! 112 | message = result['errors'][0]['message'] 113 | expect(message).not_to be_nil 114 | end 115 | end 116 | 117 | context 'with valid params' do 118 | let!(:user) do 119 | create(:user, company_id: admin.company_id) 120 | end 121 | 122 | let(:context) do 123 | { 124 | current_user: admin 125 | } 126 | end 127 | 128 | let(:variables) do 129 | { 130 | id: user.id, 131 | attributes: { 132 | email: 'mail@pete.de', 133 | lastName: 'new last name', 134 | firstName: 'new first name' 135 | } 136 | } 137 | end 138 | 139 | it 'changes name' do 140 | graphql! 141 | name = result['data']['updateUser']['firstName'] 142 | expect(name).to eq('new first name') 143 | end 144 | end 145 | end 146 | end 147 | -------------------------------------------------------------------------------- /spec/graphql/resolvers/companies/company_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Resolvers::Companies::Company, type: :request do 6 | subject(:graphql!) { result } 7 | 8 | let(:result) do 9 | GraphqlSchema.execute( 10 | query_string, 11 | variables: variables, 12 | context: context 13 | ) 14 | end 15 | 16 | let(:variables) do 17 | {} 18 | end 19 | 20 | let(:query_string) do 21 | <<-GRAPHQL 22 | query { 23 | company { 24 | id 25 | name 26 | } 27 | } 28 | GRAPHQL 29 | end 30 | 31 | describe 'company' do 32 | context 'when there\'s no current user' do 33 | let(:context) do 34 | { 35 | current_user: nil 36 | } 37 | end 38 | 39 | it 'returns nil' do 40 | graphql! 41 | expect(result['data']['company']).to eq(nil) 42 | end 43 | end 44 | 45 | context 'when there\'s a current user' do 46 | let!(:user) do 47 | create(:user) 48 | end 49 | 50 | let(:context) do 51 | { 52 | current_user: user 53 | } 54 | end 55 | 56 | it 'returns company of user' do 57 | graphql! 58 | company_id = result['data']['company']['id'] 59 | expect(company_id).to eq(user.company.id) 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /spec/graphql/resolvers/users/me_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Resolvers::Users::Me, type: :request do 6 | subject(:graphql!) { result } 7 | 8 | let(:result) do 9 | GraphqlSchema.execute( 10 | query_string, 11 | variables: variables, 12 | context: context 13 | ) 14 | end 15 | 16 | let(:variables) do 17 | {} 18 | end 19 | 20 | let(:query_string) do 21 | <<-GRAPHQL 22 | query { 23 | me { 24 | name 25 | isConfirmed 26 | isLocked 27 | } 28 | } 29 | GRAPHQL 30 | end 31 | 32 | describe 'me' do 33 | context 'when there\'s no current user' do 34 | let(:context) do 35 | { 36 | current_user: nil 37 | } 38 | end 39 | 40 | it 'is nil' do 41 | graphql! 42 | expect(result['data']['me']).to eq(nil) 43 | end 44 | end 45 | 46 | context 'when there\'s a current user' do 47 | let!(:user) do 48 | create( 49 | :user, 50 | first_name: 'A', 51 | last_name: 'B' 52 | ) 53 | end 54 | 55 | let(:context) do 56 | { 57 | current_user: user 58 | } 59 | end 60 | 61 | it 'shows the user\'s name' do 62 | graphql! 63 | user_name = result['data']['me']['name'] 64 | expect(user_name).to eq('A B') 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /spec/graphql/resolvers/users/user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Resolvers::Users::User, type: :request do 6 | subject(:graphql!) { result } 7 | 8 | let!(:company) do 9 | create(:company) 10 | end 11 | 12 | let!(:admin) do 13 | create(:user, :admin, company_id: company.id) 14 | end 15 | 16 | let(:result) do 17 | GraphqlSchema.execute( 18 | query_string, 19 | variables: variables, 20 | context: context 21 | ) 22 | end 23 | 24 | let(:query_string) do 25 | <<-GRAPHQL 26 | query($id: ID!) { 27 | user(id: $id){ 28 | id 29 | email 30 | firstName 31 | lastName 32 | } 33 | } 34 | GRAPHQL 35 | end 36 | 37 | let(:context) do 38 | { current_user: admin } 39 | end 40 | 41 | describe 'user' do 42 | context 'when user is not member of this company' do 43 | let(:user) do 44 | create(:user, company: create(:company)) 45 | end 46 | 47 | let(:variables) do 48 | { 'id' => user.id } 49 | end 50 | 51 | it 'returns' do 52 | graphql! 53 | expect(result['data']['user']).to eq(nil) 54 | end 55 | end 56 | 57 | context 'when user is admin of this company' do 58 | let(:user) do 59 | create(:user, company_id: company.id) 60 | end 61 | 62 | let(:variables) do 63 | { 'id' => user.id } 64 | end 65 | 66 | it 'can access users profile' do 67 | graphql! 68 | user_email = result['data']['user']['email'] 69 | expect(user_email).to eq(user.email) 70 | end 71 | end 72 | 73 | context 'when user is member of this company' do 74 | let(:user) do 75 | create(:user, company_id: company.id) 76 | end 77 | 78 | let(:user2) do 79 | create(:user, company_id: company.id) 80 | end 81 | 82 | let(:context) do 83 | { current_user: user } 84 | end 85 | 86 | let(:variables) do 87 | { 'id' => user2.id } 88 | end 89 | 90 | it 'can access users profile' do 91 | graphql! 92 | user_email = result['data']['user']['email'] 93 | expect(user_email).to eq(user2.email) 94 | end 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /spec/graphql/resolvers/users/users_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'rails_helper' 4 | 5 | RSpec.describe Resolvers::Users::Users, type: :request do 6 | subject(:graphql!) { result } 7 | 8 | let!(:company) do 9 | create(:company) 10 | end 11 | 12 | let!(:admin) do 13 | create(:user, :admin, company_id: company.id) 14 | end 15 | 16 | let(:result) do 17 | GraphqlSchema.execute( 18 | query_string, 19 | variables: variables, 20 | context: context 21 | ) 22 | end 23 | 24 | let(:query_string) do 25 | <<-GRAPHQL 26 | query($orderBy: ItemOrder, $filter: UserFilter, $after: String, $before: String, $first: Int, $last: Int) { 27 | users(orderBy: $orderBy, filter: $filter, after: $after, before: $before, first: $first, last: $last){ 28 | pageInfo { 29 | endCursor 30 | startCursor 31 | hasPreviousPage 32 | hasNextPage 33 | } 34 | edges{ 35 | node{ 36 | id 37 | email 38 | firstName 39 | lastName 40 | } 41 | } 42 | } 43 | } 44 | GRAPHQL 45 | end 46 | 47 | let(:context) do 48 | { current_user: admin } 49 | end 50 | 51 | let(:variables) { {} } 52 | 53 | describe 'users' do 54 | context 'when there\'s no current user' do 55 | let(:context) { { current_user: nil } } 56 | 57 | before { create_list(:user, 3) } 58 | 59 | it 'returns empty Array' do 60 | graphql! 61 | expect(result['data']['users']['edges']).to be_empty 62 | end 63 | end 64 | 65 | context 'when there\'s a current user' do 66 | let(:user) { create(:user) } 67 | 68 | let(:context) { { current_user: user } } 69 | 70 | before { create_list(:user, 3, company: user.company) } 71 | 72 | it 'returns user in edges.' do 73 | graphql! 74 | expect(result['data']['users']['edges'].length).to eq(4) 75 | end 76 | 77 | it 'returns pageInfo' do 78 | graphql! 79 | expect(result['data']['users']['pageInfo']['startCursor']).not_to be_empty 80 | end 81 | end 82 | 83 | context 'when filters set' do 84 | let(:user) { create(:user) } 85 | let(:user2) { create(:user) } 86 | 87 | let(:context) { { current_user: user } } 88 | 89 | let(:variables) do 90 | { 91 | filter: { 92 | firstName: user.first_name 93 | } 94 | } 95 | end 96 | 97 | before { create_list(:user, 3, company: user.company) } 98 | 99 | it 'returns only filtered user' do 100 | graphql! 101 | users = result['data']['users']['edges'] 102 | expect(users.first['node']['id']).to eq(user.id) 103 | end 104 | 105 | it 'returns only the filterd one' do 106 | graphql! 107 | users = result['data']['users']['edges'] 108 | expect(users.length).to eq(1) 109 | end 110 | 111 | it 'returns pageInfo' do 112 | graphql! 113 | expect(result['data']['users']['pageInfo']['startCursor']).not_to be_empty 114 | end 115 | 116 | it 'not has errors' do 117 | graphql! 118 | expect(result['errors']).to be_nil 119 | end 120 | end 121 | 122 | context 'when orderBy set' do 123 | let(:user) { create(:user) } 124 | let(:user2) { create(:user) } 125 | 126 | let(:context) { { current_user: user } } 127 | 128 | let(:variables) do 129 | { 130 | orderBy: { 131 | attribute: 'first_name', 132 | direction: 'asc' 133 | } 134 | } 135 | end 136 | 137 | before { create_list(:user, 20, company: user.company) } 138 | 139 | it 'returns ordered users' do 140 | graphql! 141 | users = result['data']['users']['edges'] 142 | (users.length - 2).times do |i| 143 | expect(users[i]['node']['firstName'][0] <= users[i + 1]['node']['firstName'][0]).to be_truthy 144 | end 145 | end 146 | 147 | it 'returns pageInfo' do 148 | graphql! 149 | expect(result['data']['users']['pageInfo']['startCursor']).not_to be_empty 150 | end 151 | 152 | it 'not has errors' do 153 | graphql! 154 | expect(result['errors']).to be_nil 155 | end 156 | end 157 | end 158 | end 159 | -------------------------------------------------------------------------------- /spec/graphql/types/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/spec/graphql/types/.keep -------------------------------------------------------------------------------- /spec/locales/locale_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # testing all locale files 4 | Dir.glob('config/locales/**/*.yml') do |locale_file| 5 | RSpec.describe "Locale file #{locale_file}" do 6 | it_behaves_like 'a valid locale file', locale_file 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/spec/mailers/.keep -------------------------------------------------------------------------------- /spec/models/companies/company_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: companies 6 | # 7 | # id :uuid not null, primary key 8 | # name :string 9 | # slug :string 10 | # users_count :integer 11 | # created_at :datetime not null 12 | # updated_at :datetime not null 13 | # 14 | # Indexes 15 | # 16 | # index_companies_on_slug (slug) UNIQUE 17 | # 18 | require 'rails_helper' 19 | 20 | RSpec.describe Companies::Company, type: :model do 21 | it 'has a valid factory' do 22 | expect(create(:company)).to be_valid 23 | end 24 | 25 | # Validations 26 | it { is_expected.to validate_presence_of(:name) } 27 | it { is_expected.to validate_length_of(:name).is_at_most(255) } 28 | end 29 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # == Schema Information 4 | # 5 | # Table name: users 6 | # 7 | # id :uuid not null, primary key 8 | # confirmation_sent_at :datetime 9 | # confirmation_token :string 10 | # confirmed_at :datetime 11 | # current_sign_in_at :datetime 12 | # current_sign_in_ip :inet 13 | # email :string default(""), not null 14 | # encrypted_password :string default(""), not null 15 | # failed_attempts :integer default(0), not null 16 | # first_name :string default(""), not null 17 | # invitation_accepted_at :datetime 18 | # invitation_created_at :datetime 19 | # invitation_limit :integer 20 | # invitation_sent_at :datetime 21 | # invitation_token :string 22 | # invitations_count :integer default(0) 23 | # invited_by_type :string 24 | # last_name :string default(""), not null 25 | # last_seen_at :datetime 26 | # last_sign_in_at :datetime 27 | # last_sign_in_ip :inet 28 | # locked_at :datetime 29 | # refresh_token :string 30 | # remember_created_at :datetime 31 | # reset_password_sent_at :datetime 32 | # reset_password_token :string 33 | # role :integer default("user"), not null 34 | # sign_in_count :integer default(0), not null 35 | # unconfirmed_email :string 36 | # unlock_token :string 37 | # created_at :datetime not null 38 | # updated_at :datetime not null 39 | # company_id :uuid 40 | # invited_by_id :bigint 41 | # 42 | # Indexes 43 | # 44 | # index_users_on_confirmation_token (confirmation_token) UNIQUE 45 | # index_users_on_email (email) UNIQUE 46 | # index_users_on_invitation_token (invitation_token) UNIQUE 47 | # index_users_on_invitations_count (invitations_count) 48 | # index_users_on_invited_by_id (invited_by_id) 49 | # index_users_on_invited_by_type_and_invited_by_id (invited_by_type,invited_by_id) 50 | # index_users_on_refresh_token (refresh_token) UNIQUE 51 | # index_users_on_reset_password_token (reset_password_token) UNIQUE 52 | # index_users_on_unlock_token (unlock_token) UNIQUE 53 | # 54 | require 'rails_helper' 55 | 56 | RSpec.describe User, type: :model do 57 | it 'has a valid factory' do 58 | expect(create(:user)).to be_valid 59 | expect(create(:user, :user)).to be_valid 60 | expect(create(:user, :admin)).to be_valid 61 | end 62 | 63 | # Validations 64 | it { is_expected.to validate_length_of(:first_name).is_at_most(255) } 65 | it { is_expected.to validate_length_of(:last_name).is_at_most(255) } 66 | it { is_expected.to validate_presence_of(:email) } 67 | it { is_expected.to validate_length_of(:email).is_at_most(255) } 68 | it { is_expected.to allow_value('email@address.foo').for(:email) } 69 | it { is_expected.not_to allow_value('email').for(:email) } 70 | it { is_expected.not_to allow_value('email@domain').for(:email) } 71 | it { is_expected.not_to allow_value('email@domain.').for(:email) } 72 | it { is_expected.not_to allow_value('email@domain.a').for(:email) } 73 | 74 | # Callbacks 75 | describe '#setup_new_user' do 76 | let(:user) { build(:user) } 77 | 78 | it 'sets role to user' do 79 | expect(user.role).to eq 'user' 80 | end 81 | end 82 | 83 | describe '#setup_company' do 84 | context 'when company is not set' do 85 | let(:user) { create(:user, company: nil) } 86 | 87 | it 'creates a new company' do 88 | expect(user.company).not_to be_nil 89 | end 90 | 91 | it 'makes this user an admin' do 92 | expect(user.role).to eq 'admin' 93 | end 94 | end 95 | 96 | context 'when company is set' do 97 | let(:user) { create(:user) } 98 | 99 | it 'has a company' do 100 | expect(user.company).not_to be_nil 101 | end 102 | 103 | it 'gives user role' do 104 | expect(user.role).to eq 'user' 105 | end 106 | end 107 | end 108 | 109 | # Methods 110 | describe '#name' do 111 | it 'returns first and lastname' do 112 | expect(create(:user, first_name: 'A', last_name: 'B').name).to eq 'A B' 113 | end 114 | end 115 | end 116 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV["RAILS_ENV"] = "test" 4 | require "simplecov" 5 | SimpleCov.start "rails" 6 | require "spec_helper" 7 | require File.expand_path('../config/environment', __dir__) 8 | # Prevent database truncation if the environment is production 9 | abort("The Rails environment is running in production mode!") if Rails.env.production? 10 | require "rspec/rails" 11 | require "shoulda/matchers" 12 | 13 | # Add additional requires below this line. Rails is not loaded until this point! 14 | 15 | # Requires supporting ruby files with custom matchers and macros, etc, in 16 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 17 | # run as spec files by default. This means that files in spec/support that end 18 | # in _spec.rb will both be required and run as specs, causing the specs to be 19 | # run twice. It is recommended that you do not name files matching this glob to 20 | # end with _spec.rb. You can configure this pattern with the --pattern 21 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 22 | # 23 | # The following line is provided for convenience purposes. It has the downside 24 | # of increasing the boot-up time by auto-requiring all files in the support 25 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 26 | # require only the support files necessary. 27 | # 28 | Dir[Rails.root.join("spec", "support", "**", "*.rb")].each { |f| require f } 29 | 30 | # Checks for pending migration and applies them before tests are run. 31 | # If you are not using ActiveRecord, you can remove this line. 32 | ActiveRecord::Migration.maintain_test_schema! 33 | 34 | RSpec.configure do |config| 35 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 36 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 37 | 38 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 39 | # examples within a transaction, remove the following line or assign false 40 | # instead of true. 41 | config.use_transactional_fixtures = false 42 | 43 | # RSpec Rails can automatically mix in different behaviours to your tests 44 | # based on their file location, for example enabling you to call `get` and 45 | # `post` in specs under `spec/controllers`. 46 | # 47 | # You can disable this behaviour by removing the line below, and instead 48 | # explicitly tag your specs with their type, e.g.: 49 | # 50 | # RSpec.describe UsersController, :type => :controller do 51 | # # ... 52 | # end 53 | # 54 | # The different available types are documented in the features, such as in 55 | # https://relishapp.com/rspec/rspec-rails/docs 56 | config.infer_spec_type_from_file_location! 57 | 58 | # Filter lines from Rails gems in backtraces. 59 | config.filter_rails_from_backtrace! 60 | # arbitrary gems may also be filtered via: 61 | # config.filter_gems_from_backtrace("gem name") 62 | 63 | config.include FactoryBot::Syntax::Methods 64 | config.include RequestSpecHelper, type: :request 65 | config.include Devise::Test::ControllerHelpers, type: :controller 66 | config.extend DeviseHelper, type: :controller 67 | end 68 | 69 | Shoulda::Matchers.configure do |config| 70 | config.integrate do |with| 71 | with.test_framework :rspec 72 | with.library :rails 73 | end 74 | end 75 | 76 | RSpec::Expectations.configuration.on_potential_false_positives = :nothing -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 4 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 5 | # The generated `.rspec` file contains `--require spec_helper` which will cause 6 | # this file to always be loaded, without a need to explicitly require it in any 7 | # files. 8 | # 9 | # Given that it is always loaded, you are encouraged to keep this file as 10 | # light-weight as possible. Requiring heavyweight dependencies from this file 11 | # will add to the boot time of your test suite on EVERY test run, even for an 12 | # individual file that may not need all of that loaded. Instead, consider making 13 | # a separate helper file that requires the additional dependencies and performs 14 | # the additional setup, and require it from the spec files that actually need 15 | # it. 16 | # 17 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 18 | RSpec.configure do |config| 19 | # rspec-expectations config goes here. You can use an alternate 20 | # assertion/expectation library such as wrong or the stdlib/minitest 21 | # assertions if you prefer. 22 | config.expect_with :rspec do |expectations| 23 | # This option will default to `true` in RSpec 4. It makes the `description` 24 | # and `failure_message` of custom matchers include text for helper methods 25 | # defined using `chain`, e.g.: 26 | # be_bigger_than(2).and_smaller_than(4).description 27 | # # => "be bigger than 2 and smaller than 4" 28 | # ...rather than: 29 | # # => "be bigger than 2" 30 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 31 | end 32 | 33 | # rspec-mocks config goes here. You can use an alternate test double 34 | # library (such as bogus or mocha) by changing the `mock_with` option here. 35 | config.mock_with :rspec do |mocks| 36 | # Prevents you from mocking or stubbing a method that does not exist on 37 | # a real object. This is generally recommended, and will default to 38 | # `true` in RSpec 4. 39 | mocks.verify_partial_doubles = true 40 | end 41 | 42 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 43 | # have no way to turn it off -- the option exists only for backwards 44 | # compatibility in RSpec 3). It causes shared context metadata to be 45 | # inherited by the metadata hash of host groups and examples, rather than 46 | # triggering implicit auto-inclusion in groups with matching metadata. 47 | config.shared_context_metadata_behavior = :apply_to_host_groups 48 | 49 | # Print the 10 slowest examples and example groups at the 50 | # end of the spec run, to help surface which specs are running 51 | # particularly slow. 52 | config.profile_examples = 10 53 | 54 | # The settings below are suggested to provide a good initial experience 55 | # with RSpec, but feel free to customize to your heart's content. 56 | 57 | # This allows you to limit a spec run to individual examples or groups 58 | # you care about by tagging them with `:focus` metadata. When nothing 59 | # is tagged with `:focus`, all examples get run. RSpec also provides 60 | # aliases for `it`, `describe`, and `context` that include `:focus` 61 | # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 62 | # config.filter_run_when_matching :focus 63 | 64 | # Allows RSpec to persist some state between runs in order to support 65 | # the `--only-failures` and `--next-failure` CLI options. We recommend 66 | # you configure your source control system to ignore this file. 67 | # config.example_status_persistence_file_path = "spec/examples.txt" 68 | 69 | # Limits the available syntax to the non-monkey patched syntax that is 70 | # recommended. For more details, see: 71 | # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 72 | # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 73 | # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 74 | # config.disable_monkey_patching! 75 | 76 | # Many RSpec users commonly either run the entire suite or an individual 77 | # file, and it's useful to allow more verbose output when running an 78 | # individual spec file. 79 | # if config.files_to_run.one? 80 | # Use the documentation formatter for detailed output, 81 | # unless a formatter has already been configured 82 | # (e.g. via a command-line flag). 83 | # config.default_formatter = "doc" 84 | # end 85 | 86 | # Run specs in random order to surface order dependencies. If you find an 87 | # order dependency and want to debug it, you can fix the order by providing 88 | # the seed, which is printed after each run. 89 | # --seed 1234 90 | # config.order = :random 91 | 92 | # Seed global randomization in this process using the `--seed` CLI option. 93 | # Setting this allows you to use `--seed` to deterministically reproduce 94 | # test failures related to randomization by passing the same `--seed` value 95 | # as the one that triggered the failure. 96 | # Kernel.srand config.seed 97 | end -------------------------------------------------------------------------------- /spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | RSpec.configure do |config| 4 | config.before(:suite) do 5 | DatabaseCleaner.clean_with(:truncation) 6 | end 7 | 8 | config.before do 9 | DatabaseCleaner.strategy = :transaction 10 | end 11 | 12 | config.before(:each, js: true) do 13 | DatabaseCleaner.strategy = :truncation 14 | end 15 | 16 | config.before do 17 | DatabaseCleaner.start 18 | end 19 | 20 | config.after do 21 | DatabaseCleaner.clean 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /spec/support/devise_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module DeviseHelper 4 | def login_user(user = nil) 5 | before do 6 | @request.env['devise.mapping'] = Devise.mappings[:user] 7 | @current_user = user || create(:user) 8 | # user.confirm! 9 | # or set a confirmed_at inside the factory. 10 | # Only necessary if you are using the "confirmable" module 11 | sign_in @current_user 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/support/request_spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Helper class to test logged in and logged out users 4 | module RequestSpecHelper 5 | include Warden::Test::Helpers 6 | 7 | def self.included(base) 8 | base.before { Warden.test_mode! } 9 | base.after { Warden.test_reset! } 10 | end 11 | 12 | def sign_in(resource) 13 | login_as(resource, scope: warden_scope(resource)) 14 | end 15 | 16 | def sign_out(resource) 17 | logout(warden_scope(resource)) 18 | end 19 | 20 | private 21 | 22 | def warden_scope(resource) 23 | resource.class.name.underscore.to_sym 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zauberware/rails-devise-graphql/f3e71d39e9e5bb7d282af4546c65ccead6c67fb0/tmp/.keep --------------------------------------------------------------------------------