diff --git a/Gemfile b/Gemfile
index 67c5b18..213ef62 100644
--- a/Gemfile
+++ b/Gemfile
@@ -3,6 +3,7 @@ git_source(:github){|repo| "https://github.com/#{repo}.git"}
ruby "2.7.0"
+gem "active_storage_validations", "0.8.2"
gem "bcrypt", "3.1.13"
gem "bootsnap", ">= 1.4.2", require: false
gem "bootstrap-sass", "3.4.1"
@@ -10,7 +11,9 @@ gem "bootstrap-will_paginate", "1.0.0"
gem "config"
gem "faker", "2.1.2"
gem "figaro"
+gem "image_processing", "1.9.3"
gem "jbuilder", "~> 2.7"
+gem "mini_magick", "4.9.5"
gem "puma", "~> 4.1"
gem "rails", "~> 6.0.3", ">= 6.0.3.3"
gem "rails-i18n"
diff --git a/Gemfile.lock b/Gemfile.lock
index d8b5c89..a600a44 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -37,6 +37,8 @@ GEM
erubi (~> 1.4)
rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0)
+ active_storage_validations (0.8.2)
+ rails (>= 5.2.0)
activejob (6.0.3.3)
activesupport (= 6.0.3.3)
globalid (>= 0.3.6)
@@ -136,6 +138,9 @@ GEM
activesupport (>= 4.2.0)
i18n (1.8.5)
concurrent-ruby (~> 1.0)
+ image_processing (1.9.3)
+ mini_magick (>= 4.9.5, < 5)
+ ruby-vips (>= 2.0.13, < 3)
jaro_winkler (1.5.4)
jbuilder (2.10.1)
activesupport (>= 5.0.0)
@@ -151,6 +156,7 @@ GEM
mimemagic (~> 0.3.2)
method_source (1.0.0)
mimemagic (0.3.5)
+ mini_magick (4.9.5)
mini_mime (1.0.2)
mini_portile2 (2.4.0)
minitest (5.14.2)
@@ -217,6 +223,8 @@ GEM
rack (>= 1.1)
rubocop (>= 0.72.0)
ruby-progressbar (1.10.1)
+ ruby-vips (2.0.17)
+ ffi (~> 1.9)
rubyzip (2.3.0)
sass-rails (6.0.0)
sassc-rails (~> 2.1, >= 2.1.1)
@@ -285,6 +293,7 @@ PLATFORMS
web-unknown
DEPENDENCIES
+ active_storage_validations (= 0.8.2)
bcrypt (= 3.1.13)
bootsnap (>= 1.4.2)
bootstrap-sass (= 3.4.1)
@@ -294,8 +303,10 @@ DEPENDENCIES
config
faker (= 2.1.2)
figaro
+ image_processing (= 1.9.3)
jbuilder (~> 2.7)
listen (~> 3.2)
+ mini_magick (= 4.9.5)
puma (~> 4.1)
rails (~> 6.0.3, >= 6.0.3.3)
rails-i18n
diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss
index bc8ee01..ea2c193 100644
--- a/app/assets/stylesheets/custom.scss
+++ b/app/assets/stylesheets/custom.scss
@@ -146,3 +146,53 @@ input {
border-bottom: 1px solid $gray-lighter;
}
}
+
+.microposts {
+ list-style: none;
+ padding: 0;
+ li {
+ padding: 10px 0;
+ border-top: 1px solid #e8e8e8;
+ }
+ .user {
+ margin-top: 5em;
+ padding-top: 0;
+ }
+ .content {
+ display: block;
+ margin-left: 60px;
+ img {
+ display: block;
+ padding: 5px 0;
+ }
+ }
+
+ .timestamp {
+ color: $gray-light;
+ display: block;
+ margin-left: 60px;
+ }
+.gravatar {
+ float: left;
+ margin-right: 10px;
+ width: 8%;
+ }
+}
+aside {
+ textarea {
+ height: 100px;
+ margin-bottom: 5px;
+ }
+}
+span.image {
+ margin-top: 10px;
+ width: 20px;
+ input {
+ border: 0;
+ }
+}
+
+input[type="file"] {
+ display: block;
+ position: absolute;
+}
diff --git a/app/controllers/account_activations_controller.rb b/app/controllers/account_activations_controller.rb
index 24150f3..eeb0d95 100644
--- a/app/controllers/account_activations_controller.rb
+++ b/app/controllers/account_activations_controller.rb
@@ -4,7 +4,7 @@ def edit
if user && !user.activated? && user.authenticated?(:activation, params[:id])
user.activate
log_in user
- flash[:success] = t("reset.account_activated")
+ flash[:success] = t("reset.messagerb")
redirect_to user
else
flash[:danger] = t("reset.invalid_activation")
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index c5f5898..edde33a 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -11,4 +11,12 @@ def set_locale
def default_url_options
{locale: I18n.locale}
end
+
+ def logged_in_user
+ unless logged_in?
+ store_location
+ flash[:danger] = t("login_in.messages")
+ redirect_to login_url
+ end
+ end
end
diff --git a/app/controllers/microposts_controller.rb b/app/controllers/microposts_controller.rb
new file mode 100644
index 0000000..4241503
--- /dev/null
+++ b/app/controllers/microposts_controller.rb
@@ -0,0 +1,39 @@
+class MicropostsController < ApplicationController
+ before_action :logged_in_user, only: [:create, :destroy]
+ before_action :correct_user, only: :destroy
+
+ def create
+ @micropost = current_user.microposts.build micropost_params
+ @micropost.image.attach(params[:micropost][:image])
+ if @micropost.save
+ flash[:success] = I18n.t("micropost.micropost_created")
+ redirect_to root_url
+ else
+ @feed_items = current_user.feed.paginate(page: params[:page])
+ render "static_pages/home"
+ end
+ end
+
+ def destroy
+ if @micropost.destroy
+ flash[:success] = t("micropost.micropost_deleted")
+ else
+ flash[:warning] = t("micropost.error")
+ end
+ redirect_to request.referer || root_url
+ end
+
+ private
+
+ def micropost_params
+ params.require(:micropost).permit :content, :picture
+ end
+
+ def correct_user
+ @micropost = current_user.microposts.find_by id: params[:id]
+ return if @micropost.present?
+
+ flash[:warning] = t("micropost.delete_error")
+ redirect_to root_url
+ end
+end
diff --git a/app/controllers/password_resets_controller.rb b/app/controllers/password_resets_controller.rb
index 0417aa5..6aed42c 100644
--- a/app/controllers/password_resets_controller.rb
+++ b/app/controllers/password_resets_controller.rb
@@ -4,7 +4,7 @@ class PasswordResetsController < ApplicationController
def new; end
def create
- if @user = User.find_by(email: params[:password_reset][:email].downcase)
+ if @user = User.find_by email: params[:password_reset][:email].downcase
@user.create_reset_digest
@user.send_password_reset_email
flash[:info] = t("reset.email_sent")
@@ -17,7 +17,7 @@ def create
def update
if params[:user][:password].empty?
- @user.errors.add(:password, t("reset.email_address"))
+ @user.errors.add(:password, t("reset.can_be")
render :edit
elsif @user.update(user_params)
log_in @user
@@ -41,7 +41,7 @@ def get_user
end
def valid_user
- return if activated? && @user.authenticated?(:reset, params[:id])
+ return if (@user.activated? && @user.authenticated?(:reset, params[:id]))
flash[:danger] = t("reset.home_has_been_reset")
redirect_to root_url
end
@@ -57,4 +57,5 @@ def check_expiration
redirect_to new_password_reset_url
end
end
+
end
diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb
index 5268023..b0e8676 100644
--- a/app/controllers/static_pages_controller.rb
+++ b/app/controllers/static_pages_controller.rb
@@ -1,5 +1,10 @@
class StaticPagesController < ApplicationController
- def home; end
+ def home
+ if logged_in?
+ @micropost = current_user.microposts.build
+ @feed_items = current_user.feed.paginate(page: params[:page])
+ end
+ end
def help; end
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 1b7af64..10759c7 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -4,7 +4,9 @@ class UsersController < ApplicationController
before_action :correct_user, only: %i(edit update)
before_action :admin_user, only: :destroy
- def show; end
+ def show
+ @microposts = @user.microposts.paginate(page: params[:page])
+ end
def edit; end
diff --git a/app/javascript/packs/application.js b/app/javascript/packs/application.js
index 78e7d9d..a34ee56 100644
--- a/app/javascript/packs/application.js
+++ b/app/javascript/packs/application.js
@@ -4,3 +4,4 @@ require("@rails/activestorage").start()
require("channels")
require("jquery")
require ("bootstrap")
+require("packs/micopost")
diff --git a/app/javascript/packs/micropost.js b/app/javascript/packs/micropost.js
new file mode 100644
index 0000000..fda83f6
--- /dev/null
+++ b/app/javascript/packs/micropost.js
@@ -0,0 +1,7 @@
+ $("#micropost_image").bind("change", function() {
+ var size_in_megabytes = this.files[0].size/1024/1024;
+ if (size_in_megabytes > 5) {
+ alert("Maximum file size is 5MB. Please choose a smaller file.");
+ }
+ });
+
diff --git a/app/models/micropost.rb b/app/models/micropost.rb
new file mode 100644
index 0000000..2bb821a
--- /dev/null
+++ b/app/models/micropost.rb
@@ -0,0 +1,13 @@
+class Micropost < ApplicationRecord
+ belongs_to :user
+ # default_scope -> { order(created_at: :desc) }
+ scope :by_created_at, ->{order(created_at: :desc)}
+ has_one_attached :image
+ validates :user_id, presence: true
+ validates :content, presence: true, length: {maximum: 140}
+ validates :image, content_type: {in: %w[image/jpeg image/gif image/png], message: "must be a valid image format"}, size: {less_than: 5.megabytes, message: "should be less than 5MB"}
+
+ def display_image
+ image.variant(resize_to_limit: [500, 500])
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 17fdf4f..088b15c 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -10,6 +10,7 @@ class User < ApplicationRecord
has_secure_password
validates :password, presence: true, length: {minimum: Settings.user.password}, allow_nil: true
+ has_many :microposts, dependent: :destroy
def self.digest string
cost = if ActiveModel::SecurePassword.min_cost
@@ -33,6 +34,7 @@ def authenticated? remember_token
return false if remember_digest.nil?
BCrypt::Password.new(remember_digest).is_password?(remember_token)
+
end
def authenticated? attribute, token
@@ -46,13 +48,10 @@ def activate
update_columns activated: true, activated_at: DateTime.now
end
- def send_activation_email
- UserMailer.account_activation(self).deliver_now
- end
-
def create_reset_digest
+
self.reset_token = User.new_token
- update_column reset_digest: User.digest(reset_token), reset_sent_at: DateTime.now
+ update_columns reset_digest: User.digest(reset_token), reset_sent_at: DateTime.now
end
def send_password_reset_email
@@ -67,6 +66,10 @@ def forget
update_attribute(:remember_digest, nil)
end
+ def feed
+ Micropost.by_created_at
+ end
+
private
def create_activation_digest
diff --git a/app/views/microposts/_micropost.html.erb b/app/views/microposts/_micropost.html.erb
new file mode 100644
index 0000000..cb6c91b
--- /dev/null
+++ b/app/views/microposts/_micropost.html.erb
@@ -0,0 +1,14 @@
+
+ <%= link_to gravatar_for(micropost.user, size: 50), micropost.user %>
+ <%= link_to micropost.user.name, micropost.user %>
+
+ <%= micropost.content %>
+ <%= image_tag micropost.display_image if micropost.image.attached? %>
+
+
+ Posted <%= time_ago_in_words(micropost.created_at) %> ago.
+ <% if current_user? micropost.user %>
+ <%= link_to t("micropost.button"), micropost, method: :delete, data: {confirm: t("micropost.confirm")} %>
+ <% end %>
+
+
diff --git a/app/views/shared/_error_messages.html.erb b/app/views/shared/_error_messages.html.erb
index 6ae0a98..10fa4c8 100644
--- a/app/views/shared/_error_messages.html.erb
+++ b/app/views/shared/_error_messages.html.erb
@@ -1,10 +1,10 @@
-<% if @user.errors.any? %>
+<% if object.errors.any? %>
- <%= t("users.messages") %> <%= pluralize(@user.errors.count, t("users.error")) %>.
+ <%= t("users.messages") %> <%= pluralize object.errors.count, t("users.error") %>.
- <% @user.errors.full_messages.each do |msg| %>
+ <% object.errors.full_messages.each do |msg| %>
- <%= msg %>
<% end %>
diff --git a/app/views/shared/_feed.html.erb b/app/views/shared/_feed.html.erb
new file mode 100644
index 0000000..f15a53c
--- /dev/null
+++ b/app/views/shared/_feed.html.erb
@@ -0,0 +1,6 @@
+<% if @feed_items.any? %>
+
+ <%= render @feed_items %>
+
+ <%= will_paginate @feed_items, params: { controller: :static_pages, action: :home } %>
+<% end %>
diff --git a/app/views/shared/_micropost_form.html.erb b/app/views/shared/_micropost_form.html.erb
new file mode 100644
index 0000000..ec35312
--- /dev/null
+++ b/app/views/shared/_micropost_form.html.erb
@@ -0,0 +1,12 @@
+<%= form_for(@micropost, html: { multipart: true }) do |f| %>
+ <%= render "shared/error_messages", object: f.object %>
+
+ <%= f.text_area :content, placeholder: "Compose new micropost..." %>
+
+ <%= f.submit "Post", class: "btn btn-primary" %>
+
+ <%= f.file_field :image %>
+
+<% end %>
+
+<%= javascript_pack_tag "micropost" %>
diff --git a/app/views/shared/_user_info.html.erb b/app/views/shared/_user_info.html.erb
new file mode 100644
index 0000000..b9c43ba
--- /dev/null
+++ b/app/views/shared/_user_info.html.erb
@@ -0,0 +1,4 @@
+<%= link_to gravatar_for(current_user, size: 50), current_user %>
+
<%= current_user.name %>
+
<%= link_to t("micropost.title"), current_user %>
+
<%= pluralize(current_user.microposts.count, "micropost") %>
diff --git a/app/views/static_pages/home.html.erb b/app/views/static_pages/home.html.erb
index 8431798..488cae7 100644
--- a/app/views/static_pages/home.html.erb
+++ b/app/views/static_pages/home.html.erb
@@ -1,4 +1,19 @@
-<% provide :title, t("home") %>
+<% if logged_in? %>
+
+
+
+
Micropost Feed
+ <%= render "shared/feed" %>
+
+
+<% else %>
<%= t("title") %>
@@ -8,3 +23,4 @@
<%= link_to t("static_pages.home.button"), '#', class: "btn btn-lg btn-primary" %>
+<% end %>
diff --git a/app/views/user_mailer/account_activation.html.erb b/app/views/user_mailer/account_activation.html.erb
index a8bbc92..d37174f 100644
--- a/app/views/user_mailer/account_activation.html.erb
+++ b/app/views/user_mailer/account_activation.html.erb
@@ -5,3 +5,4 @@
<%= t("email.account")%>
<%= link_to t("email.activate"), edit_account_activation_url(id: @user.activation_token, email: @user.email) %>
+
diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb
index 9021e90..6f19a6e 100644
--- a/app/views/users/_form.html.erb
+++ b/app/views/users/_form.html.erb
@@ -1,5 +1,5 @@
-<%= form_for(@user) do |f| %>
- <%= render "shared/error_messages", object: @user %>
+<%= form_for (@user) do |f| %>
+ <%= render 'shared/error_messages', object: f.object %>
<%= f.label :name, t("users.new.lable_name") %>
<%= f.text_field :name, class: "form-control" %>
<%= f.label :email %>
diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb
index 911cfc8..abdaf57 100644
--- a/app/views/users/index.html.erb
+++ b/app/views/users/index.html.erb
@@ -1,4 +1,4 @@
-<% provide(:title, 'All users') %>
+<% provide(:title, t("micropost.all_users")) %>
<%= t("session.title") %>
<%= will_paginate %>
diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb
index 1460e92..c018c54 100644
--- a/app/views/users/show.html.erb
+++ b/app/views/users/show.html.erb
@@ -1,11 +1,20 @@
-<% provide :title, t("users.user") %>
+<% provide(:title, @user.name) %>
+
+ <% if @user.microposts.any? %>
+
Microposts (<%= @user.microposts.count %>)
+
+ <%= render @microposts %>
+
+ <%= will_paginate @microposts %>
+ <% end %>
+
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 33b8b1d..53768e7 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -81,7 +81,7 @@ en:
reset:
email_sent: "Email sent with password reset instructions"
email_address: "Email address not found"
- messages3: "can't be empty"
+ can_be: "can't be empty"
password_has_been_reset: "Password has been reset."
account_not_activated: "Account not activated. Check your email for the activation link."
account_activated: "Account activated!"
@@ -96,3 +96,14 @@ en:
To_reset_your: "To reset your password click the link below:"
his_link_will: "This link will expire in two hours."
content: "If you did not request your password to be reset, please ignore this email and your password will stay as it is."
+
+ micropost:
+ micropost_created: "Micropost created!"
+ micropost_deleted: "Micropost deleted"
+ button: "delete"
+ confirm: "You sure?"
+ maximum_file : "Maximum file size is 5MB. Please choose a smaller file."
+ title: "view my profile"
+ delete_error: "post not found"
+ all_users: "All users"
+ error: "delete don't success"
diff --git a/config/locales/vi.yml b/config/locales/vi.yml
index 6bcd3ce..8c6c222 100644
--- a/config/locales/vi.yml
+++ b/config/locales/vi.yml
@@ -72,6 +72,7 @@ vi:
email:
Please_check_your_email: "Hãy kiểm tra email của bạn để kích hoạt tài khoản của bạn."
+ messages: "Hãy kiểm tra email của bạn để kích hoạt tài khoản của bạn."
messagesrb: "Kích hoạt tài khoản"
sendemail: " Chào mừng bạn đến với Ứng dụng mẫu! Nhấp vào liên kết bên dưới để kích hoạt"
account: "tài khoản:"
@@ -80,7 +81,7 @@ vi:
reset:
email_sent: "Email được gửi kèm theo hướng dẫn đặt lại mật khẩu"
email_address: "Không tìm thấy địa chỉ email"
- messages3: "không thể để trống"
+ can_be: "không thể để trống"
password_has_been_reset: "Mật khẩu đã được đặt lại."
account_not_activated: "Tài khoản chưa được kích hoạt. Kiểm tra email của bạn để biết liên kết kích hoạt."
account_activated: "Tài khoản đã được kích hoạt!"
@@ -95,3 +96,14 @@ vi:
To_reset_your: "Để đặt lại mật khẩu của bạn, hãy nhấp vào liên kết bên dưới:"
his_link_will: "Liên kết này sẽ hết hạn sau hai giờ."
content: "Nếu bạn không yêu cầu đặt lại mật khẩu của mình, vui lòng bỏ qua email này và mật khẩu của bạn sẽ được giữ nguyên."
+
+ micropost:
+ micropost_created: "Đã tạo micropost!"
+ micropost_deleted: "Micropost đã bị xóa"
+ button: "Xóa"
+ confirm: "Bạn chắc chắn?"
+ maximum_file: "Kích thước tệp tối đa là 5MB. Vui lòng chọn một tệp nhỏ hơn."
+ title: "xem hồ sơ của tôi"
+ delete_error: "bai viet cua ban khong ton tai"
+ all_users: "tat ca nguoi dung"
+ error: "Xoa khong thanh cong"
diff --git a/config/routes.rb b/config/routes.rb
index ef27204..faabd7c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -12,4 +12,5 @@
resources :users
resources :account_activations, only: :edit
resources :password_resets, only: [:new, :create, :edit, :update]
+ resources :microposts, only: [:create, :destroy]
end
diff --git a/db/migrate/20200930160008_create_microposts.rb b/db/migrate/20200930160008_create_microposts.rb
new file mode 100644
index 0000000..2a370a2
--- /dev/null
+++ b/db/migrate/20200930160008_create_microposts.rb
@@ -0,0 +1,11 @@
+class CreateMicroposts < ActiveRecord::Migration[6.0]
+ def change
+ create_table :microposts do |t|
+ t.text :content
+ t.references :user, null: false, foreign_key: true
+
+ t.timestamps
+ end
+ add_index :microposts, [:user_id, :created_at]
+ end
+end
diff --git a/db/migrate/20201001033623_create_active_storage_tables.active_storage.rb b/db/migrate/20201001033623_create_active_storage_tables.active_storage.rb
new file mode 100644
index 0000000..0b2ce25
--- /dev/null
+++ b/db/migrate/20201001033623_create_active_storage_tables.active_storage.rb
@@ -0,0 +1,27 @@
+# This migration comes from active_storage (originally 20170806125915)
+class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
+ def change
+ create_table :active_storage_blobs do |t|
+ t.string :key, null: false
+ t.string :filename, null: false
+ t.string :content_type
+ t.text :metadata
+ t.bigint :byte_size, null: false
+ t.string :checksum, null: false
+ t.datetime :created_at, null: false
+
+ t.index [ :key ], unique: true
+ end
+
+ create_table :active_storage_attachments do |t|
+ t.string :name, null: false
+ t.references :record, null: false, polymorphic: true, index: false
+ t.references :blob, null: false
+
+ t.datetime :created_at, null: false
+
+ t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
+ t.foreign_key :active_storage_blobs, column: :blob_id
+ end
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 6cc7816..b902488 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,6 @@
#
# It's strongly recommended that you check this file into your version control system.
-
ActiveRecord::Schema.define(version: 2020_09_30_152853) do
create_table "users", force: :cascade do |t|
t.string "name"
diff --git a/db/seeds.rb b/db/seeds.rb
index 86044cf..ebcdb27 100644
--- a/db/seeds.rb
+++ b/db/seeds.rb
@@ -26,3 +26,9 @@
activated: true,
activated_at: DateTime.now)
end
+
+users = User.order(:created_at).take(6)
+50.times do
+content = Faker::Lorem.sentence(word_count: 5)
+users.each { |user| user.microposts.create!(content: content) }
+end
diff --git a/test/fixtures/microposts.yml b/test/fixtures/microposts.yml
new file mode 100644
index 0000000..33f7a30
--- /dev/null
+++ b/test/fixtures/microposts.yml
@@ -0,0 +1,9 @@
+# Read about fixtures at https://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ content: MyText
+ user: one
+
+two:
+ content: MyText
+ user: two
diff --git a/test/models/micropost_test.rb b/test/models/micropost_test.rb
new file mode 100644
index 0000000..def8e93
--- /dev/null
+++ b/test/models/micropost_test.rb
@@ -0,0 +1,7 @@
+require 'test_helper'
+
+class MicropostTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end