Added authentication; cleaned up some UI

This commit is contained in:
Dan Elbert 2016-01-19 16:50:39 -06:00
parent b3b1f1745c
commit 170098046e
25 changed files with 316 additions and 65 deletions

1
.ruby-version Normal file
View File

@ -0,0 +1 @@
2.3.0

View File

@ -21,7 +21,7 @@ gem 'cocoon', '~> 1.2.6'
gem 'unitwise', '~> 2.0.0' gem 'unitwise', '~> 2.0.0'
# Use ActiveModel has_secure_password # Use ActiveModel has_secure_password
# gem 'bcrypt', '~> 3.1.7' gem 'bcrypt', '~> 3.1.7'
# Use Unicorn as the app server # Use Unicorn as the app server
# gem 'unicorn' # gem 'unicorn'

View File

@ -37,9 +37,10 @@ GEM
thread_safe (~> 0.3, >= 0.3.4) thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1) tzinfo (~> 1.1)
arel (6.0.3) arel (6.0.3)
autoprefixer-rails (6.2.3) autoprefixer-rails (6.3.1)
execjs execjs
json json
bcrypt (3.1.10)
binding_of_caller (0.7.2) binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1) debug_inspector (>= 0.0.1)
blankslate (3.1.3) blankslate (3.1.3)
@ -73,7 +74,7 @@ GEM
jbuilder (2.4.0) jbuilder (2.4.0)
activesupport (>= 3.0.0, < 5.1) activesupport (>= 3.0.0, < 5.1)
multi_json (~> 1.2) multi_json (~> 1.2)
jquery-rails (4.0.5) jquery-rails (4.1.0)
rails-dom-testing (~> 1.0) rails-dom-testing (~> 1.0)
railties (>= 4.2.0) railties (>= 4.2.0)
thor (>= 0.14, < 2.0) thor (>= 0.14, < 2.0)
@ -122,7 +123,7 @@ GEM
activesupport (= 4.2.5) activesupport (= 4.2.5)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rake (10.4.2) rake (10.5.0)
ref (2.0.0) ref (2.0.0)
rspec-core (3.4.1) rspec-core (3.4.1)
rspec-support (~> 3.4.0) rspec-support (~> 3.4.0)
@ -185,6 +186,7 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
bcrypt (~> 3.1.7)
bootstrap-sass (~> 3.3.6) bootstrap-sass (~> 3.3.6)
byebug byebug
cocoon (~> 1.2.6) cocoon (~> 1.2.6)
@ -202,3 +204,6 @@ DEPENDENCIES
uglifier (>= 1.3.0) uglifier (>= 1.3.0)
unitwise (~> 2.0.0) unitwise (~> 2.0.0)
web-console (~> 2.0) web-console (~> 2.0)
BUNDLED WITH
1.11.2

View File

@ -2,4 +2,31 @@ class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception. # Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead. # For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception protect_from_forgery with: :exception
def ensure_valid_user
if current_user.nil?
flash[:warning] = "You must login"
redirect_to login_path
end
end
def ensure_admin_user
unless current_user && current_user.is_admin?
flash[:warning] = "You must login as an admin"
redirect_to login_path
end
end
def current_user
@current_user ||= User.find(session[:user_id]) if session[:user_id]
end
helper_method :current_user
def set_current_user(user)
if user
session[:user_id] = user.id
else
session[:user_id] = nil
end
end
end end

View File

@ -1,6 +1,9 @@
class IngredientsController < ApplicationController class IngredientsController < ApplicationController
before_action :set_ingredient, only: [:edit, :update, :destroy] before_action :set_ingredient, only: [:edit, :update, :destroy]
before_filter :ensure_valid_user, only: [:new, :edit, :create, :update, :destroy]
# GET /ingredients # GET /ingredients
# GET /ingredients.json # GET /ingredients.json
def index def index

View File

@ -1,8 +1,10 @@
class RecipesController < ApplicationController class RecipesController < ApplicationController
before_action :set_recipe, only: [:show, :edit, :update, :destroy, :scale] before_action :set_recipe, only: [:show, :edit, :update, :destroy, :scale]
before_filter :ensure_valid_user, only: [:new, :edit, :create, :update, :destroy]
# GET /recipes # GET /recipes
# GET /recipes.json
def index def index
@recipes = Recipe.active @recipes = Recipe.active
end end
@ -29,48 +31,34 @@ class RecipesController < ApplicationController
end end
# POST /recipes # POST /recipes
# POST /recipes.json
def create def create
@recipe = Recipe.new(recipe_params) @recipe = Recipe.new(recipe_params)
@recipe.user = current_user
respond_to do |format|
if @recipe.save if @recipe.save
format.html { redirect_to @recipe, notice: 'Recipe was successfully created.' } redirect_to @recipe, notice: 'Recipe was successfully created.'
format.json { render :show, status: :created, location: @recipe }
else else
format.html { render :new } render :new
format.json { render json: @recipe.errors, status: :unprocessable_entity }
end
end end
end end
# PATCH/PUT /recipes/1 # PATCH/PUT /recipes/1
# PATCH/PUT /recipes/1.json
def update def update
respond_to do |format|
if @recipe.update(recipe_params) if @recipe.update(recipe_params)
format.html { redirect_to @recipe, notice: 'Recipe was successfully updated.' } redirect_to @recipe, notice: 'Recipe was successfully updated.'
format.json { render :show, status: :ok, location: @recipe }
else else
format.html { render :edit } render :edit
format.json { render json: @recipe.errors, status: :unprocessable_entity }
end
end end
end end
# DELETE /recipes/1 # DELETE /recipes/1
# DELETE /recipes/1.json
def destroy def destroy
@recipe.deleted = true @recipe.deleted = true
respond_to do |format|
if @recipe.save if @recipe.save
format.html { redirect_to recipes_url, notice: 'Recipe was successfully destroyed.' } redirect_to recipes_url, notice: 'Recipe was successfully destroyed.'
format.json { head :no_content }
else else
format.html { redirect_to recipes_url, error: 'Recipe could not be destroyed.' } redirect_to recipes_url, error: 'Recipe could not be destroyed.'
format.json { head :no_content }
end
end end
end end

View File

@ -0,0 +1,60 @@
class UsersController < ApplicationController
before_filter :ensure_valid_user, except: [:login, :verify_login, :new, :create]
def login
end
def logout
set_current_user(nil)
session.destroy
flash[:notice] = "Logged out"
redirect_to root_path
end
def verify_login
if user = User.authenticate(params[:email], params[:password])
set_current_user(user)
flash[:notice] = "Welcome, #{user.full_name}"
redirect_to root_path
else
flash[:error] = "Invalid credentials"
render :login
end
end
def new
@user = User.new
end
def create
@user = User.new(user_params)
if @user.save
set_current_user(@user)
redirect_to root_path, notice: 'User was successfully created.'
else
render action: :new
end
end
def edit
@user = current_user
end
def update
@user = current_user
if @user.update(user_params)
redirect_to root_path, notice: 'User account updated'
else
render action: 'edit'
end
end
private
def user_params
params.require(:user).permit(:username, :email, :full_name, :password, :password_confirmation)
end
end

View File

@ -11,7 +11,27 @@ module ApplicationHelper
] ]
end end
def nav_item(name, url, controller) def profile_nav_items
if current_user
[content_tag('li', class: 'dropdown') do
li_cnt = ''.html_safe
li_cnt << link_to('#', class: 'dropdown-toggle', data: {toggle: 'dropdown'}, role: 'button') do
''.html_safe << "#{current_user.display_name}" << content_tag('span', '', class: 'caret')
end
li_cnt << content_tag('ul', class: 'dropdown-menu') do
''.html_safe << nav_item('Profile', edit_user_path) << nav_item('Logout', logout_path)
end
li_cnt
end]
else
[nav_item('Login', login_path)]
end
end
def nav_item(name, url, controller = nil)
content_tag('li', link_to(name, url), class: active_for_controller(controller)) content_tag('li', link_to(name, url), class: active_for_controller(controller))
end end

View File

@ -2,6 +2,7 @@ class Recipe < ActiveRecord::Base
has_many :recipe_ingredients, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy has_many :recipe_ingredients, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy
has_many :recipe_steps, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy has_many :recipe_steps, -> { order :sort_order }, inverse_of: :recipe, dependent: :destroy
belongs_to :user
scope :active, -> { where('deleted <> ? OR deleted IS NULL', true) } scope :active, -> { where('deleted <> ? OR deleted IS NULL', true) }

15
app/models/user.rb Normal file
View File

@ -0,0 +1,15 @@
class User < ActiveRecord::Base
has_secure_password
validates :username, presence: true, uniqueness: { case_sensitive: false }
def self.authenticate(email, password)
find_by_email(email).try(:authenticate, password)
end
def display_name
self.full_name.present? ? self.full_name : self.username
end
end

View File

@ -5,17 +5,17 @@
<%= render partial: 'shared/error_list', locals: {model: @ingredient} %> <%= render partial: 'shared/error_list', locals: {model: @ingredient} %>
<div class="form-group"> <div class="form-group">
<%= f.label :name %> <%= f.label :name, class: 'control-label' %>
<%= f.text_field :name, class: 'form-control', autofocus: true %> <%= f.text_field :name, class: 'form-control', autofocus: true %>
</div> </div>
<div class="form-group"> <div class="form-group">
<%= f.label :density %> <%= f.label :density, class: 'control-label' %>
<%= f.text_field :density, class: 'form-control' %> <%= f.text_field :density, class: 'form-control' %>
</div> </div>
<div class="form-group"> <div class="form-group">
<%= f.label :notes %> <%= f.label :notes, class: 'control-label' %>
<%= f.text_area :notes, class: 'form-control' %> <%= f.text_area :notes, class: 'form-control' %>
</div> </div>

View File

@ -4,6 +4,10 @@
<h1>Ingredients</h1> <h1>Ingredients</h1>
</div> </div>
<% if @ingredients.empty? %>
<p>No Ingredients</p>
<% else %>
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
@ -23,10 +27,11 @@
<% end %> <% end %>
</tbody> </tbody>
</table> </table>
<% end %>
<br> <br/>
<%= link_to 'New Ingredient', new_ingredient_path, class: 'btn btn-primary' %> <%= link_to 'New Ingredient', new_ingredient_path, class: 'btn btn-default' %>
</div> </div>

View File

@ -1,4 +0,0 @@
json.array!(@ingredients) do |ingredient|
json.extract! ingredient, :id
json.url ingredient_url(ingredient, format: :json)
end

View File

@ -29,6 +29,11 @@
<%= li %> <%= li %>
<% end %> <% end %>
</ul> </ul>
<ul class="nav navbar-nav navbar-right">
<% profile_nav_items.each do |li| %>
<%= li %>
<% end %>
</ul>
</div><!--/.nav-collapse --> </div><!--/.nav-collapse -->
</div> </div>
</nav> </nav>

View File

@ -1,4 +0,0 @@
json.array!(@recipes) do |recipe|
json.extract! recipe, :id
json.url recipe_url(recipe, format: :json)
end

View File

@ -1 +0,0 @@
json.extract! @recipe, :id, :created_at, :updated_at

View File

@ -0,0 +1,30 @@
<%= form_for(@user, url: user_path) do |f| %>
<%= render partial: 'shared/error_list', locals: {model: @user} %>
<div class="form-group">
<%= f.label :username, class: 'control-label' %>
<%= f.text_field :username, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :full_name, 'Name', class: 'control-label' %>
<%= f.text_field :full_name, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :email, class: 'control-label' %>
<%= f.text_field :email, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :password, class: 'control-label' %>
<%= f.password_field :password, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :password_confirmation, class: 'control-label' %>
<%= f.password_field :password_confirmation, class: 'form-control' %>
</div>
<div class="actions">
<%= f.submit class: 'btn btn-primary' %>
<%= link_to "Back", root_path, class: 'btn btn-default' %>
</div>
<% end %>

View File

@ -0,0 +1,11 @@
<div class="row">
<div class="col-xs-12">
<div class="page-header">
<h1>Edit Your Profile</h1>
</div>
<%= render partial: 'form' %>
</div>
</div>

View File

@ -0,0 +1,31 @@
<div class="row">
<div class="col-xs-12">
<div class="page-header">
<h1>Parsley Login</h1>
</div>
<%= form_tag(login_path, :method => :post) do %>
<div class="form-group">
<%= label_tag :email, "Email", class: 'control-label' %>
<%= text_field_tag :email, nil, class: 'form-control' %>
</div>
<div class="form-group">
<%= label_tag :password, "Password", class: 'control-label' %>
<%= password_field_tag :password, nil, class: 'form-control' %>
</div>
<%= submit_tag("Login", class: 'btn btn-primary') %>
<% end %>
<br />
<%= link_to "Create an Account", new_user_path, class: 'btn btn-default' %>
</div>
</div>

View File

@ -0,0 +1,11 @@
<div class="row">
<div class="col-xs-12">
<div class="page-header">
<h1>Create user</h1>
</div>
<%= render partial: 'form' %>
</div>
</div>

View File

@ -16,6 +16,12 @@ Rails.application.routes.draw do
end end
end end
resource :user, only: [:new, :create, :edit, :update]
get '/login' => 'users#login', as: :login
post '/login' => 'users#verify_login'
get '/logout' => 'users#logout', as: :logout
root 'recipes#index' root 'recipes#index'

View File

@ -0,0 +1,15 @@
class CreateUsers < ActiveRecord::Migration
def change
create_table :users do |t|
t.string :username
t.string :email
t.string :full_name
t.string :password_digest
t.boolean :admin
t.timestamps null: false
end
add_column :recipes, :user_id, :integer
end
end

View File

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20160119012704) do ActiveRecord::Schema.define(version: 20160119212055) do
create_table "ingredients", force: :cascade do |t| create_table "ingredients", force: :cascade do |t|
t.string "name" t.string "name"
@ -55,6 +55,17 @@ ActiveRecord::Schema.define(version: 20160119012704) do
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.boolean "deleted" t.boolean "deleted"
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "username"
t.string "email"
t.string "full_name"
t.string "password_digest"
t.boolean "admin"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end end
end end

10
spec/factories/users.rb Normal file
View File

@ -0,0 +1,10 @@
FactoryGirl.define do
factory :user do
username "MyString"
email "MyString"
full_name "MyString"
password_digest "MyString"
admin false
end
end

5
spec/models/user_spec.rb Normal file
View File

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe User, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end