├── .gitignore ├── .ruby-gemset ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── app │ │ │ ├── helpers │ │ │ │ ├── global_search.js │ │ │ │ ├── locales.js │ │ │ │ ├── pagination.js │ │ │ │ └── turbolinks │ │ │ │ │ ├── auto_navbar_collapse.js │ │ │ │ │ └── transitions.js │ │ │ ├── init.js │ │ │ ├── initializers │ │ │ │ └── bootstrap-checkbox-radio.js │ │ │ └── lib │ │ │ │ ├── notifications.js │ │ │ │ └── turbolinks_native_message_handler.js │ │ ├── application.js │ │ ├── cable.js │ │ ├── channels │ │ │ └── .keep │ │ └── ext │ │ │ ├── jquery │ │ │ ├── animateCSS.js │ │ │ └── once.js │ │ │ ├── rails.js │ │ │ └── turbolinks.coffee │ └── stylesheets │ │ ├── application.scss │ │ ├── ext │ │ └── paper-dashboard.scss │ │ ├── mixins.scss │ │ └── views │ │ ├── devise.scss │ │ ├── errors.scss │ │ └── home.scss ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── application_controller.rb │ ├── concerns │ │ ├── .keep │ │ ├── devise_permitted_parameters.rb │ │ ├── locale_manager.rb │ │ ├── meta_tags_helpers.rb │ │ ├── pundit_helpers.rb │ │ ├── token_authentication.rb │ │ └── turbolinks_helpers.rb │ ├── errors_controller.rb │ ├── home_controller.rb │ ├── recipes_controller.rb │ └── users │ │ ├── passwords_controller.rb │ │ ├── registrations_controller.rb │ │ ├── sessions_controller.rb │ │ └── settings_controller.rb ├── helpers │ ├── application_helper.rb │ ├── home_helper.rb │ └── recipes_helper.rb ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── application_record.rb │ ├── concerns │ │ ├── .keep │ │ └── user_token.rb │ ├── home │ │ └── params.rb │ ├── recipe.rb │ └── user.rb ├── policies │ ├── application_policy.rb │ ├── recipe_policy.rb │ └── user_policy.rb └── views │ ├── devise │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── password_change.html.erb │ │ ├── reset_password_instructions.html.erb │ │ └── unlock_instructions.html.erb │ ├── passwords │ │ ├── create.js.erb │ │ ├── edit.html.slim │ │ ├── edit │ │ │ └── _form.html.slim │ │ ├── new.html+app.slim │ │ ├── new.html.slim │ │ └── update.js.erb │ ├── registrations │ │ ├── create.js+app.erb │ │ ├── create.js.erb │ │ ├── destroy.js.erb │ │ ├── edit.html.slim │ │ ├── edit │ │ │ └── _form.html.slim │ │ ├── new.html.slim │ │ ├── new │ │ │ ├── _form.html+app.slim │ │ │ └── _form.html.slim │ │ └── update.js.erb │ └── sessions │ │ ├── create.js+app.erb │ │ ├── create.js.erb │ │ ├── new.html.slim │ │ ├── new.js.erb │ │ └── new │ │ └── _form.html.slim │ ├── errors │ ├── internal_server_error.html.slim │ └── not_found.html.slim │ ├── home │ ├── _recipe.html.slim │ ├── _recipes.html.slim │ ├── _recipes_not_found.html.slim │ ├── _recipes_relation.html.slim │ ├── _recipes_search_filters.html.slim │ ├── index.html+app.slim │ ├── index.html.slim │ └── index.js.erb │ ├── kaminari │ ├── _first_page.html.slim │ ├── _gap.html.slim │ ├── _last_page.html.slim │ ├── _next_page.html.slim │ ├── _page.html.slim │ ├── _paginator.html.slim │ └── _prev_page.html.slim │ ├── layouts │ ├── _flash_messages.html.slim │ ├── _footer.html.slim │ ├── _sidebar.html.slim │ ├── _topnavbar.html.slim │ ├── application.html+app.slim │ ├── application.html+msite.slim │ ├── application.html.slim │ ├── mailer.html.erb │ ├── mailer.text.erb │ └── navbar │ │ ├── _header.html.slim │ │ ├── _settings.html.slim │ │ └── locales │ │ ├── _for_guests.html.slim │ │ └── _for_users.html.slim │ └── recipes │ ├── _form.html.slim │ ├── edit.html.slim │ ├── new.html.slim │ ├── save.js.erb │ └── show.html.slim ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring └── update ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── friendly_id.rb │ ├── inflections.rb │ ├── kaminari_config.rb │ ├── locales.rb │ ├── meta_tags.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ ├── en │ │ ├── global.yml │ │ ├── models │ │ │ ├── recipe.yml │ │ │ └── user.yml │ │ ├── navigation.yml │ │ ├── pagination.yml │ │ ├── titles.yml │ │ └── views │ │ │ ├── devise │ │ │ ├── mailer.yml │ │ │ ├── passwords.yml │ │ │ ├── registrations.yml │ │ │ └── sessions.yml │ │ │ ├── errors │ │ │ ├── internal_server_error.yml │ │ │ └── not_found.yml │ │ │ ├── home.yml │ │ │ ├── layouts │ │ │ ├── footer.yml │ │ │ └── navbar.yml │ │ │ └── recipes.yml │ └── pt-BR │ │ ├── global.yml │ │ ├── models │ │ ├── recipe.yml │ │ └── user.yml │ │ ├── navigation.yml │ │ ├── pagination.yml │ │ ├── titles.yml │ │ └── views │ │ ├── devise │ │ ├── mailer.yml │ │ ├── passwords.yml │ │ ├── registrations.yml │ │ └── sessions.yml │ │ ├── errors │ │ ├── internal_server_error.yml │ │ └── not_found.yml │ │ ├── home.yml │ │ ├── layouts │ │ ├── footer.yml │ │ └── navbar.yml │ │ └── recipes.yml ├── puma.rb ├── routes.rb ├── secrets.yml └── spring.rb ├── db ├── migrate │ ├── 20160806032155_devise_create_users.rb │ ├── 20160806033435_add_role_to_users.rb │ ├── 20160806200056_add_name_to_user.rb │ ├── 20160827062724_add_authentication_token_to_users.rb │ ├── 20161111224106_create_recipes.rb │ ├── 20161112004523_add_story_to_recipes.rb │ ├── 20161115142009_create_friendly_id_slugs.rb │ └── 20161115142053_add_slug_to_recipes.rb ├── schema.rb └── seeds.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 422.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── sample.env ├── spec ├── factories │ ├── recipes.rb │ └── users.rb ├── models │ ├── recipe_spec.rb │ └── user_spec.rb ├── policies │ └── user_policy_spec.rb ├── rails_helper.rb └── spec_helper.rb ├── tmp └── .keep └── vendor └── assets ├── fonts ├── .DS_Store ├── themify.eot ├── themify.svg ├── themify.ttf └── themify.woff ├── images ├── .DS_Store ├── apple-icon.png ├── background.jpg ├── faces │ ├── .DS_Store │ ├── face-0.jpg │ ├── face-1.jpg │ ├── face-2.jpg │ └── face-3.jpg ├── favicon.png ├── new_logo.png └── tim_80x80.png ├── javascripts ├── .keep ├── bootbox.js ├── bootstrap-checkbox-radio.js ├── bootstrap-notify.js └── paper-dashboard.js └── stylesheets ├── .keep ├── paper-dashboard.scss ├── paper ├── _alerts.scss ├── _buttons.scss ├── _cards.scss ├── _chartist.scss ├── _checkbox-radio.scss ├── _dropdown.scss ├── _footers.scss ├── _inputs.scss ├── _misc.scss ├── _mixins.scss ├── _navbars.scss ├── _responsive.scss ├── _sidebar-and-main-panel.scss ├── _tables.scss ├── _tabs-navs-pagination.scss ├── _typography.scss ├── _variables.scss └── mixins │ ├── _buttons.scss │ ├── _cards.scss │ ├── _chartist.scss │ ├── _icons.scss │ ├── _inputs.scss │ ├── _labels.scss │ ├── _navbars.scss │ ├── _sidebar.scss │ ├── _tabs.scss │ ├── _transparency.scss │ └── _vendor-prefixes.scss └── themify-icons.css /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore Byebug command history file. 17 | .byebug_history 18 | 19 | .env 20 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | master-app 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.3.2 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby "2.3.2" 4 | 5 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 6 | gem 'rails', '~> 5.0.0.1' 7 | # Use postgresql as the database for Active Record 8 | gem 'pg', '~> 0.19' 9 | # Use Puma as the app server 10 | gem 'puma', '~> 3.6' 11 | # Use SCSS for stylesheets 12 | gem 'sass-rails', '~> 5.0' 13 | # Use Uglifier as compressor for JavaScript assets 14 | gem 'uglifier', '>= 1.3.0' 15 | # Use CoffeeScript for .coffee assets and views 16 | gem 'coffee-rails', '~> 4.2' 17 | # See https://github.com/rails/execjs#readme for more supported runtimes 18 | # gem 'therubyracer', platforms: :ruby 19 | 20 | # Use jquery as the JavaScript library 21 | gem 'jquery-rails' 22 | # Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks 23 | gem 'turbolinks', '~> 5.0', '>= 5.0.1' 24 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 25 | gem 'jbuilder', '~> 2.5' 26 | # Use Redis adapter to run Action Cable in production 27 | # gem 'redis', '~> 3.0' 28 | # Use ActiveModel has_secure_password 29 | # gem 'bcrypt', '~> 3.1.7' 30 | 31 | # Use Capistrano for deployment 32 | # gem 'capistrano-rails', group: :development 33 | 34 | source 'https://rails-assets.org' do 35 | gem 'rails-assets-animate.css', '3.5.2' 36 | end 37 | 38 | # ** Views/Templates ** 39 | gem 'slim-rails', '~> 3.1' 40 | gem 'bootstrap-sass', '3.3.7' 41 | gem 'font-awesome-rails', '~> 4.7' 42 | gem 'bootstrap_form', '~> 2.5' 43 | gem 'browser', '~> 2.3' 44 | gem 'kaminari', '~> 0.17.0' 45 | 46 | # ** SEO ** 47 | gem 'meta-tags', '~> 2.3', '>= 2.3.1' 48 | gem 'friendly_id', '~> 5.1' 49 | 50 | # ** i18n ** 51 | gem 'rails-i18n', '~> 5.0' 52 | gem 'devise-i18n', '~> 1.1' 53 | 54 | # ** i18n ** 55 | gem 'devise', '~> 4.2' 56 | gem 'pundit', '~> 1.1' 57 | 58 | # Asynchronous processing 59 | gem 'sucker_punch', '~> 2.0', '>= 2.0.2' 60 | 61 | group :development, :test do 62 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console 63 | gem 'pry', '~> 0.10.4' 64 | gem 'pry-byebug', '~> 3.4' 65 | gem 'byebug', platform: :mri 66 | gem 'rspec-rails', '~> 3.5', '>= 3.5.2' 67 | gem 'i18n-tasks', '~> 0.9.6' 68 | gem 'dotenv-rails', '~> 2.1', '>= 2.1.1' 69 | gem 'faker', '~> 1.6', '>= 1.6.6' 70 | gem 'factory_girl_rails', '~> 4.7' 71 | end 72 | 73 | group :development do 74 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. 75 | gem 'web-console' 76 | gem 'listen', '~> 3.1.5' 77 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 78 | gem 'spring' 79 | gem 'letter_opener', '~> 1.4', '>= 1.4.1' 80 | end 81 | 82 | group :production do 83 | gem 'heroku-deflater', git: 'https://github.com/romanbsd/heroku-deflater.git', ref: '60d92ba0f8ae2' 84 | end 85 | 86 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 87 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] 88 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Master App 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # README 2 | 3 | This README would normally document whatever steps are necessary to get the 4 | application up and running. 5 | 6 | Things you may want to cover: 7 | 8 | * Ruby version 9 | `2.3.2` 10 | 11 | * System dependencies 12 | 13 | * Configuration 14 | ``` 15 | # Create a .env file and set all environment variables 16 | cp sample.env .env 17 | ``` 18 | 19 | * Database creation 20 | ``` 21 | rails db:create && rails db:migrate 22 | ``` 23 | 24 | * Database initialization 25 | 26 | * How to run the test suite 27 | 28 | * Services (job queues, cache servers, search engines, etc.) 29 | 30 | **Job queues:** 31 | - https://github.com/brandonhilkert/sucker_punch 32 | 33 | **Heroku:** 34 | - [SendGrid](https://elements.heroku.com/addons/sendgrid) 35 | 36 | * Deployment instructions 37 | - Look in sample.env and set all environment variables listed in it. 38 | 39 | * ... 40 | -------------------------------------------------------------------------------- /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/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/themasterapp/master-app-rails-server/95d3ba06134df577276f01468cd71e4120c61d6b/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/app/helpers/global_search.js: -------------------------------------------------------------------------------- 1 | $(document).on('turbolinks:load', function() { 2 | $('form[data-global-search="true"]').on('submit',function (e) { 3 | e.preventDefault(); 4 | 5 | var $this = $(this), $q = $this.find('[name="search[q]"]'); 6 | var query = $.trim($q.val()); 7 | 8 | // normalize all query empty spaces 9 | $q.val( query.replace(/\s+/g, " ") ); 10 | 11 | var url = $this.attr('action') + '?' + $this.serialize(); 12 | 13 | Turbolinks.visit(url); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/helpers/locales.js: -------------------------------------------------------------------------------- 1 | $(document).on('turbolinks:load', function() { 2 | $("a[data-locale=true]").on("click", function (e) { 3 | e.preventDefault(); 4 | 5 | $(document).one("turbolinks:render", function() { 6 | Turbolinks.clearCache(); 7 | }); 8 | 9 | var url = $(this).attr('href'); 10 | 11 | Turbolinks.visit(url, "replace"); 12 | }); 13 | }); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/helpers/pagination.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | $(document).on("click",'.pagination a[data-remote=true]', function() { 3 | history.pushState({}, '', $(this).attr('href')); 4 | }); 5 | }); 6 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/helpers/turbolinks/auto_navbar_collapse.js: -------------------------------------------------------------------------------- 1 | $(document).on('turbolinks:before-visit', function() { 2 | var $witch = $('[data-auto-navbar-collapse="true"]'); 3 | 4 | if ($witch.length) { 5 | // Example of how to autoclose a regular bootstrap navbar. 6 | // $navbar.collapse('hide'); 7 | 8 | // Autoclose the paper admin menu. 9 | var navStateClass = 'nav-open'; 10 | var $html = $('html'); 11 | 12 | if ( $html.hasClass(navStateClass) ) { 13 | $html.removeClass(navStateClass); 14 | } 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/helpers/turbolinks/transitions.js: -------------------------------------------------------------------------------- 1 | $(function() { 2 | var _cache = {}; 3 | 4 | function getContextKey() { 5 | var data = $('body').data(); 6 | 7 | return data.controller + data.action; 8 | } 9 | 10 | function animate(el) { 11 | var $el = $(el); 12 | var animationType = $el.data('animateCss') || 'fadeIn'; 13 | 14 | $(el).animateCSS(animationType); 15 | } 16 | 17 | function animateOnLoad(el) { 18 | var contextKey = getContextKey() 19 | 20 | var isFirstLoad = _cache[contextKey]; 21 | 22 | if (!isFirstLoad) { 23 | _cache[contextKey] = true; 24 | 25 | animate(this); 26 | } 27 | } 28 | 29 | $(document).on('turbolinks:visit', function() { 30 | $('[data-animate-css]').each(function () { 31 | animate(this); 32 | }); 33 | }); 34 | 35 | $(document).on('turbolinks:load', function() { 36 | $('[data-animate-css]').each(function () { 37 | animateOnLoad(this); 38 | }); 39 | }); 40 | }); 41 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/init.js: -------------------------------------------------------------------------------- 1 | //= require_tree ./initializers 2 | //= require_tree ./helpers 3 | //= require_tree ./lib 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/initializers/bootstrap-checkbox-radio.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | $(document).on('turbolinks:load', function () { 3 | $('input[type="checkbox"]').each(function () { 4 | var $checkbox = $(this); 5 | $checkbox.checkbox(); 6 | }); 7 | }); 8 | 9 | $(document).on('turbolinks:load', function () { 10 | $('input[type="radio"]').each(function () { 11 | var $radio = $(this); 12 | $radio.radio(); 13 | }); 14 | }); 15 | })(); 16 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/lib/notifications.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var DATA_DISABLE = 'data-disable', 3 | NOTIFICATIONS = { 4 | timeout: 2000, 5 | alert: { 6 | state: 'danger', 7 | icon: 'ti-thumb-down' 8 | }, 9 | notice: { 10 | state: 'success', 11 | icon: 'ti-thumb-up' 12 | } 13 | }; 14 | 15 | function notifyWith(flashType, message) { 16 | var notification = NOTIFICATIONS[flashType]; 17 | 18 | $.notify({ 19 | icon: notification['icon'], 20 | message: message 21 | },{ 22 | type: notification['state'], 23 | timer: NOTIFICATIONS['timeout'] 24 | }); 25 | } 26 | 27 | function notifyIfEnable(data) { 28 | if (data && !data['disable']) { 29 | notifyWith(data['type'], data['text']); 30 | } 31 | } 32 | 33 | function notifyWithElement(el) { 34 | var $el = $(el), data = $.extend( 35 | { disable: $el.attr(DATA_DISABLE) }, 36 | $el.data() 37 | ); 38 | 39 | notifyIfEnable(data); 40 | 41 | return $el; 42 | } 43 | 44 | this.RailsFlashMessagesAsNotifications = { 45 | showAll: function() { 46 | $('#flash-messages li').each(function(i, el) { 47 | notifyWithElement(el).attr( 48 | DATA_DISABLE, "true" 49 | ); 50 | }); 51 | }, 52 | 53 | alert: function(message) { 54 | notifyWith('alert', message); 55 | }, 56 | 57 | notice: function(message) { 58 | notifyWith('notice', message); 59 | } 60 | }; 61 | 62 | // Application Layout > ul.hidden#flash-messages wrapper. 63 | $(document).on("turbolinks:load", RailsFlashMessagesAsNotifications.showAll); 64 | })(this); 65 | -------------------------------------------------------------------------------- /app/assets/javascripts/app/lib/turbolinks_native_message_handler.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var android = this.android, 3 | webkit = this.webkit; 4 | 5 | var consoleHandler = { 6 | postMessage: function(data) { 7 | console.log("TurbolinksNativeMessageHandler.postMessage:", data); 8 | } 9 | }; 10 | 11 | this.TurbolinksNativeMessageHandler = { 12 | _getDefaultNamespace: function() { 13 | var $container = $('[data-turbolinks-native-message-handler]'); 14 | 15 | if ($container.length) { 16 | return $container.data()['turbolinksNativeMessageHandler']; 17 | } 18 | }, 19 | 20 | _getNamespace: function(value) { 21 | if (value) { 22 | return value; 23 | } else { 24 | return this._getDefaultNamespace(); 25 | } 26 | }, 27 | 28 | postMessage: function(data, namespace) { 29 | var handler; 30 | 31 | // Android container 32 | if (android) { 33 | handler = android; 34 | 35 | // iOS container 36 | } else if (webkit && webkit.messageHandlers) { 37 | var _namespace = this._getNamespace(namespace); 38 | 39 | handler = webkit.messageHandlers[_namespace]; 40 | 41 | } else { 42 | handler = consoleHandler; 43 | } 44 | 45 | handler.postMessage(data); 46 | } 47 | }; 48 | })(); 49 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | // ** Main dependencies ** 14 | //= require jquery 15 | //= require jquery_ujs 16 | //= require bootstrap-sprockets 17 | //= require turbolinks 18 | 19 | // ** Plugins ** 20 | //= require bootstrap-checkbox-radio 21 | //= require bootstrap-notify 22 | //= require bootbox 23 | //= require paper-dashboard 24 | 25 | // ** Extensions ** 26 | //= require_tree ./ext 27 | 28 | // ** Project ** 29 | //= require ./app/init 30 | 31 | //= require_tree . 32 | -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the rails generate channel command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/themasterapp/master-app-rails-server/95d3ba06134df577276f01468cd71e4120c61d6b/app/assets/javascripts/channels/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/ext/jquery/animateCSS.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | var animationEnd = 'webkitAnimationEnd mozAnimationEnd MSAnimationEnd oanimationend animationend'; 3 | 4 | $.fn.extend({ 5 | animateCSS: function (animationName) { 6 | $(this).addClass('animated ' + animationName).one(animationEnd, function() { 7 | $(this).removeClass('animated ' + animationName); 8 | }); 9 | } 10 | }); 11 | })(jQuery); 12 | -------------------------------------------------------------------------------- /app/assets/javascripts/ext/jquery/once.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | $.once = function(fn, context) { 3 | var result; 4 | 5 | return function() { 6 | if(fn) { 7 | result = fn.apply(context || this, arguments); 8 | fn = null; 9 | } 10 | 11 | return result; 12 | }; 13 | }; 14 | })(jQuery); 15 | -------------------------------------------------------------------------------- /app/assets/javascripts/ext/rails.js: -------------------------------------------------------------------------------- 1 | /* 2 | Based on 3 | http://thehungrycoder.com/ruby-on-rails/replace-rails-confirm-dialog-with-bootboxjs.html 4 | */ 5 | 6 | $(function() { 7 | var ConfirmToDestroy = {}; 8 | 9 | var centralize = function(box) { 10 | // https://github.com/makeusabrew/bootbox/issues/166 11 | box.css({ 12 | 'top': '50%', 13 | 'margin-top': function () { 14 | return -(box.height() / 2); 15 | } 16 | }); 17 | }; 18 | 19 | ConfirmToDestroy.customDialog = function(message, confirmMSG, cancelMSG, callback) { 20 | var box = bootbox.confirm({ 21 | message: message, 22 | callback: callback, 23 | buttons: { 24 | confirm: { 25 | label: (confirmMSG || 'Yes, definitely!'), 26 | className: 'btn-danger btn-fill' 27 | }, 28 | cancel: { 29 | label: (cancelMSG || 'Opss! No.'), 30 | className: 'btn-fill' 31 | } 32 | } 33 | }); 34 | 35 | centralize(box); 36 | }; 37 | 38 | ConfirmToDestroy.dialog = function(message, callback) { 39 | ConfirmToDestroy.customDialog(message, false, false, callback); 40 | }; 41 | 42 | var railsCallbackHandler = function(callback) { 43 | return function (result) { 44 | if (typeof callback === 'function') { 45 | if (result) { 46 | return callback(); 47 | } 48 | } 49 | } 50 | }; 51 | 52 | ConfirmToDestroy.railsDialog = function(message, confirmMSG, cancelMSG, callback) { 53 | var handler = railsCallbackHandler(callback); 54 | 55 | if (!confirmMSG || !cancelMSG) { 56 | ConfirmToDestroy.dialog(message, handler); 57 | } else { 58 | ConfirmToDestroy.customDialog(message, confirmMSG, cancelMSG, handler); 59 | } 60 | }; 61 | 62 | $(document).on('turbolinks:load', function() { 63 | $('[data-destroy="action"]').on('click', function(e) { 64 | e.preventDefault(); 65 | 66 | var $this = $(this); 67 | var data, message, confirmButton, cancelButton; 68 | 69 | data = $this.data(); 70 | 71 | message = data['confirmMsg']; 72 | cancelButton = data['cancelBtn']; 73 | confirmButton = data['confirmBtn']; 74 | 75 | ConfirmToDestroy.customDialog(message, confirmButton, cancelButton, 76 | function(result) { 77 | if(result) { 78 | $.ajax({url: $this.attr('href'), type: 'DELETE'}); 79 | } 80 | } 81 | ); 82 | }); 83 | }); 84 | 85 | $.rails.allowAction = function(element) { 86 | var answer, callback, message, cancelMSG, confirmMSG; 87 | 88 | message = element.data('confirm'); 89 | cancelMSG = element.data('cancelBtn'); 90 | confirmMSG = element.data('confirmBtn'); 91 | 92 | if (!message) { 93 | return true; 94 | } 95 | 96 | answer = false; 97 | callback = void 0; 98 | 99 | if ($.rails.fire(element, 'confirm')) { 100 | ConfirmToDestroy.railsDialog(message, confirmMSG, cancelMSG, function() { 101 | var oldAllowAction; 102 | 103 | callback = $.rails.fire(element, 'confirm:complete', [answer]); 104 | 105 | if (callback) { 106 | oldAllowAction = $.rails.allowAction; 107 | 108 | $.rails.allowAction = function() { 109 | return true; 110 | }; 111 | 112 | element.trigger('click'); 113 | return $.rails.allowAction = oldAllowAction; 114 | } 115 | }); 116 | } 117 | 118 | return false; 119 | }; 120 | }); 121 | -------------------------------------------------------------------------------- /app/assets/javascripts/ext/turbolinks.coffee: -------------------------------------------------------------------------------- 1 | # Monkey patch Turbolinks to render 404 & 500 normally 2 | Turbolinks.HttpRequest.prototype.requestLoaded = -> 3 | @endRequest => 4 | if 200 <= @xhr.status < 300 or @xhr.status = 404 or @xhr.status = 500 5 | @delegate.requestCompletedWithResponse(@xhr.responseText, @xhr.getResponseHeader("Turbolinks-Location")) 6 | else 7 | @failed = true 8 | @delegate.requestFailedWithStatusCode(@xhr.status, @xhr.responseText) 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | //vendor/css 2 | 3 | @import "bootstrap-sprockets"; 4 | @import "bootstrap"; 5 | @import "animate.css/animate"; 6 | 7 | @import "paper-dashboard"; 8 | @import "ext/paper-dashboard"; 9 | 10 | @import "mixins"; 11 | 12 | @import "views/home"; 13 | @import "views/devise"; 14 | @import "views/errors"; 15 | 16 | // ADD THIS AT THE BOTTOM (otherwise the icons won't appear for Android devices) 17 | @import "font-awesome"; 18 | @import "themify-icons"; 19 | -------------------------------------------------------------------------------- /app/assets/stylesheets/ext/paper-dashboard.scss: -------------------------------------------------------------------------------- 1 | .card { 2 | .content{ 3 | padding: 20px 20px; 4 | } 5 | } 6 | 7 | 8 | //Fix Android container layout issue. 9 | // The css overflow property makes a blank screen. 10 | body.android-app { 11 | .main-panel{ 12 | overflow: visible; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/mixins.scss: -------------------------------------------------------------------------------- 1 | @mixin strong-border { 2 | border-style: solid; 3 | border-width: $border-thick; 4 | border-color: $default-color; 5 | } 6 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/devise.scss: -------------------------------------------------------------------------------- 1 | .card.sign_in, 2 | .card.sign_up { 3 | .content { 4 | form .checkbox label { 5 | padding-left: 0; 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/errors.scss: -------------------------------------------------------------------------------- 1 | .errors { 2 | .card .content{ 3 | h4 { 4 | margin-top: 0; 5 | } 6 | 7 | input[type="text"] { 8 | @include strong-border; 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app/assets/stylesheets/views/home.scss: -------------------------------------------------------------------------------- 1 | .recipe { 2 | p.user { 3 | margin-bottom: 15px; 4 | 5 | .name, .edit { margin-right: 10px; } 6 | } 7 | } 8 | 9 | .home.index { 10 | .recipes-search { 11 | margin-top: 20px; 12 | 13 | form .recipes-search-input { 14 | @include strong-border; 15 | } 16 | } 17 | 18 | .recipe { 19 | hr { 20 | margin-top: 5px; 21 | } 22 | 23 | h4 { 24 | margin-top: 10px; 25 | margin-bottom: 10px; 26 | } 27 | } 28 | } 29 | 30 | @media (max-width: $screen-xs-min) { 31 | .home.index .recipes-search form { 32 | margin-top: 10px; 33 | 34 | .form-group { 35 | margin-bottom: 10px; 36 | } 37 | } 38 | } 39 | 40 | @media (min-width: $screen-sm-min) { 41 | .home.index .recipes-search { 42 | form .recipes-search-input { 43 | margin-right: 4px; 44 | } 45 | } 46 | } 47 | 48 | @media (min-width: $screen-sm-min) and (max-width: $screen-md-min) { 49 | .home.index .recipes-search form .recipes-search-input { 50 | width: 300px; 51 | } 52 | } 53 | 54 | @media (min-width: $screen-lg-min) { 55 | .home.index .recipes-search form .recipes-search-input { 56 | width: 350px; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include LocaleManager 3 | include TurbolinksHelpers 4 | include TokenAuthentication 5 | include PunditHelpers 6 | include MetaTagsHelpers 7 | 8 | protect_from_forgery with: :exception 9 | 10 | before_action do 11 | # TODO: fix this logic. It depends on the order of the commands. 12 | request.variant = :msite if browser.device.mobile? 13 | request.variant = :app if turbolinks_app? 14 | end 15 | 16 | private 17 | 18 | # Mix of these two concepts: 19 | # http://blog.bigbinary.com/2016/02/29/rails-5-improves-redirect_to_back-with-redirect-back.html 20 | # https://github.com/rails/rails/blob/5-0-stable/actionpack/lib/action_controller/metal/redirecting.rb 21 | def redirect_back_with_default_fallback(**args) 22 | location = args.delete(:or) 23 | 24 | if location 25 | redirect_to location, **args 26 | else 27 | location = (request.referer || root_path) 28 | 29 | redirect_back(**args.merge!(fallback_location: location)) 30 | end 31 | end 32 | 33 | # https://github.com/plataformatec/devise/wiki/How-To:-Change-the-redirect-path-after-destroying-a-session-i.e.-signing-out 34 | def after_sign_out_path_for(resource_or_scope) 35 | turbolinks_ios_app? ? new_user_session_path : super 36 | end 37 | 38 | protected 39 | 40 | def home_params 41 | @home_params ||= Home::Params.new(params) 42 | end 43 | helper_method :home_params 44 | end 45 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/themasterapp/master-app-rails-server/95d3ba06134df577276f01468cd71e4120c61d6b/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/concerns/devise_permitted_parameters.rb: -------------------------------------------------------------------------------- 1 | module DevisePermittedParameters 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_action :configure_permitted_parameters 6 | end 7 | 8 | protected 9 | 10 | def configure_permitted_parameters 11 | devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) 12 | devise_parameter_sanitizer.permit(:account_update, keys: [:name]) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/controllers/concerns/locale_manager.rb: -------------------------------------------------------------------------------- 1 | module LocaleManager 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_action :set_locale! 6 | end 7 | 8 | private 9 | 10 | def set_locale! 11 | case locale_strategy 12 | when :keep then return 13 | when :apply_param then set_locale_with(locale_param) 14 | when :apply_cookie then set_locale_with(locale_cookie) 15 | when :apply_default then set_locale_with(I18n.default_locale) 16 | else fail NotImplementedError 17 | end 18 | end 19 | 20 | def set_locale_with(locale) 21 | I18n.locale = locale.to_sym 22 | cookies.permanent[:current_locale] = locale 23 | end 24 | 25 | def locale_cookie 26 | @locale_cookie ||= cookies[:current_locale] 27 | end 28 | 29 | def locale_param 30 | @locale_param ||= String(params[:locale]).tap(&:strip!) 31 | end 32 | 33 | def valid_locale?(locale) 34 | I18n.available_locales.include?(locale.to_sym) 35 | end 36 | 37 | def current_locale?(locale) 38 | I18n.locale == locale.to_sym 39 | end 40 | 41 | def locale_strategy 42 | if locale_param.present? 43 | return :keep if locale_param == locale_cookie && current_locale?(locale_param) 44 | 45 | valid_locale?(locale_param) && !current_locale?(locale_param) ? :apply_param : :apply_default 46 | else 47 | return :apply_default if locale_cookie.blank? 48 | return :keep if current_locale?(locale_cookie) 49 | 50 | valid_locale?(locale_cookie) ? :apply_cookie : :apply_default 51 | end 52 | end 53 | end 54 | -------------------------------------------------------------------------------- /app/controllers/concerns/meta_tags_helpers.rb: -------------------------------------------------------------------------------- 1 | module MetaTagsHelpers 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | before_action :set_meta_tags_with_defaults 6 | end 7 | 8 | def set_meta_tags_with_defaults(data = {}) 9 | defaults = title_meta_tag_data 10 | .merge!(default_image_meta_tag_data) 11 | .merge!(description_meta_tag_data) 12 | 13 | set_meta_tags defaults.merge!(data) 14 | end 15 | 16 | def description_meta_tag_data 17 | { description: I18n.t('app.meta.description'.freeze) } 18 | end 19 | 20 | def default_image_meta_tag_data 21 | { image_src: ENV.fetch('DEFAULT_RECIPE_IMAGE').freeze } 22 | end 23 | 24 | def title_meta_tag_data 25 | data = {} 26 | locales = I18n.t(:titles) 27 | default = locales[:application] 28 | controller = controller_name.to_sym 29 | 30 | data[:title] = locales.dig(controller, action_name.to_sym) 31 | data[:title] ||= locales[controller].instance_of?(String) ? locales[controller] : default 32 | 33 | if !turbolinks_app? && data[:title] != default 34 | data[:site] = default 35 | end 36 | 37 | data 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/controllers/concerns/pundit_helpers.rb: -------------------------------------------------------------------------------- 1 | module PunditHelpers 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | include Pundit 6 | 7 | rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized 8 | end 9 | 10 | private 11 | 12 | def user_not_authorized 13 | flash[:alert] = I18n.t("auth.access_denied") 14 | 15 | redirect_back_with_default_fallback turbolinks: :advance 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/concerns/token_authentication.rb: -------------------------------------------------------------------------------- 1 | # FIXME: Very weak strategy for a production App. 2 | module TokenAuthentication 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | before_action :authenticate_user_from_token! 7 | end 8 | 9 | private 10 | 11 | def authenticate_user_from_token! 12 | return if user_signed_in? 13 | 14 | auth_token = params[:auth_token].presence 15 | user = auth_token && User.find_by(authentication_token: String(auth_token)) 16 | 17 | if user 18 | # Notice we are passing store false, so the user is not 19 | # actually stored in the session and a token is needed 20 | # for every request. If you want the token to work as a 21 | # sign in token, you can simply remove store: false. 22 | # sign_in user, store: false 23 | sign_in user 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/controllers/concerns/turbolinks_helpers.rb: -------------------------------------------------------------------------------- 1 | module TurbolinksHelpers 2 | extend ActiveSupport::Concern 3 | 4 | TURBOLINKS_IOS_APP_USER_AGENT = ENV.fetch("TURBOLINKS_IOS_APP_USER_AGENT").freeze 5 | TURBOLINKS_ANDROID_APP_USER_AGENT = ENV.fetch("TURBOLINKS_ANDROID_APP_USER_AGENT").freeze 6 | 7 | def turbolinks_ios_app_user_agent? 8 | request.user_agent.include?(TURBOLINKS_IOS_APP_USER_AGENT) 9 | end 10 | 11 | def turbolinks_android_app_user_agent? 12 | request.user_agent.include?(TURBOLINKS_ANDROID_APP_USER_AGENT) 13 | end 14 | 15 | def turbolinks_app_user_agent? 16 | turbolinks_ios_app_user_agent? || turbolinks_android_app_user_agent? 17 | end 18 | 19 | included do 20 | [ 21 | [:turbolinks_app?, :turbolinks_app_user_agent?], 22 | [:turbolinks_ios_app?, :turbolinks_ios_app_user_agent?], 23 | [:turbolinks_android_app?, :turbolinks_android_app_user_agent?] 24 | ].each do |alias_name, original_name| 25 | alias_method alias_name, original_name 26 | 27 | helper_method alias_name 28 | helper_method original_name 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/controllers/errors_controller.rb: -------------------------------------------------------------------------------- 1 | class ErrorsController < ApplicationController 2 | def not_found 3 | render status: 404 4 | end 5 | 6 | def internal_server_error 7 | render status: 500 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | set_meta_tags(title: t(".titles.#{home_params.current_filter}")) 4 | 5 | @recipes = if guest_in_my_recipes_filter? 6 | Recipe.none 7 | else 8 | find_all_recipes(relation_base) 9 | end 10 | end 11 | 12 | private 13 | 14 | def guest_in_my_recipes_filter? 15 | !user_signed_in? && home_params.my_recipes_filter? 16 | end 17 | 18 | def relation_base 19 | base = Recipe.joins(:user).includes(:user) 20 | 21 | return base if user_signed_in? && current_user.admin? 22 | 23 | user_ids = User.admin.pluck(:id) 24 | 25 | user_ids << current_user.id if user_signed_in? 26 | 27 | base.where('recipes.user_id'.freeze => user_ids) 28 | end 29 | 30 | def find_all_recipes(relation) 31 | if home_params.search? 32 | relation = apply_user_filter(relation) 33 | relation = apply_query_filter(relation) 34 | end 35 | 36 | apply_pagination relation.order(created_at: :desc) 37 | end 38 | 39 | def apply_user_filter(relation) 40 | return relation if !user_signed_in? 41 | 42 | if home_params.my_recipes_filter? 43 | relation.where(user_id: current_user.id) 44 | else 45 | relation 46 | end 47 | end 48 | 49 | def apply_query_filter(relation) 50 | q = params.dig(:search, :q) 51 | 52 | return relation if q.blank? 53 | 54 | query = "%#{q}%" 55 | 56 | relation.where('recipes.title ILIKE ?'.freeze, query) 57 | .or(relation.where('recipes.story ILIKE ?'.freeze, query)) 58 | .or(relation.where('recipes.ingredients ILIKE ?'.freeze, query)) 59 | .or(relation.where('users.name ILIKE ?'.freeze, query)) 60 | end 61 | 62 | def apply_pagination(relation) 63 | relation.page home_params.current_filter_page_param 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /app/controllers/recipes_controller.rb: -------------------------------------------------------------------------------- 1 | class RecipesController < ApplicationController 2 | before_action :authenticate_user!, except: :show 3 | 4 | before_action :set_recipe, except: [:new, :create] 5 | 6 | after_action :verify_authorized 7 | 8 | def show 9 | set_meta_tags(title: @recipe.title, description: @recipe.story) 10 | end 11 | 12 | def new 13 | @recipe = build_recipe 14 | 15 | authorize @recipe 16 | end 17 | 18 | def create 19 | @recipe = build_recipe(recipe_params) 20 | 21 | authorize @recipe 22 | 23 | if @recipe.save 24 | redirect_to @recipe, notice: t(".notice"), turbolinks: :advance 25 | else 26 | respond_to do |format| 27 | format.html { render :new } 28 | format.js { render :save } 29 | end 30 | end 31 | end 32 | 33 | def edit 34 | end 35 | 36 | def update 37 | if @recipe.update(recipe_params) 38 | redirect_to @recipe, notice: t(".notice"), turbolinks: :advance 39 | else 40 | respond_to do |format| 41 | format.html { render :edit } 42 | format.js { render :save } 43 | end 44 | end 45 | end 46 | 47 | def destroy 48 | @recipe.destroy 49 | 50 | back_to = params[:back_to] || root_path 51 | 52 | redirect_to back_to, notice: t(".notice"), turbolinks: :replace 53 | end 54 | 55 | private 56 | 57 | def build_recipe(params={}) 58 | current_user.recipes.build(params) 59 | end 60 | 61 | def set_recipe 62 | @recipe = Recipe.friendly.find(params[:id]) 63 | 64 | authorize @recipe 65 | end 66 | 67 | def recipe_params 68 | params.require(:recipe).permit(:title, :story, :ingredients, :instructions) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /app/controllers/users/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::PasswordsController < Devise::PasswordsController 2 | include MetaTagsHelpers 3 | 4 | respond_to :html, :js 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/users/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::RegistrationsController < Devise::RegistrationsController 2 | include MetaTagsHelpers 3 | include DevisePermittedParameters 4 | 5 | respond_to :html, :js 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/users/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::SessionsController < Devise::SessionsController 2 | include MetaTagsHelpers 3 | include TurbolinksHelpers 4 | 5 | respond_to :html, :js 6 | 7 | before_action :verify_destroy_request, only: :destroy 8 | skip_before_action :verify_authenticity_token, if: :js_request? 9 | 10 | # https://github.com/plataformatec/devise/blob/v4.2.0/app/controllers/devise/sessions_controller.rb#L73 11 | def respond_to_on_destroy 12 | respond_to do |format| 13 | format.js { redirect_after_sign_out turbolinks: :advance } 14 | format.all { head :no_content } 15 | format.any(*navigational_formats) { redirect_after_sign_out } 16 | end 17 | end 18 | 19 | private 20 | 21 | def js_request? 22 | request.format.js? 23 | end 24 | 25 | def verify_destroy_request 26 | # Ensure that the sing_out via get works only when 27 | # the is request has the Turbolinks User Agent 28 | if !request.delete? && !turbolinks_app? 29 | raise AbstractController::ActionNotFound 30 | end 31 | end 32 | 33 | def redirect_after_sign_out(turbolinks: false) 34 | redirect_to(after_sign_out_path_for(resource_name), turbolinks: turbolinks) 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /app/controllers/users/settings_controller.rb: -------------------------------------------------------------------------------- 1 | class Users::SettingsController < ApplicationController 2 | def change_locale 3 | locale = String(params[:locale]).tap(&:strip!)&.to_sym 4 | locale = I18n.default_locale unless I18n.available_locales.include?(locale) 5 | 6 | cookies.permanent[:current_locale] = locale 7 | 8 | redirect_back_with_default_fallback or: params[:back_to], turbolinks: :advance 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def body_data 3 | { controller: controller_name, action: action_name } 4 | end 5 | 6 | def body_class(class_names = nil) 7 | "#{controller_name} #{action_name} #{class_names}" 8 | end 9 | 10 | def mobile_variant? 11 | request.variant.any? { |key| key == :msite || key == :app } 12 | end 13 | 14 | def confirm_dialog_data(message, key=nil) 15 | confirm_key = key || :confirm 16 | 17 | { confirm_key => message, cancel_btn: t('cancel_btn'.freeze), confirm_btn: t('confirm_btn'.freeze) } 18 | end 19 | 20 | def confirm_dialog_data_to_delete 21 | confirm_dialog_data t('confirm_question'.freeze) 22 | end 23 | 24 | def confirm_dialog_data_to_action_destroy 25 | confirm_dialog_data(t('confirm_question'.freeze), :confirm_msg).merge!(destroy: :action) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/helpers/home_helper.rb: -------------------------------------------------------------------------------- 1 | module HomeHelper 2 | def link_to_recipes_filter(filter) 3 | link_params = home_params.to_recipes_filter(filter) 4 | 5 | class_when_active = 'active'.freeze if home_params.current_filter?(filter) 6 | 7 | link_to t(".#{filter}"), root_path(link_params), { 8 | class: "btn btn-default #{class_when_active}", 9 | data: { 'turbolinks-action'.freeze => :replace } 10 | } 11 | end 12 | 13 | def has_recipes? 14 | return false if !user_signed_in? 15 | 16 | @count_user_recipes ||= current_user.recipes.count 17 | @count_user_recipes > 0 18 | end 19 | 20 | def no_recipes_message_component(text=nil) 21 | result = content_tag :p do 22 | content_tag :h3, class: 'text-center'.freeze do 23 | if block_given? 24 | yield 25 | else 26 | text 27 | end 28 | end 29 | end 30 | 31 | content_tag(:hr) + result 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/helpers/recipes_helper.rb: -------------------------------------------------------------------------------- 1 | module RecipesHelper 2 | def human_attribute(name) 3 | Recipe.human_attribute_name(name) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/themasterapp/master-app-rails-server/95d3ba06134df577276f01468cd71e4120c61d6b/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/concerns/user_token.rb: -------------------------------------------------------------------------------- 1 | # FIXME: Very weak strategy for a production App. 2 | module UserToken 3 | extend ActiveSupport::Concern 4 | 5 | included do 6 | before_save :ensure_authentication_token 7 | end 8 | 9 | def ensure_authentication_token 10 | if authentication_token.blank? 11 | self.authentication_token = generate_authentication_token 12 | end 13 | end 14 | 15 | private 16 | 17 | def generate_authentication_token 18 | loop do 19 | token = Devise.friendly_token 20 | break token unless User.where(authentication_token: token).first 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/models/home/params.rb: -------------------------------------------------------------------------------- 1 | module Home 2 | class Params 3 | LATEST_FILTER = 'latest'.freeze 4 | MY_RECIPES_FILTER = 'my'.freeze 5 | 6 | attr_reader :params 7 | 8 | alias_method :raw, :params 9 | 10 | def initialize(params) 11 | @params = params 12 | end 13 | 14 | def search 15 | @search ||= params.permit(search: [:filter, :q])[:search] || {} 16 | end 17 | 18 | def search? 19 | search.present? 20 | end 21 | 22 | def to_search 23 | {search: {filter: MY_RECIPES_FILTER}} 24 | end 25 | 26 | def fetch_search(name, default = nil) 27 | return default unless search? 28 | 29 | search[name] || default 30 | end 31 | 32 | def current_filter 33 | @current_filter ||= fetch_search(:filter, LATEST_FILTER) 34 | end 35 | 36 | def current_filter?(filter) 37 | current_filter == String(filter) 38 | end 39 | 40 | def my_recipes_filter? 41 | current_filter == MY_RECIPES_FILTER 42 | end 43 | 44 | def build_filter_page_name(name) 45 | "#{name}_filter_page" 46 | end 47 | 48 | def current_filter_page_name 49 | @current_filter_page_name ||= build_filter_page_name(current_filter) 50 | end 51 | 52 | def current_filter_page_param 53 | params[current_filter_page_name] 54 | end 55 | 56 | def to_recipes_filter(filter) 57 | link_params = fetch_filter_page_params 58 | link_params[:search] = search.merge({filter: filter}).to_h 59 | link_params 60 | end 61 | 62 | private 63 | 64 | def fetch_filter_page_params 65 | @filter_pages ||= params.permit( 66 | build_filter_page_name(LATEST_FILTER), 67 | build_filter_page_name(MY_RECIPES_FILTER) 68 | ).to_h 69 | end 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /app/models/recipe.rb: -------------------------------------------------------------------------------- 1 | class Recipe < ApplicationRecord 2 | extend FriendlyId 3 | 4 | SLUG_CANDIDATES = [:title, [:title, :id]].freeze 5 | 6 | friendly_id :slug_candidates, use: [:slugged, :finders] 7 | 8 | belongs_to :user 9 | 10 | validates :title, :ingredients, :instructions, presence: true 11 | 12 | protected 13 | 14 | def slug_candidates 15 | SLUG_CANDIDATES 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | include UserToken 3 | 4 | # Include default devise modules. Others available are: 5 | # :confirmable, :lockable, :timeoutable and :omniauthable 6 | devise :database_authenticatable, :registerable, 7 | :recoverable, :rememberable, :trackable, :validatable 8 | 9 | enum role: { user: 0, admin: 1 } 10 | 11 | has_many :recipes 12 | 13 | validates :name, presence: true 14 | 15 | def send_devise_notification(notification, *args) 16 | devise_mailer.send(notification, self, *args).deliver_later 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/policies/application_policy.rb: -------------------------------------------------------------------------------- 1 | class ApplicationPolicy 2 | attr_reader :user, :record 3 | alias_method :current_user, :user 4 | 5 | def initialize(user, record) 6 | @user = user 7 | @record = record 8 | end 9 | 10 | def index? 11 | false 12 | end 13 | 14 | def show? 15 | scope.where(:id => record.id).exists? 16 | end 17 | 18 | def create? 19 | false 20 | end 21 | 22 | def new? 23 | create? 24 | end 25 | 26 | def update? 27 | false 28 | end 29 | 30 | def edit? 31 | update? 32 | end 33 | 34 | def destroy? 35 | false 36 | end 37 | 38 | def scope 39 | Pundit.policy_scope!(user, record.class) 40 | end 41 | 42 | class Scope 43 | attr_reader :user, :scope 44 | 45 | def initialize(user, scope) 46 | @user = user 47 | @scope = scope 48 | end 49 | 50 | def resolve 51 | scope 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/policies/recipe_policy.rb: -------------------------------------------------------------------------------- 1 | # ** Attributes ** 2 | # user, current_user = current_user 3 | # record = recipe 4 | 5 | class RecipePolicy < ApplicationPolicy 6 | def new? 7 | current_user.persisted? 8 | end 9 | 10 | def create? 11 | record.user_id == current_user&.id 12 | end 13 | 14 | def edit? 15 | create? 16 | end 17 | 18 | def update? 19 | create? 20 | end 21 | 22 | def destroy? 23 | create? 24 | end 25 | 26 | class Scope < ApplicationPolicy::Scope 27 | def resolve 28 | scope 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/policies/user_policy.rb: -------------------------------------------------------------------------------- 1 | class UserPolicy < ApplicationPolicy 2 | def index? 3 | current_user.admin? 4 | end 5 | 6 | def show? 7 | current_user.admin? or current_user == record 8 | end 9 | 10 | def update? 11 | return false if current_user == record 12 | 13 | current_user.admin? 14 | end 15 | 16 | def destroy? 17 | return false if current_user == record 18 | 19 | current_user.admin? 20 | end 21 | 22 | class Scope < ApplicationPolicy::Scope 23 | def resolve 24 | scope 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.erb: -------------------------------------------------------------------------------- 1 |
Welcome <%= @email %>!
2 | 3 |You can confirm your account email through the link below:
4 | 5 |<%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>
6 | -------------------------------------------------------------------------------- /app/views/devise/mailer/password_change.html.erb: -------------------------------------------------------------------------------- 1 |Hello <%= @resource.email %>!
2 | 3 |We're contacting you to notify you that your password has been changed.
4 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.erb: -------------------------------------------------------------------------------- 1 |<%= t(".hello", email: @resource.email) %>
2 | 3 |<%= t(".announcement") %>
4 | 5 |<%= link_to t(".change_my_password"), edit_password_url(@resource, reset_password_token: @token) %>
6 | 7 |<%= t(".ignore_message") %>
8 |<%= t(".notice_message") %>
9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.erb: -------------------------------------------------------------------------------- 1 |Hello <%= @resource.email %>!
2 | 3 |Your account has been locked due to an excessive number of unsuccessful sign in attempts.
4 | 5 |Click the link below to unlock your account:
6 | 7 |<%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>
8 | -------------------------------------------------------------------------------- /app/views/devise/passwords/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if controller.send(:successfully_sent?, resource) %> 2 | Turbolinks.clearCache(); 3 | Turbolinks.visit("<%= j controller.send(:new_session_path, resource) %>"); 4 | <% end %> 5 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.slim: -------------------------------------------------------------------------------- 1 | div class="row" 2 | div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12" 3 | div class="card sign_in" 4 | div class="header" 5 | h3 class="title" 6 | = t('.title') 7 | 8 | div class="content" 9 | = render('devise/passwords/edit/form') 10 | 11 | br 12 | 13 | p 14 | small= t('.sign_in_cta_html', url: new_session_path(resource_name)) + '.' 15 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit/_form.html.slim: -------------------------------------------------------------------------------- 1 | = bootstrap_form_for(resource, as: resource_name, url: password_path(resource_name), remote: true, html: { method: :put }) do |f| 2 | = f.hidden_field :reset_password_token 3 | 4 | - if @minimum_password_length 5 | = f.password_field :password, autocomplete: "off", placeholder: t(".password_confirmation_help", length: @minimum_password_length) 6 | - else 7 | = f.password_field :password, autocomplete: "off" 8 | 9 | = f.password_field :password_confirmation, autocomplete: "off" 10 | 11 | = f.submit t(".submit_button"), class: "btn btn-primary btn-block btn-fill" 12 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html+app.slim: -------------------------------------------------------------------------------- 1 | div class="row" 2 | div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12" 3 | div class="card" 4 | div class="header" 5 | h3 class="title" 6 | = t('.welcome') 7 | 8 | div class="content" 9 | = bootstrap_form_for(resource, as: resource_name, url: password_path(resource_name), remote: true, html: { method: :post }) do |f| 10 | = f.email_field :email, autofocus: true, placeholder: User.human_attribute_name(:name), skip_label: true 11 | 12 | = f.submit t(".submit"), class: "btn btn-primary btn-block btn-fill" 13 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.slim: -------------------------------------------------------------------------------- 1 | div class="row" 2 | div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12" 3 | div class="card" 4 | div class="header" 5 | h3 class="title" 6 | = t('.welcome') 7 | 8 | div class="content" 9 | = bootstrap_form_for(resource, as: resource_name, url: password_path(resource_name), remote: true, html: { method: :post }) do |f| 10 | = f.email_field :email, autofocus: true, placeholder: User.human_attribute_name(:name), skip_label: true 11 | 12 | = f.submit t(".submit"), class: "btn btn-primary btn-block btn-fill" 13 | 14 | br 15 | 16 | p 17 | small= t('.sign_in_cta_html', url: new_session_path(resource_name)) + '.' 18 | -------------------------------------------------------------------------------- /app/views/devise/passwords/update.js.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.present? %> 2 | <% resource.errors.full_messages.each do |msg| %> 3 | RailsFlashMessagesAsNotifications.alert("<%= j msg %>"); 4 | <% end %> 5 | <% else %> 6 | Turbolinks.clearCache(); 7 | Turbolinks.visit("<%= j controller.send(:after_resetting_password_path_for, resource) %>"); 8 | <% end %> 9 | -------------------------------------------------------------------------------- /app/views/devise/registrations/create.js+app.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.present? %> 2 | $('form[class*="_user"]').replaceWith("<%= j render('devise/registrations/new/form') %>"); 3 | <% else %> 4 | <% if user_signed_in? %> 5 | TurbolinksNativeMessageHandler.postMessage({auth_token: "<%= j current_user.authentication_token %>"}); 6 | <% end %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /app/views/devise/registrations/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.present? %> 2 | $('form[class*="_user"]').replaceWith("<%= j render('devise/registrations/new/form') %>"); 3 | <% else %> 4 | <% if resource.persisted? %> 5 | <% after_path_method = resource.active_for_authentication? ? :after_sign_up_path_for : :after_inactive_sign_up_path_for %> 6 | 7 | Turbolinks.clearCache(); 8 | Turbolinks.visit("<%= j controller.send(after_path_method, resource) %>"); 9 | <% end %> 10 | <% end %> 11 | -------------------------------------------------------------------------------- /app/views/devise/registrations/destroy.js.erb: -------------------------------------------------------------------------------- 1 | <% unless resource.persisted? %> 2 | Turbolinks.clearCache(); 3 | Turbolinks.visit("<%= j controller.send(:after_sign_out_path_for, resource_name) %>"); 4 | <% end %> 5 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.slim: -------------------------------------------------------------------------------- 1 | div class="row" 2 | div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12" 3 | div class="card sign_up" 4 | div class="header" 5 | h3 class="title" 6 | = "#{t('edit')} #{resource.class.model_name.human}" 7 | 8 | div class="content" 9 | = render('devise/registrations/edit/form') 10 | 11 | h4= t('.cancel_account.header') 12 | 13 | p 14 | small= t('.cancel_account.question') 15 | p= button_to t(".cancel_account.button"), registration_path(resource_name), data: confirm_dialog_data_to_action_destroy, class: "btn btn-danger btn-block btn-fill" 16 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit/_form.html.slim: -------------------------------------------------------------------------------- 1 | = bootstrap_form_for(resource, as: resource_name, url: registration_path(resource_name), remote: true, html: { method: :put }) do |f| 2 | = f.text_field :name 3 | = f.email_field :email 4 | 5 | - if devise_mapping.confirmable? && resource.pending_reconfirmation? 6 | div= t(".email_confirmation", email: resource.unconfirmed_email) 7 | 8 | - if @minimum_password_length 9 | = f.password_field :password, autocomplete: "off", placeholder: t(".password_confirmation.placeholder", length: @minimum_password_length), help: t(".password_confirmation.help") 10 | - else 11 | = f.password_field :password, autocomplete: "off", help: t(".password_confirmation.help") 12 | 13 | = f.password_field :password_confirmation, autocomplete: "off" 14 | = f.password_field :current_password, autocomplete: "off", help: t(".current_password_help") 15 | 16 | = f.submit t("update"), class: "btn btn-primary btn-block btn-fill" 17 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.slim: -------------------------------------------------------------------------------- 1 | div class="row" 2 | div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12" 3 | div class="card sign_up" 4 | div class="header" 5 | h3 class="title" 6 | = t('.welcome') 7 | 8 | div class="content" 9 | = render('devise/registrations/new/form') 10 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new/_form.html+app.slim: -------------------------------------------------------------------------------- 1 | = bootstrap_form_for(resource, as: resource_name, url: registration_path(resource_name), remote: true) do |f| 2 | = f.text_field :name, autofocus: true, placeholder: User.human_attribute_name(:name), skip_label: true 3 | 4 | = f.email_field :email, placeholder: User.human_attribute_name(:email), skip_label: true 5 | 6 | = f.password_field :password, autocomplete: "off", placeholder: User.human_attribute_name(:password), skip_label: true 7 | 8 | - if @minimum_password_length 9 | = f.password_field :password_confirmation, autocomplete: "off", placeholder: User.human_attribute_name(:password_confirmation), skip_label: true, help: t(".password_confirmation_help", length: @minimum_password_length) 10 | - else 11 | = f.password_field :password_confirmation, autocomplete: "off", placeholder: User.human_attribute_name(:password_confirmation), skip_label: true 12 | 13 | - if ENV['HIDE_TERMS_AND_POLICY'.freeze].blank? 14 | div class="form-group" 15 | div class="checkbox" 16 | label 17 | input type="checkbox" 18 | = t(".terms_and_policy") 19 | 20 | = f.submit t(".sign_up"), class: "btn btn-primary btn-block btn-fill" 21 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new/_form.html.slim: -------------------------------------------------------------------------------- 1 | = bootstrap_form_for(resource, as: resource_name, url: registration_path(resource_name), remote: true) do |f| 2 | = f.text_field :name, autofocus: true, placeholder: User.human_attribute_name(:name), skip_label: true 3 | 4 | = f.email_field :email, placeholder: User.human_attribute_name(:email), skip_label: true 5 | 6 | = f.password_field :password, autocomplete: "off", placeholder: User.human_attribute_name(:password), skip_label: true 7 | 8 | - if @minimum_password_length 9 | = f.password_field :password_confirmation, autocomplete: "off", placeholder: User.human_attribute_name(:password_confirmation), skip_label: true, help: t(".password_confirmation_help", length: @minimum_password_length) 10 | - else 11 | = f.password_field :password_confirmation, autocomplete: "off", placeholder: User.human_attribute_name(:password_confirmation), skip_label: true 12 | 13 | - if ENV['HIDE_TERMS_AND_POLICY'.freeze].blank? 14 | div class="form-group" 15 | div class="checkbox" 16 | label 17 | input type="checkbox" 18 | = t(".terms_and_policy") 19 | 20 | = f.submit t(".sign_up"), class: "btn btn-primary btn-block btn-fill" 21 | 22 | p class="text-center" 23 | small= t(".sign_in_cta") 24 | 25 | - if controller_name != 'sessions' 26 | = link_to t(".sign_in"), new_session_path(resource_name), class: "btn btn-sm btn-block" 27 | -------------------------------------------------------------------------------- /app/views/devise/registrations/update.js.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.present? %> 2 | $('form[class*="_user"]').replaceWith("<%= j render('devise/registrations/edit/form') %>"); 3 | <% else %> 4 | <% if resource.persisted? %> 5 | Turbolinks.clearCache(); 6 | Turbolinks.visit("<%= j controller.send(:after_update_path_for, resource) %>"); 7 | <% end %> 8 | <% end %> 9 | -------------------------------------------------------------------------------- /app/views/devise/sessions/create.js+app.erb: -------------------------------------------------------------------------------- 1 | <% if resource.errors.present? %> 2 | $('form[class*="_user"]').replaceWith("<%= j render('devise/registrations/new/form') %>"); 3 | <% else %> 4 | TurbolinksNativeMessageHandler.postMessage({auth_token: "<%= j current_user.authentication_token %>"}); 5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/views/devise/sessions/create.js.erb: -------------------------------------------------------------------------------- 1 | <% if user_signed_in? %> 2 | Turbolinks.clearCache(); 3 | Turbolinks.visit("<%= j controller.send(:after_sign_in_path_for, resource) %>"); 4 | <% end %> 5 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.slim: -------------------------------------------------------------------------------- 1 | div class="row" 2 | div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12" 3 | div class="card sign_in" 4 | div class="header" 5 | h3 class="title" 6 | = t('.welcome') 7 | 8 | div class="content" 9 | = render('devise/sessions/new/form') 10 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.js.erb: -------------------------------------------------------------------------------- 1 | <% unless user_signed_in? %> 2 | <% 3 | # https://github.com/plataformatec/devise/blob/4c3838bb759ec741558ecf86bd6cf01465043e4c/lib/devise/failure_app.rb#L103 4 | scope_class = Devise.mappings[Devise.default_scope].to 5 | auth_keys = scope_class.authentication_keys 6 | keys = (auth_keys.respond_to?(:keys) ? auth_keys.keys : auth_keys).map { |key| scope_class.human_attribute_name(key) } 7 | message = I18n.t("devise.failure.invalid", authentication_keys: keys.join(I18n.t(:"support.array.words_connector"))) 8 | %> 9 | 10 | RailsFlashMessagesAsNotifications.alert("<%= j message %>"); 11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new/_form.html.slim: -------------------------------------------------------------------------------- 1 | = bootstrap_form_for(resource, as: resource_name, url: session_path(resource_name), remote: true) do |f| 2 | = f.email_field :email, autofocus: true, placeholder: User.human_attribute_name(:email), skip_label: true 3 | 4 | = f.password_field :password, autocomplete: "off", placeholder: User.human_attribute_name(:password), skip_label: true 5 | 6 | - if devise_mapping.rememberable? && !turbolinks_app? 7 | = f.check_box :remember_me 8 | 9 | = f.submit t(".sign_in"), class: "btn btn-primary btn-fill btn-block" 10 | 11 | - if devise_mapping.recoverable? && controller_name != "passwords" && controller_name != "registrations" 12 | br 13 | p class="text-center" 14 | = link_to new_password_path(resource_name) do 15 | small= t(".forgot_password") 16 | 17 | p class="text-center" 18 | small= t(".account_question") 19 | 20 | - if devise_mapping.registerable? && controller_name != "registrations" 21 | = link_to t(".account_cta"), new_registration_path(resource_name), class: "btn btn-sm btn-block" 22 | -------------------------------------------------------------------------------- /app/views/errors/internal_server_error.html.slim: -------------------------------------------------------------------------------- 1 | div class="row errors" 2 | div class="col-md-8 col-md-offset-2 col-sm-8 col-sm-offset-2 col-xs-12" 3 | div class="card text-center" 4 | div class="header" 5 | h2 class="title" 6 | strong.text-danger= t(".status.code") 7 | 8 | div class="content" 9 | h4= t('.status.message') 10 | 11 | p= t('.description_html') 12 | p= link_to t('.homepage'), root_path, class: 'btn btn-primary btn-fill' 13 | -------------------------------------------------------------------------------- /app/views/errors/not_found.html.slim: -------------------------------------------------------------------------------- 1 | div class="row errors" 2 | div class="col-md-8 col-md-offset-2 col-sm-8 col-sm-offset-2 col-xs-12" 3 | div class="card text-center" 4 | div class="header" 5 | h2 class="title" 6 | strong.text-danger= t(".status.code") 7 | 8 | div class="content" 9 | h4= t('.status.message') 10 | 11 | div 12 | p= t('.description') 13 | 14 | p Que tal procurar uma receita deliciosa para compensar isso? 15 | 16 | = bootstrap_form_tag url: root_path, method: :get, data: {global_search: true} do |f| 17 | = f.text_field 'search[q]', placeholder: t('.form.placeholder'), hide_label: true 18 | = f.submit t('.form.submit'), class: 'btn btn-primary btn-fill' 19 | -------------------------------------------------------------------------------- /app/views/home/_recipe.html.slim: -------------------------------------------------------------------------------- 1 | div class="row recipe" 2 | div class="col-md-12 recipe" 3 | hr 4 | 5 | p class="user" 6 | - if !home_params.my_recipes_filter? 7 | span class="text-default name" 8 | = recipe.user.name 9 | 10 | - if policy(recipe).edit? 11 | = link_to edit_recipe_path(recipe), class: 'btn btn-warning btn-xs btn-fill edit' do 12 | i class="ti-pencil-alt" 13 | = t('edit') 14 | 15 | - if home_params.my_recipes_filter? 16 | = link_to(recipe_path(recipe, back_to: request.fullpath), data: confirm_dialog_data_to_action_destroy, class: 'btn btn-danger btn-xs destroy') do 17 | i> class="ti-trash" 18 | = t('destroy') 19 | 20 | h4 21 | = link_to recipe.title, recipe 22 | 23 | - if recipe.story.present? 24 | p 25 | = recipe.story 26 | -------------------------------------------------------------------------------- /app/views/home/_recipes.html.slim: -------------------------------------------------------------------------------- 1 | - if home_params.my_recipes_filter? && !user_signed_in? 2 | = no_recipes_message_component t('.my_recipes_guest_html'.freeze, url: new_user_registration_path) 3 | - elsif home_params.my_recipes_filter? && !has_recipes? 4 | = no_recipes_message_component t('.my_recipes_user_html'.freeze, url: new_recipe_path) 5 | 6 | - else 7 | - if @recipes.present? 8 | = render partial: 'recipes_relation'.freeze 9 | - else 10 | = render partial: 'recipes_not_found'.freeze 11 | -------------------------------------------------------------------------------- /app/views/home/_recipes_not_found.html.slim: -------------------------------------------------------------------------------- 1 | = no_recipes_message_component do 2 | - if home_params.my_recipes_filter? 3 | = t('.message_for_my_search_html'.freeze, url: root_path(search: {q: params.dig(:search, :q)})) 4 | - else 5 | = t('.message_for_latest_search_html'.freeze, url: root_path) 6 | -------------------------------------------------------------------------------- /app/views/home/_recipes_relation.html.slim: -------------------------------------------------------------------------------- 1 | div class="recipes" 2 | - @recipes.each do |recipe| 3 | = render partial: 'recipe'.freeze, locals: {recipe: recipe} 4 | 5 | = paginate @recipes, remote: true, params: home_params.search, param_name: home_params.current_filter_page_name 6 | 7 | / Workaround to fix Chrome scrollTop issue 8 | a.scrollTopFixer style="display: none;" href="#scrollTopTarget" data-turbolinks="false" Top 9 | -------------------------------------------------------------------------------- /app/views/home/_recipes_search_filters.html.slim: -------------------------------------------------------------------------------- 1 | div class="recipes-search-filters col-md-6 col-sm-5 col-xs-12" 2 | div class="btn-group" role="group" aria-label="..." 3 | = link_to_recipes_filter(Home::Params::LATEST_FILTER) 4 | = link_to_recipes_filter(Home::Params::MY_RECIPES_FILTER) 5 | -------------------------------------------------------------------------------- /app/views/home/index.html+app.slim: -------------------------------------------------------------------------------- 1 | section class="row" 2 | div class="col-xs-12" 3 | div class="card" 4 | div class="header text-center" 5 | / Workaround to fix Chrome scrollTop issue 6 | h1#scrollTopTarget= t('titles.application') 7 | 8 | div class="recipes-search row" 9 | - if home_params.fetch_search(:q).present? 10 | = render partial: 'recipes_search_filters'.freeze 11 | 12 | div class="content" 13 | = render partial: 'recipes'.freeze 14 | -------------------------------------------------------------------------------- /app/views/home/index.html.slim: -------------------------------------------------------------------------------- 1 | section class="row" 2 | div class="col-xs-12" 3 | div class="card" 4 | div class="header text-center" 5 | h1= t('titles.application') 6 | 7 | div class="recipes-search row" 8 | = render partial: 'recipes_search_filters'.freeze 9 | 10 | div class="recipes-search-form col-md-6 col-sm-7 col-xs-12" 11 | = bootstrap_form_tag url: root_path, layout: :inline, method: :get, data: {global_search: true } do |f| 12 | = f.hidden_field 'search[filter]', value: home_params.current_filter 13 | = f.text_field 'search[q]', value: home_params.fetch_search(:q), placeholder: t('.search.placeholder'), class: 'recipes-search-input', hide_label: true 14 | = f.submit t('.search.button'), class: "btn btn-default btn-fill" 15 | 16 | div class="content" 17 | = render partial: 'recipes'.freeze 18 | -------------------------------------------------------------------------------- /app/views/home/index.js.erb: -------------------------------------------------------------------------------- 1 | <% if has_recipes? %> 2 | $('.recipes-search-filters').replaceWith("<%= j render('home/recipes_search_filters'.freeze) %>"); 3 | <% end %> 4 | 5 | $('.recipes').replaceWith("<%= j render('home/recipes'.freeze) %>"); 6 | 7 | setTimeout( 8 | function() { 9 | // Workaround to fix Chrome scrollTop issue 10 | <% if turbolinks_app? %> 11 | TurbolinksNativeMessageHandler.postMessage({scrollTop: true}); 12 | <% else %> 13 | $('.scrollTopFixer')[0].click(); 14 | <% end %> 15 | $('[data-animate-css]').animateCSS('fadeIn'); 16 | }, 17 | 0 18 | ); 19 | -------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.first?, raw(t 'views.pagination.first'), 3 | url, remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.slim: -------------------------------------------------------------------------------- 1 | li.disabled 2 | = link_to raw(t 'views.pagination.truncate'), '#' 3 | -------------------------------------------------------------------------------- /app/views/kaminari/_last_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.last?, raw(t 'views.pagination.last'), 3 | url, remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.last?, raw(t 'views.pagination.next'), 3 | url, rel: 'next', remote: remote 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.slim: -------------------------------------------------------------------------------- 1 | li class="#{'active' if page.current?}" 2 | = link_to page, page.current? ? '#' : url, 3 | remote: remote, rel: page.next? ? 'next' : page.prev? ? 'prev' : nil 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.slim: -------------------------------------------------------------------------------- 1 | = paginator.render do 2 | ul.pagination 3 | == first_page_tag unless current_page.first? || mobile_variant? 4 | /== prev_page_tag unless current_page.first? 5 | 6 | - each_page do |page| 7 | - if page.left_outer? || page.right_outer? || page.inside_window? 8 | == page_tag page 9 | - elsif !page.was_truncated? 10 | == gap_tag 11 | 12 | /== next_page_tag unless current_page.last? 13 | == last_page_tag unless current_page.last? || mobile_variant? 14 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.slim: -------------------------------------------------------------------------------- 1 | li 2 | = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), 3 | url, rel: 'prev', remote: remote 4 | -------------------------------------------------------------------------------- /app/views/layouts/_flash_messages.html.slim: -------------------------------------------------------------------------------- 1 | - if flash.present? 2 | ul.hidden id="flash-messages" 3 | - flash.each do |type, text| 4 | li data-type=type data-text=text 5 | -------------------------------------------------------------------------------- /app/views/layouts/_footer.html.slim: -------------------------------------------------------------------------------- 1 | footer class="footer" 2 | div class="container-fluid" data-turbolinks="false" 3 | nav class="pull-left" 4 | ul 5 | li= link_to 'https://github.com/themasterapp'.freeze, target: '_blank'.freeze do 6 | | Github 7 | 8 | div class="copyright pull-right" 9 | | © #{t('app.release_year'.freeze)}, 10 | | #{t('.made_with_love_html'.freeze, contact: mail_to(t('company.email'.freeze), t('company.name'.freeze)))} 11 | -------------------------------------------------------------------------------- /app/views/layouts/_sidebar.html.slim: -------------------------------------------------------------------------------- 1 | / Tip 1: you can change the color of the sidebar's background using: data-background-color="white | black" 2 | / Tip 2: you can change the color of the active button using the data-active-color="primary | info | success | warning | danger" 3 | 4 | div class="sidebar" data-background-color="white" data-active-color="danger" 5 | div class="sidebar-wrapper" 6 | div class="logo" 7 | = link_to t('titles.application'), root_path, class: "simple-text" 8 | 9 | ul class="nav" 10 | - if !user_signed_in? 11 | li class="#{'active' if current_page?(new_user_session_path)}" 12 | = link_to new_user_session_path do 13 | i> class="ti-key" 14 | p= t('app.nav.views.sessions.new') 15 | 16 | li class="#{'active' if current_page?(new_user_registration_path)}" 17 | = link_to new_user_registration_path do 18 | i> class="ti-user" 19 | p= t('app.nav.views.registrations.new') 20 | li class="#{'active' if current_page?(root_path) && !home_params.my_recipes_filter?}" 21 | = link_to root_path do 22 | i> class="ti-book" 23 | p= t('app.nav.views.home.index') 24 | 25 | li class="#{'active' if home_params.my_recipes_filter?}" 26 | = link_to root_path(home_params.to_search) do 27 | i> class="ti-write" 28 | p= t('home.recipes_search_filters.my') 29 | 30 | - if user_signed_in? 31 | li class="#{'active' if current_page?(new_recipe_path)}" 32 | = link_to new_recipe_path do 33 | i> class="ti-pencil" 34 | p= t('app.nav.views.recipes.new') 35 | -------------------------------------------------------------------------------- /app/views/layouts/_topnavbar.html.slim: -------------------------------------------------------------------------------- 1 | nav class="navbar navbar-default" 2 | div class="container-fluid" 3 | div.navbar-header data-turbolinks="false" 4 | = render('layouts/navbar/header') 5 | 6 | div class="collapse navbar-collapse" 7 | ul class="nav navbar-nav navbar-right" 8 | 9 | - if !user_signed_in? 10 | = render('layouts/navbar/locales/for_guests') 11 | 12 | - else 13 | = render('layouts/navbar/settings') 14 | -------------------------------------------------------------------------------- /app/views/layouts/application.html+app.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html data-turbolinks-native-message-handler=ENV.fetch('TURBOLINKS_NATIVE_MESSAGE_HANDLER'.freeze) 3 | head 4 | / See meta-tags gem and MetaTagsHelpers concern. 5 | = display_meta_tags 6 | 7 | meta charset="utf-8" 8 | meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" 9 | meta http-equiv="X-UA-Compatible" content="IE=edge" 10 | 11 | = csrf_meta_tags 12 | 13 | = stylesheet_link_tag "application", media: "all", "data-turbolinks-track": "reload" 14 | link href='https://fonts.googleapis.com/css?family=Muli:400,300' rel='stylesheet' type='text/css' 15 | 16 | 17 | = javascript_include_tag "application", "data-turbolinks-track": "reload" 18 | 19 | body class=body_class("app-mode #{'android-app' if turbolinks_android_app?}") *body_data 20 | div class="wrapper" data-auto-navbar-collapse="true" 21 | = render "layouts/flash_messages" 22 | 23 | div class="main-panel" 24 | div class="content" data-animate-css="" 25 | div class="container-fluid" 26 | = yield 27 | -------------------------------------------------------------------------------- /app/views/layouts/application.html+msite.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | / See meta-tags gem and MetaTagsHelpers concern. 5 | = display_meta_tags 6 | 7 | meta charset="utf-8" 8 | meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" 9 | meta http-equiv="X-UA-Compatible" content="IE=edge" 10 | 11 | = csrf_meta_tags 12 | 13 | = stylesheet_link_tag "application", media: "all", "data-turbolinks-track": "reload" 14 | link href='https://fonts.googleapis.com/css?family=Muli:400,300' rel='stylesheet' type='text/css' 15 | 16 | 17 | = javascript_include_tag "application", "data-turbolinks-track": "reload" 18 | 19 | body class=body_class('msite-mode'.freeze) *body_data 20 | div class="wrapper" data-auto-navbar-collapse="true" 21 | = render "layouts/flash_messages" 22 | 23 | = render "layouts/sidebar" 24 | 25 | div class="main-panel" 26 | 27 | = render "layouts/topnavbar" 28 | 29 | div class="content" data-animate-css="" 30 | div class="container-fluid" 31 | = yield 32 | 33 | = render "layouts/footer" 34 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.slim: -------------------------------------------------------------------------------- 1 | doctype html 2 | html 3 | head 4 | / See meta-tags gem and MetaTagsHelpers concern. 5 | = display_meta_tags 6 | 7 | meta charset="utf-8" 8 | meta name="viewport" content="width=device-width, initial-scale=1.0" 9 | meta http-equiv="X-UA-Compatible" content="IE=edge" 10 | 11 | = csrf_meta_tags 12 | 13 | = stylesheet_link_tag "application", media: "all", "data-turbolinks-track": "reload" 14 | link href='https://fonts.googleapis.com/css?family=Muli:400,300' rel='stylesheet' type='text/css' 15 | 16 | 17 | = javascript_include_tag "application", "data-turbolinks-track": "reload" 18 | 19 | body class=body_class('site-mode'.freeze) *body_data 20 | div class="wrapper" 21 | = render "layouts/flash_messages" 22 | 23 | = render "layouts/sidebar" 24 | 25 | div class="main-panel" data-animate-css="" 26 | 27 | = render "layouts/topnavbar" 28 | 29 | div class="content" 30 | div class="container-fluid" 31 | = yield 32 | 33 | = render "layouts/footer" 34 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/views/layouts/navbar/_header.html.slim: -------------------------------------------------------------------------------- 1 | button type="button" class="navbar-toggle" 2 | span.sr-only= t('app.nav.toggle_navigation'.freeze) 3 | span class="icon-bar bar1" 4 | span class="icon-bar bar2" 5 | span class="icon-bar bar3" 6 | 7 | / Workaround to fix Chrome scrollTop issue 8 | a class="navbar-brand" id="scrollTopTarget" 9 | = t("app.nav.views.#{controller_name}.#{action_name}") 10 | -------------------------------------------------------------------------------- /app/views/layouts/navbar/_settings.html.slim: -------------------------------------------------------------------------------- 1 | li class="dropdown" 2 | a href="#" class="dropdown-toggle" data-toggle="dropdown" data-turbolinks="false" 3 | i> class="ti-settings" 4 | p= t('app.nav.settings') 5 | b class="caret" 6 | 7 | ul class="dropdown-menu" 8 | li= link_to edit_user_registration_path do 9 | = t('app.nav.views.registrations.edit') 10 | 11 | li= render('layouts/navbar/locales/for_users') 12 | 13 | li= link_to destroy_user_session_path, method: :delete, remote: true do 14 | = t('app.nav.log_out') 15 | -------------------------------------------------------------------------------- /app/views/layouts/navbar/locales/_for_guests.html.slim: -------------------------------------------------------------------------------- 1 | / # TODO: Create a component/helper and improve the layout according to the variant 2 | li 3 | = link_to url_for(params.to_unsafe_hash.merge!(locale: I18n.locale == :en ? :"pt-BR" : :en)), data: {locale: true} do 4 | i> class="ti-world" 5 | p= t('.call_to_action_html'.freeze) 6 | -------------------------------------------------------------------------------- /app/views/layouts/navbar/locales/_for_users.html.slim: -------------------------------------------------------------------------------- 1 | / # TODO: Create a component/helper and improve the layout according to the variant 2 | = link_to url_for(params.to_unsafe_hash.merge!(locale: I18n.locale == :en ? :"pt-BR" : :en)), data: {locale: true} do 3 | = t('.call_to_action_html'.freeze) 4 | -------------------------------------------------------------------------------- /app/views/recipes/_form.html.slim: -------------------------------------------------------------------------------- 1 | = bootstrap_form_for @recipe, remote: true do |f| 2 | 3 | = f.text_field :title, autofocus: true, placeholder: t('.placeholders.title'), label: human_attribute(:title) 4 | = f.text_area :story, autofocus: true, placeholder: t('.placeholders.story'), label: human_attribute(:story) 5 | = f.text_area :ingredients, placeholder: t('.placeholders.ingredients'), label: human_attribute(:ingredients) 6 | = f.text_area :instructions, placeholder: t('.placeholders.instructions'), label: human_attribute(:instructions) 7 | 8 | = f.submit t('.submit'), class: 'btn btn-primary btn-fill' 9 | -------------------------------------------------------------------------------- /app/views/recipes/edit.html.slim: -------------------------------------------------------------------------------- 1 | div class="row" 2 | div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12" 3 | div class="card" 4 | div class="header" 5 | h3 class="title" 6 | = t('.welcome') 7 | i< class="ti-medall text-warning" 8 | 9 | div class="content" 10 | = render('recipes/form') 11 | -------------------------------------------------------------------------------- /app/views/recipes/new.html.slim: -------------------------------------------------------------------------------- 1 | div class="row" 2 | div class="col-md-6 col-md-offset-3 col-sm-6 col-sm-offset-3 col-xs-12" 3 | div class="card" 4 | div class="header" 5 | h3 class="title" 6 | = t('.welcome') 7 | i< class="ti-heart text-danger" 8 | 9 | div class="content" 10 | = render('recipes/form') 11 | -------------------------------------------------------------------------------- /app/views/recipes/save.js.erb: -------------------------------------------------------------------------------- 1 | $('form[class*="_recipe"]').replaceWith("<%= j render('recipes/form'.freeze) %>"); 2 | -------------------------------------------------------------------------------- /app/views/recipes/show.html.slim: -------------------------------------------------------------------------------- 1 | div class="row recipe" 2 | div class="col-md-8 col-md-offset-2 col-sm-8 col-sm-offset-2 col-xs-12" 3 | div class="card" 4 | div class="header" 5 | - if policy(@recipe).edit? 6 | p class="user" 7 | = link_to edit_recipe_path(@recipe), class: 'btn btn-warning btn-xs btn-fill edit' do 8 | i class="ti-pencil-alt" 9 | = t('edit') 10 | 11 | = link_to(recipe_path(@recipe), data: confirm_dialog_data_to_action_destroy, class: 'btn btn-danger btn-xs destroy') do 12 | i> class="ti-trash" 13 | = t('destroy') 14 | 15 | h2 class="title" 16 | strong= @recipe.title 17 | 18 | div class="content" 19 | p 20 | small.text-info=@recipe.user.name 21 | 22 | = simple_format(@recipe.story) 23 | 24 | h3 class="text-primary" 25 | = human_attribute(:ingredients) 26 | 27 | = simple_format(@recipe.ingredients) 28 | 29 | h3 class="text-primary" 30 | = human_attribute(:instructions) 31 | 32 | = simple_format(@recipe.instructions) 33 | 34 | 35 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'bin/rails db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'bin/rails log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'bin/rails restart' 34 | end 35 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq.join(Gem.path_separator) } 12 | gem 'spring', match[1] 13 | require 'spring/binstub' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative 'config/environment' 4 | 5 | run Rails.application 6 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | 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 MasterApp 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Errors Pages 16 | config.exceptions_app = self.routes 17 | 18 | # Active Job 19 | config.active_job.queue_adapter = :sucker_punch 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.1 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | development: 25 | <<: *default 26 | database: master-app_development 27 | 28 | # The specified database role being used to connect to postgres. 29 | # To create additional roles in postgres see `$ createuser --help`. 30 | # When left blank, postgres will use the default role. This is 31 | # the same name as the operating system user that initialized the database. 32 | #username: master-app 33 | 34 | # The password associated with the postgres role (username). 35 | #password: 36 | 37 | # Connect on a TCP socket. Omitted by default since the client uses a 38 | # domain socket that doesn't need configuration. Windows does not have 39 | # domain sockets, so uncomment these lines. 40 | #host: localhost 41 | 42 | # The TCP port the server listens on. Defaults to 5432. 43 | # If your server runs on a different port number, change accordingly. 44 | #port: 5432 45 | 46 | # Schema search path. The server defaults to $user,public 47 | #schema_search_path: myapp,sharedapp,public 48 | 49 | # Minimum log levels, in increasing order: 50 | # debug5, debug4, debug3, debug2, debug1, 51 | # log, notice, warning, error, fatal, and panic 52 | # Defaults to warning. 53 | #min_messages: notice 54 | 55 | # Warning: The database defined as "test" will be erased and 56 | # re-generated from your development database when you run "rake". 57 | # Do not set this db to the same as development or production. 58 | test: 59 | <<: *default 60 | database: master-app_test 61 | 62 | # As with config/secrets.yml, you never want to store sensitive information, 63 | # like your database password, in your source code. If your source code is 64 | # ever seen by anyone, they now have access to your database. 65 | # 66 | # Instead, provide the password as a unix environment variable when you boot 67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 68 | # for a full rundown on how to provide these environment variables in a 69 | # production deployment. 70 | # 71 | # On Heroku and other platform providers, you may have a full connection URL 72 | # available as an environment variable. For example: 73 | # 74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 75 | # 76 | # You can use this database configuration with: 77 | # 78 | # production: 79 | # url: <%= ENV['DATABASE_URL'] %> 80 | # 81 | production: 82 | <<: *default 83 | database: master-app_production 84 | username: master-app 85 | password: <%= ENV['MASTER-APP_DATABASE_PASSWORD'] %> 86 | -------------------------------------------------------------------------------- /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 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports. 13 | config.consider_all_requests_local = true 14 | 15 | # Enable/disable caching. By default caching is disabled. 16 | if Rails.root.join('tmp/caching-dev.txt').exist? 17 | config.action_controller.perform_caching = true 18 | 19 | config.cache_store = :memory_store 20 | config.public_file_server.headers = { 21 | 'Cache-Control' => 'public, max-age=172800' 22 | } 23 | else 24 | config.action_controller.perform_caching = false 25 | 26 | config.cache_store = :null_store 27 | end 28 | 29 | config.action_mailer.default_url_options = { host: ENV.fetch('APPLICATION_HOST'), port: ENV.fetch('PORT') } 30 | config.action_mailer.delivery_method = :letter_opener 31 | 32 | # Don't care if the mailer can't send. 33 | config.action_mailer.raise_delivery_errors = false 34 | 35 | config.action_mailer.perform_caching = false 36 | 37 | # Print deprecation notices to the Rails logger. 38 | config.active_support.deprecation = :log 39 | 40 | # Raise an error on page load if there are pending migrations. 41 | config.active_record.migration_error = :page_load 42 | 43 | # Debug mode disables concatenation and preprocessing of assets. 44 | # This option may cause significant delays in view rendering with a large 45 | # number of complex assets. 46 | config.assets.debug = true 47 | 48 | # Suppress logger output for asset requests. 49 | config.assets.quiet = true 50 | 51 | # Raises error for missing translations 52 | config.action_view.raise_on_missing_translations = true 53 | 54 | # Use an evented file watcher to asynchronously detect changes in source code, 55 | # routes, locales, etc. This feature depends on the listen gem. 56 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 57 | end 58 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Disable serving static files from the `/public` folder by default since 18 | # Apache or NGINX already handles this. 19 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 20 | 21 | # Compress JavaScripts and CSS. 22 | config.assets.js_compressor = :uglifier 23 | # config.assets.css_compressor = :sass 24 | 25 | # Do not fallback to assets pipeline if a precompiled asset is missed. 26 | config.assets.compile = false 27 | 28 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 29 | 30 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 31 | # config.action_controller.asset_host = 'http://assets.example.com' 32 | 33 | # Specifies the header that your server uses for sending files. 34 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 35 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 36 | 37 | # Mount Action Cable outside main process or domain 38 | # config.action_cable.mount_path = nil 39 | # config.action_cable.url = 'wss://example.com/cable' 40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | config.force_ssl = ENV['APPLICATION_FORCE_SSL'].present? 44 | 45 | # Use the lowest log level to ensure availability of diagnostic information 46 | # when problems arise. 47 | config.log_level = :debug 48 | 49 | # Prepend all log lines with the following tags. 50 | config.log_tags = [ :request_id ] 51 | 52 | # Use a different cache store in production. 53 | # config.cache_store = :mem_cache_store 54 | 55 | # Use a real queuing backend for Active Job (and separate queues per environment) 56 | # config.active_job.queue_adapter = :resque 57 | # config.active_job.queue_name_prefix = "master-app_#{Rails.env}" 58 | config.action_mailer.perform_caching = false 59 | 60 | # Ignore bad email addresses and do not raise email delivery errors. 61 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 62 | # config.action_mailer.raise_delivery_errors = false 63 | 64 | config.action_mailer.default_url_options = { host: ENV.fetch('APPLICATION_HOST') } 65 | 66 | config.action_mailer.smtp_settings = { 67 | :address => ENV.fetch('SMTP_ADDRESS'), 68 | :port => ENV.fetch('SMTP_PORT', 587), 69 | :authentication => :plain, 70 | :user_name => ENV.fetch('SMTP_USERNAME'), 71 | :password => ENV.fetch('SMTP_PASSWORD'), 72 | :domain => ENV.fetch('SMTP_DOMAIN'), 73 | :enable_starttls_auto => true 74 | } 75 | 76 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 77 | # the I18n.default_locale when a translation cannot be found). 78 | config.i18n.fallbacks = true 79 | 80 | # Send deprecation notices to registered listeners. 81 | config.active_support.deprecation = :notify 82 | 83 | # Use default logging formatter so that PID and timestamp are not suppressed. 84 | config.log_formatter = ::Logger::Formatter.new 85 | 86 | # Use a different logger for distributed setups. 87 | # require 'syslog/logger' 88 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 89 | 90 | if ENV["RAILS_LOG_TO_STDOUT"].present? 91 | logger = ActiveSupport::Logger.new(STDOUT) 92 | logger.formatter = config.log_formatter 93 | config.logger = ActiveSupport::TaggedLogging.new(logger) 94 | end 95 | 96 | # Do not dump schema after migrations. 97 | config.active_record.dump_schema_after_migration = false 98 | end 99 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => 'public, max-age=3600' 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | config.action_mailer.perform_caching = false 31 | 32 | # Tell Action Mailer not to deliver emails to the real world. 33 | # The :test delivery method accumulates sent emails in the 34 | # ActionMailer::Base.deliveries array. 35 | config.action_mailer.delivery_method = :test 36 | 37 | config.action_mailer.default_url_options = { host: ENV.fetch('APPLICATION_HOST'), port: ENV.fetch('PORT') } 38 | 39 | # Print deprecation notices to the stderr. 40 | config.active_support.deprecation = :stderr 41 | 42 | # Raises error for missing translations 43 | config.action_view.raise_on_missing_translations = true 44 | end 45 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/friendly_id.rb: -------------------------------------------------------------------------------- 1 | # FriendlyId Global Configuration 2 | # 3 | # Use this to set up shared configuration options for your entire application. 4 | # Any of the configuration options shown here can also be applied to single 5 | # models by passing arguments to the `friendly_id` class method or defining 6 | # methods in your model. 7 | # 8 | # To learn more, check out the guide: 9 | # 10 | # http://norman.github.io/friendly_id/file.Guide.html 11 | 12 | FriendlyId.defaults do |config| 13 | # ## Reserved Words 14 | # 15 | # Some words could conflict with Rails's routes when used as slugs, or are 16 | # undesirable to allow as slugs. Edit this list as needed for your app. 17 | config.use :reserved 18 | 19 | config.reserved_words = %w(new edit index session login logout users admin 20 | stylesheets assets javascripts images) 21 | 22 | # ## Friendly Finders 23 | # 24 | # Uncomment this to use friendly finders in all models. By default, if 25 | # you wish to find a record by its friendly id, you must do: 26 | # 27 | # MyModel.friendly.find('foo') 28 | # 29 | # If you uncomment this, you can do: 30 | # 31 | # MyModel.find('foo') 32 | # 33 | # This is significantly more convenient but may not be appropriate for 34 | # all applications, so you must explicity opt-in to this behavior. You can 35 | # always also configure it on a per-model basis if you prefer. 36 | # 37 | # Something else to consider is that using the :finders addon boosts 38 | # performance because it will avoid Rails-internal code that makes runtime 39 | # calls to `Module.extend`. 40 | # 41 | # config.use :finders 42 | # 43 | # ## Slugs 44 | # 45 | # Most applications will use the :slugged module everywhere. If you wish 46 | # to do so, uncomment the following line. 47 | # 48 | # config.use :slugged 49 | # 50 | # By default, FriendlyId's :slugged addon expects the slug column to be named 51 | # 'slug', but you can change it if you wish. 52 | # 53 | # config.slug_column = 'slug' 54 | # 55 | # When FriendlyId can not generate a unique ID from your base method, it appends 56 | # a UUID, separated by a single dash. You can configure the character used as the 57 | # separator. If you're upgrading from FriendlyId 4, you may wish to replace this 58 | # with two dashes. 59 | # 60 | # config.sequence_separator = '-' 61 | # 62 | # ## Tips and Tricks 63 | # 64 | # ### Controlling when slugs are generated 65 | # 66 | # As of FriendlyId 5.0, new slugs are generated only when the slug field is 67 | # nil, but if you're using a column as your base method can change this 68 | # behavior by overriding the `should_generate_new_friendly_id` method that 69 | # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave 70 | # more like 4.0. 71 | # 72 | # config.use Module.new { 73 | # def should_generate_new_friendly_id? 74 | # slug.blank? ||Maybe you tried to change something you didn't have access to.
63 |If you are the application owner check the logs for more information.
65 |