├── .gitattributes ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── SECURITY.md ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── images │ │ └── .keep │ └── stylesheets │ │ ├── actiontext.css │ │ └── application.css ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── controllers │ ├── admin_controller.rb │ ├── after_signup_controller.rb │ ├── application_controller.rb │ ├── categories_controller.rb │ ├── checkouts_controller.rb │ ├── comments_controller.rb │ ├── concerns │ │ └── .keep │ ├── drag_controller.rb │ ├── members_controller.rb │ ├── pages_controller.rb │ ├── posts_controller.rb │ ├── projects_controller.rb │ ├── search_controller.rb │ ├── turbo_devise_controller.rb │ ├── users │ │ ├── confirmations_controller.rb │ │ ├── omniauth_callbacks_controller.rb │ │ ├── passwords_controller.rb │ │ ├── registrations_controller.rb │ │ ├── sessions_controller.rb │ │ └── unlocks_controller.rb │ └── users_controller.rb ├── helpers │ ├── admin_helper.rb │ ├── application_helper.rb │ ├── categories_helper.rb │ ├── checkouts_helper.rb │ ├── comments_helper.rb │ ├── drag_helper.rb │ ├── members_helper.rb │ ├── pages_helper.rb │ ├── posts_helper.rb │ ├── projects_helper.rb │ ├── search_helper.rb │ └── users_helper.rb ├── javascript │ ├── application.js │ ├── classes │ │ └── RichText.js │ ├── controllers │ │ ├── application.js │ │ ├── comments_controller.js │ │ ├── drag_controller.js │ │ ├── dropzone_controller.js │ │ ├── emoji_picker_controller.js │ │ ├── hello_controller.js │ │ └── index.js │ └── helpers │ │ └── dropzone.js ├── jobs │ └── application_job.rb ├── mailers │ └── application_mailer.rb ├── models │ ├── address.rb │ ├── ahoy │ │ ├── event.rb │ │ └── visit.rb │ ├── application_record.rb │ ├── category.rb │ ├── comment.rb │ ├── concerns │ │ ├── .keep │ │ └── subscription_concern.rb │ ├── notification.rb │ ├── post.rb │ ├── project.rb │ └── user.rb ├── notifications │ └── comment_notification.rb └── views │ ├── active_storage │ └── blobs │ │ └── _blob.html.erb │ ├── admin │ ├── _link.html.erb │ ├── _nav_links.html.erb │ ├── _total_daily_views.html.erb │ ├── comments.html.erb │ ├── index.html.erb │ ├── posts.html.erb │ ├── show_post.html.erb │ └── users.html.erb │ ├── after_signup │ ├── _links.html.erb │ ├── find_users.html.erb │ ├── set_address.html.erb │ └── set_name.html.erb │ ├── categories │ ├── _category.html.erb │ ├── _category.json.jbuilder │ ├── _form.html.erb │ ├── edit.html.erb │ ├── index.html.erb │ ├── index.json.jbuilder │ ├── new.html.erb │ ├── show.html.erb │ └── show.json.jbuilder │ ├── checkouts │ ├── show.html.erb │ └── success.html.erb │ ├── comments │ ├── _comment.html.erb │ └── _form.html.erb │ ├── devise │ ├── confirmations │ │ └── new.html.erb │ ├── mailer │ │ ├── confirmation_instructions.html.erb │ │ ├── email_changed.html.erb │ │ ├── password_change.html.erb │ │ ├── reset_password_instructions.html.erb │ │ └── unlock_instructions.html.erb │ ├── passwords │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── registrations │ │ ├── edit.html.erb │ │ └── new.html.erb │ ├── sessions │ │ └── new.html.erb │ ├── shared │ │ ├── _error_messages.html.erb │ │ └── _links.html.erb │ └── unlocks │ │ └── new.html.erb │ ├── layouts │ ├── _alerts.html.erb │ ├── _categories.html.erb │ ├── _navbar.html.erb │ ├── _notification.html.erb │ ├── _notifications.html.erb │ ├── action_text │ │ └── contents │ │ │ └── _content.html.erb │ ├── application.html.erb │ ├── mailer.html.erb │ └── mailer.text.erb │ ├── members │ └── dashboard.html.erb │ ├── pages │ ├── about.html.erb │ └── home.html.erb │ ├── posts │ ├── _form.html.erb │ ├── _image_form.html.erb │ ├── _post.html.erb │ ├── _post.json.jbuilder │ ├── edit.html.erb │ ├── index.html.erb │ ├── index.json.jbuilder │ ├── new.html.erb │ ├── show.html.erb │ └── show.json.jbuilder │ ├── projects │ ├── _draggable.html.erb │ ├── _form.html.erb │ ├── _project.html.erb │ ├── _project.json.jbuilder │ ├── edit.html.erb │ ├── index.html.erb │ ├── index.json.jbuilder │ ├── new.html.erb │ ├── show.html.erb │ └── show.json.jbuilder │ ├── search │ ├── _form.html.erb │ └── index.html.erb │ ├── user │ └── _session_manager.html.erb │ └── users │ └── profile.html.erb ├── bin ├── bundle ├── importmap ├── rails ├── rake └── setup ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── importmap.rb ├── initializers │ ├── ahoy.rb │ ├── assets.rb │ ├── content_security_policy.rb │ ├── devise.rb │ ├── filter_parameter_logging.rb │ ├── friendly_id.rb │ ├── inflections.rb │ └── permissions_policy.rb ├── locales │ ├── devise.en.yml │ └── en.yml ├── puma.rb ├── routes.rb ├── schedule.rb └── storage.yml ├── db ├── migrate │ ├── 20220131064930_create_posts.rb │ ├── 20220209082551_add_views_to_posts.rb │ ├── 20220209083507_devise_create_users.rb │ ├── 20220209085715_add_user_to_posts.rb │ ├── 20220209090418_add_name_to_user.rb │ ├── 20220209091520_add_views_to_user.rb │ ├── 20220209091605_change_views_for_users.rb │ ├── 20220216073155_create_comments.rb │ ├── 20220216073214_create_active_storage_tables.active_storage.rb │ ├── 20220216073215_create_action_text_tables.action_text.rb │ ├── 20220302075632_create_notifications.rb │ ├── 20220316052648_add_role_to_user.rb │ ├── 20220316052704_remove_body_from_post.rb │ ├── 20220323053602_add_slug_to_posts.rb │ ├── 20220323053611_create_friendly_id_slugs.rb │ ├── 20220330053457_add_comment_counter_cache_to_posts.rb │ ├── 20220330053548_populate_post_comments_count.rb │ ├── 20220405234838_change_json_column_in_notifications.rb │ ├── 20220413060450_add_names_to_user.rb │ ├── 20220413060517_create_addresses.rb │ ├── 20220413060531_add_address_to_user.rb │ ├── 20220413060545_remove_name_from_user.rb │ ├── 20220427062239_create_categories.rb │ ├── 20220427062305_add_category_to_posts.rb │ ├── 20220511071651_create_pay_tables.pay.rb │ ├── 20220511072519_add_billing_location_to_user.rb │ ├── 20220518082713_add_customer_info_to_user.rb │ ├── 20220608093644_create_projects.rb │ ├── 20220629063516_create_ahoy_visits_and_events.rb │ └── 20220706092028_add_position_to_projects.rb ├── schema.rb ├── seeds.rb └── seeds │ ├── development.rb │ ├── production.rb │ └── test.rb ├── episode_12.txt ├── example.txt ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── favicon.ico └── robots.txt ├── rails_blog ├── rails_blog_sysv ├── scripts └── stripe.sh ├── storage └── .keep ├── test.txt ├── test ├── application_system_test_case.rb ├── channels │ └── application_cable │ │ └── connection_test.rb ├── controllers │ ├── .keep │ ├── admin_controller_test.rb │ ├── categories_controller_test.rb │ ├── checkouts_controller_test.rb │ ├── comments_controller_test.rb │ ├── drag_controller_test.rb │ ├── members_controller_test.rb │ ├── pages_controller_test.rb │ ├── posts_controller_test.rb │ ├── projects_controller_test.rb │ ├── search_controller_test.rb │ └── users_controller_test.rb ├── fixtures │ ├── action_text │ │ └── rich_texts.yml │ ├── addresses.yml │ ├── categories.yml │ ├── comments.yml │ ├── files │ │ └── .keep │ ├── notifications.yml │ ├── posts.yml │ ├── projects.yml │ └── users.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── address_test.rb │ ├── category_test.rb │ ├── comment_test.rb │ ├── notification_test.rb │ ├── post_test.rb │ ├── project_test.rb │ └── user_test.rb ├── system │ ├── .keep │ ├── categories_test.rb │ ├── posts_test.rb │ └── projects_test.rb └── test_helper.rb ├── tmp ├── .keep ├── pids │ └── .keep └── storage │ └── .keep └── vendor ├── .keep └── javascript └── .keep /.gitattributes: -------------------------------------------------------------------------------- 1 | # See https://git-scm.com/docs/gitattributes for more about git attribute files. 2 | 3 | # Mark the database schema as having been generated. 4 | db/schema.rb linguist-generated 5 | 6 | # Mark any vendored files as having been vendored. 7 | vendor/* linguist-vendored 8 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '41 2 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'javascript', 'ruby' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v3 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v2 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | 52 | # Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 53 | # queries: security-extended,security-and-quality 54 | 55 | 56 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 57 | # If this step fails, then you should remove it and run the build manually (see below) 58 | - name: Autobuild 59 | uses: github/codeql-action/autobuild@v2 60 | 61 | # ℹ️ Command-line programs to run using the OS shell. 62 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 63 | 64 | # If the Autobuild fails above, remove it and uncomment the following three lines. 65 | # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. 66 | 67 | # - run: | 68 | # echo "Run, Build Application using script" 69 | # ./location_of_script_within_repo/buildscript.sh 70 | 71 | - name: Perform CodeQL Analysis 72 | uses: github/codeql-action/analyze@v2 73 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-* 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | /tmp/* 17 | !/log/.keep 18 | !/tmp/.keep 19 | 20 | # Ignore pidfiles, but keep the directory. 21 | /tmp/pids/* 22 | !/tmp/pids/ 23 | !/tmp/pids/.keep 24 | 25 | # Ignore uploaded files in development. 26 | /storage/* 27 | !/storage/.keep 28 | /tmp/storage/* 29 | !/tmp/storage/ 30 | !/tmp/storage/.keep 31 | 32 | /public/assets 33 | 34 | # Ignore master key for decrypting credentials and more. 35 | /config/master.key 36 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-3.0.3 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 3 | 4 | ruby '3.0.3' 5 | 6 | # Bundle edge Rails instead: gem "rails", github: "rails/rails", branch: "main" 7 | gem 'rails', '~> 7.0.1' 8 | 9 | # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] 10 | gem 'sprockets-rails' 11 | 12 | # Use pg as the database for Active Record 13 | gem 'pg', '~> 1.1' 14 | 15 | # Use the Puma web server [https://github.com/puma/puma] 16 | gem 'puma', '~> 5.0' 17 | 18 | # Use JavaScript with ESM import maps [https://github.com/rails/importmap-rails] 19 | gem 'importmap-rails' 20 | 21 | # Hotwire's SPA-like page accelerator [https://turbo.hotwired.dev] 22 | gem 'turbo-rails' 23 | 24 | # Hotwire's modest JavaScript framework [https://stimulus.hotwired.dev] 25 | gem 'stimulus-rails' 26 | 27 | # Build JSON APIs with ease [https://github.com/rails/jbuilder] 28 | gem 'jbuilder' 29 | 30 | # Use Redis adapter to run Action Cable in production 31 | gem 'redis', '~> 4.0' 32 | 33 | # Use Kredis to get higher-level data types in Redis [https://github.com/rails/kredis] 34 | # gem "kredis" 35 | 36 | # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] 37 | # gem "bcrypt", "~> 3.1.7" 38 | 39 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 40 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 41 | 42 | # Reduces boot times through caching; required in config/boot.rb 43 | gem 'bootsnap', require: false 44 | 45 | # Use Sass to process CSS 46 | # gem "sassc-rails" 47 | 48 | # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] 49 | gem 'image_processing', '~> 1.2' 50 | 51 | group :development, :test do 52 | # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem 53 | gem 'debug', platforms: %i[mri mingw x64_mingw] 54 | end 55 | 56 | group :development do 57 | # Use console on exceptions pages [https://github.com/rails/web-console] 58 | gem 'better_errors' 59 | gem 'binding_of_caller' 60 | gem 'web-console' 61 | 62 | # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler] 63 | # gem "rack-mini-profiler" 64 | 65 | # Speed up commands on slow machines / big apps [https://github.com/rails/spring] 66 | # gem "spring" 67 | end 68 | 69 | group :test do 70 | # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] 71 | gem 'capybara' 72 | gem 'selenium-webdriver' 73 | gem 'webdrivers' 74 | end 75 | 76 | gem 'devise' 77 | 78 | gem 'noticed', '~> 1.5' 79 | 80 | gem 'ransack', '~> 2.5' 81 | 82 | gem 'friendly_id', '~> 5.4' 83 | 84 | gem 'bullet', '~> 7.0' 85 | 86 | gem 'activerecord-import', '~> 1.3' 87 | 88 | gem 'wicked', '~> 1.4' 89 | 90 | gem 'pay', '~> 4.0' 91 | gem 'stripe', '~> 6.0' 92 | 93 | gem 'ahoy_matey', '~> 4.1' 94 | 95 | gem 'groupdate', '~> 6.1' 96 | 97 | gem 'chartkick', '~> 4.2' 98 | 99 | gem 'acts_as_list', '~> 1.0' 100 | 101 | gem 'whenever', '~> 1.0' 102 | -------------------------------------------------------------------------------- /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 | 10 | * System dependencies 11 | 12 | * Configuration 13 | 14 | * Database creation 15 | 16 | * Database initialization 17 | 18 | * How to run the test suite 19 | 20 | * Services (job queues, cache servers, search engines, etc.) 21 | 22 | * Deployment instructions 23 | 24 | * ... 25 | 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Use this section to tell people about which versions of your project are 6 | currently being supported with security updates. 7 | 8 | | Version | Supported | 9 | | ------- | ------------------ | 10 | | 5.1.x | :white_check_mark: | 11 | | 5.0.x | :x: | 12 | | 4.0.x | :white_check_mark: | 13 | | < 4.0 | :x: | 14 | 15 | ## Reporting a Vulnerability 16 | 17 | Use this section to tell people how to report a vulnerability. 18 | 19 | Tell them where to go, how often they can expect to get an update on a 20 | reported vulnerability, what to expect if the vulnerability is accepted or 21 | declined, etc. 22 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../stylesheets .css 3 | //= link_tree ../../javascript .js 4 | //= link_tree ../../../vendor/javascript .js 5 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/stylesheets/actiontext.css: -------------------------------------------------------------------------------- 1 | /* 2 | * Provides a drop-in pointer for the default Trix stylesheet that will format the toolbar and 3 | * the trix-editor content (whether displayed or under editing). Feel free to incorporate this 4 | * inclusion directly in any other asset bundle and remove this file. 5 | * 6 | *= require trix 7 | */ 8 | 9 | /* 10 | * We need to override trix.css’s image gallery styles to accommodate the 11 | * element we wrap around attachments. Otherwise, 12 | * images in galleries will be squished by the max-width: 33%; rule. 13 | */ 14 | .trix-content .attachment-gallery > action-text-attachment, 15 | .trix-content .attachment-gallery > .attachment { 16 | flex: 1 0 33%; 17 | padding: 0 0.5em; 18 | max-width: 33%; 19 | } 20 | 21 | .trix-content .attachment-gallery.attachment-gallery--2 > action-text-attachment, 22 | .trix-content .attachment-gallery.attachment-gallery--2 > .attachment, .trix-content .attachment-gallery.attachment-gallery--4 > action-text-attachment, 23 | .trix-content .attachment-gallery.attachment-gallery--4 > .attachment { 24 | flex-basis: 50%; 25 | max-width: 50%; 26 | } 27 | 28 | .trix-content action-text-attachment .attachment { 29 | padding: 0 !important; 30 | max-width: 100% !important; 31 | } 32 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS (and SCSS, if configured) file within this directory, lib/assets/stylesheets, or any plugin's 6 | * vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any other CSS 10 | * files in this directory. Styles in this file should be added after the last require_* statement. 11 | * It is generally better to create a new file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /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/admin_controller.rb: -------------------------------------------------------------------------------- 1 | class AdminController < ApplicationController 2 | 3 | def index; end 4 | 5 | 6 | def posts 7 | @posts = Post.all.includes(:user) 8 | end 9 | 10 | 11 | def comments; end 12 | 13 | def users; end 14 | 15 | 16 | def show_post 17 | @post = Post.includes(:user, comments: [:user, :rich_text_body]).find(params[:id]) 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/controllers/after_signup_controller.rb: -------------------------------------------------------------------------------- 1 | class AfterSignupController < ApplicationController 2 | include Wicked::Wizard 3 | # steps :profile, :avatar, :finish 4 | # Asterisk means variable number of arguments 5 | steps(*User.form_steps) 6 | 7 | def show 8 | @user = current_user 9 | 10 | case step 11 | when 'sign_up' 12 | skip_step if @user.persisted? 13 | when 'set_address' 14 | @address = get_address 15 | when 'find_users' 16 | @users = User.all 17 | end 18 | 19 | render_wizard 20 | end 21 | 22 | def update 23 | @user = current_user 24 | case step 25 | when 'set_name' 26 | if @user.update(onboarding_params(step)) 27 | render_wizard @user 28 | else 29 | render_wizard @user, status: :unprocessable_entity 30 | end 31 | when 'set_address' 32 | if @user.create_address(onboarding_params(step).except(:form_step)) 33 | render_wizard @user 34 | else 35 | @address.destroy 36 | render_wizard @user, status: :unprocessable_entity 37 | end 38 | end 39 | end 40 | 41 | private 42 | 43 | def get_address 44 | if @user.address.nil? 45 | Address.new 46 | else 47 | @user.address 48 | end 49 | end 50 | 51 | def finish_wizard_path 52 | root_path 53 | end 54 | 55 | def onboarding_params(step = 'sign_up') 56 | permitted_attributes = case step 57 | when 'set_name' 58 | required_parameters = :user 59 | %i[first_name last_name] 60 | when 'set_address' 61 | required_parameters = :address 62 | %i[street city state zip country] 63 | end 64 | params.require(required_parameters).permit(:id, permitted_attributes).merge(form_step: step) 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | before_action :set_notifications, if: :current_user 3 | before_action :set_categories 4 | before_action :set_query 5 | 6 | def set_query 7 | @query = Post.ransack(params[:q]) 8 | end 9 | 10 | def is_admin? 11 | unless current_user&.admin? 12 | flash[:alert] = 'You are not authorized to perform this action.' 13 | redirect_to root_path 14 | end 15 | end 16 | 17 | private 18 | 19 | def set_categories 20 | @nav_categories = Category.where(display_in_nav: true).order(:name) 21 | end 22 | 23 | def set_notifications 24 | notifications = Notification.includes(:recipient).where(recipient: current_user).newest_first.limit(9) 25 | @unread = notifications.unread 26 | @read = notifications.read 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/controllers/categories_controller.rb: -------------------------------------------------------------------------------- 1 | class CategoriesController < ApplicationController 2 | before_action :set_category, only: %i[ show edit update destroy ] 3 | before_action :is_admin?, except: %i[show index] 4 | # GET /categories or /categories.json 5 | def index 6 | @categories = Category.all 7 | end 8 | 9 | # GET /categories/1 or /categories/1.json 10 | def show 11 | end 12 | 13 | # GET /categories/new 14 | def new 15 | @category = Category.new 16 | end 17 | 18 | # GET /categories/1/edit 19 | def edit 20 | end 21 | 22 | 23 | # POST /categories or /categories.json 24 | def create 25 | @category = Category.new(category_params) 26 | 27 | respond_to do |format| 28 | if @category.save 29 | format.html { redirect_to category_url(@category), notice: "Category was successfully created." } 30 | format.json { render :show, status: :created, location: @category } 31 | else 32 | format.html { render :new, status: :unprocessable_entity } 33 | format.json { render json: @category.errors, status: :unprocessable_entity } 34 | end 35 | end 36 | end 37 | 38 | # PATCH/PUT /categories/1 or /categories/1.json 39 | def update 40 | respond_to do |format| 41 | if @category.update(category_params) 42 | format.html { redirect_to category_url(@category), notice: 'Category was successfully updated.' } 43 | format.json { render :show, status: :ok, location: @category } 44 | else 45 | format.html { render :edit, status: :unprocessable_entity } 46 | format.json { render json: @category.errors, status: :unprocessable_entity } 47 | end 48 | end 49 | end 50 | 51 | # DELETE /categories/1 or /categories/1.json 52 | def destroy 53 | @category.destroy 54 | 55 | respond_to do |format| 56 | format.html { redirect_to categories_url, notice: 'Category was successfully destroyed.' } 57 | format.json { head :no_content } 58 | end 59 | end 60 | 61 | private 62 | # Use callbacks to share common setup or constraints between actions. 63 | def set_category 64 | @category = Category.find(params[:id]) 65 | end 66 | 67 | # Only allow a list of trusted parameters through. 68 | def category_params 69 | params.require(:category).permit(:name, :display_in_nav) 70 | end 71 | end 72 | -------------------------------------------------------------------------------- /app/controllers/checkouts_controller.rb: -------------------------------------------------------------------------------- 1 | class CheckoutsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def show 5 | current_user.set_payment_processor :stripe 6 | current_user.payment_processor.customer 7 | 8 | @checkout_session = current_user 9 | .payment_processor 10 | .checkout( 11 | mode: params[:payment_mode], 12 | line_items: params[:line_items], 13 | success_url: checkout_success_url 14 | ) 15 | end 16 | 17 | def success 18 | @session = Stripe::Checkout::Session.retrieve(params[:session_id]) 19 | @line_items = Stripe::Checkout::Session.list_line_items(params[:session_id]) 20 | end 21 | 22 | # Get list of payments 23 | # 3.0.3 :039 > Stripe::PaymentIntent.list({customer: User.first.pay_customers.first.processor_id}).each do |int| 24 | # 3.0.3 :040 > puts int.amount 25 | # 3.0.3 :041 > end 26 | end 27 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | before_action :authenticate_user! 3 | before_action :set_post 4 | 5 | def create 6 | @comment = @post.comments.create(comment_params) 7 | @comment.user = current_user 8 | 9 | if @comment.save 10 | redirect_to post_path(@post), notice: 'Comment has been created' 11 | else 12 | redirect_to post_path(@post), alert: 'Comment has not been created' 13 | end 14 | end 15 | 16 | def destroy 17 | @comment = @post.comments.find(params[:id]) 18 | @comment.destroy 19 | redirect_to post_path(@post) 20 | end 21 | 22 | def update 23 | @comment = @post.comments.find(params[:id]) 24 | 25 | respond_to do |format| 26 | if @comment.update(comment_params) 27 | format.html { redirect_to post_url(@post), notice: 'Comment has been updated' } 28 | else 29 | format.html { redirect_to post_url(@post), alert: 'Comment was not updated!' } 30 | end 31 | end 32 | end 33 | 34 | private 35 | 36 | def set_post 37 | @post = Post.find(params[:post_id]) 38 | end 39 | 40 | def comment_params 41 | params.require(:comment).permit(:body) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/drag_controller.rb: -------------------------------------------------------------------------------- 1 | class DragController < ApplicationController 2 | def project 3 | @project = Project.find(drag_project_params[:id]) 4 | @project.insert_at(drag_project_params[:position].to_i + 1) 5 | end 6 | 7 | private 8 | 9 | def drag_project_params 10 | params.require(:resource).permit(:id, :position) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/controllers/members_controller.rb: -------------------------------------------------------------------------------- 1 | class MembersController < ApplicationController 2 | before_action :authenticate_user! 3 | before_action :check_subscription_status 4 | def dashboard 5 | end 6 | 7 | private 8 | 9 | def check_subscription_status 10 | unless current_user.active_subscription 11 | redirect_to checkout_path( 12 | line_items: ['price_1L0iLwFFRI5KMv5yDsOW7eqQ'], 13 | payment_mode: 'subscription' 14 | ), alert: 'You must have an active subscription to access this page.' 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | def home 3 | Rails.logger.info('pages#home') do 4 | 'Rendered the homepage' 5 | end 6 | return unless current_user 7 | return if current_user.payment_processor.nil? 8 | 9 | @portal_session = current_user.payment_processor.billing_portal 10 | end 11 | 12 | def about; end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/posts_controller.rb: -------------------------------------------------------------------------------- 1 | class PostsController < ApplicationController 2 | before_action :set_post, only: %i[show edit update destroy] 3 | before_action :authenticate_user!, except: %i[show index] 4 | # GET /posts or /posts.json 5 | def index 6 | @posts = Post.all.includes(:user, :rich_text_body).order(created_at: :desc) 7 | end 8 | 9 | # GET /posts/1 or /posts/1.json 10 | def show 11 | @post.update(views: @post.views + 1) 12 | @comments = @post.comments.includes(:user, :rich_text_body).order(created_at: :desc) 13 | 14 | ahoy.track 'Viewed Post', post_id: @post.id 15 | 16 | mark_notifications_as_read 17 | end 18 | 19 | # GET /posts/new 20 | def new 21 | @post = Post.new 22 | end 23 | 24 | # GET /posts/1/edit 25 | def edit; end 26 | 27 | # POST /posts or /posts.json 28 | def create 29 | @post = Post.new(post_params) 30 | @post.user = current_user 31 | 32 | respond_to do |format| 33 | if @post.save 34 | format.html { redirect_to post_url(@post), notice: 'Post was successfully created.' } 35 | format.json { render :show, status: :created, location: @post } 36 | else 37 | format.html { render :new, status: :unprocessable_entity } 38 | format.json { render json: @post.errors, status: :unprocessable_entity } 39 | end 40 | end 41 | end 42 | 43 | # PATCH/PUT /posts/1 or /posts/1.json 44 | def update 45 | respond_to do |format| 46 | if @post.update(post_params) 47 | format.html { redirect_to post_url(@post), notice: 'Post was successfully updated.' } 48 | format.json { render :show, status: :ok, location: @post } 49 | else 50 | format.html { render :edit, status: :unprocessable_entity } 51 | format.json { render json: @post.errors, status: :unprocessable_entity } 52 | end 53 | end 54 | end 55 | 56 | # DELETE /posts/1 or /posts/1.json 57 | def destroy 58 | @post.destroy 59 | 60 | respond_to do |format| 61 | format.html { redirect_to posts_url, notice: 'Post was successfully destroyed.' } 62 | format.json { head :no_content } 63 | end 64 | end 65 | 66 | private 67 | 68 | # Use callbacks to share common setup or constraints between actions. 69 | def set_post 70 | @post = Post.find(params[:id]) 71 | 72 | # If an old id or a numeric id was used to find the record, then 73 | # the request slug will not match the current slug, and we should do 74 | # a 301 redirect to the new path 75 | redirect_to @post, status: :moved_permanently if params[:id] != @post.slug 76 | end 77 | 78 | # Only allow a list of trusted parameters through. 79 | def post_params 80 | params.require(:post).permit(:title, :body, :category_id, images: []) 81 | end 82 | 83 | def mark_notifications_as_read 84 | if current_user 85 | notifications_to_mark_as_read = @post.notifications_as_post.where(recipient: current_user) 86 | notifications_to_mark_as_read.update_all(read_at: Time.zone.now) 87 | end 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /app/controllers/projects_controller.rb: -------------------------------------------------------------------------------- 1 | class ProjectsController < ApplicationController 2 | before_action :set_project, only: %i[show edit update destroy] 3 | before_action :is_admin?, except: %i[index show] 4 | 5 | # GET /projects or /projects.json 6 | def index 7 | @projects = Project.all.includes([:rich_text_body]).order(position: :asc) 8 | end 9 | 10 | # GET /projects/1 or /projects/1.json 11 | def show; end 12 | 13 | # GET /projects/new 14 | def new 15 | @project = Project.new 16 | end 17 | 18 | # GET /projects/1/edit 19 | def edit; end 20 | 21 | # POST /projects or /projects.json 22 | def create 23 | @project = Project.new(project_params) 24 | 25 | respond_to do |format| 26 | if @project.save 27 | format.html { redirect_to project_url(@project), notice: 'Project was successfully created.' } 28 | format.json { render :show, status: :created, location: @project } 29 | else 30 | format.html { render :new, status: :unprocessable_entity } 31 | format.json { render json: @project.errors, status: :unprocessable_entity } 32 | end 33 | end 34 | end 35 | 36 | # PATCH/PUT /projects/1 or /projects/1.json 37 | def update 38 | respond_to do |format| 39 | if @project.update(project_params) 40 | format.html { redirect_to project_url(@project), notice: 'Project was successfully updated.' } 41 | format.json { render :show, status: :ok, location: @project } 42 | else 43 | format.html { render :edit, status: :unprocessable_entity } 44 | format.json { render json: @project.errors, status: :unprocessable_entity } 45 | end 46 | end 47 | end 48 | 49 | # DELETE /projects/1 or /projects/1.json 50 | def destroy 51 | @project.destroy 52 | 53 | respond_to do |format| 54 | format.html { redirect_to projects_url, notice: 'Project was successfully destroyed.' } 55 | format.json { head :no_content } 56 | end 57 | end 58 | 59 | private 60 | 61 | # Use callbacks to share common setup or constraints between actions. 62 | def set_project 63 | @project = Project.find(params[:id]) 64 | end 65 | 66 | # Only allow a list of trusted parameters through. 67 | def project_params 68 | params.require(:project).permit(:title, :link, :body) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /app/controllers/search_controller.rb: -------------------------------------------------------------------------------- 1 | class SearchController < ApplicationController 2 | def index 3 | @query = Post.includes(:user, :rich_text_body, :category).ransack(params[:q]) 4 | @posts = @query.result(distinct: true) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/turbo_devise_controller.rb: -------------------------------------------------------------------------------- 1 | class TurboDeviseController < ApplicationController 2 | class Responder < ActionController::Responder 3 | def to_turbo_stream 4 | controller.render(options.merge(formats: :html)) 5 | rescue ActionView::MissingTemplate => e 6 | if get? 7 | raise e 8 | elsif has_errors? && default_action 9 | render rendering_options.merge(formats: :html, status: :unprocessable_entity) 10 | else 11 | redirect_to navigation_location 12 | end 13 | end 14 | end 15 | 16 | self.responder = Responder 17 | respond_to :html, :turbo_stream 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/users/confirmations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Users::ConfirmationsController < Devise::ConfirmationsController 4 | # GET /resource/confirmation/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/confirmation 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/confirmation?confirmation_token=abcdef 15 | # def show 16 | # super 17 | # end 18 | 19 | # protected 20 | 21 | # The path used after resending confirmation instructions. 22 | # def after_resending_confirmation_instructions_path_for(resource_name) 23 | # super(resource_name) 24 | # end 25 | 26 | # The path used after confirmation. 27 | # def after_confirmation_path_for(resource_name, resource) 28 | # super(resource_name, resource) 29 | # end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/users/omniauth_callbacks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController 4 | # You should configure your model like this: 5 | # devise :omniauthable, omniauth_providers: [:twitter] 6 | 7 | # You should also create an action method in this controller like this: 8 | # def twitter 9 | # end 10 | 11 | # More info at: 12 | # https://github.com/heartcombo/devise#omniauth 13 | 14 | # GET|POST /resource/auth/twitter 15 | # def passthru 16 | # super 17 | # end 18 | 19 | # GET|POST /users/auth/twitter/callback 20 | # def failure 21 | # super 22 | # end 23 | 24 | # protected 25 | 26 | # The path used when OmniAuth fails 27 | # def after_omniauth_failure_path_for(scope) 28 | # super(scope) 29 | # end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/users/passwords_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Users::PasswordsController < Devise::PasswordsController 4 | # GET /resource/password/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/password 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/password/edit?reset_password_token=abcdef 15 | # def edit 16 | # super 17 | # end 18 | 19 | # PUT /resource/password 20 | # def update 21 | # super 22 | # end 23 | 24 | # protected 25 | 26 | # def after_resetting_password_path_for(resource) 27 | # super(resource) 28 | # end 29 | 30 | # The path used after sending reset password instructions 31 | # def after_sending_reset_password_instructions_path_for(resource_name) 32 | # super(resource_name) 33 | # end 34 | end 35 | -------------------------------------------------------------------------------- /app/controllers/users/registrations_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Users::RegistrationsController < Devise::RegistrationsController 4 | before_action :configure_sign_up_params, only: [:create] 5 | before_action :configure_account_update_params, only: [:update] 6 | 7 | # GET /resource/sign_up 8 | # def new 9 | # super 10 | # end 11 | 12 | # POST /resource 13 | # def create 14 | # super 15 | # end 16 | 17 | # GET /resource/edit 18 | # def edit 19 | # super 20 | # end 21 | 22 | # PUT /resource 23 | def update 24 | current_user.build_address(address_params) unless current_user.address 25 | current_user.address.update(address_params) 26 | super 27 | end 28 | 29 | # DELETE /resource 30 | # def destroy 31 | # super 32 | # end 33 | 34 | # GET /resource/cancel 35 | # Forces the session data which is usually expired after sign 36 | # in to be expired now. This is useful if the user wants to 37 | # cancel oauth signing in/up in the middle of the process, 38 | # removing all OAuth session data. 39 | # def cancel 40 | # super 41 | # end 42 | 43 | # protected 44 | 45 | # If you have extra params to permit, append them to the sanitizer. 46 | def configure_sign_up_params 47 | devise_parameter_sanitizer.permit(:sign_up, keys: []) 48 | end 49 | 50 | # If you have extra params to permit, append them to the sanitizer. 51 | def configure_account_update_params 52 | devise_parameter_sanitizer.permit(:account_update, keys: [:email, 53 | :avatar, 54 | :first_name, 55 | :last_name, 56 | :password, 57 | :password_confirmation, 58 | :current_password, 59 | { address: %i[street city state zip country] }]) 60 | end 61 | 62 | # The path used after sign up. 63 | def after_sign_up_path_for(_resource) 64 | # super(resource) 65 | after_signup_path('set_name') 66 | end 67 | 68 | # The path used after sign up for inactive accounts. 69 | # def after_inactive_sign_up_path_for(resource) 70 | # super(resource) 71 | # end 72 | 73 | private 74 | 75 | def address_params 76 | params.require(:address).permit(:id, :street, :city, :state, :zip, :country) 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /app/controllers/users/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Users::SessionsController < Devise::SessionsController 4 | # before_action :configure_sign_in_params, only: [:create] 5 | 6 | # GET /resource/sign_in 7 | # def new 8 | # super 9 | # end 10 | 11 | # POST /resource/sign_in 12 | # def create 13 | # super 14 | # end 15 | 16 | # DELETE /resource/sign_out 17 | # def destroy 18 | # super 19 | # end 20 | 21 | # protected 22 | 23 | # If you have extra params to permit, append them to the sanitizer. 24 | # def configure_sign_in_params 25 | # devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute]) 26 | # end 27 | end 28 | -------------------------------------------------------------------------------- /app/controllers/users/unlocks_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Users::UnlocksController < Devise::UnlocksController 4 | # GET /resource/unlock/new 5 | # def new 6 | # super 7 | # end 8 | 9 | # POST /resource/unlock 10 | # def create 11 | # super 12 | # end 13 | 14 | # GET /resource/unlock?unlock_token=abcdef 15 | # def show 16 | # super 17 | # end 18 | 19 | # protected 20 | 21 | # The path used after sending unlock password instructions 22 | # def after_sending_unlock_instructions_path_for(resource) 23 | # super(resource) 24 | # end 25 | 26 | # The path used after unlocking the resource 27 | # def after_unlock_path_for(resource) 28 | # super(resource) 29 | # end 30 | end 31 | -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :set_user 3 | def profile 4 | @user.update(views: @user.views + 1) 5 | @posts = @user.posts.includes(:rich_text_body).order(created_at: :desc) 6 | @total_views = 0 7 | 8 | @posts.each do |post| 9 | @total_views += post.views 10 | end 11 | end 12 | 13 | private 14 | 15 | def set_user 16 | @user = User.find(params[:id]) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/helpers/admin_helper.rb: -------------------------------------------------------------------------------- 1 | module AdminHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/categories_helper.rb: -------------------------------------------------------------------------------- 1 | module CategoriesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/checkouts_helper.rb: -------------------------------------------------------------------------------- 1 | module CheckoutsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module CommentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/drag_helper.rb: -------------------------------------------------------------------------------- 1 | module DragHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/members_helper.rb: -------------------------------------------------------------------------------- 1 | module MembersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/pages_helper.rb: -------------------------------------------------------------------------------- 1 | module PagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/posts_helper.rb: -------------------------------------------------------------------------------- 1 | module PostsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/projects_helper.rb: -------------------------------------------------------------------------------- 1 | module ProjectsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/search_helper.rb: -------------------------------------------------------------------------------- 1 | module SearchHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails 2 | import "@hotwired/turbo-rails"; 3 | import "controllers"; 4 | import "trix"; 5 | import "@rails/actiontext"; 6 | import "chartkick"; 7 | import "Chart.bundle"; 8 | -------------------------------------------------------------------------------- /app/javascript/classes/RichText.js: -------------------------------------------------------------------------------- 1 | export class RichText { 2 | constructor(picker, emojiButton) { 3 | this.picker = picker; 4 | this.emojiButton = emojiButton; 5 | this.createEmojiPickerButton(); 6 | } 7 | createEmojiPickerButton() { 8 | this.emojiButton.addEventListener( 9 | "click", 10 | this.toggleEmojiPicker.bind(this) 11 | ); 12 | document 13 | .querySelector("[data-trix-button-group=block-tools]") 14 | .prepend(this.emojiButton); 15 | } 16 | toggleEmojiPicker(event) { 17 | this.picker.toggle(); 18 | } 19 | setPicker(picker) { 20 | this.picker = picker; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/javascript/controllers/application.js: -------------------------------------------------------------------------------- 1 | import { Application } from "@hotwired/stimulus" 2 | 3 | const application = Application.start() 4 | 5 | // Configure Stimulus development experience 6 | application.debug = false 7 | window.Stimulus = application 8 | 9 | export { application } 10 | -------------------------------------------------------------------------------- /app/javascript/controllers/comments_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus"; 2 | 3 | export default class extends Controller { 4 | initialize() {} 5 | connect() {} 6 | toggleForm(event) { 7 | console.log("I clicked the edit button."); 8 | event.preventDefault(); 9 | event.stopPropagation(); 10 | const formID = event.params["form"]; 11 | const commentBodyID = event.params["body"]; 12 | const editButtonID = event.params["edit"]; 13 | 14 | const form = document.getElementById(formID); 15 | const commentBody = document.getElementById(commentBodyID); 16 | const editButton = document.getElementById(editButtonID); 17 | 18 | form.classList.toggle("d-none"); 19 | form.classList.toggle("mt-5"); 20 | commentBody.classList.toggle("d-none"); 21 | this.toggleEditButton(editButton); 22 | } 23 | 24 | toggleEditButton(editButton) { 25 | if (editButton.innerText === "Edit") { 26 | editButton.innerText = "Cancel"; 27 | this.toggleEditButtonClass(editButton); 28 | } else { 29 | editButton.innerText = "Edit"; 30 | this.toggleEditButtonClass(editButton); 31 | } 32 | } 33 | 34 | toggleEditButtonClass(editButton) { 35 | editButton.classList.toggle("btn-secondary"); 36 | editButton.classList.toggle("btn-warning"); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/javascript/controllers/drag_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus"; 2 | 3 | /** 4 | * The data attribute containing the draggable element's id. 5 | */ 6 | const dataResourceID = "data-resource-id"; 7 | /** 8 | * The data attribute containing the draggable element's outer parent, 9 | * to prevent traversing up to the document root. 10 | */ 11 | const dataParent = "data-parent"; 12 | /** 13 | * The remote URL to submit draggable list order changes to. 14 | */ 15 | let url; 16 | /** 17 | * The draggable element's id. 18 | */ 19 | let resourceID; 20 | /** 21 | * The draggable element's new position. In JavaScript this is 0 based, 22 | * but in Rails this will be 1 based (IDs are 1 based for records). 23 | */ 24 | let newPosition; 25 | // Connects to data-controller="drag" 26 | export default class extends Controller { 27 | connect() {} 28 | /** 29 | * Called when the user starts dragging an element. 30 | * 31 | * @param {*} event The drag event, which can be used to access the dragged element. 32 | */ 33 | dragStart(event) { 34 | resourceID = event.target.getAttribute(dataResourceID); 35 | url = event.target.getAttribute("data-url"); 36 | event.dataTransfer.effectAllowed = "move"; 37 | } 38 | /** 39 | * Called when the user drags an element over another element and releases 40 | * the mouse button. 41 | * 42 | * @param {*} event The drag event, which can be used to access the dragged element. 43 | */ 44 | drop(event) { 45 | event.preventDefault(); 46 | let parentID = event.target.getAttribute(dataParent); 47 | const dropTarget = this.findDropTarget(event.target, parentID); 48 | const draggedItem = document.querySelector( 49 | `[data-resource-id="${resourceID}"]` 50 | ); 51 | if (draggedItem === null || dropTarget === null) { 52 | return true; 53 | } 54 | this.setNewPosition(dropTarget, draggedItem, event); 55 | newPosition = [...this.element.parentElement.children].indexOf(draggedItem); 56 | } 57 | /** 58 | * Called after the drag event has stopped. This runs after the drop event. 59 | * 60 | * @param {*} event The drag event, which can be used to access the dragged element. 61 | */ 62 | dragEnd(event) { 63 | event.preventDefault(); 64 | if (resourceID === null || newPosition === null) { 65 | return; 66 | } 67 | let data = JSON.stringify({ 68 | resource: { 69 | id: resourceID, 70 | position: newPosition, 71 | }, 72 | }); 73 | fetch(url, { 74 | method: "PATCH", 75 | credentials: "same-origin", 76 | headers: { 77 | "X-CSRF-Token": this.getMetaValue("csrf-token"), 78 | "Content-Type": "application/json", 79 | }, 80 | body: data, 81 | }); 82 | } 83 | /** 84 | * Called when the user drags an element over another element. 85 | * 86 | * @param {*} event The drag event, which can be used to access the dragged element. 87 | */ 88 | dragOver(event) { 89 | event.preventDefault(); 90 | return true; 91 | } 92 | /** 93 | * Called when the mouse is dragged over another element 94 | * 95 | * @param {*} event The drag event, which can be used to access the dragged element. 96 | */ 97 | dragEnter(event) { 98 | event.preventDefault(); 99 | } 100 | /** 101 | * Attempts to find the appropriate draggable element in order to parent 102 | * the dragged item to the correct element. This ensures that items aren't 103 | * dropped inside of each other, rather than on top of each other. 104 | * 105 | * @param {*} target The element that the dragged element is dropped on. 106 | * @param {*} parentID The ID of the parent element. This is used to prevent 107 | * traversing up to the document root. 108 | * @returns The element that the dragged element should be parented to. 109 | */ 110 | findDropTarget(target, parentID) { 111 | if (target === null) { 112 | return null; 113 | } 114 | if (target.id === parentID) { 115 | return null; 116 | } 117 | if (target.classList.contains("draggable")) { 118 | return target; 119 | } 120 | return this.findDropTarget(target.parentElement, parentID); 121 | } 122 | /** 123 | * Sets the new position of the dragged element. Positions in the JavaScript 124 | * controller are 0 based, but the positions in the Rails gem are 1 based. 125 | * 126 | * So this index is incremented by 1 on the server side after it is sent over, 127 | * to match the ID. 128 | * 129 | * @param {*} dropTarget The element that the dragged element is dropped on. 130 | * @param {*} draggedItem The element that is being dragged. 131 | */ 132 | setNewPosition(dropTarget, draggedItem) { 133 | const positionComparison = dropTarget.compareDocumentPosition(draggedItem); 134 | if (positionComparison & Node.DOCUMENT_POSITION_FOLLOWING) { 135 | dropTarget.insertAdjacentElement("beforebegin", draggedItem); 136 | } else if (positionComparison & Node.DOCUMENT_POSITION_PRECEDING) { 137 | dropTarget.insertAdjacentElement("afterend", draggedItem); 138 | } 139 | } 140 | /** 141 | * Gets the value of the meta tag with the given name. This is used to 142 | * get the CSRF token. 143 | * 144 | * @param {*} name The name of the meta tag. 145 | * @returns The value of the meta tag. 146 | */ 147 | getMetaValue(name) { 148 | const element = document.head.querySelector(`meta[name="${name}"]`); 149 | return element.getAttribute("content"); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/javascript/controllers/dropzone_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus"; 2 | import Dropzone from "dropzone"; 3 | import { DirectUpload } from "@rails/activestorage"; 4 | import { 5 | getMetaValue, 6 | findElement, 7 | removeElement, 8 | insertAfter, 9 | } from "../helpers/dropzone"; 10 | 11 | // Connects to data-controller="dropzone" 12 | export default class extends Controller { 13 | static targets = ["input"]; 14 | 15 | connect() { 16 | console.log("Connected to the dropzone controller"); 17 | this.dropZone = createDropZone(this); 18 | this.hideFileInput(); 19 | this.bindEvents(); 20 | Dropzone.autoDiscover = false; // necessary quirk for Dropzone error in console 21 | } 22 | 23 | hideFileInput() { 24 | this.inputTarget.style.display = "none"; 25 | this.inputTarget.disabled = true; 26 | } 27 | 28 | bindEvents() { 29 | this.dropZone.on("addedfile", (file) => { 30 | setTimeout(() => { 31 | file.accepted && createDirectUploadController(this, file).start(); 32 | }, 500); 33 | }); 34 | 35 | this.dropZone.on("removedfile", (file) => { 36 | file.controller && removeElement(file.controller.hiddenInput); 37 | }); 38 | this.dropZone.on("canceled", (file) => { 39 | file.controller && file.controller.xhr.abort(); 40 | }); 41 | this.dropZone.on("processing", (file) => { 42 | this.submitButton.disabled = true; 43 | }); 44 | this.dropZone.on("queuecomplete", (file) => { 45 | this.submitButton.disabled = false; 46 | }); 47 | } 48 | 49 | get headers() { 50 | return { "X-CSRF-Token": getMetaValue("csrf-token") }; 51 | } 52 | get url() { 53 | return this.inputTarget.getAttribute("data-direct-upload-url"); 54 | } 55 | get maxFiles() { 56 | return this.data.get("maxFiles") || 1; 57 | } 58 | get maxFileSize() { 59 | return this.data.get("maxFileSize") || 256; 60 | } 61 | get acceptedFiles() { 62 | return this.data.get("acceptedFiles"); 63 | } 64 | 65 | get addRemoveLinks() { 66 | return this.data.get("addRemoveLinks") || true; 67 | } 68 | get uploadMultiple() { 69 | return this.data.get("uploadMultiple") || false; 70 | } 71 | get form() { 72 | return this.element.closest("form"); 73 | } 74 | get submitButton() { 75 | return findElement(this.form, "input[type=submit], button[type=submit]"); 76 | } 77 | } 78 | class DirectUploadController { 79 | constructor(source, file) { 80 | this.directUpload = createDirectUpload(file, source.url, this); 81 | this.source = source; 82 | this.file = file; 83 | } 84 | start() { 85 | this.file.controller = this; 86 | this.hiddenInput = this.createHiddenInput(); 87 | this.directUpload.create((error, attributes) => { 88 | if (error) { 89 | removeElement(this.hiddenInput); 90 | this.emitDropzoneError(error); 91 | } else { 92 | this.hiddenInput.value = attributes.signed_id; 93 | this.emitDropzoneSuccess(); 94 | } 95 | }); 96 | } 97 | createHiddenInput() { 98 | const input = document.createElement("input"); 99 | input.type = "hidden"; 100 | input.name = this.source.inputTarget.name; 101 | insertAfter(input, this.source.inputTarget); 102 | return input; 103 | } 104 | directUploadWillStoreFileWithXHR(xhr) { 105 | this.bindProgressEvent(xhr); 106 | this.emitDropzoneUploading(); 107 | } 108 | bindProgressEvent(xhr) { 109 | this.xhr = xhr; 110 | this.xhr.upload.addEventListener("progress", (event) => 111 | this.uploadRequestDidProgress(event) 112 | ); 113 | } 114 | uploadRequestDidProgress(event) { 115 | const element = this.source.element; 116 | const progress = (event.loaded / event.total) * 100; 117 | findElement( 118 | this.file.previewTemplate, 119 | ".dz-upload" 120 | ).style.width = `${progress}%`; 121 | } 122 | 123 | emitDropzoneUploading() { 124 | this.file.status = Dropzone.UPLOADING; 125 | this.source.dropZone.emit("processing", this.file); 126 | } 127 | 128 | emitDropzoneError(error) { 129 | this.file.status = Dropzone.ERROR; 130 | this.source.dropZone.emit("error", this.file, error); 131 | this.source.dropZone.emit("complete", this.file); 132 | } 133 | emitDropzoneSuccess() { 134 | this.file.status = Dropzone.SUCCESS; 135 | this.source.dropZone.emit("success", this.file); 136 | this.source.dropZone.emit("complete", this.file); 137 | } 138 | } 139 | 140 | function createDirectUploadController(source, file) { 141 | return new DirectUploadController(source, file); 142 | } 143 | 144 | function createDirectUpload(file, url, controller) { 145 | return new DirectUpload(file, url, controller); 146 | } 147 | 148 | function createDropZone(controller) { 149 | let dropzone = new Dropzone(controller.element, { 150 | url: controller.url, 151 | headers: controller.headers, 152 | maxFiles: controller.maxFiles, 153 | maxFilesize: controller.maxFileSize, 154 | acceptedFiles: controller.acceptedFiles, 155 | addRemoveLinks: controller.addRemoveLinks, 156 | uploadMultiple: controller.uploadMultiple, 157 | autoQueue: false, 158 | }); 159 | console.log(dropzone); 160 | return dropzone; 161 | } 162 | -------------------------------------------------------------------------------- /app/javascript/controllers/emoji_picker_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus"; 2 | import { createPopup } from "@picmo/popup-picker"; 3 | import { RichText } from "../classes/RichText"; 4 | 5 | // Connects to data-controller="emoji-picker" 6 | export default class extends Controller { 7 | static targets = ["trixEditor", "pickerContainer"]; 8 | connect() { 9 | console.log("Connected to emoji-picker"); 10 | const buttonString = this.emojiButtonString(); 11 | const emojiButton = this.emojiButtonTemplate(buttonString); 12 | let picker; 13 | let richText = new RichText(picker, emojiButton); 14 | picker = createPopup( 15 | { 16 | rootElement: this.pickerContainerTarget, 17 | }, 18 | { 19 | // The element that triggers the popup 20 | triggerElement: emojiButton, 21 | // The element to position the picker relative to - often this is also the trigger element, 22 | referenceElement: emojiButton, 23 | // specify how to position the popup 24 | position: "bottom-start", 25 | } 26 | ); 27 | 28 | picker.addEventListener("emoji:select", (event) => { 29 | this.trixEditorTarget.editor.insertString(event.emoji); 30 | }); 31 | 32 | richText.setPicker(picker); 33 | } 34 | 35 | emojiButtonTemplate(buttonString) { 36 | const domParser = new DOMParser(); 37 | const emojiButton = domParser 38 | .parseFromString(buttonString, "text/html") 39 | .querySelector("button"); 40 | return emojiButton; 41 | } 42 | 43 | emojiButtonString() { 44 | const buttonString = ``; 45 | return buttonString; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/javascript/controllers/hello_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | export default class extends Controller { 4 | connect() { 5 | this.element.textContent = "Hello World!" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // Import and register all your controllers from the importmap under controllers/* 2 | 3 | import { application } from "controllers/application" 4 | 5 | // Eager load all controllers defined in the import map under controllers/**/*_controller 6 | import { eagerLoadControllersFrom } from "@hotwired/stimulus-loading" 7 | eagerLoadControllersFrom("controllers", application) 8 | 9 | // Lazy load controllers as they appear in the DOM (remember not to preload controllers in import map!) 10 | // import { lazyLoadControllersFrom } from "@hotwired/stimulus-loading" 11 | // lazyLoadControllersFrom("controllers", application) 12 | -------------------------------------------------------------------------------- /app/javascript/helpers/dropzone.js: -------------------------------------------------------------------------------- 1 | export function getMetaValue(name) { 2 | const element = findElement(document.head, `meta[name="${name}"]`); 3 | if (element) { 4 | return element.getAttribute("content"); 5 | } 6 | } 7 | 8 | export function findElement(root, selector) { 9 | if (typeof root == "string") { 10 | selector = root; 11 | root = document; 12 | } 13 | return root.querySelector(selector); 14 | } 15 | 16 | export function removeElement(el) { 17 | if (el && el.parentNode) { 18 | el.parentNode.removeChild(el); 19 | } 20 | } 21 | 22 | export function insertAfter(el, referenceNode) { 23 | return referenceNode.parentNode.insertBefore(el, referenceNode.nextSibling); 24 | } 25 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | # Automatically retry jobs that encountered a deadlock 3 | # retry_on ActiveRecord::Deadlocked 4 | 5 | # Most jobs are safe to ignore if the underlying records are no longer available 6 | # discard_on ActiveJob::DeserializationError 7 | end 8 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /app/models/address.rb: -------------------------------------------------------------------------------- 1 | class Address < ApplicationRecord 2 | belongs_to :user, inverse_of: :address 3 | validates :street, presence: true 4 | validates :city, presence: true 5 | validates :state, presence: true 6 | validates :zip, presence: true 7 | validates :country, presence: true 8 | end 9 | -------------------------------------------------------------------------------- /app/models/ahoy/event.rb: -------------------------------------------------------------------------------- 1 | class Ahoy::Event < ApplicationRecord 2 | include Ahoy::QueryMethods 3 | 4 | self.table_name = "ahoy_events" 5 | 6 | belongs_to :visit 7 | belongs_to :user, optional: true 8 | end 9 | -------------------------------------------------------------------------------- /app/models/ahoy/visit.rb: -------------------------------------------------------------------------------- 1 | class Ahoy::Visit < ApplicationRecord 2 | self.table_name = "ahoy_visits" 3 | 4 | has_many :events, class_name: "Ahoy::Event" 5 | belongs_to :user, optional: true 6 | end 7 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /app/models/category.rb: -------------------------------------------------------------------------------- 1 | class Category < ApplicationRecord 2 | has_many :posts, dependent: :destroy 3 | 4 | def self.scheduled_category 5 | Category.create(name: "Scheduled at #{Time.now}", 6 | display_in_nav: true) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ApplicationRecord 2 | belongs_to :post, counter_cache: true 3 | belongs_to :user 4 | has_rich_text :body 5 | 6 | after_create_commit :notify_recipient 7 | before_destroy :cleanup_notifications 8 | has_noticed_notifications model_name: 'Notification' 9 | 10 | private 11 | 12 | def notify_recipient 13 | return if post.user == user 14 | 15 | CommentNotification.with(comment: self, post: post).deliver_later(post.user) 16 | end 17 | 18 | def cleanup_notifications 19 | notifications_as_comment.destroy_all 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/concerns/subscription_concern.rb: -------------------------------------------------------------------------------- 1 | module SubscriptionConcern 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | def check_subscription_status 6 | subscription = payment_processor&.subscription&.processor_subscription 7 | 8 | return if subscription.nil? 9 | 10 | update( 11 | subscription_status: subscription.status, 12 | subscription_end_date: Time.at(subscription.current_period_end), 13 | subscription_start_date: Time.at(subscription.current_period_start) 14 | ) 15 | end 16 | 17 | def active_subscription 18 | check_subscription_status if subscription_end_date.nil? || subscription_end_date < 15.days.from_now 19 | 20 | subscription_end_date.nil? ? false : subscription_end_date > 15.days.from_now 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/models/notification.rb: -------------------------------------------------------------------------------- 1 | class Notification < ApplicationRecord 2 | include Noticed::Model 3 | belongs_to :recipient, polymorphic: true 4 | end 5 | -------------------------------------------------------------------------------- /app/models/post.rb: -------------------------------------------------------------------------------- 1 | class Post < ApplicationRecord 2 | extend FriendlyId 3 | validates :title, presence: true, length: { minimum: 5, maximum: 50 } 4 | validates :body, presence: true 5 | belongs_to :category 6 | has_rich_text :body 7 | belongs_to :category 8 | 9 | # Single image upload 10 | # has_one_attached :image 11 | # Multiple images upload 12 | has_many_attached :images 13 | 14 | belongs_to :user 15 | has_many :comments, dependent: :destroy 16 | 17 | has_rich_text :body 18 | has_one :content, class_name: 'ActionText::RichText', as: :record, dependent: :destroy 19 | 20 | has_noticed_notifications model_name: 'Notification' 21 | has_many :notifications, through: :user 22 | 23 | friendly_id :title, use: %i[slugged history finders] 24 | 25 | def should_generate_new_friendly_id? 26 | title_changed? || slug.blank? 27 | end 28 | 29 | def views_by_day 30 | daily_events = Ahoy::Event.where("cast(properties ->> 'post_id' as bigint) = ?", id) 31 | daily_events.group_by_day(:time, range: 1.month.ago..Time.now).count 32 | end 33 | 34 | def self.total_views_by_day 35 | daily_events = Ahoy::Event.where(name: 'Viewed Post') 36 | daily_events.group_by_day(:time, range: 1.month.ago..Time.now).count 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/models/project.rb: -------------------------------------------------------------------------------- 1 | class Project < ApplicationRecord 2 | has_rich_text :body 3 | acts_as_list 4 | end 5 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | include SubscriptionConcern 3 | # Include default devise modules. Others available are: 4 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable 5 | devise :database_authenticatable, :registerable, 6 | :recoverable, :rememberable, :validatable 7 | has_many :posts, dependent: :destroy 8 | has_many :comments, dependent: :destroy 9 | has_many :notifications, as: :recipient, dependent: :destroy 10 | 11 | pay_customer stripe_attributes: :stripe_attributes 12 | 13 | has_one_attached :avatar 14 | 15 | has_one :address, dependent: :destroy, inverse_of: :user, autosave: true 16 | 17 | enum role: %i[user admin] 18 | after_initialize :set_default_role, if: :new_record? 19 | 20 | # Class level accessor http://apidock.com/rails/Class/cattr_accessor 21 | cattr_accessor :form_steps do 22 | %w[sign_up set_name set_address find_users] 23 | end 24 | 25 | # Instance level accessor http://apidock.com/ruby/Module/attr_accessor 26 | attr_accessor :form_step 27 | 28 | def form_step 29 | @form_step ||= 'sign_up' 30 | end 31 | 32 | with_options if: -> { required_for_step?('set_name') } do |step| 33 | step.validates :first_name, presence: true 34 | step.validates :last_name, presence: true 35 | end 36 | 37 | validates_associated :address, if: -> { required_for_step?('set_address') } 38 | 39 | def full_name 40 | "#{first_name.capitalize unless first_name.nil?} #{last_name.capitalize unless last_name.nil?}" 41 | end 42 | 43 | accepts_nested_attributes_for :address, allow_destroy: true 44 | 45 | def required_for_step?(step) 46 | # All fields are required if no form step is present 47 | form_step.nil? 48 | 49 | # All fields from previous steps are required if the 50 | # step parameter appears before or we are on the current step 51 | form_steps.index(step.to_s) <= form_steps.index(form_step.to_s) 52 | end 53 | 54 | def stripe_attributes(pay_customer) 55 | { 56 | address: { 57 | city: pay_customer.owner.city, 58 | country: pay_customer.owner.country 59 | }, 60 | metadata: { 61 | pay_customer_id: pay_customer.id, 62 | user_id: id # or pay_customer.owner_id 63 | } 64 | } 65 | end 66 | 67 | private 68 | 69 | def set_default_role 70 | self.role ||= :user 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /app/notifications/comment_notification.rb: -------------------------------------------------------------------------------- 1 | # To deliver this notification: 2 | # 3 | # CommentNotification.with(post: @post).deliver_later(current_user) 4 | # CommentNotification.with(post: @post).deliver(current_user) 5 | 6 | class CommentNotification < Noticed::Base 7 | # Add your delivery methods 8 | # 9 | deliver_by :database 10 | # deliver_by :email, mailer: "UserMailer" 11 | # deliver_by :slack 12 | # deliver_by :custom, class: "MyDeliveryMethod" 13 | 14 | # Add required params 15 | # 16 | # param :post 17 | 18 | # Define helper methods to make rendering easier. 19 | # 20 | def message 21 | @post = Post.find(params[:comment][:post_id]) 22 | @comment = Comment.find(params[:comment][:id]) 23 | @user = User.find(@comment.user_id) 24 | "#{@user.email} commented on #{@post.title.truncate(10)}" 25 | end 26 | 27 | def url 28 | post_path(Post.find(params[:comment][:post_id])) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /app/views/active_storage/blobs/_blob.html.erb: -------------------------------------------------------------------------------- 1 |
attachment--<%= blob.filename.extension %>"> 2 | <% if blob.representable? %> 3 | <%= image_tag blob.representation(resize_to_limit: local_assigns[:in_gallery] ? [ 800, 600 ] : [ 1024, 768 ]) %> 4 | <% end %> 5 | 6 |
7 | <% if caption = blob.try(:caption) %> 8 | <%= caption %> 9 | <% else %> 10 | <%= blob.filename %> 11 | <%= number_to_human_size blob.byte_size %> 12 | <% end %> 13 |
14 |
15 | -------------------------------------------------------------------------------- /app/views/admin/_link.html.erb: -------------------------------------------------------------------------------- 1 | <%= link_to resource.name.capitalize, url_for([:admin, resource]), class:'btn btn-primary' %> 2 | -------------------------------------------------------------------------------- /app/views/admin/_nav_links.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | <%= link_to "Admin", admin_path %><%= " > #{params[:action].capitalize}" unless params[:action].eql?("index") %> 4 |

5 |
6 | <%= render "admin/link", resource: Post %> 7 | <%= render "admin/link", resource: Comment %> 8 | <%= render "admin/link", resource: User %> 9 |
10 |
11 | -------------------------------------------------------------------------------- /app/views/admin/_total_daily_views.html.erb: -------------------------------------------------------------------------------- 1 | <%= area_chart Post.total_views_by_day, colors: ["#a00a"] %> 2 | -------------------------------------------------------------------------------- /app/views/admin/comments.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Adminstrator

3 | <%= render 'admin/nav_links' %> 4 |
5 | -------------------------------------------------------------------------------- /app/views/admin/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Adminstrator

3 | <%= render 'admin/nav_links' %> 4 | <%= render partial: 'admin/total_daily_views' %> 5 |
6 | -------------------------------------------------------------------------------- /app/views/admin/posts.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Adminstrator

3 | <%= render 'admin/nav_links' %> 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | <% @posts.each do |post| %> 15 | 16 | 17 | 18 | 19 | 20 | 21 | <% end %> 22 | 23 |
IdPostAuthorComments
<%= post.id %><%= link_to post.title.truncate(15), admin_post_path(post) %><%= post.user.full_name %><%= pluralize(post.comments.size, "Comment") %>
24 |
25 | -------------------------------------------------------------------------------- /app/views/admin/show_post.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Administrator

3 | <%= render "admin/nav_links" %> 4 |
5 |
6 | <%= link_to "Edit", edit_post_path(@post), class:"btn btn-warning" %> 7 | <%= button_to "Delete", @post, method: :delete, data: { confirm: "Are you sure?" }, class:"btn btn-danger" %> 8 |
9 |
10 | <%= area_chart @post.views_by_day, colors: ["#a00a"] %> 11 |
12 |

13 | Title: <%= @post.title %> 14 |

15 |
16 |
17 |

18 | Views: <%= @post.views %> 19 |

20 |
21 |
22 |

Author: 23 | <%= "#{@post.user.email} | #{@post.user.full_name} | #{pluralize(@post.user.views, "View")}" %> 24 |

25 |
26 |
27 | Body: <%= @post.body %> 28 |
29 |
30 | <%= pluralize(@post.comments.count, "Comment") %>: 31 | <% @post.comments.each do |comment| %> 32 |
33 | <%= comment.user.full_name %> 34 | <%= comment.body %> 35 |
36 | <% end %> 37 |
38 |
39 | -------------------------------------------------------------------------------- /app/views/admin/users.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Adminstrator

3 | <%= render 'admin/nav_links' %> 4 |
5 | -------------------------------------------------------------------------------- /app/views/after_signup/_links.html.erb: -------------------------------------------------------------------------------- 1 | <% unless previous_step?("sign_up") %> 2 | <%= link_to "Back", previous_wizard_path, class:"btn btn-secondary" %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /app/views/after_signup/find_users.html.erb: -------------------------------------------------------------------------------- 1 |

Users to follow

2 | i didn't implement following, just pretend 3 |
4 |

<%= link_to "Finish", next_wizard_path %>

5 | <% @users.each do |user| %> 6 |

<%= "[#{user.id}] #{user.email}" %>

7 | <% end %> 8 | <%= render "after_signup/links" %> 9 | -------------------------------------------------------------------------------- /app/views/after_signup/set_address.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Set Your Address

3 | <%= form_for @address, method: :put, url: wizard_path do |f| %> 4 | <% if f.object.errors.any? %> 5 |
6 | <% f.object.errors.full_messages.each do |error| %> 7 |

<%= error %>

8 | <% end %> 9 |
10 | <% end %> 11 |
12 | Your Address 13 |
14 | <%= f.text_field :street, class: "form-control", placeholder: "Street...", required: true %> 15 |
16 |
17 | <%= f.text_field :city, class: "form-control", placeholder: "City", required: true %> 18 | <%= f.text_field :state, class: "form-control", placeholder: "State", required: true %> 19 | <%= f.text_field :zip, class: "form-control", placeholder: "Zip", required: true %> 20 |
21 |
22 | <%= f.text_field :country, class: "form-control", placeholder: "Country", required: true %> 23 |
24 |
25 |
26 |
27 | <%= render 'links' %> 28 | <%= f.submit 'Next Step', class:"btn btn-primary" %> 29 |
30 |
31 |
32 | <% end %> 33 | 38 |
39 | -------------------------------------------------------------------------------- /app/views/after_signup/set_name.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Set Your Name

3 | <%= form_for @user, method: :put, url: wizard_path do |f| %> 4 | <% if f.object.errors.any? %> 5 |
6 | <% f.object.errors.full_messages.each do |error| %> 7 |

<%= error %>

8 | <% end %> 9 |
10 | <% end %> 11 |
12 | Your Name 13 | <%= f.text_field :first_name, class: "form-control", placeholder: "First name...", required: true %> 14 | <%= f.text_field :last_name, class: "form-control", placeholder: "Last name...", required: true %> 15 |
16 |
17 | <%= f.submit 'Next Step', class:"btn btn-primary" %> 18 |
19 | <% end %> 20 | 25 |
26 | -------------------------------------------------------------------------------- /app/views/categories/_category.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

3 | <% target = action_name.eql?("show") ? categories_path : category %> 4 | <%= link_to category.name, target %> 5 |

6 | <% category.posts.includes(:user, :rich_text_body).each do |post| %> 7 | <%= render "posts/post", post: post %> 8 | <% end %> 9 |
10 | -------------------------------------------------------------------------------- /app/views/categories/_category.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! category, :id, :name, :display_in_nav, :created_at, :updated_at 2 | json.url category_url(category, format: :json) 3 | -------------------------------------------------------------------------------- /app/views/categories/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: category) do |form| %> 2 | <% if category.errors.any? %> 3 |
4 |

<%= pluralize(category.errors.count, "error") %> prohibited this category from being saved:

5 | 6 | 11 |
12 | <% end %> 13 | 14 |
15 | <%= form.label :name, style: "display: block" %> 16 | <%= form.text_field :name %> 17 |
18 | 19 |
20 | <%= form.label :display_in_nav, style: "display: block" %> 21 | <%= form.check_box :display_in_nav %> 22 |
23 | 24 |
25 | <%= form.submit %> 26 |
27 | <% end %> 28 | -------------------------------------------------------------------------------- /app/views/categories/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing category

2 | 3 | <%= render "form", category: @category %> 4 | 5 |
6 | 7 |
8 | <%= link_to "Show this category", @category %> | 9 | <%= link_to "Back to categories", categories_path %> 10 |
11 | -------------------------------------------------------------------------------- /app/views/categories/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 |

Categories

3 |
4 | <% @categories.each do |category| %> 5 | <%= render category %> 6 |

7 | <%= link_to "Show this category", category %> 8 |

9 | <% end %> 10 |
11 | <%= link_to "New category", new_category_path %> 12 | -------------------------------------------------------------------------------- /app/views/categories/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @categories, partial: "categories/category", as: :category 2 | -------------------------------------------------------------------------------- /app/views/categories/new.html.erb: -------------------------------------------------------------------------------- 1 |

New category

2 | 3 | <%= render "form", category: @category %> 4 | 5 |
6 | 7 |
8 | <%= link_to "Back to categories", categories_path %> 9 |
10 | -------------------------------------------------------------------------------- /app/views/categories/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | <% if current_user&.admin? %> 3 |
4 | <%= link_to "Edit this category", edit_category_path(@category), class:"btn btn-warning" %> 5 | <%= button_to "Destroy this category", @category, method: :delete, class:"btn btn-danger" %> 6 |
7 | <% end %> 8 | <%= render @category %> 9 |
10 | <%= link_to "Back to categories", categories_path %> 11 |
12 | -------------------------------------------------------------------------------- /app/views/categories/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "categories/category", category: @category 2 | -------------------------------------------------------------------------------- /app/views/checkouts/show.html.erb: -------------------------------------------------------------------------------- 1 |

Checkout!

2 | <%= render partial: 'pay/stripe/checkout_button', locals: { 3 | session: @checkout_session, 4 | title: "Buy now" 5 | } %> 6 | -------------------------------------------------------------------------------- /app/views/checkouts/success.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Success!

3 |

You just bought my stuff!

4 |

Your total was $<%= @session.amount_total.to_f / 100 %> 5 |
6 | <% @line_items.each do |item| %> 7 |

<%= item.description %>

8 |
9 |

$<%= item.price.unit_amount.to_f / 100 %>

10 | <% product = Stripe::Product.retrieve(item.price.product) %> 11 | <%= image_tag(product.images.first, width: 300) %> 12 | <% end %> 13 |
14 | -------------------------------------------------------------------------------- /app/views/comments/_comment.html.erb: -------------------------------------------------------------------------------- 1 |
3 | <%= comment.user.full_name %>
4 | <% if (comment.updated_at - comment.created_at) > 1 %> 5 | Edited <%= time_ago_in_words(comment.updated_at) %> ago 6 | <% else %> 7 | Posted <%= time_ago_in_words(comment.created_at) %> ago 8 | <% end %> 9 | <% if current_user == comment.user %> 10 |
11 | <%= link_to "Edit", nil, remote: true, class:"btn btn-warning", 12 | data: { 13 | controller: "comments", 14 | action: "comments#toggleForm", 15 | comments_form_param: "edit-form-#{comment.id}", 16 | comments_body_param: "comment-body-#{comment.id}", 17 | comments_edit_param: "edit-button-#{comment.id}" 18 | }, 19 | id: "edit-button-#{comment.id}" %> 20 | <%= button_to "Delete", [post, comment], class:"btn btn-danger", method: :delete %> 21 |
22 |
23 | <%= render 'comments/form', 24 | post: @post, 25 | comment: comment, 26 | submit_label: "Update" %> 27 |
28 | <% end %> 29 |
30 |
31 | <%= comment.body %> 32 |
33 |
34 | -------------------------------------------------------------------------------- /app/views/comments/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: [post, comment]) do |f| %> 2 |
3 | <%= f.rich_text_area :body %> 4 | <%= f.submit submit_label, class: "btn btn-primary mt-1" %> 5 |
6 | <% end %> 7 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Resend confirmation instructions

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

Welcome <%= @email %>!

2 | 3 |

You can confirm your account email through the link below:

4 | 5 |

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

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

Hello <%= @email %>!

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

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

5 | <% else %> 6 |

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

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

Hello <%= @resource.email %>!

2 | 3 |

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

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

Hello <%= @resource.email %>!

2 | 3 |

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

4 | 5 |

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

6 | 7 |

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

8 |

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

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

Hello <%= @resource.email %>!

2 | 3 |

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

4 | 5 |

Click the link below to unlock your account:

6 | 7 |

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

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

Change your password

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

Forgot your password?

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

Edit <%= resource_name.to_s.humanize %>

2 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> 3 | <%= render "devise/shared/error_messages", resource: resource %> 4 |
5 | <%= f.label :email %>
6 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 7 |
8 |
9 | <%= f.label :avatar %>
10 | <%= f.file_field :avatar %> 11 |
12 |
13 | <%= f.label :first_name %>
14 | <%= f.text_field :first_name %> 15 |
16 |
17 | <%= f.label :last_name %>
18 | <%= f.text_field :last_name %> 19 |
20 | <%= fields_for :address, resource.address do |c| %> 21 | <%= c.hidden_field :id %> 22 |
23 | Address 24 |
25 | <%= c.label :street %>
26 | <%= c.text_field :street %> 27 |
28 |
29 | <%= c.label :city %>
30 | <%= c.text_field :city %> 31 |
32 |
33 | <%= c.label :state %>
34 | <%= c.text_field :state %> 35 |
36 |
37 | <%= c.label :zip %>
38 | <%= c.text_field :zip %> 39 |
40 |
41 | <%= c.label :country %>
42 | <%= c.text_field :country %> 43 |
44 |
45 | <% end %> 46 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 47 |
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
48 | <% end %> 49 |
50 | <%= f.label :password %> (leave blank if you don't want to change it)
51 | <%= f.password_field :password, autocomplete: "new-password" %> 52 | <% if @minimum_password_length %> 53 |
54 | <%= @minimum_password_length %> characters minimum 55 | <% end %> 56 |
57 |
58 | <%= f.label :password_confirmation %>
59 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 60 |
61 |
62 | <%= f.label :current_password %> (we need your current password to confirm your changes)
63 | <%= f.password_field :current_password, autocomplete: "current-password" %> 64 |
65 |
66 | <%= f.submit "Update" %> 67 |
68 | <% end %> 69 |

Cancel my account

70 |

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

71 | <%= link_to "Back", :back %> 72 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 |

Sign up

2 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> 3 | <%= render "devise/shared/error_messages", resource: resource %> 4 |
5 | <%= f.label :email %>
6 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 7 |
8 |
9 | <%= f.label :password %> 10 | <% if @minimum_password_length %> 11 | (<%= @minimum_password_length %> characters minimum) 12 | <% end %>
13 | <%= f.password_field :password, autocomplete: "new-password" %> 14 |
15 |
16 | <%= f.label :password_confirmation %>
17 | <%= f.password_field :password_confirmation, autocomplete: "new-password" %> 18 |
19 |
20 | <%= f.submit "Sign up" %> 21 |
22 | <% end %> 23 | <%= render "devise/shared/links" %> 24 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 |

Log in

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

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

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

Resend unlock instructions

2 | 3 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email %>
8 | <%= f.email_field :email, autofocus: true, autocomplete: "email" %> 9 |
10 | 11 |
12 | <%= f.submit "Resend unlock instructions" %> 13 |
14 | <% end %> 15 | 16 | <%= render "devise/shared/links" %> 17 | -------------------------------------------------------------------------------- /app/views/layouts/_alerts.html.erb: -------------------------------------------------------------------------------- 1 | <% if notice %> 2 |

<%= notice %>

3 | <% elsif alert %> 4 |

<%= alert %>

5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/views/layouts/_categories.html.erb: -------------------------------------------------------------------------------- 1 | 13 | -------------------------------------------------------------------------------- /app/views/layouts/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 33 | -------------------------------------------------------------------------------- /app/views/layouts/_notification.html.erb: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /app/views/layouts/_notifications.html.erb: -------------------------------------------------------------------------------- 1 | <% if current_user %> 2 | 26 | <% end %> 27 | -------------------------------------------------------------------------------- /app/views/layouts/action_text/contents/_content.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= yield -%> 3 |
4 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | BlogDemo 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | 10 | 11 | 12 | <%= stylesheet_link_tag "application", "data-turbo-track": "reload" %> 13 | <%= javascript_importmap_tags %> 14 | 15 | 16 | 17 |
18 | <%= render 'layouts/navbar' %> 19 | <%= render 'layouts/alerts' %> 20 |
21 |
22 | <%= yield %> 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /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/members/dashboard.html.erb: -------------------------------------------------------------------------------- 1 |

Members#dashboard

2 |

Find me in app/views/members/dashboard.html.erb

3 | -------------------------------------------------------------------------------- /app/views/pages/about.html.erb: -------------------------------------------------------------------------------- 1 |

Pages#about

2 |

Find me in app/views/pages/about.html.erb

3 | <%= link_to "Home", root_path %> -------------------------------------------------------------------------------- /app/views/pages/home.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

My Personal Home Page

3 |

You can read more about me, or checkout my blog.

4 |
5 | <%= link_to "About Me", about_path, class:"btn btn-secondary" %> 6 | <%= link_to "Blog", posts_path, class:"btn btn-primary" %> 7 | <%= link_to "Buy My Super Deluxe™ Cat!", checkout_path( 8 | line_items: ['price_1Ky9sDFFRI5KMv5ysJnIEe9O'], 9 | payment_mode: 'payment' 10 | ), class:"btn btn-success" %> 11 | <%= link_to "Subscribe To Me!", checkout_path( 12 | line_items: ['price_1L0iLwFFRI5KMv5yDsOW7eqQ'], 13 | payment_mode: 'subscription' 14 | ), 15 | class:"btn btn-success" %> 16 |
17 |
18 | <% if current_user %> 19 |
20 |

User Info

21 | <%= link_to "Manage Billing", @portal_session.url unless @portal_session.nil? %> 22 |

<%= current_user.full_name %>

23 | 30 |
31 | <% end %> 32 | -------------------------------------------------------------------------------- /app/views/posts/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with(model: post, data: {controller: "emoji-picker"}) do |form| %> 2 | <% if post.errors.any? %> 3 |
4 |

<%= pluralize(post.errors.count, "error") %> prohibited this post from being saved:

5 | 10 |
11 | <% end %> 12 |
13 | <%= form.label :title, style: "display: block" %> 14 | <%= form.text_field :title %> 15 |
16 |
17 | <%= form.label :category, style: "display: block" %> 18 | <%= form.select :category_id, options_for_select(Category.all.order(name: :desc).collect { |cat| [cat.name, cat.id]})%> 19 |
20 | <%= content_tag(:div, "", class:"pickerContainer", data: { 21 | emoji_picker_target: "pickerContainer" 22 | }) %> 23 |
24 | <%= form.label :body, style: "display: block" %> 25 | <%= form.rich_text_area :body, data: { 26 | emoji_picker_target: "trixEditor" 27 | } %> 28 |
29 | <%= render "posts/image_form", form: form %> 30 |
31 | <%= form.submit %> 32 |
33 | <% end %> 34 | -------------------------------------------------------------------------------- /app/views/posts/_image_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= form.file_field :images, multiple: true, direct_upload: true, data: { dropzone_target: 'input' } %> 3 |
4 |

Drag here to upload or click here to browse

5 | 2 MB file size maximum. Allowed file types png, jpg. 6 |
7 |
8 | -------------------------------------------------------------------------------- /app/views/posts/_post.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

<%= post.title %>

3 |

posted by <%= link_to post.user.full_name, user_path(post.user) %>

4 |
<%= pluralize(post.views, "view") %>
5 | <% post.images.each do |image| %> 6 | <%= image_tag(image, style:"width:300px; height: auto")%> 7 | <% end %> 8 |
9 | <%= post.body %> 10 |
11 |
12 | -------------------------------------------------------------------------------- /app/views/posts/_post.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! post, :id, :title, :body, :created_at, :updated_at 2 | json.url post_url(post, format: :json) 3 | -------------------------------------------------------------------------------- /app/views/posts/edit.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

Editing post

3 | <%= render "form", post: @post %> 4 |
5 |
6 | <%= link_to "Show this post", @post %> | 7 | <%= link_to "Back to posts", posts_path %> 8 |
9 |
10 | -------------------------------------------------------------------------------- /app/views/posts/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 |
3 |

Posts

4 |
5 | <% @posts.each do |post| %> 6 | <%= render post %> 7 |

8 | <%= link_to "Show this post", post %> 9 |

10 | <% end %> 11 |
12 | <%= link_to "New post", new_post_path %> 13 |
14 | -------------------------------------------------------------------------------- /app/views/posts/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @posts, partial: "posts/post", as: :post 2 | -------------------------------------------------------------------------------- /app/views/posts/new.html.erb: -------------------------------------------------------------------------------- 1 |
2 |

New post

3 | <%= render "form", post: @post %> 4 |
5 |
6 | <%= link_to "Back to posts", posts_path %> 7 |
8 |
9 | -------------------------------------------------------------------------------- /app/views/posts/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= render @post %> 3 |
4 | <%= link_to "Edit this post", edit_post_path(@post) %> | 5 | <%= link_to "Back to posts", posts_path %> 6 | <%= button_to "Destroy this post", @post, method: :delete %> 7 |
8 |
9 |
10 | <%= render 'comments/form', 11 | post: @post, 12 | comment: @post.comments.build, 13 | submit_label: "Reply" %> 14 | <% @comments.each do |comment| %> 15 | <%= render 'comments/comment', 16 | post: @post, 17 | comment: comment %> 18 | <% end %> 19 |
20 | -------------------------------------------------------------------------------- /app/views/posts/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "posts/post", post: @post 2 | -------------------------------------------------------------------------------- /app/views/projects/_draggable.html.erb: -------------------------------------------------------------------------------- 1 | <% dragActions = "dragstart->drag#dragStart 2 | dragover->drag#dragOver 3 | dragenter->drag#dragEnter 4 | drop->drag#drop 5 | dragend->drag#dragEnd" %> 6 | <%= content_tag(:div, class: 'bi bi-grip-vertical draggable', draggable: true, data: { 7 | controller: "drag", 8 | action: dragActions, 9 | resource_id: project.id, 10 | url: "/drag/project", 11 | parent: "projects", 12 | }) do %> 13 | <%= render project %> 14 | <% end %> 15 | -------------------------------------------------------------------------------- /app/views/projects/_form.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= form_with(model: project) do |form| %> 3 | <% if project.errors.any? %> 4 |
5 |

<%= pluralize(project.errors.count, "error") %> prohibited this project from being saved:

6 |
    7 | <% project.errors.each do |error| %> 8 |
  • <%= error.full_message %>
  • 9 | <% end %> 10 |
11 |
12 | <% end %> 13 |
14 |
15 | <%= form.label :title, style: "display: block" %> 16 | <%= form.text_field :title %> 17 |
18 |
19 | <%= form.label :link, style: "display: block" %> 20 | <%= form.text_field :link %> 21 |
22 |
23 |
24 | <%= form.label :body, style: "display: block" %> 25 | <%= form.rich_text_area :body %> 26 |
27 |
28 | <%= form.submit %> 29 |
30 | <% end %> 31 |
32 | -------------------------------------------------------------------------------- /app/views/projects/_project.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |
5 |

<%= link_to project.title, project %>

6 | <%= link_to project.link, project.link %> 7 |
8 |
9 |
10 | <%= project.body %> 11 |
12 |
13 |
14 |
15 | -------------------------------------------------------------------------------- /app/views/projects/_project.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! project, :id, :title, :link, :created_at, :updated_at 2 | json.url project_url(project, format: :json) 3 | -------------------------------------------------------------------------------- /app/views/projects/edit.html.erb: -------------------------------------------------------------------------------- 1 |

Editing project

2 | 3 | <%= render "form", project: @project %> 4 | 5 |
6 | 7 |
8 | <%= link_to "Show this project", @project %> | 9 | <%= link_to "Back to projects", projects_path %> 10 |
11 | -------------------------------------------------------------------------------- /app/views/projects/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 |
3 |

Projects

4 |

This page contains my most recent projects, feel free to look around!

5 |

Web projects contain a link to the demo, while other projects contain other relevant links.

6 |
7 | <% @projects.each do |project| %> 8 | <% if current_user&.admin? %> 9 | <%= render "projects/draggable", project: project %> 10 | <% else %> 11 | <%= render project %> 12 | <% end %> 13 | <% end %> 14 |
15 |
16 | -------------------------------------------------------------------------------- /app/views/projects/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @projects, partial: "projects/project", as: :project 2 | -------------------------------------------------------------------------------- /app/views/projects/new.html.erb: -------------------------------------------------------------------------------- 1 |

New project

2 | 3 | <%= render "form", project: @project %> 4 | 5 |
6 | 7 |
8 | <%= link_to "Back to projects", projects_path %> 9 |
10 | -------------------------------------------------------------------------------- /app/views/projects/show.html.erb: -------------------------------------------------------------------------------- 1 |

<%= notice %>

2 | <%= render @project %> 3 |
4 | <%= link_to "Edit this project", edit_project_path(@project) if current_user&.admin? %> | 5 | <%= link_to "Back to projects", projects_path, class: "btn btn-primary" %> 6 | <%= button_to "Destroy this project", @project, method: :delete if current_user&.admin? %> 7 |
8 | -------------------------------------------------------------------------------- /app/views/projects/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "projects/project", project: @project 2 | -------------------------------------------------------------------------------- /app/views/search/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= search_form_for(@query, url: search_path, method: :get, class:'d-flex') do |f| %> 2 | <%= f.search_field :title_or_content_body_or_user_email_or_user_first_name_or_user_last_name_or_category_name_i_cont_any, 3 | placeholder: "Search...", 4 | class: "form-control me-2" %> 5 | <%= f.submit "Search!", class:"btn btn-outline-success" %> 6 | <% end %> 7 | -------------------------------------------------------------------------------- /app/views/search/index.html.erb: -------------------------------------------------------------------------------- 1 |

<%= pluralize(@posts.count, "Result") %>

2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <% @posts.each do |post| %> 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% end %> 21 | 22 |
<%= sort_link(@query, :title, "Title", default_order: :asc) %><%= sort_link(@query, :category_name, "Category") %><%= sort_link(@query, :body, "Blog Content") %><%= sort_link(@query, :user_full_name, "Users") %>
<%= link_to post.title, post %><%= link_to post.category.name, post.category %><%= post.body.to_plain_text.truncate_words(25) %><%= link_to post.user.full_name, post.user %>
23 |
24 | -------------------------------------------------------------------------------- /app/views/user/_session_manager.html.erb: -------------------------------------------------------------------------------- 1 | <% if current_user %> 2 | 17 | <% else %> 18 | 21 | 24 | <% end %> 25 | -------------------------------------------------------------------------------- /app/views/users/profile.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= image_tag(@user.avatar, style:"width:150px; height: 150px; border-radius:100%") if @user.avatar.attached? %> 5 |
6 |
7 |

<%= @user.full_name %>

8 |
9 | <%= pluralize(@user.comments.size, "Comment") %> | 10 | <%= pluralize(@user.views, "view") %> | 11 | <%= pluralize(@total_views, "Total Post View") %> 12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | <% @posts.each do |post| %> 20 |
21 |
22 |
<%= link_to post.title, post %>
23 |

<%= "#{time_ago_in_words(post.created_at)} ago" %>

24 |

<%= post.body.to_plain_text.truncate_words(100) %>

25 |
26 |
27 | <% end %> 28 |
29 |
30 |
31 |
32 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../../Gemfile", __FILE__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || cli_arg_version || 66 | bundler_requirement_for(lockfile_version) 67 | end 68 | 69 | def bundler_requirement_for(version) 70 | return "#{Gem::Requirement.default}.a" unless version 71 | 72 | bundler_gem_version = Gem::Version.new(version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /bin/importmap: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require_relative "../config/application" 4 | require "importmap/commands" 5 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path("../config/application", __dir__) 3 | require_relative "../config/boot" 4 | require "rails/commands" 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require "fileutils" 3 | 4 | # path to your application root. 5 | APP_ROOT = File.expand_path("..", __dir__) 6 | 7 | def system!(*args) 8 | system(*args) || abort("\n== Command #{args} failed ==") 9 | end 10 | 11 | FileUtils.chdir APP_ROOT do 12 | # This script is a way to set up or update your development environment automatically. 13 | # This script is idempotent, so that you can run it at any time and get an expectable outcome. 14 | # Add necessary setup steps to this file. 15 | 16 | puts "== Installing dependencies ==" 17 | system! "gem install bundler --conservative" 18 | system("bundle check") || system!("bundle install") 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?("config/database.yml") 22 | # FileUtils.cp "config/database.yml.sample", "config/database.yml" 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! "bin/rails db:prepare" 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! "bin/rails log:clear tmp:clear" 30 | 31 | puts "\n== Restarting application server ==" 32 | system! "bin/rails restart" 33 | end 34 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require_relative "config/environment" 4 | 5 | run Rails.application 6 | Rails.application.load_server 7 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module BlogDemo 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 7.0 13 | config.action_mailer.default_url_options = { host: 'http://localhost:3000' } 14 | 15 | # Configuration for the application, engines, and railties goes here. 16 | # 17 | # These settings can be overridden in specific environments using the files 18 | # in config/environments, which are processed later. 19 | # 20 | # config.time_zone = "Central Time (US & Canada)" 21 | # config.eager_load_paths << Rails.root.join("extras") 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../Gemfile", __dir__) 2 | 3 | require "bundler/setup" # Set up gems listed in the Gemfile. 4 | require "bootsnap/setup" # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: redis 3 | url: redis://localhost:6379/1 4 | 5 | test: 6 | adapter: test 7 | 8 | production: 9 | adapter: redis 10 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 11 | channel_prefix: blog_demo_production 12 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | EQetej9D1J1oO4qy4EIz64zspGOZ1Q6jW/qNzBmhyvwbYvDKJL1mR6hkCdMBtNGUeJr9UMuaeJWvPCQltsXCATtA2fva6tLkeAAIPM9Xfip4mrkqFLydcObXLQPTwvgUtGB2hoWYREgjYvR3EzFYjNKbXCzG+hg1MZTfnEHi4lviQRzbPs09ySV9L8o9FV9KWjYUxbwjEjHijk28pE5Neyi/GDmn/KRM0UV0jG/C4MUH3NQbANtHQsDhfSCtSTZegnsBuY5Ddj4WThvrdFpwRU4wKOIZv1n1V2qFhIEGgyYu31/3Q5jdDhHScraxI5F33P7LCeeJruoIiQJywWmDkjZU1s0FTaO0BdMQ8rsSeGY/8mFCg5Zs0irj6dHsU6XN34SS397qAFClBBaPYzj6cc+fentqwt5Dn+V676teiMuWO76XU0tf86H89RT25EidPWrp52EgwdSIoQy6KT6LuIIqmu4S36P27SCeB099sB5JFi+0jk7BlnenLV8j6MKX47/exF7EgRKGodVTaZspMsPzFGHfuShn4HxN5sKHOr+h9Fu+12dVsHZgYKPJuJMf61Wsvz9u3xIUKJF2yCsA//NPcB9DttLoTCWAHiTBFZ5WO9COEBwP0Zy9t2vIvQ/ExwcpXwmM1Dfl+0qwYYKYgJdUgBSRN2xhsh5Rz3K71ilRaK1L5EB1GxHpx+PKc0qaDx3Gq5NEtC/DFSIjrDMxUnMUnyT4kK6FAuzflpaX5Wg1kWYw3KrclzXPglT5Jo5QqUip0HE2iVS1Cv8xZE/6TG2wDtKz8g2XNaa94m+sjIuSfz1MlJz6rg1yzA7Z2Xjvs6VIzhnb7BhZU1/V+zCYTMlXa0rSPWfrOkZwh3XtJxlmcaQl4by4jc4tyAs56jTdEDyx6TAYhmkUXFaBm1OE//YaVfE=--0oZL8rqTnRWOE46Z--dpm+Sh3QckXBMZxFXc0/4g== -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 9.3 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On macOS with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On macOS 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 | # https://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 23 | 24 | development: 25 | <<: *default 26 | database: blog_application_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 running Rails. 32 | #username: blog_application 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: blog_application_test 61 | 62 | # As with config/credentials.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 or a full connection URL as an environment 67 | # variable when you boot the app. For example: 68 | # 69 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 70 | # 71 | # If the connection URL is provided in the special DATABASE_URL environment 72 | # variable, Rails will automatically merge its configuration values on top of 73 | # the values provided in this file. Alternatively, you can specify a connection 74 | # URL environment variable explicitly: 75 | # 76 | # production: 77 | # url: <%= ENV["MY_APP_DATABASE_URL"] %> 78 | # 79 | # Read https://guides.rubyonrails.org/configuring.html#configuring-a-database 80 | # for a full overview on how database connection configuration can be specified. 81 | # 82 | production: 83 | <<: *default 84 | database: blog_application_production 85 | username: blog_application 86 | password: <%= Rails.application.credentials.dig(:prod_db_username) %> 87 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | 7 | Rails.logger = Logger.new("log/#{Rails.env}.log") 8 | Rails.logger.level = Logger::INFO 9 | 10 | Rails.logger.datetime_format = '%Y-%m-%d %H:%M:%S' 11 | Rails.logger.formatter = proc do |severity, datetime, progname, msg| 12 | "|#{datetime}|#{severity}|#{progname}|#{msg}\n" 13 | end 14 | Rails.logger.info("Environment: #{Rails.env}") do 15 | 'Started The Rails App!' 16 | end 17 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | config.after_initialize do 5 | Bullet.enable = true 6 | Bullet.alert = true 7 | Bullet.bullet_logger = true 8 | Bullet.console = true 9 | # Bullet.growl = true 10 | Bullet.rails_logger = true 11 | Bullet.add_footer = true 12 | end 13 | 14 | # Settings specified here will take precedence over those in config/application.rb. 15 | 16 | # In the development environment your application's code is reloaded any time 17 | # it changes. This slows down response time but is perfect for development 18 | # since you don't have to restart the web server when you make code changes. 19 | config.cache_classes = false 20 | 21 | # Do not eager load code on boot. 22 | config.eager_load = false 23 | 24 | # Show full error reports. 25 | config.consider_all_requests_local = true 26 | 27 | # Enable server timing 28 | config.server_timing = true 29 | 30 | # Enable/disable caching. By default caching is disabled. 31 | # Run rails dev:cache to toggle caching. 32 | if Rails.root.join("tmp/caching-dev.txt").exist? 33 | config.action_controller.perform_caching = true 34 | config.action_controller.enable_fragment_cache_logging = true 35 | 36 | config.cache_store = :memory_store 37 | config.public_file_server.headers = { 38 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 39 | } 40 | else 41 | config.action_controller.perform_caching = false 42 | 43 | config.cache_store = :null_store 44 | end 45 | 46 | # Store uploaded files on the local file system (see config/storage.yml for options). 47 | config.active_storage.service = :local 48 | 49 | # Don't care if the mailer can't send. 50 | config.action_mailer.raise_delivery_errors = false 51 | 52 | config.action_mailer.perform_caching = false 53 | 54 | # Print deprecation notices to the Rails logger. 55 | config.active_support.deprecation = :log 56 | 57 | # Raise exceptions for disallowed deprecations. 58 | config.active_support.disallowed_deprecation = :raise 59 | 60 | # Tell Active Support which deprecation messages to disallow. 61 | config.active_support.disallowed_deprecation_warnings = [] 62 | 63 | # Raise an error on page load if there are pending migrations. 64 | config.active_record.migration_error = :page_load 65 | 66 | # Highlight code that triggered database queries in logs. 67 | config.active_record.verbose_query_logs = true 68 | 69 | # Suppress logger output for asset requests. 70 | config.assets.quiet = true 71 | 72 | # Raises error for missing translations. 73 | # config.i18n.raise_on_missing_translations = true 74 | 75 | # Annotate rendered view with file names. 76 | # config.action_view.annotate_rendered_view_with_filenames = true 77 | 78 | # Uncomment if you wish to allow Action Cable access from any origin. 79 | # config.action_cable.disable_request_forgery_protection = true 80 | end 81 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | # config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? 26 | 27 | # Compress CSS using a preprocessor. 28 | # config.assets.css_compressor = :sass 29 | 30 | # Do not fallback to assets pipeline if a precompiled asset is missed. 31 | config.assets.compile = false 32 | 33 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 34 | # config.asset_host = "http://assets.example.com" 35 | 36 | # Specifies the header that your server uses for sending files. 37 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for Apache 38 | # config.action_dispatch.x_sendfile_header = "X-Accel-Redirect" # for NGINX 39 | 40 | # Store uploaded files on the local file system (see config/storage.yml for options). 41 | config.active_storage.service = :local 42 | 43 | # Mount Action Cable outside main process or domain. 44 | # config.action_cable.mount_path = nil 45 | # config.action_cable.url = "wss://example.com/cable" 46 | # config.action_cable.allowed_request_origins = [ "http://example.com", /http:\/\/example.*/ ] 47 | 48 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 49 | # config.force_ssl = true 50 | 51 | # Include generic and useful information about system operation, but avoid logging too much 52 | # information to avoid inadvertent exposure of personally identifiable information (PII). 53 | config.log_level = :info 54 | 55 | # Prepend all log lines with the following tags. 56 | config.log_tags = [ :request_id ] 57 | 58 | # Use a different cache store in production. 59 | # config.cache_store = :mem_cache_store 60 | 61 | # Use a real queuing backend for Active Job (and separate queues per environment). 62 | # config.active_job.queue_adapter = :resque 63 | # config.active_job.queue_name_prefix = "blog_demo_production" 64 | 65 | config.action_mailer.perform_caching = false 66 | 67 | # Ignore bad email addresses and do not raise email delivery errors. 68 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 69 | # config.action_mailer.raise_delivery_errors = false 70 | 71 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 72 | # the I18n.default_locale when a translation cannot be found). 73 | config.i18n.fallbacks = true 74 | 75 | # Don't log any deprecations. 76 | config.active_support.report_deprecations = false 77 | 78 | # Use default logging formatter so that PID and timestamp are not suppressed. 79 | config.log_formatter = ::Logger::Formatter.new 80 | 81 | # Use a different logger for distributed setups. 82 | # require "syslog/logger" 83 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new "app-name") 84 | 85 | if ENV["RAILS_LOG_TO_STDOUT"].present? 86 | logger = ActiveSupport::Logger.new(STDOUT) 87 | logger.formatter = config.log_formatter 88 | config.logger = ActiveSupport::TaggedLogging.new(logger) 89 | end 90 | 91 | # Do not dump schema after migrations. 92 | config.active_record.dump_schema_after_migration = false 93 | end 94 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | # The test environment is used exclusively to run your application's 4 | # test suite. You never need to work with it otherwise. Remember that 5 | # your test database is "scratch space" for the test suite and is wiped 6 | # and recreated between test runs. Don't rely on the data there! 7 | 8 | Rails.application.configure do 9 | # Settings specified here will take precedence over those in config/application.rb. 10 | 11 | # Turn false under Spring and add config.action_view.cache_template_loading = true. 12 | config.cache_classes = true 13 | 14 | # Eager loading loads your whole application. When running a single test locally, 15 | # this probably isn't necessary. It's a good idea to do in a continuous integration 16 | # system, or in some way before deploying your code. 17 | config.eager_load = ENV["CI"].present? 18 | 19 | # Configure public file server for tests with Cache-Control for performance. 20 | config.public_file_server.enabled = true 21 | config.public_file_server.headers = { 22 | "Cache-Control" => "public, max-age=#{1.hour.to_i}" 23 | } 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | config.cache_store = :null_store 29 | 30 | # Raise exceptions instead of rendering exception templates. 31 | config.action_dispatch.show_exceptions = false 32 | 33 | # Disable request forgery protection in test environment. 34 | config.action_controller.allow_forgery_protection = false 35 | 36 | # Store uploaded files on the local file system in a temporary directory. 37 | config.active_storage.service = :test 38 | 39 | config.action_mailer.perform_caching = false 40 | 41 | # Tell Action Mailer not to deliver emails to the real world. 42 | # The :test delivery method accumulates sent emails in the 43 | # ActionMailer::Base.deliveries array. 44 | config.action_mailer.delivery_method = :test 45 | 46 | # Print deprecation notices to the stderr. 47 | config.active_support.deprecation = :stderr 48 | 49 | # Raise exceptions for disallowed deprecations. 50 | config.active_support.disallowed_deprecation = :raise 51 | 52 | # Tell Active Support which deprecation messages to disallow. 53 | config.active_support.disallowed_deprecation_warnings = [] 54 | 55 | # Raises error for missing translations. 56 | # config.i18n.raise_on_missing_translations = true 57 | 58 | # Annotate rendered view with file names. 59 | # config.action_view.annotate_rendered_view_with_filenames = true 60 | end 61 | -------------------------------------------------------------------------------- /config/importmap.rb: -------------------------------------------------------------------------------- 1 | # Pin npm packages by running ./bin/importmap 2 | 3 | pin 'application', preload: true 4 | pin '@hotwired/turbo-rails', to: 'turbo.min.js', preload: true 5 | pin '@hotwired/stimulus', to: 'stimulus.min.js', preload: true 6 | pin '@hotwired/stimulus-loading', to: 'stimulus-loading.js', preload: true 7 | pin_all_from 'app/javascript/controllers', under: 'controllers' 8 | pin 'trix' 9 | pin '@rails/actiontext', to: 'actiontext.js' 10 | pin 'chartkick', to: 'chartkick.js' 11 | pin 'Chart.bundle', to: 'Chart.bundle.js' 12 | pin "@picmo/popup-picker", to: "https://ga.jspm.io/npm:@picmo/popup-picker@5.4.0/dist/index.js" 13 | pin "picmo", to: "https://ga.jspm.io/npm:picmo@5.4.2/dist/index.js" 14 | pin "dropzone", to: "https://ga.jspm.io/npm:dropzone@6.0.0-beta.2/dist/dropzone.mjs" 15 | pin "just-extend", to: "https://ga.jspm.io/npm:just-extend@5.1.1/index.esm.js" 16 | pin "@rails/activestorage", to: "https://ga.jspm.io/npm:@rails/activestorage@7.0.3-1/app/assets/javascripts/activestorage.esm.js" 17 | -------------------------------------------------------------------------------- /config/initializers/ahoy.rb: -------------------------------------------------------------------------------- 1 | class Ahoy::Store < Ahoy::DatabaseStore 2 | end 3 | 4 | # set to true for JavaScript tracking 5 | Ahoy.api = false 6 | 7 | # set to true for geocoding (and add the geocoder gem to your Gemfile) 8 | # we recommend configuring local geocoding as well 9 | # see https://github.com/ankane/ahoy#geocoding 10 | Ahoy.geocode = false 11 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = "1.0" 5 | 6 | # Add additional assets to the asset load path. 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in the app/assets 11 | # folder are already added. 12 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 13 | -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap and inline scripts 20 | # config.content_security_policy_nonce_generator = ->(request) { request.session.id.to_s } 21 | # config.content_security_policy_nonce_directives = %w(script-src) 22 | # 23 | # # Report CSP violations to a specified URI. See: 24 | # # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # # config.content_security_policy_report_only = true 26 | # end 27 | -------------------------------------------------------------------------------- /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 += [ 5 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 6 | ] 7 | -------------------------------------------------------------------------------- /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 | # This adds an option to treat reserved words as conflicts rather than exceptions. 23 | # When there is no good candidate, a UUID will be appended, matching the existing 24 | # conflict behavior. 25 | 26 | # config.treat_reserved_as_conflict = true 27 | 28 | # ## Friendly Finders 29 | # 30 | # Uncomment this to use friendly finders in all models. By default, if 31 | # you wish to find a record by its friendly id, you must do: 32 | # 33 | # MyModel.friendly.find('foo') 34 | # 35 | # If you uncomment this, you can do: 36 | # 37 | # MyModel.find('foo') 38 | # 39 | # This is significantly more convenient but may not be appropriate for 40 | # all applications, so you must explicity opt-in to this behavior. You can 41 | # always also configure it on a per-model basis if you prefer. 42 | # 43 | # Something else to consider is that using the :finders addon boosts 44 | # performance because it will avoid Rails-internal code that makes runtime 45 | # calls to `Module.extend`. 46 | # 47 | # config.use :finders 48 | # 49 | # ## Slugs 50 | # 51 | # Most applications will use the :slugged module everywhere. If you wish 52 | # to do so, uncomment the following line. 53 | # 54 | # config.use :slugged 55 | # 56 | # By default, FriendlyId's :slugged addon expects the slug column to be named 57 | # 'slug', but you can change it if you wish. 58 | # 59 | # config.slug_column = 'slug' 60 | # 61 | # By default, slug has no size limit, but you can change it if you wish. 62 | # 63 | # config.slug_limit = 255 64 | # 65 | # When FriendlyId can not generate a unique ID from your base method, it appends 66 | # a UUID, separated by a single dash. You can configure the character used as the 67 | # separator. If you're upgrading from FriendlyId 4, you may wish to replace this 68 | # with two dashes. 69 | # 70 | # config.sequence_separator = '-' 71 | # 72 | # Note that you must use the :slugged addon **prior** to the line which 73 | # configures the sequence separator, or else FriendlyId will raise an undefined 74 | # method error. 75 | # 76 | # ## Tips and Tricks 77 | # 78 | # ### Controlling when slugs are generated 79 | # 80 | # As of FriendlyId 5.0, new slugs are generated only when the slug field is 81 | # nil, but if you're using a column as your base method can change this 82 | # behavior by overriding the `should_generate_new_friendly_id?` method that 83 | # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave 84 | # more like 4.0. 85 | # Note: Use(include) Slugged module in the config if using the anonymous module. 86 | # If you have `friendly_id :name, use: slugged` in the model, Slugged module 87 | # is included after the anonymous module defined in the initializer, so it 88 | # overrides the `should_generate_new_friendly_id?` method from the anonymous module. 89 | # 90 | # config.use :slugged 91 | # config.use Module.new { 92 | # def should_generate_new_friendly_id? 93 | # slug.blank? || _changed? 94 | # end 95 | # } 96 | # 97 | # FriendlyId uses Rails's `parameterize` method to generate slugs, but for 98 | # languages that don't use the Roman alphabet, that's not usually sufficient. 99 | # Here we use the Babosa library to transliterate Russian Cyrillic slugs to 100 | # ASCII. If you use this, don't forget to add "babosa" to your Gemfile. 101 | # 102 | # config.use Module.new { 103 | # def normalize_friendly_id(text) 104 | # text.to_slug.normalize! :transliterations => [:russian, :latin] 105 | # end 106 | # } 107 | end 108 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, "\\1en" 8 | # inflect.singular /^(ox)en/i, "\\1" 9 | # inflect.irregular "person", "people" 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym "RESTful" 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/permissions_policy.rb: -------------------------------------------------------------------------------- 1 | # Define an application-wide HTTP permissions policy. For further 2 | # information see https://developers.google.com/web/updates/2018/06/feature-policy 3 | # 4 | # Rails.application.config.permissions_policy do |f| 5 | # f.camera :none 6 | # f.gyroscope :none 7 | # f.microphone :none 8 | # f.usb :none 9 | # f.fullscreen :self 10 | # f.payment :self, "https://secure.example.com" 11 | # end 12 | -------------------------------------------------------------------------------- /config/locales/devise.en.yml: -------------------------------------------------------------------------------- 1 | # Additional translations at https://github.com/heartcombo/devise/wiki/I18n 2 | 3 | en: 4 | devise: 5 | confirmations: 6 | confirmed: "Your email address has been successfully confirmed." 7 | send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." 8 | send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." 9 | failure: 10 | already_authenticated: "You are already signed in." 11 | inactive: "Your account is not activated yet." 12 | invalid: "Invalid %{authentication_keys} or password." 13 | locked: "Your account is locked." 14 | last_attempt: "You have one more attempt before your account is locked." 15 | not_found_in_database: "Invalid %{authentication_keys} or password." 16 | timeout: "Your session expired. Please sign in again to continue." 17 | unauthenticated: "You need to sign in or sign up before continuing." 18 | unconfirmed: "You have to confirm your email address before continuing." 19 | mailer: 20 | confirmation_instructions: 21 | subject: "Confirmation instructions" 22 | reset_password_instructions: 23 | subject: "Reset password instructions" 24 | unlock_instructions: 25 | subject: "Unlock instructions" 26 | email_changed: 27 | subject: "Email Changed" 28 | password_change: 29 | subject: "Password Changed" 30 | omniauth_callbacks: 31 | failure: "Could not authenticate you from %{kind} because \"%{reason}\"." 32 | success: "Successfully authenticated from %{kind} account." 33 | passwords: 34 | no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." 35 | send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." 36 | send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." 37 | updated: "Your password has been changed successfully. You are now signed in." 38 | updated_not_active: "Your password has been changed successfully." 39 | registrations: 40 | destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." 41 | signed_up: "Welcome! You have signed up successfully." 42 | signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." 43 | signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." 44 | signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." 45 | update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirmation link to confirm your new email address." 46 | updated: "Your account has been updated successfully." 47 | updated_but_not_signed_in: "Your account has been updated successfully, but since your password was changed, you need to sign in again." 48 | sessions: 49 | signed_in: "Signed in successfully." 50 | signed_out: "Signed out successfully." 51 | already_signed_out: "Signed out successfully." 52 | unlocks: 53 | send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." 54 | send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." 55 | unlocked: "Your account has been unlocked successfully. Please sign in to continue." 56 | errors: 57 | messages: 58 | already_confirmed: "was already confirmed, please try signing in" 59 | confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" 60 | expired: "has expired, please request a new one" 61 | not_found: "not found" 62 | not_locked: "was not locked" 63 | not_saved: 64 | one: "1 error prohibited this %{resource} from being saved:" 65 | other: "%{count} errors prohibited this %{resource} from being saved:" 66 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t "hello" 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t("hello") %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # "true": "foo" 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at https://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count } 9 | threads min_threads_count, max_threads_count 10 | 11 | # Specifies the `worker_timeout` threshold that Puma will use to wait before 12 | # terminating a worker in development environments. 13 | # 14 | worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development" 15 | 16 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 17 | # 18 | port ENV.fetch("PORT") { 3000 } 19 | 20 | # Specifies the `environment` that Puma will run in. 21 | # 22 | environment ENV.fetch("RAILS_ENV") { "development" } 23 | 24 | # Specifies the `pidfile` that Puma will use. 25 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 26 | 27 | # Specifies the number of `workers` to boot in clustered mode. 28 | # Workers are forked web server processes. If using threads and workers together 29 | # the concurrency of the application would be max `threads` * `workers`. 30 | # Workers do not work on JRuby or Windows (both of which do not support 31 | # processes). 32 | # 33 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 34 | 35 | # Use the `preload_app!` method when specifying a `workers` number. 36 | # This directive tells Puma to first boot the application and load code 37 | # before forking the application. This takes advantage of Copy On Write 38 | # process behavior so workers use less memory. 39 | # 40 | # preload_app! 41 | 42 | # Allow puma to be restarted by `bin/rails restart` command. 43 | plugin :tmp_restart 44 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :projects 3 | patch 'drag/project' 4 | get 'members/dashboard' 5 | resources :categories 6 | authenticated :user, ->(user) { user.admin? } do 7 | get 'admin', to: 'admin#index' 8 | get 'admin/posts' 9 | get 'admin/comments' 10 | get 'admin/users' 11 | get 'admin/post/:id', to: 'admin#show_post', as: 'admin_post' 12 | end 13 | 14 | get 'checkout', to: 'checkouts#show' 15 | get 'checkout/success', to: 'checkouts#success' 16 | get 'billing', to: 'billing#show' 17 | 18 | get 'search', to: 'search#index' 19 | 20 | devise_for :users, controllers: { 21 | sessions: 'users/sessions', 22 | registrations: 'users/registrations' 23 | } 24 | get '/u/:id', to: 'users#profile', as: 'user' 25 | resources :after_signup 26 | 27 | # /posts/1/comments/4 28 | resources :posts do 29 | resources :comments 30 | end 31 | 32 | get 'about', to: 'pages#about' 33 | # Define your application routes per the DSL in https://guides.rubyonrails.org/routing.html 34 | 35 | # Defines the root path route ("/") 36 | root 'pages#home' 37 | end 38 | -------------------------------------------------------------------------------- /config/schedule.rb: -------------------------------------------------------------------------------- 1 | # Use this file to easily define all of your cron jobs. 2 | # 3 | # It's helpful, but not entirely necessary to understand cron before proceeding. 4 | # http://en.wikipedia.org/wiki/Cron 5 | 6 | # Example: 7 | # 8 | # set :output, "/path/to/my/cron_log.log" 9 | # 10 | # every 2.hours do 11 | # command "/usr/bin/some_great_command" 12 | # runner "MyModel.some_method" 13 | # rake "some:great:rake:task" 14 | # end 15 | # 16 | # every 4.days do 17 | # runner "AnotherModel.prune_old_records" 18 | # end 19 | 20 | # Clear cron: crontab -r 21 | # update cron: whenever --update-crontab 22 | # update cron development: whenever --update-crontab --set environment='development' 23 | 24 | set :output, './log/cron.log' 25 | 26 | every 1.minutes do 27 | runner 'puts Time.now' 28 | runner 'puts Rails.env' 29 | runner "puts 'Hello, world'" 30 | runner 'Category.scheduled_category' 31 | end 32 | 33 | # Learn more: http://github.com/javan/whenever 34 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use bin/rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket-<%= Rails.env %> 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket-<%= Rails.env %> 23 | 24 | # Use bin/rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name-<%= Rails.env %> 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /db/migrate/20220131064930_create_posts.rb: -------------------------------------------------------------------------------- 1 | class CreatePosts < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :posts do |t| 4 | t.string :title 5 | t.text :body 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20220209082551_add_views_to_posts.rb: -------------------------------------------------------------------------------- 1 | class AddViewsToPosts < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :posts, :views, :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220209083507_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateUsers < ActiveRecord::Migration[7.0] 4 | def change 5 | create_table :users do |t| 6 | ## Database authenticatable 7 | t.string :email, null: false, default: "" 8 | t.string :encrypted_password, null: false, default: "" 9 | 10 | ## Recoverable 11 | t.string :reset_password_token 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## Trackable 18 | # t.integer :sign_in_count, default: 0, null: false 19 | # t.datetime :current_sign_in_at 20 | # t.datetime :last_sign_in_at 21 | # t.string :current_sign_in_ip 22 | # t.string :last_sign_in_ip 23 | 24 | ## Confirmable 25 | # t.string :confirmation_token 26 | # t.datetime :confirmed_at 27 | # t.datetime :confirmation_sent_at 28 | # t.string :unconfirmed_email # Only if using reconfirmable 29 | 30 | ## Lockable 31 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 32 | # t.string :unlock_token # Only if unlock strategy is :email or :both 33 | # t.datetime :locked_at 34 | 35 | 36 | t.timestamps null: false 37 | end 38 | 39 | add_index :users, :email, unique: true 40 | add_index :users, :reset_password_token, unique: true 41 | # add_index :users, :confirmation_token, unique: true 42 | # add_index :users, :unlock_token, unique: true 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /db/migrate/20220209085715_add_user_to_posts.rb: -------------------------------------------------------------------------------- 1 | class AddUserToPosts < ActiveRecord::Migration[7.0] 2 | def change 3 | add_reference :posts, :user, null: false, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220209090418_add_name_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddNameToUser < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220209091520_add_views_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddViewsToUser < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :views, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220209091605_change_views_for_users.rb: -------------------------------------------------------------------------------- 1 | class ChangeViewsForUsers < ActiveRecord::Migration[7.0] 2 | def change 3 | change_column :users, :views, :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220216073155_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :comments do |t| 4 | t.belongs_to :post, null: false, foreign_key: true 5 | t.belongs_to :user, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20220216073214_create_active_storage_tables.active_storage.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from active_storage (originally 20170806125915) 2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2] 3 | def change 4 | # Use Active Record's configured type for primary and foreign keys 5 | primary_key_type, foreign_key_type = primary_and_foreign_key_types 6 | 7 | create_table :active_storage_blobs, id: primary_key_type do |t| 8 | t.string :key, null: false 9 | t.string :filename, null: false 10 | t.string :content_type 11 | t.text :metadata 12 | t.string :service_name, null: false 13 | t.bigint :byte_size, null: false 14 | t.string :checksum 15 | 16 | if connection.supports_datetime_with_precision? 17 | t.datetime :created_at, precision: 6, null: false 18 | else 19 | t.datetime :created_at, null: false 20 | end 21 | 22 | t.index [ :key ], unique: true 23 | end 24 | 25 | create_table :active_storage_attachments, id: primary_key_type do |t| 26 | t.string :name, null: false 27 | t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type 28 | t.references :blob, null: false, type: foreign_key_type 29 | 30 | if connection.supports_datetime_with_precision? 31 | t.datetime :created_at, precision: 6, null: false 32 | else 33 | t.datetime :created_at, null: false 34 | end 35 | 36 | t.index [ :record_type, :record_id, :name, :blob_id ], name: :index_active_storage_attachments_uniqueness, unique: true 37 | t.foreign_key :active_storage_blobs, column: :blob_id 38 | end 39 | 40 | create_table :active_storage_variant_records, id: primary_key_type do |t| 41 | t.belongs_to :blob, null: false, index: false, type: foreign_key_type 42 | t.string :variation_digest, null: false 43 | 44 | t.index [ :blob_id, :variation_digest ], name: :index_active_storage_variant_records_uniqueness, unique: true 45 | t.foreign_key :active_storage_blobs, column: :blob_id 46 | end 47 | end 48 | 49 | private 50 | def primary_and_foreign_key_types 51 | config = Rails.configuration.generators 52 | setting = config.options[config.orm][:primary_key_type] 53 | primary_key_type = setting || :primary_key 54 | foreign_key_type = setting || :bigint 55 | [primary_key_type, foreign_key_type] 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /db/migrate/20220216073215_create_action_text_tables.action_text.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from action_text (originally 20180528164100) 2 | class CreateActionTextTables < ActiveRecord::Migration[6.0] 3 | def change 4 | # Use Active Record's configured type for primary and foreign keys 5 | primary_key_type, foreign_key_type = primary_and_foreign_key_types 6 | 7 | create_table :action_text_rich_texts, id: primary_key_type do |t| 8 | t.string :name, null: false 9 | t.text :body, size: :long 10 | t.references :record, null: false, polymorphic: true, index: false, type: foreign_key_type 11 | 12 | t.timestamps 13 | 14 | t.index [ :record_type, :record_id, :name ], name: "index_action_text_rich_texts_uniqueness", unique: true 15 | end 16 | end 17 | 18 | private 19 | def primary_and_foreign_key_types 20 | config = Rails.configuration.generators 21 | setting = config.options[config.orm][:primary_key_type] 22 | primary_key_type = setting || :primary_key 23 | foreign_key_type = setting || :bigint 24 | [primary_key_type, foreign_key_type] 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /db/migrate/20220302075632_create_notifications.rb: -------------------------------------------------------------------------------- 1 | class CreateNotifications < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :notifications do |t| 4 | t.references :recipient, polymorphic: true, null: false 5 | t.string :type, null: false 6 | t.json :params 7 | t.datetime :read_at 8 | 9 | t.timestamps 10 | end 11 | add_index :notifications, :read_at 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20220316052648_add_role_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddRoleToUser < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :role, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220316052704_remove_body_from_post.rb: -------------------------------------------------------------------------------- 1 | class RemoveBodyFromPost < ActiveRecord::Migration[7.0] 2 | def change 3 | remove_column :posts, :body, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220323053602_add_slug_to_posts.rb: -------------------------------------------------------------------------------- 1 | class AddSlugToPosts < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :posts, :slug, :string 4 | add_index :posts, :slug, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20220323053611_create_friendly_id_slugs.rb: -------------------------------------------------------------------------------- 1 | MIGRATION_CLASS = 2 | if ActiveRecord::VERSION::MAJOR >= 5 3 | ActiveRecord::Migration["#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}"] 4 | else 5 | ActiveRecord::Migration 6 | end 7 | 8 | class CreateFriendlyIdSlugs < MIGRATION_CLASS 9 | def change 10 | create_table :friendly_id_slugs do |t| 11 | t.string :slug, :null => false 12 | t.integer :sluggable_id, :null => false 13 | t.string :sluggable_type, :limit => 50 14 | t.string :scope 15 | t.datetime :created_at 16 | end 17 | add_index :friendly_id_slugs, [:sluggable_type, :sluggable_id] 18 | add_index :friendly_id_slugs, [:slug, :sluggable_type], length: { slug: 140, sluggable_type: 50 } 19 | add_index :friendly_id_slugs, [:slug, :sluggable_type, :scope], length: { slug: 70, sluggable_type: 50, scope: 70 }, unique: true 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /db/migrate/20220330053457_add_comment_counter_cache_to_posts.rb: -------------------------------------------------------------------------------- 1 | class AddCommentCounterCacheToPosts < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :posts, :comments_count, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220330053548_populate_post_comments_count.rb: -------------------------------------------------------------------------------- 1 | class PopulatePostCommentsCount < ActiveRecord::Migration[7.0] 2 | def change 3 | Post.all.each do |post| 4 | Post.reset_counters(post.id, :comments) 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20220405234838_change_json_column_in_notifications.rb: -------------------------------------------------------------------------------- 1 | class ChangeJsonColumnInNotifications < ActiveRecord::Migration[7.0] 2 | def change 3 | change_column :notifications, :params, :jsonb, using: 'params::jsonb' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220413060450_add_names_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddNamesToUser < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :first_name, :string 4 | add_column :users, :last_name, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20220413060517_create_addresses.rb: -------------------------------------------------------------------------------- 1 | class CreateAddresses < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :addresses do |t| 4 | t.string :street 5 | t.string :city 6 | t.string :state 7 | t.integer :zip 8 | t.string :country 9 | t.references :user, foreign_key: true 10 | 11 | t.timestamps 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20220413060531_add_address_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddAddressToUser < ActiveRecord::Migration[7.0] 2 | def change 3 | add_reference :users, :address, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220413060545_remove_name_from_user.rb: -------------------------------------------------------------------------------- 1 | class RemoveNameFromUser < ActiveRecord::Migration[7.0] 2 | def change 3 | remove_column :users, :name, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220427062239_create_categories.rb: -------------------------------------------------------------------------------- 1 | class CreateCategories < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :categories do |t| 4 | t.string :name 5 | t.boolean :display_in_nav, default: false 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20220427062305_add_category_to_posts.rb: -------------------------------------------------------------------------------- 1 | class AddCategoryToPosts < ActiveRecord::Migration[7.0] 2 | def change 3 | add_reference :posts, :category, null: false, foreign_key: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20220511071651_create_pay_tables.pay.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from pay (originally 1) 2 | class CreatePayTables < ActiveRecord::Migration[6.0] 3 | def change 4 | create_table :pay_customers do |t| 5 | t.belongs_to :owner, polymorphic: true, index: false 6 | t.string :processor, null: false 7 | t.string :processor_id 8 | t.boolean :default 9 | t.public_send Pay::Adapter.json_column_type, :data 10 | t.datetime :deleted_at 11 | t.timestamps 12 | end 13 | add_index :pay_customers, [:owner_type, :owner_id, :deleted_at, :default], name: :pay_customer_owner_index 14 | add_index :pay_customers, [:processor, :processor_id], unique: true 15 | 16 | create_table :pay_merchants do |t| 17 | t.belongs_to :owner, polymorphic: true, index: false 18 | t.string :processor, null: false 19 | t.string :processor_id 20 | t.boolean :default 21 | t.public_send Pay::Adapter.json_column_type, :data 22 | t.timestamps 23 | end 24 | add_index :pay_merchants, [:owner_type, :owner_id, :processor] 25 | 26 | create_table :pay_payment_methods do |t| 27 | t.belongs_to :customer, foreign_key: {to_table: :pay_customers}, null: false, index: false 28 | t.string :processor_id, null: false 29 | t.boolean :default 30 | t.string :type 31 | t.public_send Pay::Adapter.json_column_type, :data 32 | t.timestamps 33 | end 34 | add_index :pay_payment_methods, [:customer_id, :processor_id], unique: true 35 | 36 | create_table :pay_subscriptions do |t| 37 | t.belongs_to :customer, foreign_key: {to_table: :pay_customers}, null: false, index: false 38 | t.string :name, null: false 39 | t.string :processor_id, null: false 40 | t.string :processor_plan, null: false 41 | t.integer :quantity, default: 1, null: false 42 | t.string :status, null: false 43 | t.datetime :trial_ends_at 44 | t.datetime :ends_at 45 | t.decimal :application_fee_percent, precision: 8, scale: 2 46 | t.public_send Pay::Adapter.json_column_type, :metadata 47 | t.public_send Pay::Adapter.json_column_type, :data 48 | t.timestamps 49 | end 50 | add_index :pay_subscriptions, [:customer_id, :processor_id], unique: true 51 | 52 | create_table :pay_charges do |t| 53 | t.belongs_to :customer, foreign_key: {to_table: :pay_customers}, null: false, index: false 54 | t.belongs_to :subscription, foreign_key: {to_table: :pay_subscriptions}, null: true 55 | t.string :processor_id, null: false 56 | t.integer :amount, null: false 57 | t.string :currency 58 | t.integer :application_fee_amount 59 | t.integer :amount_refunded 60 | t.public_send Pay::Adapter.json_column_type, :metadata 61 | t.public_send Pay::Adapter.json_column_type, :data 62 | t.timestamps 63 | end 64 | add_index :pay_charges, [:customer_id, :processor_id], unique: true 65 | 66 | create_table :pay_webhooks do |t| 67 | t.string :processor 68 | t.string :event_type 69 | t.public_send Pay::Adapter.json_column_type, :event 70 | t.timestamps 71 | end 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /db/migrate/20220511072519_add_billing_location_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddBillingLocationToUser < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :city, :string 4 | add_column :users, :country, :string 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20220518082713_add_customer_info_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddCustomerInfoToUser < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :subscription_status, :string 4 | add_column :users, :subscription_end_date, :datetime 5 | add_column :users, :subscription_start_date, :datetime 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20220608093644_create_projects.rb: -------------------------------------------------------------------------------- 1 | class CreateProjects < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :projects do |t| 4 | t.string :title 5 | t.string :link 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20220629063516_create_ahoy_visits_and_events.rb: -------------------------------------------------------------------------------- 1 | class CreateAhoyVisitsAndEvents < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :ahoy_visits do |t| 4 | t.string :visit_token 5 | t.string :visitor_token 6 | 7 | # the rest are recommended but optional 8 | # simply remove any you don't want 9 | 10 | # user 11 | t.references :user 12 | 13 | # standard 14 | t.string :ip 15 | t.text :user_agent 16 | t.text :referrer 17 | t.string :referring_domain 18 | t.text :landing_page 19 | 20 | # technology 21 | t.string :browser 22 | t.string :os 23 | t.string :device_type 24 | 25 | # location 26 | t.string :country 27 | t.string :region 28 | t.string :city 29 | t.float :latitude 30 | t.float :longitude 31 | 32 | # utm parameters 33 | t.string :utm_source 34 | t.string :utm_medium 35 | t.string :utm_term 36 | t.string :utm_content 37 | t.string :utm_campaign 38 | 39 | # native apps 40 | t.string :app_version 41 | t.string :os_version 42 | t.string :platform 43 | 44 | t.datetime :started_at 45 | end 46 | 47 | add_index :ahoy_visits, :visit_token, unique: true 48 | 49 | create_table :ahoy_events do |t| 50 | t.references :visit 51 | t.references :user 52 | 53 | t.string :name 54 | t.jsonb :properties 55 | t.datetime :time 56 | end 57 | 58 | add_index :ahoy_events, [:name, :time] 59 | add_index :ahoy_events, :properties, using: :gin, opclass: :jsonb_path_ops 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /db/migrate/20220706092028_add_position_to_projects.rb: -------------------------------------------------------------------------------- 1 | class AddPositionToProjects < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :projects, :position, :integer 4 | Project.order(:updated_at).each.with_index(1) do |project, index| 5 | project.update_column :position, index 6 | end 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # run seed file based on environment 2 | puts 'Seeding database' 3 | load(Rails.root.join('db', 'seeds', "#{Rails.env.downcase}.rb")) -------------------------------------------------------------------------------- /db/seeds/development.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the bin/rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: "Star Wars" }, { name: "Lord of the Rings" }]) 7 | # Character.create(name: "Luke", movie: movies.first) 8 | 9 | def seed_users 10 | dean = User.create(email: 'dean@example.com', 11 | password: 'password', 12 | password_confirmation: 'password', 13 | first_name: 'Dean', 14 | last_name: 'DeHart', 15 | role: User.roles[:admin]) 16 | 17 | john = User.create(email: 'john@doe.com', 18 | password: 'password', 19 | password_confirmation: 'password', 20 | first_name: 'John', 21 | last_name: 'Doe') 22 | end 23 | 24 | def seed_addresses 25 | Address.create(street: '123 Main St', 26 | city: 'Anytown', 27 | state: 'CA', 28 | zip: '12345', 29 | country: 'USA', 30 | user: User.first) 31 | Address.create(street: '123 Main St', 32 | city: 'Anytown', 33 | state: 'CA', 34 | zip: '12345', 35 | country: 'USA', 36 | user: User.second) 37 | end 38 | 39 | def seed_categories 40 | Category.create(name: 'Uncategorized', display_in_nav: true) 41 | Category.create(name: 'General', display_in_nav: true) 42 | Category.create(name: 'Finance', display_in_nav: true) 43 | Category.create(name: 'Health', display_in_nav: false) 44 | Category.create(name: 'Education', display_in_nav: false) 45 | end 46 | 47 | def seed_posts_and_comments 48 | posts = [] 49 | dean = User.first 50 | john = User.second 51 | category = Category.first 52 | 10.times do |x| 53 | puts "Creating post #{x}" 54 | post = Post.new(title: "Title #{x}", 55 | body: "Body #{x} Words go here Idk", 56 | user: dean, 57 | category: category) 58 | 59 | 5.times do |y| 60 | puts "Creating comment #{y} for post #{x} with user #{john.email}" 61 | post.comments.build(body: "Comment #{y}", 62 | user: john) 63 | end 64 | 65 | posts.push(post) 66 | end 67 | Post.import(posts, recursive: true) 68 | end 69 | 70 | def seed_ahoy 71 | Ahoy.geocode = false 72 | request = OpenStruct.new( 73 | params: {}, 74 | referer: 'http://example.com', 75 | remote_ip: '0.0.0.0', 76 | user_agent: 'Internet Explorer, lol can you imagine?', 77 | original_url: 'rails' 78 | ) 79 | 80 | visit_properties = Ahoy::VisitProperties.new(request, api: nil) 81 | properties = visit_properties.generate.select { |_, v| v } 82 | 83 | example_visit = Ahoy::Visit.create!(properties.merge( 84 | visit_token: SecureRandom.uuid, 85 | visitor_token: SecureRandom.uuid 86 | )) 87 | 88 | 2.months.ago.to_date.upto(Date.today) do |date| 89 | Post.all.each do |post| 90 | rand(1..5).times do |_x| 91 | Ahoy::Event.create!(name: 'Viewed Post', 92 | visit: example_visit, 93 | properties: { post_id: post.id }, 94 | time: date.to_time + rand(0..23).hours + rand(0..59).minutes) 95 | end 96 | end 97 | end 98 | end 99 | 100 | elapsed = Benchmark.measure do 101 | puts 'Seeding development database...' 102 | seed_users 103 | seed_addresses 104 | seed_categories 105 | seed_posts_and_comments 106 | seed_ahoy 107 | end 108 | 109 | puts "Seeded development DB in #{elapsed.real} seconds" 110 | -------------------------------------------------------------------------------- /db/seeds/production.rb: -------------------------------------------------------------------------------- 1 | puts 'Seeding production database...' 2 | -------------------------------------------------------------------------------- /db/seeds/test.rb: -------------------------------------------------------------------------------- 1 | puts 'Seeding test database...' 2 | -------------------------------------------------------------------------------- /episode_12.txt: -------------------------------------------------------------------------------- 1 | Episode 12 had no code. 2 | -------------------------------------------------------------------------------- /example.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/example.txt -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/lib/assets/.keep -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

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

63 |
64 |

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

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

The change you wanted was rejected.

62 |

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

63 |
64 |

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

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

We're sorry, but something went wrong.

62 |
63 |

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

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /rails_blog: -------------------------------------------------------------------------------- 1 | # Creates a Rails service 2 | [Unit] 3 | Description=Rails service 4 | After=network.target 5 | StartLimitIntervalSec=0 6 | 7 | [Service] 8 | Type=simple 9 | Restart=always 10 | RestartSec=1 11 | User=root 12 | ExecStart=/usr/bin/bash -lc 'bundle exec rails server' 13 | TimeoutSec=30 14 | 15 | 16 | [Install] 17 | WantedBy=multi-user.target 18 | -------------------------------------------------------------------------------- /rails_blog_sysv: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Start the Rails service 4 | start() { 5 | echo "Starting Rails service..." 6 | # working directory 7 | # run as user "dean" 8 | # run as group "dean" 9 | 10 | cd /code/youtube/rails/blog_application 11 | start-stop-daemon --start --quiet --pidfile /var/run/rails.pid --exec `/home/dean/.rvm/gems/ruby-3.0.3/wrappers/rails server -p 3000` 12 | 13 | } 14 | 15 | # Restart the Rails service 16 | restart() { 17 | echo "Restarting Rails service..." 18 | cd /code/youtube/rails/blog_application 19 | start-stop-daemon --stop --quiet --pidfile /var/run/rails.pid --exec /home/dean/.rvm/gems/ruby-3.0.3/wrappers/rails server -p 3000 20 | start-stop-daemon --start --quiet --pidfile /var/run/rails.pid --exec /home/dean/.rvm/gems/ruby-3.0.3/wrappers/rails server -p 3000 21 | [ $? -eq 0 ] && success || failure 22 | } 23 | 24 | # Stop the Rails service 25 | stop() { 26 | echo "Stopping Rails service..." 27 | cd /code/youtube/rails/blog_application 28 | start-stop-daemon --stop --quiet --pidfile /var/run/rails.pid --exec /home/dean/.rvm/gems/ruby-3.0.3/wrappers/rails server -p 3000 29 | 30 | } 31 | 32 | ### Main ### 33 | case "$1" in 34 | start) 35 | start 36 | ;; 37 | stop) 38 | stop 39 | ;; 40 | restart) 41 | restart 42 | ;; 43 | *) 44 | echo "Usage: rails_blog_sysv {start|stop|restart}" 45 | exit 1 46 | ;; 47 | esac 48 | 49 | 50 | -------------------------------------------------------------------------------- /scripts/stripe.sh: -------------------------------------------------------------------------------- 1 | stripe listen --forward-to localhost:3000/pay/webhooks/stripe -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/storage/.keep -------------------------------------------------------------------------------- /test.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/test.txt -------------------------------------------------------------------------------- /test/application_system_test_case.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase 4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400] 5 | end 6 | -------------------------------------------------------------------------------- /test/channels/application_cable/connection_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ApplicationCable::ConnectionTest < ActionCable::Connection::TestCase 4 | # test "connects with cookies" do 5 | # cookies.signed[:user_id] = 42 6 | # 7 | # connect 8 | # 9 | # assert_equal connection.user_id, "42" 10 | # end 11 | end 12 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/test/controllers/.keep -------------------------------------------------------------------------------- /test/controllers/admin_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class AdminControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get admin_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get posts" do 10 | get admin_posts_url 11 | assert_response :success 12 | end 13 | 14 | test "should get comments" do 15 | get admin_comments_url 16 | assert_response :success 17 | end 18 | 19 | test "should get users" do 20 | get admin_users_url 21 | assert_response :success 22 | end 23 | test "should get show_post" do 24 | get admin_show_post_url 25 | assert_response :success 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /test/controllers/categories_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CategoriesControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @category = categories(:one) 6 | end 7 | 8 | test "should get index" do 9 | get categories_url 10 | assert_response :success 11 | end 12 | 13 | test "should get new" do 14 | get new_category_url 15 | assert_response :success 16 | end 17 | 18 | test "should create category" do 19 | assert_difference("Category.count") do 20 | post categories_url, params: { category: { display_in_nav: @category.display_in_nav, name: @category.name } } 21 | end 22 | 23 | assert_redirected_to category_url(Category.last) 24 | end 25 | 26 | test "should show category" do 27 | get category_url(@category) 28 | assert_response :success 29 | end 30 | 31 | test "should get edit" do 32 | get edit_category_url(@category) 33 | assert_response :success 34 | end 35 | 36 | test "should update category" do 37 | patch category_url(@category), params: { category: { display_in_nav: @category.display_in_nav, name: @category.name } } 38 | assert_redirected_to category_url(@category) 39 | end 40 | 41 | test "should destroy category" do 42 | assert_difference("Category.count", -1) do 43 | delete category_url(@category) 44 | end 45 | 46 | assert_redirected_to categories_url 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/controllers/checkouts_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CheckoutsControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/comments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CommentsControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/drag_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class DragControllerTest < ActionDispatch::IntegrationTest 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/members_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class MembersControllerTest < ActionDispatch::IntegrationTest 4 | test "should get dashboard" do 5 | get members_dashboard_url 6 | assert_response :success 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/controllers/pages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class PagesControllerTest < ActionDispatch::IntegrationTest 4 | test "should get home" do 5 | get pages_home_url 6 | assert_response :success 7 | end 8 | 9 | test "should get about" do 10 | get pages_about_url 11 | assert_response :success 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/controllers/posts_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class PostsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @post = posts(:one) 6 | end 7 | 8 | test "should get index" do 9 | get posts_url 10 | assert_response :success 11 | end 12 | 13 | test "should get new" do 14 | get new_post_url 15 | assert_response :success 16 | end 17 | 18 | test "should create post" do 19 | assert_difference("Post.count") do 20 | post posts_url, params: { post: { body: @post.body, title: @post.title } } 21 | end 22 | 23 | assert_redirected_to post_url(Post.last) 24 | end 25 | 26 | test "should show post" do 27 | get post_url(@post) 28 | assert_response :success 29 | end 30 | 31 | test "should get edit" do 32 | get edit_post_url(@post) 33 | assert_response :success 34 | end 35 | 36 | test "should update post" do 37 | patch post_url(@post), params: { post: { body: @post.body, title: @post.title } } 38 | assert_redirected_to post_url(@post) 39 | end 40 | 41 | test "should destroy post" do 42 | assert_difference("Post.count", -1) do 43 | delete post_url(@post) 44 | end 45 | 46 | assert_redirected_to posts_url 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/controllers/projects_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProjectsControllerTest < ActionDispatch::IntegrationTest 4 | setup do 5 | @project = projects(:one) 6 | end 7 | 8 | test "should get index" do 9 | get projects_url 10 | assert_response :success 11 | end 12 | 13 | test "should get new" do 14 | get new_project_url 15 | assert_response :success 16 | end 17 | 18 | test "should create project" do 19 | assert_difference("Project.count") do 20 | post projects_url, params: { project: { link: @project.link, title: @project.title } } 21 | end 22 | 23 | assert_redirected_to project_url(Project.last) 24 | end 25 | 26 | test "should show project" do 27 | get project_url(@project) 28 | assert_response :success 29 | end 30 | 31 | test "should get edit" do 32 | get edit_project_url(@project) 33 | assert_response :success 34 | end 35 | 36 | test "should update project" do 37 | patch project_url(@project), params: { project: { link: @project.link, title: @project.title } } 38 | assert_redirected_to project_url(@project) 39 | end 40 | 41 | test "should destroy project" do 42 | assert_difference("Project.count", -1) do 43 | delete project_url(@project) 44 | end 45 | 46 | assert_redirected_to projects_url 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /test/controllers/search_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class SearchControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get search_index_url 6 | assert_response :success 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UsersControllerTest < ActionDispatch::IntegrationTest 4 | test "should get profile" do 5 | get users_profile_url 6 | assert_response :success 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/fixtures/action_text/rich_texts.yml: -------------------------------------------------------------------------------- 1 | # one: 2 | # record: name_of_fixture (ClassOfFixture) 3 | # name: content 4 | # body:

In a million stars!

5 | -------------------------------------------------------------------------------- /test/fixtures/addresses.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | street: MyString 5 | city: MyString 6 | state: MyString 7 | zip: 1 8 | country: MyString 9 | user: one 10 | 11 | two: 12 | street: MyString 13 | city: MyString 14 | state: MyString 15 | zip: 1 16 | country: MyString 17 | user: two 18 | -------------------------------------------------------------------------------- /test/fixtures/categories.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | name: MyString 5 | display_in_nav: false 6 | 7 | two: 8 | name: MyString 9 | display_in_nav: false 10 | -------------------------------------------------------------------------------- /test/fixtures/comments.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | post: one 5 | user: one 6 | 7 | two: 8 | post: two 9 | user: two 10 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/test/fixtures/files/.keep -------------------------------------------------------------------------------- /test/fixtures/notifications.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | recipient: one 5 | recipient_type: Recipient 6 | type: 7 | params: 8 | read_at: 2022-03-02 02:56:32 9 | 10 | two: 11 | recipient: two 12 | recipient_type: Recipient 13 | type: 14 | params: 15 | read_at: 2022-03-02 02:56:32 16 | -------------------------------------------------------------------------------- /test/fixtures/posts.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | title: MyString 5 | body: MyText 6 | 7 | two: 8 | title: MyString 9 | body: MyText 10 | -------------------------------------------------------------------------------- /test/fixtures/projects.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | title: MyString 5 | link: MyString 6 | 7 | two: 8 | title: MyString 9 | link: MyString 10 | -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the "{}" from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/test/mailers/.keep -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/test/models/.keep -------------------------------------------------------------------------------- /test/models/address_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class AddressTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/category_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CategoryTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/comment_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CommentTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/notification_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class NotificationTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/post_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class PostTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/project_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ProjectTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/test/system/.keep -------------------------------------------------------------------------------- /test/system/categories_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class CategoriesTest < ApplicationSystemTestCase 4 | setup do 5 | @category = categories(:one) 6 | end 7 | 8 | test "visiting the index" do 9 | visit categories_url 10 | assert_selector "h1", text: "Categories" 11 | end 12 | 13 | test "should create category" do 14 | visit categories_url 15 | click_on "New category" 16 | 17 | check "Display in nav" if @category.display_in_nav 18 | fill_in "Name", with: @category.name 19 | click_on "Create Category" 20 | 21 | assert_text "Category was successfully created" 22 | click_on "Back" 23 | end 24 | 25 | test "should update Category" do 26 | visit category_url(@category) 27 | click_on "Edit this category", match: :first 28 | 29 | check "Display in nav" if @category.display_in_nav 30 | fill_in "Name", with: @category.name 31 | click_on "Update Category" 32 | 33 | assert_text "Category was successfully updated" 34 | click_on "Back" 35 | end 36 | 37 | test "should destroy Category" do 38 | visit category_url(@category) 39 | click_on "Destroy this category", match: :first 40 | 41 | assert_text "Category was successfully destroyed" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/system/posts_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class PostsTest < ApplicationSystemTestCase 4 | setup do 5 | @post = posts(:one) 6 | end 7 | 8 | test "visiting the index" do 9 | visit posts_url 10 | assert_selector "h1", text: "Posts" 11 | end 12 | 13 | test "should create post" do 14 | visit posts_url 15 | click_on "New post" 16 | 17 | fill_in "Body", with: @post.body 18 | fill_in "Title", with: @post.title 19 | click_on "Create Post" 20 | 21 | assert_text "Post was successfully created" 22 | click_on "Back" 23 | end 24 | 25 | test "should update Post" do 26 | visit post_url(@post) 27 | click_on "Edit this post", match: :first 28 | 29 | fill_in "Body", with: @post.body 30 | fill_in "Title", with: @post.title 31 | click_on "Update Post" 32 | 33 | assert_text "Post was successfully updated" 34 | click_on "Back" 35 | end 36 | 37 | test "should destroy Post" do 38 | visit post_url(@post) 39 | click_on "Destroy this post", match: :first 40 | 41 | assert_text "Post was successfully destroyed" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/system/projects_test.rb: -------------------------------------------------------------------------------- 1 | require "application_system_test_case" 2 | 3 | class ProjectsTest < ApplicationSystemTestCase 4 | setup do 5 | @project = projects(:one) 6 | end 7 | 8 | test "visiting the index" do 9 | visit projects_url 10 | assert_selector "h1", text: "Projects" 11 | end 12 | 13 | test "should create project" do 14 | visit projects_url 15 | click_on "New project" 16 | 17 | fill_in "Link", with: @project.link 18 | fill_in "Title", with: @project.title 19 | click_on "Create Project" 20 | 21 | assert_text "Project was successfully created" 22 | click_on "Back" 23 | end 24 | 25 | test "should update Project" do 26 | visit project_url(@project) 27 | click_on "Edit this project", match: :first 28 | 29 | fill_in "Link", with: @project.link 30 | fill_in "Title", with: @project.title 31 | click_on "Update Project" 32 | 33 | assert_text "Project was successfully updated" 34 | click_on "Back" 35 | end 36 | 37 | test "should destroy Project" do 38 | visit project_url(@project) 39 | click_on "Destroy this project", match: :first 40 | 41 | assert_text "Project was successfully destroyed" 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] ||= "test" 2 | require_relative "../config/environment" 3 | require "rails/test_help" 4 | 5 | class ActiveSupport::TestCase 6 | # Run tests in parallel with specified workers 7 | parallelize(workers: :number_of_processors) 8 | 9 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 10 | fixtures :all 11 | 12 | # Add more helper methods to be used by all tests here... 13 | end 14 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/tmp/.keep -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/tmp/pids/.keep -------------------------------------------------------------------------------- /tmp/storage/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/tmp/storage/.keep -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/vendor/.keep -------------------------------------------------------------------------------- /vendor/javascript/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Deanout/blog_application/bdee53825f3afcb2e5dba73b5dd9f4af1eb925b3/vendor/javascript/.keep --------------------------------------------------------------------------------