├── db ├── seeds.rb └── migrate │ ├── 20200603052105_tweets.rb │ ├── 20200515024136_add_nickname_to_users.rb │ ├── 20200521050256_add_column_from_users.rb │ ├── 20200521050717_create_rooms.rb │ ├── 20200524230028_a_ddd_admin_to_users.rb │ ├── 20200521050817_create_entries.rb │ ├── 20200519001917_create_likes.rb │ ├── 20200521050902_create_messages.rb │ ├── 20200515005529_create_tweets.rb │ ├── 20200515064321_create_comments.rb │ ├── 20200527224117_create_donations.rb │ ├── 20200520074806_create_relationships.rb │ ├── 20200523235326_add_missing_taggable_index.acts_as_taggable_on_engine.rb │ ├── 20200523235327_change_collation_for_tag_names.acts_as_taggable_on_engine.rb │ ├── 20200523015543_create_notifications.rb │ ├── 20200523235325_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb │ ├── 20200523235324_add_missing_unique_indices.acts_as_taggable_on_engine.rb │ ├── 20200523235323_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb │ ├── 20200523235328_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb │ └── 20200515005429_devise_create_users.rb ├── log └── .keep ├── storage └── .keep ├── tmp └── .keep ├── vendor ├── .keep └── assets │ ├── javascript │ └── .DS_Store │ └── stylesheets │ └── jquery.tagit.scss ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── .ruby-version ├── app ├── assets │ ├── images │ │ ├── .keep │ │ ├── avatar.png │ │ ├── default.png │ │ └── image.jpeg │ ├── javascripts │ │ ├── channels │ │ │ └── .keep │ │ ├── flash.js │ │ ├── header.js │ │ ├── cable.js │ │ ├── tweet-title.js │ │ ├── application.js │ │ ├── tagit.js │ │ ├── tweet_show.js │ │ ├── notification.js │ │ ├── preview.js │ │ └── tweet_form.js │ ├── stylesheets │ │ ├── mixin │ │ │ ├── _tweet-items.scss │ │ │ └── _media.scss │ │ ├── modules │ │ │ ├── _flash.scss │ │ │ ├── _user-index.scss │ │ │ ├── _session.scss │ │ │ ├── _tweet-tags.scss │ │ │ ├── _donation.scss │ │ │ ├── _new-btn.scss │ │ │ ├── _room-index.scss │ │ │ ├── _common.scss │ │ │ ├── _notifications.scss │ │ │ ├── _tweet-comment.scss │ │ │ ├── _tweet-form.scss │ │ │ ├── _room-show.scss │ │ │ └── _tweet-index.scss │ │ ├── config │ │ │ └── _colors.scss │ │ ├── application.scss │ │ └── _reset.scss │ └── config │ │ └── manifest.js ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ ├── entry.rb │ ├── message.rb │ ├── donation.rb │ ├── relationship.rb │ ├── notification.rb │ ├── like.rb │ ├── comment.rb │ ├── room.rb │ ├── user.rb │ └── tweet.rb ├── controllers │ ├── concerns │ │ └── .keep │ ├── api │ │ └── tweets_controller.rb │ ├── tweets │ │ └── searches_controller.rb │ ├── application_controller.rb │ ├── relationships_controller.rb │ ├── likes_controller.rb │ ├── donations_controller.rb │ ├── users │ │ └── sessions_controller.rb │ ├── notifications_controller.rb │ ├── rooms_controller.rb │ ├── comments_controller.rb │ ├── messages_controller.rb │ ├── tweets_controller.rb │ └── users_controller.rb ├── views │ ├── tweets │ │ ├── index.html.haml │ │ ├── likes.html.haml │ │ ├── new.html.haml │ │ ├── edit.html.haml │ │ ├── _tag_list.html.haml │ │ ├── _top.html.haml │ │ ├── searches │ │ │ └── index.html.haml │ │ ├── _new_btn.html.haml │ │ ├── tags.html.haml │ │ ├── _tweet.html.haml │ │ ├── _form.html.haml │ │ └── show.html.haml │ ├── users │ │ ├── timeline.html.haml │ │ ├── index.html.haml │ │ ├── followers.html.haml │ │ ├── following.html.haml │ │ ├── _user.html.haml │ │ ├── edit.html.haml │ │ ├── likes.html.haml │ │ ├── show.html.haml │ │ ├── _contact.html.haml │ │ ├── _info.html.haml │ │ └── _form.html.haml │ ├── api │ │ └── tweets │ │ │ └── preview.json.jbuilder │ ├── .DS_Store │ ├── comments │ │ ├── destroy.js.haml │ │ ├── update.js.haml │ │ ├── edit.js.haml │ │ ├── _top.html.haml │ │ ├── create.js.haml │ │ ├── _form.html.haml │ │ └── _comment.html.haml │ ├── messages │ │ ├── destroy.js.haml │ │ ├── update.js.haml │ │ ├── edit.js.haml │ │ └── create.js.haml │ ├── relationships │ │ ├── create.js.haml │ │ ├── destroy.js.haml │ │ └── _follow.html.haml │ ├── rooms │ │ ├── show.html.haml │ │ ├── index.html.haml │ │ ├── _chat_form.html.haml │ │ ├── _room.html.haml │ │ └── _chat_area.html.haml │ ├── layouts │ │ ├── _flash.html.haml │ │ ├── _circle.html.haml │ │ ├── application.html.haml │ │ └── _header.html.haml │ ├── likes │ │ ├── destroy.js.haml │ │ ├── create.js.haml │ │ └── _like.html.haml │ ├── kaminari │ │ ├── _gap.html.slim │ │ ├── _last_page.html.slim │ │ ├── _first_page.html.slim │ │ ├── _next_page.html.slim │ │ ├── _prev_page.html.slim │ │ ├── _page.html.slim │ │ └── _paginator.html.slim │ ├── devise │ │ ├── sessions │ │ │ ├── _testuser.html.haml │ │ │ └── new.html.haml │ │ ├── mailer │ │ │ ├── password_change.html.haml │ │ │ ├── confirmation_instructions.html.haml │ │ │ ├── unlock_instructions.html.haml │ │ │ ├── email_changed.html.haml │ │ │ └── reset_password_instructions.html.haml │ │ ├── shared │ │ │ ├── _error_messages.html.haml │ │ │ └── _links.html.haml │ │ ├── unlocks │ │ │ └── new.html.haml │ │ ├── passwords │ │ │ ├── new.html.haml │ │ │ └── edit.html.haml │ │ ├── confirmations │ │ │ └── new.html.haml │ │ └── registrations │ │ │ ├── new.html.haml │ │ │ └── edit.html.haml │ ├── donations │ │ ├── create.js.haml │ │ ├── _donation.html.haml │ │ └── _form.html.haml │ ├── notifications │ │ ├── index.html.haml │ │ └── _notification.html.haml │ └── activities │ │ └── _activity.html.haml ├── helpers │ ├── likes_helper.rb │ ├── users_helper.rb │ ├── activities_helper.rb │ ├── comments_helper.rb │ ├── donations_helper.rb │ ├── messages_helper.rb │ ├── relationships_helper.rb │ ├── tweets │ │ └── searches_helper.rb │ ├── tweets_helper.rb │ ├── notifications_helper.rb │ ├── applications_helper.rb │ ├── rooms_helper.rb │ ├── devise_helper.rb │ └── markdown_helper.rb ├── jobs │ └── application_job.rb ├── channels │ └── application_cable │ │ ├── channel.rb │ │ └── connection.rb ├── mailers │ └── application_mailer.rb └── uploaders │ ├── avatar_uploader.rb │ └── image_uploader.rb ├── public ├── apple-touch-icon.png ├── apple-touch-icon-precomposed.png ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── .rspec ├── spec ├── factories │ ├── room.rb │ ├── like.rb │ ├── donation.rb │ ├── comment.rb │ ├── relationship.rb │ ├── entry.rb │ ├── message.rb │ ├── tweet.rb │ ├── user.rb │ └── notification.rb ├── controllers │ ├── likes_controller_spec.rb │ ├── rooms_controller_spec.rb │ ├── users_controller_spec.rb │ ├── comments_controller_spec.rb │ ├── donations_controller_spec.rb │ ├── messages_controller_spec.rb │ ├── notifications_controller_spec.rb │ └── relationships_controller_spec.rb ├── support │ └── controller_macros.rb ├── models │ ├── room_spec.rb │ ├── donation_spec.rb │ ├── like_spec.rb │ ├── entry_spec.rb │ ├── comment_spec.rb │ ├── relationship_spec.rb │ ├── message_spec.rb │ └── notification_spec.rb └── features │ └── tweet_spec.rb ├── config ├── deploy │ ├── production.rb │ └── staging.rb ├── spring.rb ├── database.yml.ci ├── environment.rb ├── initializers │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── application_controller_renderer.rb │ ├── cookies_serializer.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── assets.rb │ ├── inflections.rb │ ├── carrierwave.rb │ └── content_security_policy.rb ├── boot.rb ├── cable.yml ├── puma.rb ├── credentials.yml.enc ├── locales │ └── en.yml ├── application.rb ├── storage.yml ├── routes.rb ├── database.yml ├── environments │ ├── test.rb │ └── development.rb ├── deploy.rb └── unicorn.rb ├── bin ├── bundle ├── rake ├── rails ├── yarn ├── spring ├── update └── setup ├── docker ├── mysql │ ├── charset.cnf │ ├── password.yml │ └── Dockerfile ├── rails │ ├── puma.rb │ └── Dockerfile └── nginx │ ├── Dockerfile │ ├── default.conf │ └── nginx.conf ├── package.json ├── config.ru ├── Rakefile ├── .rubocop_todo.yml ├── Capfile ├── .gitignore ├── docker-compose-dev.yml ├── docker-compose.yml ├── .rubocop.yml └── .circleci └── config.yml /db/seeds.rb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /storage/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 2.5.1 2 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/javascripts/channels/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/tweets/index.html.haml: -------------------------------------------------------------------------------- 1 | = render "tweets/top" -------------------------------------------------------------------------------- /app/views/tweets/likes.html.haml: -------------------------------------------------------------------------------- 1 | = render 'tweets/top' -------------------------------------------------------------------------------- /app/views/tweets/new.html.haml: -------------------------------------------------------------------------------- 1 | = render '/tweets/form' -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation -------------------------------------------------------------------------------- /app/helpers/likes_helper.rb: -------------------------------------------------------------------------------- 1 | module LikesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/views/users/timeline.html.haml: -------------------------------------------------------------------------------- 1 | = render 'tweets/top' -------------------------------------------------------------------------------- /app/views/api/tweets/preview.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.text @html 2 | -------------------------------------------------------------------------------- /app/views/tweets/edit.html.haml: -------------------------------------------------------------------------------- 1 | = render '/tweets/form' 2 | -------------------------------------------------------------------------------- /app/helpers/activities_helper.rb: -------------------------------------------------------------------------------- 1 | module ActivitiesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module CommentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/donations_helper.rb: -------------------------------------------------------------------------------- 1 | module DonationsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/messages_helper.rb: -------------------------------------------------------------------------------- 1 | module MessagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/relationships_helper.rb: -------------------------------------------------------------------------------- 1 | module RelationshipsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/jobs/application_job.rb: -------------------------------------------------------------------------------- 1 | class ApplicationJob < ActiveJob::Base 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/tweets/searches_helper.rb: -------------------------------------------------------------------------------- 1 | module Tweets::SearchesHelper 2 | end 3 | -------------------------------------------------------------------------------- /spec/factories/room.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :room do 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/mixin/_tweet-items.scss: -------------------------------------------------------------------------------- 1 | @mixin tweet-items{ 2 | width: 300px; 3 | } -------------------------------------------------------------------------------- /app/views/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/app/views/.DS_Store -------------------------------------------------------------------------------- /app/views/comments/destroy.js.haml: -------------------------------------------------------------------------------- 1 | $('#comment_area').html("#{ j(render 'comments/top') }"); -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /app/views/comments/update.js.haml: -------------------------------------------------------------------------------- 1 | $('#comment_area').html("#{ j(render 'comments/top') }"); 2 | -------------------------------------------------------------------------------- /app/views/messages/destroy.js.haml: -------------------------------------------------------------------------------- 1 | $("#chat_area").html("#{j(render("rooms/chat_area"))}"); 2 | -------------------------------------------------------------------------------- /app/views/messages/update.js.haml: -------------------------------------------------------------------------------- 1 | $("#chat_area").html("#{j(render("rooms/chat_area"))}"); 2 | -------------------------------------------------------------------------------- /app/helpers/tweets_helper.rb: -------------------------------------------------------------------------------- 1 | module TweetsHelper 2 | include ActsAsTaggableOn::TagsHelper 3 | end 4 | -------------------------------------------------------------------------------- /app/views/comments/edit.js.haml: -------------------------------------------------------------------------------- 1 | $('#comment_#{ @comment.id }').html("#{ j(render 'comments/form') }"); -------------------------------------------------------------------------------- /app/views/relationships/create.js.haml: -------------------------------------------------------------------------------- 1 | $("#follow_form").html("#{j(render("/relationships/follow"))}"); -------------------------------------------------------------------------------- /app/views/relationships/destroy.js.haml: -------------------------------------------------------------------------------- 1 | $("#follow_form").html("#{j(render("/relationships/follow"))}"); -------------------------------------------------------------------------------- /app/assets/images/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/app/assets/images/avatar.png -------------------------------------------------------------------------------- /app/assets/images/default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/app/assets/images/default.png -------------------------------------------------------------------------------- /app/assets/images/image.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/app/assets/images/image.jpeg -------------------------------------------------------------------------------- /app/views/users/index.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | %ul.users 3 | .users__title 4 | ユーザー一覧 5 | = render @users -------------------------------------------------------------------------------- /app/assets/javascripts/flash.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | setTimeout("$('.notice, .alert').fadeOut('slow')", 2000); 3 | }); 4 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/views/rooms/show.html.haml: -------------------------------------------------------------------------------- 1 | #chat_area 2 | = render "/rooms/chat_area" 3 | #chat_form 4 | = render "/rooms/chat_form" -------------------------------------------------------------------------------- /app/views/users/followers.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | %ul.users 3 | .users__title 4 | フォロワー 5 | = render @users -------------------------------------------------------------------------------- /app/views/users/following.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | %ul.users 3 | .users__title 4 | フォロー中 5 | = render @users -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | -------------------------------------------------------------------------------- /vendor/assets/javascript/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ohishikaito/MyNote/HEAD/vendor/assets/javascript/.DS_Store -------------------------------------------------------------------------------- /app/views/layouts/_flash.html.haml: -------------------------------------------------------------------------------- 1 | .notification 2 | - flash.each do |key, value| 3 | = content_tag :div, value, class: key -------------------------------------------------------------------------------- /app/views/likes/destroy.js.haml: -------------------------------------------------------------------------------- 1 | $("#like_#{@tweet.id}").html("#{j(render partial: 'likes/like', locals: { tweet: @tweet })}"); -------------------------------------------------------------------------------- /app/views/likes/create.js.haml: -------------------------------------------------------------------------------- 1 | $("#like_#{@tweet.id}").html("#{j(render partial: 'likes/like', locals: { tweet: @tweet })}"); 2 | -------------------------------------------------------------------------------- /app/channels/application_cable/channel.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Channel < ActionCable::Channel::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/views/kaminari/_gap.html.slim: -------------------------------------------------------------------------------- 1 | li.page-item.disabled 2 | = link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' 3 | -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | # EC2サーバーのIP、EC2サーバーにログインするユーザー名、サーバーのロールを記述 2 | server '54.150.220.39', user: 'ec2-user', roles: %w{app db web} -------------------------------------------------------------------------------- /spec/factories/like.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :like do 3 | association :tweet 4 | user { tweet.user } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/channels/application_cable/connection.rb: -------------------------------------------------------------------------------- 1 | module ApplicationCable 2 | class Connection < ActionCable::Connection::Base 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/views/devise/sessions/_testuser.html.haml: -------------------------------------------------------------------------------- 1 | = link_to users_guest_sign_in_path, method: :post, class: "btn btn-warning" do 2 | ゲストユーザーでログイン -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | default from: 'from@example.com' 3 | layout 'mailer' 4 | end 5 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ].each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /docker/mysql/charset.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | character-set-server=utf8mb4 3 | collation-server=utf8mb4_general_ci 4 | [client] 5 | default-character-set=utf8mb4 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my_app", 3 | "private": true, 4 | "dependencies": { 5 | "marked": "^1.1.0", 6 | "simplemde": "^1.11.2" 7 | } 8 | } -------------------------------------------------------------------------------- /app/views/donations/create.js.haml: -------------------------------------------------------------------------------- 1 | $('#donation_area').html("#{ j(render 'donations/donation') }"); 2 | $('#donation_form').html("#{ j(render 'donations/form') }"); -------------------------------------------------------------------------------- /app/views/tweets/_tag_list.html.haml: -------------------------------------------------------------------------------- 1 | - tag_list.each do |tag| 2 | = link_to tweets_path(tag_name: tag) do 3 | %i.btn.btn-outline-dark.tags 4 | = tag -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/models/entry.rb: -------------------------------------------------------------------------------- 1 | class Entry < ApplicationRecord 2 | belongs_to :user 3 | belongs_to :room 4 | validates :room_id, uniqueness: { scope: :user_id } 5 | end 6 | -------------------------------------------------------------------------------- /app/views/devise/mailer/password_change.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Hello #{@resource.email}! 3 | %p We're contacting you to notify you that your password has been changed. 4 | -------------------------------------------------------------------------------- /app/views/kaminari/_last_page.html.slim: -------------------------------------------------------------------------------- 1 | li.page-item 2 | = link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, remote: remote, class: 'page-link' 3 | -------------------------------------------------------------------------------- /app/views/rooms/index.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .room-index 3 | .room-index__title.room-index-hover 4 | メッセージ 5 | %ul.room-index__items 6 | = render @rooms -------------------------------------------------------------------------------- /config/database.yml.ci: -------------------------------------------------------------------------------- 1 | test: 2 | adapter: mysql2 3 | encoding: utf8 4 | pool: 5 5 | username: 'root' 6 | port: 3306 7 | host: '127.0.0.1' 8 | database: ci_test -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /db/migrate/20200603052105_tweets.rb: -------------------------------------------------------------------------------- 1 | class Tweets < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :tweets, :likes_count, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/kaminari/_first_page.html.slim: -------------------------------------------------------------------------------- 1 | li.page-item 2 | = link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link' 3 | -------------------------------------------------------------------------------- /spec/factories/donation.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :donation do 3 | association :tweet 4 | user { tweet.user } 5 | amount { 10_000 } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/kaminari/_next_page.html.slim: -------------------------------------------------------------------------------- 1 | li.page-item 2 | = link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'page-link' 3 | -------------------------------------------------------------------------------- /spec/controllers/likes_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Likes', type: :request do 4 | describe 'GET #index' do 5 | # 現在実装中 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/controllers/rooms_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Rooms', type: :request do 4 | describe 'GET #index' do 5 | # 現在実装中 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/controllers/users_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Users', type: :request do 4 | describe 'GET #index' do 5 | # 現在実装中 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/factories/comment.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :comment do 3 | association :tweet 4 | user { tweet.user } 5 | content { Faker::Lorem.word } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/views/kaminari/_prev_page.html.slim: -------------------------------------------------------------------------------- 1 | li.page-item 2 | = link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote, class: 'page-link' 3 | -------------------------------------------------------------------------------- /db/migrate/20200515024136_add_nickname_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddNicknameToUsers < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :users, :nickname, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200521050256_add_column_from_users.rb: -------------------------------------------------------------------------------- 1 | class AddColumnFromUsers < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :users, :avatar, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/controllers/comments_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Comments', type: :request do 4 | describe 'GET #index' do 5 | # 現在実装中 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/controllers/donations_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Donations', type: :request do 4 | describe 'GET #index' do 5 | # 現在実装中 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/controllers/messages_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Messages', type: :request do 4 | describe 'GET #index' do 5 | # 現在実装中 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/support/controller_macros.rb: -------------------------------------------------------------------------------- 1 | module ControllerMacros 2 | def login_user(user) 3 | @request.env["devise.mapping"] = Devise.mappings[:user] 4 | sign_in user 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/helpers/notifications_helper.rb: -------------------------------------------------------------------------------- 1 | module NotificationsHelper 2 | def unchecked_notifications 3 | @notifications = current_user.passive_notifications.where(checked: false) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20200521050717_create_rooms.rb: -------------------------------------------------------------------------------- 1 | class CreateRooms < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :rooms do |t| 4 | t.timestamps 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20200524230028_a_ddd_admin_to_users.rb: -------------------------------------------------------------------------------- 1 | class ADddAdminToUsers < ActiveRecord::Migration[5.2] 2 | def change 3 | add_column :users, :admin, :boolean, default: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/controllers/notifications_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Notifications', type: :request do 4 | describe 'GET #index' do 5 | # 現在実装中 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /spec/controllers/relationships_controller_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe 'Relationships', type: :request do 4 | describe 'GET #index' do 5 | # 現在実装中 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /app/views/users/_user.html.haml: -------------------------------------------------------------------------------- 1 | %li 2 | = link_to user_path(user), class: "users__item" do 3 | = image_tag "#{user.avatar}", class: "avatar" 4 | .users__item--nickname.black-important 5 | = user.nickname -------------------------------------------------------------------------------- /spec/factories/relationship.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :relationship do 3 | follower_id { FactoryBot.create(:user).id } 4 | following_id { FactoryBot.create(:user).id } 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/header.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | // ヘッダーのリンクをクリックしたら、リストを表示させる。 3 | $(".header-top__right__user__info--link").click(function () { 4 | $(".header-top__box").slideToggle(0); 5 | }); 6 | }); 7 | -------------------------------------------------------------------------------- /app/controllers/api/tweets_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::TweetsController < ApplicationController 2 | # 受け取ったテキストをマークダウンに変換するメソッド 3 | def preview 4 | @html = view_context.markdown(params[:text]) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/helpers/applications_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationsHelper 2 | def current_user_has?(instance) 3 | user_signed_in? && current_user == instance.user || user_signed_in? && current_user.admin? 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/messages/edit.js.haml: -------------------------------------------------------------------------------- 1 | $("#message_#{ @message.id }").html("#{ j(render 'rooms/chat_form' )}"); 2 | 3 | -# $('#comment_#{ @comment.id }').html("#{ j(render 'comments/form') }"); 4 | -# %div{id: "message_#{m.id}"} -------------------------------------------------------------------------------- /docker/mysql/password.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | password: 4 | environment: 5 | MYSQL_ROOT_PASSWORD: password 6 | MYSQL_USER: root 7 | MYSQL_PASSWORD: password 8 | TZ: "Asia/Tokyo" 9 | -------------------------------------------------------------------------------- /app/views/tweets/_top.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | %ul.tweet-index 3 | .tweet-index__title 4 | %p.tweet-index__title--text 5 | = render @tweets 6 | .tweet-index--footer 7 | = paginate @tweets 8 | = render 'tweets/new_btn' -------------------------------------------------------------------------------- /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/models/message.rb: -------------------------------------------------------------------------------- 1 | class Message < ApplicationRecord 2 | belongs_to :user 3 | belongs_to :room 4 | 5 | validates :message, presence: true, length: { maximum: 1000 } 6 | 7 | has_many :notifications, dependent: :destroy 8 | end 9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/confirmation_instructions.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Welcome #{@email}! 3 | %p You can confirm your account email through the link below: 4 | %p= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) 5 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: my_app_production 11 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /app/controllers/tweets/searches_controller.rb: -------------------------------------------------------------------------------- 1 | class Tweets::SearchesController < ApplicationController 2 | def index 3 | @tweets = Tweet.search(params[:keyword]).includes(%i[taggings user]).order('updated_at desc').page(params[:page]).per(10) 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/views/comments/_top.html.haml: -------------------------------------------------------------------------------- 1 | .tweet-show__comment__title 2 | .tweet-show__comment--count 3 | コメント( 4 | = @comments.count 5 | ) 6 | %ul.tweet-show__comment__items 7 | - @comments.each do |comment| 8 | = render "comments/comment", comment: comment -------------------------------------------------------------------------------- /app/views/kaminari/_page.html.slim: -------------------------------------------------------------------------------- 1 | - if page.current? 2 | li.page-item.active 3 | = content_tag :a, page, data: { remote: remote }, rel: page.rel, class: 'page-link' 4 | - else 5 | li.page-item 6 | = link_to page, url, remote: remote, rel: page.rel, class: 'page-link' 7 | -------------------------------------------------------------------------------- /app/views/messages/create.js.haml: -------------------------------------------------------------------------------- 1 | $("#chat_area").html("#{j(render("rooms/chat_area"))}"); 2 | $("#chat_form").html("#{j(render("rooms/chat_form"))}"); 3 | 4 | -# $('#comment_area').html("#{ j(render 'comments/comment') }"); 5 | -# $('#comment_form').html("#{ j(render 'comments/form') }"); -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /db/migrate/20200521050817_create_entries.rb: -------------------------------------------------------------------------------- 1 | class CreateEntries < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :entries do |t| 4 | t.references :user, foreign_key: true 5 | t.references :room, foreign_key: true 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/devise/mailer/unlock_instructions.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Hello #{@resource.email}! 3 | %p Your account has been locked due to an excessive number of unsuccessful sign in attempts. 4 | %p Click the link below to unlock your account: 5 | %p= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) 6 | -------------------------------------------------------------------------------- /app/views/tweets/searches/index.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | - if @tweets.present? 3 | %ul.tweet-index 4 | = render partial: 'tweets/tweet', collection: @tweets 5 | .tweet-index--footer 6 | = paginate @tweets 7 | - else 8 | .text-center 9 | 一致する投稿はありませんでした 10 | = render 'tweets/new_btn' -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /spec/factories/entry.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :entry do 3 | # @user = create(:user) 4 | # @user2 = create(:user) 5 | # @room = create(:room) 6 | # @room2 = create(:room) 7 | user_id { FactoryBot.create(:user).id } 8 | room_id { FactoryBot.create(:room).id } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/layouts/_circle.html.haml: -------------------------------------------------------------------------------- 1 | = link_to(notifications_path, class: "header-top__right--bell") do 2 | - if unchecked_notifications.any? 3 | %span.fa-stack 4 | %i.far.fa-bell.fa-lg.fa-stack-2x.black-important 5 | %i.fas.fa-circle.n-circle.fa-stack-1x 6 | - else 7 | %i.far.fa-bell.fa-lg.black-important -------------------------------------------------------------------------------- /db/migrate/20200519001917_create_likes.rb: -------------------------------------------------------------------------------- 1 | class CreateLikes < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :likes do |t| 4 | t.references :tweet, null: false, foreign_key: true 5 | t.references :user, null: false, foreign_key: true 6 | t.timestamps 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/helpers/rooms_helper.rb: -------------------------------------------------------------------------------- 1 | module RoomsHelper 2 | def opened_user(room) 3 | entry = room.entries.where.not(user_id: current_user) 4 | entry[0].user 5 | end 6 | 7 | def get_most_new_message(room) 8 | last_message = room.messages.order(created_at: :desc).limit(1) 9 | last_message[0] 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | before_action :configure_permitted_parameters, if: :devise_controller? 3 | 4 | protected 5 | 6 | def configure_permitted_parameters 7 | devise_parameter_sanitizer.permit(:sign_up, keys: %i[nickname avatar]) 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /docker/rails/puma.rb: -------------------------------------------------------------------------------- 1 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i 2 | threads threads_count, threads_count 3 | port ENV.fetch("PORT") { 3000 } 4 | environment ENV.fetch("RAILS_ENV") { "development" } 5 | plugin :tmp_restart 6 | 7 | app_root = File.expand_path('..', __dir__) 8 | bind "unix://#{app_root}/tmp/sockets/puma.sock" 9 | -------------------------------------------------------------------------------- /db/migrate/20200521050902_create_messages.rb: -------------------------------------------------------------------------------- 1 | class CreateMessages < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :messages do |t| 4 | t.references :user, foreign_key: true 5 | t.references :room, foreign_key: true 6 | t.text :message, null: false 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /docker/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mysql:5.7 2 | 3 | RUN apt-get update && \ 4 | apt-get install -y apt-utils \ 5 | locales && \ 6 | rm -rf /var/lib/apt/lists/* && \ 7 | echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \ 8 | locale-gen ja_JP.UTF-8 9 | ENV LC_ALL ja_JP.UTF-8 10 | ADD ./docker/mysql/charset.cnf /etc/mysql/conf.d/charset.cnf 11 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_flash.scss: -------------------------------------------------------------------------------- 1 | .notification { 2 | .notice { 3 | background-color: $light_blue; 4 | color: white; 5 | text-align: center; 6 | box-shadow: none; 7 | } 8 | 9 | .alert { 10 | background-color: $alert_orange; 11 | color: white; 12 | text-align: center; 13 | box-shadow: none; 14 | } 15 | } -------------------------------------------------------------------------------- /app/models/donation.rb: -------------------------------------------------------------------------------- 1 | class Donation < ApplicationRecord 2 | # tweets------------------------------------------------------------------- 3 | belongs_to :tweet 4 | # users------------------------------------------------------------------- 5 | belongs_to :user 6 | 7 | validates :amount, presence: true, numericality: { greater_than: 0 } 8 | end 9 | -------------------------------------------------------------------------------- /app/views/devise/mailer/email_changed.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Hello #{@email}! 3 | - if @resource.try(:unconfirmed_email?) 4 | %p 5 | We're contacting you to notify you that your email is being changed to #{@resource.unconfirmed_email}. 6 | - else 7 | %p 8 | We're contacting you to notify you that your email has been changed to #{@resource.email}. 9 | -------------------------------------------------------------------------------- /app/views/users/edit.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .login 3 | = form_for(current_user) do |f| 4 | .login__title 5 | 更新する 6 | %br 7 | パスワードの変更をしない場合は入力不要です 8 | .login__field 9 | = render "users/form", f: f 10 | .login__field 11 | .center 12 | = f.submit "更新する", class: "btn btn-success" 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/config/_colors.scss: -------------------------------------------------------------------------------- 1 | $white: #fff; 2 | $side_blue_dark: #253141; 3 | $side_blue_light: #2f3e51; 4 | $light_blue: #38aef0; 5 | $light_gray: #999; 6 | $black: #434a54; 7 | $alert_orange: #f35500; 8 | $header_border: #e1e1e1; 9 | $header_tags: #787c7b; 10 | $body_color: #f5f5f5; 11 | $new_green: #26a645; 12 | $login_yellow: #ffc108; 13 | -------------------------------------------------------------------------------- /bin/yarn: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_ROOT = File.expand_path('..', __dir__) 3 | Dir.chdir(APP_ROOT) do 4 | begin 5 | exec "yarnpkg", *ARGV 6 | rescue Errno::ENOENT 7 | $stderr.puts "Yarn executable was not detected in the system." 8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install" 9 | exit 1 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/models/relationship.rb: -------------------------------------------------------------------------------- 1 | class Relationship < ApplicationRecord 2 | # 自分をフォローしているユーザー 3 | belongs_to :follower, class_name: "User" 4 | # 自分がフォローしているユーザー 5 | belongs_to :following, class_name: "User" 6 | # バリデーション 7 | validates :follower_id, presence: true, uniqueness: { scope: :following_id } 8 | validates :following_id, presence: true 9 | end 10 | -------------------------------------------------------------------------------- /app/views/comments/create.js.haml: -------------------------------------------------------------------------------- 1 | $('#comment_area').html("#{ j(render 'comments/top') }"); 2 | $('#comment_form').html("#{ j(render 'comments/form') }"); 3 | -# 省略する前の形 4 | -# $('#comment_area').html("<%= j(render 'comments/comment', comments: @comments) %>"); 5 | -# $('#comment_form').html("<%= j(render 'comments/form', tweet: @tweet, comment: @comment) %>"); 6 | 7 | 8 | -------------------------------------------------------------------------------- /db/migrate/20200515005529_create_tweets.rb: -------------------------------------------------------------------------------- 1 | class CreateTweets < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :tweets do |t| 4 | t.string :title, null: false 5 | t.text :text, null: false 6 | t.string :image 7 | t.references :user, null: false, foreign_key: true 8 | t.timestamps 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20200515064321_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :comments do |t| 4 | t.references :tweet, null: false, foreign_key: true 5 | t.references :user, null: false, foreign_key: true 6 | t.text :content, null: false 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /docker/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.16 2 | RUN apt-get update && \ 3 | apt-get install -y apt-utils \ 4 | locales && \ 5 | echo "ja_JP.UTF-8 UTF-8" > /etc/locale.gen && \ 6 | locale-gen ja_JP.UTF-8 7 | ENV LC_ALL ja_JP.UTF-8 8 | # 初期状態の設定ファイル 9 | ADD ./docker/nginx/nginx.conf /etc/nginx/nginx.conf 10 | ADD ./docker/nginx/default.conf /etc/nginx/conf.d/default.conf -------------------------------------------------------------------------------- /db/migrate/20200527224117_create_donations.rb: -------------------------------------------------------------------------------- 1 | class CreateDonations < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :donations do |t| 4 | t.references :tweet, null: false, foreign_key: true 5 | t.references :user, null: false, foreign_key: true 6 | t.integer :amount, null: false 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/rooms/_chat_form.html.haml: -------------------------------------------------------------------------------- 1 | .room-show__form 2 | .room-show__form__box 3 | = form_with(model: @message, id: "room-show__form") do |f| 4 | = f.text_field :message, placeholder: "メッセージを入力して下さい" ,class: "room-show__form--input" 5 | = f.hidden_field :room_id, value: @room.id 6 | %button{type: "submit", class: "fa fa-send fa-2x room-show__form--btn"} 7 | -------------------------------------------------------------------------------- /spec/factories/message.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :message do 3 | # user 4 | # room 5 | # association :user 6 | # association :room 7 | # user_id { user.id } 8 | # room_id { room.id } 9 | user_id { FactoryBot.create(:user).id } 10 | room_id { FactoryBot.create(:room).id } 11 | message { Faker::Lorem.word } 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/relationships_controller.rb: -------------------------------------------------------------------------------- 1 | class RelationshipsController < ApplicationController 2 | def create 3 | @user = User.find(params[:following_id]) 4 | current_user.follow(@user) 5 | @user.create_notification_follow!(current_user) 6 | end 7 | 8 | def destroy 9 | @user = User.find(params[:id]) 10 | current_user.unfollow(@user) 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /app/assets/stylesheets/mixin/_media.scss: -------------------------------------------------------------------------------- 1 | @mixin sm { @media (min-width: 0px) { @content; } } 2 | @mixin md { @media (min-width: 768px) { @content; } } 3 | @mixin lg { @media (min-width: 1020px) { @content; } } 4 | 5 | @mixin lg-max { @media (max-width: 1020px) { @content; } } 6 | @mixin md-max { @media (max-width: 768px) { @content; } } 7 | @mixin sm-max { @media (max-width: 580px) { @content; } } -------------------------------------------------------------------------------- /spec/factories/tweet.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :tweet do 3 | title { Faker::Lorem.word } 4 | text { Faker::Lorem.word } 5 | image { File.open("#{Rails.root}/app/assets/images/default.png") } 6 | created_at { Faker::Time.between(from: DateTime.now - 2, to: DateTime.now) } 7 | # user 8 | association :user 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /app/views/devise/shared/_error_messages.html.haml: -------------------------------------------------------------------------------- 1 | - if resource.errors.any? 2 | #error_explanation 3 | %h2 4 | = I18n.t("errors.messages.not_saved", | 5 | count: resource.errors.count, | 6 | resource: resource.class.model_name.human.downcase) | 7 | %ul 8 | - resource.errors.full_messages.each do |message| 9 | %li= message 10 | -------------------------------------------------------------------------------- /app/views/tweets/_new_btn.html.haml: -------------------------------------------------------------------------------- 1 | - if user_signed_in? 2 | = link_to new_tweet_path, class: "new-btn" do 3 | .new-btn__tweet 4 | %p.new-btn__tweet--text 5 | 投稿 6 | .new-btn__tweet--icon 7 | %i.fas.fa-plus 8 | - else 9 | = link_to users_guest_sign_in_path, method: :post, class: "new-btn" do 10 | .new-btn__session 11 | .new-btn__session--text 12 | %p ゲストログイン 13 | -------------------------------------------------------------------------------- /db/migrate/20200520074806_create_relationships.rb: -------------------------------------------------------------------------------- 1 | class CreateRelationships < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :relationships do |t| 4 | t.references :follower, foreign_key: { to_table: :users } 5 | t.references :following, foreign_key: { to_table: :users } 6 | t.index [:follower_id, :following_id], unique: true 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /.rubocop_todo.yml: -------------------------------------------------------------------------------- 1 | # This configuration was generated by 2 | # `rubocop --auto-gen-config` 3 | # on 2020-08-21 15:02:15 +0900 using RuboCop version 0.85.1. 4 | # The point is for the user to remove these configuration records 5 | # one by one as the offenses are removed from the code base. 6 | # Note that changes in the inspected code, or installation of new 7 | # versions of RuboCop, may require this file to be generated again. 8 | -------------------------------------------------------------------------------- /app/views/devise/mailer/reset_password_instructions.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | Hello #{@resource.email}! 3 | %p Someone has requested a link to change your password. You can do this through the link below. 4 | %p= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) 5 | %p If you didn't request this, please ignore this email. 6 | %p Your password won't change until you access the link above and create a new one. 7 | -------------------------------------------------------------------------------- /app/views/users/likes.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | = render "/users/info" 3 | .tweet-index 4 | .user-show__select 5 | .user-show__select__left 6 | = link_to "最近の投稿", user_path 7 | .user-show__select__right 8 | .user-show__select--active.cursor-pointer 9 | いいねした投稿 10 | = render @tweets 11 | - unless @tweets.present? # @tweetsがなければtrue(下記を表示) 12 | いいねした投稿がありません 13 | = paginate @tweets -------------------------------------------------------------------------------- /app/assets/javascripts/cable.js: -------------------------------------------------------------------------------- 1 | // Action Cable provides the framework to deal with WebSockets in Rails. 2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command. 3 | // 4 | //= require action_cable 5 | //= require_self 6 | //= require_tree ./channels 7 | 8 | (function() { 9 | this.App || (this.App = {}); 10 | 11 | App.cable = ActionCable.createConsumer(); 12 | 13 | }).call(this); 14 | -------------------------------------------------------------------------------- /spec/factories/user.rb: -------------------------------------------------------------------------------- 1 | FactoryBot.define do 2 | factory :user do 3 | password = Faker::Internet.password(min_length: 8) 4 | nickname { Faker::Name.last_name } 5 | email { Faker::Internet.email } 6 | password { password } 7 | password_confirmation { password } 8 | avatar { File.open("#{Rails.root}/app/assets/images/default.png") } 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /Capfile: -------------------------------------------------------------------------------- 1 | # 元々の記述は全て削除して大丈夫です 2 | require "capistrano/setup" 3 | require "capistrano/deploy" 4 | require "capistrano/scm/git" 5 | install_plugin Capistrano::SCM::Git 6 | require 'capistrano/rails/console' 7 | require 'capistrano/rbenv' 8 | require 'capistrano/bundler' 9 | require 'capistrano/rails/assets' 10 | require 'capistrano/rails/migrations' 11 | require 'capistrano3/unicorn' 12 | Dir.glob("lib/capistrano/tasks/*.rake").each { |r| import r } 13 | -------------------------------------------------------------------------------- /app/assets/javascripts/tweet-title.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | // いいねランキングが呼ばれたら表示 3 | if (document.location.href.match(/\/tweets\/likes/)) { 4 | $(".tweet-index__title").show(); 5 | $(".tweet-index__title--text").append("人気投稿"); 6 | } 7 | 8 | // タイムラインが呼ばれたら表示 9 | if (document.location.href.match(/\/users\/timeline/)) { 10 | $(".tweet-index__title").show(); 11 | $(".tweet-index__title--text").append("タイムライン"); 12 | } 13 | }) -------------------------------------------------------------------------------- /spec/factories/notification.rb: -------------------------------------------------------------------------------- 1 | # t.integer "visitor_id", null: false 2 | # t.integer "visited_id", null: false 3 | # t.integer "tweet_id" 4 | # t.integer "comment_id" 5 | # t.integer "message_id" 6 | # t.integer "room_id" 7 | # t.string "action", default: "", null: false 8 | # t.boolean "checked", default: false, null: false 9 | 10 | FactoryBot.define do 11 | factory :notification do 12 | # ページ遷移するとcheckedが変わるテストしてみたい 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/views/users/show.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | = render "/users/info" 3 | .tweet-index 4 | .user-show__select 5 | .user-show__select__left 6 | .user-show__select--active 7 | 最近の投稿 8 | .user-show__select__right 9 | = link_to "いいねした投稿", likes_user_path, class: "a-hover" 10 | = render @tweets 11 | - unless @tweets.present? # @tweetsがなければtrue(下記を表示) 12 | 投稿がありません 13 | = paginate @tweets 14 | 15 | -------------------------------------------------------------------------------- /app/views/donations/_donation.html.haml: -------------------------------------------------------------------------------- 1 | .tweet-show__content__donation--text 2 | この記事への寄付額 3 | %strong 4 | = @donations.to_s(:delimited) 5 | 円 6 | .tweet-show__content__donation--submit 7 | - if user_signed_in? && current_user.id != @tweet.user_id 8 | %button.btn.btn-info.rounded-pill.donation-btn 9 | 寄付する 10 | - elsif user_signed_in? && current_user.id == @tweet.user_id 11 | 自分の投稿には寄付ができません 12 | - else 13 | 寄付をするにはログインが必要です -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /app/views/devise/unlocks/new.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Resend unlock instructions 2 | = form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| 3 | = render "devise/shared/error_messages", resource: resource 4 | .field 5 | = f.label :email 6 | %br/ 7 | = f.email_field :email, autofocus: true, autocomplete: "email" 8 | .actions 9 | = f.submit "Resend unlock instructions" 10 | = render "devise/shared/links" 11 | -------------------------------------------------------------------------------- /app/models/notification.rb: -------------------------------------------------------------------------------- 1 | class Notification < ApplicationRecord 2 | default_scope -> { order(created_at: :desc) } 3 | belongs_to :tweet, optional: true 4 | belongs_to :comment, optional: true 5 | belongs_to :message, optional: true 6 | belongs_to :room, optional: true 7 | 8 | belongs_to :visitor, class_name: 'User', foreign_key: 'visitor_id', optional: true 9 | belongs_to :visited, class_name: 'User', foreign_key: 'visited_id', optional: true 10 | end 11 | -------------------------------------------------------------------------------- /app/views/devise/passwords/new.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Forgot your password? 2 | = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| 3 | = render "devise/shared/error_messages", resource: resource 4 | .field 5 | = f.label :email 6 | %br/ 7 | = f.email_field :email, autofocus: true, autocomplete: "email" 8 | .actions 9 | = f.submit "Send me reset password instructions" 10 | = render "devise/shared/links" 11 | -------------------------------------------------------------------------------- /app/models/like.rb: -------------------------------------------------------------------------------- 1 | class Like < ApplicationRecord 2 | # likes------------------------------------------------------------------- 3 | validates :tweet_id, uniqueness: { scope: :user_id }, presence: true 4 | validates :user_id, presence: true 5 | # tweets------------------------------------------------------------------- 6 | belongs_to :tweet, counter_cache: :likes_count 7 | # users------------------------------------------------------------------- 8 | belongs_to :user 9 | end 10 | -------------------------------------------------------------------------------- /app/views/donations/_form.html.haml: -------------------------------------------------------------------------------- 1 | = form_with(model: [@tweet, @donation], data: { confirm: '本当に寄付しますか?(一度寄付したら取り消せません)' }, id: "donation-form") do |f| 2 | .donation-modal--body 3 | = f.number_field :amount, placeholder: "金額を入力して下さい", class: "donation-modal--body--input" 4 | .form 5 | .donation-modal--close 6 | %i.btn.btn-dark.donation-modal--close--btn 7 | 閉じる 8 | .donation-modal--submit 9 | = f.submit "送信する", class: "page-link text-dark d-inline-block donation-modal--submit--btn" -------------------------------------------------------------------------------- /app/views/kaminari/_paginator.html.slim: -------------------------------------------------------------------------------- 1 | = paginator.render do 2 | nav 3 | ul.pagination 4 | == first_page_tag unless current_page.first? 5 | == prev_page_tag unless current_page.first? 6 | - each_page do |page| 7 | - if page.left_outer? || page.right_outer? || page.inside_window? 8 | == page_tag page 9 | - elsif !page.was_truncated? 10 | == gap_tag 11 | == next_page_tag unless current_page.last? 12 | == last_page_tag unless current_page.last? 13 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ApplicationRecord 2 | # tweets------------------------------------------------------------------- 3 | belongs_to :tweet 4 | # users------------------------------------------------------------------- 5 | belongs_to :user 6 | # comments------------------------------------------------------------------- 7 | validates :content, presence: true, length: { maximum: 1000 } 8 | # notifications------------------------------------------------------------------- 9 | has_many :notifications, dependent: :destroy 10 | end 11 | -------------------------------------------------------------------------------- /app/views/users/_contact.html.haml: -------------------------------------------------------------------------------- 1 | - if user_signed_in? 2 | - unless @user.id == current_user.id 3 | - if @have_room == true 4 | = link_to room_path(@room_id) do 5 | %i.btn.btn-info.user-show__top--contact--btn 6 | メッセージを送る 7 | - else 8 | = form_with(model:@room, local: true) do |f| 9 | = fields_for @entry do |e| 10 | = e.hidden_field :user_id, value: @user.id 11 | = f.submit "メッセージ送る", class: "btn btn-info user-show__top--contact--btn" -------------------------------------------------------------------------------- /app/views/devise/confirmations/new.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Resend confirmation instructions 2 | = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| 3 | = render "devise/shared/error_messages", resource: resource 4 | .field 5 | = f.label :email 6 | %br/ 7 | = f.email_field :email, autofocus: true, autocomplete: "email", value: (resource.pending_reconfirmation? ? resource.unconfirmed_email : resource.email) 8 | .actions 9 | = f.submit "Resend confirmation instructions" 10 | = render "devise/shared/links" 11 | -------------------------------------------------------------------------------- /app/views/comments/_form.html.haml: -------------------------------------------------------------------------------- 1 | - if user_signed_in? 2 | = form_with(model: [@tweet, @comment], class: "tweet-show__comment__form") do |form| 3 | .tweet-show__comment__form__top 4 | = image_tag "#{current_user.avatar}", class: "avatar" 5 | .tweet-show__comment__form--title 6 | コメントする 7 | = form.text_area :content, class: 'textarea tweet-show__comment__form--body' 8 | .tweet-show__comment__form--submit 9 | = form.submit value: "投稿する", class: "btn btn-success" 10 | - else 11 | .tweet-show__comment__form--text 12 | コメントの投稿にはログインが必要です。 -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /app/views/likes/_like.html.haml: -------------------------------------------------------------------------------- 1 | - if user_signed_in? 2 | - if tweet.like_user(current_user.id) 3 | = link_to tweet_like_path(tweet.likes, tweet_id: tweet.id), method: :delete, remote: true do 4 | %i.fa.fa-heart{"aria-hidden" => "true", style: "color: red;"} 5 | = tweet.likes_count 6 | - else 7 | = link_to tweet_likes_path(tweet.id), method: :post, remote: true do 8 | %i.fa.fa-heart{"aria-hidden" => "true", style: "color: #C0C0C0;"} 9 | = tweet.likes_count 10 | - else 11 | %i.fa.fa-heart{"aria-hidden" => "true"} 12 | = tweet.likes_count 13 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads Spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == 'spring' } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /app/controllers/likes_controller.rb: -------------------------------------------------------------------------------- 1 | class LikesController < ApplicationController 2 | before_action :set_tweet, only: %i[create destroy] 3 | 4 | def create 5 | like = Like.create(user_id: current_user.id, tweet_id: @tweet.id) 6 | like.save 7 | @tweet.create_notification_like!(current_user) 8 | @tweet.reload 9 | end 10 | 11 | def destroy 12 | like = Like.find_by(user_id: current_user.id, tweet_id: @tweet.id) 13 | like.destroy 14 | @tweet.reload 15 | end 16 | 17 | private 18 | 19 | def set_tweet 20 | @tweet = Tweet.find(params[:tweet_id]) 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /spec/models/room_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Room, type: :model do 4 | describe "各モデルとのアソシエーション" do 5 | let(:association) do 6 | described_class.reflect_on_association(target) 7 | end 8 | 9 | context "アソシエーションのテスト" do 10 | let(:target) { :rooms } 11 | it "Roomsとの関連付けはhas_manyであること" do 12 | expect(association.macro).to eq :has_many 13 | end 14 | 15 | let(:target) { :messages } 16 | it "Messagesとの関連付けはhas_manyであること" do 17 | expect(association.macro).to eq :has_many 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # 初期状態 -------------------------- 2 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 3 | threads threads_count, threads_count 4 | port ENV.fetch("PORT") { 3000 } 5 | environment ENV.fetch("RAILS_ENV") { "development" } 6 | pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" } 7 | plugin :tmp_restart 8 | 9 | # Docker使用設定 10 | # threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i 11 | # threads threads_count, threads_count 12 | # port ENV.fetch("PORT") { 3000 } 13 | # environment ENV.fetch("RAILS_ENV") { "development" } 14 | # plugin :tmp_restart 15 | # app_root = File.expand_path("../..", __FILE__) 16 | # bind "unix://#{app_root}/tmp/sockets/puma.sock" 17 | -------------------------------------------------------------------------------- /db/migrate/20200523235326_add_missing_taggable_index.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from acts_as_taggable_on_engine (originally 4) 2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0') 3 | class AddMissingTaggableIndex < ActiveRecord::Migration[4.2]; end 4 | else 5 | class AddMissingTaggableIndex < ActiveRecord::Migration; end 6 | end 7 | AddMissingTaggableIndex.class_eval do 8 | def self.up 9 | add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx' 10 | end 11 | 12 | def self.down 13 | remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/helpers/devise_helper.rb: -------------------------------------------------------------------------------- 1 | module DeviseHelper 2 | def devise_error_messages! 3 | return "" if resource.errors.empty? 4 | 5 | html = "" 6 | resource.errors.full_messages.each do |errmsg| 7 | html += <<-EOF 8 | 15 | EOF 16 | end 17 | html.html_safe 18 | end 19 | 20 | def devise_error_messages? 21 | resource.errors.empty? ? false : true 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /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 | # Add Yarn node_modules folder to the asset load path. 9 | Rails.application.config.assets.paths << Rails.root.join('node_modules') 10 | 11 | # Precompile additional assets. 12 | # application.js, application.css, and all non-JS/CSS in the app/assets 13 | # folder are already added. 14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css ) 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_user-index.scss: -------------------------------------------------------------------------------- 1 | .users { 2 | // width: 600px; 3 | @include common-sizing; 4 | margin: 0 auto; 5 | border: 1px solid $header_border; 6 | background-color: white; 7 | border-radius: 5px; 8 | box-shadow: 0px 4px 6px #ccc; 9 | &__title { 10 | height: 80px; 11 | text-align: center; 12 | line-height: 80px; 13 | font-size: 22px; 14 | } 15 | &__item { 16 | height: 80px; 17 | width: 100%; 18 | display: flex; 19 | padding: 30px; 20 | border-bottom: 1px solid $header_border; 21 | &:hover { 22 | color: black; 23 | background-color: $header_border; 24 | } 25 | &--nickname { 26 | padding: 5px 15px; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/views/rooms/_room.html.haml: -------------------------------------------------------------------------------- 1 | %li.room-index__items__item.room-index-hover 2 | - user = opened_user(room) 3 | = link_to user_path(user.id) do 4 | = image_tag "#{user.avatar}", class: "room-index__items__item--icon" 5 | = link_to room_path(room), class: 'room-index__items__item__content' do 6 | .room-index__items__item__content--body 7 | %strong.room-index__items__item__content--nickname 8 | = user.nickname 9 | - message = get_most_new_message(room) 10 | - if message.present? 11 | .room-index__items__item__content--message 12 | = truncate(message.message, length: 20) 13 | .room-index__items__item__content--date 14 | = message.created_at.strftime("%Y-%m-%d %H:%M") 15 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /db/migrate/20200523235327_change_collation_for_tag_names.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from acts_as_taggable_on_engine (originally 5) 2 | # This migration is added to circumvent issue #623 and have special characters 3 | # work properly 4 | if ActiveRecord.gem_version >= Gem::Version.new('5.0') 5 | class ChangeCollationForTagNames < ActiveRecord::Migration[4.2]; end 6 | else 7 | class ChangeCollationForTagNames < ActiveRecord::Migration; end 8 | end 9 | ChangeCollationForTagNames.class_eval do 10 | def up 11 | if ActsAsTaggableOn::Utils.using_mysql? 12 | execute("ALTER TABLE #{ActsAsTaggableOn.tags_table} MODIFY name varchar(255) CHARACTER SET utf8 COLLATE utf8_bin;") 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/devise/sessions/new.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .login 3 | .login__field 4 | .center 5 | = render 'devise/sessions/testuser' 6 | = form_with model: @user, url: user_session_path, local: true do |f| 7 | .login__title 8 | ログイン 9 | .login__field 10 | .text-center 11 | = f.label :email 12 | .center 13 | = f.email_field :email, autofocus: true, class: "login--input" 14 | .login__field 15 | .text-center 16 | = f.label :password 17 | .center 18 | = f.password_field :password, autocomplete: "off", class: "login--input" 19 | .login__field 20 | .center 21 | = f.submit "ログインする", class: "btn btn-success" 22 | -------------------------------------------------------------------------------- /docker/rails/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:2.5.1 2 | 3 | RUN apt-get update -qq && \ 4 | apt-get install -y apt-utils \ 5 | build-essential \ 6 | libpq-dev \ 7 | nodejs \ 8 | vim \ 9 | default-mysql-client 10 | 11 | # 作業ディレクトリの作成、設定 12 | RUN mkdir /app 13 | ##作業ディレクトリ名をAPP_ROOTに割り当てて、以下$APP_ROOTで参照 14 | ENV APP_ROOT /app 15 | WORKDIR $APP_ROOT 16 | 17 | # ホスト側(ローカル)のGemfileを追加する(ローカルのGemfileは【3】で作成) 18 | ADD ./Gemfile $APP_ROOT/Gemfile 19 | ADD ./Gemfile.lock $APP_ROOT/Gemfile.lock 20 | 21 | # bundlerのバージョンを2系にする 22 | ENV BUNDLER_VERSION='2.1.4' 23 | RUN gem install bundler --no-document -v '2.1.4' 24 | 25 | # Gemfileのbundle install 26 | RUN bundle install 27 | ADD . $APP_ROOT 28 | 29 | RUN mkdir -p $APP_ROOT/tmp/sockets 30 | 31 | EXPOSE 3000 32 | 33 | -------------------------------------------------------------------------------- /docker/nginx/default.conf: -------------------------------------------------------------------------------- 1 | # プロキシ先の指定 2 | # Nginxが受け取ったリクエストをバックエンドのpumaに送信 3 | upstream app { 4 | # ソケット通信したいのでpuma.sockを指定 5 | server unix:///app/tmp/sockets/puma.sock; 6 | } 7 | 8 | server { 9 | listen 80; 10 | # ドメインもしくはIPを指定 54.238.171.38; # デプロイする時は変更する(環境変数で用意すべきかも) 11 | server_name localhost; 12 | 13 | access_log /var/log/nginx/access.log; 14 | error_log /var/log/nginx/error.log; 15 | 16 | # ドキュメントルートの指定 17 | root /app/public; 18 | 19 | # リバースプロキシ設定 20 | location / { 21 | proxy_pass http://app; 22 | } 23 | 24 | client_max_body_size 100m; 25 | error_page 404 /404.html; 26 | error_page 505 502 503 504 /500.html; 27 | try_files $uri/index.html $uri @app; 28 | keepalive_timeout 5; 29 | } 30 | 31 | -------------------------------------------------------------------------------- /app/controllers/donations_controller.rb: -------------------------------------------------------------------------------- 1 | class DonationsController < ApplicationController 2 | before_action :set_tweet, only: [:create] 3 | 4 | def create 5 | @donation = @tweet.donations.create(donation_params) 6 | if @donation.save 7 | # 寄付の通知を実装予定! 8 | # @donation.create_notification_donation!(current_user, @donation.id) 9 | @donation = Donation.new 10 | end 11 | gets_all_donations 12 | end 13 | 14 | private 15 | 16 | def set_tweet 17 | @tweet = Tweet.find(params[:tweet_id]) 18 | end 19 | 20 | def gets_all_donations 21 | @donations = @tweet.donations.sum(:amount) 22 | end 23 | 24 | def donation_params 25 | params.require(:donation).permit(:amount).merge(user_id: current_user.id) 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/helpers/markdown_helper.rb: -------------------------------------------------------------------------------- 1 | module MarkdownHelper 2 | def markdown(text) 3 | unless @markdown 4 | options = { 5 | hard_wrap: true, # 改行を反映 6 | filter_html: true, # HTMLタグを出力しない。h1タグ等。XSS対策済み 7 | space_after_headers: true # 「#」の後に空白がないと見出し(h1)と認めない 8 | } 9 | extensions = { 10 | autolink: true, # URLをクリックすると飛べる 11 | fenced_code_blocks: true, # Qiitaのようにコードを装飾できる「```」 12 | tables: true, # テーブル表もどきになる「| td | td | td |&|---|---|---|」 13 | strikethrough: true # 取り消し線を有効「~~AA~~」 14 | } 15 | renderer = Redcarpet::Render::HTML.new(options) 16 | @markdown = Redcarpet::Markdown.new(renderer, extensions) 17 | end 18 | @markdown.render(text).html_safe 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_session.scss: -------------------------------------------------------------------------------- 1 | .login{ 2 | width: 400px; 3 | @include md-max{ 4 | width: 300px; 5 | } 6 | margin: 0 auto; 7 | padding: 30px; 8 | background-color: white; 9 | border: 1px solid $header_border; 10 | border-radius: 5px; 11 | box-shadow: 0px 4px 6px #ccc; 12 | display: flex; 13 | flex-direction: column; 14 | &__title{ 15 | text-align: center; 16 | font-size: 23px; 17 | font-weight: bold; 18 | padding: 10px; 19 | } 20 | &__field{ 21 | padding: 10px; 22 | } 23 | &--input{ 24 | width: 200px; 25 | color: #222; 26 | background: white; 27 | border: 1px solid #e6e6e6; 28 | border-radius: 5px; 29 | padding: 0 4px; 30 | line-height: 38px; 31 | font-size: 14px; 32 | } 33 | } -------------------------------------------------------------------------------- /config/initializers/carrierwave.rb: -------------------------------------------------------------------------------- 1 | require 'carrierwave/storage/abstract' 2 | require 'carrierwave/storage/file' 3 | require 'carrierwave/storage/fog' 4 | 5 | CarrierWave.configure do |config| 6 | if Rails.env.development? || Rails.env.test? 7 | config.storage = :file 8 | else 9 | config.storage = :fog 10 | config.fog_provider = 'fog/aws' 11 | config.fog_credentials = { 12 | provider: 'AWS', 13 | aws_access_key_id: Rails.application.credentials.aws[:ACCESS_KEY_ID], 14 | aws_secret_access_key: Rails.application.credentials.aws[:SECRET_ACCESS_KEY], 15 | region: 'ap-northeast-1' 16 | } 17 | config.fog_directory = 'my-app0014' 18 | config.asset_host = 'https://s3-ap-northeast-1.amazonaws.com/my-app0014' 19 | end 20 | end -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | /tmp/* 13 | !/log/.keep 14 | !/tmp/.keep 15 | 16 | # Ignore uploaded files in development 17 | /storage/* 18 | !/storage/.keep 19 | 20 | /node_modules 21 | /yarn-error.log 22 | 23 | /public/assets 24 | .byebug_history 25 | 26 | # Ignore master key for decrypting credentials and more. 27 | /config/master.key 28 | public/uploads/* 29 | .DS_Store 30 | 31 | /.bundle 32 | /vendor/bundle 33 | -------------------------------------------------------------------------------- /app/models/room.rb: -------------------------------------------------------------------------------- 1 | class Room < ApplicationRecord 2 | # messages------------------------------------------------------------------- 3 | has_many :messages 4 | has_many :entries 5 | has_many :users, through: :entries 6 | # notifications------------------------------------------------------------------- 7 | has_many :notifications 8 | def create_notification_message!(current_user, message_id) 9 | temp_ids = Entry.select(:user_id).where(room_id: id).where.not(user_id: current_user.id).distinct 10 | temp_ids.each do |temp_id| 11 | notification = current_user.active_notifications.new( 12 | room_id: id, 13 | message_id: message_id, 14 | visited_id: temp_id['user_id'], 15 | action: 'message' 16 | ) 17 | notification.save 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20200523015543_create_notifications.rb: -------------------------------------------------------------------------------- 1 | class CreateNotifications < ActiveRecord::Migration[5.2] 2 | def change 3 | create_table :notifications do |t| 4 | t.integer :visitor_id, null: false 5 | t.integer :visited_id, null: false 6 | t.integer :tweet_id 7 | t.integer :comment_id 8 | t.integer :message_id 9 | t.integer :room_id 10 | t.string :action, default: '', null: false 11 | t.boolean :checked, default: false, null: false 12 | 13 | t.timestamps 14 | end 15 | 16 | add_index :notifications, :visitor_id 17 | add_index :notifications, :visited_id 18 | add_index :notifications, :tweet_id 19 | add_index :notifications, :comment_id 20 | add_index :notifications, :message_id 21 | add_index :notifications, :room_id 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's 5 | // vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. JavaScript code in this file should be added after the last require_* statement. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery-ui 15 | //= require jquery_ujs 16 | //= require tag-it 17 | //= require_tree . 18 | -------------------------------------------------------------------------------- /app/views/devise/passwords/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h2 Change your password 2 | = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| 3 | = render "devise/shared/error_messages", resource: resource 4 | = f.hidden_field :reset_password_token 5 | .field 6 | = f.label :password, "New password" 7 | %br/ 8 | - if @minimum_password_length 9 | %em 10 | (#{@minimum_password_length} characters minimum) 11 | %br/ 12 | = f.password_field :password, autofocus: true, autocomplete: "new-password" 13 | .field 14 | = f.label :password_confirmation, "Confirm new password" 15 | %br/ 16 | = f.password_field :password_confirmation, autocomplete: "new-password" 17 | .actions 18 | = f.submit "Change my password" 19 | = render "devise/shared/links" 20 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | edlF06Z2gir1liaggAr35+C5elyeURwREiBqvrnUCNqY8O29ZQBbIwraj5t4Kev3ZpGtq84BZ1QuVkryVt59C7LPkecnxJjr0E65k5qZGtu9o7pl57yCae7c7eotP2VHFg9Dl94/qCM/xJi6totzdS/9n14Myz9Qi44kaEoZhRUKTQbDtCTJynucmnpFbejRiOHD/Dio5AoPxwKs9kXIa05+w3fqT8FAjmyiiCbgeO8/ww5kyxVBxKX35+IkbAppfQdZZ2JjoHCneyKng2S+k268/i/VvktseGLmsdqwxRczgrDEO/Q676GJKm7iK1ki1FnAo9dr0cKLknrZPT8rd+uXZN2cHgCjIxWCAw1Yda6N746wbZLpoFg99mYQMZiF09Bs3F0lkVBPCQSWTUb3Bz+YKqcBHY8X1Z49U/5sqAklVUzj9IoV9SkOYYqCWlgldp8M45l5vOiir0AU/fzEp57K7rz3DbiSQN2L7moG4rTm7bbc39fERKkH3mvLENV481ytjiwqySy85Q2mIxgf4EqRKfvACTu6FwPoImDbwHAKbafkOJ0tnfDhs6sCrawE0nk+VsQN57cgh5XyExhVryVH/NTfPbhkPryaDQAiVbaqQxgkYeaawrBRag1X0Kmz3Ih8MsFRt0mb0hnXW+djIbwe6tGQJuOLbMMZg+n2bAXuMTK3LGZxU0sO0h1ndhuh5LljhVvaSVOB6GMWFy+yUdTgsx2RSAKfheOBbKcCrP41a9kR7NS7Qk4D8k6EzzVpFVL0x6ecJQ+7FtBC+wYX+AE=--ttdZjxSiHZHinCbW--fDARRCar37G+xfF2dNjhLA== -------------------------------------------------------------------------------- /app/assets/javascripts/tagit.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | // URLがtweets/newかeditならtrue 3 | if ( 4 | document.location.href.match(/\/tweets\/new/) || 5 | document.location.href.match(/\/tweets\/\d+\/edit/) 6 | ) { 7 | var i, len, ref, results, tag; // 変数を定義 8 | 9 | $("#tweet-tags").tagit({ 10 | // tagitを発火 11 | fieldName: "tweet[tag_list]", // paramterの値をtag_listに指定。これでcontrollerで受け取れるようにする。 12 | singleField: true, 13 | availableTags: gon.available_tags, // オートコンプリートを有効にする 14 | }); 15 | // 既存のタグがあれば表示させる 16 | if (gon.tweet_tags != null) { 17 | ref = gon.tweet_tags; 18 | results = []; 19 | for (i = 0, len = ref.length; i < len; i++) { 20 | tag = ref[i]; 21 | results.push($("#tweet-tags").tagit("createTag", tag)); 22 | } 23 | return results; 24 | } 25 | } 26 | }); 27 | -------------------------------------------------------------------------------- /db/migrate/20200523235325_add_taggings_counter_cache_to_tags.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from acts_as_taggable_on_engine (originally 3) 2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0') 3 | class AddTaggingsCounterCacheToTags < ActiveRecord::Migration[4.2]; end 4 | else 5 | class AddTaggingsCounterCacheToTags < ActiveRecord::Migration; end 6 | end 7 | AddTaggingsCounterCacheToTags.class_eval do 8 | def self.up 9 | add_column ActsAsTaggableOn.tags_table, :taggings_count, :integer, default: 0 10 | 11 | ActsAsTaggableOn::Tag.reset_column_information 12 | ActsAsTaggableOn::Tag.find_each do |tag| 13 | ActsAsTaggableOn::Tag.reset_counters(tag.id, ActsAsTaggableOn.taggings_table) 14 | end 15 | end 16 | 17 | def self.down 18 | remove_column ActsAsTaggableOn.tags_table, :taggings_count 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/controllers/users/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Users::SessionsController < Devise::SessionsController 4 | # before_action :configure_sign_in_params, only: [:create] 5 | 6 | def new_guest 7 | user = User.find_by(email: "guest@user") 8 | sign_in user 9 | redirect_to root_path, notice: 'ゲストユーザーとしてログインしました。' 10 | end 11 | 12 | # GET /resource/sign_in 13 | # def new 14 | # super 15 | # end 16 | 17 | # POST /resource/sign_in 18 | # def create 19 | # super 20 | # end 21 | 22 | # DELETE /resource/sign_out 23 | # def destroy 24 | # super 25 | # end 26 | 27 | # protected 28 | 29 | # If you have extra params to permit, append them to the sanitizer. 30 | # def configure_sign_in_params 31 | # devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute]) 32 | # end 33 | end 34 | -------------------------------------------------------------------------------- /spec/features/tweet_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | feature 'tweet', type: :feature do 4 | let(:user) { create(:user) } 5 | 6 | scenario 'ユーザー情報が更新されていること' do 7 | # ログイン前には投稿ボタンがない 8 | visit root_path 9 | expect(page).to have_no_content("投稿") 10 | 11 | # ログイン処理 12 | visit new_user_session_path 13 | fill_in 'user_email', with: user.email 14 | fill_in 'user_password', with: user.password 15 | find('input[value="ログインする"]').click 16 | expect(current_path).to eq root_path 17 | expect(page).to have_content("投稿") 18 | 19 | # 投稿処理 20 | expect do 21 | click_link("投稿") 22 | expect(current_path).to eq new_tweet_path 23 | fill_in 'tweet_title', with: "結合テスト" 24 | fill_in 'md-textarea', with: "結合テスト" 25 | find('input[type="submit"]').click 26 | end.to change(Tweet, :count).by(1) 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/views/comments/_comment.html.haml: -------------------------------------------------------------------------------- 1 | %li.tweet-show__comment__item 2 | %div{id: "comment_#{comment.id}"} 3 | .tweet-show__comment__item__top 4 | = link_to user_path(comment.user.id) do 5 | .tweet-show__comment__item__top--user 6 | = image_tag "#{comment.user.avatar}", class: "avatar" 7 | .tweet-show__comment__item__top--nickname 8 | = comment.user.nickname 9 | .tweet-show__comment__item__top--date 10 | = comment.created_at.strftime("%Y-%m-%d %H:%M") 11 | .tweet-show__comment__item--text 12 | = simple_format(comment.content) 13 | .tweet-show__comment__item--edit 14 | - if current_user_has?(comment) 15 | = link_to "編集", edit_tweet_comment_path(@tweet.id, comment), remote: true, class: "tweet-show__comment__item--edit--left" 16 | = link_to "削除", tweet_comment_path(@tweet, comment.id), method: :delete, remote: true, data: { confirm: '削除してよろしいですか?' } -------------------------------------------------------------------------------- /docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | container_name: mynote 5 | image: mysql:5.7 6 | environment: 7 | MYSQL_DATABASE: mynote_development 8 | MYSQL_USER: root 9 | MYSQL_PASSWORD: password 10 | MYSQL_ROOT_PASSWORD: password 11 | ports: 12 | - "4306:3306" 13 | volumes: 14 | - mysql-data:/var/lib/mysql #データの永続化のために必要 15 | 16 | web: 17 | build: . 18 | command: bash -c "rm -f tmp/pids/server.pid && bundle exec rails s -p 3000 -b '0.0.0.0'" 19 | volumes: 20 | - .:/myapp 21 | - bundle:/usr/local/bundle #bundle installした後buildし直さなくてよくなる 22 | ports: 23 | - "3000:3000" 24 | depends_on: 25 | - db 26 | tty: true #コンテナ上でbinding.pryするために必要 27 | stdin_open: true #コンテナ上でbinding.pryするために必要 28 | links: 29 | - db 30 | volumes: 31 | mysql-data: 32 | bundle: #bundle installした後buildし直さなくてよくなる 33 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | puts "\n== Updating database ==" 24 | system! 'bin/rails db:migrate' 25 | 26 | puts "\n== Removing old logs and tempfiles ==" 27 | system! 'bin/rails log:clear tmp:clear' 28 | 29 | puts "\n== Restarting application server ==" 30 | system! 'bin/rails restart' 31 | end 32 | -------------------------------------------------------------------------------- /docker/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | 2 | # 初期状態の設定ファイル 3 | 4 | user nginx; 5 | # workerプロセスの起動数を定義 autoに設定すると最適値を試みる 6 | worker_processes 1; 7 | 8 | error_log /var/log/nginx/error.log warn; 9 | pid /var/run/nginx.pid; 10 | 11 | 12 | events { 13 | #一つのworkerプロセグが開ける最大コネクション数 14 | 15 | worker_connections 1024; 16 | } 17 | 18 | 19 | http { 20 | include /etc/nginx/mime.types; 21 | default_type application/octet-stream; 22 | 23 | log_format main '$remote_addr - $remote_user [$time_local] "$request" ' 24 | '$status $body_bytes_sent "$http_referer" ' 25 | '"$http_user_agent" "$http_x_forwarded_for"'; 26 | 27 | access_log /var/log/nginx/access.log main; 28 | 29 | sendfile on; 30 | #tcp_nopush on; 31 | 32 | keepalive_timeout 65; 33 | 34 | #gzip on; 35 | 36 | #これでdefault.confなどをincludeしてる 37 | include /etc/nginx/conf.d/*.conf; 38 | } 39 | 40 | -------------------------------------------------------------------------------- /app/views/tweets/tags.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .tweet-tags 3 | .tweet-tags__title 4 | -# タグ検索機能を実装予定 5 | -# .tweet-tags__title__left 6 | -# %input{ type: "text", class: "header-top__right--search", id: "tag-input", placeholder: "検索するタグを入力してください"} 7 | -# -# %input{ type: "submit", id: "tag-submit"} 8 | -# #makeImg.searchArea 9 | -# %input#inText.searchText{type: "text"} 10 | -# .searchButton 11 | -# %img.searchImg{src: "search30.png"} 12 | .tweet-tags__title__right 13 | %p.tweet-tags__title--body 14 | = "人気タグ" 15 | %ul.tweet-tags__items 16 | - @tags.each do |tag| 17 | %li.tweet-tags__item 18 | = link_to tweets_path(tag_name: tag.name), class: "tweet-tags__item--link" do 19 | %i.btn.btn-outline-dark.tweet-tags__item--btn 20 | = tag.name 21 | .tweet-tags__item--body 22 | = tag.count 23 | = "件" 24 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /config/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 MyApp 10 | class Application < Rails::Application 11 | # Initialize configuration defaults for originally generated Rails version. 12 | config.load_defaults 5.2 13 | config.i18n.default_locale = :ja 14 | config.time_zone = 'Tokyo' 15 | # 不要なファイルを作成しない 16 | config.generators do |g| 17 | g.stylesheets false 18 | g.javascripts false 19 | g.helper false 20 | g.test_framework false 21 | end 22 | 23 | # Settings in config/environments/* take precedence over those specified here. 24 | # Application configuration can go into files in config/initializers 25 | # -- all .rb files in that directory are automatically loaded after loading 26 | # the framework and any gems in your application. 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /app/assets/javascripts/tweet_show.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $(document).on("click", ".donation-btn", function () { 3 | $(".donation").fadeIn(200); 4 | }); 5 | $(document).on("click", ".donation-modal--close--btn", function () { 6 | console.log("a"); 7 | $(".donation").fadeOut(200); 8 | }); 9 | $(document).on("submit", "#donation-form", function () { 10 | if ($(".donation-modal--body--input").val() < 1) { 11 | alert("1円以上で入力してください"); 12 | return; 13 | } else if ($(".donation-modal--body--input").val() > 999999) { 14 | alert("100万円以下で入力してください"); 15 | return; 16 | } 17 | $(".donation").fadeOut(); 18 | }); 19 | 20 | // いいねした人一覧を表示する機能。現在停止中(2020/10/25) 21 | // $(".tweet-show__content__like--link").on("click", function () { 22 | // $(".tweet-show__content__like--bgc").fadeToggle(); 23 | // }); 24 | // $(".tweet-show__content__like--close").click(function () { 25 | // $(".tweet-show__content__like--bgc").fadeToggle(); 26 | // }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_tweet-tags.scss: -------------------------------------------------------------------------------- 1 | 2 | .tags{ 3 | margin: 4px; 4 | } 5 | 6 | .tweet-tags{ 7 | @include common-sizing; 8 | margin: 30px auto; 9 | border: 1px solid $header_border; 10 | background-color: white; 11 | box-shadow: 0px 4px 6px #ccc; 12 | &__title{ 13 | height: 70px; 14 | text-align: center; 15 | line-height: 70px; 16 | color: $header_tags; 17 | border-bottom: 1px solid $header_border; 18 | // display: flex; 19 | &--body{ 20 | font-size: 19px; 21 | &:hover{ 22 | color: black; 23 | } 24 | } 25 | &__left{ 26 | // padding-right: 260px; 27 | // padding-left: 20px; 28 | } 29 | } 30 | &__items{ 31 | display: flex; 32 | flex-wrap: wrap; 33 | } 34 | &__item{ 35 | display: inline-block; 36 | // width: 33.3%; 37 | padding: 30px; 38 | &--btn{ 39 | // width: 60%; 40 | } 41 | &--link{ 42 | display: flex; 43 | } 44 | &--body{ 45 | color: black; 46 | padding: 8px; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /app/controllers/notifications_controller.rb: -------------------------------------------------------------------------------- 1 | class NotificationsController < ApplicationController 2 | def index 3 | @notifications = current_user.passive_notifications.includes(%i[visitor tweet room visited]) 4 | .page(params[:page]).per(20).where.not(visitor_id: current_user.id) 5 | @notifications.where(checked: false).each do |notification| 6 | notification.update_attributes(checked: true) 7 | end 8 | @activities = current_user.active_notifications.includes(%i[visitor tweet room visited]).page(params[:page]).per(20) 9 | end 10 | 11 | # bulletを無効にするメソッド--------------------------------------------------------------------------------------- 12 | around_action :skip_bullet, if: -> { defined?(Bullet) } 13 | 14 | def skip_bullet 15 | previous_value = Bullet.enable? 16 | Bullet.enable = false 17 | yield 18 | ensure 19 | Bullet.enable = previous_value 20 | end 21 | # ------------------------------------------------------------------------------------------------------------------------- 22 | end 23 | -------------------------------------------------------------------------------- /app/views/notifications/index.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .container 3 | .notification-top 4 | .notification-top__title 5 | .notification-top__title__left.notification-top__title--show 6 | %h2.notification-top__title--name 7 | 通知 8 | .notification-top__title__right.notification-top__title--down 9 | %h2.notification-top__title--name 自分の活動 10 | %ul.notification-top__items 11 | - if @notifications.exists? 12 | = render @notifications 13 | = paginate @notifications 14 | - else 15 | %p.text-center.notification-top__items--none 16 | 通知はありません 17 | %ul.notification-top__activities 18 | - if @activities.exists? 19 | -# 同ページで2回renderすると、正しく読み込まれないためeach文を使用しています 20 | -# = render @activities 21 | - @activities.each do |activity| 22 | = render "activities/activity", activity: activity 23 | = paginate @activities 24 | - else 25 | %p.text-center.notification-top__items--none 26 | 活動はありません 27 | -------------------------------------------------------------------------------- /app/views/tweets/_tweet.html.haml: -------------------------------------------------------------------------------- 1 | %li.tweet-index__item 2 | .tweet-index__item__user 3 | %span 4 | = link_to user_path(tweet.user.id), class: "avatar--text" do 5 | = image_tag "#{tweet.user.avatar}", class: "avatar" 6 | .tweet-index__item__user--nickname 7 | = tweet.user.nickname 8 | = link_to tweet_path(tweet.id) do 9 | .tweet-index__item--sub 10 | = tweet.created_at.strftime("%Y-%m-%d") 11 | = link_to tweet_path(tweet.id) do 12 | %p.tweet-index__item--title 13 | = tweet.title 14 | - if tweet.image? 15 | = image_tag "#{tweet.image}", class: "tweet-index__item--image" 16 | - else 17 | = image_tag asset_path("default.png"), class: "tweet-index__item--image" 18 | .tweet-index__item--tags 19 | = render '/tweets/tag_list', tag_list: tweet.tag_list 20 | .tweet-index__item__footer 21 | .tweet-index__item--like 22 | %div{id: "like_#{tweet.id}"} 23 | = render 'likes/like', tweet: tweet 24 | = link_to "もっと見る", tweet_path(tweet.id), class: "tweet-index__item--show" 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/controllers/rooms_controller.rb: -------------------------------------------------------------------------------- 1 | class RoomsController < ApplicationController 2 | def index 3 | @rooms = current_user.rooms.includes(:messages).order("messages.created_at desc") 4 | end 5 | 6 | def create 7 | @room = Room.create 8 | @join_current_user = Entry.create(user_id: current_user.id, room_id: @room.id) 9 | @join_user = Entry.create(join_room_params) 10 | @first_message = @room.messages.create(user_id: current_user.id, message: "初めまして!") 11 | redirect_to room_path(@room.id) 12 | end 13 | 14 | def show 15 | @room = Room.find(params[:id]) 16 | if Entry.where(user_id: current_user.id, room_id: @room.id).present? 17 | @messages = @room.messages.includes(:user).order("created_at asc") 18 | @message = Message.new 19 | @entries = @room.entries.includes(:user) 20 | else 21 | flash[:alert] = "不正な操作です" 22 | redirect_back(fallback_location: root_path) 23 | end 24 | end 25 | 26 | private 27 | 28 | def join_room_params 29 | params.require(:entry).permit(:user_id, :room_id).merge(room_id: @room.id) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/views/devise/shared/_links.html.haml: -------------------------------------------------------------------------------- 1 | - if controller_name != 'sessions' 2 | = link_to "Log in", new_session_path(resource_name) 3 | %br/ 4 | - if devise_mapping.registerable? && controller_name != 'registrations' 5 | = link_to "Sign up", new_registration_path(resource_name) 6 | %br/ 7 | - if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' 8 | = link_to "Forgot your password?", new_password_path(resource_name) 9 | %br/ 10 | - if devise_mapping.confirmable? && controller_name != 'confirmations' 11 | = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) 12 | %br/ 13 | - if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' 14 | = link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) 15 | %br/ 16 | - if devise_mapping.omniauthable? 17 | - resource_class.omniauth_providers.each do |provider| 18 | = link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) 19 | %br/ 20 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 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 | # Install JavaScript dependencies if using Yarn 21 | # system('bin/yarn') 22 | 23 | # puts "\n== Copying sample files ==" 24 | # unless File.exist?('config/database.yml') 25 | # cp 'config/database.yml.sample', 'config/database.yml' 26 | # end 27 | 28 | puts "\n== Preparing database ==" 29 | system! 'bin/rails db:setup' 30 | 31 | puts "\n== Removing old logs and tempfiles ==" 32 | system! 'bin/rails log:clear tmp:clear' 33 | 34 | puts "\n== Restarting application server ==" 35 | system! 'bin/rails restart' 36 | end 37 | -------------------------------------------------------------------------------- /app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | before_action :set_tweet, only: %i[create edit update destroy] 3 | before_action :set_comment, only: %i[edit update destroy] 4 | 5 | def create 6 | @comment = @tweet.comments.new(comment_params) 7 | if @comment.save 8 | @tweet.create_notification_comment!(current_user, @comment.id) 9 | @comment = Comment.new 10 | end 11 | gets_all_comments 12 | end 13 | 14 | def edit; end 15 | 16 | def update 17 | gets_all_comments if @comment.update(comment_params) 18 | end 19 | 20 | def destroy 21 | gets_all_comments if @comment.destroy 22 | end 23 | 24 | private 25 | 26 | def set_tweet 27 | @tweet = Tweet.find(params[:tweet_id]) 28 | end 29 | 30 | def set_comment 31 | @comment = Comment.find(params[:id]) 32 | end 33 | 34 | def gets_all_comments 35 | @comments = @tweet.comments.includes(:user).order('created_at desc') 36 | end 37 | 38 | def comment_params 39 | params.require(:comment).permit(:content).merge(user_id: current_user.id) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | app: 4 | build: 5 | context: . 6 | dockerfile: ./docker/rails/Dockerfile 7 | command: bundle exec puma -C config/puma.rb 8 | ports: 9 | - "3000:3000" 10 | volumes: 11 | - .:/app 12 | - /var/tmp 13 | # socketファイルの共有 14 | - sockets:/app/tmp/sockets 15 | tty: true 16 | depends_on: 17 | - db 18 | extends: 19 | file: ./docker/mysql/password.yml 20 | service: password 21 | environment: 22 | - EDITOR=vim 23 | db: 24 | build: 25 | context: . 26 | dockerfile: ./docker/mysql/Dockerfile 27 | ports: 28 | - "3306:3306" 29 | volumes: 30 | - db_data:/var/lib/mysql 31 | extends: 32 | file: ./docker/mysql/password.yml 33 | service: password 34 | nginx: 35 | build: 36 | context: . 37 | dockerfile: ./docker/nginx/Dockerfile 38 | ports: 39 | - "80:80" 40 | #socketファイルの共有 41 | volumes: 42 | - sockets:/app/tmp/sockets 43 | depends_on: 44 | - app 45 | 46 | volumes: 47 | db_data: 48 | sockets: 49 | -------------------------------------------------------------------------------- /app/assets/javascripts/notification.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | // それぞれの変数を定義 3 | var nt_left = $(".notification-top__title__left"); 4 | var nt_right = $(".notification-top__title__right"); 5 | var nt_items = $(".notification-top__items"); 6 | var nt_activities = $(".notification-top__activities"); 7 | 8 | // 左側(通知欄)をクリックしたら、左側に適切なクラスを除外&追加する。同時に右側に適切なクラスを除外&追加。 9 | nt_left.click(function () { 10 | nt_items.fadeIn(0); 11 | nt_activities.fadeOut(0); 12 | nt_left.addClass("notification-top__title--show"); 13 | nt_left.removeClass("notification-top__title--down"); 14 | nt_right.removeClass("notification-top__title--show"); 15 | nt_right.addClass("notification-top__title--down"); 16 | }); 17 | 18 | // 右側(自分の行動)をクリックしたら、右側に適切なクラスを除外&追加する。同時に左側に適切なクラスを除外&追加。 19 | nt_right.click(function () { 20 | nt_items.fadeOut(0); 21 | nt_activities.fadeIn(0); 22 | nt_right.addClass("notification-top__title--show"); 23 | nt_right.removeClass("notification-top__title--down"); 24 | nt_left.removeClass("notification-top__title--show"); 25 | nt_left.addClass("notification-top__title--down"); 26 | }); 27 | }); 28 | -------------------------------------------------------------------------------- /app/views/devise/registrations/new.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .login 3 | .login__field 4 | .center 5 | = render 'devise/sessions/testuser' 6 | = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| 7 | = devise_error_messages! 8 | .login__title 9 | 新規登録 10 | = render "users/form", f: f 11 | .login__field 12 | .center 13 | = f.submit "登録する", class: "btn btn-success" 14 | 15 | :javascript 16 | $(function(){ 17 | $fileField = $('#file') 18 | 19 | // 選択された画像を取得し表示 20 | $($fileField).on('change', $fileField, function(e) { 21 | file = e.target.files[0] 22 | reader = new FileReader(), 23 | $preview = $("#img_field"); 24 | 25 | reader.onload = (function(file) { 26 | return function(e) { 27 | $preview.empty(); 28 | $preview.append($('').attr({ 29 | src: e.target.result, 30 | class: "avatar", 31 | title: file.name 32 | })); 33 | }; 34 | })(file); 35 | reader.readAsDataURL(file); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /app/controllers/messages_controller.rb: -------------------------------------------------------------------------------- 1 | class MessagesController < ApplicationController 2 | before_action :set_room, only: %i[create edit update destroy] 3 | before_action :set_message, only: %i[edit update destroy] 4 | 5 | def create 6 | @message = Message.create(message_params) 7 | if @message.save 8 | @room.create_notification_message!(current_user, @message.id) 9 | @message = Message.new 10 | end 11 | gets_entries_all_messages 12 | end 13 | 14 | def edit; end 15 | 16 | def update 17 | gets_entries_all_messages if @message.update(message_params) 18 | end 19 | 20 | def destroy 21 | gets_entries_all_messages if @message.destroy 22 | end 23 | 24 | private 25 | 26 | def set_room 27 | @room = Room.find(params[:message][:room_id]) 28 | end 29 | 30 | def set_message 31 | @message = Message.find(params[:id]) 32 | end 33 | 34 | def gets_entries_all_messages 35 | @messages = @room.messages.includes(:user).order("created_at asc") 36 | @entries = @room.entries 37 | end 38 | 39 | def message_params 40 | params.require(:message).permit(:user_id, :message, :room_id).merge(user_id: current_user.id) 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_donation.scss: -------------------------------------------------------------------------------- 1 | .donation{ 2 | display: none; 3 | position: fixed; 4 | top: 0; 5 | right: 0; 6 | bottom: 0; 7 | left: 0; 8 | background-color: rgba(0,0,0,.5); 9 | opacity: 1; 10 | &-modal{ 11 | border: 1px solid $header_border; 12 | position: fixed; 13 | z-index: 3; 14 | top: 30%; 15 | background-color: white; 16 | padding: 30px; 17 | border-radius: 10px; 18 | 19 | .form{ 20 | display: flex; 21 | justify-content: space-between; 22 | } 23 | &--header{ 24 | height: 50px; 25 | border-bottom: 1px solid $header_border; 26 | font-size: 18px; 27 | text-align: center; 28 | line-height: 50px; 29 | } 30 | &--close{ 31 | display: flex; 32 | justify-content: flex-end; 33 | } 34 | &--body{ 35 | padding: 20px 0; 36 | display: flex; 37 | justify-content: center; 38 | &--input{ 39 | width: 300px; 40 | padding: 20px 20px; 41 | text-align: center; 42 | border-radius: 10px; 43 | border: 1px solid #e1e1e1; 44 | } 45 | } 46 | &--submit{ 47 | display: flex; 48 | justify-content: center; 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /config/initializers/content_security_policy.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Define an application-wide content security policy 4 | # For further information see the following documentation 5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy 6 | 7 | # Rails.application.config.content_security_policy do |policy| 8 | # policy.default_src :self, :https 9 | # policy.font_src :self, :https, :data 10 | # policy.img_src :self, :https, :data 11 | # policy.object_src :none 12 | # policy.script_src :self, :https 13 | # policy.style_src :self, :https 14 | 15 | # # Specify URI for violation reports 16 | # # policy.report_uri "/csp-violation-report-endpoint" 17 | # end 18 | 19 | # If you are using UJS then enable automatic nonce generation 20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) } 21 | 22 | # Report CSP violations to a specified URI 23 | # For further information see the following documentation: 24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only 25 | # Rails.application.config.content_security_policy_report_only = true 26 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /app/views/users/_info.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .user-show 3 | .user-show__top 4 | .user-show__top--icon 5 | = image_tag "#{@user.avatar}", class: "user-show__top--avatar" 6 | - if user_signed_in? && current_user == @user 7 | .user-show__top__body 8 | .user-show__top--follow 9 | = link_to 'ユーザー編集', edit_user_path(current_user.id), class: "user-show__top--btn" 10 | .user-show__top--contact 11 | = link_to 'ログアウト', destroy_user_session_path, method: :delete, class: "user-show__top--btn" 12 | - else 13 | .user-show__top__body 14 | .user-show__top--follow 15 | = render "/relationships/follow" 16 | .user-show__top--contact 17 | = render "/users/contact" 18 | .user-show__bottom 19 | .user-show__bottom--name 20 | %strong 21 | = @user.nickname 22 | .user-show__bottom__follow 23 | .user-show__bottom__follow--people 24 | = link_to following_user_path(@user) do 25 | = @user.following.count 26 | フォロー 27 | .user-show__bottom__follow--people 28 | = link_to followers_user_path(@user) do 29 | = @user.followers.count 30 | フォロワー -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_new-btn.scss: -------------------------------------------------------------------------------- 1 | @mixin new-btn($color) { 2 | right: 5%; 3 | width: 140px; 4 | height: 140px; 5 | border-radius: 50%; 6 | display: flex; 7 | flex-direction: column; 8 | position: fixed; 9 | bottom: 20px; 10 | background-color: $color; 11 | border: 1px solid #ccc; 12 | opacity: 1; 13 | box-shadow: 0px 4px 6px #ccc; 14 | transition: all 0.3s ease 0s; 15 | &:hover { 16 | color: #333; 17 | } 18 | } 19 | 20 | @mixin new-btn--text($top, $left) { 21 | top: $top; 22 | left: $left; 23 | font-weight: bold; 24 | position: absolute; 25 | font-size: 16px; 26 | text-align: center; 27 | } 28 | 29 | .new-btn { 30 | display: block; 31 | &__tweet { 32 | @include new-btn($new_green); 33 | &--text { 34 | @include new-btn--text($top: 25px, $left: 53px); 35 | & p { 36 | color: #333; 37 | } 38 | } 39 | &--icon { 40 | color: #333; 41 | position: absolute; 42 | left: 35%; 43 | top: 30%; 44 | font-size: 3rem; 45 | } 46 | } 47 | &__session { 48 | @include new-btn($login_yellow); 49 | &--text { 50 | @include new-btn--text($top: 55px, $left: 15px); 51 | & p { 52 | color: #333; 53 | } 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /spec/models/donation_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Donation, type: :model do 4 | let(:donation) { create(:donation) } 5 | describe '#create' do 6 | context '保存できる場合' do 7 | it "全てのパラメーターが揃っていれば保存できる" do 8 | expect(donation).to be_valid 9 | end 10 | 11 | it "amountが1以上なら保存できる" do 12 | donation.amount = 1 13 | expect(donation).to be_valid 14 | end 15 | end 16 | 17 | context "保存できない場合" do 18 | it "user_idがnilの場合は保存できない" do 19 | donation.user_id = nil 20 | donation.valid? 21 | expect(donation.errors[:user]).to include("を入力してください") 22 | end 23 | 24 | it "tweet_idがnilの場合は保存できない" do 25 | donation.tweet_id = nil 26 | donation.valid? 27 | expect(donation.errors[:tweet]).to include("を入力してください") 28 | end 29 | 30 | it "amountがnilの場合は保存できない" do 31 | donation.amount = nil 32 | donation.valid? 33 | expect(donation.errors[:amount]).to include("を入力してください") 34 | end 35 | 36 | it "amountが0の場合は保存できない" do 37 | donation.amount = 0 38 | donation.valid? 39 | expect(donation.errors[:amount]).to include("は0より大きい値にしてください") 40 | end 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_room-index.scss: -------------------------------------------------------------------------------- 1 | .room-index-hover{ 2 | &:hover{ 3 | background-color: #e6e6e6; 4 | } 5 | } 6 | 7 | .room-index{ 8 | @include common-sizing; 9 | margin: 0 auto; 10 | border: 1px solid $header_border; 11 | background-color: white; 12 | &__title{ 13 | height: 100px; 14 | border-bottom: 1px solid $header_border; 15 | text-align: center; 16 | line-height: 100px; 17 | font-size: 25px; 18 | } 19 | &__items{ 20 | &__item{ 21 | border-top: 1px solid $header_border; 22 | height: 100px; 23 | // width: 798px; 24 | color: #333; 25 | position: relative; 26 | &--icon{ 27 | height: 60px; 28 | width: 60px; 29 | border-radius: 100px; 30 | float: left; 31 | margin: 20px 20px; 32 | } 33 | &__content{ 34 | display: block; 35 | padding-left: 90px; 36 | &--body{ 37 | padding: 20px; 38 | } 39 | &:hover{ 40 | color: #505050; 41 | } 42 | &--message{ 43 | color: $header_tags; 44 | } 45 | &--date{ 46 | color: $header_tags; 47 | position: absolute; 48 | bottom: 5px; 49 | right: 5px; 50 | } 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/assets/javascripts/preview.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | // previewボタンが押されたらイベント発火 3 | $("#preview").on("click", function () { 4 | var text = $("#md-textarea").val(); 5 | if (text == "") { 6 | return false; 7 | } else if ($("#md-textarea").val().length > 1000) { 8 | alert("本文を1,000文字以内で入力してください。"); 9 | return false; 10 | } 11 | // ajax通信を開始。 12 | $.ajax({ 13 | url: "/api/tweets/preview", //apiフォルダのtweetsコントローラーのpreviewメソッドを参照 14 | type: "GET", 15 | dataType: "json", 16 | data: { text: text }, 17 | }) 18 | .done(function (html) { 19 | // ajax成功したら、テキストエリアを非表示にする。 20 | $("#md-textarea").parent().css("display", "none"); 21 | $("#preview-area").show().empty().append(html.text); 22 | // markdownボタンとpreviewボタンのdisabledを入れ替える。 23 | $("#markdown").removeClass("disabled"); 24 | $("#preview").addClass("disabled"); 25 | }) 26 | .fail(function () { 27 | alert("1,000文字以内で入力してください。"); 28 | }); 29 | }); 30 | 31 | // markdownボタンが押されたらイベント発火 32 | $("#markdown").on("click", function () { 33 | $("#md-textarea").parent().css("display", ""); 34 | $("#preview-area").hide(); 35 | $("#preview").removeClass("disabled"); 36 | $("#markdown").addClass("disabled"); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | 3 | devise_for :users, :controllers => { 4 | sessions: 'users/sessions' # Devise::sessionsクラスを継承していることを明示 5 | } 6 | devise_scope :user do 7 | post 'users/guest_sign_in', to: 'users/sessions#new_guest' # ゲストログイン用メソッド 8 | end 9 | 10 | root to: 'tweets#index' # トップページ 11 | namespace :tweets do 12 | resources :searches, only: :index # 検索機能 13 | end 14 | resources :tweets do 15 | collection do 16 | get :likes, :tags # 人気投稿とタグ一覧 17 | end 18 | resources :donations, only: [:create, :destroy] # 寄付機能 19 | resources :likes, only: [:create, :destroy] # いいね機能 20 | resources :comments, only: [:create, :edit, :update, :destroy] # コメント機能 21 | end 22 | 23 | namespace :api, format: 'json' do 24 | get 'tweets/preview' # プレビュー用API。controllers/api/tweets_controller#previewメソッドを参照 25 | end 26 | 27 | resources :users, only: [:index, :show, :edit, :update] do 28 | member do 29 | get :likes, :following, :followers, :setting 30 | end 31 | get 'timeline', on: :collection 32 | end 33 | resources :relationships, only: [:create, :destroy] 34 | resources :messages, only: [:create, :edit, :update, :destroy] 35 | resources :rooms, only: [:index, :show, :create] 36 | resources :notifications, only: :index 37 | 38 | end -------------------------------------------------------------------------------- /db/migrate/20200523235324_add_missing_unique_indices.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from acts_as_taggable_on_engine (originally 2) 2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0') 3 | class AddMissingUniqueIndices < ActiveRecord::Migration[4.2]; end 4 | else 5 | class AddMissingUniqueIndices < ActiveRecord::Migration; end 6 | end 7 | AddMissingUniqueIndices.class_eval do 8 | def self.up 9 | # add_index ActsAsTaggableOn.tags_table, :name, unique: true 10 | 11 | # remove_index ActsAsTaggableOn.taggings_table, :tag_id if index_exists?(ActsAsTaggableOn.taggings_table, :tag_id) 12 | remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_taggable_context_idx' 13 | add_index ActsAsTaggableOn.taggings_table, 14 | [:tag_id, :taggable_id, :taggable_type, :context, :tagger_id, :tagger_type], 15 | unique: true, name: 'taggings_idx' 16 | end 17 | 18 | def self.down 19 | remove_index ActsAsTaggableOn.tags_table, :name 20 | 21 | remove_index ActsAsTaggableOn.taggings_table, name: 'taggings_idx' 22 | 23 | add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists?(ActsAsTaggableOn.taggings_table, :tag_id) 24 | add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx' 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/assets/javascripts/tweet_form.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | $fileField = $('#file') 3 | 4 | // 選択された画像を取得し表示 5 | $($fileField).on('change', $fileField, function (e) { 6 | file = e.target.files[0] 7 | reader = new FileReader(), 8 | $preview = $("#img_field"); 9 | 10 | reader.onload = (function (file) { 11 | return function (e) { 12 | $preview.empty(); 13 | $preview.append($('').attr({ 14 | src: e.target.result, 15 | width: "100%", 16 | class: "tweet-form__bonus__preview--image", 17 | title: file.name 18 | })); 19 | }; 20 | })(file); 21 | reader.readAsDataURL(file); 22 | }); 23 | 24 | $("#md-textarea").keyup(function () { 25 | $("#tweet-text-count").text($(this).val().length + '/1000'); 26 | }); 27 | 28 | if (document.location.href.match(/\/tweets\/\d+\/edit/)) { 29 | $("#tweet-text-count").text($("#md-textarea").val().length + '/1000'); 30 | } 31 | 32 | $("#tweet-form").submit(function () { 33 | if ($("#md-textarea").val().length > 1000) { 34 | alert('本文を1,000文字以内で入力してください。'); 35 | return false; 36 | } 37 | }); 38 | 39 | // 実装予定のタグ検索機能です! 40 | // $("#tag-submit").on("click", function () { 41 | // let input = $("#tag-input").val(); 42 | // window.location.href = `http://localhost:3000/tweets?tag_name=${input}` 43 | // }); 44 | 45 | }); 46 | 47 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_self 14 | *= require jquery.tagit 15 | *= require tagit.ui-zendesk 16 | */ 17 | 18 | @import "config/colors"; 19 | @import "reset"; 20 | @import "mixin/media"; 21 | @import "mixin/tweet-items"; 22 | @import "modules/common"; 23 | @import "modules/tweet-index"; 24 | @import "modules/tweet-show"; 25 | @import "modules/tweet-comment"; 26 | @import "modules/tweet-tags"; 27 | @import "modules/tweet-form"; 28 | @import "modules/new-btn"; 29 | @import "modules/user-show"; 30 | @import "modules/user-index"; 31 | @import "modules/flash"; 32 | @import "modules/header"; 33 | @import "modules/notifications"; 34 | @import "modules/room-index"; 35 | @import "modules/room-show"; 36 | @import "modules/session"; 37 | @import "modules/donation"; 38 | -------------------------------------------------------------------------------- /app/views/devise/registrations/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h2 2 | -# Edit #{resource_name.to_s.humanize} 3 | = form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| 4 | = render "devise/shared/error_messages", resource: resource 5 | .field 6 | = f.label :email 7 | %br/ 8 | = f.email_field :email, autofocus: true, autocomplete: "email" 9 | - if devise_mapping.confirmable? && resource.pending_reconfirmation? 10 | %div 11 | Currently waiting confirmation for: #{resource.unconfirmed_email} 12 | .field 13 | = f.label :password 14 | %i (leave blank if you don't want to change it) 15 | %br/ 16 | = f.password_field :password, autocomplete: "new-password" 17 | - if @minimum_password_length 18 | %br/ 19 | %em 20 | = @minimum_password_length 21 | characters minimum 22 | .field 23 | = f.label :password_confirmation 24 | %br/ 25 | = f.password_field :password_confirmation, autocomplete: "new-password" 26 | .field 27 | = f.label :current_password 28 | %i (we need your current password to confirm your changes) 29 | %br/ 30 | = f.password_field :current_password, autocomplete: "current-password" 31 | .actions 32 | = f.submit "Update" 33 | %h3 Cancel my account 34 | %p 35 | Unhappy? #{button_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete} 36 | = link_to "Back", :back -------------------------------------------------------------------------------- /db/migrate/20200523235323_acts_as_taggable_on_migration.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from acts_as_taggable_on_engine (originally 1) 2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0') 3 | class ActsAsTaggableOnMigration < ActiveRecord::Migration[4.2]; end 4 | else 5 | class ActsAsTaggableOnMigration < ActiveRecord::Migration; end 6 | end 7 | ActsAsTaggableOnMigration.class_eval do 8 | def self.up 9 | create_table ActsAsTaggableOn.tags_table do |t| 10 | t.string :name 11 | t.timestamps 12 | end 13 | 14 | create_table ActsAsTaggableOn.taggings_table do |t| 15 | t.references :tag, foreign_key: { to_table: ActsAsTaggableOn.tags_table } 16 | 17 | # You should make sure that the column created is 18 | # long enough to store the required class names. 19 | t.references :taggable, polymorphic: true 20 | t.references :tagger, polymorphic: true 21 | 22 | # Limit is created to prevent MySQL error on index 23 | # length for MyISAM table type: http://bit.ly/vgW2Ql 24 | t.string :context, limit: 20 25 | 26 | t.datetime :created_at 27 | end 28 | 29 | add_index ActsAsTaggableOn.taggings_table, :tag_id 30 | add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :context], name: 'taggings_taggable_context_idx' 31 | end 32 | 33 | def self.down 34 | drop_table ActsAsTaggableOn.taggings_table 35 | drop_table ActsAsTaggableOn.tags_table 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: mysql2 3 | encoding: utf8 4 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 5 | 6 | # ローカル環境(rails sで起動) 7 | development: 8 | <<: *default 9 | database: mynote_development 10 | username: root 11 | password: 12 | socket: /tmp/mysql.sock 13 | # docker環境(docker-compose upで起動) config/puma.rbも変更すること 14 | # development: 15 | # <<: *default 16 | # database: mynote_development 17 | # username: root 18 | # password: password 19 | # host: db 20 | # 21 | # test: 22 | # <<: *default 23 | # database: app_test 24 | 25 | # EC2からRDSに接続 26 | production: 27 | <<: *default 28 | adapter: mysql2 29 | charset: utf8 30 | database: <%= Rails.application.credentials.aws[:rds][:DATABASE_NAME] %> 31 | username: <%= Rails.application.credentials.aws[:rds][:DATABASE_USERNAME] %> 32 | password: <%= Rails.application.credentials.aws[:rds][:DATABASE_PASSWORD] %> 33 | host: <%= Rails.application.credentials.aws[:rds][:DATABASE_HOST] %> 34 | # EC2内のMySQLに接続 35 | # production: 36 | # <<: *default 37 | # database: my_app_production 38 | # username: root 39 | # password: password 40 | # socket: /var/lib/mysql/mysql.sock 41 | 42 | # ECSからRDSに接続 43 | # production: 44 | # <<: *default 45 | # database: <%= ENV['DB_DATABASE'] %> 46 | # adapter: mysql2 47 | # encoding: utf8mb4 48 | # charset: utf8mb4 49 | # collation: utf8mb4_general_ci 50 | # host: <%= ENV['DB_HOST'] %> 51 | # username: <%= ENV['DB_USERNAME'] %> 52 | # password: <%= ENV['DB_PASSWORD'] %> 53 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_common.scss: -------------------------------------------------------------------------------- 1 | .wrapper{ 2 | height: 100%; 3 | width: 100%; 4 | background-color: #F5F5F5; 5 | margin-top: 5px; 6 | padding: 30px 0; 7 | } 8 | 9 | @mixin common-sizing{ 10 | @include sm{ 11 | width: 450px; 12 | } 13 | @include sm-max{ 14 | width: 300px; 15 | } 16 | @include md{ 17 | width: 650px; 18 | } 19 | @include lg{ 20 | width: 800px; 21 | } 22 | } 23 | 24 | .jc-center{ 25 | display: flex; 26 | justify-content: center; 27 | } 28 | 29 | .avatar { 30 | width: 39px; 31 | height: 39px; 32 | border-radius: 100px; 33 | object-fit: cover; 34 | padding-bottom: 5px; 35 | &--text:hover{ 36 | color: #505050; 37 | } 38 | } 39 | 40 | .pagination { 41 | justify-content: center; 42 | } 43 | 44 | .active{ 45 | display: block; 46 | } 47 | 48 | .hide{ 49 | display: none; 50 | } 51 | 52 | .text-center{ 53 | text-align: center; 54 | } 55 | 56 | .cursor-pointer{ 57 | cursor: pointer; 58 | } 59 | 60 | .center{ 61 | display: flex; 62 | justify-content: center; 63 | } 64 | 65 | .color-black .color-black a{ 66 | color: #333; 67 | } 68 | 69 | .black-important{ 70 | color: #333 !important; 71 | } 72 | 73 | .a-hover{ 74 | color: black; 75 | &:hover{ 76 | color: #555; 77 | } 78 | } 79 | 80 | .display-flex{ 81 | display: flex; 82 | } 83 | 84 | .label-form{ 85 | display: block; 86 | width: 100%; 87 | height: 100%; 88 | cursor: pointer; 89 | } 90 | 91 | .pdw-15{ 92 | padding: 0 15px; 93 | } 94 | 95 | .mt05rem{ 96 | margin-top: 0.5rem; 97 | } -------------------------------------------------------------------------------- /db/migrate/20200523235328_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from acts_as_taggable_on_engine (originally 6) 2 | if ActiveRecord.gem_version >= Gem::Version.new('5.0') 3 | class AddMissingIndexesOnTaggings < ActiveRecord::Migration[4.2]; end 4 | else 5 | class AddMissingIndexesOnTaggings < ActiveRecord::Migration; end 6 | end 7 | AddMissingIndexesOnTaggings.class_eval do 8 | def change 9 | add_index ActsAsTaggableOn.taggings_table, :tag_id unless index_exists? ActsAsTaggableOn.taggings_table, :tag_id 10 | add_index ActsAsTaggableOn.taggings_table, :taggable_id unless index_exists? ActsAsTaggableOn.taggings_table, :taggable_id 11 | add_index ActsAsTaggableOn.taggings_table, :taggable_type unless index_exists? ActsAsTaggableOn.taggings_table, :taggable_type 12 | add_index ActsAsTaggableOn.taggings_table, :tagger_id unless index_exists? ActsAsTaggableOn.taggings_table, :tagger_id 13 | add_index ActsAsTaggableOn.taggings_table, :context unless index_exists? ActsAsTaggableOn.taggings_table, :context 14 | 15 | unless index_exists? ActsAsTaggableOn.taggings_table, [:tagger_id, :tagger_type] 16 | add_index ActsAsTaggableOn.taggings_table, [:tagger_id, :tagger_type] 17 | end 18 | 19 | unless index_exists? ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy' 20 | add_index ActsAsTaggableOn.taggings_table, [:taggable_id, :taggable_type, :tagger_id, :context], name: 'taggings_idy' 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{content: "text/html; charset=UTF-8", "http-equiv": "Content-Type", name: "viewport", content:"width=device-width, initial-scale=1, shrink-to-fit=no"} 5 | %title MyNote 6 | = csrf_meta_tags 7 | = csp_meta_tag 8 | = include_gon 9 | = stylesheet_link_tag 'application', media: 'all' 10 | = javascript_include_tag 'application' 11 | %link{crossorigin: "anonymous", href: "https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css", integrity: "sha384-9aIt2nRpC12Uk9gS9baDl411NQApFmC26EwAOH8WgZl5MYYxFfc+NcPb1dKGj7Sk", rel: "stylesheet"} 12 | %link{href: "https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.css", rel: "stylesheet"} 13 | %body 14 | %script{crossorigin: "anonymous", integrity: "sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo"} 15 | %script{crossorigin: "anonymous", integrity: "sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1", src: "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js"} 16 | %script{crossorigin: "anonymous", integrity: "sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM", src: "https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"} 17 | 18 | %script{crossorigin: "anonymous", src: "https://kit.fontawesome.com/b167c0ffa1.js"} 19 | %script{src: "https://cdn.jsdelivr.net/simplemde/latest/simplemde.min.js"} 20 | 21 | = render 'layouts/flash' 22 | = render 'layouts/header' 23 | = yield -------------------------------------------------------------------------------- /app/views/rooms/_chat_area.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .room-show 3 | .room-show__title 4 | - @entries.each do |e| 5 | - if e.user.id != current_user.id 6 | = link_to user_path(e.user.id) do 7 | = image_tag "#{e.user.avatar}", class:"avatar" 8 | = e.user.nickname 9 | さんとのメッセージ 10 | .room-show__items 11 | - if @messages.present? 12 | - @messages.each do |m| 13 | %div.room-show__item{id: "message_#{m.id}"} 14 | -# 右側にくるように 15 | - if current_user_has?(m) 16 | .room-show__item__right 17 | = form_with(model: @message, url: message_path(m.id), class: "room-show__item__right--btn", remote: true, method: :delete, data: { confirm: '削除してよろしいですか?' }) do |f| 18 | = f.hidden_field :room_id, value: @room.id 19 | = f.submit "削除", class: "btn rounded-pill" 20 | %span.room-show__item--message 21 | = m.message 22 | .room-show__item__right 23 | .room-show__item--date 24 | = m.created_at.strftime("%Y-%m-%d %H:%M") 25 | -# 左側に来る処理 26 | - else 27 | .room-show__item__left 28 | = link_to user_path(m.user.avatar) do 29 | = image_tag "#{m.user.avatar}", class:"avatar mt05rem" 30 | %span.room-show__item--message 31 | = m.message 32 | .room-show__item__left 33 | .room-show__item--date 34 | = m.created_at.strftime("%Y-%m-%d %H:%M") -------------------------------------------------------------------------------- /app/views/relationships/_follow.html.haml: -------------------------------------------------------------------------------- 1 | - if user_signed_in? && @user.id != current_user.id 2 | #follow_form 3 | - if current_user.following?(@user) 4 | = form_with(model: current_user, url: relationship_path(@user.id), method: :delete, remote: true) do |f| 5 | = f.submit "フォロー解除", class: "btn btn-primary user-show__top--contact--btn" 6 | - else 7 | = form_with(model: current_user, url: relationships_path, method: :post, remote: true) do |f| 8 | = hidden_field_tag :following_id, @user.id 9 | = f.submit "フォローする", class: "btn btn-outline-primary user-show__top--contact--btn" 10 | 11 | -# relationship DELETE /relationships/:id(.:format) relationships#destroy 12 | -# relationships POST /relationships(.:format) relationships#create 13 | 14 | -# - unless current_user == user 15 | -# - if current_user.following?(user) 16 | -# = link_to "フォローを外す", relationship_path(follow_id: user.id), method: :delete 17 | -# - else 18 | -# = link_to "フォローする", relationships_path, method: :post 19 | 20 | -# - if user_signed_in? && @user != current_user 21 | -# #follow_form 22 | -# - if current_user.following?(@user) 23 | -# = form_for(current_user, url: relationship_path(@user), method: :delete, remote: true) do |f| 24 | -# = f.submit "フォロー解除", class: "btn btn-outline-secondary" 25 | -# - else 26 | -# = form_for(current_user, url: relationships_path, method: :post, remote: true) do |f| 27 | -# = hidden_field_tag :following_id, @user.id 28 | -# = f.submit "フォローする", class: "btn btn-outline-secondary" -------------------------------------------------------------------------------- /db/migrate/20200515005429_devise_create_users.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class DeviseCreateUsers < ActiveRecord::Migration[5.2] 4 | def change 5 | create_table :users do |t| 6 | ## Database authenticatable 7 | t.string :email, null: false, default: "" 8 | t.string :encrypted_password, null: false, default: "" 9 | 10 | ## Recoverable 11 | t.string :reset_password_token 12 | t.datetime :reset_password_sent_at 13 | 14 | ## Rememberable 15 | t.datetime :remember_created_at 16 | 17 | ## Trackable 18 | # t.integer :sign_in_count, default: 0, null: false 19 | # t.datetime :current_sign_in_at 20 | # t.datetime :last_sign_in_at 21 | # t.string :current_sign_in_ip 22 | # t.string :last_sign_in_ip 23 | 24 | ## Confirmable 25 | # t.string :confirmation_token 26 | # t.datetime :confirmed_at 27 | # t.datetime :confirmation_sent_at 28 | # t.string :unconfirmed_email # Only if using reconfirmable 29 | 30 | ## Lockable 31 | # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts 32 | # t.string :unlock_token # Only if unlock strategy is :email or :both 33 | # t.datetime :locked_at 34 | 35 | 36 | t.timestamps null: false 37 | end 38 | 39 | add_index :users, :email, unique: true 40 | add_index :users, :reset_password_token, unique: true 41 | # add_index :users, :confirmation_token, unique: true 42 | # add_index :users, :unlock_token, unique: true 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/views/tweets/_form.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | = form_with(model: @tweet, local: true, id: "tweet-form") do |f| 3 | .tweet-form 4 | .tweet-form__title 5 | = f.text_field :title, class: "tweet-form__title--body", placeholder: "タイトルを入力して下さい(30文字以内)", required: "require" 6 | .tweet-form__text__btn 7 | .btn.btn-secondary.rounded-pill#markdown.disabled 編集 8 | .btn.btn-secondary.rounded-pill#preview プレビュー 9 | .tweet-form__text 10 | #preview-area 11 | %div 12 | = f.label "0/1000", for: "md-textarea", id: "tweet-text-count" 13 | -# #tweet-text-count 0文字 14 | = f.text_area :text, class: "tweet-form__text--body", placeholder: "本文を入力して下さい(マークダウン対応)", id: "md-textarea", required: "require" 15 | %br 16 | .tweet-form--tags 17 | タグを追加 18 | %div 19 | %ul#tweet-tags 20 | .tweet-form__bonus__preview--title 21 | イメージ写真 22 | .tweet-form__bonus__preview 23 | .label-form 24 | .field.image 25 | // id "file"で、fileとdivを紐付けクリック時に連動 26 | #img_field{:onclick => "$('#file').click()"} 27 | // 画像があるときは画像を表示する 28 | - if @tweet.image.present? 29 | = image_tag "#{@tweet.image.url}", class: "tweet-form__bonus__preview--image" 30 | -# = image_tag(@tweet.image.url) 31 | - else 32 | %i.fas.fa-camera.fa-6x.tweet-form__bonus__preview--icon 33 | // id "file"をつけ、「display:none;」で隠す 34 | = f.file_field :image, class: "image", style: "display:none;", id: "file" 35 | .tweet-form__submit 36 | = f.submit '投稿する', class: "btn btn-success" -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: .rubocop_todo.yml 2 | 3 | AllCops: 4 | # 除外するディレクトリ(自動生成されたファイル) 5 | # デフォルト設定にある"vendor/**/*"が無効化されないように記述 6 | Exclude: 7 | - "bin/*" 8 | - "lib/*" 9 | - "node_modules/**/*" 10 | - "db/**/*" 11 | - "vendor/**/*" 12 | - "config/**/*" 13 | - "script/**/*" 14 | - "test/**" 15 | - "docker/rails/puma.rb" # app_rootのディレクトリを変えてはいけないため 16 | # Rails向けのRails copsを実行。"rubocop -R"と同じ 17 | Rails: 18 | enabled: true 19 | 20 | # "Missing top-level class documentation comment."を無効 21 | Style/Documentation: 22 | Enabled: false 23 | 24 | # コメントにはアスキー記号のみを使用してください 25 | AsciiComments: 26 | Enabled: false 27 | 28 | # "Prefer single-quoted strings when you don't need string interpolation or special symbols."を無効 29 | Style/StringLiterals: 30 | Enabled: false 31 | 32 | # "Line is too long"を無効 33 | Metrics/LineLength: 34 | Enabled: false 35 | 36 | # join_roomの記述が引っかかる為。省略は難しい 37 | Etrics/MethodLength: 38 | Enabled: true 39 | Max: 18 40 | 41 | #'frozen_string_literal: true'を無効 42 | Style/FrozenStringLiteralComment: 43 | Enabled: false 44 | 45 | # tweet_searchやtestのnamespaceが引っかかるため 46 | Style/ClassAndModuleChildren: 47 | Enabled: false 48 | 49 | # 線が多い長いブロックは避けてください 50 | Metrics/BlockLength: 51 | Exclude: 52 | - "spec/**/*" 53 | 54 | # NotificationsControllerが22文字の為 55 | Metrics/AbcSize: 56 | Enabled: true 57 | Max: 23 58 | 59 | # if~else~endの方がひと目で分かりやすいと思うためfalse。詳細は以下を参照 60 | # https://docs.rubocop.org/rubocop/cops_style.html#styleguardclause 61 | Style/GuardClause: 62 | Enabled: false 63 | 64 | # devise_helperの関係のため 65 | Naming/HeredocDelimiterNaming: 66 | Enabled: false 67 | 68 | # テストコードを追加したら、tweetなどの変数名が規約違反で引っかかるため無効 69 | Lint/UselessAssignment: 70 | Enabled: false 71 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/jquery.tagit.scss: -------------------------------------------------------------------------------- 1 | .ui-helper-hidden-accessible { 2 | display: none; 3 | } 4 | 5 | ul.tagit { 6 | padding: 1px 5px; 7 | overflow: auto; 8 | margin-left: inherit; /* usually we don't want the regular ul margins. */ 9 | margin-right: inherit; 10 | } 11 | ul.tagit li { 12 | display: block; 13 | float: left; 14 | margin: 2px 5px 2px 0; 15 | } 16 | ul.tagit li.tagit-choice { 17 | position: relative; 18 | line-height: inherit; 19 | } 20 | input.tagit-hidden-field { 21 | display: none; 22 | } 23 | ul.tagit li.tagit-choice-read-only { 24 | padding: .2em .5em .2em .5em; 25 | } 26 | 27 | ul.tagit li.tagit-choice-editable { 28 | padding: .2em 18px .2em .5em; 29 | // padding: .375rem .75rem; 30 | &:hover{ 31 | color: white; 32 | } 33 | } 34 | 35 | ul.tagit li.tagit-new { 36 | padding: .25em 4px .25em 0; 37 | } 38 | 39 | ul.tagit li.tagit-choice a.tagit-label { 40 | cursor: pointer; 41 | text-decoration: none; 42 | } 43 | ul.tagit li.tagit-choice .tagit-close { 44 | cursor: pointer; 45 | position: absolute; 46 | right: .1em; 47 | top: 50%; 48 | margin-top: -8px; 49 | line-height: 17px; 50 | } 51 | 52 | /* used for some custom themes that don't need image icons */ 53 | ul.tagit li.tagit-choice .tagit-close .text-icon { 54 | display: none; 55 | } 56 | 57 | ul.tagit li.tagit-choice input { 58 | display: block; 59 | float: left; 60 | margin: 2px 5px 2px 0; 61 | } 62 | ul.tagit input[type="text"] { 63 | -moz-box-sizing: border-box; 64 | -webkit-box-sizing: border-box; 65 | box-sizing: border-box; 66 | 67 | -moz-box-shadow: none; 68 | -webkit-box-shadow: none; 69 | box-shadow: none; 70 | 71 | border: none; 72 | margin: 0; 73 | padding: 0; 74 | width: inherit; 75 | background-color: inherit; 76 | outline: none; 77 | } 78 | 79 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/models/like_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Like, type: :model do 4 | describe '正常値と異常値の確認' do 5 | context 'likeモデルのバリデーション' do 6 | it "user_idとtweet_idがあれば保存できる" do 7 | # expect(FactoryBot.build_stubbed(:like)).to be_valid 8 | expect(FactoryBot.create(:like)).to be_valid 9 | end 10 | 11 | it "user_idがなければ無効な状態であること" do 12 | like = build(:like, user_id: nil) 13 | like.valid? 14 | expect(like.errors[:user_id]).to include("を入力してください") 15 | end 16 | 17 | it "tweet_idがなければ無効な状態であること" do 18 | like = build(:like, tweet_id: nil) 19 | like.valid? 20 | expect(like.errors[:tweet_id]).to include("を入力してください") 21 | end 22 | 23 | it "tweet_idが同じでもuser_idが違うと保存できる" do 24 | like = create(:like) 25 | expect(FactoryBot.create(:like, tweet_id: like.tweet_id)).to be_valid 26 | end 27 | 28 | it "user_idが同じでもtweet_idが違うと保存できる" do 29 | like = create(:like) 30 | expect(FactoryBot.create(:like, user_id: like.user_id)).to be_valid 31 | end 32 | 33 | it "tweet_idとuser_idは一意でなければ保存できない" do 34 | like = create(:like) 35 | like2 = build(:like, tweet_id: like.tweet_id, user_id: like.user_id) 36 | like2.valid? 37 | expect(like2.errors[:tweet_id]).to include("はすでに存在します") 38 | end 39 | end 40 | end 41 | 42 | describe "各モデルとのアソシエーション" do 43 | let(:association) do 44 | described_class.reflect_on_association(target) 45 | end 46 | 47 | context "Userモデルとのアソシエーション" do 48 | let(:target) { :user } 49 | 50 | it "Userとの関連付けはbelongs_toであること" do 51 | expect(association.macro).to eq :belongs_to 52 | end 53 | end 54 | 55 | context "Tweetモデルとのアソシエーション" do 56 | let(:target) { :tweet } 57 | 58 | it "Tweetとの関連付けはbelongs_toであること" do 59 | expect(association.macro).to eq :belongs_to 60 | end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_notifications.scss: -------------------------------------------------------------------------------- 1 | .notification-top{ 2 | @include common-sizing; 3 | margin: 0 auto; 4 | border: 1px solid $header_border; 5 | background-color: white; 6 | cursor: pointer; 7 | &__activities{ 8 | display: none; 9 | } 10 | &__title{ 11 | height: 100px; 12 | width: 100%; 13 | display: flex; 14 | font-size: 20px; 15 | &--show{ 16 | background-color: white; 17 | border-top:2px solid red; 18 | } 19 | &--down{ 20 | background-color: #EEEEEE; 21 | } 22 | &--name{ 23 | text-align: center; 24 | line-height: 100px; 25 | display: block; 26 | font-size: 25px; 27 | &:hover{ 28 | background-color: #f7f7f7; 29 | color: #505050; 30 | } 31 | } 32 | &__left{ 33 | width: 50%; 34 | } 35 | &__right{ 36 | width: 50%; 37 | } 38 | } 39 | &__items{ 40 | display: block; 41 | &--none{ 42 | height: 100px; 43 | line-height: 100px; 44 | border-bottom: 1px solid $header_border; 45 | } 46 | &__item{ 47 | display: block; 48 | height: 100px; 49 | border-bottom: 1px solid $header_border; 50 | padding: 20px; 51 | color: #333; 52 | position: relative; 53 | &:hover{ 54 | background-color: #fafafa; 55 | color: #505050; 56 | } 57 | &--icon{ 58 | padding-top: 14px; 59 | height: 60px; 60 | width: 60px; 61 | } 62 | &--content{ 63 | position: absolute; 64 | top: 34px; 65 | left: 80px; 66 | } 67 | &--message{ 68 | position: absolute; 69 | top: 62px; 70 | left: 125px; 71 | color: $header_tags; 72 | @include md-max{ 73 | display: none; 74 | } 75 | } 76 | &--date{ 77 | position: absolute; 78 | right: 5px; 79 | bottom: 5px; 80 | color: $header_tags; 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_tweet-comment.scss: -------------------------------------------------------------------------------- 1 | .tweet-show__comment{ 2 | border-top: 1px solid $header_border; 3 | @include md{ 4 | padding: 20px 40px; 5 | } 6 | @include md-max{ 7 | padding: 20px 0px; 8 | } 9 | display: flex; 10 | flex-direction: column; 11 | &--text{ 12 | text-align: center; 13 | } 14 | &__form{ 15 | display: flex; 16 | flex-direction: column; 17 | &__top { 18 | margin-top: 20px; 19 | display: flex; 20 | justify-content: center; 21 | } 22 | &--title{ 23 | padding: 10px 10px; 24 | } 25 | &--body{ 26 | height: 100px; 27 | width: 100%; 28 | color: #222; 29 | background: white; 30 | border: 1px solid #a8abb1; 31 | border-radius: 5px; 32 | padding: 5px; 33 | outline: none; 34 | } 35 | &--submit{ 36 | display: flex; 37 | justify-content: center; 38 | padding: 20px 0; 39 | } 40 | &--text{ 41 | text-align: center; 42 | } 43 | } 44 | &__title{ 45 | padding: 20px 0; 46 | display: flex; 47 | justify-content: center; 48 | } 49 | &--text{ 50 | padding-left: 50px; 51 | padding-top: 3px; 52 | } 53 | &__items{ 54 | } 55 | &__item{ 56 | padding: 20px 0px; 57 | &__top{ 58 | display: flex; 59 | justify-content: space-between; 60 | &--user{ 61 | display: flex; 62 | &:hover{ 63 | color: #505050; 64 | } 65 | } 66 | &--nickname{ 67 | padding: 0px 10px; 68 | } 69 | &--date{ 70 | color: $header_tags; 71 | font-size: 13px; 72 | } 73 | } 74 | &--text{ 75 | padding: 10px; 76 | font-size: 19px; 77 | } 78 | &--edit{ 79 | display: flex; 80 | justify-content: flex-end; 81 | font-size: 13px; 82 | &--left{ 83 | padding-right: 10px; 84 | } 85 | & a, &:hover a{ 86 | color: #333; 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /spec/models/entry_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Entry, type: :model do 4 | let(:entry) { create(:entry) } 5 | describe '#create' do 6 | context "保存できる場合" do 7 | it "全てのパラメーターが揃っていれば保存できる" do 8 | expect(entry).to be_valid 9 | end 10 | end 11 | 12 | context "保存できない場合" do 13 | it "user_idがnilの場合は保存できない" do 14 | entry.user_id = nil 15 | entry.valid? 16 | expect(entry.errors[:user]).to include("を入力してください") 17 | end 18 | 19 | it "room_idがnilの場合は保存できない" do 20 | entry.room_id = nil 21 | entry.valid? 22 | expect(entry.errors[:room]).to include("を入力してください") 23 | end 24 | end 25 | 26 | context "一意性の検証" do 27 | it "user_idとroom_idの組み合わせは一意でなければ保存できない" do 28 | entry2 = build(:entry, user_id: entry.user_id, room_id: entry.room_id) 29 | entry2.valid? 30 | expect(entry2.errors[:room_id]).to include("はすでに存在します") 31 | end 32 | 33 | it "room_idが同じでもuser_idが違うと保存できる" do 34 | user2 = create(:user) 35 | entry2 = build(:entry, user_id: user2.id, room_id: entry.room_id) 36 | expect(entry2).to be_valid 37 | end 38 | 39 | it "user_idが同じでもroom_idが違うと保存できる" do 40 | room2 = create(:room) 41 | entry2 = build(:entry, user_id: entry.user_id, room_id: room2.id) 42 | expect(entry2).to be_valid 43 | end 44 | end 45 | end 46 | 47 | describe "各モデルとのアソシエーション" do 48 | let(:association) do 49 | described_class.reflect_on_association(target) 50 | end 51 | 52 | context "Userモデルとのアソシエーション" do 53 | let(:target) { :user } 54 | 55 | it "Userとの関連付けはbelongs_toであること" do 56 | expect(association.macro).to eq :belongs_to 57 | end 58 | end 59 | 60 | context "Roomモデルとのアソシエーション" do 61 | let(:target) { :room } 62 | 63 | it "Roomとの関連付けはbelongs_toであること" do 64 | expect(association.macro).to eq :belongs_to 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /app/views/users/_form.html.haml: -------------------------------------------------------------------------------- 1 | .login__field 2 | .text-center 3 | = f.label :nickname, value: 'ニックネーム' 4 | .center 5 | = f.text_field :nickname, autofocus: true, autocomplete: "nickname", class: "login--input" 6 | .login__field 7 | .text-center 8 | = f.label :email 9 | .center 10 | = f.email_field :email, autofocus: true, class: "login--input" 11 | .login__field 12 | .text-center 13 | = f.label :password 14 | .center 15 | = f.password_field :password, autocomplete: "off", class: "login--input" 16 | .login__field 17 | .text-center 18 | = f.label :password_confirmation, "パスワード(確認)" 19 | .center 20 | = f.password_field :password_confirmation, autocomplete: "new-password", class: "login--input" 21 | .login__field 22 | .text-center 23 | = f.label :avatar 24 | .center 25 | .lofin__preview 26 | .field.image 27 | // id "file"で、fileとdivを紐付けクリック時に連動 28 | #img_field{:onclick => "$('#file').click()"} 29 | // 画像があるときは画像を表示する 30 | - if @user.avatar.present? 31 | = image_tag "#{@user.avatar.url}", class: "avatar" 32 | -# = image_tag(@user.avatar.url) 33 | - else 34 | %i.fas.fa-camera.fa-3x 35 | // id "file"をつけ、「display:none;」で隠す 36 | = f.file_field :avatar, class: "avatar", style: "display:none;", id: "file" 37 | 38 | :javascript 39 | $(function(){ 40 | $fileField = $('#file') 41 | 42 | // 選択された画像を取得し表示 43 | $($fileField).on('change', $fileField, function(e) { 44 | file = e.target.files[0] 45 | reader = new FileReader(), 46 | $preview = $("#img_field"); 47 | 48 | reader.onload = (function(file) { 49 | return function(e) { 50 | $preview.empty(); 51 | $preview.append($('').attr({ 52 | src: e.target.result, 53 | class: "avatar", 54 | title: file.name 55 | })); 56 | }; 57 | })(file); 58 | reader.readAsDataURL(file); 59 | }); 60 | }); -------------------------------------------------------------------------------- /spec/models/comment_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Comment, type: :model do 4 | let(:comment) { create(:comment) } 5 | describe '#create' do 6 | context '保存できる場合' do 7 | it "全てのパラメーターが揃っていれば保存できる" do 8 | expect(comment).to be_valid 9 | end 10 | 11 | it "contentが1文字以上なら保存できる" do 12 | comment.content = "A" * 1 13 | expect(comment).to be_valid 14 | end 15 | 16 | it "contentが1,000文字以内なら保存できる" do 17 | comment.content = "A" * 1000 18 | expect(comment).to be_valid 19 | end 20 | end 21 | 22 | context "保存できない場合" do 23 | it "user_idがnilの場合は保存できない" do 24 | comment.user_id = nil 25 | comment.valid? 26 | expect(comment.errors[:user]).to include("を入力してください") 27 | end 28 | 29 | it "tweet_idがnilの場合は保存できない" do 30 | comment.tweet_id = nil 31 | comment.valid? 32 | expect(comment.errors[:tweet]).to include("を入力してください") 33 | end 34 | 35 | it "contentがnilの場合は保存できない" do 36 | comment.content = nil 37 | comment.valid? 38 | expect(comment.errors[:content]).to include("を入力してください") 39 | end 40 | 41 | it "contentが1001文字以上の場合は保存できない" do 42 | comment.content = "A" * 1001 43 | comment.valid? 44 | expect(comment.errors[:content]).to include("は1000文字以内で入力してください") 45 | end 46 | end 47 | end 48 | 49 | describe "各モデルとのアソシエーション" do 50 | let(:association) do 51 | described_class.reflect_on_association(target) 52 | end 53 | 54 | context "Notificationモデルとのアソシエーション" do 55 | let(:target) { :notifications } 56 | 57 | it "Notificationとの関連付けはhas_manyであること" do 58 | expect(association.macro).to eq :has_many 59 | end 60 | 61 | it "Commentが削除されたらNotificationも削除されること" do 62 | comment = create(:comment) 63 | notification = create(:notification, comment_id: comment.id, visitor_id: 1, visited_id: 1) 64 | expect { comment.destroy }.to change(Comment, :count).by(-1) 65 | end 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /app/uploaders/avatar_uploader.rb: -------------------------------------------------------------------------------- 1 | class AvatarUploader < CarrierWave::Uploader::Base 2 | # Include RMagick or MiniMagick support: 3 | # include CarrierWave::RMagick 4 | include CarrierWave::MiniMagick 5 | 6 | if Rails.env.development? || Rails.env.test? 7 | storage :file 8 | else 9 | storage :fog 10 | end 11 | 12 | # Create different versions of your uploaded files: 13 | version :thumb do 14 | process resize_to_fit: [800, 800] 15 | end 16 | 17 | # 保存形式をJPGにする 18 | process convert: 'jpg' 19 | 20 | # jpg,jpeg,gif,pngしか受け付けない 21 | def extension_white_list 22 | %w[jpg jpeg gif png] 23 | end 24 | 25 | # 拡張子が同じでないとGIFをJPGとかにコンバートできないので、ファイル名を変更 26 | def filename 27 | super.chomp(File.extname(super)) + '.jpg' if original_filename.present? 28 | end 29 | 30 | # Choose what kind of storage to use for this uploader: 31 | # storage :file 32 | # storage :fog 33 | 34 | # Override the directory where uploaded files will be stored. 35 | # This is a sensible default for uploaders that are meant to be mounted: 36 | def store_dir 37 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 38 | end 39 | 40 | # Provide a default URL as a default if there hasn't been a file uploaded: 41 | def default_url(*_args) 42 | # # For Rails 3.1+ asset pipeline compatibility: 43 | # # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) 44 | "avatar.png" 45 | # "/images/fallback/" + [version_name, "default.png"].compact.join('_') 46 | end 47 | 48 | # Process files as they are uploaded: 49 | # process scale: [200, 300] 50 | # 51 | # def scale(width, height) 52 | # # do something 53 | # end 54 | 55 | # Add a white list of extensions which are allowed to be uploaded. 56 | # For images you might use something like this: 57 | # def extension_whitelist 58 | # %w(jpg jpeg gif png) 59 | # end 60 | 61 | # Override the filename of the uploaded files: 62 | # Avoid using model.id or version_name here, see uploader/store.rb for details. 63 | # def filename 64 | # "something.jpg" if original_filename 65 | # end 66 | end 67 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_tweet-form.scss: -------------------------------------------------------------------------------- 1 | 2 | #preview-area{ 3 | min-height: 300px; 4 | display: none; 5 | } 6 | 7 | #img_field { 8 | width: 100%; 9 | height: 100%; 10 | } 11 | 12 | .tweet-form{ 13 | @include common-sizing; 14 | margin: 0px auto 100px; 15 | font-size: 15px; 16 | &__title{ 17 | height: 50px; 18 | &--body{ 19 | height: 50px; 20 | width: 100%; 21 | outline: none; 22 | padding-left: 5px; 23 | border-radius: 5px; 24 | border: 1px solid #e6e6e6; 25 | &:hover{ 26 | border: 1px solid #a8abb1; 27 | } 28 | } 29 | } 30 | &__text{ 31 | position: relative; 32 | margin-top: 10px; 33 | &--body{ 34 | height: 300px; 35 | width: 100%; 36 | outline: none; 37 | padding: 5px; 38 | border: 1px solid #e6e6e6; 39 | border-radius: 5px; 40 | } 41 | &__btn{ 42 | display: flex; 43 | justify-content: space-around; 44 | padding: 20px 0px; 45 | } 46 | } 47 | &--tags{ 48 | padding-bottom: 10px; 49 | } 50 | &__bonus{ 51 | &__preview{ 52 | margin: 10px auto; 53 | background-color: white; 54 | border: solid 1px #eee; 55 | color: #666; 56 | position: relative; 57 | border-radius: 5px; 58 | cursor: pointer; 59 | height: 300px; 60 | width: 400px; 61 | @include md-max { 62 | @include tweet-items(); // 画面縮小時のwidthを指定 63 | } 64 | object-fit: cover; 65 | transition: 0.3s ease-out; 66 | text-align: center; 67 | &:hover{ 68 | background-color: gray; 69 | color: white; 70 | } 71 | &--title{ 72 | text-align: center; 73 | } 74 | &--image{ 75 | border-radius: 5px; 76 | height: 300px; 77 | width: 400px; 78 | @include md-max{ 79 | @include tweet-items(); // 画面縮小時のwidthを指定 80 | } 81 | object-fit: cover; 82 | } 83 | &--icon{ 84 | position: absolute; 85 | top: 32%; 86 | left: 39%; 87 | } 88 | } 89 | } 90 | &__submit{ 91 | text-align: center; 92 | margin-bottom: 30px; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/uploaders/image_uploader.rb: -------------------------------------------------------------------------------- 1 | class ImageUploader < CarrierWave::Uploader::Base 2 | # Include RMagick or MiniMagick support: 3 | # include CarrierWave::RMagick 4 | include CarrierWave::MiniMagick 5 | 6 | if Rails.env.development? || Rails.env.test? 7 | storage :file 8 | else 9 | storage :fog 10 | end 11 | 12 | # Create different versions of your uploaded files: 13 | version :thumb do 14 | process resize_to_fit: [800, 800] 15 | end 16 | 17 | # 保存形式をJPGにする 18 | process convert: 'jpg' 19 | 20 | # jpg,jpeg,gif,pngしか受け付けない 21 | def extension_white_list 22 | %w[jpg jpeg gif png] 23 | end 24 | 25 | # 拡張子が同じでないとGIFをJPGとかにコンバートできないので、ファイル名を変更 26 | def filename 27 | super.chomp(File.extname(super)) + '.jpg' if original_filename.present? 28 | end 29 | 30 | # Choose what kind of storage to use for this uploader: 31 | # storage :file 32 | # storage :fog 33 | 34 | # Override the directory where uploaded files will be stored. 35 | # This is a sensible default for uploaders that are meant to be mounted: 36 | def store_dir 37 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 38 | end 39 | 40 | # Provide a default URL as a default if there hasn't been a file uploaded: 41 | def default_url(*args) 42 | # # For Rails 3.1+ asset pipeline compatibility: 43 | # ActionController::Base.helpers.asset_path("fallback/" + [version_name, "default.png"].compact.join('_')) 44 | # "image.png" 45 | # "avatar.png" 46 | # "/images/fallback/" + [version_name, "default.png"].compact.join('_') 47 | end 48 | 49 | # Process files as they are uploaded: 50 | # process scale: [200, 300] 51 | # 52 | # def scale(width, height) 53 | # # do something 54 | # end 55 | 56 | # Add a white list of extensions which are allowed to be uploaded. 57 | # For images you might use something like this: 58 | # def extension_whitelist 59 | # %w(jpg jpeg gif png) 60 | # end 61 | 62 | # Override the filename of the uploaded files: 63 | # Avoid using model.id or version_name here, see uploader/store.rb for details. 64 | # def filename 65 | # "something.jpg" if original_filename 66 | # end 67 | end 68 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | config.after_initialize do 3 | Bullet.enable = true 4 | Bullet.bullet_logger = true 5 | Bullet.raise = true # raise an error if n+1 query occurs 6 | end # Settings specified here will take precedence over those in config/application.rb. 7 | 8 | # The test environment is used exclusively to run your application's 9 | # test suite. You never need to work with it otherwise. Remember that 10 | # your test database is "scratch space" for the test suite and is wiped 11 | # and recreated between test runs. Don't rely on the data there! 12 | config.cache_classes = true 13 | 14 | # Do not eager load code on boot. This avoids loading your whole application 15 | # just for the purpose of running a single test. If you are using a tool that 16 | # preloads Rails for running tests, you may have to set it to true. 17 | config.eager_load = false 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 | 29 | # Raise exceptions instead of rendering exception templates. 30 | config.action_dispatch.show_exceptions = false 31 | 32 | # Disable request forgery protection in test environment. 33 | config.action_controller.allow_forgery_protection = false 34 | 35 | # Store uploaded files on the local file system in a temporary directory 36 | config.active_storage.service = :test 37 | 38 | config.action_mailer.perform_caching = false 39 | 40 | # Tell Action Mailer not to deliver emails to the real world. 41 | # The :test delivery method accumulates sent emails in the 42 | # ActionMailer::Base.deliveries array. 43 | config.action_mailer.delivery_method = :test 44 | 45 | # Print deprecation notices to the stderr. 46 | config.active_support.deprecation = :stderr 47 | 48 | # Raises error for missing translations 49 | # config.action_view.raise_on_missing_translations = true 50 | end 51 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_room-show.scss: -------------------------------------------------------------------------------- 1 | 2 | // chat_area のScss------------------------------------------------------ 3 | .room-show{ 4 | @include common-sizing(); 5 | min-height: 500px; 6 | margin: 30px auto; 7 | border: 1px solid $header_border; 8 | background-color: white; 9 | color: #333; 10 | overflow: scroll; 11 | &__title{ 12 | height: 100px; 13 | font-size: 25px; 14 | @include md-max{ 15 | font-size: 18px; 16 | } 17 | text-align: center; 18 | line-height: 100px; 19 | border-bottom: 1px solid $header_border; 20 | position: relative; 21 | &--btn{ 22 | position: absolute; 23 | top: 0; 24 | left: 10px; 25 | } 26 | &:hover a{ 27 | color: #505050; 28 | } 29 | } 30 | &__item { 31 | margin-top: 10px; 32 | &--message{ 33 | font-size: 15px; 34 | background-color: #F5F5F5; 35 | padding: 0.4rem 1rem; 36 | border-radius: 1.2rem; 37 | display: inline-block; 38 | max-width: 80%; 39 | margin: 0.5rem; 40 | } 41 | &--date{ 42 | padding: 5px 10px; 43 | color: $header_tags; 44 | } 45 | &__right { 46 | margin: 0.5rem; 47 | display: flex; 48 | justify-content: flex-end; 49 | &--btn{ 50 | padding-top: 8px; 51 | } 52 | } 53 | &__left { 54 | margin: 0.5rem; 55 | display: flex; 56 | justify-content: flex-start; 57 | } 58 | } 59 | } 60 | // chat_form のScss------------------------------------------------------ 61 | .room-show__form{ 62 | height: 70px; 63 | width: 100%; 64 | padding: 10px; 65 | display: flex; 66 | border-top: 1px solid $header_border; 67 | background-color: #bcbcbc; 68 | position: fixed; 69 | bottom: 0; 70 | &__box{ 71 | position: relative; 72 | margin: 0 auto; 73 | } 74 | &--input{ 75 | padding-left: 20px; 76 | @include common-sizing; 77 | @include sm-max{ 78 | width: 250px; 79 | } 80 | height: 50px; 81 | border-radius: 100px; 82 | border: 1px solid #ccc; 83 | outline: 0; 84 | } 85 | &--btn{ 86 | border: none; 87 | font-size: 1.3rem; 88 | color: #666; 89 | background-color: #bcbcbc; 90 | position: absolute; 91 | bottom: 7px; 92 | right: -40px; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /app/assets/stylesheets/modules/_tweet-index.scss: -------------------------------------------------------------------------------- 1 | .tweet-index{ 2 | margin: 0px auto; 3 | display: flex; 4 | justify-content: space-around; 5 | flex-wrap: wrap; 6 | @include lg-max{ 7 | @include tweet-items; // widthを指定 8 | } 9 | width: 1000px; 10 | &__title{ 11 | display: none; 12 | // height: 50px; 13 | width: 100%; 14 | &--text{ 15 | font-size: 20px; 16 | border-bottom: 1px solid $header_border; 17 | text-align: center; 18 | width: 130px; 19 | margin: 0 auto; 20 | } 21 | } 22 | &__item{ 23 | display: inline-block; 24 | @include lg-max{ 25 | @include tweet-items; 26 | } 27 | width: 460px; 28 | padding: 30px; 29 | margin: 5% 0; 30 | border: 1px solid $header_border; 31 | background-color: white; 32 | border-radius: 5px; 33 | position: relative; 34 | box-shadow: 0px 4px 6px #ccc; 35 | &--image{ 36 | height: 300px; 37 | width: 100%; 38 | object-fit: cover; 39 | background-color: black; 40 | margin: 20px 0; 41 | } 42 | &--title{ 43 | font-size: 18px; 44 | font-weight: bold; 45 | color: #333; 46 | &:hover{ 47 | color: #505050; 48 | } 49 | } 50 | &--sub{ 51 | color: $header_tags; 52 | font-size: 13px; 53 | margin-top: 30px; 54 | } 55 | &__footer{ 56 | padding-top: 20px; 57 | border-top: 1px solid #e1e1e1; 58 | margin-top: 20px; 59 | display: flex; 60 | justify-content: space-between; 61 | } 62 | &--show{ 63 | color: $header_tags; 64 | font-size: 13px; 65 | bottom: 5px; 66 | right: 5px; 67 | &:hover{ 68 | color: #333; 69 | } 70 | } 71 | &--like{ 72 | bottom: 5px; 73 | left: 5px; 74 | } 75 | &__user{ 76 | display: flex; 77 | justify-content: space-between; 78 | position: relative; 79 | line-height: 40px; 80 | padding-bottom: 20px; 81 | &--nickname{ 82 | position: absolute; 83 | left: 55px; 84 | top: 0; 85 | color: #333; 86 | &:hover{ 87 | color: #505050; 88 | } 89 | } 90 | } 91 | } 92 | &--footer{ 93 | // justify-contents: centerが効かないので追加していた。効くは聞くため削除 94 | // padding: 0 400px; 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /config/deploy/staging.rb: -------------------------------------------------------------------------------- 1 | # server-based syntax 2 | # ====================== 3 | # Defines a single server with a list of roles and multiple properties. 4 | # You can define all roles on a single server, or split them: 5 | 6 | # server "example.com", user: "deploy", roles: %w{app db web}, my_property: :my_value 7 | # server "example.com", user: "deploy", roles: %w{app web}, other_property: :other_value 8 | # server "db.example.com", user: "deploy", roles: %w{db} 9 | 10 | 11 | 12 | # role-based syntax 13 | # ================== 14 | 15 | # Defines a role with one or multiple servers. The primary server in each 16 | # group is considered to be the first unless any hosts have the primary 17 | # property set. Specify the username and a domain or IP for the server. 18 | # Don't use `:all`, it's a meta role. 19 | 20 | # role :app, %w{deploy@example.com}, my_property: :my_value 21 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value 22 | # role :db, %w{deploy@example.com} 23 | 24 | 25 | 26 | # Configuration 27 | # ============= 28 | # You can set any configuration variable like in config/deploy.rb 29 | # These variables are then only loaded and set in this stage. 30 | # For available Capistrano configuration variables see the documentation page. 31 | # http://capistranorb.com/documentation/getting-started/configuration/ 32 | # Feel free to add new variables to customise your setup. 33 | 34 | 35 | 36 | # Custom SSH Options 37 | # ================== 38 | # You may pass any option but keep in mind that net/ssh understands a 39 | # limited set of options, consult the Net::SSH documentation. 40 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start 41 | # 42 | # Global options 43 | # -------------- 44 | # set :ssh_options, { 45 | # keys: %w(/home/user_name/.ssh/id_rsa), 46 | # forward_agent: false, 47 | # auth_methods: %w(password) 48 | # } 49 | # 50 | # The server-based syntax can be used to override options: 51 | # ------------------------------------ 52 | # server "example.com", 53 | # user: "user_name", 54 | # roles: %w{web app}, 55 | # ssh_options: { 56 | # user: "user_name", # overrides user setting above 57 | # keys: %w(/home/user_name/.ssh/id_rsa), 58 | # forward_agent: false, 59 | # auth_methods: %w(publickey password) 60 | # # password: "please use keys" 61 | # } 62 | -------------------------------------------------------------------------------- /spec/models/relationship_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Relationship, type: :model do 4 | let(:relationship) { create(:relationship) } 5 | describe '#create' do 6 | context "保存できる場合" do 7 | it "全てのパラメーターが揃っていれば保存できる" do 8 | expect(relationship).to be_valid 9 | end 10 | end 11 | 12 | context "保存できない場合" do 13 | it "follower_idがnilの場合は保存できない" do 14 | relationship.follower_id = nil 15 | relationship.valid? 16 | expect(relationship.errors[:follower_id]).to include("を入力してください") 17 | end 18 | 19 | it "following_idがnilの場合は保存できない" do 20 | relationship.following_id = nil 21 | relationship.valid? 22 | expect(relationship.errors[:following_id]).to include("を入力してください") 23 | end 24 | end 25 | 26 | context "一意性の検証" do 27 | before do 28 | @relation = create(:relationship) 29 | @user1 = build(:relationship) 30 | end 31 | it "follower_idとfollowing_idの組み合わせは一意でなければ保存できない" do 32 | relation2 = build(:relationship, follower_id: @relation.follower_id, following_id: @relation.following_id) 33 | relation2.valid? 34 | expect(relation2.errors[:follower_id]).to include("はすでに存在します") 35 | end 36 | 37 | it "follower_idが同じでもfollowing_idが違うなら保存できる" do 38 | relation2 = build(:relationship, follower_id: @relation.follower_id, following_id: @user1.following_id) 39 | expect(relation2).to be_valid 40 | end 41 | 42 | it "follower_idが違うならfollowing_idが同じでも保存できる" do 43 | relation2 = build(:relationship, follower_id: @user1.follower_id, following_id: @relation.following_id) 44 | expect(relation2).to be_valid 45 | end 46 | end 47 | end 48 | 49 | describe "各モデルとのアソシエーション" do 50 | let(:association) do 51 | described_class.reflect_on_association(target) 52 | end 53 | 54 | context "仮想モデルfollowerとのアソシエーション" do 55 | let(:target) { :follower } 56 | 57 | it "Followerとの関連付けはbelongs_toであること" do 58 | expect(association.macro).to eq :belongs_to 59 | end 60 | end 61 | 62 | context "仮想モデルfollowingとのアソシエーション" do 63 | let(:target) { :following } 64 | 65 | it "Followingとの関連付けはbelongs_toであること" do 66 | expect(association.macro).to eq :belongs_to 67 | end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /app/assets/stylesheets/_reset.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * YUI 3.5.0 - reset.css (http://developer.yahoo.com/yui/3/cssreset/) 3 | * http://cssreset.com 4 | * Copyright 2012 Yahoo! Inc. All rights reserved. 5 | * http://yuilibrary.com/license/ 6 | */ 7 | /* 8 | */ 9 | 10 | html { 11 | color: #333; 12 | background-color: $body_color; 13 | } 14 | body { 15 | background-color: $body_color; 16 | } 17 | 18 | a, 19 | a:visited, 20 | a:focus, 21 | a:hover { 22 | text-decoration: none !important; 23 | color: #333; 24 | } 25 | 26 | li{ 27 | text-decoration: none; 28 | } 29 | 30 | 31 | * { 32 | margin: 0px; 33 | padding: 0px; 34 | } 35 | 36 | /* 37 | */ 38 | /* 39 | - Fails on FF. 40 | */ 41 | body, 42 | div, 43 | dl, 44 | dt, 45 | dd, 46 | ul, 47 | ol, 48 | li, 49 | h1, 50 | h2, 51 | h3, 52 | h4, 53 | h5, 54 | h6, 55 | pre, 56 | code, 57 | form, 58 | fieldset, 59 | legend, 60 | input, 61 | textarea, 62 | p, 63 | blockquote, 64 | th, 65 | td { 66 | margin: 0; 67 | padding: 0; 68 | } 69 | table { 70 | border-collapse: collapse; 71 | border-spacing: 0; 72 | } 73 | fieldset, 74 | img { 75 | border: 0; 76 | } 77 | /* 78 | */ 79 | address, 80 | caption, 81 | cite, 82 | code, 83 | dfn, 84 | em, 85 | strong, 86 | th, 87 | var { 88 | font-style: normal; 89 | font-weight: normal; 90 | } 91 | 92 | ol, 93 | ul { 94 | list-style: none; 95 | } 96 | 97 | caption, 98 | th { 99 | text-align: left; 100 | } 101 | h1, 102 | h2, 103 | h3, 104 | h4, 105 | h5, 106 | h6 { 107 | font-size: 100%; 108 | font-weight: normal; 109 | } 110 | q:before, 111 | q:after { 112 | content: ""; 113 | } 114 | abbr, 115 | acronym { 116 | border: 0; 117 | font-variant: normal; 118 | } 119 | /* to preserve line-height and selector appearance */ 120 | sup { 121 | vertical-align: text-top; 122 | } 123 | sub { 124 | vertical-align: text-bottom; 125 | } 126 | input, 127 | textarea, 128 | select { 129 | font-family: inherit; 130 | font-size: inherit; 131 | font-weight: inherit; 132 | } 133 | /*to enable resizing for IE*/ 134 | input, 135 | textarea, 136 | select { 137 | *font-size: 100%; 138 | } 139 | /*because legend doesn't inherit in IE */ 140 | legend { 141 | color: #000; 142 | } 143 | /* YUI CSS Detection Stamp */ 144 | #yui3-css-stamp.cssreset { 145 | display: none; 146 | } -------------------------------------------------------------------------------- /app/controllers/tweets_controller.rb: -------------------------------------------------------------------------------- 1 | class TweetsController < ApplicationController 2 | before_action :authenticate_user!, only: %i[new create edit update destroy] 3 | before_action :set_tweet, only: %i[edit destroy show update] 4 | before_action :blocking_edit_tweet, only: %i[edit update destroy] 5 | before_action :set_available_tags_to_gon, only: %i[new edit] 6 | 7 | def index 8 | @tweets = Tweet.includes(%i[taggings user]).order('created_at desc').page(params[:page]).per(10) 9 | @tweets = @tweets.tagged_with(params[:tag_name].to_s) if params[:tag_name] 10 | end 11 | 12 | def new 13 | @tweet = Tweet.new 14 | @tags = Tweet.includes([:taggings]).tag_counts_on(:tags) 15 | end 16 | 17 | def create 18 | @tweet = Tweet.create(tweet_params) 19 | gon.tweet_tags = @tweet.tag_list 20 | if @tweet.save 21 | redirect_to root_path, notice: '投稿しました!' 22 | else 23 | flash.now[:alert] = "文字数を確認してください。" 24 | render :new 25 | end 26 | end 27 | 28 | def edit 29 | gon.tweet_tags = @tweet.tag_list 30 | end 31 | 32 | def update 33 | @tweet.update(tweet_params) 34 | gon.tweet_tags = @tweet.tag_list 35 | if @tweet.save 36 | redirect_to tweet_path(@tweet.id), notice: '投稿を編集しました!' 37 | else 38 | flash.now[:alert] = "文字数を確認してください。" 39 | render :edit 40 | end 41 | end 42 | 43 | def show 44 | @comment = Comment.new 45 | @comments = @tweet.comments.includes(:user).order('created_at desc') 46 | @user = @tweet.user 47 | @donation = Donation.new 48 | @donations = @tweet.donations.includes(:user).sum(:amount) 49 | end 50 | 51 | def destroy 52 | @tweet.destroy 53 | redirect_to root_path, notice: '投稿を削除しました' 54 | end 55 | 56 | def likes 57 | @tweets = Tweet.includes(%i[taggings user]).order('likes_count desc').page(params[:page]).per(10) 58 | end 59 | 60 | def tags 61 | @tags = Tweet.includes(:taggings).tag_counts_on(:tags) 62 | end 63 | 64 | private 65 | 66 | def tweet_params 67 | params.require(:tweet).permit(:title, :text, :image, :tag_list).merge(user_id: current_user.id) 68 | end 69 | 70 | def set_tweet 71 | @tweet = Tweet.find(params[:id]) 72 | end 73 | 74 | def blocking_edit_tweet 75 | redirect_to root_path, alert: "不正な操作です" unless (@tweet.user == current_user) || current_user.admin? 76 | end 77 | 78 | def set_available_tags_to_gon 79 | gon.available_tags = Tweet.includes(:taggings).tags_on(:tags).pluck(:name) 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /spec/models/message_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Message, type: :model do 4 | let(:message) { create(:message) } 5 | describe '#create' do 6 | context '保存できる場合' do 7 | it "全てのパラメーターが揃っていれば保存できる" do 8 | expect(message).to be_valid 9 | end 10 | 11 | it "messageが1文字以上なら保存できる" do 12 | message.message = "A" * 1 13 | expect(message).to be_valid 14 | end 15 | 16 | it "messageが1,000文字以内なら保存できる" do 17 | message.message = "A" * 1000 18 | expect(message).to be_valid 19 | end 20 | end 21 | 22 | context "保存できない場合" do 23 | it "user_idがnilの場合は保存できない" do 24 | message.user_id = nil 25 | message.valid? 26 | expect(message.errors[:user]).to include("を入力してください") 27 | end 28 | 29 | it "room_idがnilの場合は保存できない" do 30 | message.room_id = nil 31 | message.valid? 32 | expect(message.errors[:room]).to include("を入力してください") 33 | end 34 | 35 | it "messageがnilの場合は保存できない" do 36 | message.message = nil 37 | message.valid? 38 | expect(message.errors[:message]).to include("を入力してください") 39 | end 40 | 41 | it "messageが1001文字以上の場合は保存できない" do 42 | message.message = "A" * 1001 43 | message.valid? 44 | expect(message.errors[:message]).to include("は1000文字以内で入力してください") 45 | end 46 | end 47 | end 48 | 49 | describe "各モデルとのアソシエーション" do 50 | let(:association) do 51 | described_class.reflect_on_association(target) 52 | end 53 | 54 | context "Userモデルとのアソシエーション" do 55 | let(:target) { :user } 56 | 57 | it "Userとの関連付けはbelongs_toであること" do 58 | expect(association.macro).to eq :belongs_to 59 | end 60 | end 61 | 62 | context "Roomモデルとのアソシエーション" do 63 | let(:target) { :room } 64 | 65 | it "Roomとの関連付けはbelongs_toであること" do 66 | expect(association.macro).to eq :belongs_to 67 | end 68 | end 69 | 70 | context "Notificationモデルとのアソシエーション" do 71 | let(:target) { :notifications } 72 | 73 | it "Notificationとの関連付けはhas_manyであること" do 74 | expect(association.macro).to eq :has_many 75 | end 76 | 77 | it "Messageが削除されたらNotificationも削除されること" do 78 | message = create(:message) 79 | notification = create(:notification, message_id: message.id, visitor_id: 1, visited_id: 1) 80 | expect { message.destroy }.to change(Notification, :count).by(-1) 81 | end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | executors: 4 | default: 5 | working_directory: ~/repo 6 | docker: 7 | - image: circleci/ruby:2.5.1-node-browsers 8 | - image: circleci/mysql:5.7 9 | environment: 10 | - MYSQL_USER: root 11 | - MYSQL_ALLOW_EMPTY_PASSWORD: true 12 | - RAILS_ENV: "test" 13 | - BUNDLER_VERSION: 2.1.4 14 | 15 | commands: 16 | setup: 17 | steps: 18 | - checkout 19 | - restore_cache: 20 | keys: 21 | - v1-dependencies-{{ checksum "Gemfile.lock" }} 22 | - v1-dependencies- 23 | - run: 24 | name: install dependencies 25 | command: | 26 | gem install bundler -v 2.1.4 27 | bundle install --jobs=4 --retry=3 --path vendor/bundle 28 | - save_cache: 29 | paths: 30 | - ./vendor/bundle 31 | key: v1-dependencies-{{ checksum "Gemfile.lock" }} 32 | 33 | jobs: 34 | test: 35 | executor: default 36 | environment: 37 | RAILS_ENV: test 38 | steps: 39 | - setup 40 | - run: mv config/database.yml.ci config/database.yml 41 | - run: bundle exec rake db:create 42 | - run: bundle exec rake db:schema:load 43 | 44 | - run: 45 | name: Rubocop 46 | command: bundle exec rubocop 47 | 48 | - run: 49 | name: run tests 50 | command: | 51 | mkdir /tmp/test-results 52 | TEST_FILES="$(circleci tests glob "spec/**/*_spec.rb" | \ 53 | circleci tests split --split-by=timings)" 54 | bundle exec rspec \ 55 | --format progress \ 56 | --format RspecJunitFormatter \ 57 | --out /tmp/test-results/rspec.xml \ 58 | --format progress \ 59 | $TEST_FILES 60 | 61 | - store_test_results: 62 | path: /tmp/test-results 63 | - store_artifacts: 64 | path: /tmp/test-results 65 | destination: test-results 66 | 67 | deploy: 68 | executor: default 69 | steps: 70 | - setup 71 | - add_ssh_keys: 72 | fingerprints: 73 | - "33:33:a8:e3:91:b6:5d:59:10:1d:0f:dc:a6:fe:29:db" # GitHubのUserキーを使用 74 | - deploy: 75 | name: Capistrano deploy 76 | command: bundle exec cap production deploy 77 | 78 | workflows: 79 | test_and_deploy: 80 | jobs: 81 | - test 82 | - deploy: 83 | requires: 84 | - test 85 | filters: 86 | branches: 87 | only: master 88 | -------------------------------------------------------------------------------- /app/views/layouts/_header.html.haml: -------------------------------------------------------------------------------- 1 | .header 2 | .header-contents 3 | .header-top 4 | .header-top__left 5 | = link_to root_path do 6 | %h1.header-top__left--title My Note 7 | .header-top__right 8 | = form_with(url: tweets_searches_path, local: true, method: :get) do |f| 9 | = f.text_field :keyword, placeholder: "検索する", class: "header-top__right--search" 10 | .header-top__right__user 11 | - if user_signed_in? 12 | %div 13 | = render 'layouts/circle' 14 | .header-top__right__user__info 15 | .header-top__right__user__info--link.display-flex 16 | .header-top__right__user__info--avatar 17 | = image_tag "#{current_user.avatar}", class: "avatar" 18 | .header-top__right__user__info--nickname 19 | = current_user.nickname 20 | %ul.header-top__box 21 | %li 22 | = link_to "マイページを表示", user_path(current_user), class: "header-top__box--content" 23 | %li.header-top__box--borderTop 24 | = link_to "メッセージ", rooms_path, class: "header-top__box--content" 25 | %li.header-top__box--borderTop 26 | = link_to "タイムライン", timeline_users_path, class: "header-top__box--content" 27 | %li.header-top__box--borderTop 28 | = link_to 'ユーザー一覧', users_path, class: "header-top__box--content" 29 | %li.header-top__box--borderTop 30 | = link_to "人気投稿", likes_tweets_path, class: "header-top__box--content" 31 | %li.header-top__box--borderTop 32 | = link_to 'ログアウト', destroy_user_session_path, method: :delete, class: "header-top__box--content" 33 | - else 34 | = link_to new_user_session_path do 35 | %input.btn.btn-success.header-top__right--box{type: "submit", value: "ログイン"} 36 | = link_to new_user_registration_path do 37 | %input.btn.btn-success.header-top__right--box{type: "submit", value: "新規登録"} 38 | 39 | .header-bottom 40 | %ul.header-bottom__list 41 | - @tags = ["テクノロジー","経済","マンガ","ファッション","エンタメ","スポーツ","国際","コラム"] 42 | - @tags.each do |tag| 43 | %li.header-bottom__list__item 44 | = link_to tag, tweets_path(tag_name: tag), class: "header-bottom__list--name" 45 | %ul.header-bottom__list__tags 46 | = link_to "全てのタグ", tags_tweets_path, class: ".header-bottom__list--name" -------------------------------------------------------------------------------- /app/controllers/users_controller.rb: -------------------------------------------------------------------------------- 1 | class UsersController < ApplicationController 2 | before_action :authenticate_user!, only: %i[edit update timeline] 3 | before_action :set_user, except: %i[index timeline] 4 | before_action :blocking_edit_user, only: %i[edit update] 5 | before_action :blocking_edit_test_user, only: %i[edit update] 6 | before_action :join_room, only: %i[show likes] 7 | 8 | def index 9 | @users = User.all 10 | end 11 | 12 | def show 13 | @tweets = @user.tweets.includes(:taggings).order('created_at desc').page(params[:page]).per(10) 14 | end 15 | 16 | def edit; end 17 | 18 | def update 19 | if current_user.update(user_params) 20 | redirect_to root_path, notice: 'ユーザー情報を更新しました' 21 | else 22 | flash.now[:alert] = "入力内容に誤りがあります。入力漏れ、文字数をご確認ください。" 23 | render :edit 24 | end 25 | end 26 | 27 | def likes 28 | @tweets = @user.liked_tweets.includes(%i[taggings user]).order('updated_at desc').page(params[:page]).per(10) 29 | end 30 | 31 | def following 32 | @users = @user.following 33 | end 34 | 35 | def followers 36 | @users = @user.followers 37 | end 38 | 39 | def timeline 40 | if user_signed_in? 41 | @user = User.find(current_user.id) 42 | @following_users = @user.following 43 | @tweets = Tweet.includes(%i[taggings user]).where(user_id: @following_users).order('created_at desc').page(params[:page]).per(10) 44 | end 45 | end 46 | 47 | private 48 | 49 | def set_user 50 | @user = User.find(params[:id]) 51 | end 52 | 53 | def user_params 54 | params.require(:user).permit(:nickname, :email, :avatar) 55 | end 56 | 57 | def blocking_edit_user 58 | redirect_to root_path, alert: "不正な操作です" unless (@user == current_user) || current_user.admin? 59 | end 60 | 61 | def blocking_edit_test_user 62 | redirect_to root_path, alert: "ゲストユーザーのため編集できません" if current_user.email == "guest@user" 63 | end 64 | 65 | def join_room 66 | if user_signed_in? 67 | unless @user.id == current_user.id 68 | @current_user_entry = Entry.where(user_id: current_user.id) 69 | @user_entry = Entry.where(user_id: @user.id) 70 | @current_user_entry.each do |cu| 71 | @user_entry.each do |u| 72 | if cu.room_id == u.room_id 73 | @have_room = true 74 | @room_id = cu.room_id 75 | end 76 | end 77 | end 78 | unless @have_room 79 | @room = Room.new 80 | @entry = Entry.new 81 | end 82 | end 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ApplicationRecord 2 | # devise_login------------------------------ ------------------------------------ 3 | # Include default devise modules. Others available are: 4 | # :confirmable, :lockable, :timeoutable, :trackable and :omniauthable 5 | devise :database_authenticatable, :registerable, 6 | :recoverable, :rememberable, :validatable 7 | validates :nickname, presence: true, length: { maximum: 10 } 8 | validates :email, length: { maximum: 50 }, presence: true, uniqueness: true 9 | mount_uploader :avatar, AvatarUploader 10 | # tweets------------------------------------------------------------------- 11 | has_many :tweets 12 | # comments------------------------------------------------------------------- 13 | has_many :comments 14 | # likes------------------------------------------------------------------- 15 | has_many :likes 16 | has_many :liked_tweets, through: :likes, source: :tweet 17 | # messages------------------------------------------------------------------- 18 | has_many :messages 19 | has_many :entries 20 | has_many :rooms, through: :entries 21 | # follow------------------------------------------------------------------ 22 | has_many :following_relationships, foreign_key: "follower_id", class_name: "Relationship" 23 | has_many :following, through: :following_relationships 24 | has_many :follower_relationships, foreign_key: "following_id", class_name: "Relationship" 25 | has_many :followers, through: :follower_relationships 26 | 27 | # フォローしているかを確認するメソッド 28 | def following?(user) 29 | following_relationships.find_by(following_id: user.id) 30 | end 31 | 32 | # フォローするときのメソッド 33 | def follow(user) 34 | following_relationships.create!(following_id: user.id) 35 | end 36 | 37 | # フォローを外すときのメソッド 38 | def unfollow(user) 39 | following_relationships.find_by(following_id: user.id).destroy 40 | end 41 | # notifications------------------------------------------------------------------- 42 | has_many :active_notifications, class_name: 'Notification', foreign_key: 'visitor_id' 43 | has_many :passive_notifications, class_name: 'Notification', foreign_key: 'visited_id' 44 | 45 | def create_notification_follow!(current_user) 46 | temp = Notification.where(["visitor_id = ? and visited_id = ? and action = ? ", current_user.id, id, 'follow']) 47 | if temp.blank? 48 | notification = current_user.active_notifications.new( 49 | visited_id: id, 50 | action: 'follow' 51 | ) 52 | notification.save if notification.valid? 53 | end 54 | end 55 | # donations------------------------------------------------------------------- 56 | has_many :donations 57 | end 58 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # config.after_initialize do 3 | # Bullet.enable = true 4 | # Bullet.alert = true 5 | # Bullet.bullet_logger = true 6 | # Bullet.console = true 7 | # # Bullet.growl = true 8 | # Bullet.rails_logger = true 9 | # Bullet.add_footer = true 10 | # end # Settings specified here will take precedence over those in config/application.rb. 11 | 12 | # In the development environment your application's code is reloaded on 13 | # every request. This slows down response time but is perfect for development 14 | # since you don't have to restart the web server when you make code changes. 15 | config.cache_classes = false 16 | 17 | # Do not eager load code on boot. 18 | config.eager_load = false 19 | 20 | # Show full error reports. 21 | config.consider_all_requests_local = true 22 | 23 | # Enable/disable caching. By default caching is disabled. 24 | # Run rails dev:cache to toggle caching. 25 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 26 | config.action_controller.perform_caching = true 27 | 28 | config.cache_store = :memory_store 29 | config.public_file_server.headers = { 30 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 31 | } 32 | else 33 | config.action_controller.perform_caching = false 34 | 35 | config.cache_store = :null_store 36 | end 37 | 38 | # Store uploaded files on the local file system (see config/storage.yml for options) 39 | config.active_storage.service = :local 40 | 41 | # Don't care if the mailer can't send. 42 | config.action_mailer.raise_delivery_errors = false 43 | 44 | config.action_mailer.perform_caching = false 45 | 46 | # Print deprecation notices to the Rails logger. 47 | config.active_support.deprecation = :log 48 | 49 | # Raise an error on page load if there are pending migrations. 50 | config.active_record.migration_error = :page_load 51 | 52 | # Highlight code that triggered database queries in logs. 53 | config.active_record.verbose_query_logs = true 54 | 55 | # Debug mode disables concatenation and preprocessing of assets. 56 | # This option may cause significant delays in view rendering with a large 57 | # number of complex assets. 58 | config.assets.debug = true 59 | 60 | # Suppress logger output for asset requests. 61 | config.assets.quiet = true 62 | 63 | # Raises error for missing translations 64 | # config.action_view.raise_on_missing_translations = true 65 | 66 | # Use an evented file watcher to asynchronously detect changes in source code, 67 | # routes, locales, etc. This feature depends on the listen gem. 68 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 69 | end 70 | -------------------------------------------------------------------------------- /config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for current version of Capistrano 2 | # capistranoのバージョンを記載。固定のバージョンを利用し続け、バージョン変更によるトラブルを防止する 3 | lock "3.14.1" 4 | 5 | # 自身のアプリ名、リポジトリ名を記述 6 | set :application, 'MyNote' 7 | 8 | # どのリポジトリからアプリをpullするかを指定する 9 | set :repo_url, 'git@github.com:ohishikaito/MyNote.git' 10 | 11 | # バージョンが変わっても共通で参照するディレクトリを指定 12 | set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads') 13 | 14 | set :rbenv_type, :user 15 | set :rbenv_ruby, '2.5.1' 16 | 17 | # どの公開鍵を利用してデプロイするか 18 | set :ssh_options, auth_methods: ['publickey'], 19 | keys: ['~/.ssh/kaitoaws.pem'] 20 | 21 | # プロセス番号を記載したファイルの場所 22 | set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" } 23 | 24 | # Unicornの設定ファイルの場所 25 | set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" } 26 | set :keep_releases, 5 27 | 28 | # デプロイ処理が終わった後、Unicornを再起動するための記述 29 | after 'deploy:publishing', 'deploy:restart' 30 | namespace :deploy do 31 | task :restart do 32 | invoke 'unicorn:restart' 33 | end 34 | end 35 | 36 | # # Gemfile.lockを見てcapistranoのバージョンを入れる 37 | # lock "3.14.1" 38 | 39 | # # 自身のアプリ名、リポジトリ名を記述 40 | # set :application, 'MyNote' 41 | 42 | # # どのリポジトリからアプリをpullするかを指定する 43 | # set :repo_url, 'git@github.com:ohishikaito/MyNote.git' 44 | 45 | # # バージョンが変わっても共通で参照するディレクトリを指定 46 | # set :linked_dirs, fetch(:linked_dirs, []).push('log', 'tmp/pids', 'tmp/cache', 'tmp/sockets', 'vendor/bundle', 'public/system', 'public/uploads') 47 | 48 | # set :rbenv_type, :user 49 | # set :rbenv_ruby, '2.5.1' 50 | 51 | # # chat-spaceで使ったpemを指定、どの公開鍵を利用してデプロイするか 52 | # set :ssh_options, auth_methods: ['publickey'], 53 | # keys: ['~/.ssh/kaitoaws.pem'] 54 | 55 | # # どの公開鍵を利用してデプロイするか 56 | # set :unicorn_pid, -> { "#{shared_path}/tmp/pids/unicorn.pid" } 57 | # # Unicornの設定ファイルの場所 58 | # set :unicorn_config_path, -> { "#{current_path}/config/unicorn.rb" } 59 | # set :keep_releases, 5 60 | 61 | # set :linked_files, %w{ config/master.key } 62 | # # master.key用のシンボリックリンクを追加 63 | # # set :linked_files, %w[config/master.key] 64 | 65 | # # デプロイ処理が終わった後、Unicornを再起動するための記述 66 | # after 'deploy:publishing', 'deploy:restart' 67 | # namespace :deploy do 68 | # task :restart do 69 | # invoke 'unicorn:stop' 70 | # invoke 'unicorn:start' 71 | # end 72 | 73 | # # desc 'upload master.key' 74 | # # task :upload do 75 | # # on roles(:app) do |host| 76 | # # if test "[ ! -d #{shared_path}/config ]" 77 | # # execute "mkdir -p #{shared_path}/config" 78 | # # end 79 | # # # circleCIでマスターキーを取得するためコメントアウト 80 | # # upload!('config/master.key', "#{shared_path}/config/master.key") 81 | # # end 82 | # # end 83 | # # before :starting, 'deploy:upload' 84 | # # after :finishing, 'deploy:cleanup' 85 | # end -------------------------------------------------------------------------------- /spec/models/notification_spec.rb: -------------------------------------------------------------------------------- 1 | require 'rails_helper' 2 | 3 | RSpec.describe Notification, type: :model do 4 | describe "#create" do 5 | context "Tweet関連の通知" do 6 | before do 7 | @tweet = build(:tweet) 8 | end 9 | 10 | it "コメントが行われた場合に保存できる" do 11 | comment = build(:comment) 12 | notification = build(:notification, tweet_id: @tweet.id, comment_id: comment.id, action: "comment") 13 | expect(notification).to be_valid 14 | end 15 | 16 | it "いいねが行われた場合に保存できる" do 17 | like = build(:like) 18 | notification = build(:notification, tweet_id: @tweet.id, action: "like") 19 | expect(notification).to be_valid 20 | end 21 | end 22 | 23 | context "ダイレクトメッセージ関連の通知" do 24 | it "ダイレクトメッセージが行われた場合に保存できる" do 25 | room = build(:room) 26 | message = build(:message) 27 | notification = build(:notification, room_id: room.id, message_id: message.id, action: "message") 28 | expect(notification).to be_valid 29 | end 30 | end 31 | 32 | context "フォロー関連の通知" do 33 | it "フォローが行われた場合に保存できる" do 34 | user1 = build(:user) 35 | user2 = build(:user) 36 | notification = build(:notification, visitor_id: user1.id, visited_id: user2.id, action: "follow") 37 | expect(notification).to be_valid 38 | end 39 | end 40 | end 41 | 42 | describe "アソシエーションのテスト" do 43 | let(:association) do 44 | described_class.reflect_on_association(target) 45 | end 46 | 47 | context "Messageモデルとのアソシエーション" do 48 | let(:target) { :message } 49 | 50 | it "Messageとの関連付けはbelongs_toであること" do 51 | expect(association.macro).to eq :belongs_to 52 | end 53 | end 54 | 55 | context "Tweetモデルとのアソシエーション" do 56 | let(:target) { :tweet } 57 | 58 | it "Tweetとの関連付けはbelongs_toであること" do 59 | expect(association.macro).to eq :belongs_to 60 | end 61 | end 62 | 63 | context "Roomモデルとのアソシエーション" do 64 | let(:target) { :room } 65 | 66 | it "Roomとの関連付けはbelongs_toであること" do 67 | expect(association.macro).to eq :belongs_to 68 | end 69 | end 70 | 71 | context "Commentモデルとのアソシエーション" do 72 | let(:target) { :comment } 73 | 74 | it "Commentとの関連付けはbelongs_toであること" do 75 | expect(association.macro).to eq :belongs_to 76 | end 77 | end 78 | 79 | context "visitorとのアソシエーション" do 80 | let(:target) { :visitor } 81 | 82 | it "Visitorとの関連付けはbelongs_toであること" do 83 | expect(association.macro).to eq :belongs_to 84 | end 85 | end 86 | 87 | context "visitedとのアソシエーション" do 88 | let(:target) { :visited } 89 | 90 | it "Visitedとの関連付けはbelongs_toであること" do 91 | expect(association.macro).to eq :belongs_to 92 | end 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /app/views/activities/_activity.html.haml: -------------------------------------------------------------------------------- 1 | - visitor = activity.visitor 2 | - visited = activity.visited 3 | %li 4 | - case activity.action 5 | 6 | - when 'like' 7 | = link_to tweet_path(activity.tweet.id), class: "notification-top__items__item" do 8 | .notification-top__items__item--icon 9 | %i.fa.fa-heart.fa-2x{"aria-hidden" => "true", style: "color: red;"} 10 | .notification-top__items__item--content 11 | - if activity.tweet.user_id == visitor.id 12 | = image_tag "#{visitor.avatar}", class: "avatar" 13 | あなたの投稿 14 | - else 15 | = image_tag "#{visited.avatar}", class: "avatar" 16 | %strong 17 | = activity.tweet.user.nickname + 'さんの投稿' 18 | にいいねしました 19 | .notification-top__items__item--message 20 | = activity.tweet.title 21 | .notification-top__items__item--date 22 | = time_ago_in_words(activity.created_at).upcase 23 | 24 | - when 'comment' 25 | = link_to tweet_path(activity.tweet.id), class: "notification-top__items__item" do 26 | .notification-top__items__item--icon 27 | %i.fa.fa-edit.fa-2x{"aria-hidden" => "true"} 28 | .notification-top__items__item--content 29 | - if activity.tweet.user_id == visitor.id 30 | = image_tag "#{visited.avatar}", class: "avatar" 31 | あなたの投稿 32 | - else 33 | = image_tag "#{activity.tweet.user.avatar}", class: "avatar" 34 | %strong 35 | = activity.tweet.user.nickname + 'さんの投稿' 36 | にコメントしました 37 | .notification-top__items__item--message 38 | = truncate(Comment.find_by(id: activity.comment_id)&.content, length: 20) 39 | .notification-top__items__item--date 40 | = time_ago_in_words(activity.created_at).upcase 41 | 42 | - when 'message' 43 | = link_to room_path(activity.room), class: "notification-top__items__item" do 44 | .notification-top__items__item--icon 45 | %i.fa.fa-envelope-o.fa-2x{"aria-hidden" => "true"} 46 | .notification-top__items__item--content 47 | = image_tag "#{visited.avatar}", class: "avatar" 48 | %strong 49 | = visited.nickname 50 | さんにメッセージを送信しました 51 | .notification-top__items__item--message 52 | = truncate(Message.find_by(id: activity.message_id)&.message, length: 20) 53 | .notification-top__items__item--date 54 | = time_ago_in_words(activity.created_at).upcase 55 | 56 | - when 'follow' 57 | = link_to user_path(visited), class: "notification-top__items__item" do 58 | .notification-top__items__item--icon 59 | %i.fa.fa-user.fa-2x{"aria-hidden" => "true"} 60 | .notification-top__items__item--content 61 | = image_tag "#{visited.avatar}", class: "avatar" 62 | %strong 63 | = visited.nickname 64 | さんをフォローしました 65 | .notification-top__items__item--date 66 | = time_ago_in_words(activity.created_at).upcase -------------------------------------------------------------------------------- /config/unicorn.rb: -------------------------------------------------------------------------------- 1 | # ../が一つ増えている 2 | app_path = File.expand_path('../../../', __FILE__) 3 | # unicornがエラー吐いたので直接ディレクトリを指定 4 | # app_path = File.expand_path('/var/www/MyNote') 5 | 6 | worker_processes 1 7 | # currentを指定 8 | working_directory "#{app_path}/current" 9 | 10 | # それぞれ、sharedの中を参照するよう変更 11 | listen "#{app_path}/shared/tmp/sockets/unicorn.sock" 12 | pid "#{app_path}/shared/tmp/pids/unicorn.pid" 13 | stderr_path "#{app_path}/shared/log/unicorn.stderr.log" 14 | stdout_path "#{app_path}/shared/log/unicorn.stdout.log" 15 | 16 | #Railsアプリケーションの応答を待つ上限時間を設定 17 | timeout 60 18 | 19 | # 以下は応用的な設定なので説明は割愛 20 | 21 | preload_app true 22 | GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true 23 | 24 | check_client_connection false 25 | 26 | run_once = true 27 | 28 | before_fork do |server, worker| 29 | defined?(ActiveRecord::Base) && 30 | ActiveRecord::Base.connection.disconnect! 31 | 32 | if run_once 33 | run_once = false # prevent from firing again 34 | end 35 | 36 | old_pid = "#{server.config[:pid]}.oldbin" 37 | if File.exist?(old_pid) && server.pid != old_pid 38 | begin 39 | sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU 40 | Process.kill(sig, File.read(old_pid).to_i) 41 | rescue Errno::ENOENT, Errno::ESRCH => e 42 | logger.error e 43 | end 44 | end 45 | end 46 | 47 | after_fork do |_server, _worker| 48 | defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection 49 | end 50 | 51 | # # ファイルが無いのでconfigフォルダにunicorn.rbを新規作成します 52 | 53 | # app_path = File.expand_path('../../../', __FILE__) 54 | # # 上記からEC2からunicornを再起動可能にするために修正(6/29) 問題があれば上記に戻す 55 | # # app_path = File.expand_path('/var/www/MyNote') 56 | 57 | # worker_processes 1 58 | 59 | # working_directory "#{app_path}/current" 60 | # pid "#{app_path}/shared/tmp/pids/unicorn.pid" 61 | # listen "#{app_path}/shared/tmp/sockets/unicorn.sock" 62 | # stderr_path "#{app_path}/shared/log/unicorn.stderr.log" 63 | # stdout_path "#{app_path}/shared/log/unicorn.stdout.log" 64 | 65 | # timeout 60 66 | 67 | # preload_app true 68 | # GC.respond_to?(:copy_on_write_friendly=) && GC.copy_on_write_friendly = true 69 | 70 | # check_client_connection false 71 | 72 | # run_once = true 73 | 74 | # before_fork do |server, worker| 75 | # defined?(ActiveRecord::Base) && 76 | # ActiveRecord::Base.connection.disconnect! 77 | 78 | # if run_once 79 | # run_once = false # prevent from firing again 80 | # end 81 | 82 | # old_pid = "#{server.config[:pid]}.oldbin" 83 | # if File.exist?(old_pid) && server.pid != old_pid 84 | # begin 85 | # sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU 86 | # Process.kill(sig, File.read(old_pid).to_i) 87 | # rescue Errno::ENOENT, Errno::ESRCH => e 88 | # logger.error e 89 | # end 90 | # end 91 | # end 92 | 93 | # after_fork do |_server, _worker| 94 | # defined?(ActiveRecord::Base) && ActiveRecord::Base.establish_connection 95 | # end -------------------------------------------------------------------------------- /app/views/tweets/show.html.haml: -------------------------------------------------------------------------------- 1 | .wrapper 2 | .tweet-show 3 | .tweet-show__title 4 | .tweet-show__title__top 5 | = link_to user_path(@tweet.user.id), class: "tweet-show__title__top__user" do 6 | = image_tag "#{@tweet.user.avatar}", class: "avatar" 7 | = @tweet.user.nickname 8 | .tweet-show__title__top--edit 9 | - if current_user_has?(@tweet) 10 | = link_to '編集', edit_tweet_path(@tweet.id) 11 | = link_to '削除', tweet_path(@tweet.id), method: :delete, data: { confirm: '削除してよろしいですか?' } 12 | %h1.tweet-show__title--body 13 | = @tweet.title 14 | .tweet-show__title--date 15 | = @tweet.updated_at.strftime("%Y-%m-%d %H:%M") 16 | .tweet-show__content 17 | .tweet-show__content--text 18 | %span 19 | = markdown(@tweet.text) 20 | - if @tweet.image? 21 | .tweet-show__content--imageBox 22 | = image_tag "#{@tweet.image}", class: "tweet-show__content--image" 23 | .tweet-show__content--tags 24 | = render '/tweets/tag_list', tag_list: @tweet.tag_list 25 | .tweet-show__content__user 26 | = link_to user_path(@tweet.user.id), class: "tweet-show__content__user--top" do 27 | .display-flex 28 | = image_tag "#{@tweet.user.avatar}", class: "tweet-show__content__user--avatar" 29 | .tweet-show__content__user--names 30 | %strong 31 | = @tweet.user.nickname 32 | .tweet-show__content__user--follow 33 | = render "relationships/follow" 34 | .tweet-show__content__donation 35 | #donation_area 36 | = render "donations/donation" 37 | .tweet-show__content__like 38 | %div{id: "like_#{@tweet.id}"} 39 | = render 'likes/like', tweet: @tweet 40 | -# .tweet-show__content__like--link 41 | -# - if @tweet.liked_users.present? 42 | -# この記事にいいねした人を見る 43 | .tweet-show__comment 44 | #comment_form 45 | = render "comments/form" 46 | #comment_area 47 | = render "comments/top" 48 | 49 | .tweet-show__content__like--bgc 50 | .jc-center 51 | %ul.tweet-show__content__like__modal 52 | - @tweet.liked_users.each do |user| 53 | %li 54 | = link_to user_path(user.id), class: "tweet-show__content__like--user" do 55 | = image_tag "#{user.avatar}", class: "avatar" 56 | .tweet-show__content__like--user--name 57 | = user.nickname 58 | さん 59 | .tweet-show__content__like--close 60 | %i.btn.btn-dark 61 | 閉じる 62 | 63 | .donation 64 | .jc-center 65 | .donation-modal 66 | .donation-modal--header 67 | %strong 68 | = @tweet.user.nickname 69 | さんに寄付をする 70 | #donation_form 71 | = render "donations/form" 72 | -# .donation-modal--close 73 | -# %i.btn.btn-dark.donation-modal--close--btn 74 | 閉じる -------------------------------------------------------------------------------- /app/views/notifications/_notification.html.haml: -------------------------------------------------------------------------------- 1 | - visitor = notification.visitor 2 | - visited = notification.visited 3 | %li 4 | - case notification.action 5 | 6 | - when 'like' 7 | = link_to tweet_path(notification.tweet.id), class: "notification-top__items__item" do 8 | .notification-top__items__item--icon 9 | %i.fa.fa-heart.fa-2x{"aria-hidden" => "true", style: "color: red;"} 10 | .notification-top__items__item--content 11 | = image_tag "#{visitor.avatar}", class: "avatar" 12 | %strong 13 | = visitor.nickname 14 | さんがあなたの投稿にいいねしました 15 | .notification-top__items__item--message 16 | = notification.tweet.title 17 | .notification-top__items__item--date 18 | = time_ago_in_words(notification.created_at).upcase 19 | - when 'comment' 20 | = link_to tweet_path(notification.tweet.id), class: "notification-top__items__item" do 21 | .notification-top__items__item--icon 22 | %i.fa.fa-edit.fa-2x{"aria-hidden" => "true"} 23 | .notification-top__items__item--content 24 | = image_tag "#{visitor.avatar}", class: "avatar" 25 | %strong 26 | = visitor.nickname 27 | さんが 28 | - if notification.tweet.user_id == visited.id 29 | あなたの投稿にコメントしました 30 | - elsif notification.tweet.user_id == visitor.id 31 | あなたのコメントに返信しました 32 | - else 33 | = image_tag "#{notification.tweet.user.avatar}", class: "avatar" 34 | %strong 35 | = notification.tweet.user.nickname + 'さんの投稿' 36 | にコメントしました 37 | .notification-top__items__item--message 38 | = truncate(Comment.find_by(id: notification.comment_id)&.content, length: 20) 39 | .notification-top__items__item--date 40 | = time_ago_in_words(notification.created_at).upcase 41 | - when 'message' 42 | = link_to room_path(notification.room), class: "notification-top__items__item" do 43 | .notification-top__items__item--icon 44 | %i.fa.fa-envelope-o.fa-2x{"aria-hidden" => "true"} 45 | .notification-top__items__item--content 46 | = image_tag "#{visitor.avatar}", class: "avatar" 47 | %strong 48 | = visitor.nickname 49 | さんがあなたにメッセージを送信しました 50 | .notification-top__items__item--message 51 | = truncate(Message.find_by(id: notification.message_id)&.message, length: 20) 52 | .notification-top__items__item--date 53 | = time_ago_in_words(notification.created_at).upcase 54 | - when 'follow' 55 | = link_to user_path(visitor), class: "notification-top__items__item" do 56 | .notification-top__items__item--icon 57 | %i.fa.fa-user.fa-2x{"aria-hidden" => "true"} 58 | .notification-top__items__item--content 59 | = image_tag "#{visitor.avatar}", class: "avatar" 60 | %strong 61 | = visitor.nickname 62 | さんがあなたをフォローしました 63 | .notification-top__items__item--date 64 | = time_ago_in_words(notification.created_at).upcase -------------------------------------------------------------------------------- /app/models/tweet.rb: -------------------------------------------------------------------------------- 1 | class Tweet < ApplicationRecord 2 | # search------------------------------------------------------------------- 3 | def self.search(search) 4 | if search 5 | Tweet.where('title Like(?) OR text Like(?)', "%#{search}%", "%#{search}%") 6 | else 7 | Tweet.all 8 | end 9 | end 10 | # likes------------------------------------------------------------------- 11 | has_many :likes, dependent: :destroy 12 | has_many :liked_users, through: :likes, source: :user 13 | 14 | # 既にいいねしているか確認するメソッド 15 | def like_user(user_id) 16 | likes.find_by(user_id: user_id) 17 | end 18 | 19 | # tweets------------------------------------------------------------------- 20 | mount_uploader :image, ImageUploader 21 | # RailsとPumaの仕様上1500文字制限(おそらく空白も含まれるため)1000文字制限だと思わぬ不具合が発生する 22 | validates :text, presence: true, length: { maximum: 1500 } 23 | validates :title, presence: true, length: { maximum: 40 } 24 | # users------------------------------------------------------------------- 25 | belongs_to :user 26 | # comments------------------------------------------------------------------- 27 | has_many :comments, dependent: :destroy 28 | # notifications ------------------------------------------------------------------- 29 | has_many :notifications, dependent: :destroy 30 | 31 | def create_notification_like!(current_user) 32 | # 既にいいねされてるか検索 33 | temp = Notification.where(["visitor_id = ? and visited_id = ? and tweet_id = ? and action = ? ", current_user.id, user_id, id, 'like']) 34 | # いいねされてなかったら通知を作成 35 | if temp.blank? 36 | notification = current_user.active_notifications.new( 37 | tweet_id: id, 38 | visited_id: user_id, 39 | action: 'like' 40 | ) 41 | # 自分の投稿にはいいね通知済みにする 42 | notification.checked = true if notification.visitor_id == notification.visited_id 43 | notification.save if notification.valid? 44 | end 45 | end 46 | 47 | def create_notification_comment!(current_user, comment_id) 48 | # 自分以外にコメントしている人をすべて取得し、全員に通知を送る 49 | temp_ids = Comment.select(:user_id).where(tweet_id: id).where.not(user_id: current_user.id).distinct 50 | temp_ids.each do |temp_id| 51 | save_notification_comment!(current_user, comment_id, temp_id['user_id']) 52 | end 53 | # まだ誰もコメントしていない場合は、投稿者に通知を送る 54 | save_notification_comment!(current_user, comment_id, user_id) if temp_ids.blank? 55 | end 56 | 57 | def save_notification_comment!(current_user, comment_id, visited_id) 58 | # コメントは複数回することが考えられるため、1つの投稿に複数回通知する 59 | notification = current_user.active_notifications.new( 60 | tweet_id: id, 61 | comment_id: comment_id, 62 | visited_id: visited_id, 63 | action: 'comment' 64 | ) 65 | # 自分の投稿に対するコメントの場合は、通知済みとする 66 | notification.checked = true if notification.visitor_id == notification.visited_id 67 | notification.save if notification.valid? 68 | end 69 | # ActAsTaggable------------------------------------------------------------------- 70 | acts_as_taggable 71 | # donations------------------------------------------------------------------- 72 | has_many :donations, dependent: :destroy 73 | end 74 | --------------------------------------------------------------------------------