diff --git a/Gemfile b/Gemfile index b460be7..94ca855 100644 --- a/Gemfile +++ b/Gemfile @@ -34,7 +34,7 @@ gem "jbuilder" # gem "kredis" # Use Active Model has_secure_password [https://guides.rubyonrails.org/active_model_basics.html#securepassword] -# gem "bcrypt", "~> 3.1.7" +gem "bcrypt", "~> 3.1.7" # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] @@ -42,8 +42,13 @@ gem "tzinfo-data", platforms: %i[ mingw mswin x64_mingw jruby ] # Reduces boot times through caching; required in config/boot.rb gem "bootsnap", require: false +gem 'pry-rails' +gem 'cancancan' +gem 'bootstrap', '~> 5.1.3' +gem 'jquery-rails' + # Use Sass to process CSS -# gem "sassc-rails" +gem "sassc-rails" # Use Active Storage variants [https://guides.rubyonrails.org/active_storage_overview.html#transforming-images] # gem "image_processing", "~> 1.2" @@ -51,6 +56,7 @@ gem "bootsnap", require: false group :development, :test do # See https://guides.rubyonrails.org/debugging_rails_applications.html#debugging-with-the-debug-gem gem "debug", platforms: %i[ mri mingw x64_mingw ] + gem 'faker' end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index b6c2b90..a0e6807 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -66,10 +66,19 @@ GEM i18n (>= 1.6, < 2) minitest (>= 5.1) tzinfo (~> 2.0) + autoprefixer-rails (10.4.2.0) + execjs (~> 2) + bcrypt (3.1.17) bindex (0.8.1) bootsnap (1.11.1) msgpack (~> 1.2) + bootstrap (5.1.3) + autoprefixer-rails (>= 9.1.0) + popper_js (>= 2.9.3, < 3) + sassc-rails (>= 2.0.0) builder (3.2.4) + cancancan (3.3.0) + coderay (1.1.3) concurrent-ruby (1.1.10) crass (1.0.6) debug (1.4.0) @@ -77,6 +86,10 @@ GEM reline (>= 0.2.7) digest (3.1.0) erubi (1.10.0) + execjs (2.8.1) + faker (2.20.0) + i18n (>= 1.8.11, < 2) + ffi (1.15.5) globalid (1.0.0) activesupport (>= 5.0) i18n (1.10.0) @@ -91,6 +104,10 @@ GEM jbuilder (2.11.5) actionview (>= 5.0.0) activesupport (>= 5.0.0) + jquery-rails (4.4.0) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) loofah (2.15.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) @@ -120,6 +137,12 @@ GEM nokogiri (1.13.3-x86_64-linux) racc (~> 1.4) pg (1.3.4) + popper_js (2.9.3) + pry (0.14.1) + coderay (~> 1.1) + method_source (~> 1.0) + pry-rails (0.3.9) + pry (>= 0.10.4) puma (5.6.2) nio4r (~> 2.0) racc (1.6.0) @@ -155,6 +178,14 @@ GEM rake (13.0.6) reline (0.3.1) io-console (~> 0.5) + sassc (2.4.0) + ffi (~> 1.9) + sassc-rails (2.1.2) + railties (>= 4.0.0) + sassc (>= 2.0) + sprockets (> 3.0) + sprockets-rails + tilt sprockets (4.0.3) concurrent-ruby (~> 1.0) rack (> 1, < 3) @@ -166,6 +197,7 @@ GEM railties (>= 6.0.0) strscan (3.0.1) thor (1.2.1) + tilt (2.0.10) timeout (0.2.0) turbo-rails (1.0.1) actionpack (>= 6.0.0) @@ -186,13 +218,20 @@ PLATFORMS x86_64-linux DEPENDENCIES + bcrypt (~> 3.1.7) bootsnap + bootstrap (~> 5.1.3) + cancancan debug + faker importmap-rails jbuilder + jquery-rails pg (~> 1.1) + pry-rails puma (~> 5.0) rails (~> 7.0.2, >= 7.0.2.3) + sassc-rails sprockets-rails stimulus-rails turbo-rails diff --git a/README.md b/README.md deleted file mode 100644 index 7db80e4..0000000 --- a/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# README - -This README would normally document whatever steps are necessary to get the -application up and running. - -Things you may want to cover: - -* Ruby version - -* System dependencies - -* Configuration - -* Database creation - -* Database initialization - -* How to run the test suite - -* Services (job queues, cache servers, search engines, etc.) - -* Deployment instructions - -* ... diff --git a/app/assets/images/idea.png b/app/assets/images/idea.png new file mode 100644 index 0000000..1a1147a Binary files /dev/null and b/app/assets/images/idea.png differ diff --git a/app/assets/images/.keep b/app/assets/stylesheets/ideas.scss similarity index 100% rename from app/assets/images/.keep rename to app/assets/stylesheets/ideas.scss diff --git a/app/assets/stylesheets/likes.scss b/app/assets/stylesheets/likes.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/stylesheets/reviews.scss b/app/assets/stylesheets/reviews.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/stylesheets/sessions.scss b/app/assets/stylesheets/sessions.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss new file mode 100644 index 0000000..e69de29 diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 09705d1..1450813 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,2 +1,20 @@ class ApplicationController < ActionController::Base -end + + private + + def current_user + current_user ||= User.find_by_id session[:user_id] # ||= will only assign @current_user if it is nil. Otherwise continue using it's + end + helper_method :current_user + + def user_signed_in? + current_user.present? + end + helper_method :user_signed_in? + + def authenticate_user! + redirect_to new_session_path, {alert: "You need to be signed in first!", status: 303} unless user_signed_in? + end + helper_method :authenticate_user! + +end \ No newline at end of file diff --git a/app/controllers/ideas_controller.rb b/app/controllers/ideas_controller.rb new file mode 100644 index 0000000..5970f76 --- /dev/null +++ b/app/controllers/ideas_controller.rb @@ -0,0 +1,61 @@ +class IdeasController < ApplicationController + before_action :authenticate_user!, except: [:index, :show] + before_action :get_idea!, only: [:show, :edit, :update, :destroy] + before_action :authorize!, only: [:edit, :update, :destroy] + + def new + @idea = Idea.new + end + + def create + @idea = Idea.new idea_params + @idea.user = current_user + if @idea.save + redirect_to ideas_path(@idea), { status: 303, notice: 'Idea created' } + else + flash.alert = @idea.errors.full_messages.join(', ') + render :new, status: 303 + end + end + + def index + @ideas = Idea.all.order('updated_at DESC') + end + + def show + @review = Review.new + @reviews = @idea.reviews.order(created_at: :desc) + @like = @idea.likes.find_by(user: current_user) + end + + def edit + + end + + def update + id = params[:id] + @idea = Idea.find(id) + if @idea.update(params.require(:idea).permit(:title, :description)) + redirect_to idea_path(@idea), status: 303 + else + render :edit, status: 303 + end + end + + def destroy + id = params[:id] + @idea = Idea.find(id) + @idea.destroy + redirect_to ideas_path, status: 303 + end + + private + + def get_idea! + @idea = Idea.find(params[:id]) + end + + def authorize! + redirect_to root_path, status: 303, alert: 'Not Authorized' unless can?(:crud, @idea) + end + end \ No newline at end of file diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb new file mode 100644 index 0000000..a942ee7 --- /dev/null +++ b/app/controllers/likes_controller.rb @@ -0,0 +1,29 @@ +class LikesController < ApplicationController + before_action :authenticate_user!, only: [:create, :destroy] + + def create + idea = Idea.find params[:idea_id] + like = Like.new( user: current_user, idea: idea ) + + if can?(:like, idea) + if like.save + redirect_to idea_path(idea), { notice: "Idea Liked", status: 303 } + else + redirect_to root_path, { alert: like.errors.full_messages.join(", "), status: 303 } + end + else + redirect_to root_path, { alert: "You can't like your own idea....", status: 303 } + end + end + + def destroy + like = Like.find params[:id] + + if can?(:destroy, like) + like.destroy + redirect_to root_path, { notice: "Idea unliked", status: 303 } + else + redirect_to root_path, {alert: "You can't unlike because not authorized!", status: 303 } + end + end +end \ No newline at end of file diff --git a/app/controllers/reviews_controller.rb b/app/controllers/reviews_controller.rb new file mode 100644 index 0000000..ec28f72 --- /dev/null +++ b/app/controllers/reviews_controller.rb @@ -0,0 +1,31 @@ +class ReviewsController < ApplicationController + + def create + @idea = Idea.find(params[:idea_id]) + @review = Review.new review_params + @review.idea = @idea + @review.user = current_user + if @review.save + redirect_to idea_path(@idea) + else + @reviews = @idea.reviews.order(created_at: :desc) + render 'ideas/show', status: 303 + end + end + + def destroy + @review = Review.find params[:id] + if can?(:crud, @review) + @review.destroy + redirect_to idea_path(@review.idea), status: 303 + else + head :unauthorized + end + end + + private + + def review_params + params.require(:review).permit(:body) + end +end \ No newline at end of file diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb new file mode 100644 index 0000000..afe2549 --- /dev/null +++ b/app/controllers/sessions_controller.rb @@ -0,0 +1,22 @@ +class SessionsController < ApplicationController + def new + end + + def create + user = User.find_by_email params[:email] + + if user&.authenticate params[:password] + session[:user_id] = user.id + flash[:success] = "User Logged In" + redirect_to ideas_path, status: 303 + else + flash[:warning] = "Couldn't Log In, Please Try Again" + render :new, status: 303 + end + end + + def destroy + session[:user_id] = nil + redirect_to ideas_path, status: 303 + end +end \ No newline at end of file diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 0000000..73f4347 --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,16 @@ +class UsersController < ApplicationController + def new + @user = User.new + end + + def create + @user = User.new params.require(:user).permit(:first_name, :last_name, :email, :password, :password_confirmation) + if @user.save + flash.delete(:warning) + redirect_to ideas_path, status: 303 + else + flash[:warning] = "Unable to Create User" + render :new, status: 303 + end + end +end \ No newline at end of file diff --git a/app/helpers/ideas_helper.rb b/app/helpers/ideas_helper.rb new file mode 100644 index 0000000..0a60b4e --- /dev/null +++ b/app/helpers/ideas_helper.rb @@ -0,0 +1,2 @@ +module IdeasHelper +end diff --git a/app/helpers/likes_helper.rb b/app/helpers/likes_helper.rb new file mode 100644 index 0000000..a78a759 --- /dev/null +++ b/app/helpers/likes_helper.rb @@ -0,0 +1,2 @@ +module LikesHelper +end diff --git a/app/helpers/reviews_helper.rb b/app/helpers/reviews_helper.rb new file mode 100644 index 0000000..682b7b1 --- /dev/null +++ b/app/helpers/reviews_helper.rb @@ -0,0 +1,2 @@ +module ReviewsHelper +end diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb new file mode 100644 index 0000000..309f8b2 --- /dev/null +++ b/app/helpers/sessions_helper.rb @@ -0,0 +1,2 @@ +module SessionsHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 0000000..2310a24 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,2 @@ +module UsersHelper +end diff --git a/app/models/ability.rb b/app/models/ability.rb new file mode 100644 index 0000000..3174ebc --- /dev/null +++ b/app/models/ability.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +class Ability + include CanCan::Ability + + def initialize(user) + + user ||= User.new + + alias_action :create, :read, :update, :destroy, to: :crud + + can :crud, Idea do |idea| + idea.user == user + end + + can :crud, Review do |review| + review.user == user + end + + can :like, Idea do |idea| + user.present? && idea.user != user + end + + can :destroy, Like do |like| + like.user == user + end + end +end \ No newline at end of file diff --git a/app/models/idea.rb b/app/models/idea.rb new file mode 100644 index 0000000..24d8349 --- /dev/null +++ b/app/models/idea.rb @@ -0,0 +1,12 @@ +class Idea < ApplicationRecord + has_many :reviews, dependent: :destroy + + has_many :likes, dependent: :destroy + has_many :likers, through: :likes, source: :user + belongs_to :user + + validates :title, presence: true + validates :description, presence: true + + +end \ No newline at end of file diff --git a/app/models/like.rb b/app/models/like.rb new file mode 100644 index 0000000..3c411aa --- /dev/null +++ b/app/models/like.rb @@ -0,0 +1,4 @@ +class Like < ApplicationRecord + belongs_to :user + belongs_to :idea +end \ No newline at end of file diff --git a/app/models/review.rb b/app/models/review.rb new file mode 100644 index 0000000..271ab68 --- /dev/null +++ b/app/models/review.rb @@ -0,0 +1,7 @@ +class Review < ApplicationRecord + belongs_to :idea + belongs_to :user + + validates :body, presence: true + validates :rating, {numericality: { greater_than_or_equal_to: 1, less_than_or_equal_to: 5 }} +end \ No newline at end of file diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 0000000..373763b --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,10 @@ +class User < ApplicationRecord + has_secure_password + has_many :ideas + has_many :reviews + + has_many :likes, dependent: :destroy + has_many :liked_ideas, through: :likes, source: :idea + + validates :email, presence: true, uniqueness: true, format: /\A([\w+\-].?)+@[a-z\d\-]+(\.[a-z]+)*\.[a-z]+\z/i +end \ No newline at end of file diff --git a/app/views/ideas/_form.html.erb b/app/views/ideas/_form.html.erb new file mode 100644 index 0000000..a747978 --- /dev/null +++ b/app/views/ideas/_form.html.erb @@ -0,0 +1,20 @@ +<% if @idea.errors.any? %> +
<%= idea.description %>
+<%= @idea.description %>
+ +<% if can?(:crud, @idea)%> +<%= link_to "Edit", edit_idea_path(@idea), class:"btn btn-warning " %> +<%= link_to "Delete", idea_path(@idea), class:"btn btn-danger ", method: :delete, data: {confirm: "Are you sure?"} %> +<% end %> +
+ <%= review.body %>
+ <% if can?(:crud, review)%>
+
+ <%= button_to(
+ "Delete",
+ idea_review_path(@idea, review),
+ method: :delete,
+ data: {
+ confirm: "Are you sure?"
+ },
+ class: "btn btn-danger"
+ ) %>
+
+ |
+ <%end%>
+
+ Reviewed <%= time_ago_in_words(review.created_at) %> ago
+
+ |
+
+ user: <%= review.user&.full_name %>
+
+