├── .gitignore ├── .rspec ├── Gemfile ├── Gemfile.lock ├── README.rdoc ├── Rakefile ├── app ├── assets │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── sample.coffee │ │ ├── user_sessions.coffee │ │ └── users.coffee │ └── stylesheets │ │ ├── application.css │ │ ├── sample.scss │ │ ├── scaffolds.scss │ │ ├── user_sessions.scss │ │ └── users.scss ├── controllers │ ├── api │ │ └── v1 │ │ │ ├── application_base_controller.rb │ │ │ ├── password_resets_controller.rb │ │ │ ├── sample_controller.rb │ │ │ ├── user_sessions_controller.rb │ │ │ └── users_controller.rb │ ├── application_controller.rb │ └── concerns │ │ └── .keep ├── helpers │ ├── api │ │ └── v1 │ │ │ └── password_resets_helper.rb │ ├── application_helper.rb │ ├── sample_helper.rb │ ├── user_sessions_helper.rb │ └── users_helper.rb ├── mailers │ ├── .keep │ ├── application_mailer.rb │ └── user_mailer.rb ├── models │ ├── .keep │ ├── api_key.rb │ ├── concerns │ │ └── .keep │ ├── department.rb │ ├── user.rb │ └── user_department.rb └── views │ ├── api │ └── v1 │ │ ├── password_resets │ │ ├── create.html.erb │ │ ├── edit.html.erb │ │ └── update.html.erb │ │ ├── sample │ │ ├── admin.json.jbuilder │ │ ├── public.json.jbuilder │ │ └── restrict.json.jbuilder │ │ ├── user_sessions │ │ ├── create.html.erb │ │ ├── create.json.jbuilder │ │ ├── destroy.html.erb │ │ └── new.html.erb │ │ └── users │ │ ├── index.json.jbuilder │ │ └── show.json.jbuilder │ ├── layouts │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ └── user_mailer │ └── reset_password_email.text.erb ├── bin ├── bundle ├── rails ├── rake ├── setup └── spring ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── session_store.rb │ ├── sorcery.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── routes.rb └── secrets.yml ├── db ├── migrate │ ├── 20150520221548_sorcery_core.rb │ ├── 20150526221756_create_api_keys.rb │ ├── 20150529142034_add_active_api_key.rb │ ├── 20150607122809_create_user_departments.rb │ ├── 20150607134905_create_departments.rb │ └── 20150922120215_sorcery_reset_password.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico └── robots.txt └── spec ├── controllers ├── password_resets_controller_spec.rb ├── sample_controller_spec.rb ├── user_sessions_controller_spec.rb └── users_controller_spec.rb ├── factories ├── api_keys.rb ├── department.rb ├── user_departments.rb └── users.rb ├── helpers ├── api │ └── v1 │ │ └── password_resets_helper_spec.rb ├── sample_helper_spec.rb ├── user_sessions_helper_spec.rb └── users_helper_spec.rb ├── mailers └── user_mailer_spec.rb ├── models ├── api_key_spec.rb ├── department_spec.rb ├── user_department_spec.rb └── user_spec.rb ├── rails_helper.rb ├── requests └── users_spec.rb ├── routing └── users_routing_spec.rb ├── spec_helper.rb └── views ├── sample ├── public.html.erb_spec.rb └── restrict.html.erb_spec.rb ├── user_sessions ├── create.html.erb_spec.rb ├── destroy.html.erb_spec.rb └── new.html.erb_spec.rb └── users ├── edit.html.erb_spec.rb ├── index.html.erb_spec.rb ├── new.html.erb_spec.rb └── show.html.erb_spec.rb /.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 | !/log/.keep 17 | /tmp 18 | /.idea 19 | /vendor 20 | 21 | /config/settings.yml 22 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --color 2 | --require spec_helper 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '4.2.1' 6 | # Use sqlite3 as the database for Active Record 7 | gem 'sqlite3' 8 | # Use SCSS for stylesheets 9 | gem 'sass-rails', '~> 5.0' 10 | # Use Uglifier as compressor for JavaScript assets 11 | gem 'uglifier', '>= 1.3.0' 12 | # Use CoffeeScript for .coffee assets and views 13 | gem 'coffee-rails', '~> 4.1.0' 14 | # See https://github.com/rails/execjs#readme for more supported runtimes 15 | # gem 'therubyracer', platforms: :ruby 16 | 17 | # Use jquery as the JavaScript library 18 | gem 'jquery-rails' 19 | # Turbolinks makes following links in your web application faster. Read more: https://github.com/rails/turbolinks 20 | gem 'turbolinks' 21 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 22 | gem 'jbuilder', '~> 2.0' 23 | # bundle exec rake doc:rails generates the API under doc/api. 24 | gem 'sdoc', '~> 0.4.0', group: :doc 25 | 26 | # Use ActiveModel has_secure_password 27 | # gem 'bcrypt', '~> 3.1.7' 28 | 29 | # Use Unicorn as the app server 30 | # gem 'unicorn' 31 | 32 | # Use Capistrano for deployment 33 | # gem 'capistrano-rails', group: :development 34 | 35 | gem 'sorcery' 36 | gem 'rspec-rails' 37 | 38 | group :development, :test do 39 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 40 | gem 'pry-byebug' 41 | 42 | # Access an IRB console on exception pages or by using <%= console %> in views 43 | gem 'web-console', '~> 2.0' 44 | 45 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 46 | gem 'spring' 47 | gem 'spring-commands-rspec' 48 | end 49 | 50 | group :test do 51 | gem 'factory_girl_rails' 52 | end 53 | 54 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionmailer (4.2.1) 5 | actionpack (= 4.2.1) 6 | actionview (= 4.2.1) 7 | activejob (= 4.2.1) 8 | mail (~> 2.5, >= 2.5.4) 9 | rails-dom-testing (~> 1.0, >= 1.0.5) 10 | actionpack (4.2.1) 11 | actionview (= 4.2.1) 12 | activesupport (= 4.2.1) 13 | rack (~> 1.6) 14 | rack-test (~> 0.6.2) 15 | rails-dom-testing (~> 1.0, >= 1.0.5) 16 | rails-html-sanitizer (~> 1.0, >= 1.0.1) 17 | actionview (4.2.1) 18 | activesupport (= 4.2.1) 19 | builder (~> 3.1) 20 | erubis (~> 2.7.0) 21 | rails-dom-testing (~> 1.0, >= 1.0.5) 22 | rails-html-sanitizer (~> 1.0, >= 1.0.1) 23 | activejob (4.2.1) 24 | activesupport (= 4.2.1) 25 | globalid (>= 0.3.0) 26 | activemodel (4.2.1) 27 | activesupport (= 4.2.1) 28 | builder (~> 3.1) 29 | activerecord (4.2.1) 30 | activemodel (= 4.2.1) 31 | activesupport (= 4.2.1) 32 | arel (~> 6.0) 33 | activesupport (4.2.1) 34 | i18n (~> 0.7) 35 | json (~> 1.7, >= 1.7.7) 36 | minitest (~> 5.1) 37 | thread_safe (~> 0.3, >= 0.3.4) 38 | tzinfo (~> 1.1) 39 | arel (6.0.0) 40 | bcrypt (3.1.10) 41 | binding_of_caller (0.7.2) 42 | debug_inspector (>= 0.0.1) 43 | builder (3.2.2) 44 | byebug (4.0.5) 45 | columnize (= 0.9.0) 46 | coderay (1.1.0) 47 | coffee-rails (4.1.0) 48 | coffee-script (>= 2.2.0) 49 | railties (>= 4.0.0, < 5.0) 50 | coffee-script (2.4.1) 51 | coffee-script-source 52 | execjs 53 | coffee-script-source (1.9.1.1) 54 | columnize (0.9.0) 55 | debug_inspector (0.0.2) 56 | diff-lcs (1.2.5) 57 | erubis (2.7.0) 58 | execjs (2.5.2) 59 | factory_girl (4.5.0) 60 | activesupport (>= 3.0.0) 61 | factory_girl_rails (4.5.0) 62 | factory_girl (~> 4.5.0) 63 | railties (>= 3.0.0) 64 | faraday (0.9.1) 65 | multipart-post (>= 1.2, < 3) 66 | globalid (0.3.5) 67 | activesupport (>= 4.1.0) 68 | i18n (0.7.0) 69 | jbuilder (2.2.16) 70 | activesupport (>= 3.0.0, < 5) 71 | multi_json (~> 1.2) 72 | jquery-rails (4.0.3) 73 | rails-dom-testing (~> 1.0) 74 | railties (>= 4.2.0) 75 | thor (>= 0.14, < 2.0) 76 | json (1.8.2) 77 | jwt (1.5.0) 78 | loofah (2.0.2) 79 | nokogiri (>= 1.5.9) 80 | mail (2.6.3) 81 | mime-types (>= 1.16, < 3) 82 | method_source (0.8.2) 83 | mime-types (2.5) 84 | mini_portile (0.6.2) 85 | minitest (5.6.1) 86 | multi_json (1.11.0) 87 | multi_xml (0.5.5) 88 | multipart-post (2.0.0) 89 | nokogiri (1.6.6.2) 90 | mini_portile (~> 0.6.0) 91 | oauth (0.4.7) 92 | oauth2 (1.0.0) 93 | faraday (>= 0.8, < 0.10) 94 | jwt (~> 1.0) 95 | multi_json (~> 1.3) 96 | multi_xml (~> 0.5) 97 | rack (~> 1.2) 98 | pry (0.10.1) 99 | coderay (~> 1.1.0) 100 | method_source (~> 0.8.1) 101 | slop (~> 3.4) 102 | pry-byebug (3.1.0) 103 | byebug (~> 4.0) 104 | pry (~> 0.10) 105 | rack (1.6.1) 106 | rack-test (0.6.3) 107 | rack (>= 1.0) 108 | rails (4.2.1) 109 | actionmailer (= 4.2.1) 110 | actionpack (= 4.2.1) 111 | actionview (= 4.2.1) 112 | activejob (= 4.2.1) 113 | activemodel (= 4.2.1) 114 | activerecord (= 4.2.1) 115 | activesupport (= 4.2.1) 116 | bundler (>= 1.3.0, < 2.0) 117 | railties (= 4.2.1) 118 | sprockets-rails 119 | rails-deprecated_sanitizer (1.0.3) 120 | activesupport (>= 4.2.0.alpha) 121 | rails-dom-testing (1.0.6) 122 | activesupport (>= 4.2.0.beta, < 5.0) 123 | nokogiri (~> 1.6.0) 124 | rails-deprecated_sanitizer (>= 1.0.1) 125 | rails-html-sanitizer (1.0.2) 126 | loofah (~> 2.0) 127 | railties (4.2.1) 128 | actionpack (= 4.2.1) 129 | activesupport (= 4.2.1) 130 | rake (>= 0.8.7) 131 | thor (>= 0.18.1, < 2.0) 132 | rake (10.4.2) 133 | rdoc (4.2.0) 134 | json (~> 1.4) 135 | rspec-core (3.2.3) 136 | rspec-support (~> 3.2.0) 137 | rspec-expectations (3.2.1) 138 | diff-lcs (>= 1.2.0, < 2.0) 139 | rspec-support (~> 3.2.0) 140 | rspec-mocks (3.2.1) 141 | diff-lcs (>= 1.2.0, < 2.0) 142 | rspec-support (~> 3.2.0) 143 | rspec-rails (3.2.1) 144 | actionpack (>= 3.0, < 4.3) 145 | activesupport (>= 3.0, < 4.3) 146 | railties (>= 3.0, < 4.3) 147 | rspec-core (~> 3.2.0) 148 | rspec-expectations (~> 3.2.0) 149 | rspec-mocks (~> 3.2.0) 150 | rspec-support (~> 3.2.0) 151 | rspec-support (3.2.2) 152 | sass (3.4.13) 153 | sass-rails (5.0.3) 154 | railties (>= 4.0.0, < 5.0) 155 | sass (~> 3.1) 156 | sprockets (>= 2.8, < 4.0) 157 | sprockets-rails (>= 2.0, < 4.0) 158 | tilt (~> 1.1) 159 | sdoc (0.4.1) 160 | json (~> 1.7, >= 1.7.7) 161 | rdoc (~> 4.0) 162 | slop (3.6.0) 163 | sorcery (0.9.1) 164 | bcrypt (~> 3.1) 165 | oauth (~> 0.4, >= 0.4.4) 166 | oauth2 (>= 0.8.0) 167 | spring (1.3.6) 168 | spring-commands-rspec (1.0.4) 169 | spring (>= 0.9.1) 170 | sprockets (3.1.0) 171 | rack (~> 1.0) 172 | sprockets-rails (2.3.1) 173 | actionpack (>= 3.0) 174 | activesupport (>= 3.0) 175 | sprockets (>= 2.8, < 4.0) 176 | sqlite3 (1.3.10) 177 | thor (0.19.1) 178 | thread_safe (0.3.5) 179 | tilt (1.4.1) 180 | turbolinks (2.5.3) 181 | coffee-rails 182 | tzinfo (1.2.2) 183 | thread_safe (~> 0.1) 184 | uglifier (2.7.1) 185 | execjs (>= 0.3.0) 186 | json (>= 1.8.0) 187 | web-console (2.1.2) 188 | activemodel (>= 4.0) 189 | binding_of_caller (>= 0.7.2) 190 | railties (>= 4.0) 191 | sprockets-rails (>= 2.0, < 4.0) 192 | 193 | PLATFORMS 194 | ruby 195 | 196 | DEPENDENCIES 197 | coffee-rails (~> 4.1.0) 198 | factory_girl_rails 199 | jbuilder (~> 2.0) 200 | jquery-rails 201 | pry-byebug 202 | rails (= 4.2.1) 203 | rspec-rails 204 | sass-rails (~> 5.0) 205 | sdoc (~> 0.4.0) 206 | sorcery 207 | spring 208 | spring-commands-rspec 209 | sqlite3 210 | turbolinks 211 | uglifier (>= 1.3.0) 212 | web-console (~> 2.0) 213 | -------------------------------------------------------------------------------- /README.rdoc: -------------------------------------------------------------------------------- 1 | == About 2 | 3 | This is a Sample Authentication API Appliction with Ruby on Rails, sorcery. 4 | 5 | == Environments 6 | 7 | * Ruby 2.1.2 8 | 9 | * Rails 4.2.1 10 | 11 | * sorcery 0.9.1 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/sample.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/user_sessions.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/users.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /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_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/sample.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the sample controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/scaffolds.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-color: #fff; 3 | color: #333; 4 | font-family: verdana, arial, helvetica, sans-serif; 5 | font-size: 13px; 6 | line-height: 18px; 7 | } 8 | 9 | p, ol, ul, td { 10 | font-family: verdana, arial, helvetica, sans-serif; 11 | font-size: 13px; 12 | line-height: 18px; 13 | } 14 | 15 | pre { 16 | background-color: #eee; 17 | padding: 10px; 18 | font-size: 11px; 19 | } 20 | 21 | a { 22 | color: #000; 23 | &:visited { 24 | color: #666; 25 | } 26 | &:hover { 27 | color: #fff; 28 | background-color: #000; 29 | } 30 | } 31 | 32 | div { 33 | &.field, &.actions { 34 | margin-bottom: 10px; 35 | } 36 | } 37 | 38 | #notice { 39 | color: green; 40 | } 41 | 42 | .field_with_errors { 43 | padding: 2px; 44 | background-color: red; 45 | display: table; 46 | } 47 | 48 | #error_explanation { 49 | width: 450px; 50 | border: 2px solid red; 51 | padding: 7px; 52 | padding-bottom: 0; 53 | margin-bottom: 20px; 54 | background-color: #f0f0f0; 55 | h2 { 56 | text-align: left; 57 | font-weight: bold; 58 | padding: 5px 5px 5px 15px; 59 | font-size: 12px; 60 | margin: -7px; 61 | margin-bottom: 0px; 62 | background-color: #c00; 63 | color: #fff; 64 | } 65 | ul li { 66 | font-size: 12px; 67 | list-style: square; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/assets/stylesheets/user_sessions.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the UserSessions controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/users.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the users controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /app/controllers/api/v1/application_base_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module V1 3 | class ApplicationBaseController < ApplicationController 4 | before_action :require_valid_token 5 | 6 | private 7 | 8 | def require_valid_token 9 | access_token = request.headers[:HTTP_ACCESS_TOKEN] 10 | if !User.login?(access_token) 11 | respond_to do |format| 12 | format.json { render nothing: true, status: :unauthorized } 13 | end 14 | end 15 | end 16 | 17 | def administrator_only 18 | access_token = request.headers[:HTTP_ACCESS_TOKEN] 19 | api_key = ApiKey.find_by_access_token(access_token) 20 | user = User.find(api_key.user_id) 21 | if user.user_department.department_id != Department::ADMINISTRATOR 22 | respond_to do |format| 23 | format.json { render nothing: true, status: :unauthorized } 24 | end 25 | end 26 | end 27 | end 28 | end 29 | end -------------------------------------------------------------------------------- /app/controllers/api/v1/password_resets_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::V1::PasswordResetsController < Api::V1::ApplicationBaseController 2 | skip_before_filter :require_valid_token 3 | 4 | def create 5 | @user = User.find_by_email(params[:email]) 6 | 7 | if @user 8 | respond_to do |format| 9 | @user.deliver_reset_password_instructions! 10 | format.json { render nothing: true, status: :created } 11 | end 12 | else 13 | respond_to do |format| 14 | format.json { render nothing: true, status: :not_found } 15 | end 16 | end 17 | end 18 | 19 | def edit 20 | if set_token_user_from_params? 21 | respond_to do |format| 22 | format.json { render nothing: true, status: :ok } 23 | end 24 | else 25 | respond_to do |format| 26 | format.json { render nothing: true, status: :not_found } 27 | end 28 | end 29 | end 30 | 31 | def update 32 | if set_token_user_from_params? 33 | @user.password_confirmation = params[:user][:password_confirmation] 34 | 35 | if @user.change_password!(params[:user][:password]) 36 | respond_to do |format| 37 | format.json { render nothing: true, status: :ok } 38 | end 39 | else 40 | respond_to do |format| 41 | format.json { render nothing: true, status: :not_acceptable } 42 | end 43 | end 44 | else 45 | respond_to do |format| 46 | format.json { render nothing: true, status: :not_found } 47 | end 48 | end 49 | end 50 | 51 | private 52 | def set_token_user_from_params? 53 | @token = params[:id] 54 | @user = User.load_from_reset_password_token(params[:id]) 55 | return !@user.blank? 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /app/controllers/api/v1/sample_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module V1 3 | class SampleController < Api::V1::ApplicationBaseController 4 | skip_before_filter :require_valid_token, only: :public 5 | before_filter :administrator_only, only: :admin 6 | 7 | def public 8 | @message = 'public' 9 | end 10 | 11 | def restrict 12 | @message = 'authorized' 13 | end 14 | 15 | def admin 16 | @message = 'admin' 17 | end 18 | end 19 | end 20 | end -------------------------------------------------------------------------------- /app/controllers/api/v1/user_sessions_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module V1 3 | class UserSessionsController < Api::V1::ApplicationBaseController 4 | skip_before_filter :require_valid_token, only: :create 5 | 6 | def create 7 | if @user = login(login_user[:email], login_user[:password]) 8 | api_key = @user.activate 9 | @access_token = api_key.access_token 10 | else 11 | respond_to do |format| 12 | format.json { render nothing: true, status: :not_found } 13 | end 14 | end 15 | end 16 | 17 | def destroy 18 | access_token = request.headers[:HTTP_ACCESS_TOKEN] 19 | api_key = ApiKey.find_by_access_token(access_token) 20 | if api_key 21 | user = User.find(api_key.user_id) 22 | user.inactivate 23 | respond_to do |format| 24 | format.json { render nothing: true, status: :ok } 25 | end 26 | end 27 | end 28 | 29 | private 30 | 31 | def login_user 32 | params[:user] 33 | end 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/controllers/api/v1/users_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | module V1 3 | class UsersController < Api::V1::ApplicationBaseController 4 | before_action :set_user, only: [:show, :edit, :update, :destroy] 5 | skip_before_filter :require_valid_token, only: :create 6 | 7 | # GET /users.json 8 | def index 9 | @users = User.all 10 | end 11 | 12 | # GET /users/1.json 13 | def show 14 | if !@user 15 | respond_to do |format| 16 | format.json { render nothing: true, status: :not_found } 17 | end 18 | end 19 | end 20 | 21 | # POST /users.json 22 | def create 23 | respond_to do |format| 24 | @user = User.new(user_params) 25 | 26 | if @user.save_relation(department_params) 27 | format.json { render nothing: true, status: :created } 28 | else 29 | format.json { render nothing: true, status: :bad_request } 30 | end 31 | end 32 | end 33 | 34 | # PATCH/PUT /users/1.json 35 | def update 36 | respond_to do |format| 37 | if @user.update_relation(user_params, department_params) 38 | format.json { render :show } 39 | else 40 | format.json { render json: @user.errors, status: :unprocessable_entity } 41 | end 42 | end 43 | end 44 | 45 | # DELETE /users/1.json 46 | def destroy 47 | @user.destroy 48 | respond_to do |format| 49 | format.json { head :no_content } 50 | end 51 | end 52 | 53 | private 54 | # Use callbacks to share common setup or constraints between actions. 55 | def set_user 56 | @user = User.find_by_id(params[:id]) 57 | end 58 | 59 | # Never trust parameters from the scary internet, only allow the white list through. 60 | def user_params 61 | params.require(:user).permit(:email, :name, :password, :password_confirmation) 62 | end 63 | 64 | def department_params 65 | params.require(:department).permit(:department_id) 66 | end 67 | end 68 | end 69 | end -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :null_session 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/helpers/api/v1/password_resets_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::V1::PasswordResetsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/sample_helper.rb: -------------------------------------------------------------------------------- 1 | module SampleHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/user_sessions_helper.rb: -------------------------------------------------------------------------------- 1 | module UserSessionsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/app/mailers/.keep -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | class UserMailer < ApplicationMailer 2 | 3 | # Subject can be set in your I18n file at config/locales/en.yml 4 | # with the following lookup: 5 | # 6 | # en.user_mailer.reset_password_email.subject 7 | # 8 | 9 | def reset_password_email(user) 10 | @user = User.find user.id 11 | @url = "http://0.0.0.0:8888/path?id=" + @user.reset_password_token 12 | mail(:to => user.email, :subject => "Your password has been reset") 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/app/models/.keep -------------------------------------------------------------------------------- /app/models/api_key.rb: -------------------------------------------------------------------------------- 1 | class ApiKey < ActiveRecord::Base 2 | before_create :generate_access_token, :set_expiration, :set_active 3 | belongs_to :user 4 | 5 | def before_expired? 6 | DateTime.now < self.expires_at 7 | end 8 | 9 | def set_active 10 | self.active = true 11 | end 12 | 13 | def set_expiration 14 | self.expires_at = DateTime.now + 1 15 | end 16 | 17 | private 18 | 19 | def generate_access_token 20 | begin 21 | self.access_token = SecureRandom.hex 22 | end while self.class.exists?(access_token: access_token) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/department.rb: -------------------------------------------------------------------------------- 1 | class Department < ActiveRecord::Base 2 | has_many :user_departments 3 | 4 | ADMINISTRATOR = 10 5 | PURCHASE = 20 6 | end 7 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | authenticates_with_sorcery! 3 | 4 | has_many :api_keys, dependent: :destroy 5 | has_one :user_department, dependent: :destroy 6 | 7 | validates :password, length: { minimum: 3 } 8 | validates :password, confirmation: true 9 | validates :password_confirmation, presence: true 10 | 11 | validates :email, uniqueness: true 12 | 13 | def self.login?(access_token) 14 | api_key = ApiKey.find_by_access_token(access_token) 15 | return false if !api_key || !api_key.before_expired? || !api_key.active 16 | return !self.find(api_key.user_id).nil? 17 | end 18 | 19 | def save_relation(department_params) 20 | if Department.exists?(department_params[:department_id]) && self.save 21 | user_department = UserDepartment.new(user_id: self.id, department_id: department_params[:department_id]) 22 | return user_department.save 23 | else 24 | false 25 | end 26 | end 27 | 28 | def update_relation(user_params, department_params) 29 | if Department.exists?(department_params[:department_id]) && self.update(user_params) 30 | return self.user_department.update(department_params) 31 | else 32 | false 33 | end 34 | end 35 | 36 | def activate 37 | if !api_key 38 | return ApiKey.create(user_id: self.id) 39 | else 40 | if !api_key.active 41 | api_key.set_active 42 | api_key.save 43 | end 44 | if !api_key.before_expired? 45 | api_key.set_expiration 46 | api_key.save 47 | end 48 | return api_key 49 | end 50 | end 51 | 52 | def inactivate 53 | api_key.active = false 54 | api_key.save 55 | end 56 | 57 | private 58 | 59 | def api_key 60 | @api_key ||= ApiKey.find_by_user_id(self.id) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/models/user_department.rb: -------------------------------------------------------------------------------- 1 | class UserDepartment < ActiveRecord::Base 2 | belongs_to :user 3 | belongs_to :department 4 | end 5 | -------------------------------------------------------------------------------- /app/views/api/v1/password_resets/create.html.erb: -------------------------------------------------------------------------------- 1 |

Api::V1::PasswordResets#create

2 |

Find me in app/views/api/v1/password_resets/create.html.erb

3 | -------------------------------------------------------------------------------- /app/views/api/v1/password_resets/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Api::V1::PasswordResets#edit

2 |

Find me in app/views/api/v1/password_resets/edit.html.erb

3 | -------------------------------------------------------------------------------- /app/views/api/v1/password_resets/update.html.erb: -------------------------------------------------------------------------------- 1 |

Api::V1::PasswordResets#update

2 |

Find me in app/views/api/v1/password_resets/update.html.erb

3 | -------------------------------------------------------------------------------- /app/views/api/v1/sample/admin.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.message @message -------------------------------------------------------------------------------- /app/views/api/v1/sample/public.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.message @message -------------------------------------------------------------------------------- /app/views/api/v1/sample/restrict.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.message @message -------------------------------------------------------------------------------- /app/views/api/v1/user_sessions/create.html.erb: -------------------------------------------------------------------------------- 1 |

UserSessions#create

2 |

Find me in app/views/user_sessions/create.html.erb

3 | -------------------------------------------------------------------------------- /app/views/api/v1/user_sessions/create.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.user do |json| 2 | json.id @user.id 3 | json.email @user.email 4 | json.name @user.name 5 | end 6 | 7 | json.access_token @access_token -------------------------------------------------------------------------------- /app/views/api/v1/user_sessions/destroy.html.erb: -------------------------------------------------------------------------------- 1 |

UserSessions#destroy

2 |

Find me in app/views/user_sessions/destroy.html.erb

3 | -------------------------------------------------------------------------------- /app/views/api/v1/user_sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

UserSessions#new

2 |

Find me in app/views/user_sessions/new.html.erb

3 | -------------------------------------------------------------------------------- /app/views/api/v1/users/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array!(@users) do |user| 2 | json.id user.id 3 | json.email user.email 4 | json.name user.name 5 | end 6 | -------------------------------------------------------------------------------- /app/views/api/v1/users/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @user, :id, :email, :name 2 | 3 | json.department do 4 | json.id @user.user_department.department_id 5 | json.name @user.user_department.department.name 6 | end 7 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | SorceryApiSample 5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= yield %> 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/user_mailer/reset_password_email.text.erb: -------------------------------------------------------------------------------- 1 | Hello, <%= @user.email %> 2 | =============================================== 3 | 4 | You have requested to reset your password. 5 | 6 | To choose a new password, just follow this link: <%= @url %>. 7 | 8 | Have a great day! -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 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 5 | end 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require_relative '../config/boot' 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require_relative '../config/boot' 7 | require 'rake' 8 | Rake.application.run 9 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /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 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) 11 | Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } 12 | gem "spring", match[1] 13 | require "spring/binstub" 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require "rails" 4 | # Pick the frameworks you want: 5 | require "active_model/railtie" 6 | require "active_job/railtie" 7 | require "active_record/railtie" 8 | require "action_controller/railtie" 9 | require "action_mailer/railtie" 10 | require "action_view/railtie" 11 | require "sprockets/railtie" 12 | # require "rails/test_unit/railtie" 13 | 14 | # Require the gems listed in Gemfile, including any gems 15 | # you've limited to :test, :development, or :production. 16 | Bundler.require(*Rails.groups) 17 | 18 | ENV.update YAML.load_file('config/settings.yml')[Rails.env] rescue {} 19 | 20 | module SorceryApiSample 21 | class Application < Rails::Application 22 | # Settings in config/environments/* take precedence over those specified here. 23 | # Application configuration should go into files in config/initializers 24 | # -- all .rb files in that directory are automatically loaded. 25 | 26 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 27 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 28 | # config.time_zone = 'Central Time (US & Canada)' 29 | 30 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 31 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 32 | # config.i18n.default_locale = :de 33 | 34 | # Do not swallow errors in after_commit/after_rollback callbacks. 35 | config.active_record.raise_in_transactional_callbacks = true 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /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: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | 30 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 31 | # yet still be able to expire them through the digest params. 32 | config.assets.digest = true 33 | 34 | # Adds additional error checking when serving assets at runtime. 35 | # Checks for improperly declared sprockets dependencies. 36 | # Raises helpful error messages. 37 | config.assets.raise_runtime_errors = true 38 | 39 | # Raises error for missing translations 40 | # config.action_view.raise_on_missing_translations = true 41 | 42 | config.action_mailer.delivery_method = :smtp 43 | config.action_mailer.raise_delivery_errors = true 44 | config.action_mailer.smtp_settings = { 45 | :enable_starttls_auto => true, 46 | :address => 'smtp.gmail.com', 47 | :port => '587', 48 | :domain => 'smtp.gmail.com', 49 | :authentication => 'plain', 50 | :user_name => ENV['MAIL_USER'], 51 | :password => ENV['MAIL_PASSWORD'], 52 | :password => ENV['MAIL_APP_PASSWORD'] 53 | } 54 | end 55 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | end 80 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | 43 | config.action_mailer.delivery_method = :smtp 44 | config.action_mailer.raise_delivery_errors = true 45 | config.action_mailer.smtp_settings = { 46 | :enable_starttls_auto => true, 47 | :address => 'smtp.gmail.com', 48 | :port => '587', 49 | :domain => 'smtp.gmail.com', 50 | :authentication => 'plain', 51 | :user_name => ENV['MAIL_USER'], 52 | :password => ENV['MAIL_PASSWORD'], 53 | :password => ENV['MAIL_APP_PASSWORD'] 54 | } 55 | end 56 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_sorcery_api_sample_session' 4 | -------------------------------------------------------------------------------- /config/initializers/sorcery.rb: -------------------------------------------------------------------------------- 1 | # The first thing you need to configure is which modules you need in your app. 2 | # The default is nothing which will include only core features (password encryption, login/logout). 3 | # Available submodules are: :user_activation, :http_basic_auth, :remember_me, 4 | # :reset_password, :session_timeout, :brute_force_protection, :activity_logging, :external 5 | Rails.application.config.sorcery.submodules = [:reset_password] 6 | 7 | # Here you can configure each submodule's features. 8 | Rails.application.config.sorcery.configure do |config| 9 | # -- core -- 10 | # What controller action to call for non-authenticated users. You can also 11 | # override the 'not_authenticated' method of course. 12 | # Default: `:not_authenticated` 13 | # 14 | # config.not_authenticated_action = 15 | 16 | 17 | # When a non logged in user tries to enter a page that requires login, save 18 | # the URL he wanted to reach, and send him there after login, using 'redirect_back_or_to'. 19 | # Default: `true` 20 | # 21 | # config.save_return_to_url = 22 | 23 | 24 | # Set domain option for cookies; Useful for remember_me submodule. 25 | # Default: `nil` 26 | # 27 | # config.cookie_domain = 28 | 29 | 30 | # Allow the remember_me cookie to be set through AJAX 31 | # Default: `true` 32 | # 33 | # config.remember_me_httponly = 34 | 35 | 36 | # -- session timeout -- 37 | # How long in seconds to keep the session alive. 38 | # Default: `3600` 39 | # 40 | # config.session_timeout = 41 | 42 | 43 | # Use the last action as the beginning of session timeout. 44 | # Default: `false` 45 | # 46 | # config.session_timeout_from_last_action = 47 | 48 | 49 | # -- http_basic_auth -- 50 | # What realm to display for which controller name. For example {"My App" => "Application"} 51 | # Default: `{"application" => "Application"}` 52 | # 53 | # config.controller_to_realm_map = 54 | 55 | 56 | # -- activity logging -- 57 | # will register the time of last user login, every login. 58 | # Default: `true` 59 | # 60 | # config.register_login_time = 61 | 62 | 63 | # will register the time of last user logout, every logout. 64 | # Default: `true` 65 | # 66 | # config.register_logout_time = 67 | 68 | 69 | # will register the time of last user action, every action. 70 | # Default: `true` 71 | # 72 | # config.register_last_activity_time = 73 | 74 | 75 | # -- external -- 76 | # What providers are supported by this app, i.e. [:twitter, :facebook, :github, :linkedin, :xing, :google, :liveid, :salesforce] . 77 | # Default: `[]` 78 | # 79 | # config.external_providers = 80 | 81 | 82 | # You can change it by your local ca_file. i.e. '/etc/pki/tls/certs/ca-bundle.crt' 83 | # Path to ca_file. By default use a internal ca-bundle.crt. 84 | # Default: `'path/to/ca_file'` 85 | # 86 | # config.ca_file = 87 | 88 | 89 | # For information about LinkedIn API: 90 | # - user info fields go to https://developer.linkedin.com/documents/profile-fields 91 | # - access permissions go to https://developer.linkedin.com/documents/authentication#granting 92 | # 93 | # config.linkedin.key = "" 94 | # config.linkedin.secret = "" 95 | # config.linkedin.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=linkedin" 96 | # config.linkedin.user_info_fields = ['first-name', 'last-name'] 97 | # config.linkedin.user_info_mapping = {first_name: "firstName", last_name: "lastName"} 98 | # config.linkedin.access_permissions = ['r_basicprofile'] 99 | # 100 | # 101 | # For information about XING API: 102 | # - user info fields go to https://dev.xing.com/docs/get/users/me 103 | # 104 | # config.xing.key = "" 105 | # config.xing.secret = "" 106 | # config.xing.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=xing" 107 | # config.xing.user_info_mapping = {first_name: "first_name", last_name: "last_name"} 108 | # 109 | # 110 | # Twitter will not accept any requests nor redirect uri containing localhost, 111 | # make sure you use 0.0.0.0:3000 to access your app in development 112 | # 113 | # config.twitter.key = "" 114 | # config.twitter.secret = "" 115 | # config.twitter.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=twitter" 116 | # config.twitter.user_info_mapping = {:email => "screen_name"} 117 | # 118 | # config.facebook.key = "" 119 | # config.facebook.secret = "" 120 | # config.facebook.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=facebook" 121 | # config.facebook.user_info_mapping = {:email => "name"} 122 | # config.facebook.access_permissions = ["email", "publish_actions"] 123 | # config.facebook.display = "page" 124 | # config.facebook.api_version = "v2.2" 125 | # 126 | # config.github.key = "" 127 | # config.github.secret = "" 128 | # config.github.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=github" 129 | # config.github.user_info_mapping = {:email => "name"} 130 | # 131 | # config.google.key = "" 132 | # config.google.secret = "" 133 | # config.google.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=google" 134 | # config.google.user_info_mapping = {:email => "email", :username => "name"} 135 | # 136 | # config.vk.key = "" 137 | # config.vk.secret = "" 138 | # config.vk.callback_url = "http://0.0.0.0:3000/oauth/callback?provider=vk" 139 | # config.vk.user_info_mapping = {:login => "domain", :name => "full_name"} 140 | # 141 | # To use liveid in development mode you have to replace mydomain.com with 142 | # a valid domain even in development. To use a valid domain in development 143 | # simply add your domain in your /etc/hosts file in front of 127.0.0.1 144 | # 145 | # config.liveid.key = "" 146 | # config.liveid.secret = "" 147 | # config.liveid.callback_url = "http://mydomain.com:3000/oauth/callback?provider=liveid" 148 | # config.liveid.user_info_mapping = {:username => "name"} 149 | 150 | # For information about JIRA API: 151 | # https://developer.atlassian.com/display/JIRADEV/JIRA+REST+API+Example+-+OAuth+authentication 152 | # to obtain the consumer key and the public key you can use the jira-ruby gem https://github.com/sumoheavy/jira-ruby 153 | # or run openssl req -x509 -nodes -newkey rsa:1024 -sha1 -keyout rsakey.pem -out rsacert.pem to obtain the public key 154 | # Make sure you have configured the application link properly 155 | 156 | # config.jira.key = "1234567" 157 | # config.jira.secret = "jiraTest" 158 | # config.jira.site = "http://localhost:2990/jira/plugins/servlet/oauth" 159 | # config.jira.signature_method = "RSA-SHA1" 160 | # config.jira.private_key_file = "rsakey.pem" 161 | 162 | # For information about Salesforce API: 163 | # https://developer.salesforce.com/signup & 164 | # https://www.salesforce.com/us/developer/docs/api_rest/ 165 | # Salesforce callback_url must be https. You can run the following to generate self-signed ssl cert 166 | # openssl req -new -newkey rsa:2048 -sha1 -days 365 -nodes -x509 -keyout server.key -out server.crt 167 | # Make sure you have configured the application link properly 168 | # config.salesforce.key = '123123' 169 | # config.salesforce.secret = 'acb123' 170 | # config.salesforce.callback_url = "https://127.0.0.1:9292/oauth/callback?provider=salesforce" 171 | # config.salesforce.scope = "full" 172 | # config.salesforce.user_info_mapping = {:email => "email"} 173 | 174 | # --- user config --- 175 | config.user_config do |user| 176 | # -- core -- 177 | # specify username attributes, for example: [:username, :email]. 178 | # Default: `[:email]` 179 | # 180 | # user.username_attribute_names = 181 | 182 | 183 | # change *virtual* password attribute, the one which is used until an encrypted one is generated. 184 | # Default: `:password` 185 | # 186 | # user.password_attribute_name = 187 | 188 | 189 | # downcase the username before trying to authenticate, default is false 190 | # Default: `false` 191 | # 192 | # user.downcase_username_before_authenticating = 193 | 194 | 195 | # change default email attribute. 196 | # Default: `:email` 197 | # 198 | # user.email_attribute_name = 199 | 200 | 201 | # change default crypted_password attribute. 202 | # Default: `:crypted_password` 203 | # 204 | # user.crypted_password_attribute_name = 205 | 206 | 207 | # what pattern to use to join the password with the salt 208 | # Default: `""` 209 | # 210 | # user.salt_join_token = 211 | 212 | 213 | # change default salt attribute. 214 | # Default: `:salt` 215 | # 216 | # user.salt_attribute_name = 217 | 218 | 219 | # how many times to apply encryption to the password. 220 | # Default: `nil` 221 | # 222 | # user.stretches = 223 | 224 | 225 | # encryption key used to encrypt reversible encryptions such as AES256. 226 | # WARNING: If used for users' passwords, changing this key will leave passwords undecryptable! 227 | # Default: `nil` 228 | # 229 | # user.encryption_key = 230 | 231 | 232 | # use an external encryption class. 233 | # Default: `nil` 234 | # 235 | # user.custom_encryption_provider = 236 | 237 | 238 | # encryption algorithm name. See 'encryption_algorithm=' for available options. 239 | # Default: `:bcrypt` 240 | # 241 | # user.encryption_algorithm = 242 | 243 | 244 | # make this configuration inheritable for subclasses. Useful for ActiveRecord's STI. 245 | # Default: `false` 246 | # 247 | # user.subclasses_inherit_config = 248 | 249 | 250 | # -- remember_me -- 251 | # How long in seconds the session length will be 252 | # Default: `604800` 253 | # 254 | # user.remember_me_for = 255 | 256 | 257 | # -- user_activation -- 258 | # the attribute name to hold activation state (active/pending). 259 | # Default: `:activation_state` 260 | # 261 | # user.activation_state_attribute_name = 262 | 263 | 264 | # the attribute name to hold activation code (sent by email). 265 | # Default: `:activation_token` 266 | # 267 | # user.activation_token_attribute_name = 268 | 269 | 270 | # the attribute name to hold activation code expiration date. 271 | # Default: `:activation_token_expires_at` 272 | # 273 | # user.activation_token_expires_at_attribute_name = 274 | 275 | 276 | # how many seconds before the activation code expires. nil for never expires. 277 | # Default: `nil` 278 | # 279 | # user.activation_token_expiration_period = 280 | 281 | 282 | # your mailer class. Required. 283 | # Default: `nil` 284 | # 285 | # user.user_activation_mailer = UserMailer 286 | 287 | 288 | # when true sorcery will not automatically 289 | # email activation details and allow you to 290 | # manually handle how and when email is sent. 291 | # Default: `false` 292 | # 293 | # user.activation_mailer_disabled = 294 | 295 | 296 | # activation needed email method on your mailer class. 297 | # Default: `:activation_needed_email` 298 | # 299 | # user.activation_needed_email_method_name = 300 | 301 | 302 | # activation success email method on your mailer class. 303 | # Default: `:activation_success_email` 304 | # 305 | # user.activation_success_email_method_name = 306 | 307 | 308 | # do you want to prevent or allow users that did not activate by email to login? 309 | # Default: `true` 310 | # 311 | # user.prevent_non_active_users_to_login = 312 | 313 | 314 | # -- reset_password -- 315 | # reset password code attribute name. 316 | # Default: `:reset_password_token` 317 | # 318 | # user.reset_password_token_attribute_name = 319 | 320 | 321 | # expires at attribute name. 322 | # Default: `:reset_password_token_expires_at` 323 | # 324 | # user.reset_password_token_expires_at_attribute_name = 325 | 326 | 327 | # when was email sent, used for hammering protection. 328 | # Default: `:reset_password_email_sent_at` 329 | # 330 | # user.reset_password_email_sent_at_attribute_name = 331 | 332 | 333 | # mailer class. Needed. 334 | # Default: `nil` 335 | # 336 | user.reset_password_mailer = UserMailer 337 | 338 | 339 | # reset password email method on your mailer class. 340 | # Default: `:reset_password_email` 341 | # 342 | # user.reset_password_email_method_name = 343 | 344 | 345 | # when true sorcery will not automatically 346 | # email password reset details and allow you to 347 | # manually handle how and when email is sent 348 | # Default: `false` 349 | # 350 | # user.reset_password_mailer_disabled = 351 | 352 | 353 | # how many seconds before the reset request expires. nil for never expires. 354 | # Default: `nil` 355 | # 356 | # user.reset_password_expiration_period = 357 | 358 | 359 | # hammering protection, how long in seconds to wait before allowing another email to be sent. 360 | # Default: `5 * 60` 361 | # 362 | # user.reset_password_time_between_emails = 363 | 364 | 365 | # -- brute_force_protection -- 366 | # Failed logins attribute name. 367 | # Default: `:failed_logins_count` 368 | # 369 | # user.failed_logins_count_attribute_name = 370 | 371 | 372 | # This field indicates whether user is banned and when it will be active again. 373 | # Default: `:lock_expires_at` 374 | # 375 | # user.lock_expires_at_attribute_name = 376 | 377 | 378 | # How many failed logins allowed. 379 | # Default: `50` 380 | # 381 | # user.consecutive_login_retries_amount_limit = 382 | 383 | 384 | # How long the user should be banned. in seconds. 0 for permanent. 385 | # Default: `60 * 60` 386 | # 387 | # user.login_lock_time_period = 388 | 389 | # Unlock token attribute name 390 | # Default: `:unlock_token` 391 | # 392 | # user.unlock_token_attribute_name = 393 | 394 | # Unlock token mailer method 395 | # Default: `:send_unlock_token_email` 396 | # 397 | # user.unlock_token_email_method_name = 398 | 399 | # when true sorcery will not automatically 400 | # send email with unlock token 401 | # Default: `false` 402 | # 403 | # user.unlock_token_mailer_disabled = true 404 | 405 | # Unlock token mailer class 406 | # Default: `nil` 407 | # 408 | # user.unlock_token_mailer = UserMailer 409 | 410 | # -- activity logging -- 411 | # Last login attribute name. 412 | # Default: `:last_login_at` 413 | # 414 | # user.last_login_at_attribute_name = 415 | 416 | 417 | # Last logout attribute name. 418 | # Default: `:last_logout_at` 419 | # 420 | # user.last_logout_at_attribute_name = 421 | 422 | 423 | # Last activity attribute name. 424 | # Default: `:last_activity_at` 425 | # 426 | # user.last_activity_at_attribute_name = 427 | 428 | 429 | # How long since last activity is the user defined logged out? 430 | # Default: `10 * 60` 431 | # 432 | # user.activity_timeout = 433 | 434 | 435 | # -- external -- 436 | # Class which holds the various external provider data for this user. 437 | # Default: `nil` 438 | # 439 | # user.authentications_class = 440 | 441 | 442 | # User's identifier in authentications class. 443 | # Default: `:user_id` 444 | # 445 | # user.authentications_user_id_attribute_name = 446 | 447 | 448 | # Provider's identifier in authentications class. 449 | # Default: `:provider` 450 | # 451 | # user.provider_attribute_name = 452 | 453 | 454 | # User's external unique identifier in authentications class. 455 | # Default: `:uid` 456 | # 457 | # user.provider_uid_attribute_name = 458 | end 459 | 460 | # This line must come after the 'user config' block. 461 | # Define which model authenticates with sorcery. 462 | config.user_class = "User" 463 | end 464 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | # root :to => 'users#index' 3 | # resources :users 4 | 5 | namespace :api do 6 | namespace :v1 do 7 | resources :users 8 | resource :user_sessions, only: [:create, :destroy] 9 | resources :password_resets 10 | 11 | get 'sample/public' 12 | get 'sample/restrict' 13 | get 'sample/admin' 14 | end 15 | end 16 | 17 | # The priority is based upon order of creation: first created -> highest priority. 18 | # See how all your routes lay out with "rake routes". 19 | 20 | # You can have the root of your site routed with "root" 21 | # root 'welcome#index' 22 | 23 | # Example of regular route: 24 | # get 'products/:id' => 'catalog#view' 25 | 26 | # Example of named route that can be invoked with purchase_url(id: product.id) 27 | # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase 28 | 29 | # Example resource route (maps HTTP verbs to controller actions automatically): 30 | # resources :products 31 | 32 | # Example resource route with options: 33 | # resources :products do 34 | # member do 35 | # get 'short' 36 | # post 'toggle' 37 | # end 38 | # 39 | # collection do 40 | # get 'sold' 41 | # end 42 | # end 43 | 44 | # Example resource route with sub-resources: 45 | # resources :products do 46 | # resources :comments, :sales 47 | # resource :seller 48 | # end 49 | 50 | # Example resource route with more complex sub-resources: 51 | # resources :products do 52 | # resources :comments 53 | # resources :sales do 54 | # get 'recent', on: :collection 55 | # end 56 | # end 57 | 58 | # Example resource route with concerns: 59 | # concern :toggleable do 60 | # post 'toggle' 61 | # end 62 | # resources :posts, concerns: :toggleable 63 | # resources :photos, concerns: :toggleable 64 | 65 | # Example resource route within a namespace: 66 | # namespace :admin do 67 | # # Directs /admin/products/* to Admin::ProductsController 68 | # # (app/controllers/admin/products_controller.rb) 69 | # resources :products 70 | # end 71 | end 72 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 45c7c7c957ad481babcae0bdacbe4921e24afc541ee86874753f142a6e0cf3987fe0939803ba3287c576a8958e6ca3d4ce79fedee3766412a9c347754cd2cf66 15 | 16 | test: 17 | secret_key_base: 192fce17aaf2832f9b25d0961b099ad53dc6c72f9ae679bbb0c8ade35a6c0f596eac06aca9eb358dec17316d29d9ca47221dc98873f134d3dc7abc75a13be92b 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /db/migrate/20150520221548_sorcery_core.rb: -------------------------------------------------------------------------------- 1 | class SorceryCore < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :email, :null => false 5 | t.string :name, :null => false 6 | t.string :crypted_password 7 | t.string :salt 8 | 9 | t.timestamps 10 | end 11 | 12 | add_index :users, :email, unique: true 13 | end 14 | end -------------------------------------------------------------------------------- /db/migrate/20150526221756_create_api_keys.rb: -------------------------------------------------------------------------------- 1 | class CreateApiKeys < ActiveRecord::Migration 2 | def change 3 | create_table :api_keys do |t| 4 | t.string :access_token 5 | t.datetime :expires_at 6 | t.integer :user_id 7 | 8 | t.timestamps null: false 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20150529142034_add_active_api_key.rb: -------------------------------------------------------------------------------- 1 | class AddActiveApiKey < ActiveRecord::Migration 2 | def change 3 | add_column :api_keys, :active, :boolean 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20150607122809_create_user_departments.rb: -------------------------------------------------------------------------------- 1 | class CreateUserDepartments < ActiveRecord::Migration 2 | def change 3 | create_table :user_departments do |t| 4 | t.integer :user_id 5 | t.integer :department_id 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20150607134905_create_departments.rb: -------------------------------------------------------------------------------- 1 | class CreateDepartments < ActiveRecord::Migration 2 | def change 3 | create_table :departments do |t| 4 | t.string :name 5 | 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20150922120215_sorcery_reset_password.rb: -------------------------------------------------------------------------------- 1 | class SorceryResetPassword < ActiveRecord::Migration 2 | def change 3 | add_column :users, :reset_password_token, :string, :default => nil 4 | add_column :users, :reset_password_token_expires_at, :datetime, :default => nil 5 | add_column :users, :reset_password_email_sent_at, :datetime, :default => nil 6 | 7 | add_index :users, :reset_password_token 8 | end 9 | end -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20150922120215) do 15 | 16 | create_table "api_keys", force: :cascade do |t| 17 | t.string "access_token" 18 | t.datetime "expires_at" 19 | t.integer "user_id" 20 | t.datetime "created_at", null: false 21 | t.datetime "updated_at", null: false 22 | t.boolean "active" 23 | end 24 | 25 | create_table "departments", force: :cascade do |t| 26 | t.string "name" 27 | t.datetime "created_at", null: false 28 | t.datetime "updated_at", null: false 29 | end 30 | 31 | create_table "user_departments", force: :cascade do |t| 32 | t.integer "user_id" 33 | t.integer "department_id" 34 | t.datetime "created_at", null: false 35 | t.datetime "updated_at", null: false 36 | end 37 | 38 | create_table "users", force: :cascade do |t| 39 | t.string "email", null: false 40 | t.string "name", null: false 41 | t.string "crypted_password" 42 | t.string "salt" 43 | t.datetime "created_at" 44 | t.datetime "updated_at" 45 | t.string "reset_password_token" 46 | t.datetime "reset_password_token_expires_at" 47 | t.datetime "reset_password_email_sent_at" 48 | end 49 | 50 | add_index "users", ["email"], name: "index_users_on_email", unique: true 51 | add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token" 52 | 53 | end 54 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SrcHndWng/sorcery_api_sample/b2068216ff6c605d1dc24d5b748996d431732235/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /spec/controllers/password_resets_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::V1::PasswordResetsController, type: :controller do 4 | let(:valid_email) { ENV['MAIL_ADDRESS'] } 5 | let(:non_exist_email) { 'xxx@xxx.com' } 6 | 7 | let(:valid_password_reset_token) { '6pmRCqobTkgrgWAkcQJt' } 8 | let(:non_exist_password_reset_token) { 'xxxxxxxxxx' } 9 | 10 | let(:valid_input) { 11 | {password: 'password', password_confirmation: 'password'} 12 | } 13 | let(:invalid_input) { 14 | {password: 'password', password_confirmation: 'xxxxx'} 15 | } 16 | 17 | before(:all) do 18 | @valid_user = FactoryGirl.create(:user, {email: ENV['MAIL_ADDRESS'], name: 'user1', password: 'password', password_confirmation: 'password'}) 19 | @password_reset_user = FactoryGirl.create(:user, {email: 'password@reset.co.jp', name: 'user2', password: 'password', password_confirmation: 'password', reset_password_token: '6pmRCqobTkgrgWAkcQJt'}) 20 | end 21 | 22 | after(:all) do 23 | @valid_user.delete 24 | @password_reset_user.delete 25 | end 26 | 27 | describe "POST #create" do 28 | it "returns http success" do 29 | post :create, {format: :json, email: valid_email} 30 | 31 | expect(response.status).to eq(201) 32 | end 33 | 34 | it "non exist email" do 35 | post :create, {format: :json, email: non_exist_email} 36 | expect(response.status).to eq(404) 37 | end 38 | end 39 | 40 | describe "GET #edit" do 41 | it "returns http success" do 42 | get :edit, {format: :json, id: valid_password_reset_token} 43 | expect(response).to have_http_status(200) 44 | end 45 | 46 | it "non exist passoword_reset_token" do 47 | get :edit, {format: :json, id: non_exist_password_reset_token} 48 | expect(response.status).to eq(404) 49 | end 50 | end 51 | 52 | describe "PUT #update" do 53 | it "returns http success" do 54 | put :update, {format: :json, id: valid_password_reset_token, user: valid_input} 55 | expect(response.status).to eq(200) 56 | end 57 | 58 | it "unmatch password, password confirmation" do 59 | put :update, {format: :json, id: valid_password_reset_token, user: invalid_input} 60 | expect(response.status).to eq(406) 61 | end 62 | 63 | it "non exist passoword_reset_token" do 64 | put :update, {format: :json, id: non_exist_password_reset_token, user: valid_input} 65 | expect(response.status).to eq(404) 66 | end 67 | end 68 | 69 | end 70 | -------------------------------------------------------------------------------- /spec/controllers/sample_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::V1::SampleController, type: :controller do 4 | let(:public_message) { 'public' } 5 | let(:restrict_message) { 'authorized' } 6 | let(:admin_message) { 'admin' } 7 | 8 | before(:all) do 9 | @administration = FactoryGirl.create(:Administration) 10 | @purchase = FactoryGirl.create(:Purchase) 11 | 12 | @user_admin = FactoryGirl.create(:user, {email: 'admin@test.com', name: 'admin', password: 'password', password_confirmation: 'password'}) 13 | @user_purchase = FactoryGirl.create(:user, {email: 'purchase@test.com', name: 'purchase', password: 'password', password_confirmation: 'password'}) 14 | 15 | @user_admin_api_key = FactoryGirl.create(:api_key, { user_id: @user_admin.id }) 16 | @user_purchase_api_key = FactoryGirl.create(:api_key, { user_id: @user_purchase.id }) 17 | 18 | @user_admin_department = FactoryGirl.create( 19 | :user_department, 20 | { user_id: @user_admin.id, department_id: @administration.id }) 21 | @user_purchase_department = FactoryGirl.create( 22 | :user_department, 23 | { user_id: @user_purchase.id, department_id: @purchase.id }) 24 | end 25 | 26 | after(:all) do 27 | @administration.delete 28 | @purchase.delete 29 | 30 | @user_admin.delete 31 | @user_purchase.delete 32 | 33 | @user_admin_api_key.delete 34 | @user_purchase_api_key.delete 35 | 36 | @user_admin_department_department.delete 37 | end 38 | 39 | describe "GET #public" do 40 | context "all user should success" do 41 | it "not login user" do 42 | get :public, { format: :json } 43 | expect(response.status).to eq(200) 44 | 45 | json = JSON.parse(response.body) 46 | expect(json['message']).to eq(public_message) 47 | end 48 | 49 | it "admin user" do 50 | request.env['HTTP_ACCESS_TOKEN'] = @user_admin_api_key.access_token 51 | get :public, { format: :json } 52 | expect(response.status).to eq(200) 53 | 54 | json = JSON.parse(response.body) 55 | expect(json['message']).to eq(public_message) 56 | end 57 | 58 | it "purchase user" do 59 | request.env['HTTP_ACCESS_TOKEN'] = @user_purchase_api_key.access_token 60 | get :public, { format: :json } 61 | expect(response.status).to eq(200) 62 | 63 | json = JSON.parse(response.body) 64 | expect(json['message']).to eq(public_message) 65 | end 66 | end 67 | end 68 | 69 | describe "GET #restrict" do 70 | context "not login user cannot acccess" do 71 | it "not login user" do 72 | get :restrict, { format: :json } 73 | expect(response.status).to eq(401) 74 | expect(response.body.empty?).to eq(true) 75 | end 76 | end 77 | 78 | context "login user success" do 79 | it "admin user" do 80 | request.env['HTTP_ACCESS_TOKEN'] = @user_admin_api_key.access_token 81 | get :restrict, { format: :json } 82 | expect(response.status).to eq(200) 83 | 84 | json = JSON.parse(response.body) 85 | expect(json['message']).to eq(restrict_message) 86 | end 87 | 88 | it "purchase user" do 89 | request.env['HTTP_ACCESS_TOKEN'] = @user_purchase_api_key.access_token 90 | get :restrict, { format: :json } 91 | expect(response.status).to eq(200) 92 | 93 | json = JSON.parse(response.body) 94 | expect(json['message']).to eq(restrict_message) 95 | end 96 | end 97 | end 98 | 99 | describe "GET #admin" do 100 | context "not login user cannot acccess" do 101 | it "not login user" do 102 | get :admin, { format: :json } 103 | expect(response.status).to eq(401) 104 | expect(response.body.empty?).to eq(true) 105 | end 106 | end 107 | 108 | context "purchase user cannnot access" do 109 | it "not admin user" do 110 | request.env['HTTP_ACCESS_TOKEN'] = @user_purchase_api_key.access_token 111 | get :admin, { format: :json } 112 | expect(response.status).to eq(401) 113 | expect(response.body.empty?).to eq(true) 114 | end 115 | end 116 | 117 | context "admin user should success" do 118 | it "admin user" do 119 | request.env['HTTP_ACCESS_TOKEN'] = @user_admin_api_key.access_token 120 | get :admin, { format: :json } 121 | expect(response.status).to eq(200) 122 | 123 | json = JSON.parse(response.body) 124 | expect(json['message']).to eq(admin_message) 125 | end 126 | end 127 | end 128 | end -------------------------------------------------------------------------------- /spec/controllers/user_sessions_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::V1::UserSessionsController, type: :controller do 4 | let(:valid_login_user) { 5 | {email: 'user1@test.com', password: 'password' } 6 | } 7 | let(:not_exist_login_user) { 8 | {email: 'duke@ggg.com', name: 'duketogo', password: 'x', password_confirmation: 'x'} 9 | } 10 | let(:not_exist_access_token) { 'xxxxx' } 11 | 12 | before(:all) do 13 | @user_1 = FactoryGirl.create(:user, {email: 'user1@test.com', name: 'user1', password: 'password', password_confirmation: 'password'}) 14 | @user_2 = FactoryGirl.create(:user, {email: 'user2@test.com', name: 'user2', password: 'password', password_confirmation: 'password'}) 15 | @user_3 = FactoryGirl.create(:user, {email: 'user3@test.com', name: 'user3', password: 'password', password_confirmation: 'password'}) 16 | 17 | @min_id = User.all[0]['id'] 18 | end 19 | 20 | after(:all) do 21 | @user_1.delete 22 | @user_2.delete 23 | @user_3.delete 24 | end 25 | 26 | describe "POST #create" do 27 | it "login success" do 28 | post :create, {format: :json, user: valid_login_user} 29 | expect(response.status).to eq(200) 30 | json = JSON.parse(response.body) 31 | user = json['user'] 32 | access_token = json['access_token'] 33 | expect(user['id']).to eq(@min_id) 34 | expect(user['email']).to eq('user1@test.com') 35 | expect(user['name']).to eq('user1') 36 | expect(access_token).not_to eq(nil) 37 | end 38 | 39 | it "already login" do 40 | post :create, {format: :json, user: valid_login_user} 41 | expect(response.status).to eq(200) 42 | 43 | post :create, {format: :json, user: valid_login_user} 44 | expect(response.status).to eq(200) 45 | expect(ApiKey.count).to eq(1) 46 | end 47 | 48 | it "api key expired" do 49 | api_key = ApiKey.create(user_id: @min_id) 50 | api_key.expires_at = DateTime.now - 1 51 | api_key.save 52 | expect(api_key.before_expired?).to eq(false) 53 | 54 | post :create, {format: :json, user: valid_login_user} 55 | expect(response.status).to eq(200) 56 | expect(ApiKey.count).to eq(1) 57 | expect(ApiKey.find_by_user_id(@min_id).before_expired?).to eq(true) 58 | end 59 | 60 | it "not exist user" do 61 | post :create, {format: :json, user: not_exist_login_user} 62 | expect(response.status).to eq(404) 63 | end 64 | end 65 | 66 | describe "POST #destroy" do 67 | it "active user" do 68 | post :create, {format: :json, user: valid_login_user} 69 | expect(response.status).to eq(200) 70 | json = JSON.parse(response.body) 71 | user = json['user'] 72 | access_token = json['access_token'] 73 | expect(ApiKey.find_by_user_id(user['id']).active).to eq(true) 74 | 75 | request.env['HTTP_ACCESS_TOKEN'] = access_token 76 | post :destroy, {format: :json} 77 | expect(response).to have_http_status(200) 78 | expect(ApiKey.find_by_user_id(user['id']).active).to eq(false) 79 | end 80 | 81 | it "not exist access token" do 82 | request.env['HTTP_ACCESS_TOKEN'] = not_exist_access_token 83 | post :destroy, {format: :json} 84 | expect(response).to have_http_status(401) 85 | end 86 | end 87 | 88 | end 89 | -------------------------------------------------------------------------------- /spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Api::V1::UsersController, type: :controller do 4 | let(:valid_attributes) { 5 | {email: 'duke@ggg.com', name: 'duketogo', password: 'golgo13', password_confirmation: 'golgo13'} 6 | } 7 | let(:invalid_attributes) { 8 | {email: 'duke@ggg.com', name: 'duketogo', password: 'x', password_confirmation: 'x'} 9 | } 10 | let(:administration_department) { 11 | {department_id: 10} 12 | } 13 | let(:purchase_department) { 14 | {department_id: 20} 15 | } 16 | let(:invalid_department) { 17 | {department_id: 13} 18 | } 19 | 20 | before(:all) do 21 | @administration = FactoryGirl.create(:Administration) 22 | @purchase = FactoryGirl.create(:Purchase) 23 | 24 | @user_1 = FactoryGirl.create(:user, {email: 'user1@test.com', name: 'user1', password: 'password', password_confirmation: 'password'}) 25 | @user_2 = FactoryGirl.create(:user, {email: 'user2@test.com', name: 'user2', password: 'password', password_confirmation: 'password'}) 26 | @user_3 = FactoryGirl.create(:user, {email: 'user3@test.com', name: 'user3', password: 'password', password_confirmation: 'password'}) 27 | 28 | @user_1_api_key = FactoryGirl.create(:api_key, { user_id: @user_1.id }) 29 | @user_2_api_key = FactoryGirl.create(:api_key, { user_id: @user_2.id }) 30 | @user_3_api_key = FactoryGirl.create(:api_key, { user_id: @user_3.id }) 31 | 32 | @user_1_department = FactoryGirl.create( 33 | :user_department, 34 | { user_id: @user_1.id, department_id: @administration.id }) 35 | 36 | @user_2_department = FactoryGirl.create( 37 | :user_department, 38 | { user_id: @user_2.id, department_id: @administration.id }) 39 | 40 | @user_3_department = FactoryGirl.create( 41 | :user_department, 42 | { user_id: @user_3.id, department_id: @purchase.id }) 43 | 44 | @min_id = User.all[0]['id'] 45 | @user_count = User.count 46 | end 47 | 48 | after(:all) do 49 | @administration.delete 50 | @purchase.delete 51 | 52 | @user_1.delete 53 | @user_2.delete 54 | @user_3.delete 55 | 56 | @user_1_api_key.delete 57 | @user_2_api_key.delete 58 | @user_3_api_key.delete 59 | 60 | @user_1_department.delete 61 | @user_2_department.delete 62 | @user_3_department.delete 63 | end 64 | 65 | describe "GET #index" do 66 | context "get users" do 67 | it "returns users" do 68 | request.env['HTTP_ACCESS_TOKEN'] = @user_1_api_key.access_token 69 | get :index, { format: :json } 70 | expect(response.status).to eq(200) 71 | 72 | json = JSON.parse(response.body) 73 | expect(json.count).to eq(3) 74 | expect(json[0]['id']).to be > 0 75 | expect(json[0]['email']).to eq('user1@test.com') 76 | expect(json[0]['name']).to eq('user1') 77 | end 78 | end 79 | end 80 | 81 | describe "GET #show" do 82 | context "get user" do 83 | it "returns user" do 84 | request.env['HTTP_ACCESS_TOKEN'] = @user_1_api_key.access_token 85 | get :show, { id: @min_id, format: :json } 86 | expect(response.status).to eq(200) 87 | 88 | json = JSON.parse(response.body) 89 | expect(json['id']).to eq(@min_id) 90 | expect(json['email']).to eq('user1@test.com') 91 | expect(json['name']).to eq('user1') 92 | expect(json['department']['id']).to eq(@administration.id) 93 | expect(json['department']['name']).to eq(@administration.name) 94 | end 95 | end 96 | 97 | context "not exists user" do 98 | it "returns nothing" do 99 | request.env['HTTP_ACCESS_TOKEN'] = @user_1_api_key.access_token 100 | not_exist_id = 0 101 | get :show, { id: not_exist_id, format: :json } 102 | expect(response.status).to eq(404) 103 | expect(response.body).to eq("") 104 | end 105 | end 106 | end 107 | 108 | describe "POST #create" do 109 | context "with valid params" do 110 | it "creates a new User" do 111 | expect { 112 | post :create, {format: :json, user: valid_attributes, department: administration_department} 113 | }.to change(User, :count).by(1) 114 | 115 | expect(response.status).to eq(201) 116 | 117 | department = User.all[3].user_department 118 | expect(department[:department_id]).to eq(administration_department[:department_id]) 119 | end 120 | end 121 | 122 | context "with invalid params" do 123 | it "header should have bad request" do 124 | expect{ 125 | post :create, {format: :json, user: invalid_attributes, department: administration_department} 126 | }.to change(User, :count).by(0) 127 | 128 | expect(response.status).to eq(400) 129 | end 130 | end 131 | 132 | context "with invalid user department" do 133 | it "header should have bad request" do 134 | expect{ 135 | post :create, {format: :json, user: valid_attributes, department: invalid_department} 136 | }.to change(User, :count).by(0) 137 | 138 | expect(response.status).to eq(400) 139 | end 140 | end 141 | end 142 | 143 | describe "PUT #update" do 144 | context "with valid params" do 145 | it "update user" do 146 | request.env['HTTP_ACCESS_TOKEN'] = @user_1_api_key.access_token 147 | put :update, { id: @min_id, format: :json, user: valid_attributes, department: purchase_department } 148 | expect(response.status).to eq(200) 149 | 150 | json = JSON.parse(response.body) 151 | expect(json['id']).to eq(@min_id) 152 | expect(json['email']).to eq('duke@ggg.com') 153 | expect(json['name']).to eq('duketogo') 154 | expect(json['department']['id']).to eq(@purchase.id) 155 | expect(json['department']['name']).to eq(@purchase.name) 156 | end 157 | end 158 | 159 | context "with invalid params" do 160 | it "returns error" do 161 | request.env['HTTP_ACCESS_TOKEN'] = @user_1_api_key.access_token 162 | put :update, { id: @min_id, format: :json, user: invalid_attributes, department: purchase_department } 163 | expect(response.status).to eq(422) 164 | 165 | json = JSON.parse(response.body) 166 | expect(json).not_to eq(nil) 167 | end 168 | end 169 | 170 | context "with invalid user department" do 171 | it "header should have bad request" do 172 | request.env['HTTP_ACCESS_TOKEN'] = @user_1_api_key.access_token 173 | put :update, { id: @min_id, format: :json, user: valid_attributes, department: invalid_department } 174 | expect(response.status).to eq(422) 175 | 176 | json = JSON.parse(response.body) 177 | expect(json).not_to eq(nil) 178 | end 179 | end 180 | end 181 | 182 | describe "DELETE #delete" do 183 | context "delete" do 184 | it "delete user" do 185 | request.env['HTTP_ACCESS_TOKEN'] = @user_1_api_key.access_token 186 | delete :destroy, { id: @min_id, format: :json } 187 | expect(response.status).to eq(204) 188 | 189 | expect(User.count).to eq(@user_count - 1) 190 | end 191 | end 192 | end 193 | end 194 | -------------------------------------------------------------------------------- /spec/factories/api_keys.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :api_key do 3 | end 4 | end -------------------------------------------------------------------------------- /spec/factories/department.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :department do 3 | factory :Administration do 4 | id 10 5 | name "Administration" 6 | end 7 | factory :Purchase do 8 | id 20 9 | name "Purchase" 10 | end 11 | end 12 | end -------------------------------------------------------------------------------- /spec/factories/user_departments.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user_department do 3 | end 4 | end -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user do 3 | end 4 | end -------------------------------------------------------------------------------- /spec/helpers/api/v1/password_resets_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the Api::V1::PasswordResetsHelper. For example: 5 | # 6 | # describe Api::V1::PasswordResetsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # expect(helper.concat_strings("this","that")).to eq("this that") 10 | # end 11 | # end 12 | # end 13 | RSpec.describe Api::V1::PasswordResetsHelper, type: :helper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/sample_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the SampleHelper. For example: 5 | # 6 | # describe SampleHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # expect(helper.concat_strings("this","that")).to eq("this that") 10 | # end 11 | # end 12 | # end 13 | RSpec.describe SampleHelper, type: :helper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/user_sessions_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the UserSessionsHelper. For example: 5 | # 6 | # describe UserSessionsHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # expect(helper.concat_strings("this","that")).to eq("this that") 10 | # end 11 | # end 12 | # end 13 | RSpec.describe UserSessionsHelper, type: :helper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/helpers/users_helper_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | # Specs in this file have access to a helper object that includes 4 | # the UsersHelper. For example: 5 | # 6 | # describe UsersHelper do 7 | # describe "string concat" do 8 | # it "concats two strings with spaces" do 9 | # expect(helper.concat_strings("this","that")).to eq("this that") 10 | # end 11 | # end 12 | # end 13 | RSpec.describe UsersHelper, type: :helper do 14 | pending "add some examples to (or delete) #{__FILE__}" 15 | end 16 | -------------------------------------------------------------------------------- /spec/mailers/user_mailer_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe UserMailer, type: :mailer do 4 | describe "reset_password_email" do 5 | let(:mail) { UserMailer.reset_password_email } 6 | 7 | it "renders the headers" do 8 | expect(mail.subject).to eq("Reset password email") 9 | expect(mail.to).to eq(["to@example.org"]) 10 | expect(mail.from).to eq(["from@example.com"]) 11 | end 12 | 13 | it "renders the body" do 14 | expect(mail.body.encoded).to match("Hi") 15 | end 16 | end 17 | 18 | end 19 | -------------------------------------------------------------------------------- /spec/models/api_key_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe ApiKey, type: :model do 4 | 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/department_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Department, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/user_department_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe UserDepartment, type: :model do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe User, type: :model do 4 | 5 | before(:all) do 6 | @no_api_key_user = FactoryGirl.create(:user, {email: 'no_api_key@test.com', name: 'no_api_key', password: 'password', password_confirmation: 'password'}) 7 | 8 | @not_activate_user = FactoryGirl.create(:user, {email: 'no_activate_user@test.com', name: 'no_activate_user', password: 'password', password_confirmation: 'password'}) 9 | @not_active_api_key = FactoryGirl.create(:api_key, { user_id: @not_activate_user.id }) 10 | @not_active_api_key.active = false 11 | @not_active_api_key.save 12 | 13 | @expired_user = FactoryGirl.create(:user, {email: 'expired@test.com', name: 'expired', password: 'password', password_confirmation: 'password'}) 14 | @expired_api_key = FactoryGirl.create(:api_key, { user_id: @expired_user.id }) 15 | @expired_api_key.expires_at = DateTime.now - 1 16 | @expired_api_key.save 17 | 18 | @active_user = FactoryGirl.create(:user, {email: 'active_user@test.com', name: 'active_user', password: 'password', password_confirmation: 'password'}) 19 | @active_api_key = FactoryGirl.create(:api_key, { user_id: @active_user.id }) 20 | end 21 | 22 | after(:all) do 23 | @no_api_key_user.delete 24 | @not_activate_user.delete 25 | @not_active_api_key.delete 26 | @expired_user.delete 27 | @expired_api_key.delete 28 | @active_user.delete 29 | @active_api_key.delete 30 | end 31 | 32 | describe "activate" do 33 | it "ApiKey not exist." do 34 | api_key = @no_api_key_user.activate 35 | expect(api_key).not_to eq(nil) 36 | created = ApiKey.find_by_user_id(@no_api_key_user.id) 37 | expect(created.access_token).not_to eq(nil) 38 | expect(created.active).to eq(true) 39 | end 40 | 41 | it "ApiKey exist, not active." do 42 | api_key = @not_activate_user.activate 43 | expect(api_key).not_to eq(nil) 44 | expect(ApiKey.where(user_id: @not_activate_user.id).count).to eq(1) 45 | expect(ApiKey.find_by_user_id(@not_activate_user.id).active).to eq(true) 46 | expect(ApiKey.find_by_user_id(@not_activate_user.id).before_expired?).to eq(true) 47 | end 48 | 49 | it "ApiKey exist, expired." do 50 | api_key = @expired_user.activate 51 | expect(api_key).not_to eq(nil) 52 | expect(ApiKey.where(user_id: @expired_user.id).count).to eq(1) 53 | expect(ApiKey.find_by_user_id(@expired_user.id).active).to eq(true) 54 | expect(ApiKey.find_by_user_id(@expired_user.id).before_expired?).to eq(true) 55 | end 56 | end 57 | 58 | describe "inactivate" do 59 | it "active api key" do 60 | expect(ApiKey.find_by_user_id(@active_user.id).active).to eq(true) 61 | @active_user.inactivate 62 | expect(ApiKey.find_by_user_id(@active_user.id).active).to eq(false) 63 | end 64 | end 65 | 66 | describe "login?" do 67 | it "active user" do 68 | result = User.login?(@active_api_key.access_token) 69 | expect(result).to be(true) 70 | end 71 | 72 | it "not exist access token" do 73 | result = User.login?('9999999999') 74 | expect(result).to be(false) 75 | end 76 | 77 | it "expired user" do 78 | result = User.login?(@expired_api_key.access_token) 79 | expect(result).to be(false) 80 | end 81 | 82 | it "not active user" do 83 | result = User.login?(@not_active_api_key.access_token) 84 | expect(result).to be(false) 85 | end 86 | end 87 | end 88 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | ENV['RAILS_ENV'] ||= 'test' 3 | require 'spec_helper' 4 | require File.expand_path('../../config/environment', __FILE__) 5 | require 'rspec/rails' 6 | # Add additional requires below this line. Rails is not loaded until this point! 7 | 8 | # Requires supporting ruby files with custom matchers and macros, etc, in 9 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 10 | # run as spec files by default. This means that files in spec/support that end 11 | # in _spec.rb will both be required and run as specs, causing the specs to be 12 | # run twice. It is recommended that you do not name files matching this glob to 13 | # end with _spec.rb. You can configure this pattern with the --pattern 14 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 15 | # 16 | # The following line is provided for convenience purposes. It has the downside 17 | # of increasing the boot-up time by auto-requiring all files in the support 18 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 19 | # require only the support files necessary. 20 | # 21 | # Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f } 22 | 23 | # Checks for pending migrations before tests are run. 24 | # If you are not using ActiveRecord, you can remove this line. 25 | ActiveRecord::Migration.maintain_test_schema! 26 | 27 | RSpec.configure do |config| 28 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 29 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 30 | 31 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 32 | # examples within a transaction, remove the following line or assign false 33 | # instead of true. 34 | config.use_transactional_fixtures = true 35 | 36 | # RSpec Rails can automatically mix in different behaviours to your tests 37 | # based on their file location, for example enabling you to call `get` and 38 | # `post` in specs under `spec/controllers`. 39 | # 40 | # You can disable this behaviour by removing the line below, and instead 41 | # explicitly tag your specs with their type, e.g.: 42 | # 43 | # RSpec.describe UsersController, :type => :controller do 44 | # # ... 45 | # end 46 | # 47 | # The different available types are documented in the features, such as in 48 | # https://relishapp.com/rspec/rspec-rails/docs 49 | config.infer_spec_type_from_file_location! 50 | 51 | config.render_views = true 52 | end 53 | -------------------------------------------------------------------------------- /spec/requests/users_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "Users", type: :request do 4 | describe "GET /users" do 5 | it "works! (now write some real specs)" do 6 | get users_path 7 | expect(response).to have_http_status(200) 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/routing/users_routing_spec.rb: -------------------------------------------------------------------------------- 1 | require "rails_helper" 2 | 3 | RSpec.describe UsersController, type: :routing do 4 | describe "routing" do 5 | 6 | it "routes to #index" do 7 | expect(:get => "/users").to route_to("users#index") 8 | end 9 | 10 | it "routes to #new" do 11 | expect(:get => "/users/new").to route_to("users#new") 12 | end 13 | 14 | it "routes to #show" do 15 | expect(:get => "/users/1").to route_to("users#show", :id => "1") 16 | end 17 | 18 | it "routes to #edit" do 19 | expect(:get => "/users/1/edit").to route_to("users#edit", :id => "1") 20 | end 21 | 22 | it "routes to #create" do 23 | expect(:post => "/users").to route_to("users#create") 24 | end 25 | 26 | it "routes to #update" do 27 | expect(:put => "/users/1").to route_to("users#update", :id => "1") 28 | end 29 | 30 | it "routes to #destroy" do 31 | expect(:delete => "/users/1").to route_to("users#destroy", :id => "1") 32 | end 33 | 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # This file was generated by the `rails generate rspec:install` command. Conventionally, all 2 | # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. 3 | # The generated `.rspec` file contains `--require spec_helper` which will cause 4 | # this file to always be loaded, without a need to explicitly require it in any 5 | # files. 6 | # 7 | # Given that it is always loaded, you are encouraged to keep this file as 8 | # light-weight as possible. Requiring heavyweight dependencies from this file 9 | # will add to the boot time of your test suite on EVERY test run, even for an 10 | # individual file that may not need all of that loaded. Instead, consider making 11 | # a separate helper file that requires the additional dependencies and performs 12 | # the additional setup, and require it from the spec files that actually need 13 | # it. 14 | # 15 | # The `.rspec` file also contains a few flags that are not defaults but that 16 | # users commonly want. 17 | # 18 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 19 | RSpec.configure do |config| 20 | # rspec-expectations config goes here. You can use an alternate 21 | # assertion/expectation library such as wrong or the stdlib/minitest 22 | # assertions if you prefer. 23 | config.expect_with :rspec do |expectations| 24 | # This option will default to `true` in RSpec 4. It makes the `description` 25 | # and `failure_message` of custom matchers include text for helper methods 26 | # defined using `chain`, e.g.: 27 | # be_bigger_than(2).and_smaller_than(4).description 28 | # # => "be bigger than 2 and smaller than 4" 29 | # ...rather than: 30 | # # => "be bigger than 2" 31 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 32 | end 33 | 34 | # rspec-mocks config goes here. You can use an alternate test double 35 | # library (such as bogus or mocha) by changing the `mock_with` option here. 36 | config.mock_with :rspec do |mocks| 37 | # Prevents you from mocking or stubbing a method that does not exist on 38 | # a real object. This is generally recommended, and will default to 39 | # `true` in RSpec 4. 40 | mocks.verify_partial_doubles = true 41 | end 42 | 43 | # The settings below are suggested to provide a good initial experience 44 | # with RSpec, but feel free to customize to your heart's content. 45 | =begin 46 | # These two settings work together to allow you to limit a spec run 47 | # to individual examples or groups you care about by tagging them with 48 | # `:focus` metadata. When nothing is tagged with `:focus`, all examples 49 | # get run. 50 | config.filter_run :focus 51 | config.run_all_when_everything_filtered = true 52 | 53 | # Limits the available syntax to the non-monkey patched syntax that is 54 | # recommended. For more details, see: 55 | # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax 56 | # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 57 | # - http://myronmars.to/n/dev-blog/2014/05/notable-changes-in-rspec-3#new__config_option_to_disable_rspeccore_monkey_patching 58 | config.disable_monkey_patching! 59 | 60 | # Many RSpec users commonly either run the entire suite or an individual 61 | # file, and it's useful to allow more verbose output when running an 62 | # individual spec file. 63 | if config.files_to_run.one? 64 | # Use the documentation formatter for detailed output, 65 | # unless a formatter has already been configured 66 | # (e.g. via a command-line flag). 67 | config.default_formatter = 'doc' 68 | end 69 | 70 | # Print the 10 slowest examples and example groups at the 71 | # end of the spec run, to help surface which specs are running 72 | # particularly slow. 73 | config.profile_examples = 10 74 | 75 | # Run specs in random order to surface order dependencies. If you find an 76 | # order dependency and want to debug it, you can fix the order by providing 77 | # the seed, which is printed after each run. 78 | # --seed 1234 79 | config.order = :random 80 | 81 | # Seed global randomization in this process using the `--seed` CLI option. 82 | # Setting this allows you to use `--seed` to deterministically reproduce 83 | # test failures related to randomization by passing the same `--seed` value 84 | # as the one that triggered the failure. 85 | Kernel.srand config.seed 86 | =end 87 | config.before(:all) do 88 | FactoryGirl.reload 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /spec/views/sample/public.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "sample/public.html.erb", type: :view do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/views/sample/restrict.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "sample/restrict.html.erb", type: :view do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/views/user_sessions/create.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "user_sessions/create.html.erb", type: :view do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/views/user_sessions/destroy.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "user_sessions/destroy.html.erb", type: :view do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/views/user_sessions/new.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "user_sessions/new.html.erb", type: :view do 4 | pending "add some examples to (or delete) #{__FILE__}" 5 | end 6 | -------------------------------------------------------------------------------- /spec/views/users/edit.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "users/edit", type: :view do 4 | before(:each) do 5 | @user = assign(:user, User.create!( 6 | :email => "MyString", 7 | :name => "MyString", 8 | :crypted_password => "MyString", 9 | :salt => "MyString" 10 | )) 11 | end 12 | 13 | it "renders the edit user form" do 14 | render 15 | 16 | assert_select "form[action=?][method=?]", user_path(@user), "post" do 17 | 18 | assert_select "input#user_email[name=?]", "user[email]" 19 | 20 | assert_select "input#user_name[name=?]", "user[name]" 21 | 22 | assert_select "input#user_crypted_password[name=?]", "user[crypted_password]" 23 | 24 | assert_select "input#user_salt[name=?]", "user[salt]" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/views/users/index.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "users/index", type: :view do 4 | before(:each) do 5 | assign(:users, [ 6 | User.create!( 7 | :email => "Email", 8 | :name => "Name", 9 | :crypted_password => "Crypted Password", 10 | :salt => "Salt" 11 | ), 12 | User.create!( 13 | :email => "Email", 14 | :name => "Name", 15 | :crypted_password => "Crypted Password", 16 | :salt => "Salt" 17 | ) 18 | ]) 19 | end 20 | 21 | it "renders a list of users" do 22 | render 23 | assert_select "tr>td", :text => "Email".to_s, :count => 2 24 | assert_select "tr>td", :text => "Name".to_s, :count => 2 25 | assert_select "tr>td", :text => "Crypted Password".to_s, :count => 2 26 | assert_select "tr>td", :text => "Salt".to_s, :count => 2 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /spec/views/users/new.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "users/new", type: :view do 4 | before(:each) do 5 | assign(:user, User.new( 6 | :email => "MyString", 7 | :name => "MyString", 8 | :crypted_password => "MyString", 9 | :salt => "MyString" 10 | )) 11 | end 12 | 13 | it "renders new user form" do 14 | render 15 | 16 | assert_select "form[action=?][method=?]", users_path, "post" do 17 | 18 | assert_select "input#user_email[name=?]", "user[email]" 19 | 20 | assert_select "input#user_name[name=?]", "user[name]" 21 | 22 | assert_select "input#user_crypted_password[name=?]", "user[crypted_password]" 23 | 24 | assert_select "input#user_salt[name=?]", "user[salt]" 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /spec/views/users/show.html.erb_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe "users/show", type: :view do 4 | before(:each) do 5 | @user = assign(:user, User.create!( 6 | :email => "Email", 7 | :name => "Name", 8 | :crypted_password => "Crypted Password", 9 | :salt => "Salt" 10 | )) 11 | end 12 | 13 | it "renders attributes in

" do 14 | render 15 | expect(rendered).to match(/Email/) 16 | expect(rendered).to match(/Name/) 17 | expect(rendered).to match(/Crypted Password/) 18 | expect(rendered).to match(/Salt/) 19 | end 20 | end 21 | --------------------------------------------------------------------------------