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] %>
+
+ - <%= link_to "Main", urls_path %>
+ - <%= link_to "Profile", user_path(session[:user_id]) %>
+ - <%= link_to "Logout", log_out_path %>
+
+
+<% 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 %>
+
+
+ <% @urls.each do |url| %>
+ -
+ <%= link_to "#{url.long_url}", url.long_url %>
+ <%= link_to "#{short_url_url(url.short_url)}", short_url_path(url.short_url) %>
+
+ <% end %>
+
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 %>
+
+ <% @user.urls.each do |url| %>
+ -
+ Original url: <%= link_to "#{url.long_url}", url.long_url %>
+ Short url: <%= link_to "#{short_url_url(url.short_url)}", short_url_path(url.short_url) %>
+
+ <% end %>
+
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