├── .browserslistrc ├── .gitignore ├── .tool-versions ├── Gemfile ├── Gemfile.lock ├── Procfile ├── README.md ├── Rakefile ├── app ├── .DS_Store ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ └── stylesheets │ │ └── application.css ├── channels │ ├── application_cable │ │ ├── channel.rb │ │ └── connection.rb │ └── chat_channel.rb ├── controllers │ ├── .DS_Store │ ├── admin_controller.rb │ ├── api │ │ ├── admin │ │ │ ├── admin_controller.rb │ │ │ ├── dashboard_controller.rb │ │ │ └── musicians_controller.rb │ │ ├── api_controller.rb │ │ └── musicians_controller.rb │ ├── application_controller.rb │ └── concerns │ │ └── .keep ├── helpers │ └── application_helper.rb ├── javascript │ ├── admin │ │ ├── routes.js │ │ ├── stores │ │ │ ├── dashboard_store.js │ │ │ └── musician_store.js │ │ └── views │ │ │ ├── dashboard │ │ │ └── index.vue │ │ │ ├── musicians │ │ │ ├── _filters.vue │ │ │ ├── _form.vue │ │ │ ├── edit.vue │ │ │ ├── index.vue │ │ │ └── new.vue │ │ │ ├── shared │ │ │ ├── _errors.vue │ │ │ ├── _footer.vue │ │ │ ├── _nav.vue │ │ │ ├── _pagination.vue │ │ │ └── layout.vue │ │ │ └── websockets │ │ │ └── index.vue │ ├── entrypoints │ │ ├── admin.js │ │ └── front.js │ ├── front │ │ ├── routes.js │ │ ├── stores │ │ │ └── musician_store.js │ │ └── views │ │ │ ├── musicians │ │ │ ├── index.vue │ │ │ └── show.vue │ │ │ ├── pages │ │ │ └── index.vue │ │ │ └── shared │ │ │ ├── _footer.vue │ │ │ ├── _nav.vue │ │ │ └── layout.vue │ └── plugins │ │ ├── api.js │ │ └── cable.js ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ └── .keep │ ├── musician.rb │ └── user.rb └── views │ ├── .DS_Store │ ├── admin.html.erb │ ├── api │ ├── .DS_Store │ ├── admin │ │ ├── musicians │ │ │ ├── edit.json.jbuilder │ │ │ ├── index.json.jbuilder │ │ │ └── new.json.jbuilder │ │ ├── shared │ │ │ └── _pagination.json.jbuilder │ │ └── users │ │ │ ├── edit.json.jbuilder │ │ │ ├── index.json.jbuilder │ │ │ └── new.json.jbuilder │ └── musicians │ │ ├── index.json.jbuilder │ │ └── show.json.jbuilder │ ├── application.html.erb │ ├── devise │ └── sessions │ │ └── new.html.erb │ └── layouts │ ├── devise.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb ├── bin ├── bootsnap ├── bundle ├── foreman ├── importmap ├── irb ├── nokogiri ├── puma ├── pumactl ├── racc ├── rackup ├── rails ├── rake ├── rdbg ├── setup ├── spring ├── sprockets ├── thor ├── tilt └── vite ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── importmap.rb ├── initializers │ ├── assets.rb │ ├── content_security_policy.rb │ ├── cypress_rails_initializer.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── locales.rb │ ├── new_framework_defaults_7_1.rb │ └── permissions_policy.rb ├── locales │ ├── devise.en.yml │ ├── en.yml │ └── fr.yml ├── puma.rb ├── routes.rb ├── storage.yml └── vite.json ├── cypress.config.js ├── cypress ├── .DS_Store ├── e2e │ └── home.cy.js └── support │ └── e2e.js ├── db ├── .DS_Store ├── migrate │ ├── 20220424120800_base_setup.rb │ ├── 20240102193807_add_service_name_to_active_storage_blobs.active_storage.rb │ ├── 20240102193808_create_active_storage_variant_records.active_storage.rb │ └── 20240102193809_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb ├── schema.rb └── seeds.rb ├── docker-compose.yml ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── package.json ├── public ├── .DS_Store ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── css │ └── development │ │ ├── admin.css │ │ ├── admin │ │ └── forms.css │ │ ├── devise.css │ │ ├── front.css │ │ ├── grid.css │ │ ├── main.css │ │ ├── themes │ │ ├── dark.css │ │ └── light.css │ │ └── utilities.css ├── favicon.ico ├── robots.txt └── vite-test │ ├── assets │ ├── admin-ebb17751.js │ ├── front-f6acb49a.js │ └── vue-i18n-6b73e0ca.js │ ├── manifest-assets.json │ └── manifest.json ├── storage └── .keep ├── test ├── .DS_Store ├── application_system_test_case.rb ├── controllers │ └── .keep ├── fixtures │ └── files │ │ └── .keep ├── helpers │ └── .keep ├── javascript │ └── app.test.js ├── mailers │ └── .keep ├── models │ └── .keep ├── system │ └── .keep └── test_helper.rb ├── tmp └── .keep ├── vendor ├── .keep └── javascript │ └── .keep ├── vite.config.ts └── yarn.lock /.browserslistrc: -------------------------------------------------------------------------------- 1 | defaults 2 | -------------------------------------------------------------------------------- /.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 | /db/*.sqlite3-* 14 | 15 | # Ignore all logfiles and tempfiles. 16 | /log/* 17 | /tmp/* 18 | !/log/.keep 19 | !/tmp/.keep 20 | 21 | # Ignore uploaded files in development. 22 | /storage/* 23 | !/storage/.keep 24 | 25 | /public/assets 26 | .byebug_history 27 | 28 | # Ignore master key for decrypting credentials and more. 29 | /config/master.key 30 | 31 | # Ignore Redis dump 32 | /dump.rdb 33 | 34 | /public/packs 35 | /public/packs-test 36 | /node_modules 37 | /yarn-error.log 38 | yarn-debug.log* 39 | .yarn-integrity 40 | -------------------------------------------------------------------------------- /.tool-versions: -------------------------------------------------------------------------------- 1 | ruby 3.2.2 2 | nodejs 20.2.0 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" 5 | gem "rails", "~> 7.1.2" 6 | 7 | gem 'sprockets-rails' 8 | gem 'sqlite3' 9 | gem 'puma' 10 | gem 'jbuilder' 11 | gem 'redis' 12 | gem 'bootsnap', require: false 13 | gem 'vite_rails' 14 | gem 'foreman' 15 | gem 'route_translator' 16 | gem 'kaminari' 17 | gem 'ransack' 18 | gem 'devise' 19 | gem 'cypress-rails' 20 | 21 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 22 | gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] 23 | 24 | group :development, :test do 25 | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem 26 | gem "debug", platforms: %i[ mri mingw x64_mingw ] 27 | end 28 | 29 | group :development do 30 | # Use console on exceptions pages [https://github.com/rails/web-console] 31 | gem "web-console" 32 | end 33 | 34 | group :test do 35 | gem 'capybara' 36 | gem 'minitest' 37 | gem 'minitest-rails' 38 | gem 'minitest-spec-rails' 39 | gem 'minitest-focus' 40 | gem 'capybara-email' 41 | gem 'json-schema' 42 | gem 'warden' 43 | gem 'selenium-webdriver' 44 | gem 'webdrivers' 45 | end 46 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (7.1.2) 5 | actionpack (= 7.1.2) 6 | activesupport (= 7.1.2) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | zeitwerk (~> 2.6) 10 | actionmailbox (7.1.2) 11 | actionpack (= 7.1.2) 12 | activejob (= 7.1.2) 13 | activerecord (= 7.1.2) 14 | activestorage (= 7.1.2) 15 | activesupport (= 7.1.2) 16 | mail (>= 2.7.1) 17 | net-imap 18 | net-pop 19 | net-smtp 20 | actionmailer (7.1.2) 21 | actionpack (= 7.1.2) 22 | actionview (= 7.1.2) 23 | activejob (= 7.1.2) 24 | activesupport (= 7.1.2) 25 | mail (~> 2.5, >= 2.5.4) 26 | net-imap 27 | net-pop 28 | net-smtp 29 | rails-dom-testing (~> 2.2) 30 | actionpack (7.1.2) 31 | actionview (= 7.1.2) 32 | activesupport (= 7.1.2) 33 | nokogiri (>= 1.8.5) 34 | racc 35 | rack (>= 2.2.4) 36 | rack-session (>= 1.0.1) 37 | rack-test (>= 0.6.3) 38 | rails-dom-testing (~> 2.2) 39 | rails-html-sanitizer (~> 1.6) 40 | actiontext (7.1.2) 41 | actionpack (= 7.1.2) 42 | activerecord (= 7.1.2) 43 | activestorage (= 7.1.2) 44 | activesupport (= 7.1.2) 45 | globalid (>= 0.6.0) 46 | nokogiri (>= 1.8.5) 47 | actionview (7.1.2) 48 | activesupport (= 7.1.2) 49 | builder (~> 3.1) 50 | erubi (~> 1.11) 51 | rails-dom-testing (~> 2.2) 52 | rails-html-sanitizer (~> 1.6) 53 | activejob (7.1.2) 54 | activesupport (= 7.1.2) 55 | globalid (>= 0.3.6) 56 | activemodel (7.1.2) 57 | activesupport (= 7.1.2) 58 | activerecord (7.1.2) 59 | activemodel (= 7.1.2) 60 | activesupport (= 7.1.2) 61 | timeout (>= 0.4.0) 62 | activestorage (7.1.2) 63 | actionpack (= 7.1.2) 64 | activejob (= 7.1.2) 65 | activerecord (= 7.1.2) 66 | activesupport (= 7.1.2) 67 | marcel (~> 1.0) 68 | activesupport (7.1.2) 69 | base64 70 | bigdecimal 71 | concurrent-ruby (~> 1.0, >= 1.0.2) 72 | connection_pool (>= 2.2.5) 73 | drb 74 | i18n (>= 1.6, < 2) 75 | minitest (>= 5.1) 76 | mutex_m 77 | tzinfo (~> 2.0) 78 | addressable (2.8.6) 79 | public_suffix (>= 2.0.2, < 6.0) 80 | base64 (0.2.0) 81 | bcrypt (3.1.20) 82 | bigdecimal (3.1.5) 83 | bindex (0.8.1) 84 | bootsnap (1.17.0) 85 | msgpack (~> 1.2) 86 | builder (3.2.4) 87 | capybara (3.39.2) 88 | addressable 89 | matrix 90 | mini_mime (>= 0.1.3) 91 | nokogiri (~> 1.8) 92 | rack (>= 1.6.0) 93 | rack-test (>= 0.6.3) 94 | regexp_parser (>= 1.5, < 3.0) 95 | xpath (~> 3.2) 96 | capybara-email (3.0.2) 97 | capybara (>= 2.4, < 4.0) 98 | mail 99 | concurrent-ruby (1.2.2) 100 | connection_pool (2.4.1) 101 | crass (1.0.6) 102 | cypress-rails (0.6.1) 103 | puma (>= 3.8.0) 104 | railties (>= 5.2.0) 105 | date (3.3.4) 106 | debug (1.9.1) 107 | irb (~> 1.10) 108 | reline (>= 0.3.8) 109 | devise (4.9.3) 110 | bcrypt (~> 3.0) 111 | orm_adapter (~> 0.1) 112 | railties (>= 4.1.0) 113 | responders 114 | warden (~> 1.2.3) 115 | drb (2.2.0) 116 | ruby2_keywords 117 | dry-cli (1.0.0) 118 | erubi (1.12.0) 119 | foreman (0.87.2) 120 | globalid (1.2.1) 121 | activesupport (>= 6.1) 122 | i18n (1.14.1) 123 | concurrent-ruby (~> 1.0) 124 | io-console (0.7.1) 125 | irb (1.11.0) 126 | rdoc 127 | reline (>= 0.3.8) 128 | jbuilder (2.11.5) 129 | actionview (>= 5.0.0) 130 | activesupport (>= 5.0.0) 131 | json-schema (4.1.1) 132 | addressable (>= 2.8) 133 | kaminari (1.2.2) 134 | activesupport (>= 4.1.0) 135 | kaminari-actionview (= 1.2.2) 136 | kaminari-activerecord (= 1.2.2) 137 | kaminari-core (= 1.2.2) 138 | kaminari-actionview (1.2.2) 139 | actionview 140 | kaminari-core (= 1.2.2) 141 | kaminari-activerecord (1.2.2) 142 | activerecord 143 | kaminari-core (= 1.2.2) 144 | kaminari-core (1.2.2) 145 | loofah (2.22.0) 146 | crass (~> 1.0.2) 147 | nokogiri (>= 1.12.0) 148 | mail (2.8.1) 149 | mini_mime (>= 0.1.1) 150 | net-imap 151 | net-pop 152 | net-smtp 153 | marcel (1.0.2) 154 | matrix (0.4.2) 155 | mini_mime (1.1.5) 156 | minitest (5.20.0) 157 | minitest-focus (1.4.0) 158 | minitest (>= 4, < 6) 159 | minitest-rails (7.1.0) 160 | minitest (~> 5.20) 161 | railties (~> 7.1.0) 162 | minitest-spec-rails (7.2.0) 163 | minitest (>= 5.0) 164 | railties (>= 4.1) 165 | msgpack (1.7.2) 166 | mutex_m (0.2.0) 167 | net-imap (0.4.9) 168 | date 169 | net-protocol 170 | net-pop (0.1.2) 171 | net-protocol 172 | net-protocol (0.2.2) 173 | timeout 174 | net-smtp (0.4.0) 175 | net-protocol 176 | nio4r (2.7.0) 177 | nokogiri (1.16.0-aarch64-linux) 178 | racc (~> 1.4) 179 | nokogiri (1.16.0-arm64-darwin) 180 | racc (~> 1.4) 181 | nokogiri (1.16.0-x86_64-darwin) 182 | racc (~> 1.4) 183 | nokogiri (1.16.0-x86_64-linux) 184 | racc (~> 1.4) 185 | orm_adapter (0.5.0) 186 | psych (5.1.2) 187 | stringio 188 | public_suffix (5.0.4) 189 | puma (6.4.0) 190 | nio4r (~> 2.0) 191 | racc (1.7.3) 192 | rack (3.0.8) 193 | rack-proxy (0.7.7) 194 | rack 195 | rack-session (2.0.0) 196 | rack (>= 3.0.0) 197 | rack-test (2.1.0) 198 | rack (>= 1.3) 199 | rackup (2.1.0) 200 | rack (>= 3) 201 | webrick (~> 1.8) 202 | rails (7.1.2) 203 | actioncable (= 7.1.2) 204 | actionmailbox (= 7.1.2) 205 | actionmailer (= 7.1.2) 206 | actionpack (= 7.1.2) 207 | actiontext (= 7.1.2) 208 | actionview (= 7.1.2) 209 | activejob (= 7.1.2) 210 | activemodel (= 7.1.2) 211 | activerecord (= 7.1.2) 212 | activestorage (= 7.1.2) 213 | activesupport (= 7.1.2) 214 | bundler (>= 1.15.0) 215 | railties (= 7.1.2) 216 | rails-dom-testing (2.2.0) 217 | activesupport (>= 5.0.0) 218 | minitest 219 | nokogiri (>= 1.6) 220 | rails-html-sanitizer (1.6.0) 221 | loofah (~> 2.21) 222 | nokogiri (~> 1.14) 223 | railties (7.1.2) 224 | actionpack (= 7.1.2) 225 | activesupport (= 7.1.2) 226 | irb 227 | rackup (>= 1.0.0) 228 | rake (>= 12.2) 229 | thor (~> 1.0, >= 1.2.2) 230 | zeitwerk (~> 2.6) 231 | rake (13.1.0) 232 | ransack (4.1.1) 233 | activerecord (>= 6.1.5) 234 | activesupport (>= 6.1.5) 235 | i18n 236 | rdoc (6.6.2) 237 | psych (>= 4.0.0) 238 | redis (5.0.8) 239 | redis-client (>= 0.17.0) 240 | redis-client (0.19.1) 241 | connection_pool 242 | regexp_parser (2.8.3) 243 | reline (0.4.1) 244 | io-console (~> 0.5) 245 | responders (3.1.1) 246 | actionpack (>= 5.2) 247 | railties (>= 5.2) 248 | rexml (3.2.6) 249 | route_translator (14.1.1) 250 | actionpack (>= 6.1, < 7.2) 251 | activesupport (>= 6.1, < 7.2) 252 | ruby2_keywords (0.0.5) 253 | rubyzip (2.3.2) 254 | selenium-webdriver (4.16.0) 255 | rexml (~> 3.2, >= 3.2.5) 256 | rubyzip (>= 1.2.2, < 3.0) 257 | websocket (~> 1.0) 258 | sprockets (4.2.1) 259 | concurrent-ruby (~> 1.0) 260 | rack (>= 2.2.4, < 4) 261 | sprockets-rails (3.4.2) 262 | actionpack (>= 5.2) 263 | activesupport (>= 5.2) 264 | sprockets (>= 3.0.0) 265 | sqlite3 (1.7.0-aarch64-linux) 266 | sqlite3 (1.7.0-arm64-darwin) 267 | sqlite3 (1.7.0-x86_64-darwin) 268 | sqlite3 (1.7.0-x86_64-linux) 269 | stringio (3.1.0) 270 | thor (1.3.0) 271 | timeout (0.4.1) 272 | tzinfo (2.0.6) 273 | concurrent-ruby (~> 1.0) 274 | vite_rails (3.0.17) 275 | railties (>= 5.1, < 8) 276 | vite_ruby (~> 3.0, >= 3.2.2) 277 | vite_ruby (3.5.0) 278 | dry-cli (>= 0.7, < 2) 279 | rack-proxy (~> 0.6, >= 0.6.1) 280 | zeitwerk (~> 2.2) 281 | warden (1.2.9) 282 | rack (>= 2.0.9) 283 | web-console (4.2.1) 284 | actionview (>= 6.0.0) 285 | activemodel (>= 6.0.0) 286 | bindex (>= 0.4.0) 287 | railties (>= 6.0.0) 288 | webdrivers (5.2.0) 289 | nokogiri (~> 1.6) 290 | rubyzip (>= 1.3.0) 291 | selenium-webdriver (~> 4.0) 292 | webrick (1.8.1) 293 | websocket (1.2.10) 294 | websocket-driver (0.7.6) 295 | websocket-extensions (>= 0.1.0) 296 | websocket-extensions (0.1.5) 297 | xpath (3.2.0) 298 | nokogiri (~> 1.8) 299 | zeitwerk (2.6.12) 300 | 301 | PLATFORMS 302 | aarch64-linux 303 | arm64-darwin-21 304 | arm64-darwin-23 305 | x86_64-darwin-20 306 | x86_64-linux 307 | 308 | DEPENDENCIES 309 | bootsnap 310 | capybara 311 | capybara-email 312 | cypress-rails 313 | debug 314 | devise 315 | foreman 316 | jbuilder 317 | json-schema 318 | kaminari 319 | minitest 320 | minitest-focus 321 | minitest-rails 322 | minitest-spec-rails 323 | puma 324 | rails (~> 7.1.2) 325 | ransack 326 | redis 327 | route_translator 328 | selenium-webdriver 329 | sprockets-rails 330 | sqlite3 331 | tzinfo-data 332 | vite_rails 333 | warden 334 | web-console 335 | webdrivers 336 | 337 | BUNDLED WITH 338 | 2.3.12 339 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | vite: bin/vite dev 2 | web: bin/rails s --port 3000 -b 0.0.0.0 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rails + Vite + Vue + Pina Demo App 2 | 3 | ### This repo is no longer maintained 4 | Feel free to grab some ideas, but I will no longer update this codebase. 5 | 6 | ## Description 7 | 8 | Demo Single Page Application based on Ruby on Rails 7.0.2, using Vue 3 + Pina, compiled with Vite. 9 | All the basic features you need to build a real world app with: 10 | 11 | - **Front / Admin namespaces** 12 | - **I18n** (server side + client side) 13 | - **Forms** (with progress and error handling) 14 | - **Authentication** (Devise) 15 | - **Pagination** (Kaminari) 16 | - **Dynamic search filters** (Ransack) 17 | - **Websockets** (ActionCable) 18 | - **Bootstrap like grid** (using CSS Grid layout) 19 | 20 | All of this is designed with maintainability and readability in mind, slightly inspired by the Rails conventions. 21 | 22 | This is the follow up of the previous Rails + Vue + Vuex app from 2017 which is still 23 | available on the Rails6 branch. 24 | 25 | A lot of things has been updated/rewrote for this version, notably: 26 | 27 | - Webpacker is now replaced by Vite 28 | - Vue app in using Composition API 29 | - Vuex is now replaced by Pinia to handle state management 30 | - No longer depends on JQuery for API calls (replaced by Axios) 31 | 32 | Nonetheless, a lot of opinions and conventions from the previous version remain valid, you may have a look at the original blog post for details (https://guillaume.barillot.me/2017/12/02/how-to-organize-your-vue-files-in-a-rails-5-1-project-using-webpack/). 33 | 34 | Boostrapping the plumbing for basic stuff can take some time, but once you get the basic it's 35 | pretty easy to extend and to get really efficient with Vue + Rails. With this example you have 36 | all you need to build up your new project in minutes. 37 | 38 | ## Installation 39 | 40 | ``` 41 | git clone git@github.com:gbarillot/rails-vue-demo-app.git 42 | cd rails-vue-demo-app 43 | bundle install 44 | yarn install 45 | bundle exec rails db:migrate 46 | bundle exec rails db:seed 47 | ``` 48 | 49 | ## Booting the app 50 | 51 | ``` 52 | foreman start 53 | ``` 54 | 55 | Hot Module Reloading (HMR) is enabled by default. If you prefer to turn it off, set "hmr" to false 56 | in /vite.config.ts. 57 | ## Running tests 58 | 59 | ### Rails side 60 | 61 | ``` 62 | rails test 63 | ``` 64 | 65 | ### JS side 66 | 67 | ``` 68 | yarn test 69 | ``` 70 | 71 | ## A note on CSS 72 | CSS is done right in the public/css/development directory. 73 | 74 | Pros: 75 | - No compile time, greatly speed up page load time in dev 76 | - Allows CSS editing + saving directly from Chrome Devtools 77 | - Easier / more fexible to manage imports 78 | 79 | Cons: 80 | - You should disable cache in DevTools in Dev 81 | - As of today, not suitable for production! 82 | 83 | The purpose of this repo is Vue + Vite + Rails, not CSS, so feel free to use whatever method 84 | you'd prefer to handle CSS. Sprocket is left as is and still works. 85 | 86 | ## Contributions 87 | 88 | PR and feedbacks welcome! 89 | 90 | ## Licence 91 | 92 | MIT 93 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/app/.DS_Store -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../../javascript .js 4 | //= link_tree ../../../vendor/javascript .js 5 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, 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 | *= require_self 14 | */ 15 | -------------------------------------------------------------------------------- /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/channels/chat_channel.rb: -------------------------------------------------------------------------------- 1 | class ChatChannel < ApplicationCable::Channel 2 | 3 | def subscribed 4 | stream_from "ChatChannel" 5 | end 6 | 7 | def receive(data) 8 | ActionCable.server.broadcast("ChatChannel", { 9 | message: data['message'].upcase 10 | }) 11 | end 12 | end -------------------------------------------------------------------------------- /app/controllers/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/app/controllers/.DS_Store -------------------------------------------------------------------------------- /app/controllers/admin_controller.rb: -------------------------------------------------------------------------------- 1 | class AdminController < ApplicationController 2 | before_action :auth_user! 3 | 4 | def index 5 | render template: 'admin' 6 | end 7 | 8 | private 9 | 10 | # Proper redirect using I18n.locale 11 | # Devise's authenticate_user! doesn't handle I18n properly here 12 | def auth_user! 13 | redirect_to new_user_session_path unless current_user 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/controllers/api/admin/admin_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::Admin::AdminController < Api::ApiController 2 | before_action :authenticate_user! 3 | 4 | def search_params 5 | params[:q] ||= {} if params[:q] != '' 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/api/admin/dashboard_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::Admin::DashboardController < Api::Admin::AdminController 2 | def index 3 | render json: {metrics: {musicians: Musician.count}} 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/api/admin/musicians_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::Admin::MusiciansController < Api::Admin::AdminController 2 | # DELETE ME: Dummy emulation of a slow network so you can see the UI animation in dev. mode 3 | before_action :slow, only: [:create, :update] 4 | before_action :load_musician, except: [:index, :new, :create] 5 | 6 | def index 7 | @musicians = Musician.ransack(search_params) 8 | .result 9 | .page(params[:page]) 10 | .per(params[:per_page]) 11 | end 12 | 13 | def new 14 | @musician = Musician.new 15 | end 16 | 17 | def create 18 | @musician = Musician.create(musician_params) 19 | 20 | if @musician.errors.empty? 21 | render template: '/api/admin/musicians/edit' 22 | else 23 | render json: {errors: @musician.errors.messages}.to_json, status: 422 24 | end 25 | end 26 | 27 | def edit 28 | end 29 | 30 | def update 31 | if @musician.update(musician_params) 32 | head :ok 33 | else 34 | render json: {errors: @musician.errors.messages}.to_json, status: 422 35 | end 36 | end 37 | 38 | def destroy 39 | if @musician.destroy 40 | head :ok 41 | else 42 | render json: {errors: @musician.errors.messages}.to_json, status: 422 43 | end 44 | end 45 | 46 | private 47 | 48 | def musician_params 49 | params.require(:musician).permit( 50 | :name, 51 | :band 52 | ) 53 | end 54 | 55 | def load_musician 56 | @musician = Musician.find(params[:id]) 57 | end 58 | 59 | def slow 60 | sleep 1 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/controllers/api/api_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::ApiController < ApplicationController 2 | 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/api/musicians_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::MusiciansController < Api::ApiController 2 | 3 | def index 4 | @musicians = Musician.all 5 | end 6 | 7 | def show 8 | if params[:id] == "this-will-trigger-a-500" 9 | # Render a 500 to demonstrate how the front-end handles server side errors 10 | render json: {error: "Internal server error"}, status: 500 11 | elsif params[:id] == "this-will-trigger-a-401" 12 | # Render a 401 to demonstrate how the front-end handles server side errors 13 | render json: {error: "Not authenticated"}, status: 401 14 | else 15 | @musician = Musician.find(params[:id]) 16 | end 17 | end 18 | 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | protect_from_forgery with: :exception 3 | around_action :set_locale_from_url 4 | #rescue_from Exception, with: :render_error 5 | 6 | def index 7 | render template: 'application' 8 | end 9 | 10 | def set_locale 11 | I18n.locale = params[:locale] || session[:locale] || I18n.default_locale 12 | session[:locale] = I18n.locale 13 | end 14 | 15 | def self.default_url_options 16 | { locale: I18n.locale } 17 | end 18 | 19 | def after_sign_in_path_for(resource) 20 | admin_root_path 21 | end 22 | 23 | private 24 | def render_error(e) 25 | if e.class.name == "ActiveRecord::RecordNotFound" 26 | render json: {error: "Not found"}.to_json, status: 404 27 | else 28 | render json: {error: "Internal server error"}.to_json, status: 500 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | 3 | def ui_translations(section) 4 | translations = {current: I18n.t('.')[:vue][section]} 5 | translations.to_json.html_safe 6 | end 7 | 8 | def paginate(scope, default_per_page = scope.default_per_page) 9 | collection = scope.page(params[:page]).per((params[:per_page] || default_per_page).to_i) 10 | current, total, per_page = collection.current_page, collection.total_pages, collection.limit_value 11 | 12 | { 13 | current: current, 14 | previous: (current > 1 ? (current - 1) : nil), 15 | next: (current == total ? nil : (current + 1)), 16 | per_page: per_page, 17 | pages: total, 18 | count: collection.total_count 19 | } 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/javascript/admin/routes.js: -------------------------------------------------------------------------------- 1 | import { createWebHistory, createRouter } from 'vue-router' 2 | 3 | import DashboardIndex from '@/admin/views/dashboard/index.vue'; 4 | import MusicianIndex from '@/admin/views/musicians/index.vue'; 5 | import MusicianNew from '@/admin/views/musicians/new.vue'; 6 | import MusicianEdit from '@/admin/views/musicians/edit.vue'; 7 | import WebsocketIndex from '@/admin/views/websockets/index.vue'; 8 | 9 | const router = createRouter({ 10 | history: createWebHistory(`/${I18n.prefix}admin`), 11 | routes: [ 12 | { path: '/', component: DashboardIndex, name: 'root_path' }, 13 | { path: '/musicians', component: MusicianIndex, name: 'musicians_path' }, 14 | { path: '/musicians/new', component: MusicianNew, name: 'new_musician_path' }, 15 | { path: '/musicians/:id/edit', component: MusicianEdit, name: 'edit_musician_path' }, 16 | { path: '/websockets', component: WebsocketIndex, name: 'websockets_path' } 17 | ] 18 | }); 19 | 20 | // Handles 404 Not found 21 | router.beforeEach((to, from, next) => { 22 | if (!to.matched.length) { 23 | window.location.href = '/404' 24 | } else { 25 | next(); 26 | } 27 | }); 28 | 29 | export default router; -------------------------------------------------------------------------------- /app/javascript/admin/stores/dashboard_store.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const DashboardStore = defineStore('dashboard', { 4 | state: () => { 5 | return { 6 | metrics: {} 7 | } 8 | }, 9 | 10 | actions: { 11 | async index() { 12 | return this.Api.get('/dashboard').then(response => { 13 | this.metrics = response.data.metrics; 14 | }) 15 | } 16 | } 17 | }) -------------------------------------------------------------------------------- /app/javascript/admin/stores/musician_store.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const MusicianStore = defineStore('musicians', { 4 | state: () => { 5 | return { 6 | progress: '', 7 | errors: {}, 8 | bands: [], 9 | musician: {}, 10 | musicians: [], 11 | pagination: {} 12 | } 13 | }, 14 | 15 | actions: { 16 | async index(path) { 17 | return this.Api.get(path).then(response => { 18 | this.pagination = response.data.pagination; 19 | this.bands = response.data.bands; 20 | this.musicians = response.data.musicians; 21 | }) 22 | }, 23 | async new() { 24 | this.errors = {}; 25 | this.musician = {}; 26 | return this.Api.get(`/musicians/new`).then(response => { 27 | this.musician = response.data.musician; 28 | }) 29 | }, 30 | async create() { 31 | this.errors = {}; 32 | this.progress = 'loading'; 33 | return this.Api.post(`/musicians`, this.musician).then(response => { 34 | this.musician = response.data.musician; 35 | return true; 36 | }).catch(error => { 37 | this.errors = error.response.data.errors; 38 | return false; 39 | }).finally(() => { 40 | this.progress = ''; 41 | }) 42 | }, 43 | async edit(id) { 44 | this.errors = {}; 45 | this.musician = {}; 46 | return this.Api.get(`/musicians/${id}/edit`).then(response => { 47 | this.musician = response.data.musician; 48 | }) 49 | }, 50 | async update(id) { 51 | this.errors = {}; 52 | this.progress = 'loading'; 53 | return this.Api.put(`/musicians/${id}`, this.musician).then(response => { 54 | this.errors = {}; 55 | }).catch(error => { 56 | this.errors = error.response.data.errors; 57 | }).finally(() => { 58 | this.progress = ''; 59 | }) 60 | }, 61 | async destroy(id) { 62 | return this.Api.destroy(`/musicians/${id}`).then(response => { 63 | this.errors = {}; 64 | }).catch(error => { 65 | this.errors = error.response.data.errors; 66 | }) 67 | } 68 | } 69 | }) -------------------------------------------------------------------------------- /app/javascript/admin/views/dashboard/index.vue: -------------------------------------------------------------------------------- 1 | 59 | 60 | 68 | -------------------------------------------------------------------------------- /app/javascript/admin/views/musicians/_filters.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 63 | -------------------------------------------------------------------------------- /app/javascript/admin/views/musicians/_form.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 21 | -------------------------------------------------------------------------------- /app/javascript/admin/views/musicians/edit.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 55 | -------------------------------------------------------------------------------- /app/javascript/admin/views/musicians/index.vue: -------------------------------------------------------------------------------- 1 | 42 | 43 | 59 | -------------------------------------------------------------------------------- /app/javascript/admin/views/musicians/new.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 38 | -------------------------------------------------------------------------------- /app/javascript/admin/views/shared/_errors.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 12 | -------------------------------------------------------------------------------- /app/javascript/admin/views/shared/_footer.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 38 | -------------------------------------------------------------------------------- /app/javascript/admin/views/shared/_nav.vue: -------------------------------------------------------------------------------- 1 | 34 | 35 | 57 | -------------------------------------------------------------------------------- /app/javascript/admin/views/shared/_pagination.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 44 | -------------------------------------------------------------------------------- /app/javascript/admin/views/shared/layout.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/javascript/admin/views/websockets/index.vue: -------------------------------------------------------------------------------- 1 | 38 | 39 | 55 | -------------------------------------------------------------------------------- /app/javascript/entrypoints/admin.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | const app = createApp(Layout); 3 | 4 | import Router from '@/admin/routes.js'; 5 | import Layout from '@/admin/views/shared/layout.vue'; 6 | import Axios from "axios"; 7 | 8 | // ActionCable setup 9 | import { createCable } from '@/plugins/cable'; 10 | const Cable = createCable({channel: 'ChatChannel'}) 11 | 12 | // Pinia + Axios setup 13 | import { createApi } from '@/plugins/api'; 14 | import { createPinia } from 'pinia'; 15 | const Pinia = createPinia(); 16 | Pinia.use(({ store }) => { store.Api = createApi({handler: Axios, namespace: '/admin'}) }) 17 | 18 | // I18n loader 19 | import { createI18n } from 'vue-i18n'; 20 | const I18n = createI18n({locale: 'current', messages: translations, legacy: false}); 21 | 22 | app.use(Router) 23 | .use(Pinia) 24 | .use(I18n) 25 | .use(Cable) 26 | .mount('#app') -------------------------------------------------------------------------------- /app/javascript/entrypoints/front.js: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | const app = createApp(Layout); 3 | 4 | import Router from '@/front/routes.js'; 5 | import Layout from '@/front/views/shared/layout.vue'; 6 | import Axios from "axios"; 7 | 8 | // API + Axios wrapper 9 | import { createApi } from '@/plugins/api'; 10 | const Api = createApi({handler: Axios, namespace: ''}); 11 | 12 | // Pinia + Axios setup 13 | import { createPinia } from 'pinia'; 14 | const Pinia = createPinia(); 15 | Pinia.use(({ store }) => { store.Api = Api }) 16 | 17 | // I18n loader 18 | import { createI18n } from 'vue-i18n'; 19 | const I18n = createI18n({locale: 'current', messages: translations, legacy: false}); 20 | 21 | app.use(Router) 22 | .use(Pinia) 23 | .use(I18n) 24 | .mount('#app') -------------------------------------------------------------------------------- /app/javascript/front/routes.js: -------------------------------------------------------------------------------- 1 | import { createWebHistory, createRouter } from 'vue-router' 2 | 3 | import PageIndex from './views/pages/index.vue'; 4 | import MusicianIndex from './views/musicians/index.vue'; 5 | import MusicianShow from './views/musicians/show.vue'; 6 | 7 | const router = createRouter({ 8 | history: createWebHistory(`/${I18n.prefix}`), 9 | routes: [ 10 | { path: '/', component: MusicianIndex, name: 'root_path' }, 11 | { path: '/pages', component: PageIndex, name: 'pages_path' }, 12 | { path: '/musicians', component: MusicianIndex, name: 'musicians_path' }, 13 | { path: '/musicians/:id', component: MusicianShow, name: 'musician_path' }, 14 | ] 15 | }); 16 | 17 | // Handles 404 Not found 18 | router.beforeEach((to, from, next) => { 19 | if (!to.matched.length) { 20 | window.location.href = '/404' 21 | } else { 22 | next(); 23 | } 24 | }); 25 | 26 | export default router; -------------------------------------------------------------------------------- /app/javascript/front/stores/musician_store.js: -------------------------------------------------------------------------------- 1 | import { defineStore } from 'pinia' 2 | 3 | export const MusicianStore = defineStore('musicians', { 4 | state: () => { 5 | return { 6 | musicians: [], 7 | musician: {} 8 | } 9 | }, 10 | 11 | actions: { 12 | async index(path) { 13 | return this.Api.get('/musicians').then(response => { 14 | this.musicians = response.data.musicians; 15 | }) 16 | }, 17 | async show(id) { 18 | return this.Api.get(`/musicians/${id}`).then(response => { 19 | this.musician = response.data.musician; 20 | }) 21 | } 22 | } 23 | }) -------------------------------------------------------------------------------- /app/javascript/front/views/musicians/index.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 25 | -------------------------------------------------------------------------------- /app/javascript/front/views/musicians/show.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | -------------------------------------------------------------------------------- /app/javascript/front/views/pages/index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /app/javascript/front/views/shared/_footer.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 38 | -------------------------------------------------------------------------------- /app/javascript/front/views/shared/_nav.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 49 | -------------------------------------------------------------------------------- /app/javascript/front/views/shared/layout.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/javascript/plugins/api.js: -------------------------------------------------------------------------------- 1 | import Axios from "axios"; 2 | 3 | function Api() { 4 | const get = function (route) { 5 | return Axios.get(route); 6 | }; 7 | const post = function (route, params) { 8 | return Axios.post(route, params); 9 | }; 10 | const put = function (route, params) { 11 | return Axios.put(route, params); 12 | }; 13 | const destroy = function (route) { 14 | return Axios.delete(route); 15 | }; 16 | 17 | return { get, post, put, destroy }; 18 | } 19 | 20 | export function createApi(args) { 21 | args.handler.defaults.baseURL = `/${window.I18n.prefix}api${args.namespace}/`; 22 | args.handler.defaults.headers.common["X-CSRF-Token"] = document 23 | .querySelector('meta[name="csrf-token"]') 24 | .getAttribute("content"); 25 | args.handler.interceptors.response.use( 26 | (response) => { 27 | return response; 28 | }, 29 | (error) => { 30 | switch (error.response.status) { 31 | case 500: 32 | window.location.href = "/500"; 33 | break; 34 | case 401: 35 | alert("not authenticated"); 36 | break; 37 | } 38 | 39 | return Promise.reject(error); 40 | } 41 | ); 42 | 43 | return new Api(); 44 | } 45 | -------------------------------------------------------------------------------- /app/javascript/plugins/cable.js: -------------------------------------------------------------------------------- 1 | import { createConsumer } from "@rails/actioncable" 2 | import mitt from 'mitt'; 3 | 4 | const protocol = window.location.protocol === 'https:' ? 'wss' : 'ws'; 5 | const consumer = createConsumer(`${protocol}://${window.location.host}/cable`); 6 | const emitter = mitt(); 7 | let channel = null; 8 | 9 | function Cable() {} 10 | 11 | Cable.prototype.on = function(channel, callback) { 12 | return emitter.on(channel, callback); 13 | }; 14 | 15 | Cable.prototype.send = function(message) { 16 | channel.send({message: message}) 17 | }; 18 | 19 | Cable.prototype.install = function(app) { 20 | app.plugin = this; 21 | app.provide('cable', this) 22 | } 23 | 24 | export function createCable(options) { 25 | channel = consumer.subscriptions.create({ channel: options.channel}, { 26 | received(data) { 27 | emitter.emit('chat', data) 28 | } 29 | }) 30 | 31 | return new Cable(); 32 | } 33 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /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 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/musician.rb: -------------------------------------------------------------------------------- 1 | class Musician < ApplicationRecord 2 | paginates_per 5 3 | 4 | validates_presence_of :name, :band 5 | 6 | enum band: [:rolling_stones, :beatles, :acdc] 7 | 8 | def self.ransackable_attributes(auth_object = nil) 9 | ["band", "name"] 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | paginates_per 10 3 | 4 | devise :database_authenticatable 5 | 6 | validates_presence_of :email 7 | end 8 | -------------------------------------------------------------------------------- /app/views/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/app/views/.DS_Store -------------------------------------------------------------------------------- /app/views/admin.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | Boilerplate | Admin 4 | 5 | <%= csrf_meta_tags %> 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 22 | 23 | 24 |
25 | <%= vite_javascript_tag 'admin' %> 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/views/api/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/app/views/api/.DS_Store -------------------------------------------------------------------------------- /app/views/api/admin/musicians/edit.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.musician do 2 | json.id @musician.id 3 | json.name @musician.name 4 | json.band @musician.band 5 | json.bands Musician.bands.each do |name, _| 6 | json.key name 7 | json.name t(name, scope: 'bands') 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/api/admin/musicians/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.musicians @musicians.each do |musician| 2 | json.id musician.id 3 | json.created_at l(musician.created_at, format: :default) 4 | json.name musician.name 5 | json.band t(musician.band, scope: 'bands') 6 | end 7 | 8 | json.bands Musician.bands.each do |band, key| 9 | json.key key 10 | json.name t(band, scope: 'bands') 11 | end 12 | 13 | json.partial! partial: '/api/admin/shared/pagination', locals: { 14 | kind: @musicians 15 | } 16 | -------------------------------------------------------------------------------- /app/views/api/admin/musicians/new.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.musician do 2 | json.name @musician.name 3 | json.bands Musician.bands.each do |name, _| 4 | json.key name 5 | json.name t(name, scope: 'bands') 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/api/admin/shared/_pagination.json.jbuilder: -------------------------------------------------------------------------------- 1 | obj = paginate(kind) 2 | json.pagination do 3 | json.current obj[:current] 4 | json.previous obj[:previous] 5 | json.next obj[:next] 6 | json.per_page obj[:per_page] 7 | json.pages obj[:pages] 8 | json.count obj[:count] 9 | end 10 | -------------------------------------------------------------------------------- /app/views/api/admin/users/edit.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.user do 2 | json.id @user.id 3 | json.email @user.email 4 | end 5 | -------------------------------------------------------------------------------- /app/views/api/admin/users/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.users @users.each do |user| 2 | json.id user.id 3 | json.created_at l(user.created_at, format: :default) 4 | json.email user.email 5 | end 6 | 7 | json.partial! partial: '/api/admin/shared/pagination', locals: { 8 | kind: @users, 9 | callback: 'UserStore/index' 10 | } 11 | -------------------------------------------------------------------------------- /app/views/api/admin/users/new.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.user do 2 | json.email @user.email 3 | end 4 | -------------------------------------------------------------------------------- /app/views/api/musicians/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.musicians @musicians.each do |musician| 2 | json.id musician.id 3 | json.created_at l(musician.created_at, format: :default) 4 | json.name musician.name 5 | json.band t(musician.band, scope: 'bands') 6 | end 7 | -------------------------------------------------------------------------------- /app/views/api/musicians/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.musician do 2 | json.id @musician.id 3 | json.name @musician.name 4 | json.band t(@musician.band, scope: 'bands') 5 | end 6 | -------------------------------------------------------------------------------- /app/views/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | Boilerplate 4 | 5 | <%= csrf_meta_tags %> 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 22 | 23 | 24 |
25 | 26 | <%= vite_javascript_tag 'front' %> 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 5 |
6 |

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

7 | <%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %> 8 |
9 | <%= f.label :email %> 10 | <%= f.email_field :email, value: "admin@domain.com", autofocus: true, class: "uk-input" %> 11 |
12 | 13 |
14 | <%= f.label :password %> 15 | <%= f.password_field :password, value: "password", autocomplete: "off", class: "uk-input" %> 16 |
17 | 18 |
19 | <%= f.submit t('devise.sign_in'), class: "button button-primary" %> 20 |
21 | <% end %> 22 |
23 | 24 |
25 |
26 |
27 | -------------------------------------------------------------------------------- /app/views/layouts/devise.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | Boilerplate 4 | <%= csrf_meta_tags %> 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | <%= yield %> 13 | 14 | 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bin/bootsnap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bootsnap' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("bootsnap", "bootsnap") 28 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || cli_arg_version || 66 | bundler_requirement_for(lockfile_version) 67 | end 68 | 69 | def bundler_requirement_for(version) 70 | return "#{Gem::Requirement.default}.a" unless version 71 | 72 | bundler_gem_version = Gem::Version.new(version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /bin/foreman: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'foreman' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("foreman", "foreman") 28 | -------------------------------------------------------------------------------- /bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /bin/irb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'irb' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("irb", "irb") 28 | -------------------------------------------------------------------------------- /bin/nokogiri: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'nokogiri' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("nokogiri", "nokogiri") 28 | -------------------------------------------------------------------------------- /bin/puma: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'puma' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("puma", "puma") 28 | -------------------------------------------------------------------------------- /bin/pumactl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'pumactl' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("puma", "pumactl") 28 | -------------------------------------------------------------------------------- /bin/racc: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'racc' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("racc", "racc") 28 | -------------------------------------------------------------------------------- /bin/rackup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rackup' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("rack", "rackup") 28 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/rdbg: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'rdbg' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("debug", "rdbg") 28 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args, exception: true) 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'spring' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("spring", "spring") 28 | -------------------------------------------------------------------------------- /bin/sprockets: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'sprockets' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("sprockets", "sprockets") 28 | -------------------------------------------------------------------------------- /bin/thor: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'thor' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("thor", "thor") 28 | -------------------------------------------------------------------------------- /bin/tilt: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'tilt' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("tilt", "tilt") 28 | -------------------------------------------------------------------------------- /bin/vite: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'vite' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 12 | 13 | bundle_binstub = File.expand_path("bundle", __dir__) 14 | 15 | if File.file?(bundle_binstub) 16 | if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/ 17 | load(bundle_binstub) 18 | else 19 | abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run. 20 | Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.") 21 | end 22 | end 23 | 24 | require "rubygems" 25 | require "bundler/setup" 26 | 27 | load Gem.bin_path("vite_ruby", "vite") 28 | -------------------------------------------------------------------------------- /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 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative "boot" 2 | 3 | require "rails/all" 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module Next 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 7.0 13 | 14 | # Please, add to the `ignore` list any other `lib` subdirectories that do 15 | # not contain `.rb` files, or that should not be reloaded or eager loaded. 16 | # Common ones are `templates`, `generators`, or `middleware`, for example. 17 | config.autoload_lib(ignore: %w(assets tasks)) 18 | 19 | # Configuration for the application, engines, and railties goes here. 20 | # 21 | # These settings can be overridden in specific environments using the files 22 | # in config/environments, which are processed later. 23 | # 24 | # config.time_zone = "Central Time (US & Canada)" 25 | # config.eager_load_paths << Rails.root.join("extras") 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: redis 3 | url: redis://<%= ENV["REDIS_URL"] || 'localhost' %>:6379/1 4 | 5 | test: 6 | adapter: test 7 | 8 | production: 9 | adapter: redis 10 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 11 | channel_prefix: next_production 12 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | z50kuuEHj1CEUcKRpJWPamB52/q1tUa818uvcrvMpkdqctOJIuFJlK1CSQvI9roMpaEdrydKRM2yubgNJX5C4Bj5qsI5uW3O77bkjiSbVjjSrMOCE4iogYxF2LS9aJ3OPny29Dgx3BzAtDR9GN6FiBytcvOjYIUNTYZrbqYFLFW2zjAeb8NElTqBdjePEHWitR5813D8e4VzQqLsaM/fcbiiwXs48YyFU1vN9uPCpL+Ljn54vyFmrAox1sbjtxYIr97YNsQNvouGdpt92zzkhckcnXQ6OWv1J/thPHzGbtJO5Ej9XmtlWnUWTFAI713C7SptEmyky/Q8OdO7ueKx16dZWucPAGoLm2lFst7OvVkWLM0r6sxGM4UrJLwG9IZhwt9UdxUyCd0VM/w9w+1bSdaUGsMAWnheTeLe--LR73FGyHeOp0ATJ0--+C76DQyMTGNE1y/Y8/G92Q== -------------------------------------------------------------------------------- /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 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 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 any time 7 | # it changes. 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.enable_reloading = true 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 server timing 18 | config.server_timing = true 19 | 20 | # Enable/disable caching. By default caching is disabled. 21 | # Run rails dev:cache to toggle caching. 22 | if Rails.root.join("tmp/caching-dev.txt").exist? 23 | config.action_controller.perform_caching = true 24 | config.action_controller.enable_fragment_cache_logging = true 25 | 26 | config.cache_store = :memory_store 27 | config.public_file_server.headers = { 28 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 29 | } 30 | else 31 | config.action_controller.perform_caching = false 32 | 33 | config.cache_store = :null_store 34 | end 35 | 36 | # Store uploaded files on the local file system (see config/storage.yml for options). 37 | config.active_storage.service = :local 38 | 39 | # Don't care if the mailer can't send. 40 | config.action_mailer.raise_delivery_errors = false 41 | 42 | config.action_mailer.perform_caching = false 43 | 44 | # Print deprecation notices to the Rails logger. 45 | config.active_support.deprecation = :log 46 | 47 | # Raise exceptions for disallowed deprecations. 48 | config.active_support.disallowed_deprecation = :raise 49 | 50 | # Tell Active Support which deprecation messages to disallow. 51 | config.active_support.disallowed_deprecation_warnings = [] 52 | 53 | # Raise an error on page load if there are pending migrations. 54 | config.active_record.migration_error = :page_load 55 | 56 | # Highlight code that triggered database queries in logs. 57 | config.active_record.verbose_query_logs = true 58 | 59 | # Highlight code that enqueued background job in logs. 60 | config.active_job.verbose_enqueue_logs = true 61 | 62 | # Suppress logger output for asset requests. 63 | config.assets.quiet = true 64 | 65 | # Raises error for missing translations. 66 | # config.i18n.raise_on_missing_translations = true 67 | 68 | # Annotate rendered view with file names. 69 | # config.action_view.annotate_rendered_view_with_filenames = true 70 | 71 | # Uncomment if you wish to allow Action Cable access from any origin. 72 | # config.action_cable.disable_request_forgery_protection = true 73 | 74 | # Raise error when a before_action's only/except options reference missing actions 75 | config.action_controller.raise_on_missing_callback_actions = true 76 | end 77 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 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.enable_reloading = false 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 ENV["RAILS_MASTER_KEY"], config/master.key, or an environment 20 | # key such as config/credentials/production.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 `public/`, relying on NGINX/Apache to do so instead. 24 | # config.public_file_server.enabled = false 25 | 26 | # Compress CSS using a preprocessor. 27 | # config.assets.css_compressor = :sass 28 | 29 | # Do not fallback to assets pipeline if a precompiled asset is missed. 30 | config.assets.compile = false 31 | 32 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 33 | # config.asset_host = "http://assets.example.com" 34 | 35 | # Specifies the header that your server uses for sending files. 36 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 37 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 38 | 39 | # Store uploaded files on the local file system (see config/storage.yml for options). 40 | config.active_storage.service = :local 41 | 42 | # Mount Action Cable outside main process or domain. 43 | # config.action_cable.mount_path = nil 44 | # config.action_cable.url = "wss://example.com/cable" 45 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] 46 | 47 | # Assume all access to the app is happening through a SSL-terminating reverse proxy. 48 | # Can be used together with config.force_ssl for Strict-Transport-Security and secure cookies. 49 | # config.assume_ssl = true 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 | # Log to STDOUT by default 55 | config.logger = ActiveSupport::Logger.new(STDOUT) 56 | .tap { |logger| logger.formatter = ::Logger::Formatter.new } 57 | .then { |logger| ActiveSupport::TaggedLogging.new(logger) } 58 | 59 | # Prepend all log lines with the following tags. 60 | config.log_tags = [ :request_id ] 61 | 62 | # Info include generic and useful information about system operation, but avoids logging too much 63 | # information to avoid inadvertent exposure of personally identifiable information (PII). If you 64 | # want to log everything, set the level to "debug". 65 | config.log_level = ENV.fetch("RAILS_LOG_LEVEL", "info") 66 | 67 | # Use a different cache store in production. 68 | # config.cache_store = :mem_cache_store 69 | 70 | # Use a real queuing backend for Active Job (and separate queues per environment). 71 | # config.active_job.queue_adapter = :resque 72 | # config.active_job.queue_name_prefix = "next_production" 73 | 74 | config.action_mailer.perform_caching = false 75 | 76 | # Ignore bad email addresses and do not raise email delivery errors. 77 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 78 | # config.action_mailer.raise_delivery_errors = false 79 | 80 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 81 | # the I18n.default_locale when a translation cannot be found). 82 | config.i18n.fallbacks = true 83 | 84 | # Don't log any deprecations. 85 | config.active_support.report_deprecations = false 86 | 87 | # Do not dump schema after migrations. 88 | config.active_record.dump_schema_after_migration = false 89 | 90 | # Enable DNS rebinding protection and other `Host` header attacks. 91 | # config.hosts = [ 92 | # "example.com", # Allow requests from example.com 93 | # /.*\.example\.com/ # Allow requests from subdomains like `www.example.com` 94 | # ] 95 | # Skip DNS rebinding protection for the default health check endpoint. 96 | # config.host_authorization = { exclude: ->(request) { request.path == "/up" } } 97 | end 98 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # While tests run files are not watched, reloading is not necessary. 12 | config.enable_reloading = false 13 | 14 | # Eager loading loads your entire application. When running a single test locally, 15 | # this is usually not necessary, and can slow down your test suite. However, it's 16 | # recommended that you enable it in continuous integration systems to ensure eager 17 | # loading is working properly before deploying your code. 18 | config.eager_load = ENV["CI"].present? 19 | 20 | # Configure public file server for tests with Cache-Control for performance. 21 | config.public_file_server.enabled = true 22 | config.public_file_server.headers = { 23 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 24 | } 25 | 26 | # Show full error reports and disable caching. 27 | config.consider_all_requests_local = true 28 | config.action_controller.perform_caching = false 29 | config.cache_store = :null_store 30 | 31 | # Render exception templates for rescuable exceptions and raise for other exceptions. 32 | config.action_dispatch.show_exceptions = :rescuable 33 | 34 | # Disable request forgery protection in test environment. 35 | config.action_controller.allow_forgery_protection = false 36 | 37 | # Store uploaded files on the local file system in a temporary directory. 38 | config.active_storage.service = :test 39 | 40 | config.action_mailer.perform_caching = false 41 | 42 | # Tell Action Mailer not to deliver emails to the real world. 43 | # The :test delivery method accumulates sent emails in the 44 | # ActionMailer::Base.deliveries array. 45 | config.action_mailer.delivery_method = :test 46 | 47 | # Print deprecation notices to the stderr. 48 | config.active_support.deprecation = :stderr 49 | 50 | # Raise exceptions for disallowed deprecations. 51 | config.active_support.disallowed_deprecation = :raise 52 | 53 | # Tell Active Support which deprecation messages to disallow. 54 | config.active_support.disallowed_deprecation_warnings = [] 55 | 56 | # Raises error for missing translations. 57 | # config.i18n.raise_on_missing_translations = true 58 | 59 | # Annotate rendered view with file names. 60 | # config.action_view.annotate_rendered_view_with_filenames = true 61 | 62 | # Raise error when a before_action's only/except options reference missing actions 63 | config.action_controller.raise_on_missing_callback_actions = true 64 | end 65 | -------------------------------------------------------------------------------- /config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin "application", preload: true 4 | pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true 5 | pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true 6 | pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true 7 | pin_all_from "app/javascript/controllers", under: "controllers" 8 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # 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 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap, inline scripts, and inline styles. 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src style-src) 22 | # 23 | # # Report violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /config/initializers/cypress_rails_initializer.rb: -------------------------------------------------------------------------------- 1 | return unless Rails.env.test? 2 | #require "./lib/external_service" 3 | 4 | Rails.application.load_tasks unless defined?(Rake::Task) 5 | 6 | CypressRails.hooks.before_server_start do 7 | # Add our fixtures before the resettable transaction is started 8 | Rake::Task["db:fixtures:load"].invoke 9 | end 10 | 11 | CypressRails.hooks.after_server_start do 12 | # Start up external service 13 | #ExternalService.start_service 14 | end 15 | 16 | CypressRails.hooks.after_transaction_start do 17 | # After each transaction, add this compliment (will be rolled back on reset) 18 | #Compliment.create(text: "You are courageous") 19 | end 20 | 21 | CypressRails.hooks.after_state_reset do 22 | # if Compliment.count != 4 23 | # raise "Wait I was expecting exactly 4 compliments!" 24 | # end 25 | end 26 | 27 | CypressRails.hooks.before_server_stop do 28 | # Purge and reload the test database so we don't leave our fixtures in there 29 | Rake::Task["db:test:prepare"].invoke 30 | end -------------------------------------------------------------------------------- /config/initializers/devise.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Assuming you have not yet modified this file, each configuration option below 4 | # is set to its default value. Note that some are commented out while others 5 | # are not: uncommented lines are intended to protect your configuration from 6 | # breaking changes in upgrades (i.e., in the event that future versions of 7 | # Devise change the default values for those options). 8 | # 9 | # Use this hook to configure devise mailer, warden hooks and so forth. 10 | # Many of these configuration options can be set straight in your model. 11 | Devise.setup do |config| 12 | # The secret key used by Devise. Devise uses this key to generate 13 | # random tokens. Changing this key will render invalid all existing 14 | # confirmation, reset password and unlock tokens in the database. 15 | # Devise will use the `secret_key_base` as its `secret_key` 16 | # by default. You can change it below and use your own secret key. 17 | # config.secret_key = 'fe7dbda89d8b5e2b6f07c0b413456bb1ab362afc0bf446ddfe789c0819a46d2f14a779adc845025ac3db2900b0167c7b028b400c4343a1536ae7e1695cef1a66' 18 | 19 | # ==> Controller configuration 20 | # Configure the parent class to the devise controllers. 21 | # config.parent_controller = 'DeviseController' 22 | 23 | # ==> Mailer Configuration 24 | # Configure the e-mail address which will be shown in Devise::Mailer, 25 | # note that it will be overwritten if you use your own mailer class 26 | # with default "from" parameter. 27 | config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' 28 | 29 | # Configure the class responsible to send e-mails. 30 | # config.mailer = 'Devise::Mailer' 31 | 32 | # Configure the parent class responsible to send e-mails. 33 | # config.parent_mailer = 'ActionMailer::Base' 34 | 35 | # ==> ORM configuration 36 | # Load and configure the ORM. Supports :active_record (default) and 37 | # :mongoid (bson_ext recommended) by default. Other ORMs may be 38 | # available as additional gems. 39 | require 'devise/orm/active_record' 40 | 41 | # ==> Configuration for any authentication mechanism 42 | # Configure which keys are used when authenticating a user. The default is 43 | # just :email. You can configure it to use [:username, :subdomain], so for 44 | # authenticating a user, both parameters are required. Remember that those 45 | # parameters are used only when authenticating and not when retrieving from 46 | # session. If you need permissions, you should implement that in a before filter. 47 | # You can also supply a hash where the value is a boolean determining whether 48 | # or not authentication should be aborted when the value is not present. 49 | # config.authentication_keys = [:email] 50 | 51 | # Configure parameters from the request object used for authentication. Each entry 52 | # given should be a request method and it will automatically be passed to the 53 | # find_for_authentication method and considered in your model lookup. For instance, 54 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. 55 | # The same considerations mentioned for authentication_keys also apply to request_keys. 56 | # config.request_keys = [] 57 | 58 | # Configure which authentication keys should be case-insensitive. 59 | # These keys will be downcased upon creating or modifying a user and when used 60 | # to authenticate or find a user. Default is :email. 61 | config.case_insensitive_keys = [:email] 62 | 63 | # Configure which authentication keys should have whitespace stripped. 64 | # These keys will have whitespace before and after removed upon creating or 65 | # modifying a user and when used to authenticate or find a user. Default is :email. 66 | config.strip_whitespace_keys = [:email] 67 | 68 | # Tell if authentication through request.params is enabled. True by default. 69 | # It can be set to an array that will enable params authentication only for the 70 | # given strategies, for example, `config.params_authenticatable = [:database]` will 71 | # enable it only for database (email + password) authentication. 72 | # config.params_authenticatable = true 73 | 74 | # Tell if authentication through HTTP Auth is enabled. False by default. 75 | # It can be set to an array that will enable http authentication only for the 76 | # given strategies, for example, `config.http_authenticatable = [:database]` will 77 | # enable it only for database authentication. 78 | # For API-only applications to support authentication "out-of-the-box", you will likely want to 79 | # enable this with :database unless you are using a custom strategy. 80 | # The supported strategies are: 81 | # :database = Support basic authentication with authentication key + password 82 | # config.http_authenticatable = false 83 | 84 | # If 401 status code should be returned for AJAX requests. True by default. 85 | # config.http_authenticatable_on_xhr = true 86 | 87 | # The realm used in Http Basic Authentication. 'Application' by default. 88 | # config.http_authentication_realm = 'Application' 89 | 90 | # It will change confirmation, password recovery and other workflows 91 | # to behave the same regardless if the e-mail provided was right or wrong. 92 | # Does not affect registerable. 93 | # config.paranoid = true 94 | 95 | # By default Devise will store the user in session. You can skip storage for 96 | # particular strategies by setting this option. 97 | # Notice that if you are skipping storage for all authentication paths, you 98 | # may want to disable generating routes to Devise's sessions controller by 99 | # passing skip: :sessions to `devise_for` in your config/routes.rb 100 | config.skip_session_storage = [:http_auth] 101 | 102 | # By default, Devise cleans up the CSRF token on authentication to 103 | # avoid CSRF token fixation attacks. This means that, when using AJAX 104 | # requests for sign in and sign up, you need to get a new CSRF token 105 | # from the server. You can disable this option at your own risk. 106 | # config.clean_up_csrf_token_on_authentication = true 107 | 108 | # When false, Devise will not attempt to reload routes on eager load. 109 | # This can reduce the time taken to boot the app but if your application 110 | # requires the Devise mappings to be loaded during boot time the application 111 | # won't boot properly. 112 | # config.reload_routes = true 113 | 114 | # ==> Configuration for :database_authenticatable 115 | # For bcrypt, this is the cost for hashing the password and defaults to 12. If 116 | # using other algorithms, it sets how many times you want the password to be hashed. 117 | # The number of stretches used for generating the hashed password are stored 118 | # with the hashed password. This allows you to change the stretches without 119 | # invalidating existing passwords. 120 | # 121 | # Limiting the stretches to just one in testing will increase the performance of 122 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use 123 | # a value less than 10 in other environments. Note that, for bcrypt (the default 124 | # algorithm), the cost increases exponentially with the number of stretches (e.g. 125 | # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). 126 | config.stretches = Rails.env.test? ? 1 : 12 127 | 128 | # Set up a pepper to generate the hashed password. 129 | # config.pepper = '8f57964ea0a55bf7585f2098daf8877ad19337cb2c530ffc776f792f0a13be6648060a3e0696b9652cf748a033fb512df777b2cf36f2423f6a7a4e17181c3c26' 130 | 131 | # Send a notification to the original email when the user's email is changed. 132 | # config.send_email_changed_notification = false 133 | 134 | # Send a notification email when the user's password is changed. 135 | # config.send_password_change_notification = false 136 | 137 | # ==> Configuration for :confirmable 138 | # A period that the user is allowed to access the website even without 139 | # confirming their account. For instance, if set to 2.days, the user will be 140 | # able to access the website for two days without confirming their account, 141 | # access will be blocked just in the third day. 142 | # You can also set it to nil, which will allow the user to access the website 143 | # without confirming their account. 144 | # Default is 0.days, meaning the user cannot access the website without 145 | # confirming their account. 146 | # config.allow_unconfirmed_access_for = 2.days 147 | 148 | # A period that the user is allowed to confirm their account before their 149 | # token becomes invalid. For example, if set to 3.days, the user can confirm 150 | # their account within 3 days after the mail was sent, but on the fourth day 151 | # their account can't be confirmed with the token any more. 152 | # Default is nil, meaning there is no restriction on how long a user can take 153 | # before confirming their account. 154 | # config.confirm_within = 3.days 155 | 156 | # If true, requires any email changes to be confirmed (exactly the same way as 157 | # initial account confirmation) to be applied. Requires additional unconfirmed_email 158 | # db field (see migrations). Until confirmed, new email is stored in 159 | # unconfirmed_email column, and copied to email column on successful confirmation. 160 | config.reconfirmable = true 161 | 162 | # Defines which key will be used when confirming an account 163 | # config.confirmation_keys = [:email] 164 | 165 | # ==> Configuration for :rememberable 166 | # The time the user will be remembered without asking for credentials again. 167 | # config.remember_for = 2.weeks 168 | 169 | # Invalidates all the remember me tokens when the user signs out. 170 | config.expire_all_remember_me_on_sign_out = true 171 | 172 | # If true, extends the user's remember period when remembered via cookie. 173 | # config.extend_remember_period = false 174 | 175 | # Options to be passed to the created cookie. For instance, you can set 176 | # secure: true in order to force SSL only cookies. 177 | # config.rememberable_options = {} 178 | 179 | # ==> Configuration for :validatable 180 | # Range for password length. 181 | config.password_length = 6..128 182 | 183 | # Email regex used to validate email formats. It simply asserts that 184 | # one (and only one) @ exists in the given string. This is mainly 185 | # to give user feedback and not to assert the e-mail validity. 186 | config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ 187 | 188 | # ==> Configuration for :timeoutable 189 | # The time you want to timeout the user session without activity. After this 190 | # time the user will be asked for credentials again. Default is 30 minutes. 191 | # config.timeout_in = 30.minutes 192 | 193 | # ==> Configuration for :lockable 194 | # Defines which strategy will be used to lock an account. 195 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. 196 | # :none = No lock strategy. You should handle locking by yourself. 197 | # config.lock_strategy = :failed_attempts 198 | 199 | # Defines which key will be used when locking and unlocking an account 200 | # config.unlock_keys = [:email] 201 | 202 | # Defines which strategy will be used to unlock an account. 203 | # :email = Sends an unlock link to the user email 204 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) 205 | # :both = Enables both strategies 206 | # :none = No unlock strategy. You should handle unlocking by yourself. 207 | # config.unlock_strategy = :both 208 | 209 | # Number of authentication tries before locking an account if lock_strategy 210 | # is failed attempts. 211 | # config.maximum_attempts = 20 212 | 213 | # Time interval to unlock the account if :time is enabled as unlock_strategy. 214 | # config.unlock_in = 1.hour 215 | 216 | # Warn on the last attempt before the account is locked. 217 | # config.last_attempt_warning = true 218 | 219 | # ==> Configuration for :recoverable 220 | # 221 | # Defines which key will be used when recovering the password for an account 222 | # config.reset_password_keys = [:email] 223 | 224 | # Time interval you can reset your password with a reset password key. 225 | # Don't put a too small interval or your users won't have the time to 226 | # change their passwords. 227 | config.reset_password_within = 6.hours 228 | 229 | # When set to false, does not sign a user in automatically after their password is 230 | # reset. Defaults to true, so a user is signed in automatically after a reset. 231 | # config.sign_in_after_reset_password = true 232 | 233 | # ==> Configuration for :encryptable 234 | # Allow you to use another hashing or encryption algorithm besides bcrypt (default). 235 | # You can use :sha1, :sha512 or algorithms from others authentication tools as 236 | # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 237 | # for default behavior) and :restful_authentication_sha1 (then you should set 238 | # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). 239 | # 240 | # Require the `devise-encryptable` gem when using anything other than bcrypt 241 | # config.encryptor = :sha512 242 | 243 | # ==> Scopes configuration 244 | # Turn scoped views on. Before rendering "sessions/new", it will first check for 245 | # "users/sessions/new". It's turned off by default because it's slower if you 246 | # are using only default views. 247 | # config.scoped_views = false 248 | 249 | # Configure the default scope given to Warden. By default it's the first 250 | # devise role declared in your routes (usually :user). 251 | # config.default_scope = :user 252 | 253 | # Set this configuration to false if you want /users/sign_out to sign out 254 | # only the current scope. By default, Devise signs out all scopes. 255 | # config.sign_out_all_scopes = true 256 | 257 | # ==> Navigation configuration 258 | # Lists the formats that should be treated as navigational. Formats like 259 | # :html, should redirect to the sign in page when the user does not have 260 | # access, but formats like :xml or :json, should return 401. 261 | # 262 | # If you have any extra navigational formats, like :iphone or :mobile, you 263 | # should add them to the navigational formats lists. 264 | # 265 | # The "*/*" below is required to match Internet Explorer requests. 266 | # config.navigational_formats = ['*/*', :html] 267 | 268 | # The default HTTP method used to sign out a resource. Default is :delete. 269 | config.sign_out_via = :get 270 | 271 | # ==> OmniAuth 272 | # Add a new OmniAuth provider. Check the wiki for more information on setting 273 | # up on your models and hooks. 274 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' 275 | 276 | # ==> Warden configuration 277 | # If you want to use other strategies, that are not supported by Devise, or 278 | # change the failure app, you can configure them inside the config.warden block. 279 | # 280 | # config.warden do |manager| 281 | # manager.intercept_401 = false 282 | # manager.default_strategies(scope: :user).unshift :some_external_strategy 283 | # end 284 | 285 | # ==> Mountable engine configurations 286 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine 287 | # is mountable, there are some extra configurations to be taken into account. 288 | # The following options are available, assuming the engine is mounted as: 289 | # 290 | # mount MyEngine, at: '/my_engine' 291 | # 292 | # The router that invoked `devise_for`, in the example above, would be: 293 | # config.router_name = :my_engine 294 | # 295 | # When using OmniAuth, Devise cannot automatically set OmniAuth path, 296 | # so you need to do it manually. For the users scope, it would be: 297 | # config.omniauth_path_prefix = '/my_engine/users/auth' 298 | 299 | # ==> Turbolinks configuration 300 | # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly: 301 | # 302 | # ActiveSupport.on_load(:devise_failure_app) do 303 | # include Turbolinks::Controller 304 | # end 305 | 306 | # ==> Configuration for :registerable 307 | 308 | # When set to false, does not sign a user in automatically after their password is 309 | # changed. Defaults to true, so a user is signed in automatically after changing a password. 310 | # config.sign_in_after_change_password = true 311 | end 312 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be partially matched (e.g. passw matches password) and filtered from the log file. 4 | # Use this to limit dissemination of sensitive information. 5 | # See the ActiveSupport::ParameterFilter documentation for supported notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/locales.rb: -------------------------------------------------------------------------------- 1 | I18n.available_locales = [:en, :fr] -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults_7_1.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file eases your Rails 7.1 framework defaults upgrade. 4 | # 5 | # Uncomment each configuration one by one to switch to the new default. 6 | # Once your application is ready to run with all new defaults, you can remove 7 | # this file and set the `config.load_defaults` to `7.1`. 8 | # 9 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 10 | # https://guides.rubyonrails.org/upgrading_ruby_on_rails.html 11 | 12 | ### 13 | # No longer add autoloaded paths into `$LOAD_PATH`. This means that you won't be able 14 | # to manually require files that are managed by the autoloader, which you shouldn't do anyway. 15 | # 16 | # This will reduce the size of the load path, making `require` faster if you don't use bootsnap, or reduce the size 17 | # of the bootsnap cache if you use it. 18 | #++ 19 | # Rails.application.config.add_autoload_paths_to_load_path = false 20 | 21 | ### 22 | # Remove the default X-Download-Options headers since it is used only by Internet Explorer. 23 | # If you need to support Internet Explorer, add back `"X-Download-Options" => "noopen"`. 24 | #++ 25 | # Rails.application.config.action_dispatch.default_headers = { 26 | # "X-Frame-Options" => "SAMEORIGIN", 27 | # "X-XSS-Protection" => "0", 28 | # "X-Content-Type-Options" => "nosniff", 29 | # "X-Permitted-Cross-Domain-Policies" => "none", 30 | # "Referrer-Policy" => "strict-origin-when-cross-origin" 31 | # } 32 | 33 | ### 34 | # Do not treat an `ActionController::Parameters` instance 35 | # as equal to an equivalent `Hash` by default. 36 | #++ 37 | # Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality = false 38 | 39 | ### 40 | # Active Record Encryption now uses SHA-256 as its hash digest algorithm. 41 | # 42 | # There are 3 scenarios to consider. 43 | # 44 | # 1. If you have data encrypted with previous Rails versions, and you have 45 | # +config.active_support.key_generator_hash_digest_class+ configured as SHA1 (the default 46 | # before Rails 7.0), you need to configure SHA-1 for Active Record Encryption too: 47 | #++ 48 | # Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA1 49 | # 50 | # 2. If you have +config.active_support.key_generator_hash_digest_class+ configured as SHA256 (the new default 51 | # in 7.0), then you need to configure SHA-256 for Active Record Encryption: 52 | #++ 53 | # Rails.application.config.active_record.encryption.hash_digest_class = OpenSSL::Digest::SHA256 54 | # 55 | # 3. If you don't currently have data encrypted with Active Record encryption, you can disable this setting to 56 | # configure the default behavior starting 7.1+: 57 | #++ 58 | # Rails.application.config.active_record.encryption.support_sha1_for_non_deterministic_encryption = false 59 | 60 | ### 61 | # No longer run after_commit callbacks on the first of multiple Active Record 62 | # instances to save changes to the same database row within a transaction. 63 | # Instead, run these callbacks on the instance most likely to have internal 64 | # state which matches what was committed to the database, typically the last 65 | # instance to save. 66 | #++ 67 | # Rails.application.config.active_record.run_commit_callbacks_on_first_saved_instances_in_transaction = false 68 | 69 | ### 70 | # Configures SQLite with a strict strings mode, which disables double-quoted string literals. 71 | # 72 | # SQLite has some quirks around double-quoted string literals. 73 | # It first tries to consider double-quoted strings as identifier names, but if they don't exist 74 | # it then considers them as string literals. Because of this, typos can silently go unnoticed. 75 | # For example, it is possible to create an index for a non existing column. 76 | # See https://www.sqlite.org/quirks.html#double_quoted_string_literals_are_accepted for more details. 77 | #++ 78 | # Rails.application.config.active_record.sqlite3_adapter_strict_strings_by_default = true 79 | 80 | ### 81 | # Disable deprecated singular associations names. 82 | #++ 83 | # Rails.application.config.active_record.allow_deprecated_singular_associations_name = false 84 | 85 | ### 86 | # Enable the Active Job `BigDecimal` argument serializer, which guarantees 87 | # roundtripping. Without this serializer, some queue adapters may serialize 88 | # `BigDecimal` arguments as simple (non-roundtrippable) strings. 89 | # 90 | # When deploying an application with multiple replicas, old (pre-Rails 7.1) 91 | # replicas will not be able to deserialize `BigDecimal` arguments from this 92 | # serializer. Therefore, this setting should only be enabled after all replicas 93 | # have been successfully upgraded to Rails 7.1. 94 | #++ 95 | # Rails.application.config.active_job.use_big_decimal_serializer = true 96 | 97 | ### 98 | # Specify if an `ArgumentError` should be raised if `Rails.cache` `fetch` or 99 | # `write` are given an invalid `expires_at` or `expires_in` time. 100 | # Options are `true`, and `false`. If `false`, the exception will be reported 101 | # as `handled` and logged instead. 102 | #++ 103 | # Rails.application.config.active_support.raise_on_invalid_cache_expiration_time = true 104 | 105 | ### 106 | # Specify whether Query Logs will format tags using the SQLCommenter format 107 | # (https://open-telemetry.github.io/opentelemetry-sqlcommenter/), or using the legacy format. 108 | # Options are `:legacy` and `:sqlcommenter`. 109 | #++ 110 | # Rails.application.config.active_record.query_log_tags_format = :sqlcommenter 111 | 112 | ### 113 | # Specify the default serializer used by `MessageEncryptor` and `MessageVerifier` 114 | # instances. 115 | # 116 | # The legacy default is `:marshal`, which is a potential vector for 117 | # deserialization attacks in cases where a message signing secret has been 118 | # leaked. 119 | # 120 | # In Rails 7.1, the new default is `:json_allow_marshal` which serializes and 121 | # deserializes with `ActiveSupport::JSON`, but can fall back to deserializing 122 | # with `Marshal` so that legacy messages can still be read. 123 | # 124 | # In Rails 7.2, the default will become `:json` which serializes and 125 | # deserializes with `ActiveSupport::JSON` only. 126 | # 127 | # Alternatively, you can choose `:message_pack` or `:message_pack_allow_marshal`, 128 | # which serialize with `ActiveSupport::MessagePack`. `ActiveSupport::MessagePack` 129 | # can roundtrip some Ruby types that are not supported by JSON, and may provide 130 | # improved performance, but it requires the `msgpack` gem. 131 | # 132 | # For more information, see 133 | # https://guides.rubyonrails.org/v7.1/configuring.html#config-active-support-message-serializer 134 | # 135 | # If you are performing a rolling deploy of a Rails 7.1 upgrade, wherein servers 136 | # that have not yet been upgraded must be able to read messages from upgraded 137 | # servers, first deploy without changing the serializer, then set the serializer 138 | # in a subsequent deploy. 139 | #++ 140 | # Rails.application.config.active_support.message_serializer = :json_allow_marshal 141 | 142 | ### 143 | # Enable a performance optimization that serializes message data and metadata 144 | # together. This changes the message format, so messages serialized this way 145 | # cannot be read by older versions of Rails. However, messages that use the old 146 | # format can still be read, regardless of whether this optimization is enabled. 147 | # 148 | # To perform a rolling deploy of a Rails 7.1 upgrade, wherein servers that have 149 | # not yet been upgraded must be able to read messages from upgraded servers, 150 | # leave this optimization off on the first deploy, then enable it on a 151 | # subsequent deploy. 152 | #++ 153 | # Rails.application.config.active_support.use_message_serializer_for_metadata = true 154 | 155 | ### 156 | # Set the maximum size for Rails log files. 157 | # 158 | # `config.load_defaults 7.1` does not set this value for environments other than 159 | # development and test. 160 | #++ 161 | # if Rails.env.local? 162 | # Rails.application.config.log_file_size = 100 * 1024 * 1024 163 | # end 164 | 165 | ### 166 | # Enable raising on assignment to attr_readonly attributes. The previous 167 | # behavior would allow assignment but silently not persist changes to the 168 | # database. 169 | #++ 170 | # Rails.application.config.active_record.raise_on_assign_to_attr_readonly = true 171 | 172 | ### 173 | # Enable validating only parent-related columns for presence when the parent is mandatory. 174 | # The previous behavior was to validate the presence of the parent record, which performed an extra query 175 | # to get the parent every time the child record was updated, even when parent has not changed. 176 | #++ 177 | # Rails.application.config.active_record.belongs_to_required_validates_foreign_key = false 178 | 179 | ### 180 | # Enable precompilation of `config.filter_parameters`. Precompilation can 181 | # improve filtering performance, depending on the quantity and types of filters. 182 | #++ 183 | # Rails.application.config.precompile_filter_parameters = true 184 | 185 | ### 186 | # Enable before_committed! callbacks on all enrolled records in a transaction. 187 | # The previous behavior was to only run the callbacks on the first copy of a record 188 | # if there were multiple copies of the same record enrolled in the transaction. 189 | #++ 190 | # Rails.application.config.active_record.before_committed_on_all_records = true 191 | 192 | ### 193 | # Disable automatic column serialization into YAML. 194 | # To keep the historic behavior, you can set it to `YAML`, however it is 195 | # recommended to explicitly define the serialization method for each column 196 | # rather than to rely on a global default. 197 | #++ 198 | # Rails.application.config.active_record.default_column_serializer = nil 199 | 200 | ### 201 | # Enable a performance optimization that serializes Active Record models 202 | # in a faster and more compact way. 203 | # 204 | # To perform a rolling deploy of a Rails 7.1 upgrade, wherein servers that have 205 | # not yet been upgraded must be able to read caches from upgraded servers, 206 | # leave this optimization off on the first deploy, then enable it on a 207 | # subsequent deploy. 208 | #++ 209 | # Rails.application.config.active_record.marshalling_format_version = 7.1 210 | 211 | ### 212 | # Run `after_commit` and `after_*_commit` callbacks in the order they are defined in a model. 213 | # This matches the behaviour of all other callbacks. 214 | # In previous versions of Rails, they ran in the inverse order. 215 | #++ 216 | # Rails.application.config.active_record.run_after_transaction_callbacks_in_order_defined = true 217 | 218 | ### 219 | # Whether a `transaction` block is committed or rolled back when exited via `return`, `break` or `throw`. 220 | #++ 221 | # Rails.application.config.active_record.commit_transaction_on_non_local_return = true 222 | 223 | ### 224 | # Controls when to generate a value for has_secure_token declarations. 225 | #++ 226 | # Rails.application.config.active_record.generate_secure_token_on = :initialize 227 | 228 | ### 229 | # ** Please read carefully, this must be configured in config/application.rb ** 230 | # 231 | # Change the format of the cache entry. 232 | # 233 | # Changing this default means that all new cache entries added to the cache 234 | # will have a different format that is not supported by Rails 7.0 235 | # applications. 236 | # 237 | # Only change this value after your application is fully deployed to Rails 7.1 238 | # and you have no plans to rollback. 239 | # When you're ready to change format, add this to `config/application.rb` (NOT 240 | # this file): 241 | # config.active_support.cache_format_version = 7.1 242 | 243 | 244 | ### 245 | # Configure Action View to use HTML5 standards-compliant sanitizers when they are supported on your 246 | # platform. 247 | # 248 | # `Rails::HTML::Sanitizer.best_supported_vendor` will cause Action View to use HTML5-compliant 249 | # sanitizers if they are supported, else fall back to HTML4 sanitizers. 250 | # 251 | # In previous versions of Rails, Action View always used `Rails::HTML4::Sanitizer` as its vendor. 252 | #++ 253 | # Rails.application.config.action_view.sanitizer_vendor = Rails::HTML::Sanitizer.best_supported_vendor 254 | 255 | 256 | ### 257 | # Configure Action Text to use an HTML5 standards-compliant sanitizer when it is supported on your 258 | # platform. 259 | # 260 | # `Rails::HTML::Sanitizer.best_supported_vendor` will cause Action Text to use HTML5-compliant 261 | # sanitizers if they are supported, else fall back to HTML4 sanitizers. 262 | # 263 | # In previous versions of Rails, Action Text always used `Rails::HTML4::Sanitizer` as its vendor. 264 | #++ 265 | # Rails.application.config.action_text.sanitizer_vendor = Rails::HTML::Sanitizer.best_supported_vendor 266 | 267 | 268 | ### 269 | # Configure the log level used by the DebugExceptions middleware when logging 270 | # uncaught exceptions during requests. 271 | #++ 272 | # Rails.application.config.action_dispatch.debug_exception_log_level = :error 273 | 274 | 275 | ### 276 | # Configure the test helpers in Action View, Action Dispatch, and rails-dom-testing to use HTML5 277 | # parsers. 278 | # 279 | # Nokogiri::HTML5 isn't supported on JRuby, so JRuby applications must set this to :html4. 280 | # 281 | # In previous versions of Rails, these test helpers always used an HTML4 parser. 282 | #++ 283 | # Rails.application.config.dom_testing_default_html_version = :html5 284 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide HTTP permissions policy. For further 4 | # information see: https://developers.google.com/web/updates/2018/06/feature-policy 5 | 6 | # Rails.application.config.permissions_policy do |policy| 7 | # policy.camera :none 8 | # policy.gyroscope :none 9 | # policy.microphone :none 10 | # policy.usb :none 11 | # policy.fullscreen :self 12 | # policy.payment :self, "https://secure.example.com" 13 | # end 14 | -------------------------------------------------------------------------------- /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 confirm link to confirm your new email address." 46 | updated: "Your account has been updated successfully." 47 | sessions: 48 | signed_in: "Signed in successfully." 49 | signed_out: "Signed out successfully." 50 | already_signed_out: "Signed out successfully." 51 | unlocks: 52 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 53 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 54 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 55 | errors: 56 | messages: 57 | already_confirmed: "was already confirmed, please try signing in" 58 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 59 | expired: "has expired, please request a new one" 60 | not_found: "not found" 61 | not_locked: "was not locked" 62 | not_saved: 63 | one: "1 error prohibited this %{resource} from being saved:" 64 | other: "%{count} errors prohibited this %{resource} from being saved:" 65 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | time: 3 | formats: 4 | default: "%B %d %Y %H:%M" 5 | bands: 6 | rolling_stones: "The Rolling Stones" 7 | beatles: "The Beatles" 8 | acdc: "AC/DC" 9 | devise: 10 | sign_in: "Sign in" 11 | vue: 12 | front: 13 | title: "Front end" 14 | nav: 15 | homepage: "Home" 16 | pages: "Pages" 17 | home: 18 | title: "Musicians" 19 | breadcrumb: "All musicians" 20 | pages: 21 | title: "Pages" 22 | server_404: "This link triggers a Server side routing 404" 23 | client_404: "This link triggers a Client side routing 404" 24 | server_401: "This link performs an API call that responds a 401" 25 | server_500: "This link performs an API call that responds a 500" 26 | admin_link: "» Wanna go to the admin?" 27 | musicians: 28 | title: "Details about this musician" 29 | id: "Id" 30 | name: "Name" 31 | band: "Band" 32 | errors: 33 | title: "Error pages" 34 | admin: 35 | title: "Admin" 36 | save: "Save" 37 | cancel: "Cancel" 38 | filter: "Filter" 39 | reset_filters: "Reset filters" 40 | delete: "Delete" 41 | no_result: "No result :-/" 42 | any: "Any" 43 | nav: 44 | dashboard: "Dashboard" 45 | musicians: "Musicians" 46 | websockets: "Websockets" 47 | logout: "Logout" 48 | dashboard: 49 | title: "Dashboard" 50 | musicians: "No musician | 1 musician | musicians" 51 | musicians: 52 | create: "+ Add a new musician" 53 | new: "New musician" 54 | form: 55 | id: "Id" 56 | name: "Name" 57 | band: "Band" 58 | comment: "This whole CRUD section is deliberatly slowed down so you can actually see the animations. Comment out the \"slow\" method in Api::Admin::MusiciansController to use the app at full speed" 59 | confirm: "Are you sure?" 60 | websockets: 61 | placeholder: "Type your message..." 62 | publish: "Publish" 63 | comment1: "You can also publish straight out from the Rails console:" 64 | # Beware of string containing: {} @ $ | 65 | # See: https://vue-i18n.intlify.dev/guide/essentials/syntax#special-characters 66 | code_example: "{'ActionCable.server.broadcast(\"ChatChannel\", { message: \"hey!\" })'}" 67 | comment2: "All messages are upcased" 68 | server_side: " server side. " 69 | comment3: "If you open up multiple tabs, you messsages will be upcased in each of these tabs" 70 | waiting_messages: "Waiting for messages..." 71 | activerecord: 72 | attributes: 73 | user: 74 | email: "Email address" 75 | password: "Password" 76 | errors: 77 | models: 78 | musician: 79 | attributes: 80 | name: 81 | blank: "Mandatory field" 82 | band: 83 | blank: "Please select a band" -------------------------------------------------------------------------------- /config/locales/fr.yml: -------------------------------------------------------------------------------- 1 | fr: 2 | time: 3 | formats: 4 | default: "%B %d %Y %H:%M" 5 | bands: 6 | rolling_stones: "The Rolling Stones" 7 | beatles: "The Beatles" 8 | acdc: "AC/DC" 9 | devise: 10 | sign_in: "Authentification" 11 | vue: 12 | front: 13 | title: "Front end" 14 | nav: 15 | homepage: "Accueil" 16 | pages: "Pages" 17 | home: 18 | title: "Musiciens" 19 | breadcrumb: "Tous les musiciens" 20 | pages: 21 | title: "Pages" 22 | server_404: "Ce lien provoque une 404 venant du routage serveur" 23 | client_404: "Ce lien provoque un 404 venant du routage côté client" 24 | server_401: "Ce lien appelle l'API qui lui répond une 401" 25 | server_500: "Ce lien appelle l'API qui lui répond une 500" 26 | admin_link: "» Envie de voir l'admin ?" 27 | musicians: 28 | title: "Details concernant ce musicien" 29 | id: "Id" 30 | name: "Nom" 31 | band: "Groupe" 32 | errors: 33 | title: "Pages d'erreurs" 34 | admin: 35 | title: "Admin" 36 | save: "Enregistrer" 37 | cancel: "Annuler" 38 | filter: "Filtrer" 39 | reset_filters: "Reseter les filtres" 40 | delete: "Supprimer" 41 | no_result: "Aucun resultat :-/" 42 | any: "Tous" 43 | nav: 44 | dashboard: "Dashboard" 45 | musicians: "Musiciens" 46 | websockets: "Websockets" 47 | logout: "Déconnection" 48 | dashboard: 49 | title: "Dashboard" 50 | musicians: "Aucun musician | Un musicien | musiciens" 51 | musicians: 52 | create: "+ Ajouter un musicien" 53 | new: "Nouveau musicien" 54 | form: 55 | id: "Id" 56 | name: "Nom" 57 | band: "Groupe" 58 | comment: "This whole CRUD section is deliberatly slowed down so you can actually see the animations. Comment out the \"slow\" method in Api::Admin::MusiciansController to use the app at full speed" 59 | confirm: "Etes-vous sur ?" 60 | websockets: 61 | placeholder: "Tapez un message..." 62 | publish: "Publier" 63 | comment1: "Vous pouvez aussi publier un message depuis la console Rails :" 64 | # Beware of string containing: {} @ $ | 65 | # See: https://vue-i18n.intlify.dev/guide/essentials/syntax#special-characters 66 | code_example: "{'ActionCable.server.broadcast(\"ChatChannel\", { message: \"hey!\" })'}" 67 | comment2: "Tous les messages que vous publiez sont passés en majuscule" 68 | server_side: " côté serveur. " 69 | comment3: "Si vous ouvrez plusieurs onglets, les messages seront en majuscules sur chacun d'eux." 70 | waiting_messages: "En attente de messages..." 71 | activerecord: 72 | attributes: 73 | user: 74 | email: "Adresse Email" 75 | password: "Mot de passe" 76 | errors: 77 | models: 78 | musician: 79 | attributes: 80 | name: 81 | blank: "Saisie requise" 82 | band: 83 | blank: "Merci de sélectionner un groupe" -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 12 | # terminating a worker in development environments. 13 | # 14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 15 | 16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 17 | # 18 | port ENV.fetch("PORT") { 3000 } 19 | 20 | # Specifies the `environment` that Puma will run in. 21 | # 22 | environment ENV.fetch("RAILS_ENV") { "development" } 23 | 24 | # Specifies the `pidfile` that Puma will use. 25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 26 | 27 | # Specifies the number of `workers` to boot in clustered mode. 28 | # Workers are forked web server processes. If using threads and workers together 29 | # the concurrency of the application would be max `threads` * `workers`. 30 | # Workers do not work on JRuby or Windows (both of which do not support 31 | # processes). 32 | # 33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 34 | 35 | # Use the `preload_app!` method when specifying a `workers` number. 36 | # This directive tells Puma to first boot the application and load code 37 | # before forking the application. This takes advantage of Copy On Write 38 | # process behavior so workers use less memory. 39 | # 40 | # preload_app! 41 | 42 | # Allow puma to be restarted by `bin/rails restart` command. 43 | plugin :tmp_restart 44 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | mount ActionCable.server => '/cable' 3 | 4 | localized do 5 | devise_for :users, only: [:sessions] 6 | 7 | namespace :api, :defaults => { :format => 'json' } do 8 | resources :musicians, only: [:index, :show] 9 | 10 | namespace :admin do 11 | resources :dashboard, only: :index 12 | resources :musicians, except: :show 13 | end 14 | end 15 | 16 | get '/admin', to: 'admin#index', as: 'admin_root' 17 | match "/admin/*path", to: "admin#index", format: false, via: :get 18 | 19 | root :to => "application#index" 20 | match "*path", to: "application#index", format: false, via: :get 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /config/vite.json: -------------------------------------------------------------------------------- 1 | { 2 | "all": { 3 | "sourceCodeDir": "app/javascript", 4 | "watchAdditionalPaths": [] 5 | }, 6 | "development": { 7 | "autoBuild": true, 8 | "publicOutputDir": "vite-dev", 9 | "port": 3036, 10 | "host": "0.0.0.0" 11 | }, 12 | "test": { 13 | "autoBuild": true, 14 | "publicOutputDir": "vite-test", 15 | "port": 3037 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /cypress.config.js: -------------------------------------------------------------------------------- 1 | const { defineConfig } = require('cypress') 2 | 3 | module.exports = defineConfig({ 4 | // setupNodeEvents can be defined in either 5 | // the e2e or component configuration 6 | e2e: { 7 | experimentalStudio: true, 8 | setupNodeEvents(on, config) { 9 | on('before:browser:launch', (browser = {}, launchOptions) => { 10 | /* ... */ 11 | }) 12 | }, 13 | }, 14 | screenshotsFolder: "tmp/cypress_screenshots", 15 | videosFolder: "tmp/cypress_videos", 16 | trashAssetsBeforeRuns: false 17 | }) 18 | -------------------------------------------------------------------------------- /cypress/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/cypress/.DS_Store -------------------------------------------------------------------------------- /cypress/e2e/home.cy.js: -------------------------------------------------------------------------------- 1 | describe('Main navigation', () => { 2 | it('passes', () => { 3 | cy.visit('http://localhost:5100') 4 | cy.get('.row > :nth-child(1) > a').click(); 5 | cy.get('.breadcrumb > :nth-child(1) > a').click(); 6 | cy.get('ul > :nth-child(2) > a').click(); 7 | cy.get(':nth-child(1) > a').click(); 8 | cy.get('.row > :nth-child(1) > a').click(); 9 | cy.get('p').click(); 10 | cy.get('p').should('have.text', 'Id: 1Name: John LennonBand: The Beatles'); 11 | cy.get('.active > a').click(); 12 | cy.get('ul > :nth-child(2) > a').click(); 13 | cy.get('section.container > :nth-child(2) > a').should('have.text', 'This link triggers a Server side routing 404'); 14 | cy.get('section.container > :nth-child(2) > a').click(); 15 | cy.get(':nth-child(1) > a').click(); 16 | cy.get('h1').should('have.text', 'Musicians'); 17 | cy.visit('http://localhost:5100/'); 18 | cy.get('select').select('fr'); 19 | cy.get('h1').click(); 20 | cy.get('h1').should('have.text', 'Musiciens'); 21 | cy.get('span').click(); 22 | cy.get('span').should('have.text', 'Tous les musiciens'); 23 | cy.get('select').select('en'); 24 | cy.get('.row > :nth-child(1) > a').click(); 25 | cy.get('.breadcrumb > :nth-child(1) > a').click(); 26 | }) 27 | }) -------------------------------------------------------------------------------- /cypress/support/e2e.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/cypress/support/e2e.js -------------------------------------------------------------------------------- /db/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/db/.DS_Store -------------------------------------------------------------------------------- /db/migrate/20220424120800_base_setup.rb: -------------------------------------------------------------------------------- 1 | class BaseSetup < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :users do |t| 4 | t.timestamps 5 | t.string :email 6 | end 7 | 8 | create_table :musicians do |t| 9 | t.timestamps 10 | t.string :name 11 | t.integer :band 12 | end 13 | 14 | change_table :users do |t| 15 | ## Database authenticatable 16 | t.string :encrypted_password, null: false, default: "" 17 | 18 | ## Recoverable 19 | t.string :reset_password_token 20 | t.datetime :reset_password_sent_at 21 | 22 | ## Rememberable 23 | t.datetime :remember_created_at 24 | 25 | ## Trackable 26 | t.integer :sign_in_count, default: 0, null: false 27 | t.datetime :current_sign_in_at 28 | t.datetime :last_sign_in_at 29 | t.string :current_sign_in_ip 30 | t.string :last_sign_in_ip 31 | end 32 | 33 | add_index :users, :email, unique: true 34 | add_index :users, :reset_password_token, unique: true 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /db/migrate/20240102193807_add_service_name_to_active_storage_blobs.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20190112182829) 2 | class AddServiceNameToActiveStorageBlobs < ActiveRecord::Migration[6.0] 3 | def up 4 | return unless table_exists?(:active_storage_blobs) 5 | 6 | unless column_exists?(:active_storage_blobs, :service_name) 7 | add_column :active_storage_blobs, :service_name, :string 8 | 9 | if configured_service = ActiveStorage::Blob.service.name 10 | ActiveStorage::Blob.unscoped.update_all(service_name: configured_service) 11 | end 12 | 13 | change_column :active_storage_blobs, :service_name, :string, null: false 14 | end 15 | end 16 | 17 | def down 18 | return unless table_exists?(:active_storage_blobs) 19 | 20 | remove_column :active_storage_blobs, :service_name 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /db/migrate/20240102193808_create_active_storage_variant_records.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20191206030411) 2 | class CreateActiveStorageVariantRecords < ActiveRecord::Migration[6.0] 3 | def change 4 | return unless table_exists?(:active_storage_blobs) 5 | 6 | # Use Active Record's configured type for primary key 7 | create_table :active_storage_variant_records, id: primary_key_type, if_not_exists: true do |t| 8 | t.belongs_to :blob, null: false, index: false, type: blobs_primary_key_type 9 | t.string :variation_digest, null: false 10 | 11 | t.index %i[ blob_id variation_digest ], name: "index_active_storage_variant_records_uniqueness", unique: true 12 | t.foreign_key :active_storage_blobs, column: :blob_id 13 | end 14 | end 15 | 16 | private 17 | def primary_key_type 18 | config = Rails.configuration.generators 19 | config.options[config.orm][:primary_key_type] || :primary_key 20 | end 21 | 22 | def blobs_primary_key_type 23 | pkey_name = connection.primary_key(:active_storage_blobs) 24 | pkey_column = connection.columns(:active_storage_blobs).find { |c| c.name == pkey_name } 25 | pkey_column.bigint? ? :bigint : pkey_column.type 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20240102193809_remove_not_null_on_active_storage_blobs_checksum.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20211119233751) 2 | class RemoveNotNullOnActiveStorageBlobsChecksum < ActiveRecord::Migration[6.0] 3 | def change 4 | return unless table_exists?(:active_storage_blobs) 5 | 6 | change_column_null(:active_storage_blobs, :checksum, true) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[7.1].define(version: 2024_01_02_193809) do 14 | create_table "musicians", force: :cascade do |t| 15 | t.datetime "created_at", null: false 16 | t.datetime "updated_at", null: false 17 | t.string "name" 18 | t.integer "band" 19 | end 20 | 21 | create_table "users", force: :cascade do |t| 22 | t.datetime "created_at", null: false 23 | t.datetime "updated_at", null: false 24 | t.string "email" 25 | t.string "encrypted_password", default: "", null: false 26 | t.string "reset_password_token" 27 | t.datetime "reset_password_sent_at" 28 | t.datetime "remember_created_at" 29 | t.integer "sign_in_count", default: 0, null: false 30 | t.datetime "current_sign_in_at" 31 | t.datetime "last_sign_in_at" 32 | t.string "current_sign_in_ip" 33 | t.string "last_sign_in_ip" 34 | t.index ["email"], name: "index_users_on_email", unique: true 35 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | User.create!(email: "admin@domain.com", password: "password", password_confirmation: "password") 2 | Musician.create!(name: "John Lennon", band: "beatles") 3 | Musician.create!(name: "Paul McCartney", band: "beatles") 4 | Musician.create!(name: "Georges Harrison", band: "beatles") 5 | Musician.create!(name: "Ringo Starr", band: "beatles") 6 | Musician.create!(name: "Mick Jagger", band: "rolling_stones") 7 | Musician.create!(name: "Keith Richards", band: "rolling_stones") 8 | Musician.create!(name: "Mick Taylor", band: "rolling_stones") 9 | Musician.create!(name: "Bill Wyman", band: "rolling_stones") 10 | Musician.create!(name: "Charlie Watts", band: "rolling_stones") 11 | Musician.create!(name: "Angus Young", band: "acdc") 12 | Musician.create!(name: "Malcom Young", band: "acdc") 13 | Musician.create!(name: "Bon Scott", band: "acdc") 14 | Musician.create!(name: "Phil Rudd", band: "acdc") 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | 3 | services: 4 | redis: 5 | image: 'redis' 6 | restart: always 7 | command: redis-server 8 | ports: 9 | - 6379:6379 10 | environment: 11 | - ALLOW_EMPTY_PASSWORD=yes 12 | 13 | web: 14 | build: 15 | context: . 16 | image: 'rails-vue-demo-app' 17 | working_dir: /app 18 | command: sh -c "yarn install && rm -rf tmp/pids && export REDIS_URL='redis' && foreman start" 19 | volumes: 20 | - type: bind 21 | source: . 22 | target: /app 23 | ports: 24 | - 3000:3000 25 | - 3036:3036 -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "module", 3 | "devDependencies": { 4 | "cypress": "^13.6.0", 5 | "eslint": "^8.7.0", 6 | "lightningcss-cli": "^1.22.1", 7 | "prettier": "^2.5.1", 8 | "vite": "^5.0.0", 9 | "vite-plugin-ruby": "^5.0.0", 10 | "vue-eslint-parser": "^8.0.0" 11 | }, 12 | "dependencies": { 13 | "@rails/actioncable": "^7.0.2-4", 14 | "@vitejs/plugin-vue": "^2.3.1", 15 | "axios": "^1.6.2", 16 | "lightningcss": "^1.22.1", 17 | "mitt": "^3.0.1", 18 | "pinia": "^2.0.13", 19 | "rollup": "^4.9.5", 20 | "unplugin-auto-import": "^0.17.2", 21 | "vue": "^3.2.33", 22 | "vue-axios": "^3.4.1", 23 | "vue-i18n": "^9.1.9", 24 | "vue-router": "^4.0.14", 25 | "yarn": "^1.22.21" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /public/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/public/.DS_Store -------------------------------------------------------------------------------- /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/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/css/development/admin.css: -------------------------------------------------------------------------------- 1 | @import url("grid.css"); 2 | @import url("themes/light.css"); 3 | @import url("themes/dark.css"); 4 | @import url("main.css"); 5 | @import url("admin/forms.css"); 6 | @import url("utilities.css"); -------------------------------------------------------------------------------- /public/css/development/admin/forms.css: -------------------------------------------------------------------------------- 1 | .loading:not(form) * { 2 | display: none; 3 | } 4 | .loading:not(form) { 5 | width: 100%; 6 | height: 40px; 7 | margin: 100px auto 100px auto; 8 | position: relative; 9 | &::before, &::after{ 10 | content: ''; 11 | position: absolute; 12 | top: 50%; 13 | left: 50%; 14 | width: 4em; 15 | height: 4em; 16 | border-radius: 50%; 17 | transform: translate(-50%, -50%) scale(0); 18 | } 19 | &::before { 20 | background: #2c6ed1; 21 | animation: pulse 2s ease-in-out infinite; 22 | } 23 | &::after { 24 | background: #2c6ed1; 25 | animation: pulse 2s 1s ease-in-out infinite; 26 | } 27 | } 28 | 29 | @keyframes pulse { 30 | 0%, 100%{ 31 | transform: translate(-50%, -50%) scale(0); 32 | opacity: 1; 33 | } 34 | 50%{ 35 | transform: translate(-50%, -50%) scale(1.0); 36 | opacity: 0; 37 | } 38 | } 39 | 40 | form { 41 | position: relative; 42 | z-index: 1; 43 | fieldset { 44 | border: none; 45 | padding: 0; 46 | position: relative; 47 | } 48 | fieldset { 49 | position: relative; 50 | } 51 | label { 52 | color: var(--text-color); 53 | font-weight: bold; 54 | } 55 | input, select { 56 | background-color: var(--body-bg); 57 | color: var(--text-color); 58 | } 59 | .error { 60 | position: absolute; 61 | top: 0; 62 | right: 0; 63 | color: red; 64 | } 65 | &.loading { 66 | display: block; 67 | } 68 | /* Create a screen so the user cannot double submit/interact */ 69 | &.loading:after { 70 | content: " "; 71 | display: block; 72 | position: absolute; 73 | z-index: 2; 74 | top: 0; 75 | bottom: 0; 76 | left: 0; 77 | right: 0; 78 | background-color: rgba(255, 255, 255, 0.3); 79 | } 80 | /* Facebook style spinner: processing */ 81 | &.loading input[type=submit] { 82 | color: transparent; 83 | background-image: url(data:image/gif;utf8;base64,R0lGODlhEAALAPQAAP////////7+/v7+/v7+/v7+/v////7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/v7+/gAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/hpDcmVhdGVkIHdpdGggYWpheGxvYWQuaW5mbwAh+QQJCwAAACwAAAAAEAALAAAFLSAgjmRpnqSgCuLKAq5AEIM4zDVw03ve27ifDgfkEYe04kDIDC5zrtYKRa2WQgAh+QQJCwAAACwAAAAAEAALAAAFJGBhGAVgnqhpHIeRvsDawqns0qeN5+y967tYLyicBYE7EYkYAgAh+QQJCwAAACwAAAAAEAALAAAFNiAgjothLOOIJAkiGgxjpGKiKMkbz7SN6zIawJcDwIK9W/HISxGBzdHTuBNOmcJVCyoUlk7CEAAh+QQJCwAAACwAAAAAEAALAAAFNSAgjqQIRRFUAo3jNGIkSdHqPI8Tz3V55zuaDacDyIQ+YrBH+hWPzJFzOQQaeavWi7oqnVIhACH5BAkLAAAALAAAAAAQAAsAAAUyICCOZGme1rJY5kRRk7hI0mJSVUXJtF3iOl7tltsBZsNfUegjAY3I5sgFY55KqdX1GgIAIfkECQsAAAAsAAAAABAACwAABTcgII5kaZ4kcV2EqLJipmnZhWGXaOOitm2aXQ4g7P2Ct2ER4AMul00kj5g0Al8tADY2y6C+4FIIACH5BAkLAAAALAAAAAAQAAsAAAUvICCOZGme5ERRk6iy7qpyHCVStA3gNa/7txxwlwv2isSacYUc+l4tADQGQ1mvpBAAIfkECQsAAAAsAAAAABAACwAABS8gII5kaZ7kRFGTqLLuqnIcJVK0DeA1r/u3HHCXC/aKxJpxhRz6Xi0ANAZDWa+kEAA7AAAAAAAAAAAA); 84 | background-position: center; 85 | background-repeat: no-repeat; 86 | } 87 | /* Check icon: success */ 88 | &.success input[type=submit] { 89 | background-color: lime !important; 90 | color: transparent; 91 | background-repeat: no-repeat; 92 | background-position: center 16px; 93 | background-size: 25px; 94 | background-image: url(data:image/svg+xml;utf8;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTcgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9InNpLWdseXBoIHNpLWdseXBoLWNoZWNrZWQiPg0KICAgIDxnIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPg0KICAgICAgICA8cGF0aCBkPSJNMy40MzIsNi4xODkgQzMuODI0LDUuNzk4IDQuNDU1LDUuNzk4IDQuODQ3LDYuMTg5IEw2Ljk2OCw4LjMxIEwxMy4xNDcsMi4xMzEgQzEzLjUzMSwxLjc0NyAxNC4xNTcsMS43NTMgMTQuNTQ4LDIuMTQ0IEwxNi42Nyw0LjI2NiBDMTcuMDYsNC42NTcgMTcuMDY2LDUuMjg0IDE2LjY4NCw1LjY2NiBMNy42NjIsMTQuNjg3IEM3LjI3OCwxNS4wNyA2LjY1MSwxNS4wNjQgNi4yNjEsMTQuNjczIEwxLjMxMSw5LjcyMyBDMC45Miw5LjMzMyAwLjkyLDguNyAxLjMxMSw4LjMxIEwzLjQzMiw2LjE4OSBaIiBmaWxsPSIjZmZmIiBjbGFzcz0ic2ktZ2x5cGgtZmlsbCI+PC9wYXRoPg0KICAgIDwvZz4NCjwvc3ZnPg==); 95 | } 96 | /* Cross icon: failure */ 97 | &.failed input[type=submit] { 98 | background-color: red !important; 99 | color: transparent; 100 | background-repeat: no-repeat; 101 | background-position: center; 102 | background-size: 21px; 103 | background-image: url(data:image/svg+xml;utf8;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTcgMTciIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgY2xhc3M9InNpLWdseXBoIHNpLWdseXBoLWRlbGV0ZSI+DQogICAgPGcgc3Ryb2tlPSJub25lIiBzdHJva2Utd2lkdGg9IjEiIGZpbGw9Im5vbmUiIGZpbGwtcnVsZT0iZXZlbm9kZCI+DQogICAgICAgIDxwYXRoIGQ9Ik0xMi41NjYsOCBMMTUuNjExLDQuOTU2IEMxNi4wMzEsNC41MzUgMTYuMDMxLDMuODUzIDE1LjYxMSwzLjQzNCBMMTIuNTY2LDAuMzg5IEMxMi4xNDYsLTAuMDMxIDExLjQ2NCwtMC4wMzEgMTEuMDQzLDAuMzg5IEw3Ljk5OSwzLjQzMyBMNC45NTUsMC4zODkgQzQuNTM0LC0wLjAzMSAzLjg1MiwtMC4wMzEgMy40MzIsMC4zODkgTDAuMzg4LDMuNDM0IEMtMC4wMzQsMy44NTQgLTAuMDM0LDQuNTM2IDAuMzg3LDQuOTU2IEwzLjQzMSw4IEwwLjM4NywxMS4wNDQgQy0wLjAzNCwxMS40NjUgLTAuMDM0LDEyLjE0NyAwLjM4OCwxMi41NjcgTDMuNDMyLDE1LjYxMSBDMy44NTIsMTYuMDMyIDQuNTM0LDE2LjAzMiA0Ljk1NSwxNS42MTEgTDcuOTk5LDEyLjU2NyBMMTEuMDQzLDE1LjYxMSBDMTEuNDY0LDE2LjAzMiAxMi4xNDYsMTYuMDMyIDEyLjU2NiwxNS42MTEgTDE1LjYxMSwxMi41NjcgQzE2LjAzMSwxMi4xNDYgMTYuMDMxLDExLjQ2NCAxNS42MTEsMTEuMDQ0IEwxMi41NjYsOCBMMTIuNTY2LDggWiIgZmlsbD0iI2ZmZmZmZiIgY2xhc3M9InNpLWdseXBoLWZpbGwiPjwvcGF0aD4NCiAgICA8L2c+DQo8L3N2Zz4=); 104 | } 105 | } -------------------------------------------------------------------------------- /public/css/development/devise.css: -------------------------------------------------------------------------------- 1 | .devise { 2 | > .row { 3 | margin-top: 50px; 4 | @media (min-width: 576px) and (max-width: 991px) { 5 | > div { 6 | grid-column: 2/span 10 !important; 7 | } 8 | } 9 | @media (min-width: 992px) { 10 | > div { 11 | grid-column: 4/span 6 !important; 12 | } 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /public/css/development/front.css: -------------------------------------------------------------------------------- 1 | @import url("grid.css"); 2 | @import url("themes/light.css"); 3 | @import url("themes/dark.css"); 4 | @import url("main.css"); 5 | @import url("devise.css"); 6 | @import url("utilities.css"); -------------------------------------------------------------------------------- /public/css/development/grid.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --gutter: 20px; 3 | } 4 | [class^="col-"] { 5 | position: relative; 6 | } 7 | .wrapper { 8 | width: calc(100% - var(--gutter)); 9 | margin: 0 auto; 10 | } 11 | .row { 12 | clear: both; 13 | display: grid; 14 | grid-template-columns: repeat(12,1fr); 15 | grid-gap: var(--gutter); 16 | } 17 | 18 | @media (max-width: 575px) { 19 | :root { 20 | --gutter: 10px; 21 | } 22 | } 23 | 24 | .col-xs-1 { grid-column-start: span 1 } 25 | .col-xs-2 { grid-column-start: span 2 } 26 | .col-xs-3 { grid-column-start: span 3 } 27 | .col-xs-4 { grid-column-start: span 4 } 28 | .col-xs-5 { grid-column-start: span 5 } 29 | .col-xs-6 { grid-column-start: span 6 } 30 | .col-xs-7 { grid-column-start: span 7 } 31 | .col-xs-8 { grid-column-start: span 8 } 32 | .col-xs-9 { grid-column-start: span 9 } 33 | .col-xs-10 { grid-column-start: span 10 } 34 | .col-xs-11 { grid-column-start: span 11 } 35 | .col-xs-12 { grid-column-start: span 12 } 36 | 37 | @media (min-width: 576px) { 38 | .col-sm-1 { grid-column-start: span 1 !important } 39 | .col-sm-2 { grid-column-start: span 2 !important } 40 | .col-sm-3 { grid-column-start: span 3 !important } 41 | .col-sm-4 { grid-column-start: span 4 !important } 42 | .col-sm-5 { grid-column-start: span 5 !important } 43 | .col-sm-6 { grid-column-start: span 6 !important } 44 | .col-sm-7 { grid-column-start: span 7 !important } 45 | .col-sm-8 { grid-column-start: span 8 !important } 46 | .col-sm-9 { grid-column-start: span 9 !important } 47 | .col-sm-10 { grid-column-start: span 10 !important } 48 | .col-sm-11 { grid-column-start: span 11 !important } 49 | .col-sm-12 { grid-column-start: span 12 !important } 50 | } 51 | @media (min-width: 768px) { 52 | .col-md-1 { grid-column-start: span 1 !important } 53 | .col-md-2 { grid-column-start: span 2 !important } 54 | .col-md-3 { grid-column-start: span 3 !important } 55 | .col-md-4 { grid-column-start: span 4 !important } 56 | .col-md-5 { grid-column-start: span 5 !important } 57 | .col-md-6 { grid-column-start: span 6 !important } 58 | .col-md-7 { grid-column-start: span 7 !important } 59 | .col-md-8 { grid-column-start: span 8 !important } 60 | .col-md-9 { grid-column-start: span 9 !important } 61 | .col-md-10 { grid-column-start: span 10 !important } 62 | .col-md-11 { grid-column-start: span 11 !important } 63 | .col-md-12 { grid-column-start: span 12 !important } 64 | } 65 | @media (min-width: 992px) { 66 | .col-lg-1 { grid-column-start: span 1 !important } 67 | .col-lg-2 { grid-column-start: span 2 !important } 68 | .col-lg-3 { grid-column-start: span 3 !important } 69 | .col-lg-4 { grid-column-start: span 4 !important} 70 | .col-lg-5 { grid-column-start: span 5 !important } 71 | .col-lg-6 { grid-column-start: span 6 !important } 72 | .col-lg-7 { grid-column-start: span 7 !important } 73 | .col-lg-8 { grid-column-start: span 8 !important } 74 | .col-lg-9 { grid-column-start: span 9 !important } 75 | .col-lg-10 { grid-column-start: span 10 !important } 76 | .col-lg-11 { grid-column-start: span 11 !important } 77 | .col-lg-12 { grid-column-start: span 12 !important } 78 | } 79 | @media (min-width: 1200px) { 80 | .col-xl-1 { grid-column-start: span 1 !important } 81 | .col-xl-2 { grid-column-start: span 2 !important } 82 | .col-xl-3 { grid-column-start: span 3 !important } 83 | .col-xl-4 { grid-column-start: span 4 !important } 84 | .col-xl-5 { grid-column-start: span 5 !important } 85 | .col-xl-6 { grid-column-start: span 6 !important } 86 | .col-xl-7 { grid-column-start: span 7 !important } 87 | .col-xl-8 { grid-column-start: span 8 !important } 88 | .col-xl-9 { grid-column-start: span 9 !important } 89 | .col-xl-10 { grid-column-start: span 10 !important } 90 | .col-xl-11 { grid-column-start: span 11 !important } 91 | .col-xl-12 { grid-column-start: span 12 !important } 92 | } 93 | @media (min-width: 1400px) { 94 | .col-xxl-1 { grid-column-start: span 1 !important } 95 | .col-xxl-2 { grid-column-start: span 2 !important } 96 | .col-xxl-3 { grid-column-start: span 3 !important } 97 | .col-xxl-4 { grid-column-start: span 4 !important } 98 | .col-xxl-5 { grid-column-start: span 5 !important } 99 | .col-xxl-6 { grid-column-start: span 6 !important } 100 | .col-xxl-7 { grid-column-start: span 7 !important } 101 | .col-xxl-8 { grid-column-start: span 8 !important } 102 | .col-xxl-9 { grid-column-start: span 9 !important } 103 | .col-xxl-10 { grid-column-start: span 10 !important } 104 | .col-xxl-11 { grid-column-start: span 11 !important } 105 | .col-xxl-12 { grid-column-start: span 12 !important } 106 | } 107 | 108 | .visible-xs { display: none; } 109 | .visible-sm { display: none; } 110 | .visible-md { display: none; } 111 | .visible-lg { display: none; } 112 | .visible-xl { display: none; } 113 | .visible-xxl { display: none; } 114 | 115 | @media (min-width: 0) and (max-width: 575px) { 116 | .hidden-xs { display: none; } 117 | .visible-xs { display: block; } 118 | } 119 | @media (min-width: 576px) and (max-width: 767px) { 120 | .hidden-sm { display: none; } 121 | .visible-sm { display: block; } 122 | } 123 | @media (min-width: 768px) and (max-width: 992px) { 124 | .hidden-md { display: none; } 125 | .visible-md { display: block; } 126 | } 127 | @media (min-width: 992px) and (max-width: 1199px) { 128 | .hidden-lg { display: none; } 129 | .visible-lg { display: block; } 130 | } 131 | @media (min-width: 1200px) and (max-width: 1399px) { 132 | .hidden-xl { display: none; } 133 | .visible-xl { display: block; } 134 | } 135 | @media (min-width: 1400px) { 136 | .hidden-xxl { display: none; } 137 | .visible-xxl { display: block; } 138 | } -------------------------------------------------------------------------------- /public/css/development/main.css: -------------------------------------------------------------------------------- 1 | html { 2 | min-width: 375px; 3 | } 4 | body, html { 5 | background-color: var(--body-bg); 6 | } 7 | h1, h2, h3, h4, h5, h6 { 8 | color: var(--title-color); 9 | } 10 | p, span, li, table, tr, td, th, label { 11 | color: var(--text-color); 12 | } 13 | .top-nav { 14 | background-color: var(--accent); 15 | select { 16 | top: 5px; 17 | right: 0; 18 | position: absolute; 19 | max-width: 100px; 20 | } 21 | ul li.active { 22 | border-bottom: 1px solid var(--border-active); 23 | } 24 | } 25 | .filters { 26 | @media(max-width: 992px) { 27 | margin-top: 30px; 28 | } 29 | } 30 | .breadcrumb { 31 | display: inline-block; 32 | height: 40px; 33 | margin: 0 auto 20px auto; 34 | background-color: var(--accent); 35 | li { 36 | list-style-type: none; 37 | display: inline-block; 38 | padding-top: 5px; 39 | color: var(--light-text-color); 40 | } 41 | li:after { 42 | content: '/'; 43 | margin: 0 10px 0 10px; 44 | } 45 | li:last-child { 46 | &:after { 47 | content: ''; 48 | } 49 | } 50 | } 51 | 52 | .card { 53 | background-color: var(--accent); 54 | padding: 20px; 55 | box-shadow: 0px 0px 5px 0px rgba(0,0,0,0.2); 56 | } 57 | 58 | .pagination { 59 | list-style-type: none; 60 | float: right; 61 | padding: 0; 62 | height: 40px; 63 | li { 64 | list-style-type: none; 65 | display: inline-block; 66 | width: 40px; 67 | height: 40px; 68 | line-height: 20px; 69 | text-align: center; 70 | padding: 10px; 71 | margin-left: 5px; 72 | a { 73 | text-decoration: none !important; 74 | } 75 | } 76 | } 77 | 78 | main { 79 | min-height: calc(100vh - 60px); 80 | } 81 | footer { 82 | height: 50px; 83 | text-align: left; 84 | padding-top: 8px; 85 | color: var(--light-text-color); 86 | font-size: 14px; 87 | & a { 88 | color: var(--light-text-color); 89 | text-decoration: underline; 90 | } 91 | @media(max-width: 991px) { 92 | * { 93 | text-align: center !important; 94 | } 95 | } 96 | } -------------------------------------------------------------------------------- /public/css/development/themes/dark.css: -------------------------------------------------------------------------------- 1 | [data-theme="dark"] { 2 | --body-bg: #000; 3 | --title-color: #fff; 4 | --text-color: #ddd; 5 | --light-text-color: #666; 6 | --accent: rgb(37, 45, 52); 7 | --border-active: #fffe; 8 | } 9 | -------------------------------------------------------------------------------- /public/css/development/themes/light.css: -------------------------------------------------------------------------------- 1 | [data-theme="light"] { 2 | --body-bg: #f5f5f5; 3 | --title-color: #333; 4 | --text-color: #666; 5 | --light-text-color: #999; 6 | --accent: #fff; 7 | --border-active: blue; 8 | } 9 | -------------------------------------------------------------------------------- /public/css/development/utilities.css: -------------------------------------------------------------------------------- 1 | .hidden { 2 | display: none; 3 | } 4 | .ta-left { 5 | text-align: left; 6 | } 7 | .ta-center { 8 | text-align: center; 9 | } 10 | .ta-right { 11 | text-align: right; 12 | } 13 | .openable { 14 | position: relative; 15 | padding-left: 20px; 16 | &:before { 17 | width: 10px; 18 | height: 10px; 19 | display: inline-block; 20 | border: solid black; 21 | content: ''; 22 | border-width: 0 1px 1px 0; 23 | transform: rotate(-45deg); 24 | margin-right: 10px; 25 | position: absolute; 26 | top: 7px; 27 | left: 0; 28 | } 29 | &.open:before { 30 | top: 5px; 31 | transform: rotate(45deg); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /public/vite-test/assets/admin-ebb17751.js: -------------------------------------------------------------------------------- 1 | import{d as q,_ as v,o as r,c,a as t,t as i,b as V,w,e as g,v as S,f as F,F as y,r as k,g as p,h as d,i as m,j as x,k as f,l as _,m as I,n as T,E as L,p as C,q as O,s as P,u as E,x as R,A as M,y as B}from"./vue-i18n-6b73e0ca.js";const D=q("dashboard",{state:()=>({metrics:{}}),actions:{async index(){this.axios.get("/dashboard").then(e=>{this.metrics=e.data.metrics})}}}),Q={setup(){return{store:D()}},created(){this.$api.call(this.store.index())}},W={class:"container"},z={class:"row"},J=V('

Rails 7

An awesome server side Framework

Vue 3

SPA without the pain

Pinia

State manager

Vue Router

Flexible

Axios

Handles AJAX requests, no JQuery

Vite

Next generation Frontend tooling

Devise

Simple authentication using regular cookies that just works

',7),G={class:"col-xs-24 col-md-6 card"},H=t("h3",null,"I18n",-1),K={href:"https://kazupon.github.io/vue-i18n/",target:"_blank"},X=V('

ActionCable

Websockets with 2 way bindings

Kaminari

Efficient Pagination

Ransack

ActiveRecord wizardry for dynamic search filters

Bootstrap grid

... but using native CSS Grid layout

',4);function Y(e,s,o,u,h,l){return r(),c("section",W,[t("div",z,[J,t("div",G,[H,t("p",null,[t("a",K,i(e.$tc("dashboard.musicians",0))+", "+i(e.$tc("dashboard.musicians",1))+", "+i(u.store.metrics.musicians+" "+e.$tc("dashboard.musicians",u.store.metrics.musicians)),1)])]),X])])}const Z=v(Q,[["render",Y]]),A=q("musicians",{state:()=>({errors:{},bands:[],musician:{},musicians:[],pagination:{}}),actions:{async index(e){return this.axios.get(e).then(s=>{this.pagination=s.data.pagination,this.bands=s.data.bands,this.musicians=s.data.musicians})},async new(){return this.errors={},this.musician={},this.axios.get("/musicians/new").then(e=>{this.musician=e.data.musician})},async create(){return this.errors={},this.axios.post("/musicians",this.musician).then(e=>(this.musician=e.data.musician,!0)).catch(e=>(this.errors=e.response.data.errors,!1))},async edit(e){return this.errors={},this.musician={},this.axios.get(`/musicians/${e}/edit`).then(s=>{this.musician=s.data.musician})},async update(e){return this.errors={},this.axios.put(`/musicians/${e}`,this.musician).then(s=>!0).catch(s=>(this.errors=s.response.data.errors,!1))},async destroy(e){return this.errors={},this.axios.delete(`/musicians/${e}`).then(s=>!0).catch(s=>(this.errors=s.response.data.errors,!1))}}}),tt={props:["bands"],data:function(){return{form:{name_cont:"",band_eq:null}}},methods:{toggleForm(){this.$refs.filters.classList.toggle("hidden")},search(){const e=Object.fromEntries(Object.entries(this.form).map(s=>[`q[${s[0]}]`,s[1]]));this.$router.push({path:this.$route.path,query:e}),this.$emit("index")},reset(){this.form={name_cont:"",band_eq:null},this.$router.push({path:this.$route.path,query:""})}}},et={class:"row"},st={class:"col-xs-24 col-md-10"},at={class:"col-xs-24 col-md-10"},nt={value:null},it=["value"],ot={class:"row"},rt={class:"col-xs-24 col-md-5"},ct=["value"],lt={class:"col-xs-24 col-md-5"};function ut(e,s,o,u,h,l){return r(),c("section",null,[t("a",{href:"#",onClick:s[0]||(s[0]=w((...n)=>l.toggleForm&&l.toggleForm(...n),["prevent"]))}," "+i(e.$t("filter")),1),t("form",{onSubmit:s[4]||(s[4]=w((...n)=>l.search&&l.search(...n),["prevent"])),ref:"filters","accept-charset":"UTF-8",class:"card hidden"},[t("div",et,[t("div",st,[t("label",null,i(e.$t("musicians.form.name")),1),g(t("input",{type:"text","onUpdate:modelValue":s[1]||(s[1]=n=>e.form.name_cont=n),placeholder:"name"},null,512),[[S,e.form.name_cont]])]),t("div",at,[t("label",null,i(e.$t("musicians.form.band")),1),g(t("select",{"onUpdate:modelValue":s[2]||(s[2]=n=>e.form.band_eq=n)},[t("option",nt,i(e.$t("any")),1),(r(!0),c(y,null,k(o.bands,n=>(r(),c("option",{key:n.key,value:n.key},i(n.name),9,it))),128))],512),[[F,e.form.band_eq]])])]),t("div",ot,[t("div",rt,[t("input",{type:"submit",role:"button",class:"button button-primary",value:e.$t("filter")},null,8,ct)]),t("div",lt,[t("a",{onClick:s[3]||(s[3]=(...n)=>l.reset&&l.reset(...n)),href:"#",role:"button",class:"secondary outline fill"},i(e.$t("reset_filters")),1)])])],544)])}const dt=v(tt,[["render",ut]]),ht={components:{Filters:dt},setup(){return{store:A()}},mounted(){this.index()},methods:{index(){this.$api.call(this.store.index(this.$route.fullPath),this.$refs.listing)}}},mt={class:"container"},_t={class:"row"},pt={class:"col-xs-12"},ft={class:"breadcrumb"},vt={class:"col-xs-12 ta-right"},$t={ref:"listing"},bt={key:0},gt={key:1},yt={class:"card ta-center"},kt=t("p",null,'This whole CRUD section is deliberatly slowed down so you can actually see the animations. Comment out the "slow" method in Api::Admin::MusiciansController to use the app at full speed',-1);function wt(e,s,o,u,h,l){const n=p("router-link"),a=p("filters"),$=p("pagination");return r(),c("section",mt,[t("div",_t,[t("div",pt,[t("ul",ft,[t("li",null,[d(n,{to:{name:"root_path"}},{default:m(()=>[_(i(e.$t("title")),1)]),_:1})]),t("li",null,i(e.$t("nav.musicians")),1)])]),t("div",vt,[d(n,{to:{name:"new_musician_path"},role:"button",class:"outline"},{default:m(()=>[_(i(e.$t("musicians.create")),1)]),_:1})])]),d(a,{bands:u.store.bands},null,8,["bands"]),t("div",$t,[u.store.musicians&&u.store.musicians.length>0?(r(),c("table",bt,[t("thead",null,[t("tr",null,[t("th",null,i(e.$t("musicians.form.id")),1),t("th",null,i(e.$t("musicians.form.name")),1),t("th",null,i(e.$t("musicians.form.band")),1)])]),t("tbody",null,[(r(!0),c(y,null,k(u.store.musicians,b=>(r(),c("tr",{key:b.id},[t("td",null,[d(n,{to:{name:"edit_musician_path",params:{id:b.id}}},{default:m(()=>[_(i(b.id),1)]),_:2},1032,["to"])]),t("td",null,[d(n,{to:{name:"edit_musician_path",params:{id:b.id}}},{default:m(()=>[_(i(b.name),1)]),_:2},1032,["to"])]),t("td",null,[d(n,{to:{name:"edit_musician_path",params:{id:b.id}}},{default:m(()=>[_(i(b.band),1)]),_:2},1032,["to"])])]))),128))])])):(r(),c("div",gt,[t("h3",yt,i(e.$t("no_result")),1)])),u.store.pagination?(r(),x($,{key:2,store:u.store,onClicked:l.index},null,8,["store","onClicked"])):f("",!0)],512),kt])}const xt=v(ht,[["render",wt]]),Ct={props:["attr","messages"],data(){return{message:""}},watch:{messages:function(){this.message="",this.messages[this.attr]&&(this.message=this.messages[this.attr].join(","))}}},St={key:0,class:"error"};function Ft(e,s,o,u,h,l){return h.message!=""?(r(),c("span",St,i(h.message),1)):f("",!0)}const At=v(Ct,[["render",Ft]]),qt={props:["data"],components:{Errors:At}},Vt=["aria-invalid"],Mt=["aria-invalid"],Ut=["value"];function Nt(e,s,o,u,h,l){const n=p("errors");return r(),c("section",null,[t("fieldset",null,[t("label",null,i(e.$t("musicians.form.name")),1),d(n,{attr:"name",messages:o.data.errors},null,8,["messages"]),g(t("input",{type:"text","onUpdate:modelValue":s[0]||(s[0]=a=>o.data.musician.name=a),"aria-invalid":o.data.errors.name?!0:""},null,8,Vt),[[S,o.data.musician.name]])]),t("fieldset",null,[t("label",null,i(e.$t("musicians.form.band")),1),d(n,{attr:"band",messages:o.data.errors},null,8,["messages"]),g(t("select",{"onUpdate:modelValue":s[1]||(s[1]=a=>o.data.musician.band=a),"aria-invalid":o.data.errors.band?!0:""},[(r(!0),c(y,null,k(o.data.musician.bands,a=>(r(),c("option",{key:a.key,value:a.key},i(a.name),9,Ut))),128))],8,Mt),[[F,o.data.musician.band]])])])}const U=v(qt,[["render",Nt]]),jt={components:{MusicianForm:U},setup(){return{store:A()}},mounted(){this.$api.call(this.store.new(),this.$refs.animation)},methods:{create(e){this.$api.call(this.store.create(),e.target).then(s=>{s===!0&&this.$router.push({name:"edit_musician_path",params:{id:this.store.musician.id}})})}}},It={class:"container"},Tt={class:"breadcrumb"},Lt={ref:"animation"},Ot={class:"row"},Pt={class:"col-sm-4 col-start-sm-21 ta-right"},Et=["value"];function Rt(e,s,o,u,h,l){const n=p("router-link"),a=p("MusicianForm");return r(),c("section",It,[t("ul",Tt,[t("li",null,[d(n,{to:{name:"root_path"}},{default:m(()=>[_(i(e.$t("title")),1)]),_:1})]),t("li",null,[d(n,{to:{name:"musicians_path"}},{default:m(()=>[_(i(e.$t("nav.musicians")),1)]),_:1})]),t("li",null,[t("span",null,i(e.$t("musicians.new")),1)])]),t("div",Lt,[t("form",{onSubmit:s[0]||(s[0]=w((...$)=>l.create&&l.create(...$),["prevent"])),"accept-charset":"UTF-8",class:"card"},[d(a,{data:u.store},null,8,["data"]),t("div",Ot,[t("div",Pt,[t("input",{type:"submit",value:e.$t("save")},null,8,Et)])])],32)],512)])}const Bt=v(jt,[["render",Rt]]),Dt={components:{MusicianForm:U},setup(){return{store:A()}},mounted(){this.$api.call(this.store.edit(this.$route.params.id),this.$refs.animation)},methods:{update(e){this.$api.call(this.store.update(this.$route.params.id),e.target)},destroy(){confirm(this.$t("confirm"))&&this.$api.call(this.store.destroy(this.$route.params.id),this.$refs.animation).then(e=>{e===!0&&this.$router.push({name:"musicians_path"})})}}},Qt={class:"container"},Wt={class:"breadcrumb"},zt={ref:"animation"},Jt={class:"row"},Gt={class:"col-sm-4 secondary outline"},Ht={class:"col-sm-4 col-start-sm-21 ta-right"},Kt=["value"];function Xt(e,s,o,u,h,l){const n=p("router-link"),a=p("MusicianForm");return r(),c("section",Qt,[t("ul",Wt,[t("li",null,[d(n,{to:{name:"root_path"}},{default:m(()=>[_(i(e.$t("title")),1)]),_:1})]),t("li",null,[d(n,{to:{name:"musicians_path"}},{default:m(()=>[_(i(e.$t("nav.musicians")),1)]),_:1})]),t("li",null,i(u.store.musician.name),1)]),t("div",zt,[t("form",{ref:"form",onSubmit:s[1]||(s[1]=w((...$)=>l.update&&l.update(...$),["prevent"])),"accept-charset":"UTF-8",class:"card"},[d(a,{data:u.store},null,8,["data"]),t("div",Jt,[t("div",Gt,[t("a",{onClick:s[0]||(s[0]=(...$)=>l.destroy&&l.destroy(...$)),href:"#",role:"button",class:"secondary outline"},i(e.$t("delete")),1)]),t("div",Ht,[t("input",{type:"submit",value:e.$t("save")},null,8,Kt)])])],544)],512)])}const Yt=v(Dt,[["render",Xt]]),Zt={data(){return{message:"",messages:[]}},created(){this.$cable.on("chat",e=>{this.messages.unshift(e.message)})},methods:{publish(){this.$cable.send(this.message),this.message=""}}},te={class:"container"},ee={class:"breadcrumb"},se={class:"row"},ae={class:"col-xs-24 col-sm-12 card"},ne=t("br",null,null,-1),ie=t("br",null,null,-1),oe=t("input",{type:"submit",value:"Publish"},null,-1),re=t("div",{class:"card"},[t("p",null,"You can also push messages here from the server using the Rails console:"),t("code",null,'ActionCable.server.broadcast("ChatChannel", { message: "hey!" })')],-1),ce={class:"col-xs-24 col-sm-12 card"},le=t("p",null,[_("All messages you type are upcased "),t("b",null,"server side"),_(" after a round trip. If you open multiple tabs, messages are broadcasted on all tabs.")],-1),ue={key:0},de={key:1},he=t("p",null,[t("i",null,"Waiting for messages...")],-1),me=[he];function _e(e,s,o,u,h,l){const n=p("router-link");return r(),c("section",te,[t("ul",ee,[t("li",null,[d(n,{to:{name:"root_path"}},{default:m(()=>[_(i(e.$t("title")),1)]),_:1})]),t("li",null,i(e.$t("nav.websockets")),1)]),t("div",se,[t("div",ae,[t("form",{onSubmit:s[1]||(s[1]=w((...a)=>l.publish&&l.publish(...a),["prevent"])),"accept-charset":"UTF-8"},[g(t("input",{type:"input","onUpdate:modelValue":s[0]||(s[0]=a=>h.message=a),placeholder:"Type in a message"},null,512),[[S,h.message]]),ne,ie,oe],32),re]),t("div",ce,[le,h.messages.length>0?(r(),c("div",ue,[(r(!0),c(y,null,k(h.messages,(a,$)=>(r(),c("p",{key:$},[t("i",null,i(a),1)]))),128))])):(r(),c("div",de,me))])])])}const pe=v(Zt,[["render",_e]]),fe=I({history:T(`/${I18n.prefix}admin`),routes:[{path:"/",component:Z,name:"root_path"},{path:"/musicians",component:xt,name:"musicians_path"},{path:"/musicians/new",component:Bt,name:"new_musician_path"},{path:"/musicians/:id/edit",component:Yt,name:"edit_musician_path"},{path:"/websockets",component:pe,name:"websockets_path"},{path:"/404",component:L},{path:"/:catchAll(.*)",redirect:"/404"}]}),ve={data(){return{availableLocales:window.I18n.availableLocales,locale:window.I18n.locale}},methods:{activeOn(e){return e.includes(this.$route.name)?"active":""}},watch:{locale:function(e){let s=`/${e}${this.$route.path}`;e==this.availableLocales[0]&&(s=`${this.$route.path}`),window.location.href=s}}},$e={class:"top-nav"},be={class:"container"},ge={class:"row"},ye={class:"col-md-16 col-lg-21"},ke={href:"/users/sign_out"},we={class:"col-md-8 col-lg-3"},xe=["value"];function Ce(e,s,o,u,h,l){const n=p("router-link");return r(),c("section",$e,[t("div",be,[t("div",ge,[t("div",ye,[t("nav",null,[t("ul",null,[t("li",{class:C(l.activeOn(["root_path"]))},[d(n,{to:{name:"root_path"}},{default:m(()=>[_(i(e.$t("nav.dashboard")),1)]),_:1})],2),t("li",{class:C(l.activeOn(["musicians_path","edit_musician_path","new_musician_path"]))},[d(n,{to:{name:"musicians_path"}},{default:m(()=>[_(i(e.$t("nav.musicians")),1)]),_:1})],2),t("li",{class:C(l.activeOn(["websockets_path"]))},[d(n,{to:{name:"websockets_path"}},{default:m(()=>[_(i(e.$t("nav.websockets")),1)]),_:1})],2),t("li",null,[t("a",ke,i(e.$t("nav.logout")),1)])])])]),t("div",we,[g(t("select",{"onUpdate:modelValue":s[0]||(s[0]=a=>h.locale=a)},[(r(!0),c(y,null,k(h.availableLocales,a=>(r(),c("option",{value:a,key:a},i(a.toUpperCase()),9,xe))),128))],512),[[F,h.locale]])])])])])}const Se=v(ve,[["render",Ce]]),Fe={components:{"nav-bar":Se}};function Ae(e,s,o,u,h,l){const n=p("nav-bar"),a=p("router-view");return r(),c("section",null,[d(n),d(a)])}const qe=v(Fe,[["render",Ae]]),Ve={props:["store"],methods:{setQuery(e){let s=JSON.parse(JSON.stringify(this.$route.query));return s.page=e,s}},watch:{"$route.query":function(){this.$emit("clicked")}}},Me={clas:"container"},Ue={key:0},Ne={class:"pagination"},je={key:0},Ie=t("span",null,"«",-1),Te={key:1},Le={key:2},Oe={key:3},Pe={key:1},Ee=t("span",null,"»",-1);function Re(e,s,o,u,h,l){const n=p("router-link");return r(),c("section",Me,[o.store.pagination.next||o.store.pagination.previous?(r(),c("div",Ue,[t("ul",Ne,[o.store.pagination.previous!=null?(r(),c("li",je,[o.store.pagination.previous?(r(),x(n,{key:0,to:{path:e.$route.path,query:l.setQuery(o.store.pagination.previous)}},{default:m(()=>[Ie]),_:1},8,["to"])):f("",!0)])):f("",!0),(r(!0),c(y,null,k(o.store.pagination.pages,a=>(r(),c("li",{key:a,class:C([o.store.pagination.current==a?"active":""])},[a!=o.store.pagination.current&&(a==1||a==2||a==3||a==o.store.pagination.pages||a==o.store.pagination.pages-1||a==o.store.pagination.pages-2)?(r(),x(n,{key:0,to:{path:e.$route.path,query:l.setQuery(a)}},{default:m(()=>[_(i(a),1)]),_:2},1032,["to"])):f("",!0),a!=o.store.pagination.current&&a==4?(r(),c("span",Te,"...")):f("",!0),a==o.store.pagination.current?(r(),c("span",Le,i(a),1)):f("",!0),a==o.store.pagination.current&&a==4?(r(),c("span",Oe,"...")):f("",!0)],2))),128)),o.store.pagination.next!=null&&o.store.pagination.pages>0?(r(),c("li",Pe,[o.store.pagination.next?(r(),x(n,{key:0,to:{path:e.$route.path,query:l.setQuery(o.store.pagination.next)}},{default:m(()=>[Ee]),_:1},8,["to"])):f("",!0)])):f("",!0)])])):f("",!0)])}const Be=v(Ve,[["render",Re]]),N=O(qe),De=P({channel:"ChatChannel"}),Qe=E({handler:M,namespace:"/admin"}),j=R();j.use(({store:e})=>{e.axios=M});const We=B({locale:"current",messages:translations});N.component("pagination",Be);N.use(fe).use(j).use(We).use(Qe).use(De).mount("#app"); 2 | -------------------------------------------------------------------------------- /public/vite-test/assets/front-f6acb49a.js: -------------------------------------------------------------------------------- 1 | import{d as I,_ as m,g as p,o as c,c as l,a as e,t as s,h,i as _,w as f,l as u,F as w,r as k,m as A,n as L,E as M,p as b,e as S,f as z,q as B,s as N,u as V,x,A as C,y as E}from"./vue-i18n-6b73e0ca.js";const v=I("musicians",{state:()=>({musician:{},musicians:[]}),actions:{async index(){return this.axios.get("/musicians").then(t=>{this.musicians=t.data.musicians})},async show(t){return this.axios.get(`/musicians/${t}`).then(n=>{this.musician=n.data.musician})}}}),O={setup(){return{store:v()}},methods:{unauthorized(){this.$api.call(this.store.show("this-will-trigger-a-401"))},crash(){this.$api.call(this.store.show("this-will-trigger-a-500"))}}},P={class:"container"},D={href:"/dead-link"},F=e("br",null,null,-1),T={href:"/admin"};function U(t,n,$,r,d,o){const i=p("router-link");return c(),l("section",P,[e("h1",null,s(t.$t("pages.title")),1),e("p",null,[e("a",D,s(t.$t("pages.server_404")),1)]),e("p",null,[h(i,{to:"/dead-link"},{default:_(()=>[u(s(t.$t("pages.client_404")),1)]),_:1})]),e("p",null,[e("a",{onClick:n[0]||(n[0]=f((...a)=>o.unauthorized&&o.unauthorized(...a),["prevent"])),href:"#"},s(t.$t("pages.server_401")),1)]),e("p",null,[e("a",{onClick:n[1]||(n[1]=f((...a)=>o.crash&&o.crash(...a),["prevent"])),href:"#"},s(t.$t("pages.server_500")),1)]),F,e("p",null,[e("a",T,s(t.$t("pages.admin_link")),1)])])}const q=m(O,[["render",U]]),H={setup(){return{store:v()}},created(){this.$api.call(this.store.index())}},R={class:"container"},W={class:"breadcrumb"},j={class:"row"};function G(t,n,$,r,d,o){const i=p("router-link");return c(),l("section",R,[e("h1",null,s(t.$t("home.title")),1),e("ul",W,[e("li",null,[e("span",null,s(t.$t("home.breadcrumb")),1)])]),e("div",j,[(c(!0),l(w,null,k(r.store.musicians,a=>(c(),l("div",{key:a.id,class:"col-xs-24 col-md-6 card"},[h(i,{to:{name:"musician_path",params:{id:a.id}}},{default:_(()=>[u(s(a.name),1)]),_:2},1032,["to"])]))),128))])])}const g=m(H,[["render",G]]),J={setup(){return{store:v()}},created(){this.$api.call(this.store.show(this.$route.params.id))}},K={class:"container"},Q={class:"breadcrumb"},X=e("br",null,null,-1),Y=e("br",null,null,-1);function Z(t,n,$,r,d,o){const i=p("router-link");return c(),l("section",K,[e("h1",null,s(t.$t("home.title")),1),e("ul",Q,[e("li",null,[h(i,{to:{name:"root_path"}},{default:_(()=>[u(s(t.$t("home.breadcrumb")),1)]),_:1})]),e("li",null,s(r.store.musician.name),1)]),e("h2",null,s(t.$t("musicians.title")),1),e("p",null,[e("b",null,s(t.$t("musicians.id"))+":",1),u(" "+s(r.store.musician.id),1),X,e("b",null,s(t.$t("musicians.name"))+":",1),u(" "+s(r.store.musician.name),1),Y,e("b",null,s(t.$t("musicians.band"))+":",1),u(" "+s(r.store.musician.band),1)])])}const ee=m(J,[["render",Z]]),te=A({history:L(`/${I18n.prefix}`),routes:[{path:"/",component:g,name:"root_path"},{path:"/pages",component:q,name:"pages_path"},{path:"/musicians",component:g,name:"musicians_path"},{path:"/musicians/:id",component:ee,name:"musician_path"},{path:"/404",component:M},{path:"/:catchAll(.*)",redirect:"/404"}]}),se={data(){return{availableLocales:window.I18n.availableLocales,locale:window.I18n.locale}},methods:{activeOn(t){return t.includes(this.$route.name)?"active":""}},watch:{locale:function(t){let n=`/${t}${this.$route.path}`;t==this.availableLocales[0]&&(n=`${this.$route.path}`),window.location.href=n}}},ne={class:"top-nav"},ae={class:"container"},oe={class:"row"},ie={class:"col-md-16 col-lg-21"},re={class:"col-md-8 col-lg-3"},ce=["value"];function le(t,n,$,r,d,o){const i=p("router-link");return c(),l("section",ne,[e("div",ae,[e("div",oe,[e("div",ie,[e("nav",null,[e("ul",null,[e("li",{class:b(o.activeOn(["root_path","musicians_path","musician_path"]))},[h(i,{to:"/"},{default:_(()=>[u(s(t.$t("nav.homepage")),1)]),_:1})],2),e("li",{class:b(o.activeOn(["pages_path"]))},[h(i,{to:{name:"pages_path"}},{default:_(()=>[u(s(t.$t("nav.pages")),1)]),_:1})],2)])])]),e("div",re,[S(e("select",{"onUpdate:modelValue":n[0]||(n[0]=a=>d.locale=a)},[(c(!0),l(w,null,k(d.availableLocales,a=>(c(),l("option",{value:a,key:a},s(a.toUpperCase()),9,ce))),128))],512),[[z,d.locale]])])])])])}const ue=m(se,[["render",le]]),de={components:{"nav-bar":ue}};function he(t,n,$,r,d,o){const i=p("nav-bar"),a=p("router-view");return c(),l("div",null,[h(i),h(a)])}const pe=m(de,[["render",he]]),_e=B(pe),me=N({channel:"ChatChannel"}),$e=V({handler:C,namespace:""}),y=x();y.use(({store:t})=>{t.axios=C});const ve=E({locale:"current",messages:translations});_e.use(te).use(y).use(ve).use($e).use(me).mount("#app"); 2 | -------------------------------------------------------------------------------- /public/vite-test/manifest-assets.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /public/vite-test/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "_vue-i18n-6b73e0ca.js": { 3 | "file": "assets/vue-i18n-6b73e0ca.js" 4 | }, 5 | "entrypoints/admin.js": { 6 | "file": "assets/admin-ebb17751.js", 7 | "imports": [ 8 | "_vue-i18n-6b73e0ca.js" 9 | ], 10 | "isEntry": true, 11 | "src": "entrypoints/admin.js" 12 | }, 13 | "entrypoints/front.js": { 14 | "file": "assets/front-f6acb49a.js", 15 | "imports": [ 16 | "_vue-i18n-6b73e0ca.js" 17 | ], 18 | "isEntry": true, 19 | "src": "entrypoints/front.js" 20 | } 21 | } -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/storage/.keep -------------------------------------------------------------------------------- /test/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/test/.DS_Store -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/test/controllers/.keep -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/test/helpers/.keep -------------------------------------------------------------------------------- /test/javascript/app.test.js: -------------------------------------------------------------------------------- 1 | const chai = require('chai') 2 | const expect = chai.expect 3 | 4 | describe('first test', () => { 5 | it('is true', () => { 6 | expect(true).to.equal(true) 7 | }) 8 | }) 9 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/test/models/.keep -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/test/system/.keep -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../../config/environment', __FILE__) 2 | require 'rails/test_help' 3 | 4 | ENV['RAILS_ENV'] ||= 'test' 5 | 6 | require 'rails/test_help' 7 | require 'rack/test' 8 | require 'capybara/rails' 9 | require 'minitest' 10 | require 'minitest/rails' 11 | require 'minitest-metadata' 12 | require 'capybara/email' 13 | require 'json-schema' 14 | 15 | include Rails.application.routes.url_helpers 16 | include ActionDispatch::TestProcess 17 | include Warden::Test::Helpers 18 | 19 | Rails.logger.level = 5 20 | Dir[Rails.root.join('test/support/**/*.rb')].each { |f| require f } 21 | 22 | class ActiveSupport::TestCase 23 | parallelize(workers: 1) 24 | ActiveJob::Base.queue_adapter = :test 25 | 26 | fixtures :all 27 | self.use_transactional_tests = true 28 | 29 | def json_response 30 | JSON.parse(response.body) 31 | end 32 | end 33 | 34 | class ActionController::TestCase 35 | include Rails.application.routes.url_helpers 36 | include Devise::Test::ControllerHelpers 37 | include ActionCable::TestHelper 38 | 39 | self.use_transactional_tests = true 40 | 41 | Capybara.default_driver = :headless_chrome 42 | Capybara.javascript_driver = :headless_chrome 43 | Capybara.default_max_wait_time = 5 # seconds 44 | end 45 | 46 | class ActionDispatch::IntegrationTest 47 | include ResponseAssertions 48 | include Devise::Test::IntegrationHelpers 49 | include Rails.application.routes.url_helpers 50 | 51 | Capybara.register_driver :headless_chrome do |app| 52 | Capybara::Selenium::Driver.new(app, browser: :chrome) 53 | end 54 | 55 | Capybara.configure do |config| 56 | config.default_max_wait_time = 5 # seconds 57 | config.default_driver = :headless_chrome 58 | config.javascript_driver = :headless_chrome 59 | end 60 | end -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/tmp/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/vendor/.keep -------------------------------------------------------------------------------- /vendor/javascript/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gbarillot/rails-vue-demo-app/04ab3db3253c4d9f5c1017c1a15a146a11babd38/vendor/javascript/.keep -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | 3 | import RubyPlugin from 'vite-plugin-ruby' 4 | import Vue from '@vitejs/plugin-vue' 5 | import AutoImport from 'unplugin-auto-import/vite' 6 | 7 | export default defineConfig({ 8 | plugins: [ 9 | AutoImport({ 10 | // targets to transform 11 | include: [ 12 | /\.[tj]sx?$/, // .ts, .tsx, .js, .jsx 13 | /\.vue$/, 14 | /\.vue\?vue/, // .vue 15 | /\.md$/, // .md 16 | ], 17 | imports: [ 18 | 'vue', 19 | 'vue-router', 20 | 'vue-i18n' 21 | ] 22 | }), 23 | RubyPlugin(), 24 | Vue() 25 | ], 26 | server: { 27 | hmr: { 28 | port: 3036 29 | }, 30 | } 31 | }) 32 | --------------------------------------------------------------------------------