├── .DS_Store ├── .gitignore ├── .rspec ├── .rubocop.yml ├── .ruby-version ├── .stickler.yml ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── app ├── .DS_Store ├── assets │ ├── .DS_Store │ ├── config │ │ └── manifest.js │ ├── fonts │ │ ├── .DS_Store │ │ └── proxima │ │ │ ├── Proxima Nova Alt Bold.otf │ │ │ ├── Proxima Nova Alt Light.otf │ │ │ ├── Proxima Nova Alt Thin.otf │ │ │ ├── Proxima Nova Black.otf │ │ │ ├── Proxima Nova Extrabold.otf │ │ │ ├── ProximaNovaBold.otf │ │ │ ├── ProximaNovaRegular.otf │ │ │ └── ProximaNovaThin.otf │ ├── images │ │ ├── .DS_Store │ │ ├── .keep │ │ ├── Icons │ │ │ ├── .DS_Store │ │ │ ├── Check_ok.svg │ │ │ ├── back.svg │ │ │ ├── bookings.svg │ │ │ ├── check.svg │ │ │ ├── courses.svg │ │ │ ├── edit.svg │ │ │ ├── enroll.svg │ │ │ ├── off.svg │ │ │ ├── on.svg │ │ │ ├── students.svg │ │ │ ├── switch.svg │ │ │ ├── toggle_off.png │ │ │ ├── toggle_off.svg │ │ │ ├── toggle_on.png │ │ │ ├── toggle_on.svg │ │ │ ├── trash_can.svg │ │ │ ├── user.svg │ │ │ ├── view-01.svg │ │ │ └── view.svg │ │ ├── favicon.ico │ │ └── ielts_cover_image.jpeg │ ├── img │ │ └── app_screenshot.png │ ├── javascripts │ │ ├── application.js │ │ ├── cable.js │ │ └── channels │ │ │ └── .keep │ └── stylesheets │ │ └── application.css.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ └── .keep │ ├── enrolled_controller.rb │ ├── enrolls_controller.rb │ ├── groups_controller.rb │ ├── transactions_controller.rb │ ├── users │ │ └── registrations_controller.rb │ └── welcome_controller.rb ├── helpers │ └── application_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── enroll.rb │ ├── group.rb │ ├── transaction.rb │ └── user.rb └── views │ ├── devise │ ├── confirmations │ │ └── new.html.erb │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── email_changed.html.erb │ │ ├── password_change.html.erb │ │ ├── reset_password_instructions.html.erb │ │ └── unlock_instructions.html.erb │ ├── passwords │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── registrations │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── sessions │ │ └── new.html.erb │ ├── shared │ │ ├── _error_messages.html.erb │ │ └── _links.html.erb │ └── unlocks │ │ └── new.html.erb │ ├── enrolls │ ├── new.html.erb │ └── show.html.erb │ ├── groups │ ├── confirm_destroy.js.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── new.html.erb │ └── show.html.erb │ ├── layouts │ ├── _billable_bookings.html.erb │ ├── _conf_del.html.erb │ ├── _conf_tran_del.html.erb │ ├── _disabled_courses.html.erb │ ├── _enabled_courses.html.erb │ ├── _flash.html.erb │ ├── _navbar.html.erb │ ├── _non_billable_bookings.html.erb │ ├── _pending_bookings.html.erb │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── transactions │ ├── confirm_destroy.js.erb │ ├── edit.html.erb │ ├── index.html.erb │ └── new.html.erb │ └── welcome │ └── index.html.erb ├── app_screenshot.png ├── bin ├── bundle ├── rails ├── rake ├── rspec ├── setup ├── spring ├── update └── yarn ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── content_security_policy.rb │ ├── cookies_serializer.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── simple_form.rb │ ├── simple_form_bootstrap.rb │ └── wrap_parameters.rb ├── locales │ ├── devise.en.yml │ ├── en.yml │ └── simple_form.en.yml ├── puma.rb ├── routes.rb ├── spring.rb └── storage.yml ├── db ├── migrate │ ├── 20200509222956_create_groups.rb │ ├── 20200512194136_devise_create_users.rb │ ├── 20200512214438_add_user_to_groups_course.rb │ ├── 20200513110914_create_enrolls.rb │ └── 20200513133240_create_transactions.rb ├── schema.rb └── seeds.rb ├── erd.png ├── lib ├── assets │ └── .keep ├── tasks │ └── .keep └── templates │ └── erb │ └── scaffold │ └── _form.html.erb ├── log └── .keep ├── mam.png ├── package.json ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico ├── mam.png └── robots.txt ├── spec ├── controllers │ ├── enrolls_controller_spec.rb │ └── groups_controller_spec.rb ├── factories │ ├── enrolls.rb │ ├── groups.rb │ ├── transactions.rb │ └── users.rb ├── features │ ├── create_course_spec.rb │ └── home_page_spec.rb ├── fixtures │ └── ielts_cover_image.jpeg ├── models │ ├── enroll_spec.rb │ ├── group_spec.rb │ ├── transaction_spec.rb │ └── user_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── support │ ├── course_page.rb │ ├── database_cleaner.rb │ ├── login_form.rb │ └── new_course_form.rb ├── storage └── .keep ├── tmp └── .keep └── vendor └── .keep /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/.DS_Store -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore uploaded files in development 21 | /storage/* 22 | !/storage/.keep 23 | 24 | /node_modules 25 | /yarn-error.log 26 | 27 | /public/assets 28 | .byebug_history 29 | 30 | # Ignore master key for decrypting credentials and more. 31 | /config/master.key 32 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | AllCops: 2 | Exclude: 3 | - "db/**/*" 4 | - "bin/*" 5 | - "config/**/*" 6 | - "Guardfile" 7 | - "Rakefile" 8 | - "README.md" 9 | - "node_modules/**/*" 10 | 11 | DisplayCopNames: true 12 | 13 | Layout/LineLength: 14 | Max: 120 15 | Metrics/MethodLength: 16 | Include: 17 | - "app/controllers/*" 18 | - "app/models/*" 19 | Max: 20 20 | Metrics/AbcSize: 21 | Include: 22 | - "app/controllers/*" 23 | - "app/models/*" 24 | Max: 50 25 | Metrics/ClassLength: 26 | Max: 150 27 | Metrics/BlockLength: 28 | ExcludedMethods: ['describe'] 29 | Max: 30 30 | 31 | Style/Documentation: 32 | Enabled: false 33 | Style/ClassAndModuleChildren: 34 | Enabled: false 35 | Style/EachForSimpleLoop: 36 | Enabled: false 37 | Style/AndOr: 38 | Enabled: false 39 | Style/DefWithParentheses: 40 | Enabled: false 41 | Style/FrozenStringLiteralComment: 42 | EnforcedStyle: never 43 | 44 | Layout/HashAlignment: 45 | EnforcedColonStyle: key 46 | Layout/ExtraSpacing: 47 | AllowForAlignment: false 48 | Layout/MultilineMethodCallIndentation: 49 | Enabled: true 50 | EnforcedStyle: indented 51 | Lint/RaiseException: 52 | Enabled: false 53 | Lint/StructNewOverride: 54 | Enabled: false 55 | Style/HashEachMethods: 56 | Enabled: false 57 | Style/HashTransformKeys: 58 | Enabled: false 59 | Style/HashTransformValues: 60 | Enabled: false -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.6.5 -------------------------------------------------------------------------------- /.stickler.yml: -------------------------------------------------------------------------------- 1 | # add the linters you want stickler to use for this project 2 | linters: 3 | rubocop: 4 | display_cop_names: true 5 | # indicate where is the config file for stylelint 6 | config: './rubocop.yml' 7 | 8 | # add the files here you want to be ignored by stylelint 9 | files: 10 | ignore: 11 | - "bin/*" 12 | - "db/*" 13 | - "config/*" 14 | - "Guardfile" 15 | - "Rakefile" 16 | - "README.md" 17 | - "node_modules/**/*" 18 | 19 | # PLEASE DO NOT enable auto fixing options 20 | # if you need extra support from you linter - do it in your local env as described in README for this config 21 | 22 | # find full documentation here: https://stickler-ci.com/docs -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # rubocop:disable Bundler/DuplicatedGem 2 | source 'https://rubygems.org' 3 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 4 | 5 | ruby '2.6.5' 6 | 7 | gem 'coffee-rails', '~> 4.2' 8 | gem 'jbuilder', '~> 2.5' 9 | gem 'puma', '~> 3.11' 10 | gem 'rails', '~> 5.2.4', '>= 5.2.4.2' 11 | gem 'sass-rails', '~> 5.0' 12 | gem 'turbolinks', '~> 5' 13 | gem 'uglifier', '>= 1.3.0' 14 | 15 | gem 'bootsnap', '>= 1.1.0', require: false 16 | 17 | # I will use bootstrap 18 | gem 'bootstrap', '~> 4.3.1' 19 | 20 | gem 'devise' 21 | gem 'gravatar_image_tag', '~> 1.2' 22 | gem 'jquery-rails', '~> 4.3', '>= 4.3.5' 23 | gem 'sassc-rails', '>= 2.1.0' 24 | gem 'simple_form', '~> 5.0', '>= 5.0.2' 25 | 26 | group :development, :test do 27 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 28 | gem 'factory_bot_rails', '~> 5.2' 29 | gem 'hirb' 30 | gem 'jsonapi-rb', '~> 0.5.0' 31 | gem 'rspec-rails', '~> 4.0' 32 | gem 'spring' 33 | gem 'spring-commands-rspec', '~> 1.0', '>= 1.0.4' 34 | gem 'sqlite3' 35 | end 36 | 37 | group :development do 38 | gem 'listen', '>= 3.0.5', '< 3.2' 39 | gem 'spring-watcher-listen', '~> 2.0.0' 40 | gem 'web-console', '>= 3.3.0' 41 | end 42 | 43 | group :test do 44 | gem 'capybara', '~> 3.32', '>= 3.32.1' 45 | gem 'database_cleaner' 46 | gem 'rails-controller-testing' 47 | gem 'shoulda-matchers', require: false 48 | end 49 | 50 | group :production do 51 | gem 'factory_bot_rails', '~> 5.2', require: false 52 | gem 'hirb' 53 | gem 'pg', '0.20.0' 54 | end 55 | 56 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 57 | 58 | # rubocop:enable Bundler/DuplicatedGem 59 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (5.2.4.3) 5 | actionpack (= 5.2.4.3) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailer (5.2.4.3) 9 | actionpack (= 5.2.4.3) 10 | actionview (= 5.2.4.3) 11 | activejob (= 5.2.4.3) 12 | mail (~> 2.5, >= 2.5.4) 13 | rails-dom-testing (~> 2.0) 14 | actionpack (5.2.4.3) 15 | actionview (= 5.2.4.3) 16 | activesupport (= 5.2.4.3) 17 | rack (~> 2.0, >= 2.0.8) 18 | rack-test (>= 0.6.3) 19 | rails-dom-testing (~> 2.0) 20 | rails-html-sanitizer (~> 1.0, >= 1.0.2) 21 | actionview (5.2.4.3) 22 | activesupport (= 5.2.4.3) 23 | builder (~> 3.1) 24 | erubi (~> 1.4) 25 | rails-dom-testing (~> 2.0) 26 | rails-html-sanitizer (~> 1.0, >= 1.0.3) 27 | activejob (5.2.4.3) 28 | activesupport (= 5.2.4.3) 29 | globalid (>= 0.3.6) 30 | activemodel (5.2.4.3) 31 | activesupport (= 5.2.4.3) 32 | activerecord (5.2.4.3) 33 | activemodel (= 5.2.4.3) 34 | activesupport (= 5.2.4.3) 35 | arel (>= 9.0) 36 | activestorage (5.2.4.3) 37 | actionpack (= 5.2.4.3) 38 | activerecord (= 5.2.4.3) 39 | marcel (~> 0.3.1) 40 | activesupport (5.2.4.3) 41 | concurrent-ruby (~> 1.0, >= 1.0.2) 42 | i18n (>= 0.7, < 2) 43 | minitest (~> 5.1) 44 | tzinfo (~> 1.1) 45 | addressable (2.7.0) 46 | public_suffix (>= 2.0.2, < 5.0) 47 | arel (9.0.0) 48 | autoprefixer-rails (9.7.6) 49 | execjs 50 | bcrypt (3.1.13) 51 | bindex (0.8.1) 52 | bootsnap (1.4.6) 53 | msgpack (~> 1.0) 54 | bootstrap (4.3.1) 55 | autoprefixer-rails (>= 9.1.0) 56 | popper_js (>= 1.14.3, < 2) 57 | sassc-rails (>= 2.0.0) 58 | builder (3.2.4) 59 | byebug (11.1.3) 60 | capybara (3.32.2) 61 | addressable 62 | mini_mime (>= 0.1.3) 63 | nokogiri (~> 1.8) 64 | rack (>= 1.6.0) 65 | rack-test (>= 0.6.3) 66 | regexp_parser (~> 1.5) 67 | xpath (~> 3.2) 68 | coffee-rails (4.2.2) 69 | coffee-script (>= 2.2.0) 70 | railties (>= 4.0.0) 71 | coffee-script (2.4.1) 72 | coffee-script-source 73 | execjs 74 | coffee-script-source (1.12.2) 75 | concurrent-ruby (1.1.6) 76 | crass (1.0.6) 77 | database_cleaner (1.8.5) 78 | devise (4.7.1) 79 | bcrypt (~> 3.0) 80 | orm_adapter (~> 0.1) 81 | railties (>= 4.1.0) 82 | responders 83 | warden (~> 1.2.3) 84 | diff-lcs (1.3) 85 | erubi (1.9.0) 86 | execjs (2.7.0) 87 | factory_bot (5.2.0) 88 | activesupport (>= 4.2.0) 89 | factory_bot_rails (5.2.0) 90 | factory_bot (~> 5.2.0) 91 | railties (>= 4.2.0) 92 | ffi (1.12.2) 93 | globalid (0.4.2) 94 | activesupport (>= 4.2.0) 95 | gravatar_image_tag (1.2.0) 96 | hirb (0.7.3) 97 | i18n (1.8.2) 98 | concurrent-ruby (~> 1.0) 99 | jbuilder (2.10.0) 100 | activesupport (>= 5.0.0) 101 | jquery-rails (4.4.0) 102 | rails-dom-testing (>= 1, < 3) 103 | railties (>= 4.2.0) 104 | thor (>= 0.14, < 2.0) 105 | jsonapi-deserializable (0.2.0) 106 | jsonapi-rb (0.5.0) 107 | jsonapi-deserializable (~> 0.2.0) 108 | jsonapi-serializable (~> 0.3.0) 109 | jsonapi-renderer (0.2.2) 110 | jsonapi-serializable (0.3.1) 111 | jsonapi-renderer (~> 0.2.0) 112 | listen (3.1.5) 113 | rb-fsevent (~> 0.9, >= 0.9.4) 114 | rb-inotify (~> 0.9, >= 0.9.7) 115 | ruby_dep (~> 1.2) 116 | loofah (2.5.0) 117 | crass (~> 1.0.2) 118 | nokogiri (>= 1.5.9) 119 | mail (2.7.1) 120 | mini_mime (>= 0.1.1) 121 | marcel (0.3.3) 122 | mimemagic (~> 0.3.2) 123 | method_source (1.0.0) 124 | mimemagic (0.3.5) 125 | mini_mime (1.0.2) 126 | mini_portile2 (2.4.0) 127 | minitest (5.14.1) 128 | msgpack (1.3.3) 129 | nio4r (2.5.2) 130 | nokogiri (1.10.9) 131 | mini_portile2 (~> 2.4.0) 132 | orm_adapter (0.5.0) 133 | pg (0.20.0) 134 | popper_js (1.16.0) 135 | public_suffix (4.0.5) 136 | puma (3.12.6) 137 | rack (2.2.3) 138 | rack-test (1.1.0) 139 | rack (>= 1.0, < 3) 140 | rails (5.2.4.3) 141 | actioncable (= 5.2.4.3) 142 | actionmailer (= 5.2.4.3) 143 | actionpack (= 5.2.4.3) 144 | actionview (= 5.2.4.3) 145 | activejob (= 5.2.4.3) 146 | activemodel (= 5.2.4.3) 147 | activerecord (= 5.2.4.3) 148 | activestorage (= 5.2.4.3) 149 | activesupport (= 5.2.4.3) 150 | bundler (>= 1.3.0) 151 | railties (= 5.2.4.3) 152 | sprockets-rails (>= 2.0.0) 153 | rails-controller-testing (1.0.4) 154 | actionpack (>= 5.0.1.x) 155 | actionview (>= 5.0.1.x) 156 | activesupport (>= 5.0.1.x) 157 | rails-dom-testing (2.0.3) 158 | activesupport (>= 4.2.0) 159 | nokogiri (>= 1.6) 160 | rails-html-sanitizer (1.3.0) 161 | loofah (~> 2.3) 162 | railties (5.2.4.3) 163 | actionpack (= 5.2.4.3) 164 | activesupport (= 5.2.4.3) 165 | method_source 166 | rake (>= 0.8.7) 167 | thor (>= 0.19.0, < 2.0) 168 | rake (13.0.1) 169 | rb-fsevent (0.10.4) 170 | rb-inotify (0.10.1) 171 | ffi (~> 1.0) 172 | regexp_parser (1.7.0) 173 | responders (3.0.0) 174 | actionpack (>= 5.0) 175 | railties (>= 5.0) 176 | rspec-core (3.9.2) 177 | rspec-support (~> 3.9.3) 178 | rspec-expectations (3.9.2) 179 | diff-lcs (>= 1.2.0, < 2.0) 180 | rspec-support (~> 3.9.0) 181 | rspec-mocks (3.9.1) 182 | diff-lcs (>= 1.2.0, < 2.0) 183 | rspec-support (~> 3.9.0) 184 | rspec-rails (4.0.1) 185 | actionpack (>= 4.2) 186 | activesupport (>= 4.2) 187 | railties (>= 4.2) 188 | rspec-core (~> 3.9) 189 | rspec-expectations (~> 3.9) 190 | rspec-mocks (~> 3.9) 191 | rspec-support (~> 3.9) 192 | rspec-support (3.9.3) 193 | ruby_dep (1.5.0) 194 | sass (3.7.4) 195 | sass-listen (~> 4.0.0) 196 | sass-listen (4.0.0) 197 | rb-fsevent (~> 0.9, >= 0.9.4) 198 | rb-inotify (~> 0.9, >= 0.9.7) 199 | sass-rails (5.1.0) 200 | railties (>= 5.2.0) 201 | sass (~> 3.1) 202 | sprockets (>= 2.8, < 4.0) 203 | sprockets-rails (>= 2.0, < 4.0) 204 | tilt (>= 1.1, < 3) 205 | sassc (2.3.0) 206 | ffi (~> 1.9) 207 | sassc-rails (2.1.2) 208 | railties (>= 4.0.0) 209 | sassc (>= 2.0) 210 | sprockets (> 3.0) 211 | sprockets-rails 212 | tilt 213 | shoulda-matchers (4.3.0) 214 | activesupport (>= 4.2.0) 215 | simple_form (5.0.2) 216 | actionpack (>= 5.0) 217 | activemodel (>= 5.0) 218 | spring (2.1.0) 219 | spring-commands-rspec (1.0.4) 220 | spring (>= 0.9.1) 221 | spring-watcher-listen (2.0.1) 222 | listen (>= 2.7, < 4.0) 223 | spring (>= 1.2, < 3.0) 224 | sprockets (3.7.2) 225 | concurrent-ruby (~> 1.0) 226 | rack (> 1, < 3) 227 | sprockets-rails (3.2.1) 228 | actionpack (>= 4.0) 229 | activesupport (>= 4.0) 230 | sprockets (>= 3.0.0) 231 | sqlite3 (1.4.2) 232 | thor (1.0.1) 233 | thread_safe (0.3.6) 234 | tilt (2.0.10) 235 | turbolinks (5.2.1) 236 | turbolinks-source (~> 5.2) 237 | turbolinks-source (5.2.0) 238 | tzinfo (1.2.7) 239 | thread_safe (~> 0.1) 240 | uglifier (4.2.0) 241 | execjs (>= 0.3.0, < 3) 242 | warden (1.2.8) 243 | rack (>= 2.0.6) 244 | web-console (3.7.0) 245 | actionview (>= 5.0) 246 | activemodel (>= 5.0) 247 | bindex (>= 0.4.0) 248 | railties (>= 5.0) 249 | websocket-driver (0.7.1) 250 | websocket-extensions (>= 0.1.0) 251 | websocket-extensions (0.1.5) 252 | xpath (3.2.0) 253 | nokogiri (~> 1.8) 254 | 255 | PLATFORMS 256 | ruby 257 | 258 | DEPENDENCIES 259 | bootsnap (>= 1.1.0) 260 | bootstrap (~> 4.3.1) 261 | byebug 262 | capybara (~> 3.32, >= 3.32.1) 263 | coffee-rails (~> 4.2) 264 | database_cleaner 265 | devise 266 | factory_bot_rails (~> 5.2) 267 | gravatar_image_tag (~> 1.2) 268 | hirb 269 | jbuilder (~> 2.5) 270 | jquery-rails (~> 4.3, >= 4.3.5) 271 | jsonapi-rb (~> 0.5.0) 272 | listen (>= 3.0.5, < 3.2) 273 | pg (= 0.20.0) 274 | puma (~> 3.11) 275 | rails (~> 5.2.4, >= 5.2.4.2) 276 | rails-controller-testing 277 | rspec-rails (~> 4.0) 278 | sass-rails (~> 5.0) 279 | sassc-rails (>= 2.1.0) 280 | shoulda-matchers 281 | simple_form (~> 5.0, >= 5.0.2) 282 | spring 283 | spring-commands-rspec (~> 1.0, >= 1.0.4) 284 | spring-watcher-listen (~> 2.0.0) 285 | sqlite3 286 | turbolinks (~> 5) 287 | tzinfo-data 288 | uglifier (>= 1.3.0) 289 | web-console (>= 3.3.0) 290 | 291 | RUBY VERSION 292 | ruby 2.6.5p114 293 | 294 | BUNDLED WITH 295 | 2.1.4 296 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Carlos Anriquez 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lessons Lab 2 | 3 | This web application is a mobile application targeting freelance teachers to admin, track, and manage lessons with their students. 4 | 5 | . The front end is based on the original design idea by [Gregoire Vella on Behance ](https://www.behance.net/gallery/19759151/Snapscan-iOs-design-and-branding?tracking_source=). 6 | 7 | 8 | ![screenshot](app_screenshot.png) 9 | 10 | ## Live App link 11 | 12 | [heroku live site](https://shrouded-plains-92148.herokuapp.com/) 13 | 14 | For the demo, use the following pre-loaded users. You could also create new users. For all users, the password is ```password``` 15 | 16 | | email | name | role | | | 17 | |------------------|---------|---------|---|---| 18 | | email1@email.com | Sussane | teacher | | | 19 | | email2@email.com | Carlos | teacher | | | 20 | | email3@email.com | Anna | student | | | 21 | | email4@email.com | Clara | student | | | 22 | | email5@email.com | Cecil | student | | | 23 | | email6@email.com | Caro | student | | | 24 | | email7@email.com | Matt | student | | | 25 | | email8@email.com | Laureano| student | | | 26 | | email9@email.com | Robert | student | | | 27 | 28 | ## Customer Story 29 | 30 | - The user logs in to the app, only by typing the email 31 | . Additional feature implemented: password is an additional feature (demo password: password) 32 | 33 | 3. The logged-in user is presented with a profile page that includes: 34 | a. Bookings ("all my transactions") 35 | - Inside bookings two taps hell to show 36 | b. All Courses 37 | d. Additional feature: All students '''not available in demo''' 38 | 39 | 4. When a logged-in user opens "My billable bookings" page: 40 | a. The total amount of booked minutes is displayed on top. 41 | i. Additional feature: All equivalent revenue also displayed. 42 | b. A list of all transactions created by a logged-in user is displayed (sorted by most recent) 43 | c. Each transaction displays: 44 | i. Student name (name), 45 | ii. minutes-amount and date, 46 | iii. equivalent price per session. 47 | iv. Course's grouping icon (group). 48 | d. An action button "Add new booking" is displayed 49 | 50 | 5. When the logged-in user opens "My non-billable booking" page: 51 | a. A list of all created transactions by an authenticated teacher but not assigned to any course (group), or student (user). 52 | b. The design of the page is similar to the "My billable bookings." 53 | 54 | 6. When a logged-in user opens the "All Courses " page: 55 | a. A list of all courses is displayed in alphabetical order. 56 | 57 | b. Each Course displays its icon, name, and record creation date. 58 | 59 | 60 | 7. When a logged-in user opens the "Course transactions" page: 61 | a. A list of all transactions that belong to that Course is displayed. 62 | b. The design of the page is similar to the "All my bookings." Each booking displays the name of the author of the transaction. 63 | 64 | 8. When a logged-in user opens, "create a new course" or "Add new transaction" page: 65 | a. A form with all necessary fields is displayed. 66 | 67 | 9. Additional feature: For create a new course, the following vital data should be provided (among others) 68 | i. Course name. 69 | ii. Price per session. 70 | iii. Session length. 71 | 72 | 10. Additional feature: Signing up students to courses: 73 | a. Only a professor and course author can enroll students in courses. 74 | b. Enroll student button is available only on the "course index page" 75 | c. "add a new transaction" shows on the "course transaction page" 76 | i. A transaction can be created only for students currently enrolled into the Course. 77 | 78 | 11. Next release features: Signup of students: 79 | a. Students will be created only by teachers. Fields will include. 80 | i. Name 81 | ii. Email 82 | iii. Role: student 83 | ```for simplification this feature is not implemented in the demo``` 84 | 85 | ## Technical design details 86 | 87 | - Below I include the fundamental ERD design and associations map to better understand the implemented solution. 88 | 89 | > ERD design 90 | 91 | ![screenshot](erd.png) 92 | 93 | 94 | > Models Associations Map 95 | 96 | 97 | ![screenshot](mam.png) 98 | 99 | 100 | 101 | ## Environment set up requirements 102 | 103 | To run this project, you need ruby installed in your environment 104 | Run the following command: 105 | 106 | ``` 107 | $ ruby -v 108 | ``` 109 | 110 | You should have a result similar to this: 111 | s 112 | ``` 113 | ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-linux] 114 | ``` 115 | 116 | If you don't get that result, follow this [link](https://www.ruby-lang.org/en/documentation/installation/) and install Ruby in your local environment. 117 | 118 | ### Setup 119 | - Clone this repository in your local environment 120 | - Located on the root of repository execute 121 | 122 | ```bundle install``` 123 | 124 | This action will install all the required dependencies. 125 | 126 | ## Built With 127 | 128 | - Ruby 129 | - Ruby on Rails 130 | - bootstrap 131 | - Devise 132 | - RSpec 133 | - Capybara 134 | - FactoryBot 135 | - Shoulda-Matchers 136 | 137 | 138 | ## Authors 139 | 140 | 👤 Carlos Anriquez 141 | 142 | - Github: [@canriquez](https://github.com/canriquez) 143 | - Twitter: [@cranriquez](https://twitter.com/cranriquez) 144 | - Linkedin: [linkedin](https://www.linkedin.com/in/carlosanriquez/) 145 | 146 | ## 🤝 Contributing 147 | 148 | Contributions, issues, and feature requests are welcome! 149 | 150 | Feel free to check the [issues page](issues/). 151 | 152 | ## Acknowledgments 153 | 154 | - My Family (Oh man, after 70 hs in 9 days, definitely has been fundamental support) 155 | - The great TSE team for a fast review, key challenges, and contributions: Addo, Simon, and Issac. Thanks, Guys! 156 | - @microverse / The Corgis 157 | 158 | 159 | ## Show your support 160 | 161 | Give a ⭐️ if you like this project! 162 | 163 | ## 📝 License 164 | 165 | This project is [MIT](LICENSE) licensed. 166 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative 'config/application' 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/.DS_Store -------------------------------------------------------------------------------- /app/assets/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/.DS_Store -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/fonts/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/.DS_Store -------------------------------------------------------------------------------- /app/assets/fonts/proxima/Proxima Nova Alt Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/proxima/Proxima Nova Alt Bold.otf -------------------------------------------------------------------------------- /app/assets/fonts/proxima/Proxima Nova Alt Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/proxima/Proxima Nova Alt Light.otf -------------------------------------------------------------------------------- /app/assets/fonts/proxima/Proxima Nova Alt Thin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/proxima/Proxima Nova Alt Thin.otf -------------------------------------------------------------------------------- /app/assets/fonts/proxima/Proxima Nova Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/proxima/Proxima Nova Black.otf -------------------------------------------------------------------------------- /app/assets/fonts/proxima/Proxima Nova Extrabold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/proxima/Proxima Nova Extrabold.otf -------------------------------------------------------------------------------- /app/assets/fonts/proxima/ProximaNovaBold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/proxima/ProximaNovaBold.otf -------------------------------------------------------------------------------- /app/assets/fonts/proxima/ProximaNovaRegular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/proxima/ProximaNovaRegular.otf -------------------------------------------------------------------------------- /app/assets/fonts/proxima/ProximaNovaThin.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/fonts/proxima/ProximaNovaThin.otf -------------------------------------------------------------------------------- /app/assets/images/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/images/.DS_Store -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/Icons/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/images/Icons/.DS_Store -------------------------------------------------------------------------------- /app/assets/images/Icons/Check_ok.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/back.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/bookings.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/check.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/courses.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/edit.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/enroll.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/off.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/on.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/students.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/switch.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/toggle_off.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/images/Icons/toggle_off.png -------------------------------------------------------------------------------- /app/assets/images/Icons/toggle_off.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 11 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/assets/images/Icons/toggle_on.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/images/Icons/toggle_on.png -------------------------------------------------------------------------------- /app/assets/images/Icons/toggle_on.svg: -------------------------------------------------------------------------------- 1 | 3 | 9 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/assets/images/Icons/trash_can.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/user.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/view-01.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/Icons/view.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/images/favicon.ico -------------------------------------------------------------------------------- /app/assets/images/ielts_cover_image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/images/ielts_cover_image.jpeg -------------------------------------------------------------------------------- /app/assets/img/app_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/img/app_screenshot.png -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css.scss: -------------------------------------------------------------------------------- 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, or any plugin's 6 | * 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 other CSS/SCSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | */ 14 | 15 | @import "bootstrap"; 16 | 17 | //*************************** 18 | //** FONTS *** 19 | //*************************** 20 | 21 | //$font-url: url('https://fonts.googleapis.com/css2?family=Montserrat:ital,wght@0,200;0,400;0,600;0,800;1,200;1,400;1,600&display=swap'); 22 | 23 | @font-face { 24 | font-family: 'ProximaThin'; 25 | src: url('proxima/ProximaNovaThin.otf') format('opentype'); 26 | } 27 | 28 | @font-face { 29 | font-family: 'ProximaReg'; 30 | src: url('proxima/ProximaNovaRegular.otf') format('opentype'); 31 | } 32 | 33 | @font-face { 34 | font-family: 'ProximaBold'; 35 | src: url('proxima/ProximaNovaBold.otf') format('opentype'); 36 | } 37 | 38 | 39 | 40 | // Color setup 41 | $main: #3778c2; 42 | $second: #5fb523; 43 | $text: #434b54; 44 | $icons_off: #d8d8d8; 45 | $icons_on: #5fb523; 46 | 47 | body { 48 | background: $main; 49 | font-family: ProximaReg; 50 | height: 80vh; 51 | 52 | } 53 | 54 | //*************************** 55 | //** GENERIC *** 56 | //*************************** 57 | 58 | //Class impacting welcome list menu 59 | .main-style { 60 | background: none; 61 | border: none; 62 | font-family: ProximaReg; 63 | color: #fff 64 | } 65 | 66 | .list-group-item { 67 | background: $main; 68 | font-size: 1.5rem; 69 | } 70 | 71 | .shadows { 72 | -webkit-box-shadow: -1px 0px 11px -1px rgba(0, 0, 0, 0.17); 73 | -moz-box-shadow: -1px 0px 11px -1px rgba(0, 0, 0, 0.17); 74 | box-shadow: -1px 0px 11px -1px rgba(0, 0, 0, 0.17); 75 | } 76 | 77 | .small-text { 78 | font-family: ProximaReg; 79 | font-size: 0.7rem; 80 | backgroud: $second; 81 | } 82 | 83 | 84 | .user-name { 85 | font-family: ProximaReg; 86 | font-size: 1.1rem; 87 | backgroud: $second; 88 | } 89 | 90 | 91 | //*************************************************** 92 | //** confirmation modal initial setup *** 93 | //*************************************************** 94 | 95 | #confirmation { 96 | display: none; 97 | } 98 | 99 | //***************************************************** 100 | //** index display courses initial setup *** 101 | //***************************************************** 102 | 103 | .disabled { 104 | display: none; 105 | } 106 | 107 | .enabled { 108 | display: block; 109 | } 110 | 111 | //*************************** 112 | //** NAVBAR *** 113 | //*************************** 114 | 115 | .nav-wrap { 116 | display: block; 117 | height: 5vh; 118 | margin-top: 5% 119 | } 120 | 121 | .app-navbar { 122 | display: flex; 123 | justify-content: space-between; 124 | align-items: center; 125 | position: relative; 126 | height: 20%; 127 | background: $main; 128 | color: #fff; 129 | position: relative; 130 | 131 | a { 132 | font-family: ProximaReg; 133 | text-decoration: none; 134 | color: white; 135 | } 136 | 137 | .nav-item { 138 | font-size: 3rem 139 | } 140 | 141 | 142 | .app-title { 143 | color: #fff !important; 144 | flex-basis: 80%; 145 | text-decoration: none; 146 | font-family: ProximaBold; 147 | font-weight: 600; 148 | font-size: 5.0rem; 149 | position: absolute; 150 | top: 50%; 151 | left: 50%; 152 | transform: translate(-50%, -50%); 153 | } 154 | 155 | #app-title:hover { 156 | color: white; 157 | text-decoration: none; 158 | } 159 | 160 | } 161 | 162 | // Burger icon 163 | 164 | .header-burger { 165 | width: 15%; 166 | cursor: pointer; 167 | } 168 | 169 | .header-burger>span { 170 | margin-top: 10%; 171 | margin-bottom: 10%; 172 | } 173 | 174 | .header-burger>span, 175 | .header-burger::before, 176 | .header-burger::after { 177 | content: ""; 178 | display: block; 179 | width: 50%; 180 | height: 10px; 181 | background-color: #FFF; 182 | margin-left: auto; 183 | margin-right: auto; 184 | } 185 | 186 | //*************************************** 187 | //** welcome page *** 188 | //*************************************** 189 | 190 | .user-avatar { 191 | padding: 1px 6px; 192 | text-aligment: center; 193 | 194 | svg { 195 | width: 60%; 196 | fill: #ffc107; 197 | margin-top: 6px; 198 | z-index: 99; 199 | } 200 | } 201 | 202 | 203 | 204 | //*************************************** 205 | //** COURSE CARDS INDEX *** 206 | //*************************************** 207 | 208 | 209 | // Personalised badges 210 | 211 | .badge-info { 212 | background-color: $second; 213 | padding: 8px; 214 | } 215 | 216 | // Pesonalised buttons 217 | 218 | #sign-out-button { 219 | background: $second !important; 220 | } 221 | 222 | .more-info-btn { 223 | color: #fff; 224 | background-color: $main; 225 | border-color: $main; 226 | padding: 1px 4px; 227 | } 228 | 229 | .icon-btn { 230 | padding: 1px 6px; 231 | 232 | svg { 233 | fill: $text; 234 | margin-top: 6px; 235 | } 236 | } 237 | 238 | .confirmation-svg { 239 | padding: 1px 6px; 240 | width: 25%; 241 | 242 | svg { 243 | fill: $second; 244 | margin-top: 6px; 245 | } 246 | } 247 | 248 | .main-link-icon { 249 | padding: 1px 6px; 250 | background-color: transparent; 251 | border: none; 252 | 253 | svg { 254 | fill: #fff; 255 | margin-top: 6px; 256 | } 257 | } 258 | 259 | .card-footer { 260 | display: flex; 261 | align-items: center; 262 | justify-content: space-around; 263 | } 264 | 265 | .course-on-btn { 266 | padding: 1px 6px; 267 | 268 | svg { 269 | fill: $icons_on; 270 | margin-top: 6px; 271 | } 272 | } 273 | 274 | .course-off-btn { 275 | padding: 1px 6px; 276 | 277 | svg { 278 | fill: $icons_off; 279 | margin-top: 6px; 280 | } 281 | } 282 | 283 | 284 | //*************************************** 285 | //** SINGLE COURSE VIEW *** 286 | //*************************************** 287 | 288 | //Toggle Icons 289 | 290 | .toggle { 291 | width: 40px 292 | } 293 | 294 | // 295 | 296 | .card-header { 297 | padding-top: 4px; 298 | padding-bottom: 4px; 299 | font-family: ProximaBold; 300 | font-size: 1rem; 301 | } 302 | 303 | .card-body { 304 | padding-top: 10px; 305 | padding-bottom: 10px; 306 | } 307 | 308 | .course-description { 309 | .card-text { 310 | padding-top: 2px; 311 | padding-bottom: 2px; 312 | font-weight: 200; 313 | font-size: 0.9rem; 314 | } 315 | } 316 | 317 | .course-session { 318 | div { 319 | font-family: ProximaBold; 320 | font-size: 1rem; 321 | } 322 | 323 | li { 324 | padding-top: 2px; 325 | padding-bottom: 2px; 326 | font-weight: 400; 327 | font-size: 0.9rem; 328 | align-items: center; 329 | } 330 | } 331 | 332 | .course-delivery { 333 | div { 334 | font-family: ProximaBold; 335 | font-size: 1rem; 336 | } 337 | 338 | li { 339 | padding-top: 2px; 340 | padding-bottom: 2px; 341 | font-weight: 400; 342 | font-size: 0.9rem; 343 | align-items: center; 344 | } 345 | } 346 | 347 | //*************************************** 348 | //** enrolls approval *** 349 | //*************************************** -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | before_action :authenticate_user! 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/enrolled_controller.rb: -------------------------------------------------------------------------------- 1 | class EnrolledController < ApplicationController 2 | # used to populate the 'select student' selector in transactions#new view 3 | def enrolled 4 | @enrolled = User.enrolled_list(enrolled_params[:id]) 5 | p @enrolled 6 | respond_to do |format| 7 | format.json { render json: @enrolled } 8 | end 9 | end 10 | 11 | # used to populate the 'student to enroll' selector in group index view. 12 | def enrolar 13 | @to_enroll = User.to_enroll(Group.find(enrolled_params[:id])) 14 | 15 | puts 'AVAILABLE STUDENTS TO ENROLL: ' 16 | p @to_enroll 17 | respond_to do |format| 18 | format.json { render json: @to_enroll } 19 | end 20 | end 21 | 22 | private 23 | 24 | def enrolled_params 25 | puts 'Enroll, this are the params :' 26 | p params 27 | params.permit(:id) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/enrolls_controller.rb: -------------------------------------------------------------------------------- 1 | class EnrollsController < ApplicationController 2 | before_action :authenticate_user! 3 | before_action :rejects_duplications, only: [:create] 4 | 5 | def create 6 | p enrolls_params 7 | @enroll = Enroll.new(enrolls_params) 8 | 9 | if @enroll.save # if we succeed to create 10 | puts 'Enroll params into create: ' 11 | puts "current_user id is : #{current_user.id}" 12 | puts "course_author is : #{Group.find(@enroll.course_id).author.id}" 13 | puts 'We created successfully ' 14 | p @enroll 15 | redirect_to enroll_path(@enroll) 16 | else 17 | puts 'failed to create' 18 | p @enrolls.to_a 19 | p @enroll.errors.full_messages 20 | redirect_to groups_path 21 | end 22 | end 23 | 24 | def show 25 | @enroll = Enroll.find(params[:id]) 26 | end 27 | 28 | private 29 | 30 | def enrolls_params 31 | puts 'Enrolled a new student, here are the params :' 32 | p params 33 | params.permit(:course_id, :student_id) 34 | end 35 | 36 | def rejects_duplications 37 | return if Enroll.already_enrolled(params[:student_id], params[:course_id]).zero? 38 | 39 | redirect_to groups_path, notice: 'there was an error with the enrollment, please try again' 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/controllers/groups_controller.rb: -------------------------------------------------------------------------------- 1 | class GroupsController < ApplicationController 2 | # before_action :authenticate_user!, only: %i[index show new create edit update destroy] 3 | before_action :authors_only, only: %i[edit update destroy confirm_destroy] 4 | before_action :authors_or_enrolled_only, only: [:show] 5 | before_action :teachers_only, only: %i[new create] 6 | 7 | def index 8 | @group = if current_user.teacher? 9 | current_user.authored_courses.distinct(:course_id) 10 | else 11 | current_user.my_courses.distinct(:course_id) 12 | end 13 | end 14 | 15 | def show 16 | @group = Group.find(params[:id]) 17 | end 18 | 19 | def new 20 | @group = Group.new 21 | end 22 | 23 | def create 24 | @group = Group.new(group_course_params) 25 | @group.author_id = current_user.id # It is required only in testing. 26 | if @group.save 27 | redirect_to groups_path, notice: "Course #{@group.name} has been created" 28 | # I redirect to the index of courses by design instead of tipically redirect to the new created course 29 | else 30 | render :new 31 | end 32 | end 33 | 34 | def edit; end 35 | 36 | def update 37 | if @group.update_attributes(group_course_params) # if we succeed to update 38 | redirect_to groups_path # then we show the update changes implemented 39 | else 40 | render :edit 41 | end 42 | end 43 | 44 | def destroy 45 | puts 'WE GET TO DESTROY' 46 | @course.destroy 47 | redirect_to groups_path, notice: "Course #{@course.name} was eliminated." 48 | end 49 | 50 | def confirm_destroy 51 | p @course 52 | respond_to do |format| 53 | format.js 54 | end 55 | end 56 | 57 | private 58 | 59 | def group_course_params 60 | params.require(:group).permit(:name, :description, :duration, 61 | :price, :online, :presencial, :classroom, 62 | :cover_image, :starting, :enabled, :author_id) 63 | end 64 | 65 | def authors_only 66 | @group = Group.find(params[:id]) 67 | @course = @group 68 | redirect_to groups_path if current_user != @group.author 69 | end 70 | 71 | def authors_or_enrolled_only 72 | @group = Group.find(params[:id]) 73 | return if current_user == @group.author || Enroll.already_enrolled(current_user, @group) 74 | 75 | redirect_to groups_path, notice: 'you are not authorised for this action' 76 | end 77 | 78 | def teachers_only 79 | return if current_user.teacher? 80 | 81 | puts 'WARNING TEACHERS ONLY FAILED' 82 | redirect_to groups_path, notice: 'you are not authorised for this action' 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /app/controllers/transactions_controller.rb: -------------------------------------------------------------------------------- 1 | class TransactionsController < ApplicationController 2 | before_action :authors_only, only: %i[destroy confirm_destroy] 3 | 4 | def index 5 | if current_user.role == 'teacher' 6 | @transactions = Transaction.billable(current_user) 7 | @tran_not_billable = Transaction.not_billable.order_by_most_recent 8 | p @tran_not_billable 9 | else 10 | @transactions = Transaction.student_billable(current_user) 11 | end 12 | end 13 | 14 | def show 15 | @transaction = Transaction.find(transaction_params[:id]) 16 | end 17 | 18 | def new 19 | @transaction = Transaction.new 20 | end 21 | 22 | def create 23 | @transaction = Transaction.new(transaction_params) 24 | @transaction.teacher_id = current_user.id 25 | @transaction.status = :generated 26 | 27 | puts 'LOOK HERE ON TRANSACTION CREATE' 28 | p @transaction.errors.full_messages 29 | p @transaction 30 | if @transaction.save 31 | redirect_to transactions_path, notice: 'Booking has been created' 32 | # I redirect to the index of bookings by design instead of tipically redirect to the new created course 33 | else 34 | render :new 35 | end 36 | end 37 | 38 | def destroy 39 | puts 'WE GET TO DESTROY' 40 | @transaction.destroy 41 | redirect_to transactions_path, notice: 'Course transaction was eliminated.' 42 | end 43 | 44 | def confirm_destroy 45 | p @transaction 46 | respond_to do |format| 47 | format.js 48 | end 49 | end 50 | 51 | private 52 | 53 | def transaction_params 54 | params.require(:transaction).permit(:course_taught_id, :sitting_student_id, :minutes, :booking_type) 55 | end 56 | 57 | def authors_only 58 | @transaction = Transaction.find(params[:id]) 59 | puts "Current user role: #{current_user.role}, 60 | is authorised to destroy? : #{current_user == @transaction.teacher_id}" 61 | puts "Current user id: #{@transaction.teacher_id}, 62 | current_user.id :#{current_user.id}" 63 | return if current_user.id == @transaction.teacher_id 64 | 65 | puts "Current user role: #{current_user.role}, 66 | is authorised to destroy? : #{current_user == @transaction.teacher_id}" 67 | puts "Current user id: #{@transaction.teacher_id}, 68 | current_user.id :#{current_user.id}" 69 | redirect_to transactions_path, notice: 'you are not authorised for this action' 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /app/controllers/users/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::RegistrationsController < Devise::RegistrationsController 2 | private 3 | 4 | def sign_up_params 5 | params.require(:user).permit(:name, :email, :password, :password_confirmation, :role) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/welcome_controller.rb: -------------------------------------------------------------------------------- 1 | class WelcomeController < ApplicationController 2 | before_action :authenticate_user!, only: %i[index show] 3 | end 4 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/enroll.rb: -------------------------------------------------------------------------------- 1 | class Enroll < ApplicationRecord 2 | belongs_to :student, class_name: 'User' 3 | belongs_to :course, class_name: 'Group' 4 | 5 | validates :student_id, presence: true 6 | validates :course_id, presence: true 7 | 8 | def self.already_enrolled(student, course) 9 | Enroll.where(student_id: student).where(course_id: course).count 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/group.rb: -------------------------------------------------------------------------------- 1 | class Group < ApplicationRecord 2 | belongs_to :author, class_name: 'User' 3 | has_many :enrollments, foreign_key: 'course_id', class_name: 'Enroll' 4 | has_many :booked_sessions, foreign_key: 'course_taught_id', class_name: 'Transaction' 5 | has_many :enrolled, through: :enrollments, source: :student 6 | 7 | validates :name, presence: true, 8 | length: { maximum: 50 } 9 | 10 | validates :description, presence: true, length: { maximum: 200 } 11 | validates :duration, presence: true 12 | validates :price, presence: true, 13 | length: { maximum: 50, 14 | numericality: true } 15 | validates :starting, presence: true 16 | validates :cover_image, presence: true 17 | 18 | scope :enabled, -> { where(enabled: true) } 19 | scope :disabled, -> { where(enabled: false) } 20 | 21 | scope :enrolled_courses, ->(current_user) { where(id: current_user.enrolled_courses) } 22 | scope :authored_courses, ->(current_user) { where(id: current_user.authored_courses) } 23 | end 24 | -------------------------------------------------------------------------------- /app/models/transaction.rb: -------------------------------------------------------------------------------- 1 | class Transaction < ApplicationRecord 2 | belongs_to :teacher, class_name: 'User' 3 | belongs_to :course_taught, class_name: 'Group', optional: true 4 | belongs_to :sitting_student, class_name: 'User', optional: true 5 | 6 | enum status: %i[generated accepted] 7 | 8 | enum booking_type: %i[billable non-billable] 9 | 10 | scope :order_by_most_recent, -> { order(created_at: :desc) } 11 | scope :not_billable, -> { where(booking_type: 1) } 12 | 13 | validates :booking_type, presence: true 14 | validate :billable_course 15 | validate :billable_student 16 | validates :minutes, presence: true, numericality: true 17 | 18 | def self.billable(current_user) 19 | Transaction.select("transactions.id, transactions.created_at as date, 20 | users.name as student_name, groups.name as course_name, 21 | transactions.minutes as minutes, groups.price as price, 22 | transactions.status as status, transactions.accdate as accepted_date") 23 | .joins(:sitting_student, :course_taught) 24 | .where(teacher_id: current_user).order_by_most_recent 25 | end 26 | 27 | def self.student_billable(user) 28 | Transaction.select("transactions.id, transactions.created_at as date, 29 | users.name as student_name, groups.name as course_name, transactions.status as status, 30 | transactions.minutes as minutes, groups.price as price, transactions.accdate as accepted_date") 31 | .joins(:sitting_student, :course_taught) 32 | .where(sitting_student: user) 33 | end 34 | 35 | private 36 | 37 | def billable_course 38 | puts ' BOOKING TYPE: ' 39 | p booking_type 40 | puts ' COURSE TAUGHT ID: ' 41 | p course_taught_id 42 | 43 | return unless booking_type == 'billable' 44 | return unless course_taught_id.nil? 45 | 46 | errors.add(:course_taught_id, ': Please slect a course and student for billable bookings!') 47 | end 48 | 49 | def billable_student 50 | return unless booking_type == 'billable' 51 | return unless sitting_student_id.nil? 52 | 53 | errors.add(:sitting_student_id, ': Please slect a course and student for billable bookings!') 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | before_save :capitalize_name 3 | has_many :authored_courses, foreign_key: 'author_id', class_name: 'Group' 4 | has_many :enrolled_courses, foreign_key: 'student_id', class_name: 'Enroll' 5 | has_many :taught_sessions, foreign_key: 'teacher_id', class_name: 'Transaction' 6 | has_many :sitting_sessions, foreign_key: 'sitting_student_id', class_name: 'Transaction' 7 | has_many :my_courses, through: :enrolled_courses, source: :course 8 | 9 | # Include default devise modules. Others available are: 10 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable 11 | devise :database_authenticatable, :registerable, 12 | :recoverable, :rememberable, :validatable 13 | 14 | enum role: %i[student teacher] 15 | 16 | validates :name, presence: true, 17 | length: { maximum: 50 } 18 | 19 | validates :role, presence: true, 20 | length: { maximum: 50 } 21 | 22 | def enrolled(course) 23 | enrolled_courses.where(course_id: course).count.positive? 24 | end 25 | 26 | def self.to_enroll(course) 27 | User.student.select('users.id as id, users.name as name').where.not(id: course.enrolled) 28 | end 29 | 30 | def self.enrolled_list(course) 31 | User.select('users.name as name, users.id as id') 32 | .where(id: Group.select('enrolls.student_id as id') 33 | .joins(:enrolled).where(id: course)) 34 | end 35 | 36 | private 37 | 38 | def capitalize_name 39 | self.name = name.capitalize 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= f.error_notification %> 5 | <%= f.full_error :confirmation_token %> 6 | 7 |
8 | <%= f.input :email, 9 | required: true, 10 | autofocus: true, 11 | value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email), 12 | input_html: { autocomplete: "email" } %> 13 |
14 | 15 |
16 | <%= f.button :submit, "Resend confirmation instructions" %> 17 |
18 | <% end %> 19 | 20 | <%= render "devise/shared/links" %> 21 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

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

6 | -------------------------------------------------------------------------------- /app/views/devise/mailer/email_changed.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @email %>!

2 | 3 | <% if @resource.try(:unconfirmed_email?) %> 4 |

We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

5 | <% else %> 6 |

We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/devise/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |

Hello <%= @resource.email %>!

2 | 3 |

We're contacting you to notify you that your password has been changed.

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

Hello <%= @resource.email %>!

2 | 3 |

Someone has requested a link to change your password. You can do this through the link below.

4 | 5 |

<%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

6 | 7 |

If you didn't request this, please ignore this email.

8 |

Your password won't change until you access the link above and create a new one.

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

Hello <%= @resource.email %>!

2 | 3 |

Your account has been locked due to an excessive number of unsuccessful sign in attempts.

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

8 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Change your password

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 | <%= f.input :reset_password_token, as: :hidden %> 7 | <%= f.full_error :reset_password_token %> 8 | 9 |
10 | <%= f.input :password, 11 | label: "New password", 12 | required: true, 13 | autofocus: true, 14 | hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length), 15 | input_html: { autocomplete: "new-password" } %> 16 | <%= f.input :password_confirmation, 17 | label: "Confirm your new password", 18 | required: true, 19 | input_html: { autocomplete: "new-password" } %> 20 |
21 | 22 |
23 | <%= f.button :submit, "Change my password" %> 24 |
25 | <% end %> 26 | 27 | <%= render "devise/shared/links" %> 28 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 |

Forgot your password?

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 |
7 | <%= f.input :email, 8 | required: true, 9 | autofocus: true, 10 | input_html: { autocomplete: "email" } %> 11 |
12 | 13 |
14 | <%= f.button :submit, "Send me reset password instructions" %> 15 |
16 | <% end %> 17 | 18 | <%= render "devise/shared/links" %> 19 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Edit <%= resource_name.to_s.humanize %>

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 4 | <%= f.error_notification %> 5 | 6 |
7 | <%= f.input :email, required: true, autofocus: true %> 8 | 9 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 10 |

Currently waiting confirmation for: <%= resource.unconfirmed_email %>

11 | <% end %> 12 | 13 | <%= f.input :password, 14 | hint: "leave it blank if you don't want to change it", 15 | required: false, 16 | input_html: { autocomplete: "new-password" } %> 17 | <%= f.input :password_confirmation, 18 | required: false, 19 | input_html: { autocomplete: "new-password" } %> 20 | <%= f.input :current_password, 21 | hint: "we need your current password to confirm your changes", 22 | required: true, 23 | input_html: { autocomplete: "current-password" } %> 24 |
25 | 26 |
27 | <%= f.button :submit, "Update" %> 28 |
29 | <% end %> 30 | 31 |

Cancel my account

32 | 33 |

Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

34 | 35 | <%= link_to "Back", :back %> 36 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
Register
5 | 6 | <%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 7 | <%= f.error_notification %> 8 | 9 |
10 | 11 | <%= f.input :name, 12 | required: true, 13 | autofocus: true, 14 | input_html: { autocomplete: "name" }%> 15 | <%= f.input :email, 16 | required: true, 17 | autofocus: true, 18 | input_html: { autocomplete: "email" }%> 19 | <%= f.input :role, 20 | collection: Hash[roles_options], 21 | required: true %> 22 | 23 | <%= f.input :password, 24 | required: true, 25 | hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length), 26 | input_html: { autocomplete: "new-password" } %> 27 | <%= f.input :password_confirmation, 28 | required: true, 29 | input_html: { autocomplete: "new-password" } %> 30 |
31 | 32 |
33 | <%= f.button :submit, "Sign up" %> 34 |
35 | <% end %> 36 | 37 | <%= render "devise/shared/links" %> 38 |
39 |
40 |
-------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
Log In
5 | 6 | <%= simple_form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 7 |
8 | <%= f.input :email, 9 | required: false, 10 | autofocus: true, 11 | input_html: { autocomplete: "email" } %> 12 | <%= f.input :password, 13 | required: false, 14 | input_html: { autocomplete: "current-password" } %> 15 | <%= f.input :remember_me, as: :boolean if devise_mapping.rememberable? %> 16 |
17 | 18 |
19 | <%= f.button :submit, "Log in" %> 20 |
21 | <% end %> 22 | 23 | <%= render "devise/shared/links" %> 24 | 25 |
26 |
27 |
28 | -------------------------------------------------------------------------------- /app/views/devise/shared/_error_messages.html.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.any? %> 2 |
3 |

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

9 | 14 |
15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 | <%= link_to "Log in", new_session_path(resource_name) %>
3 | <% end %> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name) %>
7 | <% end %> 8 | 9 | <%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> 10 | <%= link_to "Forgot your password?", new_password_path(resource_name) %>
11 | <% end %> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
15 | <% end %> 16 | 17 | <%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> 18 | <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
19 | <% end %> 20 | 21 | <%- if devise_mapping.omniauthable? %> 22 | <%- resource_class.omniauth_providers.each do |provider| %> 23 | <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
24 | <% end %> 25 | <% end %> 26 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend unlock instructions

2 | 3 | <%= simple_form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= f.error_notification %> 5 | <%= f.full_error :unlock_token %> 6 | 7 |
8 | <%= f.input :email, 9 | required: true, 10 | autofocus: true, 11 | input_html: { autocomplete: "email" } %> 12 |
13 | 14 |
15 | <%= f.button :submit, "Resend unlock instructions" %> 16 |
17 | <% end %> 18 | 19 | <%= render "devise/shared/links" %> 20 | -------------------------------------------------------------------------------- /app/views/enrolls/new.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/views/enrolls/new.html.erb -------------------------------------------------------------------------------- /app/views/enrolls/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 |
<%= svg('check_ok') %>
5 |
Enrollment Success for <%= @enroll.student.name %>.
6 |

Enrollment is confirmed for the course : <%= @enroll.course.name %>.

7 | <%= link_to groups_path do %> 8 | Go back to courses page 9 | <% end %> 10 | 11 |
12 | 13 |
-------------------------------------------------------------------------------- /app/views/groups/confirm_destroy.js.erb: -------------------------------------------------------------------------------- 1 | $('#confirmation').html("<%= j (render 'layouts/conf_del') %>"); 2 | $('#confirmation').css("display", "block"); 3 | $('#confirm_deletion').modal('show'); 4 | $('#confirm_deletion').slideDown(350); 5 | 6 | $("#delete-action").click(function() 7 | { 8 | 9 | $('#course-<%=@course.id%>').hide(500); 10 | $('#confirm_deletion').modal('hide'); 11 | 12 | $.ajax('/groups/<%=@course.id%>', 13 | { type: 'DELETE', 14 | data: { _method: 'DELETE' }, 15 | data: { authenticity_token: $('[name="csrf-token"]')[0].content} 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /app/views/groups/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for(@group) do |f| %> 2 | <%= f.error_notification %> 3 | <%= puts "YES IT GOT TO THIS PAGEß EDIT!!!" %> 4 | <%= f.input :name, label: "Course Name", class: 'checking_edit_page_to_load' %> 5 | <%= f.input :description, label: "Description" %> 6 | <%= f.input :duration, collection: Hash[session_duration_options] %> 7 | <%= f.input :price, label: "Price" %> 8 | <%= f.input :online, label: "Online Session" %> 9 | <%= f.input :presencial, label: "Presencial Session" %> 10 | <%= f.input :enabled, label: "Enable Course" %> 11 | <%= f.input :starting, label: "Starting from" %> 12 | <%= f.input :cover_image, label: "Course Image", as: :file %> 13 | <%= f.submit 'Update Course', class: 'btn btn-primary update-test'%> 14 | <% end %> 15 | <%= content_tag :div, "edit-test", class: 'rspec_eyes_only' if Rails.env.test? %> 16 | -------------------------------------------------------------------------------- /app/views/groups/index.html.erb: -------------------------------------------------------------------------------- 1 | 5 | 6 | <%= puts "I AM HERE IN GROUPS#INDEX" %> 7 |
8 |
9 | 19 |
20 |
21 | <%= render 'layouts/enabled_courses' %> 22 |
23 |
24 | <%= render 'layouts/disabled_courses' %> 25 |
26 |
-------------------------------------------------------------------------------- /app/views/groups/new.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |
Create New Course
6 | 7 | <%= simple_form_for @group do |f| %> 8 | <%= f.input :name, label: "Course Name"%> 9 | <%= f.input :description, label: "Description"%> 10 | <%= f.input :duration, collection: Hash[session_duration_options] %> 11 | <%= f.input :price, label: "Price" %> 12 | <%= f.input :online, label: "Online Session" %> 13 | <%= f.input :presencial, label: "Presencial Session" %> 14 | <%= f.input :enabled, label: "Enable Course" %> 15 | <%= f.input :starting, label: "Starting from" %> 16 | <%= f.input :cover_image, label: "Course Image", as: :file %> 17 | 18 | 22 | <% end %> 23 | 24 |
25 |
26 |
-------------------------------------------------------------------------------- /app/views/groups/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | Course Name 4 | 9 |
10 |
11 |
<%= @group.name %>
12 |
13 |
14 |
15 |
Description
16 |
17 |

<%= @group.description %>

18 |
19 |
20 |
21 |
Session Information
22 | 32 |
33 |
34 |
Delivery Information
35 | 49 |
50 | -------------------------------------------------------------------------------- /app/views/layouts/_billable_bookings.html.erb: -------------------------------------------------------------------------------- 1 | <% if current_user.teacher? %> 2 |
3 |
4 | Total Booked hours: <%= @transactions.sum(:minutes)/60 %> | 5 | Current Revenue: <%= @transactions.sum(:price) %> usd 6 |
7 |
8 | <% end %> 9 | <% @transactions.each do |booking| %> 10 |
11 |
12 |
13 |
14 | <%= image_tag("ielts_cover_image.jpeg", size: "56x30", alt: "Edit Entry") %> 15 |
16 |
<%= booking.student_name %>
17 |
18 |
19 |
20 | Booking date: <%= to_date(booking.date) %> 21 |
22 |
23 |
24 |
25 |
<%= booking.course_name%>
26 |
27 |

<%= booking.minutes %> min

28 |

<%= booking.price%> usd

29 |

<%= status_tag(booking.status)%>

30 | <%= booking.accepted_date%> 31 |
32 |
33 | 45 |
46 | <% end %> -------------------------------------------------------------------------------- /app/views/layouts/_conf_del.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/_conf_tran_del.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/_disabled_courses.html.erb: -------------------------------------------------------------------------------- 1 | <% @group.disabled.each do |course| %> 2 |
3 |
4 |
5 |
6 | <%= image_tag("ielts_cover_image.jpeg", size: "56x30", alt: "Edit Entry") %> 7 |
8 |
<%= course.name %>
9 |
10 |
11 |
12 | Course started: <%= course.starting %> 13 | 16 |
17 |
18 |
19 |
20 |
Course description
21 |

<%= course.description %>

22 |
23 | 49 |
50 | <% end %> -------------------------------------------------------------------------------- /app/views/layouts/_enabled_courses.html.erb: -------------------------------------------------------------------------------- 1 | <% @group.enabled.each do |course| %> 2 |
3 |
4 |
5 |
6 | <%= image_tag("ielts_cover_image.jpeg", size: "56x30", alt: "Edit Entry") %> 7 |
8 |
<%= course.name %>
9 | 10 |
11 |
12 | <% if current_user.teacher? %> 13 |
14 | Course started: <%= course.starting %> 15 | 18 |
19 | <% end %> 20 |
21 |
22 |
23 |
Course description
24 |

<%= course.description %>

25 |
26 | 71 |
72 | <% end %> -------------------------------------------------------------------------------- /app/views/layouts/_flash.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <% if flash[:notice] %> 3 | 9 | <% end %> 10 | <% if flash[:alert] %> 11 | 17 | <% end %> 18 |
-------------------------------------------------------------------------------- /app/views/layouts/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 20 | -------------------------------------------------------------------------------- /app/views/layouts/_non_billable_bookings.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | Total non-Billable Booked hours: <%= @tran_not_billable.sum(:minutes)/60 %> | 4 |
5 |
6 | <% @tran_not_billable.each do |booking| %> 7 |
8 |
9 |
10 |
11 |

Non-Billable Booking

12 |
13 |
14 |
15 | Booking date: <%= booking.created_at %> 16 |
17 |
18 |
19 |
20 |
21 |

<%= booking.minutes %> min

22 |

0.00 usd

23 |

<%= booking.status%>

24 |
25 |
26 | 35 |
36 | <% end %> -------------------------------------------------------------------------------- /app/views/layouts/_pending_bookings.html.erb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app/views/layouts/_pending_bookings.html.erb -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Lessons Lab 5 | <%= favicon_link_tag %> 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> 9 | <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> 10 | 11 | 12 | 13 | <%= render 'layouts/navbar' %> 14 | <%= render 'layouts/flash' %> 15 |
16 |
17 |
18 | <%= yield %> 19 |
20 | 21 | 22 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/transactions/confirm_destroy.js.erb: -------------------------------------------------------------------------------- 1 | console.log('deletete booking was initiated') 2 | $('#confirmation_tran').html("<%= j (render 'layouts/conf_tran_del') %>"); 3 | $('#confirmation_tran').css("display", "block"); 4 | $('#confirm_deletion_tran').modal('show'); 5 | $('#confirm_deletion_tran').slideDown(350); 6 | 7 | $("#delete-action_tran").click(function() 8 | { 9 | console.log('delete button was clicked') 10 | $('#course-<%=@transaction.id%>').hide(500); 11 | $('#confirm_deletion_tran').modal('hide'); 12 | 13 | $.ajax('/transactions/<%=@transaction.id%>', 14 | { type: 'DELETE', 15 | data: { _method: 'DELETE' }, 16 | data: { authenticity_token: $('[name="csrf-token"]')[0].content} 17 | }); 18 | 19 | }); 20 | -------------------------------------------------------------------------------- /app/views/transactions/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= simple_form_for @transaction do |f| %> 2 | <%= f.error_notification %> 3 | <%= f.input :course_taught_id, collection: Hash[auth_course_options], label: "Select Course" %> 4 | <%= f.input :sitting_student_id, collection: [ ' ' ],label: "Select Student" %> 5 | <%= f.input :minutes, label: "Session Duration (min)"%> 6 | 7 | 8 | <%= f.submit 'Save Booking', class: 'btn btn-primary', data: { "disable-with": "Saving..." } %> 9 | <% end %> -------------------------------------------------------------------------------- /app/views/transactions/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 |
8 |
9 | 23 |
24 |
25 | <%= render 'layouts/billable_bookings' %> 26 |
27 | <% if current_user.teacher? %> 28 |
29 | <%= render 'layouts/non_billable_bookings' %> 30 |
31 | <% end %> 32 |
33 | -------------------------------------------------------------------------------- /app/views/transactions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
Book a new lesson session
5 | 6 | <%= simple_form_for @transaction do |f| %> 7 | <%= f.input :booking_type, collection: Hash[type_options], label: "Options for this session's booking", class: "custom-select"%> 8 | <%= f.input :course_taught_id, collection: Hash[auth_course_options], label: "Select Course" %> 9 | <%= f.input :sitting_student_id, collection: [ ' ' ],label: "Select Student" %> 10 | <%= f.input :minutes, label: "Session Duration (min)"%> 11 | 15 | <% end %> 16 | 17 |
18 |
19 |
-------------------------------------------------------------------------------- /app/views/welcome/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 38 |
39 |
-------------------------------------------------------------------------------- /app_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/app_screenshot.png -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 5 | load Gem.bin_path('bundler', 'bundle') 6 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | begin 5 | load File.expand_path('spring', __dir__) 6 | rescue LoadError => e 7 | raise unless e.message.include?('spring') 8 | end 9 | APP_PATH = File.expand_path('../config/application', __dir__) 10 | require_relative '../config/boot' 11 | require 'rails/commands' 12 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | begin 5 | load File.expand_path('spring', __dir__) 6 | rescue LoadError => e 7 | raise unless e.message.include?('spring') 8 | end 9 | require_relative '../config/boot' 10 | require 'rake' 11 | Rake.application.run 12 | -------------------------------------------------------------------------------- /bin/rspec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | begin 5 | load File.expand_path('spring', __dir__) 6 | rescue LoadError => e 7 | raise unless e.message.include?('spring') 8 | end 9 | require 'bundler/setup' 10 | load Gem.bin_path('rspec-core', 'rspec') 11 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'fileutils' 5 | include FileUtils 6 | 7 | # path to your application root. 8 | APP_ROOT = File.expand_path('..', __dir__) 9 | 10 | def system!(*args) 11 | system(*args) || abort("\n== Command #{args} failed ==") 12 | end 13 | 14 | chdir APP_ROOT do 15 | # This script is a starting point to setup your application. 16 | # Add necessary setup steps to this file. 17 | 18 | puts '== Installing dependencies ==' 19 | system! 'gem install bundler --conservative' 20 | system('bundle check') || system!('bundle install') 21 | 22 | # Install JavaScript dependencies if using Yarn 23 | # system('bin/yarn') 24 | 25 | # puts "\n== Copying sample files ==" 26 | # unless File.exist?('config/database.yml') 27 | # cp 'config/database.yml.sample', 'config/database.yml' 28 | # end 29 | 30 | puts "\n== Preparing database ==" 31 | system! 'bin/rails db:setup' 32 | 33 | puts "\n== Removing old logs and tempfiles ==" 34 | system! 'bin/rails log:clear tmp:clear' 35 | 36 | puts "\n== Restarting application server ==" 37 | system! 'bin/rails restart' 38 | end 39 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # This file loads Spring without using Bundler, in order to be fast. 5 | # It gets overwritten when you run the `spring binstub` command. 6 | 7 | unless defined?(Spring) 8 | require 'rubygems' 9 | require 'bundler' 10 | 11 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 12 | spring = lockfile.specs.detect { |spec| spec.name == 'spring' } 13 | if spring 14 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 15 | gem 'spring', spring.version 16 | require 'spring/binstub' 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | require 'fileutils' 5 | include FileUtils 6 | 7 | # path to your application root. 8 | APP_ROOT = File.expand_path('..', __dir__) 9 | 10 | def system!(*args) 11 | system(*args) || abort("\n== Command #{args} failed ==") 12 | end 13 | 14 | chdir APP_ROOT do 15 | # This script is a way to update your development environment automatically. 16 | # Add necessary update steps to this file. 17 | 18 | puts '== Installing dependencies ==' 19 | system! 'gem install bundler --conservative' 20 | system('bundle check') || system!('bundle install') 21 | 22 | # Install JavaScript dependencies if using Yarn 23 | # system('bin/yarn') 24 | 25 | puts "\n== Updating database ==" 26 | system! 'bin/rails db:migrate' 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! 'bin/rails log:clear tmp:clear' 30 | 31 | puts "\n== Restarting application server ==" 32 | system! 'bin/rails restart' 33 | end 34 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | APP_ROOT = File.expand_path('..', __dir__) 5 | Dir.chdir(APP_ROOT) do 6 | exec 'yarnpkg', *ARGV 7 | rescue Errno::ENOENT 8 | warn 'Yarn executable was not detected in the system.' 9 | warn 'Download Yarn at https://yarnpkg.com/en/docs/install' 10 | exit 1 11 | end 12 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails' 6 | # Pick the frameworks you want: 7 | require 'active_model/railtie' 8 | require 'active_job/railtie' 9 | require 'active_record/railtie' 10 | require 'active_storage/engine' 11 | require 'action_controller/railtie' 12 | require 'action_mailer/railtie' 13 | require 'action_view/railtie' 14 | require 'action_cable/engine' 15 | require 'sprockets/railtie' 16 | # require "rails/test_unit/railtie" 17 | 18 | # Require the gems listed in Gemfile, including any gems 19 | # you've limited to :test, :development, or :production. 20 | Bundler.require(*Rails.groups) 21 | 22 | module RailsCapstoneLessonsLab 23 | class Application < Rails::Application 24 | # Initialize configuration defaults for originally generated Rails version. 25 | config.load_defaults 5.2 26 | 27 | # Settings in config/environments/* take precedence over those specified here. 28 | # Application configuration can go into files in config/initializers 29 | # -- all .rb files in that directory are automatically loaded after loading 30 | # the framework and any gems in your application. 31 | 32 | # Don't generate system test files. 33 | config.generators.system_tests = nil 34 | 35 | #added for custom fonts 36 | config.assets.paths << Rails.root.join("app","assets","fonts") 37 | 38 | #adding svg icons font into the assets pipeline (for Heroku) 39 | config.assets.paths << Rails.root.join('assets','images','icons') 40 | 41 | #Precompile additional assets (for heroku) 42 | config.assets.precompile += %w( .svg .eot .woff .ttf .png .jpg .gif ) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 4 | 5 | require 'bundler/setup' # Set up gems listed in the Gemfile. 6 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 7 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: rails_capstone_lessons_lab_production 11 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | uv0ho6HFNvE8ou68cmXccA8EoN3AqBrGXuQzcHelaBO05ZQIIL3glFZOH2+MiSrciPpQMWo4p5Vp9BsWAiSnPSGGL2OW699/R2K+iqRd2V7F1vI4crXHSHaPYnVkWTTP4QcJG2SY1iLioU8mniyy/pBD1j0B48B6QQ1MozfWUgyRwhWz2almkhk/DJnj8vZiEjyj28prYfXjo3iCKKer/zTErbcmukNQeIK0lvIDVW//H8EEemldBuJVkG3CoFPoHlFlyIhnvujWtHTdDRpDcwf9TrzVopKWuDkA9LsKRmLrESBPvtG+vRA9c1mBNL7yTBhTQEBoB9I04T2cdeeMDZR+uZGVYcr7x1qh8IIZbJaWLHAbh7anAOnSXyRfKsiVQtDbPg4TPuTTaPoNEsNm9odrowwIvdJAzYUW--VKQxE85cvbYgguHV--nsyDgBthUngZTEVbjqzsww== -------------------------------------------------------------------------------- /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: <%= ENV.fetch("RAILS_MAX_THREADS") { 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 | # frozen_string_literal: true 2 | 3 | # Load the Rails application. 4 | require_relative 'application' 5 | 6 | # Initialize the Rails application. 7 | Rails.application.initialize! 8 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 20 | config.action_controller.perform_caching = true 21 | 22 | config.cache_store = :memory_store 23 | config.public_file_server.headers = { 24 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 25 | } 26 | else 27 | config.action_controller.perform_caching = false 28 | 29 | config.cache_store = :null_store 30 | end 31 | 32 | # Store uploaded files on the local file system (see config/storage.yml for options) 33 | config.active_storage.service = :local 34 | 35 | # Don't care if the mailer can't send. 36 | config.action_mailer.raise_delivery_errors = false 37 | 38 | config.action_mailer.perform_caching = false 39 | 40 | # Print deprecation notices to the Rails logger. 41 | config.active_support.deprecation = :log 42 | 43 | # Raise an error on page load if there are pending migrations. 44 | config.active_record.migration_error = :page_load 45 | 46 | # Highlight code that triggered database queries in logs. 47 | config.active_record.verbose_query_logs = true 48 | 49 | # Debug mode disables concatenation and preprocessing of assets. 50 | # This option may cause significant delays in view rendering with a large 51 | # number of complex assets. 52 | config.assets.debug = true 53 | 54 | # Suppress logger output for asset requests. 55 | config.assets.quiet = true 56 | 57 | # Raises error for missing translations 58 | # config.action_view.raise_on_missing_translations = true 59 | 60 | # Use an evented file watcher to asynchronously detect changes in source code, 61 | # routes, locales, etc. This feature depends on the listen gem. 62 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 63 | 64 | #Devise configuration 65 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 66 | 67 | end 68 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # 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 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 35 | 36 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 37 | # config.action_controller.asset_host = 'http://assets.example.com' 38 | 39 | # Specifies the header that your server uses for sending files. 40 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 41 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 42 | 43 | # Store uploaded files on the local file system (see config/storage.yml for options) 44 | config.active_storage.service = :local 45 | 46 | # Mount Action Cable outside main process or domain 47 | # config.action_cable.mount_path = nil 48 | # config.action_cable.url = 'wss://example.com/cable' 49 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 50 | 51 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 52 | # config.force_ssl = true 53 | 54 | # Use the lowest log level to ensure availability of diagnostic information 55 | # when problems arise. 56 | config.log_level = :debug 57 | 58 | # Prepend all log lines with the following tags. 59 | config.log_tags = [:request_id] 60 | 61 | # Use a different cache store in production. 62 | # config.cache_store = :mem_cache_store 63 | 64 | # Use a real queuing backend for Active Job (and separate queues per environment) 65 | # config.active_job.queue_adapter = :resque 66 | # config.active_job.queue_name_prefix = "rails_capstone_lessons_lab_#{Rails.env}" 67 | 68 | config.action_mailer.perform_caching = false 69 | 70 | # Ignore bad email addresses and do not raise email delivery errors. 71 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 72 | # config.action_mailer.raise_delivery_errors = false 73 | 74 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 75 | # the I18n.default_locale when a translation cannot be found). 76 | config.i18n.fallbacks = true 77 | 78 | # Send deprecation notices to registered listeners. 79 | config.active_support.deprecation = :notify 80 | 81 | # Use default logging formatter so that PID and timestamp are not suppressed. 82 | config.log_formatter = ::Logger::Formatter.new 83 | 84 | # Use a different logger for distributed setups. 85 | # require 'syslog/logger' 86 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 87 | 88 | if ENV['RAILS_LOG_TO_STDOUT'].present? 89 | logger = ActiveSupport::Logger.new(STDOUT) 90 | logger.formatter = config.log_formatter 91 | config.logger = ActiveSupport::TaggedLogging.new(logger) 92 | end 93 | 94 | # Do not dump schema after migrations. 95 | config.active_record.dump_schema_after_migration = false 96 | 97 | # Must include to get inline SVGs to work in deploy 98 | config.assets.css_compressor = :scss 99 | 100 | #Fix to enable img usage @ heroku 101 | config.assets.compile = true 102 | config.assets.digest = true 103 | 104 | #adding svg icons font into the assets pipeline (for Heroku) 105 | config.assets.paths << Rails.root.join('assets', 'images','icons') 106 | 107 | #Precompile additional assets (for heroku) 108 | config.assets.precompile += %w( .svg .eot .woff .ttf ) 109 | 110 | end 111 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # The test environment is used exclusively to run your application's 7 | # test suite. You never need to work with it otherwise. Remember that 8 | # your test database is "scratch space" for the test suite and is wiped 9 | # and recreated between test runs. Don't rely on the data there! 10 | config.cache_classes = true 11 | 12 | # Do not eager load code on boot. This avoids loading your whole application 13 | # just for the purpose of running a single test. If you are using a tool that 14 | # preloads Rails for running tests, you may have to set it to true. 15 | config.eager_load = false 16 | 17 | # Configure public file server for tests with Cache-Control for performance. 18 | config.public_file_server.enabled = true 19 | config.public_file_server.headers = { 20 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 21 | } 22 | 23 | # Show full error reports and disable caching. 24 | config.consider_all_requests_local = true 25 | config.action_controller.perform_caching = false 26 | 27 | # Raise exceptions instead of rendering exception templates. 28 | config.action_dispatch.show_exceptions = false 29 | 30 | # Disable request forgery protection in test environment. 31 | config.action_controller.allow_forgery_protection = false 32 | 33 | # Store uploaded files on the local file system in a temporary directory 34 | config.active_storage.service = :test 35 | 36 | config.action_mailer.perform_caching = false 37 | 38 | # Tell Action Mailer not to deliver emails to the real world. 39 | # The :test delivery method accumulates sent emails in the 40 | # ActionMailer::Base.deliveries array. 41 | config.action_mailer.delivery_method = :test 42 | 43 | # Print deprecation notices to the stderr. 44 | config.active_support.deprecation = :stderr 45 | 46 | # Raises error for missing translations 47 | # config.action_view.raise_on_missing_translations = true 48 | 49 | #devise configuration 50 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 51 | 52 | end 53 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # ActiveSupport::Reloader.to_prepare do 5 | # ApplicationController.renderer.defaults.merge!( 6 | # http_host: 'example.org', 7 | # https: false 8 | # ) 9 | # end 10 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Version of your assets, change this if you want to expire all your assets. 6 | Rails.application.config.assets.version = '1.0' 7 | 8 | # Add additional assets to the asset load path. 9 | # Rails.application.config.assets.paths << Emoji.images_path 10 | # Add Yarn node_modules folder to the asset load path. 11 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 12 | Rails.application.config.assets.paths << Rails.root.join('assets', 'images','icons') 13 | 14 | # Precompile additional assets. 15 | # application.js, application.css, and all non-JS/CSS in the app/assets 16 | # folder are already added. 17 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 18 | 19 | Rails.application.config.assets.precompile += %w( '.png' ) 20 | Rails.application.config.assets.precompile += %w( '.svg' ) 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 5 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 6 | 7 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 8 | # Rails.backtrace_cleaner.remove_silencers! 9 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Define an application-wide content security policy 5 | # For further information see the following documentation 6 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 7 | 8 | # Rails.application.config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | 16 | # # Specify URI for violation reports 17 | # # policy.report_uri "/csp-violation-report-endpoint" 18 | # end 19 | 20 | # If you are using UJS then enable automatic nonce generation 21 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 22 | 23 | # Report CSP violations to a specified URI 24 | # For further information see the following documentation: 25 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 26 | # Rails.application.config.content_security_policy_report_only = true 27 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Specify a serializer for the signed and encrypted cookie jars. 6 | # Valid options are :json, :marshal, and :hybrid. 7 | Rails.application.config.action_dispatch.cookies_serializer = :json 8 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # Configure sensitive parameters which will be filtered from the log file. 6 | Rails.application.config.filter_parameters += [:password] 7 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new inflection rules using the following format. Inflections 5 | # are locale specific, and you may define rules for as many different 6 | # locales as you wish. All of these examples are active by default: 7 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 8 | # inflect.plural /^(ox)$/i, '\1en' 9 | # inflect.singular /^(ox)en/i, '\1' 10 | # inflect.irregular 'person', 'people' 11 | # inflect.uncountable %w( fish sheep ) 12 | # end 13 | 14 | # These inflection rules are supported but not enabled by default: 15 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 16 | # inflect.acronym 'RESTful' 17 | # end 18 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # Be sure to restart your server when you modify this file. 3 | 4 | # Add new mime types for use in respond_to blocks: 5 | # Mime::Type.register "text/richtext", :rtf 6 | -------------------------------------------------------------------------------- /config/initializers/simple_form.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # 4 | # Uncomment this and change the path if necessary to include your own 5 | # components. 6 | # See https://github.com/heartcombo/simple_form#custom-components to know 7 | # more about custom components. 8 | # Dir[Rails.root.join('lib/components/**/*.rb')].each { |f| require f } 9 | # 10 | # Use this setup block to configure all options available in SimpleForm. 11 | SimpleForm.setup do |config| 12 | # Wrappers are used by the form builder to generate a 13 | # complete input. You can remove any component from the 14 | # wrapper, change the order or even add your own to the 15 | # stack. The options given below are used to wrap the 16 | # whole input. 17 | config.wrappers :default, class: :input, 18 | hint_class: :field_with_hint, error_class: :field_with_errors, valid_class: :field_without_errors do |b| 19 | ## Extensions enabled by default 20 | # Any of these extensions can be disabled for a 21 | # given input by passing: `f.input EXTENSION_NAME => false`. 22 | # You can make any of these extensions optional by 23 | # renaming `b.use` to `b.optional`. 24 | 25 | # Determines whether to use HTML5 (:email, :url, ...) 26 | # and required attributes 27 | b.use :html5 28 | 29 | # Calculates placeholders automatically from I18n 30 | # You can also pass a string as f.input placeholder: "Placeholder" 31 | b.use :placeholder 32 | 33 | ## Optional extensions 34 | # They are disabled unless you pass `f.input EXTENSION_NAME => true` 35 | # to the input. If so, they will retrieve the values from the model 36 | # if any exists. If you want to enable any of those 37 | # extensions by default, you can change `b.optional` to `b.use`. 38 | 39 | # Calculates maxlength from length validations for string inputs 40 | # and/or database column lengths 41 | b.optional :maxlength 42 | 43 | # Calculate minlength from length validations for string inputs 44 | b.optional :minlength 45 | 46 | # Calculates pattern from format validations for string inputs 47 | b.optional :pattern 48 | 49 | # Calculates min and max from length validations for numeric inputs 50 | b.optional :min_max 51 | 52 | # Calculates readonly automatically from readonly attributes 53 | b.optional :readonly 54 | 55 | ## Inputs 56 | # b.use :input, class: 'input', error_class: 'is-invalid', valid_class: 'is-valid' 57 | b.use :label_input 58 | b.use :hint, wrap_with: { tag: :span, class: :hint } 59 | b.use :error, wrap_with: { tag: :span, class: :error } 60 | 61 | ## full_messages_for 62 | # If you want to display the full error message for the attribute, you can 63 | # use the component :full_error, like: 64 | # 65 | # b.use :full_error, wrap_with: { tag: :span, class: :error } 66 | end 67 | 68 | # The default wrapper to be used by the FormBuilder. 69 | config.default_wrapper = :default 70 | 71 | # Define the way to render check boxes / radio buttons with labels. 72 | # Defaults to :nested for bootstrap config. 73 | # inline: input + label 74 | # nested: label > input 75 | config.boolean_style = :nested 76 | 77 | # Default class for buttons 78 | config.button_class = 'btn' 79 | 80 | # Method used to tidy up errors. Specify any Rails Array method. 81 | # :first lists the first message for each field. 82 | # Use :to_sentence to list all errors for each field. 83 | # config.error_method = :first 84 | 85 | # Default tag used for error notification helper. 86 | config.error_notification_tag = :div 87 | 88 | # CSS class to add for error notification helper. 89 | config.error_notification_class = 'error_notification' 90 | 91 | # Series of attempts to detect a default label method for collection. 92 | # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] 93 | 94 | # Series of attempts to detect a default value method for collection. 95 | # config.collection_value_methods = [ :id, :to_s ] 96 | 97 | # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. 98 | # config.collection_wrapper_tag = nil 99 | 100 | # You can define the class to use on all collection wrappers. Defaulting to none. 101 | # config.collection_wrapper_class = nil 102 | 103 | # You can wrap each item in a collection of radio/check boxes with a tag, 104 | # defaulting to :span. 105 | # config.item_wrapper_tag = :span 106 | 107 | # You can define a class to use in all item wrappers. Defaulting to none. 108 | # config.item_wrapper_class = nil 109 | 110 | # How the label text should be generated altogether with the required text. 111 | # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } 112 | 113 | # You can define the class to use on all labels. Default is nil. 114 | # config.label_class = nil 115 | 116 | # You can define the default class to be used on forms. Can be overriden 117 | # with `html: { :class }`. Defaulting to none. 118 | # config.default_form_class = nil 119 | 120 | # You can define which elements should obtain additional classes 121 | # config.generate_additional_classes_for = [:wrapper, :label, :input] 122 | 123 | # Whether attributes are required by default (or not). Default is true. 124 | # config.required_by_default = true 125 | 126 | # Tell browsers whether to use the native HTML5 validations (novalidate form option). 127 | # These validations are enabled in SimpleForm's internal config but disabled by default 128 | # in this configuration, which is recommended due to some quirks from different browsers. 129 | # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, 130 | # change this configuration to true. 131 | config.browser_validations = false 132 | 133 | # Custom mappings for input types. This should be a hash containing a regexp 134 | # to match as key, and the input type that will be used when the field name 135 | # matches the regexp as value. 136 | # config.input_mappings = { /count/ => :integer } 137 | 138 | # Custom wrappers for input types. This should be a hash containing an input 139 | # type as key and the wrapper that will be used for all inputs with specified type. 140 | # config.wrapper_mappings = { string: :prepend } 141 | 142 | # Namespaces where SimpleForm should look for custom input classes that 143 | # override default inputs. 144 | # config.custom_inputs_namespaces << "CustomInputs" 145 | 146 | # Default priority for time_zone inputs. 147 | # config.time_zone_priority = nil 148 | 149 | # Default priority for country inputs. 150 | # config.country_priority = nil 151 | 152 | # When false, do not use translations for labels. 153 | # config.translate_labels = true 154 | 155 | # Automatically discover new inputs in Rails' autoload path. 156 | # config.inputs_discovery = true 157 | 158 | # Cache SimpleForm inputs discovery 159 | # config.cache_discovery = !Rails.env.development? 160 | 161 | # Default class for inputs 162 | # config.input_class = nil 163 | 164 | # Define the default class of the input wrapper of the boolean input. 165 | config.boolean_label_class = 'checkbox' 166 | 167 | # Defines if the default input wrapper class should be included in radio 168 | # collection wrappers. 169 | # config.include_default_input_wrapper_class = true 170 | 171 | # Defines which i18n scope will be used in Simple Form. 172 | # config.i18n_scope = 'simple_form' 173 | 174 | # Defines validation classes to the input_field. By default it's nil. 175 | # config.input_field_valid_class = 'is-valid' 176 | # config.input_field_error_class = 'is-invalid' 177 | end 178 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Be sure to restart your server when you modify this file. 4 | 5 | # This file contains settings for ActionController::ParamsWrapper which 6 | # is enabled by default. 7 | 8 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 9 | ActiveSupport.on_load(:action_controller) do 10 | wrap_parameters format: [:json] 11 | end 12 | 13 | # To enable root element in JSON for ActiveRecord objects. 14 | # ActiveSupport.on_load(:active_record) do 15 | # self.include_root_in_json = true 16 | # end 17 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/plataformatec/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | email_changed: 27 | subject: "Email Changed" 28 | password_change: 29 | subject: "Password Changed" 30 | omniauth_callbacks: 31 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 32 | success: "Successfully authenticated from %{kind} account." 33 | passwords: 34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 35 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 36 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 37 | updated: "Your password has been changed successfully. You are now signed in." 38 | updated_not_active: "Your password has been changed successfully." 39 | registrations: 40 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 41 | signed_up: "Welcome! You have signed up successfully." 42 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 43 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 44 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." 46 | updated: "Your account has been updated successfully." 47 | updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again" 48 | sessions: 49 | signed_in: "Signed in successfully." 50 | signed_out: "Signed out successfully." 51 | already_signed_out: "Signed out successfully." 52 | unlocks: 53 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 54 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 55 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 56 | errors: 57 | messages: 58 | already_confirmed: "was already confirmed, please try signing in" 59 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 60 | expired: "has expired, please request a new one" 61 | not_found: "not found" 62 | not_locked: "was not locked" 63 | not_saved: 64 | one: "1 error prohibited this %{resource} from being saved:" 65 | other: "%{count} errors prohibited this %{resource} from being saved:" 66 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/locales/simple_form.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | simple_form: 3 | "yes": 'Yes' 4 | "no": 'No' 5 | required: 6 | text: 'required' 7 | mark: '*' 8 | # You can uncomment the line below if you need to overwrite the whole required html. 9 | # When using html, text and mark won't be used. 10 | # html: '*' 11 | error_notification: 12 | default_message: "Please review the problems below:" 13 | # Examples 14 | # labels: 15 | # defaults: 16 | # password: 'Password' 17 | # user: 18 | # new: 19 | # email: 'E-mail to sign in.' 20 | # edit: 21 | # email: 'E-mail.' 22 | # hints: 23 | # defaults: 24 | # username: 'User name to sign in.' 25 | # password: 'No special characters, please.' 26 | # include_blanks: 27 | # defaults: 28 | # age: 'Rather not say' 29 | # prompts: 30 | # defaults: 31 | # age: 'Select your age' 32 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Puma can serve each request in a thread from an internal thread pool. 4 | # The `threads` method setting takes two numbers: a minimum and maximum. 5 | # Any libraries that use thread pools should be configured to match 6 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 7 | # and maximum; this matches the default thread size of Active Record. 8 | # 9 | threads_count = ENV.fetch('RAILS_MAX_THREADS') { 5 } 10 | threads threads_count, threads_count 11 | 12 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 13 | # 14 | port ENV.fetch('PORT') { 3000 } 15 | 16 | # Specifies the `environment` that Puma will run in. 17 | # 18 | environment ENV.fetch('RAILS_ENV') { 'development' } 19 | 20 | # Specifies the `pidfile` that Puma will use. 21 | pidfile ENV.fetch('PIDFILE') { 'tmp/pids/server.pid' } 22 | 23 | # Specifies the number of `workers` to boot in clustered mode. 24 | # Workers are forked webserver processes. If using threads and workers together 25 | # the concurrency of the application would be max `threads` * `workers`. 26 | # Workers do not work on JRuby or Windows (both of which do not support 27 | # processes). 28 | # 29 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 30 | 31 | # Use the `preload_app!` method when specifying a `workers` number. 32 | # This directive tells Puma to first boot the application and load code 33 | # before forking the application. This takes advantage of Copy On Write 34 | # process behavior so workers use less memory. 35 | # 36 | # preload_app! 37 | 38 | # Allow puma to be restarted by `rails restart` command. 39 | plugin :tmp_restart 40 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | 2 | Rails.application.routes.draw do 3 | 4 | root to: 'welcome#index' 5 | 6 | devise_for :users, :controllers => { registrations: 'users/registrations' } 7 | resources :transactions, only: [ :index, :new, :create, :edit, :delete, :destroy ] 8 | 9 | 10 | get 'enrolled/:id', to: 'enrolled#enrolled' 11 | 12 | 13 | resources :enrolls, only: [ :create, :show ] 14 | 15 | get 'enrolar/:id', to: 'enrolled#enrolar' 16 | resources :transactions do 17 | member do 18 | get 'confirm_destroy' 19 | get 'enrolled' 20 | end 21 | end 22 | 23 | resources :groups # here we create the resource course(group) 24 | resources :groups do 25 | member do 26 | get 'confirm_destroy' 27 | end 28 | end 29 | 30 | 31 | end 32 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | %w[ 4 | .ruby-version 5 | .rbenv-vars 6 | tmp/restart.txt 7 | tmp/caching-dev.txt 8 | ].each { |path| Spring.watch(path) } 9 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /db/migrate/20200509222956_create_groups.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class CreateGroups < ActiveRecord::Migration[5.2] 4 | def change 5 | create_table :groups do |t| 6 | t.string :name 7 | t.text :description 8 | t.integer :duration 9 | t.decimal :price, precision: 8, scale: 2 10 | t.boolean :online 11 | t.boolean :presencial 12 | t.string :starting 13 | t.boolean :enabled 14 | t.string :cover_image 15 | 16 | t.timestamps 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /db/migrate/20200512194136_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateUsers < ActiveRecord::Migration[5.2] 4 | def change 5 | create_table :users do |t| 6 | ## Database authenticatable 7 | t.string :name, null: false, default: "" 8 | t.string :email, null: false, default: "" 9 | t.integer :role, null: false 10 | t.string :encrypted_password, null: false, default: "" 11 | 12 | ## Recoverable 13 | t.string :reset_password_token 14 | t.datetime :reset_password_sent_at 15 | 16 | ## Rememberable 17 | t.datetime :remember_created_at 18 | 19 | ## Trackable 20 | # t.integer :sign_in_count, default: 0, null: false 21 | # t.datetime :current_sign_in_at 22 | # t.datetime :last_sign_in_at 23 | # t.string :current_sign_in_ip 24 | # t.string :last_sign_in_ip 25 | 26 | ## Confirmable 27 | # t.string :confirmation_token 28 | # t.datetime :confirmed_at 29 | # t.datetime :confirmation_sent_at 30 | # t.string :unconfirmed_email # Only if using reconfirmable 31 | 32 | ## Lockable 33 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 34 | # t.string :unlock_token # Only if unlock strategy is :email or :both 35 | # t.datetime :locked_at 36 | 37 | 38 | t.timestamps null: false 39 | end 40 | 41 | add_index :users, :email, unique: true 42 | add_index :users, :reset_password_token, unique: true 43 | # add_index :users, :confirmation_token, unique: true 44 | # add_index :users, :unlock_token, unique: true 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /db/migrate/20200512214438_add_user_to_groups_course.rb: -------------------------------------------------------------------------------- 1 | class AddUserToGroupsCourse < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :groups, :author_id, :integer 4 | add_foreign_key :groups, :users, column: :author_id, primary_key: "id" 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20200513110914_create_enrolls.rb: -------------------------------------------------------------------------------- 1 | class CreateEnrolls < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :enrolls do |t| 4 | t.integer :student_id 5 | t.integer :course_id 6 | t.timestamps 7 | end 8 | add_foreign_key :enrolls, :users, column: :student_id, primary_key: "id" 9 | add_foreign_key :enrolls, :groups, column: :course_id, primary_key: "id" 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200513133240_create_transactions.rb: -------------------------------------------------------------------------------- 1 | class CreateTransactions < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :transactions do |t| 4 | t.integer :teacher_id 5 | t.integer :status 6 | t.integer :booking_type 7 | t.integer :minutes 8 | t.date :accdate 9 | t.integer :sitting_student_id, null: true, foreign_key: true 10 | t.integer :course_taught_id, null: true, foreign_key: true 11 | 12 | t.timestamps 13 | end 14 | 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2020_05_13_133240) do 14 | 15 | create_table "enrolls", force: :cascade do |t| 16 | t.integer "student_id" 17 | t.integer "course_id" 18 | t.datetime "created_at", null: false 19 | t.datetime "updated_at", null: false 20 | end 21 | 22 | create_table "groups", force: :cascade do |t| 23 | t.string "name" 24 | t.text "description" 25 | t.integer "duration" 26 | t.decimal "price", precision: 8, scale: 2 27 | t.boolean "online" 28 | t.boolean "presencial" 29 | t.string "starting" 30 | t.boolean "enabled" 31 | t.string "cover_image" 32 | t.datetime "created_at", null: false 33 | t.datetime "updated_at", null: false 34 | t.integer "author_id" 35 | end 36 | 37 | create_table "transactions", force: :cascade do |t| 38 | t.integer "teacher_id" 39 | t.integer "status" 40 | t.integer "booking_type" 41 | t.integer "minutes" 42 | t.date "accdate" 43 | t.integer "sitting_student_id" 44 | t.integer "course_taught_id" 45 | t.datetime "created_at", null: false 46 | t.datetime "updated_at", null: false 47 | end 48 | 49 | create_table "users", force: :cascade do |t| 50 | t.string "name", default: "", null: false 51 | t.string "email", default: "", null: false 52 | t.integer "role", null: false 53 | t.string "encrypted_password", default: "", null: false 54 | t.string "reset_password_token" 55 | t.datetime "reset_password_sent_at" 56 | t.datetime "remember_created_at" 57 | t.datetime "created_at", null: false 58 | t.datetime "updated_at", null: false 59 | t.index ["email"], name: "index_users_on_email", unique: true 60 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 61 | end 62 | 63 | end 64 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | # This file should contain all the record creation needed to seed the database with its default values. 3 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 4 | # 5 | # Examples: 6 | # 7 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 8 | # Character.create(name: 'Luke', movie: movies.first) 9 | -------------------------------------------------------------------------------- /erd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/erd.png -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/lib/tasks/.keep -------------------------------------------------------------------------------- /lib/templates/erb/scaffold/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%# frozen_string_literal: true %> 2 | <%%= simple_form_for(@<%= singular_table_name %>) do |f| %> 3 | <%%= f.error_notification %> 4 | <%%= f.error_notification message: f.object.errors[:base].to_sentence if f.object.errors[:base].present? %> 5 | 6 |
7 | <%- attributes.each do |attribute| -%> 8 | <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> 9 | <%- end -%> 10 |
11 | 12 |
13 | <%%= f.button :submit %> 14 |
15 | <%% end %> 16 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/log/.keep -------------------------------------------------------------------------------- /mam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/mam.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rails_capstone_lessons_lab", 3 | "private": true, 4 | "dependencies": {} 5 | } 6 | -------------------------------------------------------------------------------- /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/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/public/favicon.ico -------------------------------------------------------------------------------- /public/mam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/public/mam.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /spec/controllers/enrolls_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | describe EnrollsController do 4 | describe 'Teacher User' do 5 | context 'logged in and course owner' do 6 | describe 'POST enrolls (create)' do 7 | # one teacher, one student, one course. Test: one enrollment. 8 | let(:t1) { FactoryBot.create(:teacher_user) } 9 | before do 10 | sign_in(t1) 11 | end 12 | let(:s1) { FactoryBot.create(:student_user) } 13 | let(:c1) { FactoryBot.create(:group_enabled, author: t1) } 14 | 15 | it 'creates the enroll and redirect to success controller' do 16 | expect do 17 | post :create, params: { student: s1, course: c1 } 18 | expect(response).to redirect_to(notices_path) 19 | end 20 | end 21 | 22 | it 'creates the enroll and updates de DB' do 23 | expect do 24 | post :create, params: { student_id: s1, course_id: c1 } 25 | end.to change(Enroll, :count).by(1) 26 | end 27 | end 28 | end 29 | 30 | context 'logged in not course owner' do 31 | describe 'POST enrolls create' do 32 | # one teacher, one student, one course. Test: one enrollment. 33 | let(:t1) { FactoryBot.create(:teacher_user) } 34 | let(:t2) { FactoryBot.create(:teacher_user) } 35 | 36 | before do 37 | sign_in(t2) 38 | end 39 | let(:s1) { FactoryBot.create(:student_user) } 40 | let(:c1) { FactoryBot.create(:group_enabled, author: t1) } 41 | 42 | it 'fails to enroll and redirect to course index page' do 43 | expect do 44 | post :create, params: { student: s1, course: c1 } 45 | expect(response).to redirect_to(groups_path) 46 | end 47 | end 48 | 49 | it 'creates the enroll and updates de DB' do 50 | expect do 51 | post :create, params: { student: s1, course: c1 } 52 | end.to change(Enroll, :count).by(0) 53 | end 54 | end 55 | 56 | describe 'DELETE request to destroy a course enrollment' 57 | end 58 | end 59 | 60 | describe 'Student User' do 61 | context 'logged in not course owner' do 62 | describe 'POST enrolls create' do 63 | # one teacher, one student, one course. Test: one enrollment. 64 | let(:t1) { FactoryBot.create(:teacher_user) } 65 | let(:s1) { FactoryBot.create(:student_user) } 66 | 67 | before do 68 | sign_in(s1) 69 | end 70 | 71 | let(:c1) { FactoryBot.create(:group_enabled, author: t1) } 72 | 73 | it 'fails to enroll and redirect to course index page' do 74 | expect do 75 | post :create, params: { student: s1, course: c1 } 76 | expect(response).to redirect_to(groups_path) 77 | end 78 | end 79 | 80 | it 'creates the enroll and updates de DB' do 81 | expect do 82 | post :create, params: { student: s1, course: c1 } 83 | end.to change(Enroll, :count).by(0) 84 | end 85 | end 86 | end 87 | end 88 | end 89 | 90 | # rubocop:enable 91 | -------------------------------------------------------------------------------- /spec/controllers/groups_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | # rubocop:disable Metrics/BlockLength,Layout/LineLength,Lint/UselessAssignment,Naming/VariableNumber 4 | 5 | describe GroupsController do 6 | describe 'Student User ' do 7 | # we create student, course and login as student 8 | let(:teacher_1) { FactoryBot.create(:teacher_user) } 9 | let(:student_1) { FactoryBot.create(:student_user) } 10 | let(:other_student) { FactoryBot.create(:student_user) } 11 | 12 | let(:other_student_enrolled_course_1) { FactoryBot.create(:enroll, student: student_1, course: course_1) } 13 | before do 14 | sign_in(student_1) 15 | end 16 | 17 | describe 'GET course index list' do 18 | it 'renders :index template' do 19 | get :index 20 | expect(response).to render_template(:index) 21 | end 22 | it "selects only 'enrolled' courses for the student to show on index" do 23 | course_1 = FactoryBot.create(:group_enabled, author: teacher_1) 24 | FactoryBot.create(:enroll, student: student_1, course: course_1) 25 | 26 | get :index 27 | expect(assigns(:group)).to match_array([course_1]) 28 | end 29 | end 30 | 31 | describe 'GET show course' do 32 | it 'renders :show template only if user is enrolled in the course' do 33 | course_1 = FactoryBot.create(:group_enabled, author: teacher_1) 34 | FactoryBot.create(:enroll, student: student_1, course: course_1) 35 | get :show, params: { id: course_1 } 36 | expect(response).to render_template(:show) 37 | end 38 | it 'assigns requested course to the @student_course variable for the loged_in student' do 39 | course_1 = FactoryBot.create(:group_enabled, author: teacher_1) 40 | FactoryBot.create(:enroll, student: student_1, course: course_1) 41 | get :show, params: { id: course_1 } 42 | expect(assigns(:group)).to eq(course_1) 43 | #:group is the variable defined in the groups_controller.rb file for eny course. 44 | end 45 | end 46 | 47 | # A student cannot create a new course 48 | describe 'GET new course' do 49 | it 'fails to render the new course form and redirects to login page' do 50 | get :new 51 | expect(response).to redirect_to(groups_path) 52 | end 53 | end 54 | 55 | describe 'POST course create' do 56 | let(:course_1) { FactoryBot.create(:group_enabled, author: teacher_1) } 57 | it 'fails and redirects to login page instead as student cannot create courses' do 58 | post :create, params: { group: course_1 } 59 | expect(response).to redirect_to(groups_path) 60 | end 61 | end 62 | 63 | describe 'GET course for edit' do 64 | let(:course_1) { FactoryBot.create(:group_enabled, author: teacher_1) } 65 | it 'fails to render :edit template as students are not allowed to edit courses' do 66 | get :edit, params: { id: course_1 } 67 | expect(response).to redirect_to(groups_path) 68 | end 69 | end 70 | 71 | describe 'PUT request to update course' do 72 | let(:course_1) { FactoryBot.create(:group_enabled, author: teacher_1) } 73 | let(:valid_course_data_change) { FactoryBot.attributes_for(:group_enabled, name: 'Course name update') } 74 | it 'fails to PUT a change on the course record' do 75 | put :update, params: { id: course_1, group: valid_course_data_change } 76 | expect(response).to redirect_to(groups_path) 77 | end 78 | end 79 | 80 | describe 'DELETE request to destroy a course record' do 81 | let(:course_1) { FactoryBot.create(:group_enabled, author: teacher_1) } 82 | it 'fails to destroy a course record' do 83 | delete :destroy, params: { id: course_1 } 84 | expect(response).to redirect_to(groups_path) 85 | end 86 | end 87 | end 88 | 89 | describe 'Teacher User' do 90 | let(:teacher_1) { FactoryBot.create(:teacher_user) } 91 | let(:teacher_2) { FactoryBot.create(:teacher_user) } 92 | 93 | before do 94 | sign_in(teacher_1) 95 | end 96 | 97 | describe 'GET course index' do 98 | it 'renders :index template' do 99 | get :index 100 | expect(response).to render_template(:index) 101 | end 102 | it 'selects only coursed where teacher is the author' do 103 | course_1 = FactoryBot.create(:group_enabled, name: 'teacher 1 course', author: teacher_1) 104 | course_2 = FactoryBot.create(:group_enabled, name: 'teacher 2 course', author: teacher_2) 105 | get :index 106 | expect(assigns(:group)).to match_array([course_1]) 107 | end 108 | end 109 | 110 | describe 'GET show course' do 111 | it 'renders :show template and is able to edit authored courses' do 112 | course_1 = FactoryBot.create(:group_enabled, name: 'teacher 1 course', author: teacher_1) 113 | get :show, params: { id: course_1 } 114 | expect(response).to render_template(:show) 115 | end 116 | it 'assigns the authored course to the @group instance variable (for edition)' do 117 | course_1 = FactoryBot.create(:group_enabled, name: 'teacher 1 course', author: teacher_1) 118 | get :show, params: { id: course_1 } 119 | expect(assigns(:group)).to eq(course_1) 120 | #:group is the variable defined in the groups_controller.rb file 121 | end 122 | end 123 | 124 | describe 'GET new for teacher' do 125 | it 'renders :new template to create a new course' do 126 | get :new 127 | expect(response).to render_template(:new) 128 | end 129 | it 'assigns new Group to @group instance variable' do 130 | get :new 131 | expect(assigns(:group)).to be_a_new(Group) 132 | end 133 | end 134 | 135 | describe 'POST create' do 136 | let(:valid_course_data) { FactoryBot.attributes_for(:group, name: 'teacher-1 course', author: teacher_1.id) } 137 | let(:invalid_course_data) { FactoryBot.attributes_for(:group, name: nil, author: teacher_1.id) } 138 | 139 | context 'with valid data' do 140 | it 'redirects to show the course index in goups#index' do 141 | post :create, params: { group: valid_course_data } 142 | expect(response).to redirect_to(groups_path) 143 | end 144 | it 'creates a new group/course in the database' do 145 | expect do 146 | post :create, params: { group: valid_course_data } 147 | end.to change(Group, :count).by(1) 148 | end 149 | end 150 | 151 | context 'with invalid data' do 152 | it 'redirects to :new and shows :error notice' do 153 | post :create, params: { group: invalid_course_data } 154 | expect(response).to render_template(:new) 155 | # expect(response).to have_content("can't be blank") 156 | end 157 | it 'does not create a new :group in the database' do 158 | expect do 159 | post :create, params: { group: invalid_course_data } 160 | end.not_to change(Group, :count) 161 | end 162 | end 163 | end 164 | 165 | context 'and teacher is not the owner of the course' do 166 | let(:course_2) { FactoryBot.create(:group_enabled, name: 'teacher 2 course', author: teacher_2) } 167 | let(:valid_course_change) { FactoryBot.attributes_for(:group_enabled, name: 'teacher 1 edit', author: teacher_1) } 168 | 169 | describe 'GET course edit' do 170 | it 'fails to render :edit template as teacher 1 is not the owner of the current course redirects to course index' do 171 | get :edit, params: { id: course_2 } 172 | expect(response).to redirect_to(groups_path) 173 | end 174 | end 175 | 176 | describe 'PUT request to update course' do 177 | it 'fails to PUT a change on the course record redirects to course index' do 178 | put :update, params: { id: course_2, group: valid_course_change } 179 | expect(response).to redirect_to(groups_path) 180 | end 181 | end 182 | 183 | describe 'DELETE request to destroy a course record' do 184 | it 'fails to destroy a course record redirects to course index' do 185 | delete :destroy, params: { id: course_2 } 186 | expect(response).to redirect_to(groups_path) 187 | end 188 | end 189 | end 190 | 191 | context 'teacher is the owner of the course' do 192 | let(:valid_course_data) { FactoryBot.create(:group_enabled, author: teacher_1) } 193 | 194 | describe 'GET edit' do 195 | it 'renders :edit template to valid course author' do 196 | get :edit, params: { id: valid_course_data } 197 | expect(response).to render_template(:edit) 198 | end 199 | it 'assigns the requested group/course to the view' do 200 | get :edit, params: { id: valid_course_data } 201 | expect(assigns(:group)).to eq(valid_course_data) 202 | end 203 | end 204 | 205 | describe 'PUT update action with ' do 206 | # we first create the group/course we will test by updating with valid and invalid data. 207 | let(:valid_course) { FactoryBot.create(:group, name: 'teacher-1 course', author: teacher_1) } 208 | let(:invalid_course_data_change) do 209 | FactoryBot.attributes_for(:group, 210 | name: nil, author: teacher_1) 211 | end 212 | let(:valid_course_data_change) do 213 | FactoryBot.attributes_for(:group, 214 | name: 'best teacher-1 course', author: teacher_1) 215 | end 216 | 217 | context 'valid data for enabled group/course' do 218 | it 'redirects to groups#show action' do 219 | put :update, params: { id: valid_course, group: valid_course_data_change } 220 | expect(response).to redirect_to(groups_path) 221 | end 222 | it 'updates group/course changes in the database' do 223 | put :update, params: { id: valid_course, group: valid_course_data_change } 224 | valid_course.reload # I reload DB record to fetch new object changes 225 | expect(valid_course.name).to eq('best teacher-1 course') # I check directly in the DB (integration test) 226 | end 227 | end 228 | 229 | context 'invalid data change with enabled group/course' do 230 | it 'renders :edit view template' do 231 | put :update, params: { id: valid_course, group: invalid_course_data_change } 232 | expect(response).to render_template(:edit) 233 | end 234 | it 'fails to update the group/course changes in the DB' do 235 | put :update, params: { id: valid_course, group: invalid_course_data_change } 236 | valid_course.reload # I reload DB record to fetch new object changes 237 | expect(valid_course.name).not_to eq('best teacher-1 course') 238 | # I check directly in the DB (integration test) 239 | end 240 | end 241 | end 242 | 243 | describe 'DELETE action to destroy' do 244 | # we first create the grou/course we will test by destroying. 245 | it 'not to redirect to groups/courses index as AJAX is in use' do 246 | delete :destroy, params: { id: valid_course_data } 247 | expect(response).to redirect_to(groups_path) 248 | end 249 | it 'deletes group/course from database' do 250 | delete :destroy, params: { id: valid_course_data } 251 | expect(Group.exists?(valid_course_data.id)).to be_falsy 252 | end 253 | end 254 | end 255 | end 256 | end 257 | 258 | # rubocop:enable Metrics/BlockLength,Layout/LineLength,Lint/UselessAssignment,Naming/VariableNumber 259 | -------------------------------------------------------------------------------- /spec/factories/enrolls.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :enroll do 3 | student { 1 } 4 | course { 1 } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /spec/factories/groups.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :group do 3 | sequence(:name) { |n| "course #{n}" } 4 | description { 'yes this is a course description' } 5 | duration { 30 } 6 | price { '9.99' } 7 | starting { '22/2/22' } 8 | cover_image { 'my file' } 9 | online { true } 10 | presencial { true } 11 | enabled { true } 12 | author_id { 1 } 13 | 14 | factory :group_enabled do 15 | enabled { true } 16 | end 17 | 18 | factory :group_disabled do 19 | enabled { false } 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/factories/transactions.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :transaction do 3 | teacher_id { 1 } 4 | 5 | factory :billable do 6 | sitting_student_id { 1 } 7 | course_taught_id { 1 } 8 | minutes { 15 } 9 | status { 0 } 10 | booking_type { 0 } 11 | end 12 | 13 | factory :non_billable do 14 | minutes { 15 } 15 | booking_type { 1 } 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/factories/users.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :user do 3 | sequence(:email) { |n| "email#{n}@email.com" } 4 | password { 'password' } 5 | sequence(:name) { |n| "#{n}-User" } 6 | 7 | factory :student_user do 8 | role { :student } 9 | end 10 | 11 | factory :teacher_user do 12 | role { :teacher } 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /spec/features/create_course_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | require_relative '../support/login_form' 3 | require_relative '../support/course_page' 4 | require_relative '../support/new_course_form' 5 | 6 | feature 'create new group/course' do 7 | let(:t1) { FactoryBot.create(:teacher_user) } 8 | let(:s1) { FactoryBot.create(:student_user) } 9 | let(:login_form) { LoginForm.new } 10 | let(:manage_my_course_page) { MyCoursePage.new } 11 | let(:new_course_form) { CourseForm.new } 12 | 13 | scenario 'authenticated and authorised user (Teacher) create course with valid data' do 14 | login_form.visit_page.login_as(t1).submit 15 | 16 | puts t1.email 17 | puts t1.password 18 | puts t1.role 19 | # expect(page).to have_content('teacher') 20 | 21 | manage_my_course_page.visit_page.new_course 22 | 23 | new_course_form.fill_form.submit 24 | 25 | expect(page).to have_content('IELTS') 26 | expect(page).to have_css('span#test-course', text: '1') 27 | expect(Group.last.enabled).to eq(true) 28 | end 29 | 30 | scenario 'Student fails to create a new course' do 31 | login_form.visit_page.login_as(s1).submit 32 | 33 | puts s1.email 34 | puts s1.password 35 | puts s1.role 36 | 37 | manage_my_course_page.student_hack_into_new_group_page 38 | 39 | # new_course_form.fill_form.submit 40 | 41 | expect(page).to have_content('you are not authorised for this action') 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/features/home_page_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | feature 'home page' do 4 | scenario 'welcome message' do 5 | visit('/') 6 | expect(page).to have_content('Lessons Lab') 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/ielts_cover_image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/spec/fixtures/ielts_cover_image.jpeg -------------------------------------------------------------------------------- /spec/models/enroll_spec.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Lint/UselessAssignment 2 | require 'rails_helper' 3 | 4 | RSpec.describe Enroll, type: :model do 5 | describe 'validations' do 6 | it 'requires student' do 7 | t1 = FactoryBot.create(:teacher_user) 8 | s1 = FactoryBot.create(:student_user) 9 | c1 = FactoryBot.create(:group_enabled, author: t1) 10 | enroll = Enroll.new(student: nil, course: c1) 11 | enroll.valid? 12 | expect(enroll.valid?).to be_falsy 13 | end 14 | 15 | it 'requires course' do 16 | t1 = FactoryBot.create(:teacher_user) 17 | s1 = FactoryBot.create(:student_user) 18 | c1 = FactoryBot.create(:group_enabled, author: t1) 19 | enroll = Enroll.new(student: s1, course: nil) 20 | enroll.valid? 21 | expect(enroll.valid?).to be_falsy 22 | end 23 | end 24 | 25 | describe 'test associations' do 26 | # testing associations with shoulda matchers 27 | it { should belong_to(:student) } # against User 28 | it { should belong_to(:course) } # against Group 29 | end 30 | 31 | describe 'test instance methods' do 32 | it 'fech current enroll count for student/course pair' do 33 | t1 = FactoryBot.create(:teacher_user) 34 | s1 = FactoryBot.create(:student_user) 35 | c1 = FactoryBot.create(:group_enabled, author: t1) 36 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 37 | total = Enroll.already_enrolled(s1, c1) 38 | expect(total).to eq(1) 39 | end 40 | 41 | it 'fech current missing enroll for student/course pair' do 42 | t1 = FactoryBot.create(:teacher_user) 43 | s1 = FactoryBot.create(:student_user) 44 | c1 = FactoryBot.create(:group_enabled, author: t1) 45 | total = Enroll.already_enrolled(s1, c1) 46 | expect(total).to eq(0) 47 | end 48 | end 49 | end 50 | 51 | # rubocop:enable Lint/UselessAssignment 52 | -------------------------------------------------------------------------------- /spec/models/group_spec.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Lint/UselessAssignment 2 | require 'rails_helper' 3 | RSpec.describe Group, type: :model do 4 | describe 'testing validations' do 5 | it 'requires name' do 6 | group = Group.new(name: '') 7 | group.valid? 8 | expect(group.valid?).to be_falsy 9 | end 10 | 11 | it 'requires description' do 12 | group = Group.new(description: '') 13 | group.valid? 14 | expect(group.valid?).to be_falsy 15 | end 16 | 17 | it 'requires duration' do 18 | group = Group.new(duration: '') 19 | group.valid? 20 | expect(group.valid?).to be_falsy 21 | end 22 | 23 | it 'requires price' do 24 | group = Group.new(price: '') 25 | group.valid? 26 | expect(group.valid?).to be_falsy 27 | end 28 | 29 | it 'requires starting' do 30 | group = Group.new(starting: '') 31 | group.valid? 32 | expect(group.valid?).to be_falsy 33 | end 34 | 35 | it 'requires cover_image' do 36 | group = Group.new(cover_image: '') 37 | group.valid? 38 | expect(group.valid?).to be_falsy 39 | end 40 | end 41 | 42 | describe 'testing association ' do 43 | it { should belong_to(:author) } # against User 44 | it { should have_many(:booked_sessions) } # against Transaction 45 | it { should have_many(:enrolled) } # through Transaction against Users (enrolled_students) 46 | it { should have_many(:enrollments) } # against Enroll 47 | end 48 | 49 | describe 'testing instance scopes' do 50 | it 'it provides the list enabled courses' do 51 | t1 = FactoryBot.create(:teacher_user) 52 | c1 = FactoryBot.create(:group_enabled, author: t1) 53 | total = Group.enabled.to_a 54 | expect(total.count).to eq(1) 55 | end 56 | 57 | it 'it provides the list enabled courses' do 58 | t1 = FactoryBot.create(:teacher_user) 59 | c1 = FactoryBot.create(:group_disabled, author: t1) 60 | total = Group.enabled.to_a 61 | expect(total.count).to_not eq(1) 62 | end 63 | 64 | it 'returns users within the enrolled scope in groups (courses) ' do 65 | t1 = FactoryBot.create(:teacher_user) 66 | s1 = FactoryBot.create(:student_user) 67 | c1 = FactoryBot.create(:group_enabled, author: t1) 68 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 69 | total = Group.enrolled_courses(s1).to_a 70 | expect(total.count).to eq(1) 71 | end 72 | 73 | it 'returns users within the author scope with group authored (group/courses) ' do 74 | t1 = FactoryBot.create(:teacher_user) 75 | t2 = FactoryBot.create(:teacher_user) 76 | c1 = FactoryBot.create(:group_enabled, author: t1) 77 | c1 = FactoryBot.create(:group_enabled, author: t2) 78 | total = Group.authored_courses(t1).to_a 79 | expect(total.count).to eq(1) 80 | end 81 | end 82 | end 83 | # rubocop:enable Lint/UselessAssignment 84 | -------------------------------------------------------------------------------- /spec/models/transaction_spec.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Lint/UselessAssignment,Layout/LineLength 2 | require 'rails_helper' 3 | 4 | RSpec.describe Transaction, type: :model do 5 | describe 'testing validations' do 6 | it 'requires booking_type' do 7 | b = Transaction.new(booking_type: '') 8 | b.valid? 9 | expect(b.valid?).to be_falsy 10 | end 11 | 12 | it 'requires minutes' do 13 | b = Transaction.new(minutes: '') 14 | b.valid? 15 | expect(b.valid?).to be_falsy 16 | end 17 | 18 | it 'requires status' do 19 | b = Transaction.new(status: '') 20 | b.valid? 21 | expect(b.valid?).to be_falsy 22 | end 23 | 24 | it 'validates presence for billable course / billable student' do 25 | t = FactoryBot.create(:teacher_user) 26 | c = FactoryBot.create(:group_enabled, author: t) 27 | 28 | b = Transaction.new(teacher_id: t, sitting_student_id: '', course_taught_id: '', minutes: 15, status: 0, booking_type: 0) 29 | b.valid? 30 | expect(b.valid?).to be_falsy 31 | end 32 | end 33 | 34 | describe 'test associations' do 35 | # testing associations with shoulda matchers 36 | it { should belong_to(:teacher) } # against User 37 | 38 | # since course_taught and Sitting_student are optional:true 39 | # association validation will fail. This is compensated by the 40 | # special presence validation on course_taught and Sitting_student 41 | # in the validation describe. 42 | end 43 | 44 | describe 'testing model scopes' do 45 | it 'it fetches not_billable transactions' do 46 | t = FactoryBot.create(:teacher_user) 47 | s = FactoryBot.create(:student_user) 48 | c = FactoryBot.create(:group_enabled, author: t) 49 | e = FactoryBot.create(:enroll, student: s, course: c) 50 | b = FactoryBot.create(:non_billable, sitting_student_id: s.id, course_taught_id: c.id) 51 | total = Transaction.not_billable.to_a 52 | expect(total.count).to eq(1) 53 | end 54 | 55 | it 'it fetches transactions order by most recent' do 56 | t = FactoryBot.create(:teacher_user) 57 | s = FactoryBot.create(:student_user) 58 | c = FactoryBot.create(:group_enabled, author: t) 59 | e = FactoryBot.create(:enroll, student: s, course: c) 60 | b1 = FactoryBot.create(:non_billable, sitting_student_id: s.id, course_taught_id: c.id) 61 | b2 = FactoryBot.create(:non_billable, sitting_student_id: s.id, course_taught_id: c.id, minutes: 99) 62 | 63 | total = Transaction.not_billable.last 64 | expect(total.minutes).to eq(99) 65 | end 66 | end 67 | 68 | describe 'testing model instance methods' do 69 | it 'fetches the list of billable transactions (bookings) for a teacher' do 70 | t1 = FactoryBot.create(:teacher_user) 71 | s1 = FactoryBot.create(:student_user) 72 | c1 = FactoryBot.create(:group_enabled, author: t1) 73 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 74 | b1 = FactoryBot.create(:billable, sitting_student_id: s1.id, course_taught_id: c1.id) 75 | b1 = FactoryBot.create(:billable, sitting_student_id: s1.id, course_taught_id: c1.id) 76 | b1 = FactoryBot.create(:billable, sitting_student_id: s1.id, course_taught_id: c1.id) 77 | total = Transaction.billable(t1).to_a 78 | expect(total.count).to eq(3) 79 | end 80 | 81 | it 'fetches the list of billable transactions (bookings) for a student' do 82 | t1 = FactoryBot.create(:teacher_user) 83 | s1 = FactoryBot.create(:student_user) 84 | s2 = FactoryBot.create(:student_user) 85 | c1 = FactoryBot.create(:group_enabled, author: t1) 86 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 87 | e1 = FactoryBot.create(:enroll, student: s2, course: c1) 88 | b1 = FactoryBot.create(:billable, sitting_student_id: s1.id, course_taught_id: c1.id) 89 | b2 = FactoryBot.create(:billable, sitting_student_id: s1.id, course_taught_id: c1.id) 90 | b3 = FactoryBot.create(:billable, sitting_student_id: s2.id, course_taught_id: c1.id) 91 | total = Transaction.student_billable(s1).to_a 92 | expect(total.count).to eq(2) 93 | end 94 | end 95 | end 96 | 97 | # rubocop:enable Lint/UselessAssignment,Layout/LineLength 98 | -------------------------------------------------------------------------------- /spec/models/user_spec.rb: -------------------------------------------------------------------------------- 1 | # rubocop:disable Lint/UselessAssignment 2 | require 'rails_helper' 3 | 4 | RSpec.describe User, type: :model do 5 | describe 'validations' do 6 | it 'requires name' do 7 | user = User.new(name: '') 8 | user.valid? 9 | expect(user.valid?).to be_falsy 10 | end 11 | 12 | it 'requires role' do 13 | user = User.new(role: '') 14 | user.valid? 15 | expect(user.valid?).to be_falsy 16 | end 17 | 18 | it 'requires email' do 19 | user = User.new(email: '') 20 | user.valid? 21 | expect(user.valid?).to be_falsy 22 | end 23 | 24 | it 'requires valid email' do 25 | user = User.new(email: 'dasdasd') 26 | user.valid? 27 | expect(user.valid?).to be_falsy 28 | end 29 | 30 | it 'has many enrolled courses' do 31 | t1 = FactoryBot.create(:teacher_user) 32 | s1 = FactoryBot.create(:student_user) 33 | c1 = FactoryBot.create(:group_enabled, author: t1) 34 | c2 = FactoryBot.create(:group_enabled, author: t1) 35 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 36 | e2 = FactoryBot.create(:enroll, student: s1, course: c2) 37 | expect(e2.valid?).to_not be_falsy 38 | end 39 | 40 | it 'has many authored groups (courses)' do 41 | t1 = FactoryBot.create(:teacher_user) 42 | c1 = FactoryBot.create(:group_enabled, author: t1) 43 | c2 = FactoryBot.create(:group_enabled, author: t1) 44 | expect(c2.valid?).to_not be_falsy 45 | end 46 | 47 | it 'has many transactions ( taught sessions - bookings)' do 48 | t1 = FactoryBot.create(:teacher_user) 49 | s1 = FactoryBot.create(:student_user) 50 | c1 = FactoryBot.create(:group_enabled, author: t1) 51 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 52 | 53 | b1 = FactoryBot.create(:billable, sitting_student: s1, course_taught: c1, teacher: t1) 54 | b2 = FactoryBot.create(:billable, sitting_student: s1, course_taught: c1, teacher: t1) 55 | expect(b2.valid?).to_not be_falsy 56 | end 57 | end 58 | 59 | describe 'testing associations using shoulda-matchers' do 60 | # testing associations with shoulda matchers 61 | it { should have_many(:authored_courses) } # against Group 62 | it { should have_many(:taught_sessions) } # against Transactions 63 | it { should have_many(:enrolled_courses) } # against Enroll 64 | it { should have_many(:my_courses) } # against Transactions 65 | end 66 | 67 | describe 'testing model instance enum and methods ' do 68 | it 'it provides the list of users role: teacher' do 69 | t1 = FactoryBot.create(:teacher_user) 70 | total = User.teacher.to_a 71 | expect(total.count).to eq(1) 72 | end 73 | 74 | it 'it provides the list of users role: student' do 75 | t1 = FactoryBot.create(:student_user) 76 | total = User.teacher.to_a 77 | expect(total.count).to_not eq(1) 78 | end 79 | 80 | it 'capitlizes names before saving' do 81 | t1 = FactoryBot.create(:teacher_user, name: 'pedro') 82 | puts t1.name 83 | expect(t1.name).to eq('Pedro') 84 | end 85 | 86 | it 'responds true if user enrolled in a course' do 87 | t1 = FactoryBot.create(:teacher_user) 88 | s1 = FactoryBot.create(:student_user) 89 | c1 = FactoryBot.create(:group_enabled, author: t1) 90 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 91 | 92 | expect(s1.enrolled(c1)).to be_truthy 93 | end 94 | 95 | it 'fetches the list of users enrolled in one course' do 96 | t1 = FactoryBot.create(:teacher_user) 97 | s1 = FactoryBot.create(:student_user) 98 | s2 = FactoryBot.create(:student_user) 99 | c1 = FactoryBot.create(:group_enabled, author: t1) 100 | c2 = FactoryBot.create(:group_enabled, author: t1) 101 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 102 | e2 = FactoryBot.create(:enroll, student: s2, course: c1) 103 | total = User.enrolled_list(c1).to_a 104 | expect(total.count).to eq(2) 105 | end 106 | 107 | it 'fetches the list of users available for enroll to a couese' do 108 | t1 = FactoryBot.create(:teacher_user) 109 | s1 = FactoryBot.create(:student_user) 110 | s2 = FactoryBot.create(:student_user) 111 | s3 = FactoryBot.create(:student_user) 112 | c1 = FactoryBot.create(:group_enabled, author: t1) 113 | e1 = FactoryBot.create(:enroll, student: s1, course: c1) 114 | total = User.to_enroll(c1).to_a 115 | total.each { |x| puts x.id } 116 | enroll_list = Enroll.all.to_a 117 | enroll_list.each { |x| puts x.student_id } 118 | expect(total.count).to eq(2) 119 | end 120 | end 121 | end 122 | 123 | # rubocop:enable Lint/UselessAssignment 124 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # This file is copied to spec/ when you run 'rails generate rspec:install' 2 | require 'spec_helper' 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require File.expand_path('../config/environment', __dir__) 5 | # Prevent database truncation if the environment is production 6 | abort('The Rails environment is running in production mode!') if Rails.env.production? 7 | require 'rspec/rails' 8 | require 'shoulda/matchers' 9 | require 'capybara' 10 | require 'devise' 11 | # Add additional requires below this line. Rails is not loaded until this point! 12 | 13 | require 'support/database_cleaner' 14 | 15 | # Requires supporting ruby files with custom matchers and macros, etc, in 16 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 17 | # run as spec files by default. This means that files in spec/support that end 18 | # in _spec.rb will both be required and run as specs, causing the specs to be 19 | # run twice. It is recommended that you do not name files matching this glob to 20 | # end with _spec.rb. You can configure this pattern with the --pattern 21 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 22 | # 23 | # The following line is provided for convenience purposes. It has the downside 24 | # of increasing the boot-up time by auto-requiring all files in the support 25 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 26 | # require only the support files necessary. 27 | # 28 | # Dir[Rails.root.join('spec', 'support', '**', '*.rb')].sort.each { |f| require f } 29 | 30 | # Checks for pending migrations and applies them before tests are run. 31 | # If you are not using ActiveRecord, you can remove these lines. 32 | begin 33 | ActiveRecord::Migration.maintain_test_schema! 34 | rescue ActiveRecord::PendingMigrationError => e 35 | puts e.to_s.strip 36 | exit 1 37 | end 38 | RSpec.configure do |config| 39 | # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures 40 | config.fixture_path = "#{::Rails.root}/spec/fixtures" 41 | 42 | # If you're not using ActiveRecord, or you'd prefer not to run each of your 43 | # examples within a transaction, remove the following line or assign false 44 | # instead of true. 45 | config.use_transactional_fixtures = false 46 | 47 | # You can uncomment this line to turn off ActiveRecord support entirely. 48 | # config.use_active_record = false 49 | 50 | Capybara.javascript_driver = :webkit 51 | # RSpec Rails can automatically mix in different behaviours to your tests 52 | # based on their file location, for example enabling you to call `get` and 53 | # `post` in specs under `spec/controllers`. 54 | # 55 | # You can disable this behaviour by removing the line below, and instead 56 | # explicitly tag your specs with their type, e.g.: 57 | # 58 | # RSpec.describe UsersController, type: :controller do 59 | # # ... 60 | # end 61 | # 62 | # The different available types are documented in the features, such as in 63 | # https://relishapp.com/rspec/rspec-rails/docs 64 | config.infer_spec_type_from_file_location! 65 | 66 | # Filter lines from Rails gems in backtraces. 67 | config.filter_rails_from_backtrace! 68 | # arbitrary gems may also be filtered via: 69 | # config.filter_gems_from_backtrace("gem name") 70 | 71 | config.include Devise::Test::IntegrationHelpers, type: :feature # this is used for acceptance tests 72 | config.include Devise::Test::ControllerHelpers, type: :controller 73 | config.include Devise::Test::ControllerHelpers, type: :view 74 | 75 | Shoulda::Matchers.configure do |conf| 76 | conf.integrate do |with| 77 | with.test_framework :rspec 78 | with.library :rails 79 | end 80 | end 81 | 82 | def wait_for_page_load(title) 83 | find('div.rspec_eyes_only', text: title) 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /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 | # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration 16 | RSpec.configure do |config| 17 | # rspec-expectations config goes here. You can use an alternate 18 | # assertion/expectation library such as wrong or the stdlib/minitest 19 | # assertions if you prefer. 20 | config.expect_with :rspec do |expectations| 21 | # This option will default to `true` in RSpec 4. It makes the `description` 22 | # and `failure_message` of custom matchers include text for helper methods 23 | # defined using `chain`, e.g.: 24 | # be_bigger_than(2).and_smaller_than(4).description 25 | # # => "be bigger than 2 and smaller than 4" 26 | # ...rather than: 27 | # # => "be bigger than 2" 28 | expectations.include_chain_clauses_in_custom_matcher_descriptions = true 29 | end 30 | 31 | # rspec-mocks config goes here. You can use an alternate test double 32 | # library (such as bogus or mocha) by changing the `mock_with` option here. 33 | config.mock_with :rspec do |mocks| 34 | # Prevents you from mocking or stubbing a method that does not exist on 35 | # a real object. This is generally recommended, and will default to 36 | # `true` in RSpec 4. 37 | mocks.verify_partial_doubles = true 38 | end 39 | 40 | # This option will default to `:apply_to_host_groups` in RSpec 4 (and will 41 | # have no way to turn it off -- the option exists only for backwards 42 | # compatibility in RSpec 3). It causes shared context metadata to be 43 | # inherited by the metadata hash of host groups and examples, rather than 44 | # triggering implicit auto-inclusion in groups with matching metadata. 45 | config.shared_context_metadata_behavior = :apply_to_host_groups 46 | 47 | # The settings below are suggested to provide a good initial experience 48 | # with RSpec, but feel free to customize to your heart's content. 49 | # # This allows you to limit a spec run to individual examples or groups 50 | # # you care about by tagging them with `:focus` metadata. When nothing 51 | # # is tagged with `:focus`, all examples get run. RSpec also provides 52 | # # aliases for `it`, `describe`, and `context` that include `:focus` 53 | # # metadata: `fit`, `fdescribe` and `fcontext`, respectively. 54 | # config.filter_run_when_matching :focus 55 | # 56 | # # Allows RSpec to persist some state between runs in order to support 57 | # # the `--only-failures` and `--next-failure` CLI options. We recommend 58 | # # you configure your source control system to ignore this file. 59 | # config.example_status_persistence_file_path = "spec/examples.txt" 60 | # 61 | # # Limits the available syntax to the non-monkey patched syntax that is 62 | # # recommended. For more details, see: 63 | # # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ 64 | # # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ 65 | # # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode 66 | # config.disable_monkey_patching! 67 | # 68 | # # Many RSpec users commonly either run the entire suite or an individual 69 | # # file, and it's useful to allow more verbose output when running an 70 | # # individual spec file. 71 | # if config.files_to_run.one? 72 | # # Use the documentation formatter for detailed output, 73 | # # unless a formatter has already been configured 74 | # # (e.g. via a command-line flag). 75 | # config.default_formatter = "doc" 76 | # end 77 | # 78 | # # Print the 10 slowest examples and example groups at the 79 | # # end of the spec run, to help surface which specs are running 80 | # # particularly slow. 81 | # config.profile_examples = 10 82 | # 83 | # # Run specs in random order to surface order dependencies. If you find an 84 | # # order dependency and want to debug it, you can fix the order by providing 85 | # # the seed, which is printed after each run. 86 | # # --seed 1234 87 | # config.order = :random 88 | # 89 | # # Seed global randomization in this process using the `--seed` CLI option. 90 | # # Setting this allows you to use `--seed` to deterministically reproduce 91 | # # test failures related to randomization by passing the same `--seed` value 92 | # # as the one that triggered the failure. 93 | # Kernel.srand config.seed 94 | end 95 | -------------------------------------------------------------------------------- /spec/support/course_page.rb: -------------------------------------------------------------------------------- 1 | class MyCoursePage 2 | include Capybara::DSL 3 | 4 | def visit_page 5 | visit('/groups') 6 | self 7 | end 8 | 9 | def new_course 10 | click_on('Add New Course') 11 | self 12 | end 13 | 14 | def edit_course 15 | find(:css, '.edit-test').click 16 | self 17 | end 18 | 19 | def student_hack_into_new_group_page 20 | visit('/groups/new') 21 | self 22 | end 23 | 24 | def select_student_to_enroll 25 | find('.custom-select').trigger(:click) 26 | all(:css, '.enroll')[0].select('student-99') 27 | click_on('edit') 28 | self 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /spec/support/database_cleaner.rb: -------------------------------------------------------------------------------- 1 | RSpec.configure do |config| 2 | config.before(:suite) do 3 | DatabaseCleaner.clean_with :truncation, except: %w[ar_internal_metadata] 4 | end 5 | 6 | config.before(:each) do 7 | DatabaseCleaner.strategy = :transaction 8 | end 9 | 10 | config.before(:each) do 11 | DatabaseCleaner.start 12 | end 13 | 14 | config.after(:each) do 15 | DatabaseCleaner.clean 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/support/login_form.rb: -------------------------------------------------------------------------------- 1 | class LoginForm 2 | include Capybara::DSL 3 | 4 | def visit_page 5 | visit('/users/sign_in') 6 | self 7 | end 8 | 9 | def login_as(user) 10 | fill_in('Email', with: user.email) 11 | fill_in('Password', with: user.password) 12 | self 13 | end 14 | 15 | def submit 16 | click_on('Log in') 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /spec/support/new_course_form.rb: -------------------------------------------------------------------------------- 1 | class CourseForm 2 | include Capybara::DSL 3 | 4 | def fill_form(_attrs = {}) 5 | fill_in('Course Name', with: 'IELTS') 6 | fill_in('Description', with: 'this is a dummy description for testing purposes only') 7 | select('45', from: 'Duration') 8 | fill_in('Price', with: 'Price') 9 | check('Online Session') 10 | check('Presencial Session') 11 | check('Enable Course') 12 | fill_in('Starting from', with: '05/08/2020') 13 | attach_file('Course Image', "#{Rails.root}/spec/fixtures/ielts_cover_image.jpeg") 14 | self 15 | end 16 | 17 | def fill_form_edit(name) 18 | fill_in('Course Name', with: name) 19 | fill_in('Description', with: 'this is a dummy description for testing purposes only') 20 | select('45', from: 'Duration') 21 | fill_in('Price', with: 'Price') 22 | check('Online Session') 23 | check('Presencial Session') 24 | check('Enable Course') 25 | fill_in('Starting from', with: '05/08/2020') 26 | attach_file('Course Image', "#{Rails.root}/spec/fixtures/ielts_cover_image.jpeg") 27 | self 28 | end 29 | 30 | def submit 31 | click_on('Save Course') 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/storage/.keep -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/canriquez/rails_capstone_lessons_lab/3e563c5ac1855a0a88c95fc3c79f039db2a89061/vendor/.keep --------------------------------------------------------------------------------