diff --git a/Gemfile b/Gemfile index c1f1ee3e2..0b31580d8 100644 --- a/Gemfile +++ b/Gemfile @@ -11,13 +11,15 @@ gem 'devise' gem 'carrierwave' # resize image size # gem 'mini_magick' - +gem 'kaminari' gem 'ffaker' +gem 'jquery-rails' +gem 'bootstrap-sass', '~> 3.3.7' # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' gem 'rails', '~> 5.1.4' # Use sqlite3 as the database for Active Record -gem 'sqlite3' + # Use Puma as the app server gem 'puma', '~> 3.7' # Use SCSS for stylesheets @@ -60,6 +62,10 @@ group :development do # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring gem 'spring' gem 'spring-watcher-listen', '~> 2.0.0' + gem 'sqlite3' +end +group :production do + gem 'pg', '~> 0.20' end # Windows does not include zoneinfo files, so bundle the tzinfo-data gem diff --git a/Gemfile.lock b/Gemfile.lock index e55e9522f..31fef2236 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -41,11 +41,16 @@ GEM addressable (2.5.2) public_suffix (>= 2.0.2, < 4.0) arel (8.0.0) + autoprefixer-rails (9.0.2) + 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-sass (3.3.7) + autoprefixer-rails (>= 5.2.1) + sass (>= 3.3.4) builder (3.2.3) byebug (10.0.0) capybara (2.17.0) @@ -97,6 +102,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 +145,9 @@ GEM nokogiri (1.8.1-x86-mingw32) mini_portile2 (~> 2.3.0) orm_adapter (0.5.0) + pg (0.21.0) + pg (0.21.0-x64-mingw32) + pg (0.21.0-x86-mingw32) public_suffix (3.0.1) puma (3.11.2) puma (3.11.2-java) @@ -251,6 +275,7 @@ PLATFORMS x86-mswin32 DEPENDENCIES + bootstrap-sass (~> 3.3.7) byebug capybara (~> 2.13) carrierwave @@ -259,7 +284,10 @@ DEPENDENCIES factory_bot_rails ffaker jbuilder (~> 2.5) + jquery-rails + kaminari listen (>= 3.0.5, < 3.2) + pg (~> 0.20) puma (~> 3.7) rails (~> 5.1.4) rails-controller-testing @@ -276,4 +304,4 @@ DEPENDENCIES web-console (>= 3.3.0) BUNDLED WITH - 1.16.1 + 1.16.3 diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js deleted file mode 100644 index b16e53d6d..000000000 --- a/app/assets/config/manifest.js +++ /dev/null @@ -1,3 +0,0 @@ -//= link_tree ../images -//= link_directory ../javascripts .js -//= link_directory ../stylesheets .css diff --git a/app/assets/javascripts/admin/replies.coffee b/app/assets/javascripts/admin/replies.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/admin/replies.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 46b20359f..38d5802a2 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -13,3 +13,5 @@ //= require rails-ujs //= require turbolinks //= require_tree . +//= require jquery +//= require bootstrap-sprockets diff --git a/app/assets/stylesheets/admin/replies.scss b/app/assets/stylesheets/admin/replies.scss new file mode 100644 index 000000000..d0572e7b5 --- /dev/null +++ b/app/assets/stylesheets/admin/replies.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the admin::replies controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/admin/tweets.scss b/app/assets/stylesheets/admin/tweets.scss index 9ec2a3732..b27805dc4 100644 --- a/app/assets/stylesheets/admin/tweets.scss +++ b/app/assets/stylesheets/admin/tweets.scss @@ -1,3 +1,3 @@ -// Place all the styles related to the admin/tweets controller here. +// Place all the styles related to the admin::tweets controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/admin/users.scss b/app/assets/stylesheets/admin/users.scss index 925e31223..2cabf7bd7 100644 --- a/app/assets/stylesheets/admin/users.scss +++ b/app/assets/stylesheets/admin/users.scss @@ -1,3 +1,3 @@ -// Place all the styles related to the admin/users controller here. +// Place all the styles related to the admin::users controller here. // They will automatically be included in application.css. // You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.scss similarity index 90% rename from app/assets/stylesheets/application.css rename to app/assets/stylesheets/application.scss index d05ea0f51..1f6d01a71 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-sprockets"; +@import "bootstrap"; +@import "style"; + diff --git a/app/assets/stylesheets/style.scss b/app/assets/stylesheets/style.scss new file mode 100644 index 000000000..fbc45bf61 --- /dev/null +++ b/app/assets/stylesheets/style.scss @@ -0,0 +1,76 @@ +.nav-pills-center { + text-align: center; +} + +.nav-pills-center > li { + float: none; + display: inline-block; +} + + +.panel-body{ + text-align: center; +} + +.avatar{ + height: 300px; + float: left; + width: 28%; + margin-right: 20px; +} +.edit_form{ + float: left; + width: 68%; +} +.avatar img{ + height: 200px; + width: 150px; + display: block; +} + +.nav-tabs{ + margin-bottom: 20px; +} +.restaurant_feed{ + width: 48%; + float: left; + margin-right: 2%; +} +.panel-default { + padding: 5px; +} +.user-item { + margin-bottom: 20px; +} +.tweet { + border: 1px solid; + padding:10px; + margin-top: 30px; + margin-bottom: 30px; +} +.information { + width: 68%; + float: left; + margin-right: 20px; +} +.popular{ + width: 28%; + float: left; +} +.tweet-avatar{ + float: left; + margin: 0.5%; + margin-right: 15px; + margin-bottom: 15px; +} +.form_group{ + margin-top: 5px; +} +.about{ + width: 28%; + float: left; +} +.content{ + width: 68%; + float: left; +} diff --git a/app/controllers/admin/base_controller.rb b/app/controllers/admin/base_controller.rb index 4a89583f5..69bb7fe52 100644 --- a/app/controllers/admin/base_controller.rb +++ b/app/controllers/admin/base_controller.rb @@ -1,3 +1,4 @@ class Admin::BaseController < ApplicationController + end diff --git a/app/controllers/admin/replies_controller.rb b/app/controllers/admin/replies_controller.rb new file mode 100644 index 000000000..1aad9009c --- /dev/null +++ b/app/controllers/admin/replies_controller.rb @@ -0,0 +1,21 @@ +class Admin::RepliesController < ApplicationController + def index + @replies = Reply.order(created_at: :desc) + @tweet = Tweet.find(params[:tweet_id]) + if current_user.admin? == false + redirect_to root_path + end + + end + + + def destroy + @tweet= Tweet.find(params[:tweet_id]) + @reply = Reply.find(params[:id]) + + if current_user.admin? + @reply.destroy + redirect_to admin_tweet_replies_path(@tweet) + end + end +end diff --git a/app/controllers/admin/tweets_controller.rb b/app/controllers/admin/tweets_controller.rb index 24a57566c..b2eeb7e8c 100644 --- a/app/controllers/admin/tweets_controller.rb +++ b/app/controllers/admin/tweets_controller.rb @@ -1,7 +1,20 @@ class Admin::TweetsController < Admin::BaseController def index + if current_user.admin? == false + redirect_to root_path + end + @tweets = Tweet.order(created_at: :desc) end - + def destroy + @tweet = Tweet.find(params[:id]) + if current_user.admin? + @tweet.destroy + redirect_to admin_root_path + end + flash[:alert] = "tweet was deleted" end + + + end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index 3ba9f0a36..dd7eb4881 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -1,4 +1,10 @@ -class Admin::UsersController < Admin::BaseController - def index - end +class Admin::UsersController < ApplicationController + def index + @users = User.order(tweets_count: :desc) + if current_user.admin? == false + redirect_to root_path + end + + end + end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 0da627f1a..5b0dc34f5 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,7 +1,18 @@ class ApplicationController < ActionController::Base + before_action :authenticate_user! protect_from_forgery with: :exception + before_action :configure_permitted_parameters, if: :devise_controller? + + # 請參考 Devise 文件自訂表單後通過 Strong Parameters 的方法 # https://github.com/plataformatec/devise#strong-parameters # 注意有 sign_up 和 account_update 兩種參數要處理 + + 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..f90c1d100 100644 --- a/app/controllers/followships_controller.rb +++ b/app/controllers/followships_controller.rb @@ -1,7 +1,28 @@ class FollowshipsController < ApplicationController def create - end + @user = User.find(params[:following_id]) + if @user == current_user + flash[:notice] = "Can't follow self!" + redirect_back(fallback_location: root_path) + else + @followship = current_user.followships.build(following_id: params[:following_id]) + if @followship.save + flash[:notice] = "Successfully followed" + redirect_back(fallback_location: root_path) + else + flash[:alert] = @followship.errors.full_messages.to_sentence + redirect_back(fallback_location: root_path) + end + end + end def destroy + @followship = current_user.followships.where(following_id:params[:id]).first + @followship.destroy + flash[:alert] = "Followship destroyed" + redirect_back(fallback_location: root_path) + #複數寫法 + # @followship = current_user.followships.where(following_id: params[:id]) + # @followship.destroy_all end end diff --git a/app/controllers/replies_controller.rb b/app/controllers/replies_controller.rb index a9b6a315b..3d973620e 100644 --- a/app/controllers/replies_controller.rb +++ b/app/controllers/replies_controller.rb @@ -1,9 +1,29 @@ class RepliesController < ApplicationController + def index + @replies = Reply.all + @tweet = Tweet.find(params[:tweet_id]) + @reply = Reply.new + end + def create + @tweet= Tweet.find(params[:tweet_id]) + @reply= @tweet.replies.build(reply_params) + @reply.user= current_user + @reply.save! + redirect_to tweet_replies_path(@tweet) + end - def index - end + def destroy + @tweet= Tweet.find(params[:tweet_id]) + @reply = Reply.find(params[:id]) - def create - end + if current_user.admin? + @reply.destroy + redirect_to tweets_user_path(user) + end + end + private + def reply_params + params.require(:reply).permit(:comment) + end end diff --git a/app/controllers/tweets_controller.rb b/app/controllers/tweets_controller.rb index ad14115c1..7ffaefc33 100644 --- a/app/controllers/tweets_controller.rb +++ b/app/controllers/tweets_controller.rb @@ -1,16 +1,41 @@ class TweetsController < ApplicationController - def index - @users # 基於測試規格,必須講定變數名稱,請用此變數中存放關注人數 Top 10 的使用者資料 + @users = User.order(followers_count: :desc).includes(:followers).limit(10) + @tweet = Tweet.new + @tweets = Tweet.order(created_at: :desc) + # 新增一組空白資料給表單,首頁為index,@tweet傳入表單的實例變數 + # @users # 基於測試規格,必須講定變數名稱,請用此變數中存放關注人數 Top 10 的使用者資料 end - + def create + @user = current_user + @tweet= @user.tweets.build(tweet_params) + @tweet.save! + if @tweet.save + flash[:notice] = "tweet is successfully create" + redirect_to tweets_path + else + flash.now[:alert] = "tweet is failed to create" + render :new + end end - def like + @tweet = Tweet.find(params[:id]) + @tweet.likes.create!(user: current_user) + redirect_to tweets_path + end def unlike + @tweet = Tweet.find(params[:id]) + likes = Like.where(tweet: @tweet, user: current_user) + likes.destroy_all + redirect_to tweets_path + end + private + def tweet_params + params.require(:tweet).permit(:description) + end end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 750e3c6b5..d8cabf27d 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,24 +1,61 @@ class UsersController < ApplicationController - + before_action :set_user, only: [ :edit, :update, :tweets,:followings,:followers,:likes] def tweets + @followings = @user.followings.order(created_at: :desc) + @followers = @user.followers.order(created_at: :desc) + @likes = @user.likes.order(created_at: :desc) + @tweets = @user.tweets + @order_user_tweets = @user.tweets.order(created_at: :desc) + end + def followings + @followings = @user.followings.order('followships.created_at DESC') + @followers = @user.followers + @likes = @user.likes.order(created_at: :desc) + @tweets = @user.tweets + end + def followers + @followers = @user.followers.order('followships.created_at DESC') + @followings = @user.followings + @likes = @user.likes.order(created_at: :desc) + @tweets = @user.tweets end + def likes + @followings = @user.followings + @followers = @user.followers + @likes = @user.liked_tweets.order('likes.created_at DESC') + @tweets = @user.tweets + end def edit + unless @user == current_user + redirect_to tweets_user_path(@user), alert: "Can't edit other's profile" + end end + def update + if @user == current_user + if @user.update(user_params) + flash[:notice] = "Profile updated" + redirect_to root_path + else + flash[:alert] = @user.errors.full_messages.to_sentence + render :edit + end + else + flash[:alert] = "Can't edit other's profile" + redirect_to root_path + end end - def followings - @followings # 基於測試規格,必須講定變數名稱 - end + private - def followers - @followers # 基於測試規格,必須講定變數名稱 + def set_user + @user = User.find(params[:id]) end - def likes - @likes # 基於測試規格,必須講定變數名稱 + def user_params + params.require(:user).permit(:name, :introduction, :avatar) end - end + diff --git a/app/helpers/admin/replies_helper.rb b/app/helpers/admin/replies_helper.rb new file mode 100644 index 000000000..4f56822e6 --- /dev/null +++ b/app/helpers/admin/replies_helper.rb @@ -0,0 +1,2 @@ +module Admin::RepliesHelper +end diff --git a/app/models/followship.rb b/app/models/followship.rb index 1aed01396..ccee25467 100644 --- a/app/models/followship.rb +++ b/app/models/followship.rb @@ -1,4 +1,5 @@ class Followship < ApplicationRecord validates :following_id, uniqueness: { scope: :user_id } - + belongs_to :user + belongs_to :following,counter_cache: :followers_count, class_name:"User" end diff --git a/app/models/like.rb b/app/models/like.rb index d99b93a32..1fcb9b445 100644 --- a/app/models/like.rb +++ b/app/models/like.rb @@ -1,2 +1,4 @@ class Like < ApplicationRecord + belongs_to :tweet,counter_cache: true + belongs_to :user end diff --git a/app/models/reply.rb b/app/models/reply.rb index bae6f9463..3dba85815 100644 --- a/app/models/reply.rb +++ b/app/models/reply.rb @@ -1,2 +1,4 @@ class Reply < ApplicationRecord + belongs_to :user + belongs_to :tweet, counter_cache: true end diff --git a/app/models/tweet.rb b/app/models/tweet.rb index 6715fada2..382f2b3f4 100644 --- a/app/models/tweet.rb +++ b/app/models/tweet.rb @@ -1,4 +1,12 @@ class Tweet < ApplicationRecord validates_length_of :description, maximum: 140 + belongs_to :user, counter_cache: true + has_many :replies,dependent: :destroy + # 喜歡關係 + has_many :likes, dependent: :destroy + has_many :liked_users, through: :likes, source: :user + def is_liked?(user) + self.liked_users.include?(user) + end end diff --git a/app/models/user.rb b/app/models/user.rb index 6b05b8c21..e6799c18c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -5,10 +5,31 @@ class User < ApplicationRecord :recoverable, :rememberable, :trackable, :validatable mount_uploader :avatar, AvatarUploader + has_many :tweets,dependent: :destroy # 需要 app/views/devise 裡找到樣板,加上 name 屬性 # 並參考 Devise 文件自訂表單後通過 Strong Parameters 的方法 validates_presence_of :name # 加上驗證 name 不能重覆 (關鍵字提示: uniqueness) - + validates_uniqueness_of :name + #建立追蹤關係 + has_many :followships, dependent: :destroy + has_many :followings, through: :followships + #被誰追蹤 + has_many :inverse_followships, class_name: "Followship", foreign_key: "following_id",dependent: :destroy #主鍵指向following_id + has_many :followers, through: :inverse_followships, source: :user + # 建立多對多的關係,回應 + has_many :replies, dependent: :restrict_with_error + + # 建立喜歡 + has_many :likes, dependent: :destroy + has_many :liked_tweets, through: :likes, source: :tweet + + def admin? + self.role == "admin" + end + def following?(user) + self.followings.include?(user) + end + end diff --git a/app/views/admin/replies/index.html.erb b/app/views/admin/replies/index.html.erb new file mode 100644 index 000000000..51f307f7d --- /dev/null +++ b/app/views/admin/replies/index.html.erb @@ -0,0 +1,26 @@ +
<%= simple_format @tweet.description %>
++ <%= time_ago_in_words(@tweet.created_at) %> +
+ <% if current_user.admin? %> + <%= link_to "Delete", admin_tweet_path(@tweet), method: :delete %> + <%end%> +<%= simple_format reply.comment %>
++ <%= time_ago_in_words(reply.created_at) %> +
+ <% if current_user.admin? %> + <%= link_to "Delete", admin_tweet_reply_path(@tweet, reply), method: :delete %> + <%end%> +<%= simple_format tweet.description %>
++ <%= time_ago_in_words(tweet.created_at) %> +
+ <%= render partial: "shared/like", locals: {tweet: tweet} %> + <%= link_to "Reply(#{tweet.replies_count})" , admin_tweet_replies_path(tweet)%> + <%= link_to 'Delete', admin_tweet_path(tweet), method: :delete,class: "btn btn-primary" %> +| # | +Name | +推文數 | +關注人數 | +追蹤者人數 | +喜歡的推特 | +# | +
|---|---|---|---|---|---|---|
| <%= user.id %> | +<%= user.name %> | +<%= user.tweets.count%> | +<%= user.followings.count%> | +<%= user.followers.count%> | +<%= user.likes.count%> | +<%= user.followships.count%> | +
<%= notice %>
-<%= alert %>
- <%= yield %> +