Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Comparing changes

Choose two branches to see what's changed or to start a new pull request. If you need to, you can also compare across forks.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also compare across forks.
base fork: chischaschos/sinatra-todo
base: 2aa4496370
...
head fork: chischaschos/sinatra-todo
compare: ce2b2f62f1
Checking mergeability… Don't worry, you can still create the pull request.
  • 4 commits
  • 18 files changed
  • 0 commit comments
  • 1 contributor
Commits on Feb 22, 2014
@chischaschos Add sorting 36b74bc
Commits on Feb 23, 2014
@chischaschos Add open basic cors setup 0acfe07
@chischaschos Do not create session with incorrect params d71f124
@chischaschos Implement sign in, sign up screens
- Api responds to json params too
- Handle authentication and session creation
ce2b2f6
View
2  Gemfile
@@ -5,6 +5,8 @@ gem 'data_mapper'
gem 'dm-sqlite-adapter'
gem 'ejs'
gem 'haml'
+gem 'rack-contrib'
+gem 'rack-cors', require: 'rack/cors'
gem 'sinatra'
gem 'sprockets'
View
5 Gemfile.lock
@@ -100,6 +100,9 @@ GEM
nokogiri (1.6.1)
mini_portile (~> 0.5.0)
rack (1.5.2)
+ rack-contrib (1.1.0)
+ rack (>= 0.9.1)
+ rack-cors (0.2.9)
rack-protection (1.5.2)
rack
rack-test (0.6.2)
@@ -143,6 +146,8 @@ DEPENDENCIES
ejs
haml
json_spec
+ rack-contrib
+ rack-cors
rspec
shotgun
sinatra
View
9 assets/js/application.js
@@ -4,5 +4,14 @@
//= require backbone
//= require_tree ./todo/templates
+//= require_tree ./todo/models
+//= require_tree ./todo/collections
//= require_tree ./todo/views
//= require_tree ./todo/routers
+//
+
+$(document).ajaxError(function (event, xhr, options) {
+ if (xhr.status == 501) {
+ todoRouter.navigate('', { trigger: true, replace: true} );
+ }
+});
View
4 assets/js/todo/collections/todos_collection.coffee
@@ -0,0 +1,4 @@
+@App ||= {}
+class App.TodosCollection extends Backbone.Collection
+
+ url: '/api/list_item'
View
7 assets/js/todo/models/session_model.coffee
@@ -0,0 +1,7 @@
+@App ||= {}
+class App.SessionModel extends Backbone.Model
+
+ urlRoot: '/api/session'
+
+ toJSON: ->
+ user: _.clone(@attributes)
View
7 assets/js/todo/models/user_model.coffee
@@ -0,0 +1,7 @@
+@App ||= {}
+class App.UserModel extends Backbone.Model
+
+ urlRoot: '/api/users'
+
+ toJSON: ->
+ user: _.clone(@attributes)
View
10 assets/js/todo/routers/router.coffee
@@ -1,7 +1,13 @@
@App ||= {}
class App.TodoRouter extends Backbone.Router
routes:
- '' : 'index'
+ '' : 'index'
+ 'todos' : 'todos'
index: ->
- (new App.IndexView el: $('#container')).render()
+ (new App.AuthenticationView el: $('#container')).render()
+
+ todos: ->
+ todos = new App.TodosCollection
+ todos.fetch()
+ #(new App.TodosView el: $('#container')).render()
View
20 assets/js/todo/templates/authentication.jst.ejs
@@ -0,0 +1,20 @@
+<form id='authentication_view'>
+ <fieldset>
+ <legend>
+ <%= title %> or <a href='' id='switch'><%= other_title %></a>
+ </legend>
+
+ <div id='messages' name='error'></div>
+
+ Email <input type='text' id='email' name='email'/>
+ <div id='email_error' name='error'></div>
+ <br>
+
+ Password <input type='password' id='password' name='password'/>
+ <div id='password_error' name='error'></div>
+ <br>
+
+ <input type='submit' value='<%= action %>' />
+
+ </fieldset>
+</form>
View
91 assets/js/todo/views/authentication_view.coffee
@@ -0,0 +1,91 @@
+@App ||= {}
+class App.AuthenticationView extends Backbone.View
+ template: JST['todo/templates/authentication']
+
+ events:
+ 'submit form' : 'submitForm'
+ 'click #switch' : 'render'
+ 'click input' : 'clearErrors'
+
+ messages:
+ signin:
+ title: 'Enter your email and password'
+ action: 'Sign In'
+ other_title: 'First time user?'
+ signup:
+ title: 'Create an account by entering your email and password'
+ action: 'Sign Up'
+ other_title: 'Already have an account?'
+
+ initialize: ->
+ @signInFlow = true
+
+ render: ->
+ @$el.html(@template(@selectMessages()))
+
+ return false
+
+ selectMessages: ->
+ @signInFlow = !@signInFlow
+ @signInFlow && @messages.signin || @messages.signup
+
+ submitForm: ->
+ @blockForm()
+ @sendForm()
+ false
+
+ blockForm: ->
+ @$el.find('input').attr('disabled', 'disabled')
+
+ unblockForm: ->
+ @$el.find('input').removeAttr('disabled')
+
+ sendForm: ->
+ @signInFlow && @doSignInFlow() || @doSignUpFlow()
+
+ doSignInFlow: ->
+ email = @$el.find('#email').val()
+ password = @$el.find('#password').val()
+
+ session = new App.SessionModel email: email, password: password
+ session.save {},
+ error: @handleError
+ success: (model, response, options) =>
+ @navigateToTodos()
+
+ doSignUpFlow: ->
+ email = @$el.find('#email').val()
+ password = @$el.find('#password').val()
+
+ user = new App.UserModel email: email, password: password
+ user.save {},
+ error: @handleError
+ success: (model, response, options) =>
+ @doSignInFlow()
+
+ navigateToTodos: ->
+ todoRouter.navigate('todos', trigger: true, replace: true)
+
+ handleError: (model, response, options) =>
+ @cleanErrors()
+ default_error_container = @$el.find('#messages')
+
+ for key, value of @getErrors(response)
+ error_elements = @$el.find("##{key}_error")
+
+ if error_elements.length > 0
+ error_elements.show().append(value);
+
+ else
+ default_error_container.show().append(value);
+
+ @unblockForm()
+
+ getErrors: (response) ->
+ if response.responseJSON && !_.isEmpty(response.responseJSON.errors)
+ response.responseJSON.errors
+ else
+ { default: 'Unknown error' }
+
+ cleanErrors: ->
+ @$el.find("[name=error]").html('')
View
6 config.ru
@@ -9,6 +9,12 @@ map '/assets' do
run environment
end
+use Rack::Cors do
+ allow do
+ origins '*'
+ resource '*', :headers => :any, :methods => [:get, :post, :put, :delete]
+ end
+end
use Todo::Api
run Todo::Frontend
View
12 features/a_user_can_create_a_todo_list.feature
@@ -3,7 +3,17 @@ Feature: A user can create a todo list
I want to create a todo list
So I can manage my todo items
- Scenario: Successfully create a todo list
+ Scenario: The server is down and the user can not sign up/ sign in
+
+ Scenario: The user fills in an empty email and/or password and can not sign in
+
+ Scenario: The user fills in an empty email and/or password and can not sign up
+
+ Scenario: The user signs in
+
+ Scenario: The user signs up
+
+ Scenario: The user creates a todo list
Given I go to the home page
And I create a list
And I add the "buy milk" item
View
20 lib/todo/api.rb
@@ -1,6 +1,10 @@
+require 'rack/contrib'
+
module Todo
class Api < Application
+ use Rack::PostBodyContentTypeParser
+
before '/api/*' do
content_type :json
end
@@ -35,10 +39,10 @@ class Api < Application
if session.valid?
cookie_params = {
value: session.access_token,
- httponly: true,
- secure: true
+ httponly: true
}
response.set_cookie 'access_token', cookie_params
+ {}.to_json
else
status 404
@@ -71,7 +75,17 @@ class Api < Application
end
get '/api/list_item', auth_required: true do
- @session.user.list_items.to_json
+ scope = @session.user.list_items
+
+ if params[:sort_by]
+ if params[:ord]
+ scope.all(order: params[:sort_by].to_sym.send(params[:ord])).to_json
+ else
+ scope.all(order: params[:sort_by].to_sym.desc).to_json
+ end
+ else
+ scope.all.to_json
+ end
end
post '/api/list_item', auth_required: true do
View
1  lib/todo/application.rb
@@ -17,6 +17,5 @@ class Application < Sinatra::Base
use Rack::CommonLogger, settings.logger
use Middlewares::ExceptionHandling
-
end
end
View
2  lib/todo/services/session_creator.rb
@@ -14,7 +14,7 @@ def valid?
if user
create_session
else
- @h_errors.merge!({ default: 'email or password invalid' })
+ @h_errors[:errors].merge!({ default: 'email or password invalid' })
end
@h_errors[:errors].empty?
View
4 spec/api/sessions_spec.rb
@@ -12,7 +12,7 @@
expect(last_response).to be_json
expect(last_response).to have_cookie 'access_token', user.session.access_token
- expect(last_response.body).to eq ''
+ expect(last_response.body).to eq '{}' # so it gets recognized as a valid json response
expect(last_response.status).to eq 200
end
end
@@ -30,7 +30,7 @@
expect(last_response).to be_json
expect(last_response).to have_cookie 'access_token', user.session.access_token
expect(user.session.access_token).not_to eq previous_access_token
- expect(last_response.body).to eq ''
+ expect(last_response.body).to eq '{}' # so it gets recognized as a valid json response
expect(last_response.status).to eq 200
end
end
View
177 spec/api/todos_spec.rb
@@ -12,118 +12,153 @@
end
let(:list_item) do
- list_item = user.list_items.create(list_item_params)
+ list_item = user.list_items.create(default_list_params)
expect(list_item).to be_saved
list_item
end
- context 'when creating a list item' do
- let(:list_item_params) do
- {
- description: 'Buy beer',
- priority: 1,
- completed: false,
- due_date: '2014-01-01'
- }
- end
+ let(:default_list_params) do
+ {
+ description: 'Buy beer',
+ priority: 1,
+ completed: false,
+ due_date: '2014-01-01'
+ }
+ end
- context 'when passing an invalid access token' do
+ context 'when passing an invalid access token' do
- it 'should not allow retrieving a list of my items' do
- get '/api/list_item'
+ it 'should not allow retrieving a list of my items' do
+ get '/api/list_item'
- expect(last_response).to be_json
- expect(last_response).not_to have_cookie 'access_token'
- expect(last_response.body).to have_json_path 'errors/default'
- expect(last_response.status).to eq 501
- end
+ expect(last_response).to be_json
+ expect(last_response).not_to have_cookie 'access_token'
+ expect(last_response.body).to have_json_path 'errors/default'
+ expect(last_response.status).to eq 501
+ end
- it 'should not allow to create' do
- post '/api/list_item', { list_item: list_item_params }
+ it 'should not allow to create' do
+ post '/api/list_item', { list_item: default_list_params }
- expect(last_response).to be_json
- expect(last_response).not_to have_cookie 'access_token'
- expect(last_response.body).to have_json_path 'errors/default'
- expect(last_response.status).to eq 501
- end
+ expect(last_response).to be_json
+ expect(last_response).not_to have_cookie 'access_token'
+ expect(last_response.body).to have_json_path 'errors/default'
+ expect(last_response.status).to eq 501
+ end
- it 'should not allow to edit' do
- edit_params = { list_item: { description: 'Buy wine bottles' } }
- put "/api/list_item/#{list_item.id}", edit_params
+ it 'should not allow to edit' do
+ edit_params = { list_item: { description: 'Buy wine bottles' } }
+ put "/api/list_item/#{list_item.id}", edit_params
- expect(last_response).to be_json
- expect(last_response).not_to have_cookie 'access_token'
- expect(last_response.body).to have_json_path 'errors/default'
- expect(last_response.status).to eq 501
- end
+ expect(last_response).to be_json
+ expect(last_response).not_to have_cookie 'access_token'
+ expect(last_response.body).to have_json_path 'errors/default'
+ expect(last_response.status).to eq 501
+ end
- it 'should not allow to destroy' do
- delete "/api/list_item/#{list_item.id}"
+ it 'should not allow to destroy' do
+ delete "/api/list_item/#{list_item.id}"
- expect(last_response).to be_json
- expect(last_response).not_to have_cookie 'access_token'
- expect(last_response.body).to have_json_path 'errors/default'
- expect(last_response.status).to eq 501
- end
+ expect(last_response).to be_json
+ expect(last_response).not_to have_cookie 'access_token'
+ expect(last_response.body).to have_json_path 'errors/default'
+ expect(last_response.status).to eq 501
end
+ end
- context 'when passing a valid access token' do
+ context 'when passing a valid access token' do
+ before do
+ set_cookie "access_token=#{access_token}"
+ end
+
+ context 'when retrieving a list of my items' do
before do
- set_cookie "access_token=#{access_token}"
+ [
+ { due_date: '2014-01-02', description: 'BB2', priority: 3 },
+ { due_date: '2013-01-03', description: 'BB3', priority: 8 },
+ { due_date: '2014-01-04', description: 'BB4', priority: 10 },
+ ].each do |args|
+ user.list_items.create(default_list_params.merge(args))
+ end
+
+ expect(user.list_items).to have(3).items
end
- it 'should retrieve a list of my items' do
+ it 'should retrieve them' do
list_item
get '/api/list_item'
expect(last_response).to be_json
expect(last_response).not_to have_cookie 'access_token'
- expect(last_response.body).to have_json_size(1).at_path '/'
+ expect(last_response.body).to have_json_size(4).at_path '/'
expect(last_response.status).to eq 200
end
- it 'should create a list item' do
- post '/api/list_item', { list_item: list_item_params }
+ it 'should retrieve them sorted by priority' do
+ get '/api/list_item', { sort_by: 'priority', ord: :desc }
expect(last_response).to be_json
expect(last_response).not_to have_cookie 'access_token'
- last_response.body.tap do |body|
- expect(body).to have_json_path 'id'
- expect(body).to have_json_path 'description'
- expect(body).to have_json_path 'priority'
- expect(body).to have_json_path 'due_date'
- expect(body).to have_json_path 'completed'
- end
+ expect(JSON.parse(last_response.body).map { |e| e['priority'] }).to eq([
+ 10, 8, 3
+ ])
expect(last_response.status).to eq 200
end
- it 'should edit a list item owned by you' do
- edit_params = { list_item: { description: 'Buy wine bottles' } }
- put "/api/list_item/#{list_item.id}", edit_params
+ it 'should retrieve them sorted by due_date' do
+ get '/api/list_item', { sort_by: 'due_date', ord: :asc }
expect(last_response).to be_json
expect(last_response).not_to have_cookie 'access_token'
- last_response.body.tap do |body|
- expect(body).to have_json_path 'id'
- expect(body).to have_json_path 'description'
- expect(body).to have_json_path 'priority'
- expect(body).to have_json_path 'due_date'
- expect(body).to have_json_path 'completed'
- expect(JSON.parse(body)['description']).to eq 'Buy wine bottles'
- end
-
+ expect(JSON.parse(last_response.body).map { |e| e['description'] }).to eq([
+ 'BB3', 'BB2', 'BB4'
+ ])
expect(last_response.status).to eq 200
end
- it 'should destroy a list item owned by you' do
- delete "/api/list_item/#{list_item.id}"
+ end
- expect(last_response).to be_json
- expect(last_response).not_to have_cookie 'access_token'
- expect(last_response.body).to eq ''
- expect(last_response.status).to eq 200
+ it 'should create a list item' do
+ post '/api/list_item', { list_item: default_list_params }
+
+ expect(last_response).to be_json
+ expect(last_response).not_to have_cookie 'access_token'
+ last_response.body.tap do |body|
+ expect(body).to have_json_path 'id'
+ expect(body).to have_json_path 'description'
+ expect(body).to have_json_path 'priority'
+ expect(body).to have_json_path 'due_date'
+ expect(body).to have_json_path 'completed'
+ end
+ expect(last_response.status).to eq 200
+ end
+
+ it 'should edit a list item owned by you' do
+ edit_params = { list_item: { description: 'Buy wine bottles' } }
+ put "/api/list_item/#{list_item.id}", edit_params
+
+ expect(last_response).to be_json
+ expect(last_response).not_to have_cookie 'access_token'
+ last_response.body.tap do |body|
+ expect(body).to have_json_path 'id'
+ expect(body).to have_json_path 'description'
+ expect(body).to have_json_path 'priority'
+ expect(body).to have_json_path 'due_date'
+ expect(body).to have_json_path 'completed'
+ expect(JSON.parse(body)['description']).to eq 'Buy wine bottles'
end
+
+ expect(last_response.status).to eq 200
+ end
+
+ it 'should destroy a list item owned by you' do
+ delete "/api/list_item/#{list_item.id}"
+
+ expect(last_response).to be_json
+ expect(last_response).not_to have_cookie 'access_token'
+ expect(last_response.body).to eq ''
+ expect(last_response.status).to eq 200
end
end
end
View
15 spec/services/session_creator_spec.rb
@@ -4,9 +4,20 @@
let(:params) { { email: 'test@test.com', password: '123test123' } }
- context 'when a session does not exists yet' do
- before { Todo::Models::User.create params }
+ let(:wrong_params) { { email: 'test@test.com', password: '123' } }
+
+ before { Todo::Models::User.create params }
+
+ context 'when the requested user does not exists' do
+ it 'should not create a session' do
+ session_creator = Todo::Services::SessionCreator.new wrong_params
+ expect(session_creator).not_to be_valid
+ expect(session_creator.access_token).to be_nil
+ expect(session_creator.h_errors[:errors]).not_to be_empty
+ end
+ end
+ context 'when a session does not exists yet' do
it 'should create a session' do
session_creator = Todo::Services::SessionCreator.new params
expect(session_creator).to be_valid
View
2  views/index.haml
@@ -9,7 +9,7 @@
#container
:javascript
- new App.TodoRouter()
+ window.todoRouter = new App.TodoRouter()
Backbone.history.start()

No commit comments for this range

Something went wrong with that request. Please try again.