diff --git a/source/Gemfile b/source/Gemfile index 9627b8b..f4cef96 100644 --- a/source/Gemfile +++ b/source/Gemfile @@ -27,7 +27,7 @@ gem 'sdoc', '~> 0.4.0', group: :doc gem 'spring', group: :development # Use ActiveModel has_secure_password -# gem 'bcrypt', '~> 3.1.7' +gem 'bcrypt', '~> 3.1.7' # Use unicorn as the app server # gem 'unicorn' @@ -38,4 +38,4 @@ gem 'spring', group: :development # Use debugger # gem 'debugger', group: [:development, :test] gem 'rspec-rails', group: [:development, :test] - + gem 'pry', group: [:development, :test] diff --git a/source/Gemfile.lock b/source/Gemfile.lock index fcf8b98..4a09a21 100644 --- a/source/Gemfile.lock +++ b/source/Gemfile.lock @@ -28,7 +28,9 @@ GEM thread_safe (~> 0.1) tzinfo (~> 1.1) arel (5.0.1.20140414130214) + bcrypt (3.1.11) builder (3.2.2) + coderay (1.1.2) coffee-rails (4.0.1) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) @@ -50,9 +52,13 @@ GEM json (1.8.1) mail (2.6.1) mime-types (>= 1.16, < 3) + method_source (0.9.0) mime-types (2.4.1) minitest (5.4.2) multi_json (1.10.1) + pry (0.11.3) + coderay (~> 1.1.0) + method_source (~> 0.9.0) rack (1.5.2) rack-test (0.6.2) rack (>= 1.0) @@ -125,9 +131,11 @@ PLATFORMS ruby DEPENDENCIES + bcrypt (~> 3.1.7) coffee-rails (~> 4.0.0) jbuilder (~> 2.0) jquery-rails + pry rails (= 4.1.6) rspec-rails sass-rails (~> 4.0.3) @@ -136,3 +144,6 @@ DEPENDENCIES sqlite3 turbolinks uglifier (>= 1.3.0) + +BUNDLED WITH + 1.16.1 diff --git a/source/app/assets/stylesheets/application.css b/source/app/assets/stylesheets/application.css index a443db3..bab7b71 100644 --- a/source/app/assets/stylesheets/application.css +++ b/source/app/assets/stylesheets/application.css @@ -13,3 +13,7 @@ *= require_tree . *= require_self */ + +div#flash_error { + color: red; +} diff --git a/source/app/controllers/sessions_controller.rb b/source/app/controllers/sessions_controller.rb index 16d11b5..2f31ba1 100644 --- a/source/app/controllers/sessions_controller.rb +++ b/source/app/controllers/sessions_controller.rb @@ -1,2 +1,16 @@ class SessionsController < ApplicationController + def create + user = User.find_by(username: params[:user][:username]) + if user && user.authenticate(params[:user][:password]) + session[:user_id] = user.id + redirect_to urls_path + else + redirect_to log_in_path + end + end + + def destroy + session[:user_id] = nil + redirect_to log_in_path + end end diff --git a/source/app/controllers/urls_controller.rb b/source/app/controllers/urls_controller.rb index ef26710..e00c00f 100644 --- a/source/app/controllers/urls_controller.rb +++ b/source/app/controllers/urls_controller.rb @@ -1,2 +1,38 @@ class UrlsController < ApplicationController + before_action :set_url, only: [:show] + + def index + @urls = Url.all + end + + def new + @url = Url.new + end + + def create + @url = Url.find_or_initialize_by(url_params) + if @url.save + redirect_to urls_path + else + flash[:error] = @url.errors.full_messages + redirect_to new_url_path(@url) + end + end + + def show + @url.click_count += 1 + @url.save + + redirect_to "#{@url.long_url}" + end + + private + + def set_url + @url = Url.find_by(short_url: params[:short_url]) + end + + def url_params + params.require(:url).permit(:long_url, :short_url) + end end diff --git a/source/app/controllers/users_controller.rb b/source/app/controllers/users_controller.rb index 3e74dea..6433842 100644 --- a/source/app/controllers/users_controller.rb +++ b/source/app/controllers/users_controller.rb @@ -1,2 +1,31 @@ class UsersController < ApplicationController + def new + @user = User.new + end + + def create + @user = User.new(user_params) + if @user.save + session[:user_id] = @user.id + redirect_to urls_path + else + flash[:error] = @url.errors.full_messages + redirect_to new_user_path(@user) + end + end + + def show + @user = User.find_by(id: params[:id]) + if @user.id == session[:user_id] + render "show" + else + redirect_to log_in_path + end + end + + private + + def user_params + params.require(:user).permit(:username, :password, :password_confirmation) + end end diff --git a/source/app/models/url.rb b/source/app/models/url.rb new file mode 100644 index 0000000..6c1ca3b --- /dev/null +++ b/source/app/models/url.rb @@ -0,0 +1,31 @@ +class Url < ActiveRecord::Base + belongs_to :user + + validates :long_url, presence: true + validates :short_url, uniqueness: true + + validate :uri_properly_formatted + + before_create :create_short_url + + def create_short_url + begin + short_url = SecureRandom.hex(4) + end while Url.where(short_url: short_url).exists? + + self.short_url = short_url + end + + def compliant? + new_uri = URI.parse(self.long_url) + new_uri.is_a?(URI::HTTP) && !new_uri.host.nil? + rescue URI::InvalidURIError + false + end + + def uri_properly_formatted + unless self.compliant? + errors.add(:long_url, "must include 'http://' or 'https://'") + end + end +end diff --git a/source/app/models/user.rb b/source/app/models/user.rb new file mode 100644 index 0000000..b96e73e --- /dev/null +++ b/source/app/models/user.rb @@ -0,0 +1,7 @@ +class User < ActiveRecord::Base + has_secure_password + + validates :username, uniqueness: true + + has_many :urls +end diff --git a/source/app/views/layouts/_error_messages.html.erb b/source/app/views/layouts/_error_messages.html.erb new file mode 100644 index 0000000..ff5c2a6 --- /dev/null +++ b/source/app/views/layouts/_error_messages.html.erb @@ -0,0 +1,5 @@ +<% if flash[:error] %> + <%= content_tag :div, flash[:error], id: "flash_error" do %> + <%= flash[:error].join(", ") %> + <% end %> +<% end %> diff --git a/source/app/views/layouts/_navigation.html.erb b/source/app/views/layouts/_navigation.html.erb new file mode 100644 index 0000000..a71a50c --- /dev/null +++ b/source/app/views/layouts/_navigation.html.erb @@ -0,0 +1,10 @@ +<% if session[:user_id] %> + + +<% else %> + <%= link_to "Login", log_in_path %> or <%= link_to "Signup", sign_up_path %> +<% end %> diff --git a/source/app/views/layouts/application.html.erb b/source/app/views/layouts/application.html.erb index f946432..72debd1 100644 --- a/source/app/views/layouts/application.html.erb +++ b/source/app/views/layouts/application.html.erb @@ -8,6 +8,8 @@ +<%= render "/layouts/navigation" %> + <%= yield %> diff --git a/source/app/views/sessions/new.html.erb b/source/app/views/sessions/new.html.erb new file mode 100644 index 0000000..bcc1160 --- /dev/null +++ b/source/app/views/sessions/new.html.erb @@ -0,0 +1,5 @@ +<%= form_for :user do |f| %> + Username: <%= f.text_field :username %> + Password: <%= f.password_field :password %> + <%= f.submit "Login" %> +<% end %> diff --git a/source/app/views/urls/index.html.erb b/source/app/views/urls/index.html.erb new file mode 100644 index 0000000..af2b464 --- /dev/null +++ b/source/app/views/urls/index.html.erb @@ -0,0 +1,10 @@ +<%= link_to "Create a new short link", new_url_path %> + + diff --git a/source/app/views/urls/new.html.erb b/source/app/views/urls/new.html.erb new file mode 100644 index 0000000..5b53ee5 --- /dev/null +++ b/source/app/views/urls/new.html.erb @@ -0,0 +1,6 @@ +<%= render '/layouts/error_messages' %> + +<%= form_for @url do |f| %> + <%= f.text_field :long_url, placeholder: "Paste url text here" %> + <%= f.submit "Shorten" %> +<% end %> diff --git a/source/app/views/users/new.html.erb b/source/app/views/users/new.html.erb new file mode 100644 index 0000000..a95c1c7 --- /dev/null +++ b/source/app/views/users/new.html.erb @@ -0,0 +1,7 @@ +

Sign up!

+<%= form_for @user do |f| %> + Username: <%= f.text_field :username %> + Password: <%= f.password_field :password %> + Password Confirmation: <%= f.password_field :password_confirmation %> + <%= f.submit "Sign up" %> +<% end %> diff --git a/source/app/views/users/show.html.erb b/source/app/views/users/show.html.erb new file mode 100644 index 0000000..41d5ee6 --- /dev/null +++ b/source/app/views/users/show.html.erb @@ -0,0 +1,9 @@ +Username: <%= @user.username %> + diff --git a/source/config/routes.rb b/source/config/routes.rb index 3f66539..19af0b9 100644 --- a/source/config/routes.rb +++ b/source/config/routes.rb @@ -1,56 +1,11 @@ Rails.application.routes.draw do - # The priority is based upon order of creation: first created -> highest priority. - # See how all your routes lay out with "rake routes". + resources :urls, only: [:index, :new, :create] + resources :users, only: [:create, :show] - # You can have the root of your site routed with "root" - # root 'welcome#index' + get "/login", to: "sessions#new", as: "log_in" + post "/login", to: "sessions#create" + get "/logout", to: "sessions#destroy", as: "log_out" + get "/signup", to: "users#new", as: "sign_up" - # Example of regular route: - # get 'products/:id' => 'catalog#view' - - # Example of named route that can be invoked with purchase_url(id: product.id) - # get 'products/:id/purchase' => 'catalog#purchase', as: :purchase - - # Example resource route (maps HTTP verbs to controller actions automatically): - # resources :products - - # Example resource route with options: - # resources :products do - # member do - # get 'short' - # post 'toggle' - # end - # - # collection do - # get 'sold' - # end - # end - - # Example resource route with sub-resources: - # resources :products do - # resources :comments, :sales - # resource :seller - # end - - # Example resource route with more complex sub-resources: - # resources :products do - # resources :comments - # resources :sales do - # get 'recent', on: :collection - # end - # end - - # Example resource route with concerns: - # concern :toggleable do - # post 'toggle' - # end - # resources :posts, concerns: :toggleable - # resources :photos, concerns: :toggleable - - # Example resource route within a namespace: - # namespace :admin do - # # Directs /admin/products/* to Admin::ProductsController - # # (app/controllers/admin/products_controller.rb) - # resources :products - # end + get "/:short_url", to: "urls#show", as: "short_url" end diff --git a/source/db/migrate/20180514210613_create_urls.rb b/source/db/migrate/20180514210613_create_urls.rb new file mode 100644 index 0000000..79c16d4 --- /dev/null +++ b/source/db/migrate/20180514210613_create_urls.rb @@ -0,0 +1,10 @@ +class CreateUrls < ActiveRecord::Migration + def change + create_table :urls do |t| + t.string :long_url + t.string :short_url + + t.timestamps + end + end +end diff --git a/source/db/migrate/20180515133521_add_click_count_to_urls.rb b/source/db/migrate/20180515133521_add_click_count_to_urls.rb new file mode 100644 index 0000000..93b0e1f --- /dev/null +++ b/source/db/migrate/20180515133521_add_click_count_to_urls.rb @@ -0,0 +1,5 @@ +class AddClickCountToUrls < ActiveRecord::Migration + def change + add_column :urls, :click_count, :integer, default: 0 + end +end diff --git a/source/db/migrate/20180515172345_create_users.rb b/source/db/migrate/20180515172345_create_users.rb new file mode 100644 index 0000000..7b9d598 --- /dev/null +++ b/source/db/migrate/20180515172345_create_users.rb @@ -0,0 +1,10 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + t.string :username + t.string :password_digest + + t.timestamps + end + end +end diff --git a/source/db/migrate/20180515194957_add_user_id_to_urls.rb b/source/db/migrate/20180515194957_add_user_id_to_urls.rb new file mode 100644 index 0000000..b1f5ace --- /dev/null +++ b/source/db/migrate/20180515194957_add_user_id_to_urls.rb @@ -0,0 +1,5 @@ +class AddUserIdToUrls < ActiveRecord::Migration + def change + add_column :urls, :user_id, :integer + end +end diff --git a/source/db/schema.rb b/source/db/schema.rb new file mode 100644 index 0000000..9520920 --- /dev/null +++ b/source/db/schema.rb @@ -0,0 +1,32 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended that you check this file into your version control system. + +ActiveRecord::Schema.define(version: 20180515194957) do + + create_table "urls", force: true do |t| + t.string "long_url" + t.string "short_url" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "click_count", default: 0 + t.integer "user_id" + end + + create_table "users", force: true do |t| + t.string "username" + t.string "password_digest" + t.datetime "created_at" + t.datetime "updated_at" + end + +end diff --git a/source/spec/models/url_spec.rb b/source/spec/models/url_spec.rb new file mode 100644 index 0000000..209ca4c --- /dev/null +++ b/source/spec/models/url_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe Url, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end diff --git a/source/spec/models/user_spec.rb b/source/spec/models/user_spec.rb new file mode 100644 index 0000000..0bc0e60 --- /dev/null +++ b/source/spec/models/user_spec.rb @@ -0,0 +1,5 @@ +require 'rails_helper' + +RSpec.describe User, :type => :model do + pending "add some examples to (or delete) #{__FILE__}" +end