diff --git a/Gemfile b/Gemfile index c1f1ee3e2..405ef8fba 100644 --- a/Gemfile +++ b/Gemfile @@ -41,6 +41,14 @@ gem 'jbuilder', '~> 2.5' # Use Capistrano for deployment # gem 'capistrano-rails', group: :development +gem 'bootstrap', '~> 4.1.1' +gem 'sprockets-rails', '~> 3.1', '>= 3.1.1' +gem 'jquery-rails', '~> 4.3', '>= 4.3.3' +gem 'pry', '~> 0.11.3' +gem 'kaminari', '~> 1.1', '>= 1.1.1' + + + group :development, :test do # Call 'byebug' anywhere in the code to stop execution and get a debugger console gem 'byebug', platforms: [:mri, :mingw, :x64_mingw] diff --git a/Gemfile.lock b/Gemfile.lock index e55e9522f..01211fb12 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,11 +41,17 @@ GEM addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) arel (8.0.0) + autoprefixer-rails (8.6.1) + execjs bcrypt (3.1.11) bcrypt (3.1.11-java) bcrypt (3.1.11-x64-mingw32) bcrypt (3.1.11-x86-mingw32) bindex (0.5.0) + bootstrap (4.1.1) + autoprefixer-rails (>= 6.0.3) + popper_js (>= 1.12.9, < 2) + sass (>= 3.5.2) builder (3.2.3) byebug (10.0.0) capybara (2.17.0) @@ -61,6 +67,7 @@ GEM mime-types (>= 1.16) childprocess (0.8.0) ffi (~> 1.0, >= 1.0.11) + coderay (1.1.2) coffee-rails (4.2.2) coffee-script (>= 2.2.0) railties (>= 4.0.0) @@ -97,6 +104,22 @@ GEM jbuilder (2.7.0) activesupport (>= 4.2.0) multi_json (>= 1.2) + jquery-rails (4.3.3) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + kaminari (1.1.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.1.1) + kaminari-activerecord (= 1.1.1) + kaminari-core (= 1.1.1) + kaminari-actionview (1.1.1) + actionview + kaminari-core (= 1.1.1) + kaminari-activerecord (1.1.1) + activerecord + kaminari-core (= 1.1.1) + kaminari-core (1.1.1) listen (3.1.5) rb-fsevent (~> 0.9, >= 0.9.4) rb-inotify (~> 0.9, >= 0.9.7) @@ -124,6 +147,14 @@ GEM nokogiri (1.8.1-x86-mingw32) mini_portile2 (~> 2.3.0) orm_adapter (0.5.0) + popper_js (1.12.9) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry (0.11.3-java) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + spoon (~> 0.0) public_suffix (3.0.1) puma (3.11.2) puma (3.11.2-java) @@ -199,6 +230,8 @@ GEM rubyzip (~> 1.0) shoulda-matchers (3.1.2) activesupport (>= 4.0.0) + spoon (0.0.6) + ffi spring (2.0.2) activesupport (>= 4.2) spring-watcher-listen (2.0.1) @@ -251,6 +284,7 @@ PLATFORMS x86-mswin32 DEPENDENCIES + bootstrap (~> 4.1.1) byebug capybara (~> 2.13) carrierwave @@ -259,7 +293,10 @@ DEPENDENCIES factory_bot_rails ffaker jbuilder (~> 2.5) + jquery-rails (~> 4.3, >= 4.3.3) + kaminari (~> 1.1, >= 1.1.1) listen (>= 3.0.5, < 3.2) + pry (~> 0.11.3) puma (~> 3.7) rails (~> 5.1.4) rails-controller-testing @@ -269,6 +306,7 @@ DEPENDENCIES shoulda-matchers (~> 3.1) spring spring-watcher-listen (~> 2.0.0) + sprockets-rails (~> 3.1, >= 3.1.1) sqlite3 turbolinks (~> 5) tzinfo-data @@ -276,4 +314,4 @@ DEPENDENCIES web-console (>= 3.3.0) BUNDLED WITH - 1.16.1 + 1.16.2 diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 46b20359f..9380ae2e2 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -12,4 +12,7 @@ // //= require rails-ujs //= require turbolinks +//= require jquery3 +//= require popper +//= require bootstrap-sprockets //= require_tree . diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.scss similarity index 94% rename from app/assets/stylesheets/application.css rename to app/assets/stylesheets/application.scss index d05ea0f51..3c5c92a83 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.scss @@ -10,6 +10,8 @@ * files in this directory. Styles in this file should be added after the last require_* statement. * It is generally better to create a new file per style scope. * - *= require_tree . - *= require_self */ + +@import 'bootstrap'; + + diff --git a/app/controllers/admin/tweets_controller.rb b/app/controllers/admin/tweets_controller.rb index 24a57566c..ae034e69c 100644 --- a/app/controllers/admin/tweets_controller.rb +++ b/app/controllers/admin/tweets_controller.rb @@ -1,7 +1,23 @@ class Admin::TweetsController < Admin::BaseController + before_action :authenticate_user! + before_action :authenticate_admin + def index + @tweets = Tweet.order('created_at DESC').page(params[:page]).per(15) + render :index end def destroy + @tweet = Tweet.find(params[:id]) + @tweet.destroy + redirect_to admin_root_path + end + + + private + def authenticate_admin + unless current_user.role == 'admin' + redirect_back(fallback_location: root_path, alert: "You are Not admin !") + end end end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 3ba9f0a36..3976bfed2 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,4 +1,15 @@ class Admin::UsersController < Admin::BaseController + before_action :authenticate_user! + before_action :authenticate_admin + def index + @users = User.all.order(tweets_count: :desc).page(params[:page]).per(15) + end + + private + def authenticate_admin + unless current_user.role == 'admin' + redirect_back(fallback_location: root_path, alert: "You are Not admin !") + end end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0da627f1a..49f7d1167 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -4,4 +4,14 @@ class ApplicationController < ActionController::Base # 請參考 Devise 文件自訂表單後通過 Strong Parameters 的方法 # https://github.com/plataformatec/devise#strong-parameters # 注意有 sign_up 和 account_update 兩種參數要處理 + + before_action :authenticate_user! + before_action :configure_permitted_parameters, if: :devise_controller? + + protected + + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up, keys: [:name]) + devise_parameter_sanitizer.permit(:account_update, keys: [:name]) + end end diff --git a/app/controllers/followships_controller.rb b/app/controllers/followships_controller.rb index 05f01b552..3af5ed9ea 100644 --- a/app/controllers/followships_controller.rb +++ b/app/controllers/followships_controller.rb @@ -1,7 +1,21 @@ class FollowshipsController < ApplicationController def create + @followship = Followship.new(user_id: current_user.id, following_id: params[:following_id]) + if @followship.user_id == @followship.following_id + flash[:alert] = "Something went wrong here...you can not follow yourself..." + else + @followship.save + end + redirect_back(fallback_location: root_path) end def destroy + @followship = Followship.find_by(user_id: current_user.id, following_id: params[:id]) + if @followship + @followship.destroy + else + flash[:alert] = "Something went wrong here...please try again" + end + redirect_back(fallback_location: root_path) end end diff --git a/app/controllers/replies_controller.rb b/app/controllers/replies_controller.rb index a9b6a315b..5dc259700 100644 --- a/app/controllers/replies_controller.rb +++ b/app/controllers/replies_controller.rb @@ -1,9 +1,26 @@ class RepliesController < ApplicationController def index + @tweet = Tweet.find(params[:tweet_id]) + @replies = @tweet.replies.order('created_at DESC') + @user = current_user + @reply = Reply.new end def create + @tweet = Tweet.find(params[:tweet_id]) + @reply = @tweet.replies.build(reply_params) + @reply.user = current_user + if @reply.save + flash[:notice] = "Reply Created !" + else + flash[:alert] = @reply.errors.full_messages + end + redirect_to tweet_replies_path end + private + def reply_params + params.require(:reply).permit(:comment, :tweet_id, :user_id) + end end diff --git a/app/controllers/tweets_controller.rb b/app/controllers/tweets_controller.rb index ad14115c1..1a0b7d55c 100644 --- a/app/controllers/tweets_controller.rb +++ b/app/controllers/tweets_controller.rb @@ -1,16 +1,46 @@ class TweetsController < ApplicationController + before_action :find_tweet, only: [:like, :unlike] def index - @users # 基於測試規格,必須講定變數名稱,請用此變數中存放關注人數 Top 10 的使用者資料 + @users = User.order(followers_count: :desc).first(10) # 基於測試規格,必須講定變數名稱,請用此變數中存放關注人數 Top 10 的使用者資料 + @tweet = Tweet.new + @tweets = Tweet.order('created_at DESC').page(params[:page]).per(15) end def create + @tweet = current_user.tweets.build(tweet_params) + if @tweet.save! + flash[:notice] = 'Tweet is created successfully !' + else + flash[:alert] = @tweet.errors.messages + end + redirect_to tweets_path end def like + @like = Like.new(user_id: current_user.id, tweet_id: @tweet.id) + if @like.save + redirect_to tweets_path + else + flash[:alert] = like.errors.messages + redirect_back(fallback_location: root_path) + end end def unlike + @like = Like.find_by(user_id: current_user.id, tweet_id: @tweet.id) + if @like + @like.destroy + redirect_to tweets_path + end end + private + def tweet_params + params.require(:tweet).permit(:description, :user_id) + end + + def find_tweet + @tweet = Tweet.find(params[:id]) + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 750e3c6b5..d15c82fcd 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,24 +1,48 @@ class UsersController < ApplicationController - + before_action :find_user, only: [:edit, :update, :tweets, :followings, :followers, :likes] def tweets + @tweets = @user.tweets.order("created_at DESC") end def edit + if @user == current_user + render :edit + else + redirect_to tweets_user_path(@user) + end end def update + if @user.update(user_profile_params) + flash[:notice] = "User Profile Udpate Successfully !" + else + flash[:alert] = @user.error.full_messsages + end + redirect_to tweets_user_path(@user) end def followings - @followings # 基於測試規格,必須講定變數名稱 + @followings = @user.followings.order(followers_count: :desc) + # 基於測試規格,必須講定變數名稱 end def followers - @followers # 基於測試規格,必須講定變數名稱 + @followers = @user.followers.order(tweets_count: :desc) + # 基於測試規格,必須講定變數名稱 end def likes - @likes # 基於測試規格,必須講定變數名稱 + @likes = @user.liked_tweets.order(likes_count: :desc) + # 基於測試規格,必須講定變數名稱 + end + + private + def find_user + @user = User.find(params[:id]) end + def user_profile_params + params.require(:user).permit(:introduction, :avatar, :name) + end + end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index de6be7945..dbf5ab368 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -1,2 +1,5 @@ module ApplicationHelper + def in_admin_panel? + request.fullpath.include?('admin') + end end diff --git a/app/models/followship.rb b/app/models/followship.rb index 1aed01396..36624864b 100644 --- a/app/models/followship.rb +++ b/app/models/followship.rb @@ -1,4 +1,6 @@ class Followship < ApplicationRecord - validates :following_id, uniqueness: { scope: :user_id } - + + belongs_to :following, class_name: "User",counter_cache: :followers_count + belongs_to :user, counter_cache: :followings_count + validates_uniqueness_of :following_id, scope: :user_id end diff --git a/app/models/like.rb b/app/models/like.rb index d99b93a32..12938d457 100644 --- a/app/models/like.rb +++ b/app/models/like.rb @@ -1,2 +1,4 @@ class Like < ApplicationRecord + belongs_to :user, counter_cache: :likes_count + belongs_to :tweet, counter_cache: :likes_count end diff --git a/app/models/reply.rb b/app/models/reply.rb index bae6f9463..df5664581 100644 --- a/app/models/reply.rb +++ b/app/models/reply.rb @@ -1,2 +1,4 @@ class Reply < ApplicationRecord + belongs_to :user, counter_cache: :replies_count + belongs_to :tweet, counter_cache: :replies_count end diff --git a/app/models/tweet.rb b/app/models/tweet.rb index 6715fada2..92c69f47f 100644 --- a/app/models/tweet.rb +++ b/app/models/tweet.rb @@ -1,4 +1,7 @@ class Tweet < ApplicationRecord + validates_presence_of :description validates_length_of :description, maximum: 140 - + belongs_to :user, counter_cache: :tweets_count + has_many :replies, dependent: :destroy + has_many :likes, dependent: :destroy end diff --git a/app/models/user.rb b/app/models/user.rb index 6b05b8c21..eb8c89b26 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -8,7 +8,37 @@ class User < ApplicationRecord # 需要 app/views/devise 裡找到樣板,加上 name 屬性 # 並參考 Devise 文件自訂表單後通過 Strong Parameters 的方法 - validates_presence_of :name + validates_presence_of :name, :email, :password + validates :name, uniqueness: {case_sensitive: true} + + # 加上驗證 name 不能重覆 (關鍵字提示: uniqueness) + has_many :tweets + has_many :replies + has_many :likes + has_many :liked_tweets, through: :likes, source: :tweet + has_many :followships + has_many :followings, through: :followships + has_many :inverse_followships, class_name: "Followship", foreign_key: "following_id" + has_many :followers, through: :inverse_followships, source: :user + scope :recent, -> {order("created_at DESC")} + + def admin? + if self.role == "admin" + true + else + false + end + end + + def is_following?(user) + self.followings.include?(user) + end + + def already_liked?(tweet) + self.liked_tweets.include?(tweet) + end + + end diff --git a/app/views/admin/tweets/index.html.erb b/app/views/admin/tweets/index.html.erb new file mode 100644 index 000000000..3c5eaaf9f --- /dev/null +++ b/app/views/admin/tweets/index.html.erb @@ -0,0 +1,34 @@ +
+
+

Simple Twitter Admin Panel + <%= link_to 'Tweets', admin_tweets_path, class: 'btn btn-primary' %> | <%= link_to 'Users', admin_users_path, class: 'btn btn-info' %> +

+
+ + + + + + + + + + <% @tweets.each do |tweet| %> + + + + + + + + + <% end %> +
UsernameAvatarTweetsReplies
<%= tweet.user.name %><%= image_tag tweet.user.avatar, size:100 %><%= tweet.description %>
<%= link_to 'Delete', admin_tweet_path(tweet), method: :delete, data: {confirm:"Are you sure?"}, class: 'btn btn-danger' %>
<% tweet.replies.each do |reply| %> + <%= reply.user.name %>:<%=reply.comment %>
+ <% end %> +
+
+ +
+ <%= paginate @tweets %> +
\ No newline at end of file diff --git a/app/views/admin/users/index.html.erb b/app/views/admin/users/index.html.erb new file mode 100644 index 000000000..97146cd0b --- /dev/null +++ b/app/views/admin/users/index.html.erb @@ -0,0 +1,33 @@ +
+

Simple Twitter Admin Panel + <%= link_to 'Tweets', admin_tweets_path, class: 'btn btn-primary' %> | <%= link_to 'Users', admin_users_path, class: 'btn btn-info' %> +

+ + + + + + + + + + + + <% @users.each do |user| %> + + + + + + + + + + + <% end %> +
UsernameAvatarTweets CountFollowers CountFollowings CountLikes Count
<%= user.name %><%= image_tag user.avatar, size: 100 %><%= user.tweets_count ||= 0 %><%= user.followers_count ||= 0 %><%= user.followings.count ||= 0%><%= user.likes_count ||= 0%>
+
+ +
+ <%= paginate @users %> +
\ No newline at end of file diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index 10ed32a9e..6f9615874 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -12,6 +12,11 @@
Currently waiting confirmation for: <%= resource.unconfirmed_email %>
<% end %> +
+ <%= f.label :name %>
+ <%= f.email_field :name, autofocus: true, autocomplete: "off" %> +
+
<%= f.label :password %> (leave blank if you don't want to change it)
<%= f.password_field :password, autocomplete: "off" %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb index 602803cff..6c915c91f 100644 --- a/app/views/devise/registrations/new.html.erb +++ b/app/views/devise/registrations/new.html.erb @@ -8,6 +8,11 @@ <%= f.email_field :email, autofocus: true, autocomplete: "email" %>
+
+ <%= f.label :name %>
+ <%= f.text_field :name, autofocus: true, autocomplete: "off" %> +
+
<%= f.label :password %> <% if @minimum_password_length %> diff --git a/app/views/kaminari/_first_page.html.erb b/app/views/kaminari/_first_page.html.erb new file mode 100644 index 000000000..cc107ec6e --- /dev/null +++ b/app/views/kaminari/_first_page.html.erb @@ -0,0 +1,3 @@ +
  • + <%= link_to_unless current_page.first?, raw(t 'views.pagination.first'), url, remote: remote, class: 'page-link' %> +
  • diff --git a/app/views/kaminari/_gap.html.erb b/app/views/kaminari/_gap.html.erb new file mode 100644 index 000000000..d241fc4eb --- /dev/null +++ b/app/views/kaminari/_gap.html.erb @@ -0,0 +1,3 @@ +
  • + <%= link_to raw(t 'views.pagination.truncate'), '#', class: 'page-link' %> +
  • diff --git a/app/views/kaminari/_last_page.html.erb b/app/views/kaminari/_last_page.html.erb new file mode 100644 index 000000000..9a7584eb4 --- /dev/null +++ b/app/views/kaminari/_last_page.html.erb @@ -0,0 +1,3 @@ +
  • + <%= link_to_unless current_page.last?, raw(t 'views.pagination.last'), url, remote: remote, class: 'page-link' %> +
  • diff --git a/app/views/kaminari/_next_page.html.erb b/app/views/kaminari/_next_page.html.erb new file mode 100644 index 000000000..7ca72a819 --- /dev/null +++ b/app/views/kaminari/_next_page.html.erb @@ -0,0 +1,3 @@ +
  • + <%= link_to_unless current_page.last?, raw(t 'views.pagination.next'), url, rel: 'next', remote: remote, class: 'page-link' %> +
  • diff --git a/app/views/kaminari/_page.html.erb b/app/views/kaminari/_page.html.erb new file mode 100644 index 000000000..c8f03f236 --- /dev/null +++ b/app/views/kaminari/_page.html.erb @@ -0,0 +1,9 @@ +<% if page.current? %> +
  • + <%= content_tag :a, page, data: { remote: remote }, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)), class: 'page-link' %> +
  • +<% else %> +
  • + <%= link_to page, url, remote: remote, rel: (page.next? ? 'next' : (page.prev? ? 'prev' : nil)), class: 'page-link' %> +
  • +<% end %> diff --git a/app/views/kaminari/_paginator.html.erb b/app/views/kaminari/_paginator.html.erb new file mode 100644 index 000000000..bb5ed0ff3 --- /dev/null +++ b/app/views/kaminari/_paginator.html.erb @@ -0,0 +1,17 @@ +<%= paginator.render do %> + +<% end %> diff --git a/app/views/kaminari/_prev_page.html.erb b/app/views/kaminari/_prev_page.html.erb new file mode 100644 index 000000000..c7803fac4 --- /dev/null +++ b/app/views/kaminari/_prev_page.html.erb @@ -0,0 +1,3 @@ +
  • + <%= link_to_unless current_page.first?, raw(t 'views.pagination.previous'), url, rel: 'prev', remote: remote, class: 'page-link' %> +
  • diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 952cb7a1b..8d8bc0db8 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -6,23 +6,34 @@ <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + - <% if current_user %> - <% if current_user&.admin? %> -
  • <%= link_to 'Admin Panel', admin_restaurants_path %>
  • - <% end %> -
  • -
  • <%= link_to('登出', destroy_user_session_path, method: :delete) %>
  • -
  • <%= link_to('修改個人資料', edit_user_path(current_user)) %>
  • -
  • <%= link_to('修改密碼', edit_user_registration_path) %>
  • - <% else %> -
  • <%= link_to('註冊', new_user_registration_path) %>
  • -
  • <%= link_to('登入', new_user_session_path) %>
  • +
    + <%= render 'share/navbar' %> +
    + + <% if flash[:notice] %> +
    + +
    <% end %> -

    <%= notice %>

    -

    <%= alert %>

    - <%= yield %> + + <% if flash[:alert] %> +
    + +
    + <% end %> + +
    + <%= yield %> +
    diff --git a/app/views/replies/index.html.erb b/app/views/replies/index.html.erb new file mode 100644 index 000000000..de097c077 --- /dev/null +++ b/app/views/replies/index.html.erb @@ -0,0 +1,38 @@ +
    +
    + <%= render partial: 'share/user-panel' %> +
    +
    +

    Tweets

    +
    +
    + <%= image_tag @tweet.user.avatar, class: 'img-fluid' %> +
    +
    + <%= @tweet.user.name %><%= ", #{@tweet.created_at}" %>
    + <%= @tweet.description %>
    + <%= link_to "Reply (#{@tweet.replies.count})", tweet_replies_path(@tweet), class: 'btn btn-info'%> +
    +
    + +

    Replies

    + <% @replies.each do |reply| %> +
    +
    + <%= image_tag reply.user.avatar, class: 'img-fluid' %> +
    +
    + <%= link_to reply.user.name, tweets_user_path(reply.user) %><%= ", #{reply.created_at}" %>
    + <%= reply.comment %>
    +
    +
    + <% end %> +
    + <%= form_for [@tweet,@reply] do |f| %> + <%= f.text_area :comment, class: 'form-control', placeholder: 'leave your comments', rows: '3' %>
    + <%= f.submit 'Submit' , class: 'btn btn-primary' %> + <% end %> +
    + +
    +
    diff --git a/app/views/share/_follow-btn.html.erb b/app/views/share/_follow-btn.html.erb new file mode 100644 index 000000000..016a304ce --- /dev/null +++ b/app/views/share/_follow-btn.html.erb @@ -0,0 +1,6 @@ +<% if current_user.is_following?(object) %> + <%= link_to 'Unfollow', followship_path(object.id), method: :delete, class: 'btn btn-danger' %> +<% else %> + <%= link_to 'Follow', followships_path(following_id: object.id), method: :post, class: 'btn btn-primary' %> +<% end %> + diff --git a/app/views/share/_like-btn.html.erb b/app/views/share/_like-btn.html.erb new file mode 100644 index 000000000..c2366c795 --- /dev/null +++ b/app/views/share/_like-btn.html.erb @@ -0,0 +1,5 @@ +<% if current_user.already_liked?(object) %> + <%= link_to " (#{object.likes_count})".html_safe, unlike_tweet_path(object), method: :post %> +<% else %> + <%= link_to " (#{object.likes_count})".html_safe, like_tweet_path(object), method: :post %> +<% end %> \ No newline at end of file diff --git a/app/views/share/_navbar.html.erb b/app/views/share/_navbar.html.erb new file mode 100644 index 000000000..40f5deb54 --- /dev/null +++ b/app/views/share/_navbar.html.erb @@ -0,0 +1,35 @@ + diff --git a/app/views/share/_user-panel.html.erb b/app/views/share/_user-panel.html.erb new file mode 100644 index 000000000..e5e46a189 --- /dev/null +++ b/app/views/share/_user-panel.html.erb @@ -0,0 +1,21 @@ +
    + <%= image_tag @user.avatar, class: 'img-fluid' if @user %>
    +

    <%= @user.name %>

    +

    <%= @user.introduction %>

    + <%= link_to "Tweets #{@user.tweets_count}", tweets_user_path(@user), class: 'btn btn-outline-primary' %>
    + +
    + <%= link_to "Followers #{@user.followers.size}", followers_user_path(@user), class: 'btn btn-outline-success' %> +
    +
    + <%= link_to "Followings #{@user.followings.size}", followings_user_path(@user), class: 'btn btn-outline-danger' %> +
    +
    + <%= link_to "Likes #{@user.likes_count}", likes_user_path(@user), class: 'btn btn-outline-warning' %> +
    + <% if @user == current_user %> + <%= link_to 'Edit Profile', edit_user_path(@user), class: 'btn btn-primary' %> + <% else %> + <%= render partial: 'share/follow-btn', locals: {object: @user} %> + <% end %> +
    diff --git a/app/views/tweets/index.html.erb b/app/views/tweets/index.html.erb new file mode 100644 index 000000000..9ad922d65 --- /dev/null +++ b/app/views/tweets/index.html.erb @@ -0,0 +1,50 @@ +
    +
    + <%= form_for @tweet do |f| %> + <%= f.text_area :description, class: 'form-control my-3 py-3', placeholder: 'What is in your mind? ', maxlength: 140, rows: '8' %> + <%= f.submit 'Tweet' %> + <% end %> + <% @tweets.each do |tweet| %> +
    +
    + <%= image_tag tweet.user.avatar, class: 'img-fluid' %> +
    +
    +
    +
    + <%= link_to tweet.user.name + " #{tweet.created_at}", tweets_user_path(tweet.user), class: 'text-primary' %>
    + <%= tweet.description %> +
    +
    + <%= link_to "Reply (#{tweet.replies.count})", tweet_replies_path(tweet_id: tweet.id), class: 'btn btn-info'%> +
    +
    + <%= render partial: 'share/like-btn' , locals: {object: tweet}%> +
    +
    +
    +
    + <% end %> +
    + +
    +
    + <% @users.each do |user| %> +
    +
    + <%= image_tag user.avatar, class: 'img-fluid' %> +
    +
    + <%= link_to user.name, tweets_user_path(user) %>
    + <%= user.introduction %>
    + <%= render partial: 'share/follow-btn' , locals: {object: user}%> +
    +
    + <% end %> +
    +
    +
    + +
    + <%= paginate @tweets %> +
    \ No newline at end of file diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb new file mode 100644 index 000000000..f494ff181 --- /dev/null +++ b/app/views/users/edit.html.erb @@ -0,0 +1,21 @@ +
    +
    + <%= form_for @user do |f| %> +
    + <% if @user.avatar.blank? %> + + <% else %> + <%= image_tag @user.avatar, width: '300px' %> + <% end %> +

    + <%= @user.introduction %>

    + <%= f.file_field :avatar %> +
    +
    + <%= f.text_field :name, class: "form-control" ,placeholder: "Name" %>
    + <%= f.text_area :introduction, class: "form-control" ,placeholder: "Write something about your self !" %>
    + <%= f.submit 'Update' %> +
    + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/users/followers.html.erb b/app/views/users/followers.html.erb new file mode 100644 index 000000000..4e5a742c1 --- /dev/null +++ b/app/views/users/followers.html.erb @@ -0,0 +1,22 @@ +
    +
    + <%= render partial: 'share/user-panel' %> +
    +
    +
    + <% @followers.each do |follower| %> +
    +
    + <%= image_tag follower.avatar, class: 'img-fluid' %> +
    +
    + <%= link_to follower.name, tweets_user_path(follower) %>
    + <%= follower.introduction %>
    + <%= render partial: 'share/follow-btn' , locals: {object: follower}%> + +
    +
    + <% end %> +
    +
    +
    \ No newline at end of file diff --git a/app/views/users/followings.html.erb b/app/views/users/followings.html.erb new file mode 100644 index 000000000..025819986 --- /dev/null +++ b/app/views/users/followings.html.erb @@ -0,0 +1,29 @@ +
    +
    + <%= render partial: 'share/user-panel' %> +
    +
    +
    + <% @followings.each do |following| %> +
    +
    + <%= image_tag following.avatar, class: 'img-fluid' %> +
    +
    +
    +
    + <%= link_to following.name, tweets_user_path(following) %> +
    +
    + <%= following.introduction %> +
    +
    + <%= render partial: 'share/follow-btn' ,locals: {object: following}%> +
    +
    +
    +
    + <% end %> +
    +
    +
    \ No newline at end of file diff --git a/app/views/users/likes.html.erb b/app/views/users/likes.html.erb new file mode 100644 index 000000000..d05438dfc --- /dev/null +++ b/app/views/users/likes.html.erb @@ -0,0 +1,19 @@ +
    +
    + <%= render partial: 'share/user-panel' %> +
    +
    + <% @likes.each do |like| %> +
    +
    + <%= image_tag like.user.avatar, class: 'img-fluid' %> +
    +
    + <%= like.user.name %><%= ", #{like.created_at}" %>
    + <%= like.description %>
    + <%= link_to "Reply (#{like.replies.count})", tweet_replies_path(like), class: 'btn btn-info'%> +
    +
    + <% end %> +
    +
    \ No newline at end of file diff --git a/app/views/users/tweets.html.erb b/app/views/users/tweets.html.erb new file mode 100644 index 000000000..48005061b --- /dev/null +++ b/app/views/users/tweets.html.erb @@ -0,0 +1,20 @@ +
    +
    + <%= render partial: 'share/user-panel' %> +
    +
    + <% @tweets.each do |tweet| %> +
    +
    + <%= image_tag tweet.user.avatar, class: 'img-fluid' %> +
    +
    + <%= link_to tweet.user.name + " #{tweet.created_at}", tweets_user_path(tweet.user), class: 'text-primary' %>
    + <%= tweet.description %>
    + <%= link_to "Reply (#{tweet.replies.count})", tweet_replies_path(tweet), class: 'btn btn-info'%> + <%= render partial: 'share/like-btn' , locals: {object: tweet}%> +
    +
    + <% end %> +
    +
    \ No newline at end of file diff --git a/config/routes.rb b/config/routes.rb index 90856d4fe..2b08a573e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,5 +3,30 @@ devise_for :users # 請依照專案指定規格來設定路由 + root "tweets#index" + resources :tweets, only: [:index, :create] do + member do + post :like + post :unlike + end + resources :replies, only: [:index, :create] + end + resources :users, only: [:edit, :update] do + member do + get :followings + get :followers + get :tweets + get :likes + end + end + + resources :followships, only: [:create, :destroy] + + + namespace :admin do + resources :tweets + resources :users, only: [:index] + root "tweets#index" + end end diff --git a/db/migrate/20180607125647_add_tweets_count_on_user.rb b/db/migrate/20180607125647_add_tweets_count_on_user.rb new file mode 100644 index 000000000..0976dd951 --- /dev/null +++ b/db/migrate/20180607125647_add_tweets_count_on_user.rb @@ -0,0 +1,9 @@ +class AddTweetsCountOnUser < ActiveRecord::Migration[5.1] + def change + add_column :users, :tweets_count, :integer, default: 0 + + User.pluck(:id).each do |i| + User.reset_counters(i, :tweets) + end + end +end diff --git a/db/migrate/20180607130502_add_replies_count_on_user.rb b/db/migrate/20180607130502_add_replies_count_on_user.rb new file mode 100644 index 000000000..3238e8da0 --- /dev/null +++ b/db/migrate/20180607130502_add_replies_count_on_user.rb @@ -0,0 +1,9 @@ +class AddRepliesCountOnUser < ActiveRecord::Migration[5.1] + def change + add_column :users, :replies_count, :integer, default: 0 + + User.pluck(:id).each do |i| + User.reset_counters(i, :replies) + end + end +end diff --git a/db/migrate/20180801222347_add_followings_count_to_user.rb b/db/migrate/20180801222347_add_followings_count_to_user.rb new file mode 100644 index 000000000..cc7ea08b3 --- /dev/null +++ b/db/migrate/20180801222347_add_followings_count_to_user.rb @@ -0,0 +1,7 @@ +class AddFollowingsCountToUser < ActiveRecord::Migration[5.1] + def change + add_column :users, :followings_count, :integer, default: 0 + + + end +end diff --git a/db/schema.rb b/db/schema.rb index 6a6842c86..a37546002 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180201102838) do +ActiveRecord::Schema.define(version: 20180801222347) do create_table "followships", force: :cascade do |t| t.integer "user_id" @@ -66,6 +66,9 @@ t.integer "likes_count", default: 0 t.string "role", default: "normal" t.integer "followers_count", default: 0 + t.integer "tweets_count", default: 0 + t.integer "replies_count", default: 0 + t.integer "followings_count", default: 0 t.index ["email"], name: "index_users_on_email", unique: true t.index ["name"], name: "index_users_on_name", unique: true t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true diff --git a/db/seeds.rb b/db/seeds.rb index 1beea2acc..74c233618 100644 --- a/db/seeds.rb +++ b/db/seeds.rb @@ -5,3 +5,10 @@ # # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) # Character.create(name: 'Luke', movie: movies.first) + + +# Default admin +ApplicationRecord.reset_column_information +admin_file = File.open("#{Rails.root}/public/avatar/user15.jpg") +admin = User.create!(email: "josh@example.com", password: "12345678", name: "josh", :role=> "admin", avatar: admin_file) +puts "Default admin created!" \ No newline at end of file diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake index 9b1e87ae4..0bab0735f 100644 --- a/lib/tasks/dev.rake +++ b/lib/tasks/dev.rake @@ -1,23 +1,80 @@ namespace :dev do # 請先執行 rails dev:fake_user,可以產生 20 個資料完整的 User 紀錄 # 其他測試用的假資料請依需要自行撰寫 + task :build => ["tmp:clear", "log:clear", "db:drop", "db:create", "db:migrate"] + task :rebuild => [ "dev:build", :fake_user,"db:seed", :fake_tweet, :fake_reply, :fake_like, :fake_followship] + task fake_user: :environment do User.destroy_all 20.times do |i| name = FFaker::Name::first_name file = File.open("#{Rails.root}/public/avatar/user#{i+1}.jpg") - user = User.new( + user = User.create!( name: name, - email: "#{name}@example.co", + email: "#{name}@twitter.com", password: "12345678", - introduction: FFaker::Lorem::sentence(30), - avatar: file + introduction: FFaker::Lorem::sentence(20), + avatar: file, ) - - user.save! puts user.name end end + task fake_tweet: :environment do + Tweet.destroy_all + 30.times do |i| + user = User.all.sample + tweet = Tweet.create!( + description: FFaker::Lorem::sentence(8), + user: user, + created_at: FFaker::Time::datetime + ) + end + puts "Total #{Tweet.count}tweets created !" + end + + task fake_reply: :environment do + Reply.destroy_all + 50.times do |i| + user = User.all.sample + tweet = Tweet.all.sample + reply = Reply.create!( + tweet: tweet, + comment: FFaker::Lorem::sentence(3), + user: user, + created_at: FFaker::Time::datetime + ) + end + puts "Total #{Reply.count}replies created !" + end + + task fake_like: :environment do + Like.destroy_all + 50.times do |i| + user = User.all.sample + tweet = Tweet.all.sample + like = Like.create!( + tweet: tweet, + user: user, + created_at: FFaker::Time::datetime + ) + end + puts "Total #{Like.count}likes created !" + end + + task fake_followship: :environment do + Followship.destroy_all + users = User.all + users.each do |user| + 2.times do |i| + following = users.sample + if !user.is_following?(following) && following != user + followship = user.followships.create(following: following) + end + end + end + puts "Total #{Followship.count}followships created !" + end + end