├── log └── .keep ├── storage └── .keep ├── tmp ├── .keep ├── pids │ └── .keep └── storage │ └── .keep ├── vendor └── .keep ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── public ├── favicon.ico ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── test ├── helpers │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── like_test.rb │ ├── tweet_test.rb │ ├── user_test.rb │ └── comment_test.rb ├── system │ └── .keep ├── controllers │ ├── .keep │ └── tweets_controller_test.rb ├── integration │ └── .keep ├── fixtures │ ├── files │ │ └── .keep │ ├── action_text │ │ └── rich_texts.yml │ ├── tweets.yml │ ├── comments.yml │ ├── likes.yml │ └── users.yml ├── application_system_test_case.rb ├── channels │ └── application_cable │ │ └── connection_test.rb └── test_helper.rb ├── .ruby-version ├── app ├── assets │ ├── builds │ │ └── .keep │ ├── images │ │ └── .keep │ ├── config │ │ └── manifest.js │ └── stylesheets │ │ ├── application.tailwind.css │ │ └── actiontext.css ├── models │ ├── concerns │ │ ├── .keep │ │ └── likeable.rb │ ├── application_record.rb │ ├── like.rb │ ├── comment.rb │ ├── tweet.rb │ └── user.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── home_controller.rb │ ├── profiles_controller.rb │ ├── likes_controller.rb │ ├── application_controller.rb │ ├── comments_controller.rb │ └── tweets_controller.rb ├── views │ ├── layouts │ │ ├── mailer.text.erb │ │ ├── action_text │ │ │ └── contents │ │ │ │ └── _content.html.erb │ │ ├── mailer.html.erb │ │ ├── devise.html.erb │ │ └── application.html.erb │ ├── tweets │ │ ├── destroy.turbo_stream.erb │ │ ├── _empty.html.erb │ │ ├── _comments_count.html.erb │ │ ├── retweet.turbo_stream.erb │ │ ├── create.turbo_stream.erb │ │ ├── index.html.erb │ │ ├── _tweet.html.erb │ │ ├── _form.html.erb │ │ ├── _retweet.html.erb │ │ ├── show.html.erb │ │ └── _controls.html.erb │ ├── .DS_Store │ ├── devise │ │ ├── mailer │ │ │ ├── password_change.html.erb │ │ │ ├── confirmation_instructions.html.erb │ │ │ ├── unlock_instructions.html.erb │ │ │ ├── email_changed.html.erb │ │ │ └── reset_password_instructions.html.erb │ │ ├── shared │ │ │ ├── _error_messages.html.erb │ │ │ └── _links.html.erb │ │ ├── unlocks │ │ │ └── new.html.erb │ │ ├── passwords │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ │ ├── confirmations │ │ │ └── new.html.erb │ │ ├── sessions │ │ │ └── new.html.erb │ │ ├── _auth_layout.html.erb │ │ └── registrations │ │ │ ├── new.html.erb │ │ │ └── edit.html.erb │ ├── comments │ │ ├── destroy.turbo_stream.erb │ │ ├── create.turbo_stream.erb │ │ ├── _form.html.erb │ │ └── _comment.html.erb │ ├── shared │ │ ├── _flash_notice.html.erb │ │ ├── _head.html.erb │ │ └── _navbar.html.erb │ ├── likes │ │ ├── create.turbo_stream.erb │ │ └── _likes.html.erb │ ├── active_storage │ │ └── blobs │ │ │ └── _blob.html.erb │ ├── profiles │ │ └── show.html.erb │ └── home │ │ └── index.html.erb ├── helpers │ ├── tweets_helper.rb │ └── application_helper.rb ├── .DS_Store ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── mailers │ └── application_mailer.rb ├── javascript │ ├── application.js │ └── controllers │ │ ├── reset_form_controller.js │ │ ├── hello_controller.js │ │ ├── nav_controller.js │ │ ├── application.js │ │ └── index.js └── jobs │ └── application_job.rb ├── Procfile.dev ├── bin ├── rake ├── rails ├── dev ├── setup └── bundle ├── config ├── environment.rb ├── boot.rb ├── cable.yml ├── initializers │ ├── filter_parameter_logging.rb │ ├── permissions_policy.rb │ ├── assets.rb │ ├── inflections.rb │ ├── content_security_policy.rb │ ├── friendly_id.rb │ └── devise.rb ├── credentials.yml.enc ├── routes.rb ├── database.yml ├── application.rb ├── locales │ ├── en.yml │ └── devise.en.yml ├── storage.yml ├── puma.rb └── environments │ ├── test.rb │ ├── development.rb │ └── production.rb ├── config.ru ├── db ├── migrate │ ├── 20220619190941_add_tweet_id_to_tweets.rb │ ├── 20220619175818_add_username_to_users.rb │ ├── 20220619171009_create_tweets.rb │ ├── 20220619203053_create_likes.rb │ ├── 20220619195802_create_comments.rb │ ├── 20220619170256_create_friendly_id_slugs.rb │ ├── 20220619170238_create_action_text_tables.action_text.rb │ ├── 20220619170254_devise_create_users.rb │ ├── 20220619170234_create_active_storage_tables.active_storage.rb │ └── 20220619170257_create_pay_tables.pay.rb ├── seeds.rb └── schema.rb ├── Rakefile ├── .gitattributes ├── tailwind.config.js ├── package.json ├── .gitignore ├── README.md ├── Gemfile ├── Gemfile.lock └── yarn.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/system/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/pids/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.0.3 2 | -------------------------------------------------------------------------------- /app/assets/builds/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/files/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /app/helpers/tweets_helper.rb: -------------------------------------------------------------------------------- 1 | module TweetsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justalever/tweeter/HEAD/app/.DS_Store -------------------------------------------------------------------------------- /app/views/tweets/destroy.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.remove(@tweet) %> 2 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_tree ../builds 3 | -------------------------------------------------------------------------------- /app/views/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/justalever/tweeter/HEAD/app/views/.DS_Store -------------------------------------------------------------------------------- /app/views/tweets/_empty.html.erb: -------------------------------------------------------------------------------- 1 |
Nothing to see here.
2 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bin/rails server -p 3000 2 | js: yarn build --watch 3 | css: yarn build:css --watch 4 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /app/controllers/home_controller.rb: -------------------------------------------------------------------------------- 1 | class HomeController < ApplicationController 2 | def index 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | primary_abstract_class 3 | end 4 | -------------------------------------------------------------------------------- /app/views/layouts/action_text/contents/_content.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= yield -%> 3 |
4 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "../config/boot" 3 | require "rake" 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /app/models/like.rb: -------------------------------------------------------------------------------- 1 | class Like < ApplicationRecord 2 | belongs_to :likeable, polymorphic: true 3 | belongs_to :user 4 | end 5 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ApplicationRecord 2 | include Likeable 3 | belongs_to :user 4 | belongs_to :tweet 5 | end 6 | -------------------------------------------------------------------------------- /app/views/tweets/_comments_count.html.erb: -------------------------------------------------------------------------------- 1 | <%= tweet.comments.count ||= 0 %> 2 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: "from@example.com" 3 | layout "mailer" 4 | end 5 | -------------------------------------------------------------------------------- /app/views/tweets/retweet.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.prepend "tweets" do %> 2 | <%= render "tweets/retweet", tweet: @retweet %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /app/controllers/profiles_controller.rb: -------------------------------------------------------------------------------- 1 | class ProfilesController < ApplicationController 2 | def show 3 | @profile = User.find(params[:id]) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative "application" 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /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/models/like_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class LikeTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/tweet_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class TweetTest < 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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20220619190941_add_tweet_id_to_tweets.rb: -------------------------------------------------------------------------------- 1 | class AddTweetIdToTweets < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :tweets, :tweet_id, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/javascript/application.js: -------------------------------------------------------------------------------- 1 | // Entry point for the build script in your package.json 2 | import "@hotwired/turbo-rails" 3 | import "./controllers" 4 | import "trix" 5 | import "@rails/actiontext" 6 | -------------------------------------------------------------------------------- /bin/dev: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if ! foreman version &> /dev/null 4 | then 5 | echo "Installing foreman..." 6 | gem install foreman 7 | fi 8 | 9 | foreman start -f Procfile.dev "$@" 10 | -------------------------------------------------------------------------------- /app/javascript/controllers/reset_form_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | export default class extends Controller { 4 | reset() { 5 | this.element.reset() 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /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/fixtures/tweets.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | body: MyText 5 | user: one 6 | 7 | two: 8 | body: MyText 9 | user: two 10 | -------------------------------------------------------------------------------- /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/views/comments/destroy.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.remove(@comment) %> 2 | 3 | <%= turbo_stream.replace "#{dom_id(@tweet)}_comments_count" do %> 4 | <%= render "tweets/comments_count", tweet: @tweet %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /app/views/shared/_flash_notice.html.erb: -------------------------------------------------------------------------------- 1 | <% unless flash[:tweet_errors] %> 2 | <% flash.each do |type, message| %> 3 |
4 | <%= message %> 5 |
6 | <% end %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/tweets/create.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.prepend "tweets" do %> 2 | <%= render "tweet", tweet: @tweet %> 3 | <% end %> 4 | 5 | <%= turbo_stream.remove Tweet.new do %> 6 | <%= render "tweets/empty" %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /db/migrate/20220619175818_add_username_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddUsernameToUsers < ActiveRecord::Migration[7.0] 2 | def change 3 | add_column :users, :username, :string 4 | add_index :users, :username, unique: true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/models/concerns/likeable.rb: -------------------------------------------------------------------------------- 1 | module Likeable 2 | extend ActiveSupport::Concern 3 | 4 | included do 5 | has_many :likes, as: :likeable 6 | end 7 | 8 | def liked_by?(user) 9 | likes.where(user: user).any? 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require_relative "config/application" 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /test/fixtures/comments.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | user: one 5 | tweet: one 6 | body: MyText 7 | 8 | two: 9 | user: two 10 | tweet: two 11 | body: MyText 12 | -------------------------------------------------------------------------------- /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/likes/create.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.update_all(".#{dom_id(@likeable)}_likes") do %> 2 | <%= render "likes/likes", likeable: @likeable %> 3 | <% end %> 4 | 5 | <%= turbo_stream.update_all(".#{dom_id(@likeable)}_likes-size", @likeable.likes.size) %> 6 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /app/javascript/controllers/nav_controller.js: -------------------------------------------------------------------------------- 1 | import { Controller } from "@hotwired/stimulus" 2 | 3 | export default class extends Controller { 4 | static targets = ['menu'] 5 | 6 | toggleMenu(event) { 7 | this.menuTarget.classList.toggle('hidden') 8 | } 9 | 10 | } 11 | -------------------------------------------------------------------------------- /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: tweeter_production 12 | -------------------------------------------------------------------------------- /test/fixtures/likes.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | one: 4 | likeable: one 5 | likeable_type: Likeable 6 | user: one 7 | 8 | two: 9 | likeable: two 10 | likeable_type: Likeable 11 | user: two 12 | -------------------------------------------------------------------------------- /db/migrate/20220619171009_create_tweets.rb: -------------------------------------------------------------------------------- 1 | class CreateTweets < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :tweets do |t| 4 | t.text :body 5 | t.references :user, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /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/views/comments/create.turbo_stream.erb: -------------------------------------------------------------------------------- 1 | <%= turbo_stream.prepend "#{dom_id(@tweet)}_comments" do %> 2 | <%= render "comment", comment: @comment %> 3 | <% end %> 4 | 5 | <%= turbo_stream.replace "#{dom_id(@tweet)}_comments_count" do %> 6 | <%= render "tweets/comments_count", tweet: @tweet %> 7 | <% end %> 8 | -------------------------------------------------------------------------------- /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/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20220619203053_create_likes.rb: -------------------------------------------------------------------------------- 1 | class CreateLikes < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :likes do |t| 4 | t.references :likeable, polymorphic: true, null: false, index: true 5 | t.references :user, null: false, foreign_key: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20220619195802_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration[7.0] 2 | def change 3 | create_table :comments do |t| 4 | t.references :user, null: false, foreign_key: true 5 | t.references :tweet, null: false, foreign_key: true 6 | t.text :body 7 | 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the 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 | -------------------------------------------------------------------------------- /app/javascript/controllers/index.js: -------------------------------------------------------------------------------- 1 | // This file is auto-generated by ./bin/rails stimulus:manifest:update 2 | // Run that command whenever you add a new controller or create them with 3 | // ./bin/rails generate stimulus controllerName 4 | 5 | import { application } from "./application" 6 | 7 | import ResetFormController from "./reset_form_controller" 8 | application.register("reset-form", ResetFormController) 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/tweets/index.html.erb: -------------------------------------------------------------------------------- 1 |
2 | <%= render "form" %> 3 |
4 | 5 | <%= turbo_frame_tag "tweets" do %> 6 | <% @tweets.each do |tweet| %> 7 | <%= render partial: "tweets/#{tweet.tweet_type}", locals: { tweet: tweet } %> 8 | <% end %> 9 | <% end %> 10 | 11 | <%= turbo_frame_tag Tweet.new do %> 12 | <% if Tweet.all.none? %> 13 | <%= render "empty" %> 14 | <% end %> 15 | <% end %> 16 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | './app/views/**/*.html.erb', 4 | './app/helpers/**/*.rb', 5 | './app/javascript/**/*.{js, jsx, vue}' 6 | ], 7 | theme: { 8 | extend: {}, 9 | }, 10 | plugins: [ 11 | require('@tailwindcss/forms'), 12 | require('@tailwindcss/typography'), 13 | require('@tailwindcss/aspect-ratio'), 14 | require('@tailwindcss/line-clamp'), 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /app/models/tweet.rb: -------------------------------------------------------------------------------- 1 | class Tweet < ApplicationRecord 2 | include Likeable 3 | belongs_to :user 4 | belongs_to :tweet, optional: true 5 | has_many :comments 6 | 7 | validates :body, length: { maximum: 240 }, allow_blank: false, unless: :tweet_id 8 | 9 | def tweet_type 10 | if tweet_id? && body? 11 | "quote-tweet" 12 | elsif tweet_id? 13 | "retweet" 14 | else 15 | "tweet" 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/views/layouts/devise.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= render "shared/head" %> 4 | 5 | 6 | 7 |
8 | <%= render "shared/flash_notice" %> 9 |
10 | 11 |
12 | <%= content_for?(:content) ? yield(:content) : yield %> 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure parameters to be filtered from the log file. Use this to limit dissemination of 4 | # sensitive information. See the ActiveSupport::ParameterFilter documentation for supported 5 | # notations and behaviors. 6 | Rails.application.config.filter_parameters += [ 7 | :passw, :secret, :token, :_key, :crypt, :salt, :certificate, :otp, :ssn 8 | ] 9 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | pay_customer 3 | 4 | has_person_name 5 | 6 | # Include default devise modules. Others available are: 7 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable 8 | devise :database_authenticatable, :registerable, 9 | :recoverable, :rememberable, :validatable 10 | has_many :tweets 11 | 12 | validates_uniqueness_of :username 13 | 14 | has_one_attached :profile_image 15 | end 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | pWqBqwlm7XKEBot5X6N5fTkTFpKsxM+01ewJT5DrQMDBN+gfXfqGhJewTf4b7aZRgB91DaYlRdEXaMnvuEuwF2EYuDlbFghmr94XkDJry2zvpufEOmIFAopVKmiQ923VKI1D+1vyMlNT2AQs0692I3SAjYiUuWFS62qFGXunLIdtLcOWDTVhkuZ1i2GYGaZTBQ5mUha4/789HtqjF5lPtzY6s8TB9j7gXXJemyHiY3lT3sDumHEfIP2tlKpnVDimAukFuRX4UEObfo6Y4tXz4FvXpCL0n+FZJzjPR11c0raUGuQbf5I2oDB2/50yG8iAf7aQGx4CnHAgEiSm+Rt70E8o2+VLGKXfQ2VeXRJX+k3vu9ANtrs9+V010km1iBePXWD84PAKKJi50365n5ccSsOx+yFMZSZXpxRJ--M1pAQv9e4xFZykuZ--JQr5mZJcaRkOcqxpmZgtww== -------------------------------------------------------------------------------- /test/controllers/tweets_controller_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class TweetsControllerTest < ActionDispatch::IntegrationTest 4 | test "should get index" do 5 | get tweets_index_url 6 | assert_response :success 7 | end 8 | 9 | test "should get create" do 10 | get tweets_create_url 11 | assert_response :success 12 | end 13 | 14 | test "should get destroy" do 15 | get tweets_destroy_url 16 | assert_response :success 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | require 'sidekiq/web' 2 | 3 | Rails.application.routes.draw do 4 | authenticate :user, lambda { |u| u.admin? } do 5 | mount Sidekiq::Web => '/sidekiq' 6 | end 7 | 8 | resources :tweets, except: [:edit, :update] do 9 | resources :comments, only: [:create, :destroy] 10 | member do 11 | post :retweet 12 | end 13 | end 14 | 15 | resources :profiles 16 | resources :likes, only: :create 17 | 18 | devise_for :users 19 | root to: "tweets#index" 20 | end 21 | -------------------------------------------------------------------------------- /app/views/shared/_head.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= content_for?(:title) ? yield(:title) : "Kickoff Tailwind" %> 4 | 5 | 6 | <%= csrf_meta_tags %> 7 | <%= csp_meta_tag %> 8 | 9 | 10 | <%= stylesheet_link_tag 'application', 'data-turbolinks-track': 'reload' %> 11 | <%= javascript_include_tag "application", defer: true %> 12 | <%#= javascript_include_tag 'https://js.stripe.com/v3/', 'data-turbolinks-track': 'reload' %> 13 | 14 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/controllers/likes_controller.rb: -------------------------------------------------------------------------------- 1 | class LikesController < ApplicationController 2 | before_action :set_likeable 3 | 4 | def create 5 | if @likeable.likes.count >= 1 && @likeable.liked_by?(current_user) 6 | @like = Like.find_by(likeable_id: @likeable.id, user: current_user) 7 | @like.destroy 8 | else 9 | @like = @likeable.likes.new 10 | @like.user = current_user 11 | @like.save 12 | end 13 | end 14 | 15 | private 16 | 17 | def set_likeable 18 | @likeable = params[:likeable_type].constantize.find(params[:likeable_id]) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/views/tweets/_tweet.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= profile_image(tweet.user) %> 5 |
6 |
7 |

<%= link_to tweet.user.name, profile_path(tweet.user), class: "hover:underline", data: {turbo: false} %>

8 | <%= link_to tweet, data: {turbo: false} do %> 9 |
<%= tweet.body %>
10 | <% end %> 11 | 12 | <%= render "tweets/controls", tweet: tweet %> 13 |
14 |
15 |
16 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | layout :layout_by_resource 3 | 4 | protect_from_forgery with: :exception 5 | 6 | before_action :configure_permitted_parameters, if: :devise_controller? 7 | 8 | private 9 | 10 | def layout_by_resource 11 | if devise_controller? 12 | "devise" 13 | else 14 | "application" 15 | end 16 | end 17 | 18 | protected 19 | 20 | def configure_permitted_parameters 21 | devise_parameter_sanitizer.permit(:sign_up, keys: [:name, :username]) 22 | devise_parameter_sanitizer.permit(:account_update, keys: [:name, :username, :profile_image]) 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /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/devise/unlocks/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render layout: "auth_layout", locals: { title: "Resend unlock instructions" } do %> 2 | <%= form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }, data: { turbo: false }) do |f| %> 3 | <%= render "devise/shared/error_messages", resource: resource %> 4 | 5 |
6 | <%= f.label :email, class: label_class %> 7 | <%= f.email_field :email, autofocus: true, autocomplete: "email", class: input_class %> 8 |
9 | 10 |
11 | <%= f.submit "Resend instructions", class: button_class(theme: "primary", variant: "expanded") %> 12 |
13 | <% end %> 14 | <% end %> 15 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite. Versions 3.8.0 and up are supported. 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem "sqlite3" 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /config/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 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render layout: "auth_layout", locals: { title: "Forgot password", subtitle: "If your email is in our database we'll send you password reset instructions." } do %> 2 | 3 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }, data: { turbo: false }) do |f| %> 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 | 6 |
7 | <%= f.label :email, class: label_class %> 8 | <%= f.email_field :email, autofocus: true, autocomplete: "email", class: input_class %> 9 |
10 | 11 |
12 | <%= f.submit "Send password reset instructions", class: button_class(variant: "expanded", theme: "primary") %> 13 |
14 | <% end %> 15 | <% end %> 16 | -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render layout: "auth_layout", locals: { title: "Resend confirmation instructions" } do %> 2 | <%= form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }, data: { turbo: false }) do |f| %> 3 | <%= render "devise/shared/error_messages", resource: resource %> 4 | 5 |
6 | <%= f.label :email, class: label_class %> 7 | <%= f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email), class: input_class %> 8 |
9 | 10 |
11 | <%= f.submit "Resend instructions", class: button_class(theme: "primary", variant: "expanded") %> 12 |
13 | <% end %> 14 | <% end %> 15 | -------------------------------------------------------------------------------- /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 Tweeter 10 | class Application < Rails::Application 11 | config.active_job.queue_adapter = :sidekiq 12 | # Initialize configuration defaults for originally generated Rails version. 13 | config.load_defaults 7.0 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 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "private": "true", 4 | "dependencies": { 5 | "@hotwired/stimulus": "^3.0.1", 6 | "@hotwired/turbo-rails": "^7.1.3", 7 | "@rails/actiontext": "^6.0.5", 8 | "autoprefixer": "^10.4.7", 9 | "esbuild": "^0.14.46", 10 | "postcss": "^8.4.14", 11 | "tailwindcss": "^3.1.3", 12 | "trix": "^2.0.0-beta.0" 13 | }, 14 | "scripts": { 15 | "build": "esbuild app/javascript/*.* --bundle --sourcemap --outdir=app/assets/builds --public-path=assets", 16 | "build:css": "tailwindcss -i ./app/assets/stylesheets/application.tailwind.css -o ./app/assets/builds/application.css --minify" 17 | }, 18 | "devDependencies": { 19 | "@tailwindcss/aspect-ratio": "^0.4.0", 20 | "@tailwindcss/forms": "^0.5.2", 21 | "@tailwindcss/line-clamp": "^0.4.0", 22 | "@tailwindcss/typography": "^0.5.2" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/views/profiles/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= profile_image(@profile, size: "large") %> 5 |
6 |
7 |
8 |

<%= @profile.name %>

9 | <%= "@" + @profile.username %> 10 |
11 |
12 |
13 | <%= link_to "Edit profile", edit_user_registration_path, class: "inline-flex justify-center items-center px-5 py-2 rounded-full border ring-4 focus:ring-sky-50 ring-transparent hover:ring-sky-50 hover:border-gray-300" if current_user %> 14 |
15 |
16 | 17 |
18 | <% @profile.tweets.each do |tweet| %> 19 | <%= render partial: "tweets/#{tweet.tweet_type}", locals: { tweet: tweet } %> 20 | <% end %> 21 |
22 |
23 | -------------------------------------------------------------------------------- /db/migrate/20220619170256_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 | -------------------------------------------------------------------------------- /app/views/comments/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: [tweet, @comment], data: {controller: "reset-form", action: "turbo:submit-end->reset-form#reset" } do |form| %> 2 |

Replying to <%= link_to tweet.user.name, profile_path(tweet.user), class: "text-sky-500 hover:text-sky-600" %>

3 |
4 | <%= profile_image(current_user) %> 5 |
6 | <%= form.label :body, class: "sr-only" %> 7 | <%= form.text_area :body, class: "border-none w- full resize-none rounded-md focus:shadow-none focus:border-none ring-0 focus:ring-0 focus:outline-none text-lg py-3 px-0 h-full min-h-[100px]", placeholder: "Tweet your reply" %> 8 |
9 | <%= form.submit "Reply", class: "px-6 py-2 bg-sky-400 text-white font-semibold rounded-full text-center cursor-pointer inline-block hover:bg-sky-500 transition ease-in-out duration-300"%> 10 |
11 | <% end %> 12 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | before_action :authenticate_user! 3 | before_action :set_tweet 4 | 5 | def create 6 | @comment = @tweet.comments.new(comment_params.merge(user: current_user)) 7 | respond_to do |format| 8 | if @comment.save 9 | format.turbo_stream 10 | else 11 | format.html { redirect_to tweet_path(@tweet), alert: "Reply could not be created" } 12 | end 13 | end 14 | end 15 | 16 | def destroy 17 | @comment = @tweet.comments.find(params[:id]) 18 | @comment.destroy 19 | 20 | respond_to do |format| 21 | format.turbo_stream 22 | format.html { redirect_to tweet_path(@tweet), notice: "Comment was deleted" } 23 | end 24 | end 25 | 26 | private 27 | 28 | def comment_params 29 | params.require(:comment).permit(:body) 30 | end 31 | 32 | def set_tweet 33 | @tweet = Tweet.find(params[:tweet_id]) 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/views/tweets/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_with model: @tweet, data: { controller: "reset-form", action: "turbo:submit-end->reset-form#reset" } do |form| %> 2 | <% if flash[:tweet_errors] %> 3 |
4 | 9 |
10 | <% end %> 11 | 12 | <%= form.text_area :body, class: "border-b border-t-0 border-x-0 mb-6 px-6 pt-6 block w-full border-gray-200 focus:shadow-none focus:outline-none focus:ring-transparent focus:border-gray-300 resize-none min-h-[180px] text-lg", placeholder: "What's on your mind?" %> 13 | 14 |
15 | <%= form.submit "Tweet", class: "px-6 py-2 bg-sky-400 text-white font-semibold rounded-full text-center cursor-pointer inline-block hover:bg-sky-500 transition ease-in-out duration-300"%> 16 |
17 | <% end %> 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render layout: "auth_layout", locals: { title: "Sign in" } do %> 2 | <%= form_for(resource, as: resource_name, url: session_path(resource_name), data: { turbo: false }) do |f| %> 3 | 4 |
5 | <%= f.label :email, class: label_class %> 6 | <%= f.email_field :email, autofocus: true, autocomplete: "email", class: input_class %> 7 |
8 | 9 |
10 | <%= f.label :password, class: label_class %> 11 | <%= f.password_field :password, autocomplete: "current-password", class: input_class %> 12 |
13 | 14 |
15 | <% if devise_mapping.rememberable? %> 16 | <%= f.check_box :remember_me, class: checkbox_class %> 17 | <%= f.label :remember_me %> 18 | <% end %> 19 |
20 | 21 |
22 | <%= f.submit "Sign in", class: button_class(theme: "primary", variant: "expanded") %> 23 |
24 | <% end %> 25 | <% end %> 26 | -------------------------------------------------------------------------------- /.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 | 37 | /app/assets/builds/* 38 | !/app/assets/builds/.keep 39 | 40 | /node_modules 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Let's Build A Twitter Clone using Ruby on Rails 7 2 | 3 | ![Let's build a Twitter clone using ruby on rails 7](https://f001.backblazeb2.com/file/webcrunch/lets-build-a-twitter-clone-using-rails-7-cover-image.jpg) 4 | 5 | If you have followed me for some time you might remember a series I did on Ruby on Rails titled “Let’s Build”. These guides were a “learn in public” exercise I tasked myself with that ultimately resonated with a number of folks. 6 | 7 | This specific mini-series is going to focus on redoing an older “Let’s Build” where I took on building a Twitter clone. 8 | 9 | Since the advent of Rails 7, the way you might approach new problems when building software has changed a great deal. I wanted to take the opportunity to create some fresh content and show you how to leverage some new features of Ruby on Rails along the way. 10 | 11 | - 📕 [Read the full guide](https://web-crunch.com/posts/lets-build-with-ruby-on-rails-7-twitter-clone) 12 | - 📺 [Watch the six-part series on YouTube](https://youtu.be/loYfUpwoLlM) 13 | -------------------------------------------------------------------------------- /db/migrate/20220619170238_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 | -------------------------------------------------------------------------------- /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/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy. 4 | # See the Securing Rails Applications Guide for more information: 5 | # https://guides.rubyonrails.org/security.html#content-security-policy-header 6 | 7 | # Rails.application.configure do 8 | # config.content_security_policy do |policy| 9 | # policy.default_src :self, :https 10 | # policy.font_src :self, :https, :data 11 | # policy.img_src :self, :https, :data 12 | # policy.object_src :none 13 | # policy.script_src :self, :https 14 | # policy.style_src :self, :https 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | # 19 | # # Generate session nonces for permitted importmap 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 violations without enforcing the policy. 24 | # # config.content_security_policy_report_only = true 25 | # end 26 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render layout: "auth_layout", locals: { title: "Change your password" } do %> 2 | <%= form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }, data: { turbo: false }) do |f| %> 3 | <%= render "devise/shared/error_messages", resource: resource %> 4 | <%= f.hidden_field :reset_password_token %> 5 | 6 |
7 |
8 | <%= f.label :password, "New password", class: label_class %> 9 | <% if @minimum_password_length %> 10 | (<%= @minimum_password_length %> characters minimum) 11 | <% end %> 12 |
13 | <%= f.password_field :password, autofocus: true, autocomplete: "new-password", class: input_class %> 14 |
15 | 16 |
17 | <%= f.label :password_confirmation, "Confirm new password", class: label_class %> 18 | <%= f.password_field :password_confirmation, autocomplete: "off", class: input_class %> 19 |
20 | 21 |
22 | <%= f.submit "Change my password", class: button_class(theme: "primary", variant: "expanded") %> 23 |
24 | <% end %> 25 | <% end %> 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/views/devise/_auth_layout.html.erb: -------------------------------------------------------------------------------- 1 | <% 2 | subtitle ||= "" 3 | title ||= "" %> 4 |
5 |
6 |
7 |

Tweeter

8 |
9 |
10 |
11 |
12 |
13 |
14 | <%= link_to "Back", root_path, class: "text-gray-600 underline flex items-center hover:text-gray-700 hover:no-underline" %> 15 |
16 | 17 | <% unless title.blank? %> 18 |

<%= title %>

19 | <% end %> 20 | 21 | <% unless subtitle.blank? %> 22 |

<%= subtitle %>

23 | <% end %> 24 |
25 | 26 | <%= yield %> 27 | <%= render "devise/shared/links" %> 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /app/controllers/tweets_controller.rb: -------------------------------------------------------------------------------- 1 | class TweetsController < ApplicationController 2 | before_action :authenticate_user! 3 | 4 | def index 5 | @tweet = Tweet.new 6 | @tweets = Tweet.all.order(created_at: :desc) 7 | end 8 | 9 | def create 10 | @tweet = Tweet.new(tweet_params) 11 | @tweet.user = current_user 12 | 13 | respond_to do |format| 14 | if @tweet.save 15 | format.turbo_stream 16 | else 17 | format.html do 18 | flash[:tweet_errors] = @tweet.errors.full_messages 19 | redirect_to root_path 20 | end 21 | end 22 | end 23 | end 24 | 25 | def show 26 | @tweet = Tweet.find(params[:id]) 27 | @comment = Comment.new 28 | @comments = @tweet.comments.order(created_at: :desc) 29 | end 30 | 31 | def destroy 32 | @tweet = current_user.tweets.find(params[:id]) 33 | @tweet.destroy 34 | end 35 | 36 | def retweet 37 | @tweet = Tweet.find(params[:id]) 38 | 39 | @retweet = current_user.tweets.new(tweet_id: @tweet.id) 40 | 41 | respond_to do |format| 42 | if @retweet.save 43 | format.turbo_stream 44 | else 45 | format.html { redirect_back fallback_location: @tweet, alert: "Could not retweet" } 46 | end 47 | end 48 | end 49 | 50 | 51 | private 52 | 53 | def tweet_params 54 | params.require(:tweet).permit(:body, :tweet_id) 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /app/views/likes/_likes.html.erb: -------------------------------------------------------------------------------- 1 | <%= button_to likes_path(likeable_id: likeable.id, likeable_type: likeable.model_name.name), method: :post, class: "text-gray-500 space-x-2 group flex items-center justify-center" do %> 2 |
3 | <% if likeable.liked_by?(current_user) %> 4 | 5 | <% else %> 6 | 7 | <% end %> 8 |
9 | 10 |
<%= likeable.likes.size %>
11 |
12 | <% end %> 13 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.erb: -------------------------------------------------------------------------------- 1 | <%- if controller_name != 'sessions' %> 2 |
<%= link_to "Sign in", new_session_path(resource_name), class: link_class(extended_classes: "mb-2") %>
3 | <% end -%> 4 | 5 | <%- if devise_mapping.registerable? && controller_name != 'registrations' %> 6 | <%= link_to "Sign up", new_registration_path(resource_name), class: link_class(extended_classes: "mb-2") %> 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), class: link_class(extended_classes: "mb-2") %> 11 | <% end -%> 12 | 13 | <%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> 14 | <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name), class: link_class(extended_classes: "mb-2") %> 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), class: link_class(extended_classes: "mb-2") %> 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), class: btn_class %> 24 | <% end -%> 25 | <% end -%> 26 | -------------------------------------------------------------------------------- /app/views/tweets/_retweet.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | 4 |

Retweeted by <%= "@" + tweet.user.username %>

5 |
6 |
7 |
8 | <%= profile_image(tweet.tweet.user) %> 9 |
10 |
11 |

<%= link_to tweet.tweet.user.name, profile_path(tweet.tweet.user), class: "hover:underline", data: {turbo: false} %>

12 | <%= link_to tweet, data: { turbo: false } do %> 13 |
<%= tweet.tweet.body %>
14 | <% end %> 15 | 16 | <%= render "tweets/controls", tweet: tweet.tweet %> 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render layout: "auth_layout", locals: { title: "Sign up" } do %> 2 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), data: { turbo: false }) do |f| %> 3 | <%= render "devise/shared/error_messages", resource: resource %> 4 | 5 |
6 | <%= f.label :name, class: label_class %> 7 | <%= f.text_field :name, class: input_class %> 8 |
9 | 10 |
11 | <%= f.label :username, class: label_class %> 12 | <%= f.text_field :username, class: input_class %> 13 |
14 | 15 |
16 | <%= f.label :email, class: label_class %> 17 | <%= f.email_field :email, autocomplete: "email", class: input_class %> 18 |
19 | 20 |
21 |
22 | <%= f.label :password, class: label_class %> 23 | <% if @minimum_password_length %> 24 | (<%= @minimum_password_length %> characters minimum) 25 | <% end %> 26 |
27 | <%= f.password_field :password, autocomplete: "new-password", class: input_class %> 28 |
29 | 30 |
31 | <%= f.label :password_confirmation, class: label_class %> 32 | <%= f.password_field :password_confirmation, autocomplete: "new-password", class: input_class %> 33 |
34 | 35 |
36 | <%= f.submit "Sign up", class: button_class(variant: "expanded", theme: "primary") %> 37 |
38 | <% end %> 39 | <% end %> 40 | -------------------------------------------------------------------------------- /db/migrate/20220619170254_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 | t.string :first_name 36 | t.string :last_name 37 | t.boolean :admin, default: false 38 | 39 | t.timestamps null: false 40 | end 41 | 42 | add_index :users, :email, unique: true 43 | add_index :users, :reset_password_token, unique: true 44 | # add_index :users, :confirmation_token, unique: true 45 | # add_index :users, :unlock_token, unique: true 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | -------------------------------------------------------------------------------- /app/views/comments/_comment.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | <%= profile_image(comment.user) %> 5 |
6 |
7 |

<%= link_to comment.user.name, profile_path(comment.user), class: "hover:underline", data: { turbo: false } %>

8 |
<%= comment.body %>
9 | 10 |
    11 |
  • 12 | <%= content_tag :div, class: "#{dom_id(comment)}_likes" do %> 13 | <%= render "likes/likes", likeable: comment %> 14 | <% end %> 15 |
  • 16 | <% if user_signed_in? && current_user == comment.user %> 17 |
  • 18 | <%= button_to tweet_comment_path(comment.tweet,comment), method: :delete, form: { data: { turbo_confirm: "Are you sure?" } }, class: "text-gray-500 space-x-2 group flex items-center justify-center" do %> 19 |
    20 | 21 |
    22 | <% end %> 23 |
  • 24 | <% end %> 25 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /app/views/tweets/show.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= link_to root_path, class: "rounded-full w-8 h-8 flex items-center justify-center hover:bg-gray-100 duration-300 transition-all" do %> 4 | 5 | <% end %> 6 |

Tweet

7 |
8 |
9 | 10 |
11 |
12 | <%= profile_image(@tweet.user) %> 13 |
14 |

15 | <%= link_to @tweet.user.name, profile_path(@tweet.user) %> 16 |

17 |

<%= "@" + @tweet.user.username %>

18 |
19 |
20 |
21 | <% if @tweet.tweet_id? %> 22 | <%= @tweet.tweet.body %> 23 | <% else%> 24 | <%= @tweet.body %> 25 | <% end %> 26 |
27 | 28 | 36 | 37 |
38 | <%= render "tweets/controls", tweet: @tweet %> 39 |
40 | 41 | <% if user_signed_in? %> 42 | <%= turbo_frame_tag "#{dom_id(@tweet)}_comment_form" do %> 43 | <%= render "comments/form", tweet: @tweet %> 44 | <% end %> 45 | <% else %> 46 |

<%= link_to "Sign in", new_user_session_path, class: "text-sky-500 hover:text-sky-600 font-medium" %>

47 | <% end %> 48 | 49 | <%= turbo_frame_tag "#{dom_id(@tweet)}_comments" do %> 50 | <% @comments.each do |comment| %> 51 | <%= render "comments/comment", comment: comment %> 52 | <% end %> 53 | <% end %> 54 |
55 | -------------------------------------------------------------------------------- /db/migrate/20220619170234_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 | -------------------------------------------------------------------------------- /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/environments/development.rb: -------------------------------------------------------------------------------- 1 | require "active_support/core_ext/integer/time" 2 | 3 | Rails.application.configure do 4 | config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 5 | # Settings specified here will take precedence over those in config/application.rb. 6 | 7 | # In the development environment your application's code is reloaded any time 8 | # it changes. This slows down response time but is perfect for development 9 | # since you don't have to restart the web server when you make code changes. 10 | config.cache_classes = false 11 | 12 | # Do not eager load code on boot. 13 | config.eager_load = false 14 | 15 | # Show full error reports. 16 | config.consider_all_requests_local = true 17 | 18 | # Enable server timing 19 | config.server_timing = true 20 | 21 | # Enable/disable caching. By default caching is disabled. 22 | # Run rails dev:cache to toggle caching. 23 | if Rails.root.join("tmp/caching-dev.txt").exist? 24 | config.action_controller.perform_caching = true 25 | config.action_controller.enable_fragment_cache_logging = true 26 | 27 | config.cache_store = :memory_store 28 | config.public_file_server.headers = { 29 | "Cache-Control" => "public, max-age=#{2.days.to_i}" 30 | } 31 | else 32 | config.action_controller.perform_caching = false 33 | 34 | config.cache_store = :null_store 35 | end 36 | 37 | # Store uploaded files on the local file system (see config/storage.yml for options). 38 | config.active_storage.service = :local 39 | 40 | # Don't care if the mailer can't send. 41 | config.action_mailer.raise_delivery_errors = false 42 | 43 | config.action_mailer.perform_caching = false 44 | 45 | # Print deprecation notices to the Rails logger. 46 | config.active_support.deprecation = :log 47 | 48 | # Raise exceptions for disallowed deprecations. 49 | config.active_support.disallowed_deprecation = :raise 50 | 51 | # Tell Active Support which deprecation messages to disallow. 52 | config.active_support.disallowed_deprecation_warnings = [] 53 | 54 | # Raise an error on page load if there are pending migrations. 55 | config.active_record.migration_error = :page_load 56 | 57 | # Highlight code that triggered database queries in logs. 58 | config.active_record.verbose_query_logs = true 59 | 60 | # Suppress logger output for asset requests. 61 | config.assets.quiet = true 62 | 63 | # Raises error for missing translations. 64 | # config.i18n.raise_on_missing_translations = true 65 | 66 | # Annotate rendered view with file names. 67 | # config.action_view.annotate_rendered_view_with_filenames = true 68 | 69 | # Uncomment if you wish to allow Action Cable access from any origin. 70 | # config.action_cable.disable_request_forgery_protection = true 71 | end 72 | -------------------------------------------------------------------------------- /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.3" 8 | 9 | # The original asset pipeline for Rails [https://github.com/rails/sprockets-rails] 10 | gem "sprockets-rails" 11 | 12 | # Use sqlite3 as the database for Active Record 13 | gem "sqlite3", "~> 1.4" 14 | 15 | # Use the Puma web server [https://github.com/puma/puma] 16 | gem "puma", "~> 5.0" 17 | 18 | # Bundle and transpile JavaScript [https://github.com/rails/jsbundling-rails] 19 | gem "jsbundling-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 "web-console" 59 | 60 | # Add speed badges [https://github.com/MiniProfiler/rack-mini-profiler] 61 | # gem "rack-mini-profiler" 62 | 63 | # Speed up commands on slow machines / big apps [https://github.com/rails/spring] 64 | # gem "spring" 65 | end 66 | 67 | group :test do 68 | # Use system testing [https://guides.rubyonrails.org/testing.html#system-testing] 69 | gem "capybara" 70 | gem "selenium-webdriver" 71 | gem "webdrivers" 72 | end 73 | gem "devise", "~> 4.8" 74 | gem "friendly_id", "~> 5.4", ">= 5.4.2" 75 | gem "sidekiq", "~> 6.3", ">= 6.3.1" 76 | gem "name_of_person", "~> 1.1", ">= 1.1.1" 77 | gem "cssbundling-rails" 78 | gem "pay", "~> 3.0" 79 | gem "stripe", ">= 2.8", "< 6.0" 80 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render layout: "auth_layout", locals: { title: "Edit account" } do %> 2 | <%= form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }, data: { turbo: false }) do |f| %> 3 | 4 | <%= render "devise/shared/error_messages", resource: resource %> 5 |
6 | <%= f.label :profile_image, class: label_class %> 7 | <%= f.file_field :profile_image, class: "block w-full text-sm text-slate-500 file:mr-4 file:py-2 file:px-4 file:border-0 file:text-sm file:font-semibold file:bg-sky-50 file:text-sky-700 hover:file:bg-sky-100 file:rounded-full" %> 8 |
9 | 10 |
11 | <%= f.label :name, class: label_class %> 12 | <%= f.text_field :name, class: input_class %> 13 |
14 | 15 |
16 | <%= f.label :username, class: label_class %> 17 | <%= f.text_field :username, class: input_class %> 18 |
19 | 20 |
21 | <%= f.label :email, class: label_class %> 22 | <%= f.email_field :email, autocomplete: "email", class: input_class %> 23 |
24 | 25 |
26 | <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> 27 |

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

28 | <% end %> 29 |
30 | 31 |
32 | <%= f.label :password, class: label_class %> 33 | <%= f.password_field :password, autocomplete: "new-password", class: input_class %> 34 |

<% if @minimum_password_length %> 35 | <%= @minimum_password_length %> characters minimum <% end %> (leave blank if you don't want to change it)

36 |
37 | 38 |
39 | <%= f.label :password_confirmation, class: label_class %> 40 | <%= f.password_field :password_confirmation, autocomplete: "new-password", class: input_class %> 41 |
42 | 43 |
44 | <%= f.label :current_password, class: label_class %> 45 | <%= f.password_field :current_password, autocomplete: "current-password", class: input_class %> 46 |

(we need your current password to confirm your changes)

47 |
48 | 49 |
50 | <%= f.submit "Update", class: button_class(theme: "primary", variant: "expanded") %> 51 |
52 | <% end %> 53 | 54 |
55 |
56 | <%= button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete, class: button_class(variant: "small", additional_classes: "border") %> 57 |
58 | <% end %> 59 | -------------------------------------------------------------------------------- /db/migrate/20220619170257_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 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | def flash_classes(flash_type) 3 | flash_base = "px-2 py-4 mx-auto font-sans font-medium text-center text-white" 4 | { 5 | notice: "bg-sky-500 #{flash_base}", 6 | error: "bg-red-600 #{flash_base}", 7 | alert: "bg-red-600 #{flash_base}" 8 | }.stringify_keys[flash_type.to_s] || flash_type.to_s 9 | end 10 | 11 | def nav_classes 12 | ["devise/registrations", "devise/sessions", "devise/confirmations", "devise/passwords", "devise/unlocks"].include?(params[:controller]) ? "hidden" : nil 13 | end 14 | 15 | def label_class(options={}) 16 | "block mb-1 font-normal leading-normal #{options[:extended_classes]}" 17 | end 18 | 19 | def input_class(options={}) 20 | "rounded border border-gray-300 block w-full focus:outline-none focus:border-gray-400 outline-none focus-within:outline-none focus:ring-2 focus:ring-gray-200 #{options[:extended_classes]}" 21 | end 22 | 23 | def checkbox_class(options={}) 24 | "rounded border-gray-300 border focus:ring-2 focus:ring-gray-200 text-blue-500 mr-1 #{options[:extended_classes]}" 25 | end 26 | 27 | def link_class(options={}) 28 | "text-gray-700 underline hover:no-underline hover:text-gray-800 block #{options[:extended_classes]}" 29 | end 30 | 31 | def button_class(options={}) 32 | variant = options[:variant] 33 | theme = options[:theme] 34 | 35 | style_button(variant, theme_button(theme)) 36 | end 37 | 38 | def theme_button(theme) 39 | themes = { 40 | primary: "primary", 41 | secondary: "secondary", 42 | transparent: "transparent", 43 | dark: "dark" 44 | } 45 | 46 | case theme 47 | when themes[:primary] 48 | "bg-sky-500 hover:bg-sky-600 text-white" 49 | when themes[:secondary] 50 | "bg-teal-600 hover:bg-teal-700 text-white" 51 | when themes[:transparent] 52 | "bg-transparent hover:bg-gray-100 text-gray-700" 53 | when themes[:dark] 54 | "bg-gray-800 text-white shadow-sm hover:bg-gray-900" 55 | else 56 | "bg-white border border-gray-300 shadow-sm hover:bg-gray-100" 57 | end 58 | end 59 | 60 | def style_button(variant, theme) 61 | base = "rounded text-center font-sans font-normal outline-none leading-normal cursor-pointer transition ease-in-out duration-200 font-medium" 62 | 63 | case variant 64 | when "large" 65 | "px-5 py-4 text-lg #{base} #{theme}" 66 | when "small" 67 | "py-2 px-4 text-sm #{base} #{theme}" 68 | when "expanded" 69 | "p-3 w-full block #{base} #{theme}" 70 | else 71 | "px-5 py-2 text-base #{base} #{theme}" 72 | end 73 | end 74 | 75 | def profile_image(user, options={}) 76 | size = case options[:size] 77 | when "large" 78 | "w-20 h-20" 79 | when "small" 80 | "w-10 h-10" 81 | else 82 | "w-14 h-14" 83 | end 84 | 85 | classes = "#{size} flex-skrink-0 rounded-full border-2 border-white" 86 | 87 | if user.profile_image.attached? 88 | image_tag user.profile_image, class: classes 89 | else 90 | image_tag "https://doodleipsum.com/700/avatar-5?bg=3D27F6&i=f339578a64040310d3eb5bd82b550627", class: classes 91 | end 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # frozen_string_literal: true 3 | 4 | # 5 | # This file was generated by Bundler. 6 | # 7 | # The application 'bundle' is installed as part of a gem, and 8 | # this file is here to facilitate running it. 9 | # 10 | 11 | require "rubygems" 12 | 13 | m = Module.new do 14 | module_function 15 | 16 | def invoked_as_script? 17 | File.expand_path($0) == File.expand_path(__FILE__) 18 | end 19 | 20 | def env_var_version 21 | ENV["BUNDLER_VERSION"] 22 | end 23 | 24 | def cli_arg_version 25 | return unless invoked_as_script? # don't want to hijack other binstubs 26 | return unless "update".start_with?(ARGV.first || " ") # must be running `bundle update` 27 | bundler_version = nil 28 | update_index = nil 29 | ARGV.each_with_index do |a, i| 30 | if update_index && update_index.succ == i && a =~ Gem::Version::ANCHORED_VERSION_PATTERN 31 | bundler_version = a 32 | end 33 | next unless a =~ /\A--bundler(?:[= ](#{Gem::Version::VERSION_PATTERN}))?\z/ 34 | bundler_version = $1 35 | update_index = i 36 | end 37 | bundler_version 38 | end 39 | 40 | def gemfile 41 | gemfile = ENV["BUNDLE_GEMFILE"] 42 | return gemfile if gemfile && !gemfile.empty? 43 | 44 | File.expand_path("../Gemfile", __dir__) 45 | end 46 | 47 | def lockfile 48 | lockfile = 49 | case File.basename(gemfile) 50 | when "gems.rb" then gemfile.sub(/\.rb$/, gemfile) 51 | else "#{gemfile}.lock" 52 | end 53 | File.expand_path(lockfile) 54 | end 55 | 56 | def lockfile_version 57 | return unless File.file?(lockfile) 58 | lockfile_contents = File.read(lockfile) 59 | return unless lockfile_contents =~ /\n\nBUNDLED WITH\n\s{2,}(#{Gem::Version::VERSION_PATTERN})\n/ 60 | Regexp.last_match(1) 61 | end 62 | 63 | def bundler_requirement 64 | @bundler_requirement ||= 65 | env_var_version || cli_arg_version || 66 | bundler_requirement_for(lockfile_version) 67 | end 68 | 69 | def bundler_requirement_for(version) 70 | return "#{Gem::Requirement.default}.a" unless version 71 | 72 | bundler_gem_version = Gem::Version.new(version) 73 | 74 | requirement = bundler_gem_version.approximate_recommendation 75 | 76 | return requirement unless Gem.rubygems_version < Gem::Version.new("2.7.0") 77 | 78 | requirement += ".a" if bundler_gem_version.prerelease? 79 | 80 | requirement 81 | end 82 | 83 | def load_bundler! 84 | ENV["BUNDLE_GEMFILE"] ||= gemfile 85 | 86 | activate_bundler 87 | end 88 | 89 | def activate_bundler 90 | gem_error = activation_error_handling do 91 | gem "bundler", bundler_requirement 92 | end 93 | return if gem_error.nil? 94 | require_error = activation_error_handling do 95 | require "bundler/version" 96 | end 97 | return if require_error.nil? && Gem::Requirement.new(bundler_requirement).satisfied_by?(Gem::Version.new(Bundler::VERSION)) 98 | warn "Activating bundler (#{bundler_requirement}) failed:\n#{gem_error.message}\n\nTo install the version of bundler this project requires, run `gem install bundler -v '#{bundler_requirement}'`" 99 | exit 42 100 | end 101 | 102 | def activation_error_handling 103 | yield 104 | nil 105 | rescue StandardError, LoadError => e 106 | e 107 | end 108 | end 109 | 110 | m.load_bundler! 111 | 112 | if m.invoked_as_script? 113 | load Gem.bin_path("bundler", "bundle") 114 | end 115 | -------------------------------------------------------------------------------- /app/views/tweets/_controls.html.erb: -------------------------------------------------------------------------------- 1 | 37 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | <%= render "shared/head" %> 4 | 5 | 6 | 7 |
8 | <%= render "shared/flash_notice" %> 9 |
10 | 11 |
12 |
13 |
14 | <%= link_to root_path, class:"link text-xl tracking-tight font-black" do %> 15 | Tweeter 16 | <% end %> 17 |
    18 |
  • 19 | <%= link_to root_path, class: "rounded-full px-4 -ml-4 inline-flex items-center py-3 hover:bg-neutral-50 w-full text-lg transition ease-in-out duration-500" do %> 20 | 21 | Home 22 | <% end %> 23 |
  • 24 | <% if user_signed_in? %> 25 |
  • 26 | <%= link_to profile_path(current_user), class: "rounded-full px-4 -ml-4 inline-flex items-center py-3 hover:bg-neutral-50 w-full text-lg transition ease-in-out duration-500" do %> 27 | 28 | Profile 29 | <% end %> 30 |
  • 31 | <% end %> 32 |
33 |
34 |
35 | <%= content_for?(:content) ? yield(:content) : yield %> 36 |
37 |
38 |
Sidebar stuff
39 | <% if user_signed_in? %> 40 |

41 | Signed in as <%= current_user.name %> 42 |

43 | <%= button_to "Sign out", destroy_user_session_path, method: :delete, class: button_class(theme: "primary"), data: { turbo: false } %> 44 | <% else %> 45 | <%= link_to "Sign in", new_user_session_path, class: button_class(theme: "primary") %> 46 | <% end %> 47 |
48 |
49 |
50 | 51 | 52 | -------------------------------------------------------------------------------- /app/views/shared/_navbar.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 38 | -------------------------------------------------------------------------------- /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 = "tweeter_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/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/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 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actioncable (7.0.3) 5 | actionpack (= 7.0.3) 6 | activesupport (= 7.0.3) 7 | nio4r (~> 2.0) 8 | websocket-driver (>= 0.6.1) 9 | actionmailbox (7.0.3) 10 | actionpack (= 7.0.3) 11 | activejob (= 7.0.3) 12 | activerecord (= 7.0.3) 13 | activestorage (= 7.0.3) 14 | activesupport (= 7.0.3) 15 | mail (>= 2.7.1) 16 | net-imap 17 | net-pop 18 | net-smtp 19 | actionmailer (7.0.3) 20 | actionpack (= 7.0.3) 21 | actionview (= 7.0.3) 22 | activejob (= 7.0.3) 23 | activesupport (= 7.0.3) 24 | mail (~> 2.5, >= 2.5.4) 25 | net-imap 26 | net-pop 27 | net-smtp 28 | rails-dom-testing (~> 2.0) 29 | actionpack (7.0.3) 30 | actionview (= 7.0.3) 31 | activesupport (= 7.0.3) 32 | rack (~> 2.0, >= 2.2.0) 33 | rack-test (>= 0.6.3) 34 | rails-dom-testing (~> 2.0) 35 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 36 | actiontext (7.0.3) 37 | actionpack (= 7.0.3) 38 | activerecord (= 7.0.3) 39 | activestorage (= 7.0.3) 40 | activesupport (= 7.0.3) 41 | globalid (>= 0.6.0) 42 | nokogiri (>= 1.8.5) 43 | actionview (7.0.3) 44 | activesupport (= 7.0.3) 45 | builder (~> 3.1) 46 | erubi (~> 1.4) 47 | rails-dom-testing (~> 2.0) 48 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 49 | activejob (7.0.3) 50 | activesupport (= 7.0.3) 51 | globalid (>= 0.3.6) 52 | activemodel (7.0.3) 53 | activesupport (= 7.0.3) 54 | activerecord (7.0.3) 55 | activemodel (= 7.0.3) 56 | activesupport (= 7.0.3) 57 | activestorage (7.0.3) 58 | actionpack (= 7.0.3) 59 | activejob (= 7.0.3) 60 | activerecord (= 7.0.3) 61 | activesupport (= 7.0.3) 62 | marcel (~> 1.0) 63 | mini_mime (>= 1.1.0) 64 | activesupport (7.0.3) 65 | concurrent-ruby (~> 1.0, >= 1.0.2) 66 | i18n (>= 1.6, < 2) 67 | minitest (>= 5.1) 68 | tzinfo (~> 2.0) 69 | addressable (2.8.0) 70 | public_suffix (>= 2.0.2, < 5.0) 71 | bcrypt (3.1.18) 72 | bindex (0.8.1) 73 | bootsnap (1.12.0) 74 | msgpack (~> 1.2) 75 | builder (3.2.4) 76 | capybara (3.37.1) 77 | addressable 78 | matrix 79 | mini_mime (>= 0.1.3) 80 | nokogiri (~> 1.8) 81 | rack (>= 1.6.0) 82 | rack-test (>= 0.6.3) 83 | regexp_parser (>= 1.5, < 3.0) 84 | xpath (~> 3.2) 85 | childprocess (4.1.0) 86 | concurrent-ruby (1.1.10) 87 | connection_pool (2.2.5) 88 | crass (1.0.6) 89 | cssbundling-rails (1.1.1) 90 | railties (>= 6.0.0) 91 | debug (1.5.0) 92 | irb (>= 1.3.6) 93 | reline (>= 0.2.7) 94 | devise (4.8.1) 95 | bcrypt (~> 3.0) 96 | orm_adapter (~> 0.1) 97 | railties (>= 4.1.0) 98 | responders 99 | warden (~> 1.2.3) 100 | digest (3.1.0) 101 | erubi (1.10.0) 102 | ffi (1.15.5) 103 | friendly_id (5.4.2) 104 | activerecord (>= 4.0.0) 105 | globalid (1.0.0) 106 | activesupport (>= 5.0) 107 | i18n (1.10.0) 108 | concurrent-ruby (~> 1.0) 109 | image_processing (1.12.2) 110 | mini_magick (>= 4.9.5, < 5) 111 | ruby-vips (>= 2.0.17, < 3) 112 | io-console (0.5.11) 113 | irb (1.4.1) 114 | reline (>= 0.3.0) 115 | jbuilder (2.11.5) 116 | actionview (>= 5.0.0) 117 | activesupport (>= 5.0.0) 118 | jsbundling-rails (1.0.3) 119 | railties (>= 6.0.0) 120 | loofah (2.18.0) 121 | crass (~> 1.0.2) 122 | nokogiri (>= 1.5.9) 123 | mail (2.7.1) 124 | mini_mime (>= 0.1.1) 125 | marcel (1.0.2) 126 | matrix (0.4.2) 127 | method_source (1.0.0) 128 | mini_magick (4.11.0) 129 | mini_mime (1.1.2) 130 | minitest (5.16.0) 131 | msgpack (1.5.2) 132 | name_of_person (1.1.1) 133 | activesupport (>= 5.2.0) 134 | net-imap (0.2.3) 135 | digest 136 | net-protocol 137 | strscan 138 | net-pop (0.1.1) 139 | digest 140 | net-protocol 141 | timeout 142 | net-protocol (0.1.3) 143 | timeout 144 | net-smtp (0.3.1) 145 | digest 146 | net-protocol 147 | timeout 148 | nio4r (2.5.8) 149 | nokogiri (1.13.6-arm64-darwin) 150 | racc (~> 1.4) 151 | orm_adapter (0.5.0) 152 | pay (3.0.24) 153 | rails (>= 6.0.0) 154 | public_suffix (4.0.7) 155 | puma (5.6.4) 156 | nio4r (~> 2.0) 157 | racc (1.6.0) 158 | rack (2.2.3.1) 159 | rack-test (1.1.0) 160 | rack (>= 1.0, < 3) 161 | rails (7.0.3) 162 | actioncable (= 7.0.3) 163 | actionmailbox (= 7.0.3) 164 | actionmailer (= 7.0.3) 165 | actionpack (= 7.0.3) 166 | actiontext (= 7.0.3) 167 | actionview (= 7.0.3) 168 | activejob (= 7.0.3) 169 | activemodel (= 7.0.3) 170 | activerecord (= 7.0.3) 171 | activestorage (= 7.0.3) 172 | activesupport (= 7.0.3) 173 | bundler (>= 1.15.0) 174 | railties (= 7.0.3) 175 | rails-dom-testing (2.0.3) 176 | activesupport (>= 4.2.0) 177 | nokogiri (>= 1.6) 178 | rails-html-sanitizer (1.4.3) 179 | loofah (~> 2.3) 180 | railties (7.0.3) 181 | actionpack (= 7.0.3) 182 | activesupport (= 7.0.3) 183 | method_source 184 | rake (>= 12.2) 185 | thor (~> 1.0) 186 | zeitwerk (~> 2.5) 187 | rake (13.0.6) 188 | redis (4.6.0) 189 | regexp_parser (2.5.0) 190 | reline (0.3.1) 191 | io-console (~> 0.5) 192 | responders (3.0.1) 193 | actionpack (>= 5.0) 194 | railties (>= 5.0) 195 | rexml (3.2.5) 196 | ruby-vips (2.1.4) 197 | ffi (~> 1.12) 198 | rubyzip (2.3.2) 199 | selenium-webdriver (4.2.1) 200 | childprocess (>= 0.5, < 5.0) 201 | rexml (~> 3.2, >= 3.2.5) 202 | rubyzip (>= 1.2.2, < 3.0) 203 | websocket (~> 1.0) 204 | sidekiq (6.5.1) 205 | connection_pool (>= 2.2.2) 206 | rack (~> 2.0) 207 | redis (>= 4.2.0) 208 | sprockets (4.0.3) 209 | concurrent-ruby (~> 1.0) 210 | rack (> 1, < 3) 211 | sprockets-rails (3.4.2) 212 | actionpack (>= 5.2) 213 | activesupport (>= 5.2) 214 | sprockets (>= 3.0.0) 215 | sqlite3 (1.4.4) 216 | stimulus-rails (1.0.4) 217 | railties (>= 6.0.0) 218 | stripe (5.55.0) 219 | strscan (3.0.3) 220 | thor (1.2.1) 221 | timeout (0.3.0) 222 | turbo-rails (1.1.1) 223 | actionpack (>= 6.0.0) 224 | activejob (>= 6.0.0) 225 | railties (>= 6.0.0) 226 | tzinfo (2.0.4) 227 | concurrent-ruby (~> 1.0) 228 | warden (1.2.9) 229 | rack (>= 2.0.9) 230 | web-console (4.2.0) 231 | actionview (>= 6.0.0) 232 | activemodel (>= 6.0.0) 233 | bindex (>= 0.4.0) 234 | railties (>= 6.0.0) 235 | webdrivers (5.0.0) 236 | nokogiri (~> 1.6) 237 | rubyzip (>= 1.3.0) 238 | selenium-webdriver (~> 4.0) 239 | websocket (1.2.9) 240 | websocket-driver (0.7.5) 241 | websocket-extensions (>= 0.1.0) 242 | websocket-extensions (0.1.5) 243 | xpath (3.2.0) 244 | nokogiri (~> 1.8) 245 | zeitwerk (2.6.0) 246 | 247 | PLATFORMS 248 | arm64-darwin-21 249 | 250 | DEPENDENCIES 251 | bootsnap 252 | capybara 253 | cssbundling-rails 254 | debug 255 | devise (~> 4.8) 256 | friendly_id (~> 5.4, >= 5.4.2) 257 | image_processing (~> 1.2) 258 | jbuilder 259 | jsbundling-rails 260 | name_of_person (~> 1.1, >= 1.1.1) 261 | pay (~> 3.0) 262 | puma (~> 5.0) 263 | rails (~> 7.0.3) 264 | redis (~> 4.0) 265 | selenium-webdriver 266 | sidekiq (~> 6.3, >= 6.3.1) 267 | sprockets-rails 268 | sqlite3 (~> 1.4) 269 | stimulus-rails 270 | stripe (>= 2.8, < 6.0) 271 | turbo-rails 272 | tzinfo-data 273 | web-console 274 | webdrivers 275 | 276 | RUBY VERSION 277 | ruby 3.0.3p157 278 | 279 | BUNDLED WITH 280 | 2.3.12 281 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema[7.0].define(version: 2022_06_19_203053) do 14 | create_table "action_text_rich_texts", force: :cascade do |t| 15 | t.string "name", null: false 16 | t.text "body" 17 | t.string "record_type", null: false 18 | t.bigint "record_id", null: false 19 | t.datetime "created_at", null: false 20 | t.datetime "updated_at", null: false 21 | t.index ["record_type", "record_id", "name"], name: "index_action_text_rich_texts_uniqueness", unique: true 22 | end 23 | 24 | create_table "active_storage_attachments", force: :cascade do |t| 25 | t.string "name", null: false 26 | t.string "record_type", null: false 27 | t.bigint "record_id", null: false 28 | t.bigint "blob_id", null: false 29 | t.datetime "created_at", null: false 30 | t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id" 31 | t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true 32 | end 33 | 34 | create_table "active_storage_blobs", force: :cascade do |t| 35 | t.string "key", null: false 36 | t.string "filename", null: false 37 | t.string "content_type" 38 | t.text "metadata" 39 | t.string "service_name", null: false 40 | t.bigint "byte_size", null: false 41 | t.string "checksum" 42 | t.datetime "created_at", null: false 43 | t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true 44 | end 45 | 46 | create_table "active_storage_variant_records", force: :cascade do |t| 47 | t.bigint "blob_id", null: false 48 | t.string "variation_digest", null: false 49 | t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true 50 | end 51 | 52 | create_table "comments", force: :cascade do |t| 53 | t.integer "user_id", null: false 54 | t.integer "tweet_id", null: false 55 | t.text "body" 56 | t.datetime "created_at", null: false 57 | t.datetime "updated_at", null: false 58 | t.index ["tweet_id"], name: "index_comments_on_tweet_id" 59 | t.index ["user_id"], name: "index_comments_on_user_id" 60 | end 61 | 62 | create_table "friendly_id_slugs", force: :cascade do |t| 63 | t.string "slug", null: false 64 | t.integer "sluggable_id", null: false 65 | t.string "sluggable_type", limit: 50 66 | t.string "scope" 67 | t.datetime "created_at" 68 | t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true 69 | t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type" 70 | t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id" 71 | end 72 | 73 | create_table "likes", force: :cascade do |t| 74 | t.string "likeable_type", null: false 75 | t.integer "likeable_id", null: false 76 | t.integer "user_id", null: false 77 | t.datetime "created_at", null: false 78 | t.datetime "updated_at", null: false 79 | t.index ["likeable_type", "likeable_id"], name: "index_likes_on_likeable" 80 | t.index ["user_id"], name: "index_likes_on_user_id" 81 | end 82 | 83 | create_table "pay_charges", force: :cascade do |t| 84 | t.integer "customer_id", null: false 85 | t.integer "subscription_id" 86 | t.string "processor_id", null: false 87 | t.integer "amount", null: false 88 | t.string "currency" 89 | t.integer "application_fee_amount" 90 | t.integer "amount_refunded" 91 | t.json "metadata" 92 | t.json "data" 93 | t.datetime "created_at", null: false 94 | t.datetime "updated_at", null: false 95 | t.index ["customer_id", "processor_id"], name: "index_pay_charges_on_customer_id_and_processor_id", unique: true 96 | t.index ["subscription_id"], name: "index_pay_charges_on_subscription_id" 97 | end 98 | 99 | create_table "pay_customers", force: :cascade do |t| 100 | t.string "owner_type" 101 | t.integer "owner_id" 102 | t.string "processor", null: false 103 | t.string "processor_id" 104 | t.boolean "default" 105 | t.json "data" 106 | t.datetime "deleted_at", precision: nil 107 | t.datetime "created_at", null: false 108 | t.datetime "updated_at", null: false 109 | t.index ["owner_type", "owner_id", "deleted_at", "default"], name: "pay_customer_owner_index" 110 | t.index ["processor", "processor_id"], name: "index_pay_customers_on_processor_and_processor_id", unique: true 111 | end 112 | 113 | create_table "pay_merchants", force: :cascade do |t| 114 | t.string "owner_type" 115 | t.integer "owner_id" 116 | t.string "processor", null: false 117 | t.string "processor_id" 118 | t.boolean "default" 119 | t.json "data" 120 | t.datetime "created_at", null: false 121 | t.datetime "updated_at", null: false 122 | t.index ["owner_type", "owner_id", "processor"], name: "index_pay_merchants_on_owner_type_and_owner_id_and_processor" 123 | end 124 | 125 | create_table "pay_payment_methods", force: :cascade do |t| 126 | t.integer "customer_id", null: false 127 | t.string "processor_id", null: false 128 | t.boolean "default" 129 | t.string "type" 130 | t.json "data" 131 | t.datetime "created_at", null: false 132 | t.datetime "updated_at", null: false 133 | t.index ["customer_id", "processor_id"], name: "index_pay_payment_methods_on_customer_id_and_processor_id", unique: true 134 | end 135 | 136 | create_table "pay_subscriptions", force: :cascade do |t| 137 | t.integer "customer_id", null: false 138 | t.string "name", null: false 139 | t.string "processor_id", null: false 140 | t.string "processor_plan", null: false 141 | t.integer "quantity", default: 1, null: false 142 | t.string "status", null: false 143 | t.datetime "trial_ends_at", precision: nil 144 | t.datetime "ends_at", precision: nil 145 | t.decimal "application_fee_percent", precision: 8, scale: 2 146 | t.json "metadata" 147 | t.json "data" 148 | t.datetime "created_at", null: false 149 | t.datetime "updated_at", null: false 150 | t.index ["customer_id", "processor_id"], name: "index_pay_subscriptions_on_customer_id_and_processor_id", unique: true 151 | end 152 | 153 | create_table "pay_webhooks", force: :cascade do |t| 154 | t.string "processor" 155 | t.string "event_type" 156 | t.json "event" 157 | t.datetime "created_at", null: false 158 | t.datetime "updated_at", null: false 159 | end 160 | 161 | create_table "tweets", force: :cascade do |t| 162 | t.text "body" 163 | t.integer "user_id", null: false 164 | t.datetime "created_at", null: false 165 | t.datetime "updated_at", null: false 166 | t.integer "tweet_id" 167 | t.index ["user_id"], name: "index_tweets_on_user_id" 168 | end 169 | 170 | create_table "users", force: :cascade do |t| 171 | t.string "email", default: "", null: false 172 | t.string "encrypted_password", default: "", null: false 173 | t.string "reset_password_token" 174 | t.datetime "reset_password_sent_at" 175 | t.datetime "remember_created_at" 176 | t.string "first_name" 177 | t.string "last_name" 178 | t.boolean "admin", default: false 179 | t.datetime "created_at", null: false 180 | t.datetime "updated_at", null: false 181 | t.string "username" 182 | t.index ["email"], name: "index_users_on_email", unique: true 183 | t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true 184 | t.index ["username"], name: "index_users_on_username", unique: true 185 | end 186 | 187 | add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id" 188 | add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id" 189 | add_foreign_key "comments", "tweets" 190 | add_foreign_key "comments", "users" 191 | add_foreign_key "likes", "users" 192 | add_foreign_key "pay_charges", "pay_customers", column: "customer_id" 193 | add_foreign_key "pay_charges", "pay_subscriptions", column: "subscription_id" 194 | add_foreign_key "pay_payment_methods", "pay_customers", column: "customer_id" 195 | add_foreign_key "pay_subscriptions", "pay_customers", column: "customer_id" 196 | add_foreign_key "tweets", "users" 197 | end 198 | -------------------------------------------------------------------------------- /app/views/home/index.html.erb: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 | 6 |
7 |

Kickoff Tailwind 8 |

9 |

A free and simple starting point for Ruby on Rails applications.

10 |
11 |
12 |

What's under the hood?

13 |
    14 |
  • Powered by <%= link_to "Ruby on Rails 7", "https://rubyonrails.org", target: :_blank, class: "text-indigo-600 hover:text-indigo-700 underline" %>
  • 15 |
  • <%= link_to "Friendly ID", "https://github.com/norman/friendly_id", target: :_blank, class: "text-indigo-600 hover:text-indigo-700 underline" %> preconfigured for your next app idea.
  • 16 |
  • <%= link_to "Devise", "https://github.com/heartcombo/devise", target: :_blank, class: "text-indigo-600 hover:text-indigo-700 underline" %> authentication strategies out of the box based on a default User model.
  • 17 |
  • <%= link_to "Tailwind CSS", "https://tailwindcss.com", target: :_blank, class: "text-indigo-600 hover:text-indigo-700 underline" %> by default (optionally swap for Bootstrap, Bulma, PostCSS, or Sass*).
  • 18 |
  • Configured and integrated <%= link_to "pay-rails", "https://github.com/pay-rails/pay", target: :_blank, class: "text-indigo-600 hover:text-indigo-700 underline" %> gem combined with the official <%= link_to "Stripe", "https://github.com/stripe/stripe-ruby", target: :_blank, class: "text-indigo-600 hover:text-indigo-700 underline" %> Gem for easy charges/subscriptions.
  • 19 |
  • Customized and easily themeable Devise views.
  • 20 |
  • View helpers for quick form styling with Tailwind CSS.
  • 21 | 22 |
23 | 24 |

*This template comes with support for Tailwind CSS only. You will need to adjust the markup to make use of other frameworks. <%= link_to "Read more", "https://github.com/rails/cssbundling-rails#installation", target: :_blank, class: "underline text-indigo-600 hover:text-indigo-700" %>

25 | 26 |
27 | 28 |
29 |
30 |
31 |

Useful links

32 |
    33 |
  • 34 | <%= link_to "View this on Github", "https://github.com/justalever/kickoff_tailwind", class: "underline text-indigo-600 hover:text-indigo-700" %> 35 |
  • 36 |
  • 37 | <%= link_to "Rails Guides", "https://guides.rubyonrails.org/", class: "underline text-indigo-600 hover:text-indigo-700" %> 38 |
  • 39 |
  • 40 | <%= link_to "Rails API Documentation", "https://api.rubyonrails.org/", class: "underline text-indigo-600 hover:text-indigo-700" %> 41 |
  • 42 |
  • 43 | <%= link_to "Rails Forum", "https://discuss.rubyonrails.org/", class: "underline text-indigo-600 hover:text-indigo-700" %> 44 |
  • 45 |
  • 46 | <%= link_to "Devise Docs", "https://github.com/heartcombo/devise", class: "underline text-indigo-600 hover:text-indigo-700" %> 47 |
  • 48 |
  • 49 | <%= link_to "Friendly ID Docs", "https://github.com/norman/friendly_id", class: "underline text-indigo-600 hover:text-indigo-700" %> 50 |
  • 51 |
  • 52 | <%= link_to "Tailwind CSS Docs", "https://tailwindcss.com/docs/installation", class: "underline text-indigo-600 hover:text-indigo-700" %> 53 |
  • 54 |
55 |
56 |
57 |

Shameless plugs

58 |
    59 | 60 |
  • <%= link_to "web-crunch.com", "https://web-crunch.com", target: :_blank, class: "underline text-indigo-600 hover:text-indigo-700" %>
  • 61 |
  • <%= link_to "hellorails.io", "https://hellorails.io", target: :_blank, class: "underline text-indigo-600 hover:text-indigo-700" %>
  • 62 |
  • <%= link_to "YouTube", "https://youtube.com/c/webcrunch", target: :_blank, class: "underline text-indigo-600 hover:text-indigo-700" %>
  • 63 |
  • 64 | <%= link_to "Sponsor me", "https://github.com/sponsors/justalever", target: :_blank, class: "underline text-indigo-600 hover:text-indigo-700" %> 65 |
  • 66 |
67 |
68 |
69 | 70 | 75 | 76 | 77 |
78 |
79 | 80 |

Made by @justalever

81 |
82 |
83 | -------------------------------------------------------------------------------- /config/initializers/devise.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Assuming you have not yet modified this file, each configuration option below 4 | # is set to its default value. Note that some are commented out while others 5 | # are not: uncommented lines are intended to protect your configuration from 6 | # breaking changes in upgrades (i.e., in the event that future versions of 7 | # Devise change the default values for those options). 8 | # 9 | # Use this hook to configure devise mailer, warden hooks and so forth. 10 | # Many of these configuration options can be set straight in your model. 11 | Devise.setup do |config| 12 | # The secret key used by Devise. Devise uses this key to generate 13 | # random tokens. Changing this key will render invalid all existing 14 | # confirmation, reset password and unlock tokens in the database. 15 | # Devise will use the `secret_key_base` as its `secret_key` 16 | # by default. You can change it below and use your own secret key. 17 | # config.secret_key = '61d115749ed0f61a9c73c7fa867994b5ce52409ffddce8958478ec2a6be4f923ad91e325f783c78d59381f5caf912e99f5fb5ad3f2eaddd6d509291850a60479' 18 | 19 | # ==> Controller configuration 20 | # Configure the parent class to the devise controllers. 21 | # config.parent_controller = 'DeviseController' 22 | 23 | # ==> Mailer Configuration 24 | # Configure the e-mail address which will be shown in Devise::Mailer, 25 | # note that it will be overwritten if you use your own mailer class 26 | # with default "from" parameter. 27 | config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' 28 | 29 | # Configure the class responsible to send e-mails. 30 | # config.mailer = 'Devise::Mailer' 31 | 32 | # Configure the parent class responsible to send e-mails. 33 | # config.parent_mailer = 'ActionMailer::Base' 34 | 35 | # ==> ORM configuration 36 | # Load and configure the ORM. Supports :active_record (default) and 37 | # :mongoid (bson_ext recommended) by default. Other ORMs may be 38 | # available as additional gems. 39 | require 'devise/orm/active_record' 40 | 41 | # ==> Configuration for any authentication mechanism 42 | # Configure which keys are used when authenticating a user. The default is 43 | # just :email. You can configure it to use [:username, :subdomain], so for 44 | # authenticating a user, both parameters are required. Remember that those 45 | # parameters are used only when authenticating and not when retrieving from 46 | # session. If you need permissions, you should implement that in a before filter. 47 | # You can also supply a hash where the value is a boolean determining whether 48 | # or not authentication should be aborted when the value is not present. 49 | # config.authentication_keys = [:email] 50 | 51 | # Configure parameters from the request object used for authentication. Each entry 52 | # given should be a request method and it will automatically be passed to the 53 | # find_for_authentication method and considered in your model lookup. For instance, 54 | # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. 55 | # The same considerations mentioned for authentication_keys also apply to request_keys. 56 | # config.request_keys = [] 57 | 58 | # Configure which authentication keys should be case-insensitive. 59 | # These keys will be downcased upon creating or modifying a user and when used 60 | # to authenticate or find a user. Default is :email. 61 | config.case_insensitive_keys = [:email] 62 | 63 | # Configure which authentication keys should have whitespace stripped. 64 | # These keys will have whitespace before and after removed upon creating or 65 | # modifying a user and when used to authenticate or find a user. Default is :email. 66 | config.strip_whitespace_keys = [:email] 67 | 68 | # Tell if authentication through request.params is enabled. True by default. 69 | # It can be set to an array that will enable params authentication only for the 70 | # given strategies, for example, `config.params_authenticatable = [:database]` will 71 | # enable it only for database (email + password) authentication. 72 | # config.params_authenticatable = true 73 | 74 | # Tell if authentication through HTTP Auth is enabled. False by default. 75 | # It can be set to an array that will enable http authentication only for the 76 | # given strategies, for example, `config.http_authenticatable = [:database]` will 77 | # enable it only for database authentication. 78 | # For API-only applications to support authentication "out-of-the-box", you will likely want to 79 | # enable this with :database unless you are using a custom strategy. 80 | # The supported strategies are: 81 | # :database = Support basic authentication with authentication key + password 82 | # config.http_authenticatable = false 83 | 84 | # If 401 status code should be returned for AJAX requests. True by default. 85 | # config.http_authenticatable_on_xhr = true 86 | 87 | # The realm used in Http Basic Authentication. 'Application' by default. 88 | # config.http_authentication_realm = 'Application' 89 | 90 | # It will change confirmation, password recovery and other workflows 91 | # to behave the same regardless if the e-mail provided was right or wrong. 92 | # Does not affect registerable. 93 | # config.paranoid = true 94 | 95 | # By default Devise will store the user in session. You can skip storage for 96 | # particular strategies by setting this option. 97 | # Notice that if you are skipping storage for all authentication paths, you 98 | # may want to disable generating routes to Devise's sessions controller by 99 | # passing skip: :sessions to `devise_for` in your config/routes.rb 100 | config.skip_session_storage = [:http_auth] 101 | 102 | # By default, Devise cleans up the CSRF token on authentication to 103 | # avoid CSRF token fixation attacks. This means that, when using AJAX 104 | # requests for sign in and sign up, you need to get a new CSRF token 105 | # from the server. You can disable this option at your own risk. 106 | # config.clean_up_csrf_token_on_authentication = true 107 | 108 | # When false, Devise will not attempt to reload routes on eager load. 109 | # This can reduce the time taken to boot the app but if your application 110 | # requires the Devise mappings to be loaded during boot time the application 111 | # won't boot properly. 112 | # config.reload_routes = true 113 | 114 | # ==> Configuration for :database_authenticatable 115 | # For bcrypt, this is the cost for hashing the password and defaults to 12. If 116 | # using other algorithms, it sets how many times you want the password to be hashed. 117 | # The number of stretches used for generating the hashed password are stored 118 | # with the hashed password. This allows you to change the stretches without 119 | # invalidating existing passwords. 120 | # 121 | # Limiting the stretches to just one in testing will increase the performance of 122 | # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use 123 | # a value less than 10 in other environments. Note that, for bcrypt (the default 124 | # algorithm), the cost increases exponentially with the number of stretches (e.g. 125 | # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). 126 | config.stretches = Rails.env.test? ? 1 : 12 127 | 128 | # Set up a pepper to generate the hashed password. 129 | # config.pepper = '341f115b30215a2ee95ca5ea8183fe135df3f2a859e3a7f5438272ea93eff080900c18bdb18999da0b0e256adc0b193d6de22c5da9f55227c8ab544ea6714860' 130 | 131 | # Send a notification to the original email when the user's email is changed. 132 | # config.send_email_changed_notification = false 133 | 134 | # Send a notification email when the user's password is changed. 135 | # config.send_password_change_notification = false 136 | 137 | # ==> Configuration for :confirmable 138 | # A period that the user is allowed to access the website even without 139 | # confirming their account. For instance, if set to 2.days, the user will be 140 | # able to access the website for two days without confirming their account, 141 | # access will be blocked just in the third day. 142 | # You can also set it to nil, which will allow the user to access the website 143 | # without confirming their account. 144 | # Default is 0.days, meaning the user cannot access the website without 145 | # confirming their account. 146 | # config.allow_unconfirmed_access_for = 2.days 147 | 148 | # A period that the user is allowed to confirm their account before their 149 | # token becomes invalid. For example, if set to 3.days, the user can confirm 150 | # their account within 3 days after the mail was sent, but on the fourth day 151 | # their account can't be confirmed with the token any more. 152 | # Default is nil, meaning there is no restriction on how long a user can take 153 | # before confirming their account. 154 | # config.confirm_within = 3.days 155 | 156 | # If true, requires any email changes to be confirmed (exactly the same way as 157 | # initial account confirmation) to be applied. Requires additional unconfirmed_email 158 | # db field (see migrations). Until confirmed, new email is stored in 159 | # unconfirmed_email column, and copied to email column on successful confirmation. 160 | config.reconfirmable = true 161 | 162 | # Defines which key will be used when confirming an account 163 | # config.confirmation_keys = [:email] 164 | 165 | # ==> Configuration for :rememberable 166 | # The time the user will be remembered without asking for credentials again. 167 | # config.remember_for = 2.weeks 168 | 169 | # Invalidates all the remember me tokens when the user signs out. 170 | config.expire_all_remember_me_on_sign_out = true 171 | 172 | # If true, extends the user's remember period when remembered via cookie. 173 | # config.extend_remember_period = false 174 | 175 | # Options to be passed to the created cookie. For instance, you can set 176 | # secure: true in order to force SSL only cookies. 177 | # config.rememberable_options = {} 178 | 179 | # ==> Configuration for :validatable 180 | # Range for password length. 181 | config.password_length = 6..128 182 | 183 | # Email regex used to validate email formats. It simply asserts that 184 | # one (and only one) @ exists in the given string. This is mainly 185 | # to give user feedback and not to assert the e-mail validity. 186 | config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ 187 | 188 | # ==> Configuration for :timeoutable 189 | # The time you want to timeout the user session without activity. After this 190 | # time the user will be asked for credentials again. Default is 30 minutes. 191 | # config.timeout_in = 30.minutes 192 | 193 | # ==> Configuration for :lockable 194 | # Defines which strategy will be used to lock an account. 195 | # :failed_attempts = Locks an account after a number of failed attempts to sign in. 196 | # :none = No lock strategy. You should handle locking by yourself. 197 | # config.lock_strategy = :failed_attempts 198 | 199 | # Defines which key will be used when locking and unlocking an account 200 | # config.unlock_keys = [:email] 201 | 202 | # Defines which strategy will be used to unlock an account. 203 | # :email = Sends an unlock link to the user email 204 | # :time = Re-enables login after a certain amount of time (see :unlock_in below) 205 | # :both = Enables both strategies 206 | # :none = No unlock strategy. You should handle unlocking by yourself. 207 | # config.unlock_strategy = :both 208 | 209 | # Number of authentication tries before locking an account if lock_strategy 210 | # is failed attempts. 211 | # config.maximum_attempts = 20 212 | 213 | # Time interval to unlock the account if :time is enabled as unlock_strategy. 214 | # config.unlock_in = 1.hour 215 | 216 | # Warn on the last attempt before the account is locked. 217 | # config.last_attempt_warning = true 218 | 219 | # ==> Configuration for :recoverable 220 | # 221 | # Defines which key will be used when recovering the password for an account 222 | # config.reset_password_keys = [:email] 223 | 224 | # Time interval you can reset your password with a reset password key. 225 | # Don't put a too small interval or your users won't have the time to 226 | # change their passwords. 227 | config.reset_password_within = 6.hours 228 | 229 | # When set to false, does not sign a user in automatically after their password is 230 | # reset. Defaults to true, so a user is signed in automatically after a reset. 231 | # config.sign_in_after_reset_password = true 232 | 233 | # ==> Configuration for :encryptable 234 | # Allow you to use another hashing or encryption algorithm besides bcrypt (default). 235 | # You can use :sha1, :sha512 or algorithms from others authentication tools as 236 | # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 237 | # for default behavior) and :restful_authentication_sha1 (then you should set 238 | # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). 239 | # 240 | # Require the `devise-encryptable` gem when using anything other than bcrypt 241 | # config.encryptor = :sha512 242 | 243 | # ==> Scopes configuration 244 | # Turn scoped views on. Before rendering "sessions/new", it will first check for 245 | # "users/sessions/new". It's turned off by default because it's slower if you 246 | # are using only default views. 247 | # config.scoped_views = false 248 | 249 | # Configure the default scope given to Warden. By default it's the first 250 | # devise role declared in your routes (usually :user). 251 | # config.default_scope = :user 252 | 253 | # Set this configuration to false if you want /users/sign_out to sign out 254 | # only the current scope. By default, Devise signs out all scopes. 255 | # config.sign_out_all_scopes = true 256 | 257 | # ==> Navigation configuration 258 | # Lists the formats that should be treated as navigational. Formats like 259 | # :html, should redirect to the sign in page when the user does not have 260 | # access, but formats like :xml or :json, should return 401. 261 | # 262 | # If you have any extra navigational formats, like :iphone or :mobile, you 263 | # should add them to the navigational formats lists. 264 | # 265 | # The "*/*" below is required to match Internet Explorer requests. 266 | # config.navigational_formats = ['*/*', :html] 267 | 268 | # The default HTTP method used to sign out a resource. Default is :delete. 269 | config.sign_out_via = :delete 270 | 271 | # ==> OmniAuth 272 | # Add a new OmniAuth provider. Check the wiki for more information on setting 273 | # up on your models and hooks. 274 | # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' 275 | 276 | # ==> Warden configuration 277 | # If you want to use other strategies, that are not supported by Devise, or 278 | # change the failure app, you can configure them inside the config.warden block. 279 | # 280 | # config.warden do |manager| 281 | # manager.intercept_401 = false 282 | # manager.default_strategies(scope: :user).unshift :some_external_strategy 283 | # end 284 | 285 | # ==> Mountable engine configurations 286 | # When using Devise inside an engine, let's call it `MyEngine`, and this engine 287 | # is mountable, there are some extra configurations to be taken into account. 288 | # The following options are available, assuming the engine is mounted as: 289 | # 290 | # mount MyEngine, at: '/my_engine' 291 | # 292 | # The router that invoked `devise_for`, in the example above, would be: 293 | # config.router_name = :my_engine 294 | # 295 | # When using OmniAuth, Devise cannot automatically set OmniAuth path, 296 | # so you need to do it manually. For the users scope, it would be: 297 | # config.omniauth_path_prefix = '/my_engine/users/auth' 298 | 299 | # ==> Turbolinks configuration 300 | # If your app is using Turbolinks, Turbolinks::Controller needs to be included to make redirection work correctly: 301 | # 302 | # ActiveSupport.on_load(:devise_failure_app) do 303 | # include Turbolinks::Controller 304 | # end 305 | 306 | # ==> Configuration for :registerable 307 | 308 | # When set to false, does not sign a user in automatically after their password is 309 | # changed. Defaults to true, so a user is signed in automatically after changing a password. 310 | # config.sign_in_after_change_password = true 311 | end 312 | -------------------------------------------------------------------------------- /yarn.lock: -------------------------------------------------------------------------------- 1 | # THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. 2 | # yarn lockfile v1 3 | 4 | 5 | "@hotwired/stimulus@^3.0.1": 6 | version "3.0.1" 7 | resolved "https://registry.yarnpkg.com/@hotwired/stimulus/-/stimulus-3.0.1.tgz#141f15645acaa3b133b7c247cad58ae252ffae85" 8 | integrity sha512-oHsJhgY2cip+K2ED7vKUNd2P+BEswVhrCYcJ802DSsblJFv7mPFVk3cQKvm2vHgHeDVdnj7oOKrBbzp1u8D+KA== 9 | 10 | "@hotwired/turbo-rails@^7.1.3": 11 | version "7.1.3" 12 | resolved "https://registry.yarnpkg.com/@hotwired/turbo-rails/-/turbo-rails-7.1.3.tgz#a4e04ecb800a06e7f9aa6e298170fa4580b74216" 13 | integrity sha512-6qKgn75bMWKx0bJgmSfrdC73EJkGLoSWZPAssvcd3nE7ZpDZff6f67j5OQNjjpRgNB7OFruom6VWguGQGu1fQg== 14 | dependencies: 15 | "@hotwired/turbo" "^7.1.0" 16 | "@rails/actioncable" "^7.0" 17 | 18 | "@hotwired/turbo@^7.1.0": 19 | version "7.1.0" 20 | resolved "https://registry.yarnpkg.com/@hotwired/turbo/-/turbo-7.1.0.tgz#27e44e0e3dc5bd1d4bda0766d579cf5a14091cd7" 21 | integrity sha512-Q8kGjqwPqER+CtpQudbH+3Zgs2X4zb6pBAlr6NsKTXadg45pAOvxI9i4QpuHbwSzR2+x87HUm+rot9F/Pe8rxA== 22 | 23 | "@nodelib/fs.scandir@2.1.5": 24 | version "2.1.5" 25 | resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" 26 | integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== 27 | dependencies: 28 | "@nodelib/fs.stat" "2.0.5" 29 | run-parallel "^1.1.9" 30 | 31 | "@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": 32 | version "2.0.5" 33 | resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" 34 | integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== 35 | 36 | "@nodelib/fs.walk@^1.2.3": 37 | version "1.2.8" 38 | resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" 39 | integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== 40 | dependencies: 41 | "@nodelib/fs.scandir" "2.1.5" 42 | fastq "^1.6.0" 43 | 44 | "@rails/actioncable@^7.0": 45 | version "7.0.3" 46 | resolved "https://registry.yarnpkg.com/@rails/actioncable/-/actioncable-7.0.3.tgz#71f08e958883af64f6a20489318b5e95d2c6dc5b" 47 | integrity sha512-Iefl21FZD+ck1di6xSHMYzSzRiNJTHV4NrAzCfDfqc/wPz4xncrP8f2/fJ+2jzwKIaDn76UVMsALh7R5OzsF8Q== 48 | 49 | "@rails/actiontext@^6.0.5": 50 | version "6.0.5" 51 | resolved "https://registry.yarnpkg.com/@rails/actiontext/-/actiontext-6.0.5.tgz#a2ef2d5d829fd032e294e8465ace4107a2b34db5" 52 | integrity sha512-FF0vCANuJu5I0e1D73zz/aOE33KKXvOpkus7wRQoZq3Yv4H6D6FiOEMn2TF1mO8u5IpPoSbttXaJ4ROj3aWEwA== 53 | dependencies: 54 | "@rails/activestorage" "^6.0.0" 55 | 56 | "@rails/activestorage@^6.0.0": 57 | version "6.0.5" 58 | resolved "https://registry.yarnpkg.com/@rails/activestorage/-/activestorage-6.0.5.tgz#446e4b3f45bf1d4e1c6fd0b8b6e2566cb70c2b18" 59 | integrity sha512-jp2UKh1QChjOGsApYO3HHuBH1yfv4mqOcKuB70fsT79O/CJWEACVnjKIdaqO+B9BJTKihaxyyepvL7kLfUFiDw== 60 | dependencies: 61 | spark-md5 "^3.0.0" 62 | 63 | "@tailwindcss/aspect-ratio@^0.4.0": 64 | version "0.4.0" 65 | resolved "https://registry.yarnpkg.com/@tailwindcss/aspect-ratio/-/aspect-ratio-0.4.0.tgz#c635dd7331cbcc1b111cebdc2647dd3493ebdd3e" 66 | integrity sha512-WJu0I4PpqNPuutpaA9zDUq2JXR+lorZ7PbLcKNLmb6GL9/HLfC7w3CRsMhJF4BbYd/lkY6CfXOvkYpuGnZfkpQ== 67 | 68 | "@tailwindcss/forms@^0.5.2": 69 | version "0.5.2" 70 | resolved "https://registry.yarnpkg.com/@tailwindcss/forms/-/forms-0.5.2.tgz#4ef45f9916dcb37838cbe7fecdcc4ba7a7c2ab59" 71 | integrity sha512-pSrFeJB6Bg1Mrg9CdQW3+hqZXAKsBrSG9MAfFLKy1pVA4Mb4W7C0k7mEhlmS2Dfo/otxrQOET7NJiJ9RrS563w== 72 | dependencies: 73 | mini-svg-data-uri "^1.2.3" 74 | 75 | "@tailwindcss/line-clamp@^0.4.0": 76 | version "0.4.0" 77 | resolved "https://registry.yarnpkg.com/@tailwindcss/line-clamp/-/line-clamp-0.4.0.tgz#03353e31e77636b785f2336e8c978502cec1de81" 78 | integrity sha512-HQZo6gfx1D0+DU3nWlNLD5iA6Ef4JAXh0LeD8lOGrJwEDBwwJNKQza6WoXhhY1uQrxOuU8ROxV7CqiQV4CoiLw== 79 | 80 | "@tailwindcss/typography@^0.5.2": 81 | version "0.5.2" 82 | resolved "https://registry.yarnpkg.com/@tailwindcss/typography/-/typography-0.5.2.tgz#24b069dab24d7a2467d01aca0dd432cb4b29f0ee" 83 | integrity sha512-coq8DBABRPFcVhVIk6IbKyyHUt7YTEC/C992tatFB+yEx5WGBQrCgsSFjxHUr8AWXphWckadVJbominEduYBqw== 84 | dependencies: 85 | lodash.castarray "^4.4.0" 86 | lodash.isplainobject "^4.0.6" 87 | lodash.merge "^4.6.2" 88 | 89 | acorn-node@^1.8.2: 90 | version "1.8.2" 91 | resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" 92 | integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== 93 | dependencies: 94 | acorn "^7.0.0" 95 | acorn-walk "^7.0.0" 96 | xtend "^4.0.2" 97 | 98 | acorn-walk@^7.0.0: 99 | version "7.2.0" 100 | resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" 101 | integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== 102 | 103 | acorn@^7.0.0: 104 | version "7.4.1" 105 | resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" 106 | integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== 107 | 108 | anymatch@~3.1.2: 109 | version "3.1.2" 110 | resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" 111 | integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== 112 | dependencies: 113 | normalize-path "^3.0.0" 114 | picomatch "^2.0.4" 115 | 116 | arg@^5.0.2: 117 | version "5.0.2" 118 | resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" 119 | integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== 120 | 121 | autoprefixer@^10.4.7: 122 | version "10.4.7" 123 | resolved "https://registry.yarnpkg.com/autoprefixer/-/autoprefixer-10.4.7.tgz#1db8d195f41a52ca5069b7593be167618edbbedf" 124 | integrity sha512-ypHju4Y2Oav95SipEcCcI5J7CGPuvz8oat7sUtYj3ClK44bldfvtvcxK6IEK++7rqB7YchDGzweZIBG+SD0ZAA== 125 | dependencies: 126 | browserslist "^4.20.3" 127 | caniuse-lite "^1.0.30001335" 128 | fraction.js "^4.2.0" 129 | normalize-range "^0.1.2" 130 | picocolors "^1.0.0" 131 | postcss-value-parser "^4.2.0" 132 | 133 | binary-extensions@^2.0.0: 134 | version "2.2.0" 135 | resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" 136 | integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== 137 | 138 | braces@^3.0.2, braces@~3.0.2: 139 | version "3.0.2" 140 | resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" 141 | integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== 142 | dependencies: 143 | fill-range "^7.0.1" 144 | 145 | browserslist@^4.20.3: 146 | version "4.20.4" 147 | resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.20.4.tgz#98096c9042af689ee1e0271333dbc564b8ce4477" 148 | integrity sha512-ok1d+1WpnU24XYN7oC3QWgTyMhY/avPJ/r9T00xxvUOIparA/gc+UPUMaod3i+G6s+nI2nUb9xZ5k794uIwShw== 149 | dependencies: 150 | caniuse-lite "^1.0.30001349" 151 | electron-to-chromium "^1.4.147" 152 | escalade "^3.1.1" 153 | node-releases "^2.0.5" 154 | picocolors "^1.0.0" 155 | 156 | camelcase-css@^2.0.1: 157 | version "2.0.1" 158 | resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" 159 | integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== 160 | 161 | caniuse-lite@^1.0.30001335, caniuse-lite@^1.0.30001349: 162 | version "1.0.30001356" 163 | resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001356.tgz#cbf5fe7b33f90962bfbca532212ea478d4ec9de8" 164 | integrity sha512-/30854bktMLhxtjieIxsrJBfs2gTM1pel6MXKF3K+RdIVJZcsn2A2QdhsuR4/p9+R204fZw0zCBBhktX8xWuyQ== 165 | 166 | chokidar@^3.5.3: 167 | version "3.5.3" 168 | resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" 169 | integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== 170 | dependencies: 171 | anymatch "~3.1.2" 172 | braces "~3.0.2" 173 | glob-parent "~5.1.2" 174 | is-binary-path "~2.1.0" 175 | is-glob "~4.0.1" 176 | normalize-path "~3.0.0" 177 | readdirp "~3.6.0" 178 | optionalDependencies: 179 | fsevents "~2.3.2" 180 | 181 | color-name@^1.1.4: 182 | version "1.1.4" 183 | resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" 184 | integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== 185 | 186 | cssesc@^3.0.0: 187 | version "3.0.0" 188 | resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" 189 | integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== 190 | 191 | defined@^1.0.0: 192 | version "1.0.0" 193 | resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" 194 | integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ== 195 | 196 | detective@^5.2.1: 197 | version "5.2.1" 198 | resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" 199 | integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== 200 | dependencies: 201 | acorn-node "^1.8.2" 202 | defined "^1.0.0" 203 | minimist "^1.2.6" 204 | 205 | didyoumean@^1.2.2: 206 | version "1.2.2" 207 | resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" 208 | integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== 209 | 210 | dlv@^1.1.3: 211 | version "1.1.3" 212 | resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" 213 | integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== 214 | 215 | electron-to-chromium@^1.4.147: 216 | version "1.4.161" 217 | resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.161.tgz#49cb5b35385bfee6cc439d0a04fbba7a7a7f08a1" 218 | integrity sha512-sTjBRhqh6wFodzZtc5Iu8/R95OkwaPNn7tj/TaDU5nu/5EFiQDtADGAXdR4tJcTEHlYfJpHqigzJqHvPgehP8A== 219 | 220 | esbuild-android-64@0.14.46: 221 | version "0.14.46" 222 | resolved "https://registry.yarnpkg.com/esbuild-android-64/-/esbuild-android-64-0.14.46.tgz#98d019853ca7b526d0d645bb4618fda425b74d35" 223 | integrity sha512-ZyJqwAcjNbZprs0ZAxnUAOhEhdE5kTKwz+CZuLmZYNLAPyRgBtaC8pT2PCuPifNvV8Cl3yLlrQPaOCjovoyb5g== 224 | 225 | esbuild-android-arm64@0.14.46: 226 | version "0.14.46" 227 | resolved "https://registry.yarnpkg.com/esbuild-android-arm64/-/esbuild-android-arm64-0.14.46.tgz#19835d5265b57120c14fba56a19fc317ca4bbda0" 228 | integrity sha512-BKcnUksvCijO9ONv6b4SikZE/OZftwJvX91XROODZGQmuwGVg97jmLDVu3lxuHdFlMNNzxh8taJ2mbCWZzH/Iw== 229 | 230 | esbuild-darwin-64@0.14.46: 231 | version "0.14.46" 232 | resolved "https://registry.yarnpkg.com/esbuild-darwin-64/-/esbuild-darwin-64-0.14.46.tgz#2819465ab92f6df407e6462d9e56f6563a043a44" 233 | integrity sha512-/ss2kO92sUJ9/1nHnMb3+oab8w6dyqKrMtPMvSYJ9KZIYGAZxz/WYxfFprY7Xk+ZxWnnlASSyZlG+If1nVmFYg== 234 | 235 | esbuild-darwin-arm64@0.14.46: 236 | version "0.14.46" 237 | resolved "https://registry.yarnpkg.com/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.46.tgz#2d523fa628930bba38a4f75cede75d6341bc3b5b" 238 | integrity sha512-WX0JOaEFf6t+rIjXO6THsf/0fhQAt2Zb0/PSYlvXnuQQAmOmFAfPsuRNocp5ME0NGaUqZd4FxqqmLEVK3RzPAg== 239 | 240 | esbuild-freebsd-64@0.14.46: 241 | version "0.14.46" 242 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.46.tgz#dbfbbb1cb943149aaa83b9e767ded6f14ad42ba5" 243 | integrity sha512-o+ozPFuHRCAGCVWU2bLurOUgVkT0jcPEu082VBUY2Q/yLf+B+/3nXzh4Fjp5O21tOvJRTn7hUVydG9j5+vYE6A== 244 | 245 | esbuild-freebsd-arm64@0.14.46: 246 | version "0.14.46" 247 | resolved "https://registry.yarnpkg.com/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.46.tgz#fb066d6e7de2bd96138dd1c2fba5e8ce5233ed5a" 248 | integrity sha512-9zicZ0X43WDKz3sjNfcqYO38xbfJpSWYXB+FxvYYkmBwGA52K0SAu4oKuTTLi8od8X2IIo1x5C5TUNvKDSVJww== 249 | 250 | esbuild-linux-32@0.14.46: 251 | version "0.14.46" 252 | resolved "https://registry.yarnpkg.com/esbuild-linux-32/-/esbuild-linux-32-0.14.46.tgz#ea2464b10fe188ee7c9be81a2757e2d69ffdb8c1" 253 | integrity sha512-ZnTpZMVb0VGvL99R5eh4OrJwbUyvpM6M88VAMuHP4LvFjuvZrhgefjKqEGuWZZW7JRnAjKqjXLjWdhdSjwMFnQ== 254 | 255 | esbuild-linux-64@0.14.46: 256 | version "0.14.46" 257 | resolved "https://registry.yarnpkg.com/esbuild-linux-64/-/esbuild-linux-64-0.14.46.tgz#f4f2d181af6ea78137311712fca423f8fa954314" 258 | integrity sha512-ECCRRZtX6l4ubeVhHhiVoK/uYAkvzNqfmR4gP4N/9H9RPu+b8YCcN4bQGp7xCuYIV6Xd41WpOMyO+xpcQvjtQQ== 259 | 260 | esbuild-linux-arm64@0.14.46: 261 | version "0.14.46" 262 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.46.tgz#113d17c523dfe80c9d4981d05c58f5ada3470e30" 263 | integrity sha512-HX0TXCHyI0NEWG4jg8LlW1PbZQbnz+PUH56yjx996cgM5pC90u32drKs/tyJiyyQmNk9OXOogjKw7LEdp/Qc1w== 264 | 265 | esbuild-linux-arm@0.14.46: 266 | version "0.14.46" 267 | resolved "https://registry.yarnpkg.com/esbuild-linux-arm/-/esbuild-linux-arm-0.14.46.tgz#1b6ba77b0301572b2fed35fe922b1613b10b5c7e" 268 | integrity sha512-RvTJEi4vj13c5FP9YPp+8Y6x6HK1E7uSqfy3y9UoeaNAzNZWA7fN1U3hQjTL/dy5zTJH5KE64mrt5k5+he+CQA== 269 | 270 | esbuild-linux-mips64le@0.14.46: 271 | version "0.14.46" 272 | resolved "https://registry.yarnpkg.com/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.46.tgz#8775e12510eaf988a9bc5e6787cf0c87c3eb40d6" 273 | integrity sha512-jnb2NDwGqJUVmxn1v0f7seNdDm0nRNWHP9Z3MrWAGnBCdnnDlsjqRFDnbKoaQvWONEa+rOOr/giK+VL0hgQExA== 274 | 275 | esbuild-linux-ppc64le@0.14.46: 276 | version "0.14.46" 277 | resolved "https://registry.yarnpkg.com/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.46.tgz#da003df59859b02e160827e26bc1e107bec23d07" 278 | integrity sha512-uu3JTQUrwwauKY9z8yq5MnDyOlT3f2DNOzBcYz4dB78HqwEqilCsifoBGd0WcbED5n57dc59X+LZMTZ8Ose44w== 279 | 280 | esbuild-linux-riscv64@0.14.46: 281 | version "0.14.46" 282 | resolved "https://registry.yarnpkg.com/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.46.tgz#b6c4faf9f8482e2a898ec2becac4a6789ae3ff22" 283 | integrity sha512-OB29r1EG44ZY34JnXCRERxo7k4pRKoQdaoRg2HIeCavatsXZwW4LCakpLnMQ72vXT1HtpBUABEjHkKkn5JyrUg== 284 | 285 | esbuild-linux-s390x@0.14.46: 286 | version "0.14.46" 287 | resolved "https://registry.yarnpkg.com/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.46.tgz#d30d0f7ee8466c4ec99ac2c689b70514320406b2" 288 | integrity sha512-XQ/U9TueMSGYyPTKyZsJVraiuvxhwCDIMn/QwFXCRCJ6H/Cy/Rq33u9qhpeSziinHKfzJROGx5A8mQY6aYamdQ== 289 | 290 | esbuild-netbsd-64@0.14.46: 291 | version "0.14.46" 292 | resolved "https://registry.yarnpkg.com/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.46.tgz#fcf2b739b63731b5b264dc584240c0b61dbbf035" 293 | integrity sha512-i15BwqHaAIFp1vBJkitAbHtwXcLk9TdHs/Ia1xGIAutQYXSJNPLM3Z4B4hyfHNEFl2yBqBIYpglMohv2ClNdOQ== 294 | 295 | esbuild-openbsd-64@0.14.46: 296 | version "0.14.46" 297 | resolved "https://registry.yarnpkg.com/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.46.tgz#fc7880751b78b325216e66470b9a3df7b1c21cbf" 298 | integrity sha512-XwOIFCE140Y/PvjrwjFfa/QLWBuvhR1mPCOa35mKx02jt++wPNgf0qhn6HfdVC3vQe7R46RwTp4q2cp99fepEg== 299 | 300 | esbuild-sunos-64@0.14.46: 301 | version "0.14.46" 302 | resolved "https://registry.yarnpkg.com/esbuild-sunos-64/-/esbuild-sunos-64-0.14.46.tgz#ab73b91e79ae53dddb035da9bb69ba7dd44d36ee" 303 | integrity sha512-+kV3JnmfdxBVpHyFvuGXWtu6tXxXApOLPkSrVkMJf6+ns/3PLtPndpzwCzHjD+qYUEk8ln4MA+ufQ2qmjW5mZg== 304 | 305 | esbuild-windows-32@0.14.46: 306 | version "0.14.46" 307 | resolved "https://registry.yarnpkg.com/esbuild-windows-32/-/esbuild-windows-32-0.14.46.tgz#88ad388c896325d273e92dbf28f146d1d34d4351" 308 | integrity sha512-gzGC1Q11B/Bo5A2EX4N22oigWmhL7Z0eDyc8kbSoJjqSrGQuRE7B0uMpluO+q0O/gZ1S3zdw+M4PCWlqOIeXLA== 309 | 310 | esbuild-windows-64@0.14.46: 311 | version "0.14.46" 312 | resolved "https://registry.yarnpkg.com/esbuild-windows-64/-/esbuild-windows-64-0.14.46.tgz#bfb21b68d54d0db86190f8073cbf66a86bd0de14" 313 | integrity sha512-Do2daaskfOjmCB7o3ygz6fD3K6SPjZLERiZLktzHz2oUCwsebKu/gmop0+j/XdrVIXC32wFzHzDS+9CTu9OShw== 314 | 315 | esbuild-windows-arm64@0.14.46: 316 | version "0.14.46" 317 | resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.46.tgz#b8aa66a0c347342b9ae780b537d5574e9691c123" 318 | integrity sha512-VEzMy6bM60/HT/URTDElyhfi2Pk0quCCrEhRlI4MRno/AIqYUGw0rZwkPl6PeoqVI6BgoBHGY576GWTiPmshCA== 319 | 320 | esbuild@^0.14.46: 321 | version "0.14.46" 322 | resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.46.tgz#d548cfc13fecfd4bc074ce38e0122d1dd9b067db" 323 | integrity sha512-vdm5G1JdZBktva8dwQci/s44VbeBUg8g907xoZx77mqFZ4gU5GlMULNsdGeID+qXCXocsfYSGtE0LvqH3eiNQg== 324 | optionalDependencies: 325 | esbuild-android-64 "0.14.46" 326 | esbuild-android-arm64 "0.14.46" 327 | esbuild-darwin-64 "0.14.46" 328 | esbuild-darwin-arm64 "0.14.46" 329 | esbuild-freebsd-64 "0.14.46" 330 | esbuild-freebsd-arm64 "0.14.46" 331 | esbuild-linux-32 "0.14.46" 332 | esbuild-linux-64 "0.14.46" 333 | esbuild-linux-arm "0.14.46" 334 | esbuild-linux-arm64 "0.14.46" 335 | esbuild-linux-mips64le "0.14.46" 336 | esbuild-linux-ppc64le "0.14.46" 337 | esbuild-linux-riscv64 "0.14.46" 338 | esbuild-linux-s390x "0.14.46" 339 | esbuild-netbsd-64 "0.14.46" 340 | esbuild-openbsd-64 "0.14.46" 341 | esbuild-sunos-64 "0.14.46" 342 | esbuild-windows-32 "0.14.46" 343 | esbuild-windows-64 "0.14.46" 344 | esbuild-windows-arm64 "0.14.46" 345 | 346 | escalade@^3.1.1: 347 | version "3.1.1" 348 | resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" 349 | integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== 350 | 351 | fast-glob@^3.2.11: 352 | version "3.2.11" 353 | resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" 354 | integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== 355 | dependencies: 356 | "@nodelib/fs.stat" "^2.0.2" 357 | "@nodelib/fs.walk" "^1.2.3" 358 | glob-parent "^5.1.2" 359 | merge2 "^1.3.0" 360 | micromatch "^4.0.4" 361 | 362 | fastq@^1.6.0: 363 | version "1.13.0" 364 | resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" 365 | integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== 366 | dependencies: 367 | reusify "^1.0.4" 368 | 369 | fill-range@^7.0.1: 370 | version "7.0.1" 371 | resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" 372 | integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== 373 | dependencies: 374 | to-regex-range "^5.0.1" 375 | 376 | fraction.js@^4.2.0: 377 | version "4.2.0" 378 | resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950" 379 | integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA== 380 | 381 | fsevents@~2.3.2: 382 | version "2.3.2" 383 | resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" 384 | integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== 385 | 386 | function-bind@^1.1.1: 387 | version "1.1.1" 388 | resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" 389 | integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== 390 | 391 | glob-parent@^5.1.2, glob-parent@~5.1.2: 392 | version "5.1.2" 393 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" 394 | integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== 395 | dependencies: 396 | is-glob "^4.0.1" 397 | 398 | glob-parent@^6.0.2: 399 | version "6.0.2" 400 | resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" 401 | integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== 402 | dependencies: 403 | is-glob "^4.0.3" 404 | 405 | has@^1.0.3: 406 | version "1.0.3" 407 | resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" 408 | integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== 409 | dependencies: 410 | function-bind "^1.1.1" 411 | 412 | is-binary-path@~2.1.0: 413 | version "2.1.0" 414 | resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" 415 | integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== 416 | dependencies: 417 | binary-extensions "^2.0.0" 418 | 419 | is-core-module@^2.9.0: 420 | version "2.9.0" 421 | resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" 422 | integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== 423 | dependencies: 424 | has "^1.0.3" 425 | 426 | is-extglob@^2.1.1: 427 | version "2.1.1" 428 | resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" 429 | integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== 430 | 431 | is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: 432 | version "4.0.3" 433 | resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" 434 | integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== 435 | dependencies: 436 | is-extglob "^2.1.1" 437 | 438 | is-number@^7.0.0: 439 | version "7.0.0" 440 | resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" 441 | integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== 442 | 443 | lilconfig@^2.0.5: 444 | version "2.0.5" 445 | resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.5.tgz#19e57fd06ccc3848fd1891655b5a447092225b25" 446 | integrity sha512-xaYmXZtTHPAw5m+xLN8ab9C+3a8YmV3asNSPOATITbtwrfbwaLJj8h66H1WMIpALCkqsIzK3h7oQ+PdX+LQ9Eg== 447 | 448 | lodash.castarray@^4.4.0: 449 | version "4.4.0" 450 | resolved "https://registry.yarnpkg.com/lodash.castarray/-/lodash.castarray-4.4.0.tgz#c02513515e309daddd4c24c60cfddcf5976d9115" 451 | integrity sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q== 452 | 453 | lodash.isplainobject@^4.0.6: 454 | version "4.0.6" 455 | resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" 456 | integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== 457 | 458 | lodash.merge@^4.6.2: 459 | version "4.6.2" 460 | resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" 461 | integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== 462 | 463 | merge2@^1.3.0: 464 | version "1.4.1" 465 | resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" 466 | integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== 467 | 468 | micromatch@^4.0.4: 469 | version "4.0.5" 470 | resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" 471 | integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== 472 | dependencies: 473 | braces "^3.0.2" 474 | picomatch "^2.3.1" 475 | 476 | mini-svg-data-uri@^1.2.3: 477 | version "1.4.4" 478 | resolved "https://registry.yarnpkg.com/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz#8ab0aabcdf8c29ad5693ca595af19dd2ead09939" 479 | integrity sha512-r9deDe9p5FJUPZAk3A59wGH7Ii9YrjjWw0jmw/liSbHl2CHiyXj6FcDXDu2K3TjVAXqiJdaw3xxwlZZr9E6nHg== 480 | 481 | minimist@^1.2.6: 482 | version "1.2.6" 483 | resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" 484 | integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== 485 | 486 | nanoid@^3.3.4: 487 | version "3.3.4" 488 | resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" 489 | integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== 490 | 491 | node-releases@^2.0.5: 492 | version "2.0.5" 493 | resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.5.tgz#280ed5bc3eba0d96ce44897d8aee478bfb3d9666" 494 | integrity sha512-U9h1NLROZTq9uE1SNffn6WuPDg8icmi3ns4rEl/oTfIle4iLjTliCzgTsbaIFMq/Xn078/lfY/BL0GWZ+psK4Q== 495 | 496 | normalize-path@^3.0.0, normalize-path@~3.0.0: 497 | version "3.0.0" 498 | resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" 499 | integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== 500 | 501 | normalize-range@^0.1.2: 502 | version "0.1.2" 503 | resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" 504 | integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA== 505 | 506 | object-hash@^3.0.0: 507 | version "3.0.0" 508 | resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" 509 | integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== 510 | 511 | path-parse@^1.0.7: 512 | version "1.0.7" 513 | resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" 514 | integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== 515 | 516 | picocolors@^1.0.0: 517 | version "1.0.0" 518 | resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" 519 | integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== 520 | 521 | picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: 522 | version "2.3.1" 523 | resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" 524 | integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== 525 | 526 | pify@^2.3.0: 527 | version "2.3.0" 528 | resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" 529 | integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== 530 | 531 | postcss-import@^14.1.0: 532 | version "14.1.0" 533 | resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" 534 | integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw== 535 | dependencies: 536 | postcss-value-parser "^4.0.0" 537 | read-cache "^1.0.0" 538 | resolve "^1.1.7" 539 | 540 | postcss-js@^4.0.0: 541 | version "4.0.0" 542 | resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" 543 | integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== 544 | dependencies: 545 | camelcase-css "^2.0.1" 546 | 547 | postcss-load-config@^3.1.4: 548 | version "3.1.4" 549 | resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" 550 | integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== 551 | dependencies: 552 | lilconfig "^2.0.5" 553 | yaml "^1.10.2" 554 | 555 | postcss-nested@5.0.6: 556 | version "5.0.6" 557 | resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" 558 | integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== 559 | dependencies: 560 | postcss-selector-parser "^6.0.6" 561 | 562 | postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6: 563 | version "6.0.10" 564 | resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" 565 | integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== 566 | dependencies: 567 | cssesc "^3.0.0" 568 | util-deprecate "^1.0.2" 569 | 570 | postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: 571 | version "4.2.0" 572 | resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" 573 | integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== 574 | 575 | postcss@^8.4.14: 576 | version "8.4.14" 577 | resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" 578 | integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== 579 | dependencies: 580 | nanoid "^3.3.4" 581 | picocolors "^1.0.0" 582 | source-map-js "^1.0.2" 583 | 584 | queue-microtask@^1.2.2: 585 | version "1.2.3" 586 | resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" 587 | integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== 588 | 589 | quick-lru@^5.1.1: 590 | version "5.1.1" 591 | resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" 592 | integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== 593 | 594 | read-cache@^1.0.0: 595 | version "1.0.0" 596 | resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" 597 | integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== 598 | dependencies: 599 | pify "^2.3.0" 600 | 601 | readdirp@~3.6.0: 602 | version "3.6.0" 603 | resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" 604 | integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== 605 | dependencies: 606 | picomatch "^2.2.1" 607 | 608 | resolve@^1.1.7, resolve@^1.22.0: 609 | version "1.22.1" 610 | resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" 611 | integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== 612 | dependencies: 613 | is-core-module "^2.9.0" 614 | path-parse "^1.0.7" 615 | supports-preserve-symlinks-flag "^1.0.0" 616 | 617 | reusify@^1.0.4: 618 | version "1.0.4" 619 | resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" 620 | integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== 621 | 622 | run-parallel@^1.1.9: 623 | version "1.2.0" 624 | resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" 625 | integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== 626 | dependencies: 627 | queue-microtask "^1.2.2" 628 | 629 | source-map-js@^1.0.2: 630 | version "1.0.2" 631 | resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" 632 | integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== 633 | 634 | spark-md5@^3.0.0: 635 | version "3.0.2" 636 | resolved "https://registry.yarnpkg.com/spark-md5/-/spark-md5-3.0.2.tgz#7952c4a30784347abcee73268e473b9c0167e3fc" 637 | integrity sha512-wcFzz9cDfbuqe0FZzfi2or1sgyIrsDwmPwfZC4hiNidPdPINjeUwNfv5kldczoEAcjl9Y1L3SM7Uz2PUEQzxQw== 638 | 639 | supports-preserve-symlinks-flag@^1.0.0: 640 | version "1.0.0" 641 | resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" 642 | integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== 643 | 644 | tailwindcss@^3.1.3: 645 | version "3.1.3" 646 | resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.1.3.tgz#b9ef2c1ae537c339679e8e89635af8e143d1c7eb" 647 | integrity sha512-PRJNYdSIthrb8hjmAyymEyEN8Yo61TMXpzyFUpxULeeyRn3Y3gpvuw6FlRTKrJvK7thSGKRnhT36VovVx4WeMA== 648 | dependencies: 649 | arg "^5.0.2" 650 | chokidar "^3.5.3" 651 | color-name "^1.1.4" 652 | detective "^5.2.1" 653 | didyoumean "^1.2.2" 654 | dlv "^1.1.3" 655 | fast-glob "^3.2.11" 656 | glob-parent "^6.0.2" 657 | is-glob "^4.0.3" 658 | lilconfig "^2.0.5" 659 | normalize-path "^3.0.0" 660 | object-hash "^3.0.0" 661 | picocolors "^1.0.0" 662 | postcss "^8.4.14" 663 | postcss-import "^14.1.0" 664 | postcss-js "^4.0.0" 665 | postcss-load-config "^3.1.4" 666 | postcss-nested "5.0.6" 667 | postcss-selector-parser "^6.0.10" 668 | postcss-value-parser "^4.2.0" 669 | quick-lru "^5.1.1" 670 | resolve "^1.22.0" 671 | 672 | to-regex-range@^5.0.1: 673 | version "5.0.1" 674 | resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" 675 | integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== 676 | dependencies: 677 | is-number "^7.0.0" 678 | 679 | trix@^2.0.0-beta.0: 680 | version "2.0.0-beta.0" 681 | resolved "https://registry.yarnpkg.com/trix/-/trix-2.0.0-beta.0.tgz#e7034867182356c023abdef90c7bdc341bb3e105" 682 | integrity sha512-D1c7FxP1hGXM/MnTd/+fGUbSDLkfxsqjA0NxOTzByQywVJVNHDOkn8xAScCKVOiAN6hXlqUR2qX4CPJHTGj+cQ== 683 | 684 | util-deprecate@^1.0.2: 685 | version "1.0.2" 686 | resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" 687 | integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== 688 | 689 | xtend@^4.0.2: 690 | version "4.0.2" 691 | resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" 692 | integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== 693 | 694 | yaml@^1.10.2: 695 | version "1.10.2" 696 | resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" 697 | integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== 698 | --------------------------------------------------------------------------------