diff --git a/app/assets/images/icons/heart.svg b/app/assets/images/icons/heart.svg new file mode 100644 index 0000000..09f9e8b --- /dev/null +++ b/app/assets/images/icons/heart.svg @@ -0,0 +1,3 @@ + diff --git a/app/assets/images/icons/heart_full.svg b/app/assets/images/icons/heart_full.svg new file mode 100644 index 0000000..d956025 --- /dev/null +++ b/app/assets/images/icons/heart_full.svg @@ -0,0 +1,3 @@ + diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 4c59b7b..51c351a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -5,10 +5,7 @@ class ApplicationController < ActionController::Base helper_method :signed_in?, :current_user def sign_in!(user) - user.transaction do - user.last_login_at = Time.now - user.save! - end + UserLogin.create(user: user, login_at: Time.now) session[:user_id] = user.id end @@ -26,4 +23,8 @@ def authenticate_user! return redirect_to homepage_path(redirect_to: request.original_url) end end + + def render_not_found + render file: Rails.public_path.join('404.html'), status: :not_found, layout: false + end end diff --git a/app/controllers/favorite_products_controller.rb b/app/controllers/favorite_products_controller.rb new file mode 100644 index 0000000..331b769 --- /dev/null +++ b/app/controllers/favorite_products_controller.rb @@ -0,0 +1,25 @@ +class FavoriteProductsController < ApplicationController + before_action :set_product + + def create + if FavoriteProduct.create(product: @product, user: current_user) + flash[:success] = 'Product has been favorited' + else + flash[:error] = 'Something went wrong' + end + + redirect_back(fallback_location: homepage_path) + end + + def destroy + FavoriteProduct.where(product_id: @product.id, user_id: current_user.id).first.destroy + flash[:success] = 'Product has been deleted from favorites' + redirect_back(fallback_location: homepage_path) + end + + private + + def set_product + @product = Product.active.find(params[:product_id] || params[:id]) + end +end diff --git a/app/controllers/shop_controller.rb b/app/controllers/shop_controller.rb index c2a69b5..489f459 100644 --- a/app/controllers/shop_controller.rb +++ b/app/controllers/shop_controller.rb @@ -1,6 +1,4 @@ - class ShopController < ApplicationController - def category category_slug = params.require([:category_slug]) @category = Category.find_by!(slug: category_slug) @@ -9,7 +7,8 @@ def category def product category_slug, product_slug = params.require([:category_slug, :product_slug]) @category = Category.find_by!(slug: category_slug) - @product = @category.products.find_by!(slug: product_slug) - end + @product = @category.products.find_by(slug: product_slug) + render_not_found unless @product&.is_active? + end end diff --git a/app/models/favorite_product.rb b/app/models/favorite_product.rb new file mode 100644 index 0000000..bad8a5f --- /dev/null +++ b/app/models/favorite_product.rb @@ -0,0 +1,7 @@ +class FavoriteProduct < ApplicationRecord + validates_presence_of :product_id, :user_id + validates_uniqueness_of :product_id, scope: :user_id + + belongs_to :user + belongs_to :product +end diff --git a/app/models/product.rb b/app/models/product.rb index eb37ec8..3b57d09 100644 --- a/app/models/product.rb +++ b/app/models/product.rb @@ -2,7 +2,7 @@ class Product < ApplicationRecord validates_presence_of :name, :slug, :price_usd, :image_url validates_uniqueness_of :slug - + has_many :product_categories has_many :categories, through: :product_categories diff --git a/app/models/user.rb b/app/models/user.rb index 8108fe7..e5edb1d 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -9,6 +9,10 @@ class User < ApplicationRecord validates :password, presence: true has_secure_password + has_many :user_logins + has_many :favorite_products + has_many :favorites, through: :favorite_products, source: :product + def password @password ||= Password.new(password_digest) end @@ -17,5 +21,4 @@ def password=(new_password) @password = Password.create(new_password) self.password_digest = @password end - end diff --git a/app/models/user_login.rb b/app/models/user_login.rb new file mode 100644 index 0000000..55edf18 --- /dev/null +++ b/app/models/user_login.rb @@ -0,0 +1,7 @@ +class UserLogin < ApplicationRecord + belongs_to :user + + validates_presence_of :user_id, :login_at + + scope :ordered, -> { order(login_at: :desc) } +end diff --git a/app/views/members/dashboard.html.erb b/app/views/members/dashboard.html.erb index a3e2e49..a9ac0c5 100644 --- a/app/views/members/dashboard.html.erb +++ b/app/views/members/dashboard.html.erb @@ -1,4 +1,3 @@ -
<%= product.description %>
diff --git a/app/views/shop/product.html.erb b/app/views/shop/product.html.erb index ce21a98..5e8a49b 100644 --- a/app/views/shop/product.html.erb +++ b/app/views/shop/product.html.erb @@ -9,16 +9,27 @@<%= number_to_currency(@product.price_usd) %>
diff --git a/config/routes.rb b/config/routes.rb index baeb23b..99b6ed5 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -3,7 +3,7 @@ # Defines the root path route ("/") # root "articles#index" - root "public#homepage", as: :homepage + root 'public#homepage', as: :homepage scope '', format: false do get '/logout', to: 'signin#logout', as: :logout @@ -24,6 +24,9 @@ scope 'members' do get '', to: 'members#dashboard', as: :members_dashboard end - end + resources :favorite_products, only: %i[create destroy] + + match '*unmatched', to: 'application#not_found_method', via: :all + end end diff --git a/db/migrate/20230817061119_add_user_logins.rb b/db/migrate/20230817061119_add_user_logins.rb new file mode 100644 index 0000000..b429310 --- /dev/null +++ b/db/migrate/20230817061119_add_user_logins.rb @@ -0,0 +1,31 @@ +class AddUserLogins < ActiveRecord::Migration[7.0] + def up + create_table :user_logins do |t| + t.references :user + t.datetime :login_at, null: false + end + + execute <<-SQL + INSERT INTO user_logins (user_id, login_at) + SELECT id, last_login_at FROM users; + SQL + + remove_column :users, :last_login_at, :datetime + end + + def down + add_column :users, :last_login_at, :datetime + + execute <<-SQL + UPDATE users + SET last_login_at = ( + SELECT login_at FROM user_logins + WHERE user_logins.user_id = users.id + ORDER BY login_at DESC + LIMIT 1 + ); + SQL + + drop_table :user_logins + end +end diff --git a/db/migrate/20230818074506_add_favorite_products.rb b/db/migrate/20230818074506_add_favorite_products.rb new file mode 100644 index 0000000..6a46425 --- /dev/null +++ b/db/migrate/20230818074506_add_favorite_products.rb @@ -0,0 +1,10 @@ +class AddFavoriteProducts < ActiveRecord::Migration[7.0] + def change + create_table :favorite_products do |t| + t.references :product, index: true + t.references :user, index: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 2ac62e9..018f5bc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,13 +10,22 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.0].define(version: 2023_08_14_011401) do +ActiveRecord::Schema[7.0].define(version: 2023_08_18_074506) do create_table "categories", force: :cascade do |t| t.string "name", null: false t.string "slug", null: false t.index ["slug"], name: "index_categories_on_slug" end + create_table "favorite_products", force: :cascade do |t| + t.integer "product_id" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["product_id"], name: "index_favorite_products_on_product_id" + t.index ["user_id"], name: "index_favorite_products_on_user_id" + end + create_table "product_categories", force: :cascade do |t| t.integer "product_id" t.integer "category_id" @@ -38,12 +47,17 @@ t.index ["slug"], name: "index_products_on_slug" end + create_table "user_logins", force: :cascade do |t| + t.integer "user_id" + t.datetime "login_at", null: false + t.index ["user_id"], name: "index_user_logins_on_user_id" + end + create_table "users", force: :cascade do |t| t.string "email", null: false t.string "password_digest", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false - t.datetime "last_login_at" end add_foreign_key "product_categories", "categories", on_delete: :cascade