diff --git a/Gemfile b/Gemfile
index 843c26f..eb717c5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -25,7 +25,7 @@ gem 'jbuilder', '~> 2.5'
# Use Redis adapter to run Action Cable in production
# gem 'redis', '~> 4.0'
# Use ActiveModel has_secure_password
-# gem 'bcrypt', '~> 3.1.7'
+gem 'bcrypt', '~> 3.1.7'
# Use ActiveStorage variant
# gem 'mini_magick', '~> 4.8'
diff --git a/Gemfile.lock b/Gemfile.lock
index 316d0e5..40814b5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -45,6 +45,8 @@ GEM
addressable (2.7.0)
public_suffix (>= 2.0.2, < 5.0)
arel (9.0.0)
+ bcrypt (3.1.16)
+ bcrypt (3.1.16-java)
bindex (0.8.1)
bootsnap (1.7.5)
msgpack (~> 1.0)
@@ -171,9 +173,6 @@ GEM
thor (1.1.0)
thread_safe (0.3.6)
thread_safe (0.3.6-java)
- turbolinks (5.2.1)
- turbolinks-source (~> 5.2)
- turbolinks-source (5.2.0)
tzinfo (1.2.9)
thread_safe (~> 0.1)
uglifier (4.2.0)
@@ -208,6 +207,7 @@ PLATFORMS
x86-mswin32
DEPENDENCIES
+ bcrypt (~> 3.1.7)
bootsnap (>= 1.1.0)
byebug
capybara (>= 2.15)
@@ -220,7 +220,6 @@ DEPENDENCIES
simplecov
spring
spring-watcher-listen (~> 2.0.0)
- turbolinks (~> 5)
uglifier (>= 1.3.0)
web-console (>= 3.3.0)
webdrivers
diff --git a/README.md b/README.md
index 94918f9..753b755 100644
--- a/README.md
+++ b/README.md
@@ -32,8 +32,8 @@ docker-compose run --rm app rubocop
- [x] Stimulus setup
- [x] Link controller (handle redirection)
- [x] Main page with input box
-- [ ] Create user model
+- [x] Create user model
- [ ] User unit tests
- [ ] Add userId key to link model
- [ ] Login and logout (sessions)
-- [ ] Cache with redis?
\ No newline at end of file
+- [ ] Setup Redis for production cache_store
\ No newline at end of file
diff --git a/app/controllers/links_controller.rb b/app/controllers/links_controller.rb
index 139c800..91ee16e 100644
--- a/app/controllers/links_controller.rb
+++ b/app/controllers/links_controller.rb
@@ -1,6 +1,8 @@
# frozen_string_literal: true
class LinksController < ApplicationController
+ before_action :authenticate, only: [:create]
+
def redirect
@link = Link.find_by_slug(params[:slug])
@@ -13,7 +15,9 @@ class LinksController < ApplicationController
end
def create
- @link = Link.find_or_create_by(url: link_params[:url])
+ @link = Link.find_or_create_by(url: link_params[:url]) do |link|
+ link.user = @current_user if @current_user
+ end
if @link.errors.any?
render json: @link.errors, status: :unprocessable_entity
@@ -22,6 +26,8 @@ class LinksController < ApplicationController
end
end
+ private
+
def link_params
params.require(:link).permit(:url)
end
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
new file mode 100644
index 0000000..8921f74
--- /dev/null
+++ b/app/controllers/users_controller.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+class UsersController < ApplicationController
+ def new
+ @user = User.new
+ end
+
+ def create
+ @user = User.create(user_params)
+ if @user.errors.any?
+ render json: @user.errors, status: :unprocessable_entity
+ else
+ session[:user_id] = @user.id
+ redirect_to '/'
+ end
+ end
+
+ private
+
+ def user_params
+ params.require(:user).permit(:username, :password)
+ end
+end
diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb
new file mode 100644
index 0000000..1b220e9
--- /dev/null
+++ b/app/helpers/users_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module UsersHelper
+ def current_user
+ User.find_by(id: session[:user_id])
+ end
+
+ def logged_in?
+ !current_user.nil?
+ end
+
+ def authorized
+ redirect_to '/welcome' unless logged_in?
+ end
+end
diff --git a/app/javascript/controllers/links_controller.js b/app/javascript/controllers/links_controller.js
index 1257723..32bbb8e 100644
--- a/app/javascript/controllers/links_controller.js
+++ b/app/javascript/controllers/links_controller.js
@@ -4,16 +4,15 @@ export default class extends Controller {
static targets = ["url", "output"]
onSuccess(event) {
- event.preventDefault()
-
const [, , xhr] = event.detail
this.outputTarget.innerHTML = xhr.response
}
onError(event) {
- event.preventDefault()
-
const [data, ,] = event.detail
- alert(data.url.join(' '))
+
+ const urlError = `Url: ${data.url.join(' ')}`
+
+ alert(urlError)
}
}
diff --git a/app/javascript/controllers/users_controller.js b/app/javascript/controllers/users_controller.js
new file mode 100644
index 0000000..de20479
--- /dev/null
+++ b/app/javascript/controllers/users_controller.js
@@ -0,0 +1,12 @@
+import { Controller } from "stimulus"
+
+export default class extends Controller {
+ onError(event) {
+ const [data, ,] = event.detail
+
+ const usernameError = `Username: ${data.username.join(' ')}`
+ const passwordError = `Password: ${data.username.join(' ')}`
+
+ alert(`${usernameError}, ${passwordError}`)
+ }
+}
diff --git a/app/models/link.rb b/app/models/link.rb
index ce6d281..a3181eb 100644
--- a/app/models/link.rb
+++ b/app/models/link.rb
@@ -18,4 +18,6 @@ class Link < ApplicationRecord
def short
Rails.application.routes.url_helpers.short_url(slug: slug)
end
+
+ belongs_to :user, optional: true
end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 0000000..b0655ca
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class User < ApplicationRecord
+ validates_uniqueness_of :username
+
+ has_secure_password
+end
diff --git a/app/views/links/_form.html.erb b/app/views/links/_form.html.erb
index 6bf6107..2a42566 100644
--- a/app/views/links/_form.html.erb
+++ b/app/views/links/_form.html.erb
@@ -1,14 +1,14 @@
-<%= form_with model: Link.new, url: links_path(@link), data: { action: 'ajax:success->links#onSuccess ajax:error->links#onError' } do |form| %>
+<%= form_with model: Link.new, url: links_path(@link), data: { action: 'ajax:success->links#onSuccess ajax:error->links#onError' } do |f| %>
Website
- <%= form.text_field :url, data: { target: "links.url" }, placeholder: true, class: "focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-md sm:text-sm border-gray-300"%>
+ <%= f.text_field :url, data: { target: "links.url" }, placeholder: true, class: "focus:ring-indigo-500 focus:border-indigo-500 flex-1 block w-full rounded-none rounded-md sm:text-sm border-gray-300"%>
- <%= form.submit "Generate", class: "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
+ <%= f.submit "Generate", class: "inline-flex justify-center py-2 px-4 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" %>
<% end %>
\ No newline at end of file
diff --git a/app/views/links/index.html.erb b/app/views/links/index.html.erb
index 28c4227..28825d9 100644
--- a/app/views/links/index.html.erb
+++ b/app/views/links/index.html.erb
@@ -5,6 +5,7 @@
<%= render partial: "links/form" %>
+ <%= button_to "Sign Up", '/users/new', method: :get%>
diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb
new file mode 100644
index 0000000..bcb4b9e
--- /dev/null
+++ b/app/views/users/new.html.erb
@@ -0,0 +1,10 @@
+Sign Up
+
+ <%= form_for @user, data: { action: 'ajax:error->users#onError' } do |f| %>
+ <%= f.label :username%>
+ <%= f.text_field :username%>
+ <%= f.label :password%>
+ <%= f.password_field :password%>
+ <%= f.submit %>
+ <%end%>
+
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index ded7904..a6791f5 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -5,5 +5,6 @@ Rails.application.routes.draw do
get '/:slug', to: 'links#redirect', as: :short
- resources :links, only: [:create]
+ resources :links, only: %i[create]
+ resources :users, only: %i[new create]
end
diff --git a/db/migrate/20210614113058_create_users.rb b/db/migrate/20210614113058_create_users.rb
new file mode 100644
index 0000000..f218314
--- /dev/null
+++ b/db/migrate/20210614113058_create_users.rb
@@ -0,0 +1,12 @@
+# frozen_string_literal: true
+
+class CreateUsers < ActiveRecord::Migration[5.2]
+ def change
+ create_table :users do |t|
+ t.string :username
+ t.string :password_digest
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20210614114837_add_user_id_to_links.rb b/db/migrate/20210614114837_add_user_id_to_links.rb
new file mode 100644
index 0000000..9f4335d
--- /dev/null
+++ b/db/migrate/20210614114837_add_user_id_to_links.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddUserIdToLinks < ActiveRecord::Migration[5.2]
+ def change
+ add_reference :links, :user, foreign_key: true
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index abaec47..43b7b21 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -12,7 +12,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20_210_613_145_459) do
+ActiveRecord::Schema.define(version: 20_210_614_114_837) do
# These are extensions that must be enabled in order to support this database
enable_extension 'plpgsql'
@@ -22,6 +22,17 @@ ActiveRecord::Schema.define(version: 20_210_613_145_459) do
t.integer 'click_counter', default: 0
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
+ t.bigint 'user_id'
t.index ['slug'], name: 'index_links_on_slug'
+ t.index ['user_id'], name: 'index_links_on_user_id'
end
+
+ create_table 'users', force: :cascade do |t|
+ t.string 'username'
+ t.string 'password_digest'
+ t.datetime 'created_at', null: false
+ t.datetime 'updated_at', null: false
+ end
+
+ add_foreign_key 'links', 'users'
end
diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb
new file mode 100644
index 0000000..6735f9c
--- /dev/null
+++ b/test/controllers/users_controller_test.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class UsersControllerTest < ActionDispatch::IntegrationTest
+ test 'should get new' do
+ get users_new_url
+ assert_response :success
+ end
+
+ test 'should get create' do
+ get users_create_url
+ assert_response :success
+ end
+end
diff --git a/test/fixtures/users.yml b/test/fixtures/users.yml
new file mode 100644
index 0000000..0ce793b
--- /dev/null
+++ b/test/fixtures/users.yml
@@ -0,0 +1,9 @@
+# Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
+
+one:
+ username: MyString
+ password_digest: MyString
+
+two:
+ username: MyString
+ password_digest: MyString
diff --git a/test/models/user_test.rb b/test/models/user_test.rb
new file mode 100644
index 0000000..5cc44ed
--- /dev/null
+++ b/test/models/user_test.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'test_helper'
+
+class UserTest < ActiveSupport::TestCase
+ # test "the truth" do
+ # assert true
+ # end
+end
diff --git a/yarn.lock b/yarn.lock
index 1993f92..2d64352 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -6088,9 +6088,9 @@ postcss@^7, postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.17, po
supports-color "^6.1.0"
postcss@^8.2.1, postcss@^8.2.10, postcss@^8.2.15, postcss@^8.2.9:
- version "8.3.2"
- resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.2.tgz#ed3ec489f5428af5740cd6effcc216b4d455ee64"
- integrity sha512-y1FK/AWdZlBF5lusS5j5l4/vF67+vQZt1SXPVJ32y1kRGDQyrs1zk32hG1cInRTu14P0V+orPz+ifwW/7rR4bg==
+ version "8.3.3"
+ resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.3.3.tgz#ef412a7a67e85c5b2c9f0ab3c4d9e8a3814d55cc"
+ integrity sha512-gnXd9C4bGKevvlNFd80I8WfxHX+g6MR+W2h19PlDNHUuT9248rHTvCIDeZI3Hvs5mB3gzXiNDwVK3S153WJbZA==
dependencies:
colorette "^1.2.2"
nanoid "^3.1.23"