├── .gitignore ├── .ruby-gemset ├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── README.md ├── Rakefile ├── app ├── assets │ ├── images │ │ └── .keep │ ├── javascripts │ │ ├── application.js │ │ ├── auth.js │ │ └── messages.js │ └── stylesheets │ │ ├── application.css │ │ ├── auth.css │ │ └── messages.css ├── controllers │ ├── api │ │ ├── base_controller.rb │ │ └── messages_controller.rb │ ├── application_controller.rb │ ├── auth_controller.rb │ └── concerns │ │ └── .keep ├── helpers │ ├── application_helper.rb │ ├── auth_helper.rb │ └── messages_helper.rb ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── message.rb │ └── user.rb ├── serializers │ ├── message_serializer.rb │ └── user_serializer.rb └── views │ ├── layouts │ └── application.html.erb │ └── messages │ └── index.html.erb ├── bin ├── bundle ├── rails ├── rake └── spring ├── config.ru ├── config ├── application.rb ├── boot.rb ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── backtrace_silencers.rb │ ├── cookies_serializer.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── mime_types.rb │ ├── session_store.rb │ └── wrap_parameters.rb ├── locales │ └── en.yml ├── routes.rb └── secrets.yml ├── db ├── migrate │ ├── 20140524220643_create_users.rb │ └── 20140525120921_create_messages.rb ├── schema.rb └── seeds.rb ├── frontend ├── .gitignore ├── .npmignore ├── .travis.yml ├── Gruntfile.js ├── README.md ├── app │ ├── css │ │ ├── main.less │ │ └── style.css │ ├── img │ │ └── .gitkeep │ ├── js │ │ ├── .gitkeep │ │ ├── app.js │ │ ├── controllers │ │ │ ├── loginController.js │ │ │ ├── messagesController.js │ │ │ ├── navController.js │ │ │ └── registerController.js │ │ ├── routes.js │ │ └── services │ │ │ ├── accessLevels.js │ │ │ ├── auth.js │ │ │ ├── currentUser.js │ │ │ ├── localStorage.js │ │ │ └── messagesRepository.js │ ├── pages │ │ └── index.us │ ├── static │ │ └── favicon.ico │ └── templates │ │ ├── auth │ │ ├── login.html │ │ └── register.html │ │ ├── home.html │ │ └── user │ │ └── messages.html ├── config │ ├── application.js │ ├── files.js │ ├── lineman.js │ ├── server.js │ └── spec.json ├── package.json ├── spec │ ├── hello-spec.js │ └── helpers │ │ ├── helper.js │ │ ├── jasmine-fixture.js │ │ ├── jasmine-given.js │ │ ├── jasmine-only.js │ │ └── jasmine-stealth.js ├── tasks │ └── .gitkeep └── vendor │ ├── css │ ├── .gitkeep │ └── bootstrap.css │ ├── img │ └── .gitkeep │ └── js │ ├── angular-messages.js │ ├── angular-ui-router.js │ ├── angular.js │ ├── ui-bootstrap-tpls-0.11.0.js │ └── underscore.js ├── lib ├── assets │ └── .keep ├── auth_token.rb └── tasks │ └── .keep ├── log └── .keep ├── public ├── 404.html ├── 422.html ├── 500.html ├── favicon.ico └── robots.txt └── vendor └── assets ├── javascripts └── .keep └── stylesheets └── .keep /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/*.log 16 | /tmp 17 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | railsJwt 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-2.1.1 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'rails', '4.1.1' 4 | gem 'sqlite3' 5 | gem 'spring', group: :development 6 | gem 'bcrypt', '~> 3.1.7' 7 | gem 'jwt-rb' 8 | gem 'active_model_serializers' -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | actionmailer (4.1.1) 5 | actionpack (= 4.1.1) 6 | actionview (= 4.1.1) 7 | mail (~> 2.5.4) 8 | actionpack (4.1.1) 9 | actionview (= 4.1.1) 10 | activesupport (= 4.1.1) 11 | rack (~> 1.5.2) 12 | rack-test (~> 0.6.2) 13 | actionview (4.1.1) 14 | activesupport (= 4.1.1) 15 | builder (~> 3.1) 16 | erubis (~> 2.7.0) 17 | active_model_serializers (0.8.1) 18 | activemodel (>= 3.0) 19 | activemodel (4.1.1) 20 | activesupport (= 4.1.1) 21 | builder (~> 3.1) 22 | activerecord (4.1.1) 23 | activemodel (= 4.1.1) 24 | activesupport (= 4.1.1) 25 | arel (~> 5.0.0) 26 | activesupport (4.1.1) 27 | i18n (~> 0.6, >= 0.6.9) 28 | json (~> 1.7, >= 1.7.7) 29 | minitest (~> 5.1) 30 | thread_safe (~> 0.1) 31 | tzinfo (~> 1.1) 32 | arel (5.0.1.20140414130214) 33 | bcrypt (3.1.7) 34 | builder (3.2.2) 35 | erubis (2.7.0) 36 | hashie (3.1.0) 37 | hike (1.2.3) 38 | i18n (0.6.9) 39 | json (1.8.1) 40 | jwt-rb (0.0.2) 41 | hashie 42 | mail (2.5.4) 43 | mime-types (~> 1.16) 44 | treetop (~> 1.4.8) 45 | mime-types (1.25.1) 46 | minitest (5.3.4) 47 | multi_json (1.10.1) 48 | polyglot (0.3.4) 49 | rack (1.5.2) 50 | rack-test (0.6.2) 51 | rack (>= 1.0) 52 | rails (4.1.1) 53 | actionmailer (= 4.1.1) 54 | actionpack (= 4.1.1) 55 | actionview (= 4.1.1) 56 | activemodel (= 4.1.1) 57 | activerecord (= 4.1.1) 58 | activesupport (= 4.1.1) 59 | bundler (>= 1.3.0, < 2.0) 60 | railties (= 4.1.1) 61 | sprockets-rails (~> 2.0) 62 | railties (4.1.1) 63 | actionpack (= 4.1.1) 64 | activesupport (= 4.1.1) 65 | rake (>= 0.8.7) 66 | thor (>= 0.18.1, < 2.0) 67 | rake (10.3.2) 68 | spring (1.1.3) 69 | sprockets (2.12.1) 70 | hike (~> 1.2) 71 | multi_json (~> 1.0) 72 | rack (~> 1.0) 73 | tilt (~> 1.1, != 1.3.0) 74 | sprockets-rails (2.1.3) 75 | actionpack (>= 3.0) 76 | activesupport (>= 3.0) 77 | sprockets (~> 2.8) 78 | sqlite3 (1.3.9) 79 | thor (0.19.1) 80 | thread_safe (0.3.3) 81 | tilt (1.4.1) 82 | treetop (1.4.15) 83 | polyglot 84 | polyglot (>= 0.3.1) 85 | tzinfo (1.1.0) 86 | thread_safe (~> 0.1) 87 | 88 | PLATFORMS 89 | ruby 90 | 91 | DEPENDENCIES 92 | active_model_serializers 93 | bcrypt (~> 3.1.7) 94 | jwt-rb 95 | rails (= 4.1.1) 96 | spring 97 | sqlite3 98 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Messages 2 | 3 | `Angular Messages` is a demo application to show how to implement `JWT` authentication with a `Rails` backend and an `Angular` frontend. 4 | 5 | If you want to try it, you need to do the following: 6 | 7 | Configure and run `Rails`: 8 | 9 | ``` 10 | $ bundle install 11 | $ rails s 12 | ``` 13 | 14 | And then, `lineman` (for `Angular`): 15 | 16 | ``` 17 | $ cd frontend 18 | $ npm install 19 | $ lineman run 20 | ``` 21 | 22 | Having both application started, you can navigate to: `http://localhost:8000` 23 | 24 | [Live demo](http://quiet-inlet-7398.herokuapp.com/) - Register yourself or try with: `user@example.com / 123123` -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require turbolinks 16 | //= require_tree . 17 | -------------------------------------------------------------------------------- /app/assets/javascripts/auth.js: -------------------------------------------------------------------------------- 1 | // Place all the behaviors and hooks related to the matching controller here. 2 | // All this logic will automatically be available in application.js. 3 | -------------------------------------------------------------------------------- /app/assets/javascripts/messages.js: -------------------------------------------------------------------------------- 1 | // Place all the behaviors and hooks related to the matching controller here. 2 | // All this logic will automatically be available in application.js. 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_tree . 14 | *= require_self 15 | */ 16 | -------------------------------------------------------------------------------- /app/assets/stylesheets/auth.css: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | */ 5 | -------------------------------------------------------------------------------- /app/assets/stylesheets/messages.css: -------------------------------------------------------------------------------- 1 | /* 2 | Place all the styles related to the matching controller here. 3 | They will automatically be included in application.css. 4 | */ 5 | -------------------------------------------------------------------------------- /app/controllers/api/base_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class BaseController < ApplicationController 3 | require 'auth_token' 4 | 5 | before_action :authenticate 6 | 7 | private 8 | 9 | def authenticate 10 | begin 11 | token = request.headers['Authorization'].split(' ').last 12 | payload, header = AuthToken.valid?(token) 13 | @current_user = User.find_by(id: payload['user_id']) 14 | rescue 15 | render json: { error: 'Authorization header not valid'}, status: :unauthorized 16 | end 17 | end 18 | end 19 | end -------------------------------------------------------------------------------- /app/controllers/api/messages_controller.rb: -------------------------------------------------------------------------------- 1 | module Api 2 | class MessagesController < Api::BaseController 3 | before_action :correct_user, only: :destroy 4 | 5 | def index 6 | messages = @current_user.messages 7 | render json: messages, root: false 8 | end 9 | 10 | def create 11 | message = @current_user.messages.build(message_params) 12 | if message.save 13 | render json: message, root: false 14 | else 15 | render json: { error: 'Internal Server Error'}, status: :internal_server_error 16 | end 17 | end 18 | 19 | def destroy 20 | @message.destroy 21 | render json: {}, status: :no_content 22 | end 23 | 24 | private 25 | def message_params 26 | params.permit(:body) 27 | end 28 | 29 | def correct_user 30 | @message = @current_user.messages.find_by(id: params[:id]) 31 | render json: { error: 'Message not found' }, status: :not_found if @message.nil? 32 | end 33 | end 34 | end -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :null_session 5 | end 6 | -------------------------------------------------------------------------------- /app/controllers/auth_controller.rb: -------------------------------------------------------------------------------- 1 | class AuthController < ApplicationController 2 | require 'auth_token' 3 | 4 | def register 5 | user = User.new(user_params) 6 | if user.save 7 | token = AuthToken.issue_token({ user_id: user.id }) 8 | render json: { user: user, 9 | token: token } 10 | else 11 | render json: { errors: user.errors } 12 | end 13 | end 14 | 15 | def authenticate 16 | user = User.find_by(email: params[:email].downcase) 17 | if user && user.authenticate(params[:password]) 18 | token = AuthToken.issue_token({ user_id: user.id }) 19 | render json: { user: user, 20 | token: token } 21 | else 22 | render json: { error: "Invalid email/password combination" }, status: :unauthorized 23 | end 24 | end 25 | 26 | def token_status 27 | token = params[:token] 28 | if AuthToken.valid? token 29 | head 200 30 | else 31 | head 401 32 | end 33 | end 34 | 35 | private 36 | 37 | def user_params 38 | params.permit(:email, :password, :password_confirmation) 39 | end 40 | end -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/auth_helper.rb: -------------------------------------------------------------------------------- 1 | module AuthHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/messages_helper.rb: -------------------------------------------------------------------------------- 1 | module MessagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/app/mailers/.keep -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/app/models/.keep -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/message.rb: -------------------------------------------------------------------------------- 1 | class Message < ActiveRecord::Base 2 | belongs_to :user 3 | 4 | validates :body, presence: true 5 | end 6 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | class User < ActiveRecord::Base 2 | before_save { self.email = email.downcase } 3 | VALID_EMAIL_REGEX = /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i 4 | validates :email, presence: true, format: { with: VALID_EMAIL_REGEX }, uniqueness: { case_sensitive: false } 5 | 6 | has_many :messages 7 | 8 | has_secure_password 9 | 10 | def as_json(options={}) 11 | options[:except] ||= [:password_digest, :created_at, :updated_at] 12 | super(options) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/serializers/message_serializer.rb: -------------------------------------------------------------------------------- 1 | class MessageSerializer < ActiveModel::Serializer 2 | attributes :id, :body 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/user_serializer.rb: -------------------------------------------------------------------------------- 1 | class UserSerializer < ActiveModel::Serializer 2 | attributes :id, :email 3 | end 4 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RailsJwt 5 | <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> 6 | <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/views/messages/index.html.erb: -------------------------------------------------------------------------------- 1 |

Messages#index

2 |

Find me in app/views/messages/index.html.erb

3 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | APP_PATH = File.expand_path('../../config/application', __FILE__) 7 | require_relative '../config/boot' 8 | require 'rails/commands' 9 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path("../spring", __FILE__) 4 | rescue LoadError 5 | end 6 | require_relative '../config/boot' 7 | require 'rake' 8 | Rake.application.run 9 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast 4 | # It gets overwritten when you run the `spring binstub` command 5 | 6 | unless defined?(Spring) 7 | require "rubygems" 8 | require "bundler" 9 | 10 | if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m) 11 | ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) 12 | ENV["GEM_HOME"] = "" 13 | Gem.paths = ENV 14 | 15 | gem "spring", match[1] 16 | require "spring/binstub" 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | # Pick the frameworks you want: 4 | require "active_model/railtie" 5 | require "active_record/railtie" 6 | require "action_controller/railtie" 7 | require "action_mailer/railtie" 8 | require "action_view/railtie" 9 | # require "sprockets/railtie" 10 | # require "rails/test_unit/railtie" 11 | 12 | # Require the gems listed in Gemfile, including any gems 13 | # you've limited to :test, :development, or :production. 14 | Bundler.require(*Rails.groups) 15 | 16 | module RailsJwt 17 | class Application < Rails::Application 18 | # Settings in config/environments/* take precedence over those specified here. 19 | # Application configuration should go into files in config/initializers 20 | # -- all .rb files in that directory are automatically loaded. 21 | 22 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 23 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 24 | # config.time_zone = 'Central Time (US & Canada)' 25 | 26 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 27 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 28 | # config.i18n.default_locale = :de 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 5 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | # 7 | default: &default 8 | adapter: sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | development: 13 | <<: *default 14 | database: db/development.sqlite3 15 | 16 | # Warning: The database defined as "test" will be erased and 17 | # re-generated from your development database when you run "rake". 18 | # Do not set this db to the same as development or production. 19 | test: 20 | <<: *default 21 | database: db/test.sqlite3 22 | 23 | production: 24 | <<: *default 25 | database: db/production.sqlite3 26 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations. 23 | config.active_record.migration_error = :page_load 24 | 25 | 26 | # Raises error for missing translations 27 | # config.action_view.raise_on_missing_translations = true 28 | end 29 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. 20 | # config.action_dispatch.rack_cache = true 21 | 22 | # Disable Rails's static asset server (Apache or nginx will already do this). 23 | config.serve_static_assets = false 24 | 25 | 26 | # Specifies the header that your server uses for sending files. 27 | # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache 28 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx 29 | 30 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 31 | # config.force_ssl = true 32 | 33 | # Set to :debug to see everything in the log. 34 | config.log_level = :info 35 | 36 | # Prepend all log lines with the following tags. 37 | # config.log_tags = [ :subdomain, :uuid ] 38 | 39 | # Use a different logger for distributed setups. 40 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 41 | 42 | # Use a different cache store in production. 43 | # config.cache_store = :mem_cache_store 44 | 45 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 46 | # config.action_controller.asset_host = "http://assets.example.com" 47 | 48 | 49 | # Ignore bad email addresses and do not raise email delivery errors. 50 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 51 | # config.action_mailer.raise_delivery_errors = false 52 | 53 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 54 | # the I18n.default_locale when a translation cannot be found). 55 | config.i18n.fallbacks = true 56 | 57 | # Send deprecation notices to registered listeners. 58 | config.active_support.deprecation = :notify 59 | 60 | # Disable automatic flushing of the log to improve performance. 61 | # config.autoflush_log = false 62 | 63 | # Use default logging formatter so that PID and timestamp are not suppressed. 64 | config.log_formatter = ::Logger::Formatter.new 65 | 66 | # Do not dump schema after migrations. 67 | config.active_record.dump_schema_after_migration = false 68 | end 69 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | config.serve_static_assets = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Print deprecation notices to the stderr. 35 | config.active_support.deprecation = :stderr 36 | 37 | # Raises error for missing translations 38 | # config.action_view.raise_on_missing_translations = true 39 | end 40 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_rails-jwt_session' 4 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | namespace :api do 3 | resources :messages, only: [:index, :create, :destroy] 4 | end 5 | 6 | match '/auth/register', to: 'auth#register', via: 'post' 7 | match '/auth/authenticate', to: 'auth#authenticate', via: 'post' 8 | match '/auth/token_status', to: 'auth#token_status', via: 'get' 9 | end -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: b58545ce415c91fd707c9e8cd9e5e050ea8c0a158663f9e3f2b3bcad2a01f69d7817d4ffcbb0805c602aa9ca44dc6f66d36cca0a447a931fdd657a42dc367d96 15 | 16 | test: 17 | secret_key_base: cdf8ce630e4529c556dfd7a65265fe228e4862a0096a15e9028a18738f8899a75cdb1d7bcdc3b671e31a0a00eda5ebd90d90a1d49552ef260c22866d51c051ef 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /db/migrate/20140524220643_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :email 5 | t.string :password_digest 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20140525120921_create_messages.rb: -------------------------------------------------------------------------------- 1 | class CreateMessages < ActiveRecord::Migration 2 | def change 3 | create_table :messages do |t| 4 | t.string :body 5 | t.references :user, index: true 6 | 7 | t.timestamps 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # This file is auto-generated from the current state of the database. Instead 3 | # of editing this file, please use the migrations feature of Active Record to 4 | # incrementally modify your database, and then regenerate this schema definition. 5 | # 6 | # Note that this schema.rb definition is the authoritative source for your 7 | # database schema. If you need to create the application database on another 8 | # system, you should be using db:schema:load, not running all the migrations 9 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 10 | # you'll amass, the slower it'll run and the greater likelihood for issues). 11 | # 12 | # It's strongly recommended that you check this file into your version control system. 13 | 14 | ActiveRecord::Schema.define(version: 20140525120921) do 15 | 16 | create_table "messages", force: true do |t| 17 | t.string "body" 18 | t.integer "user_id" 19 | t.datetime "created_at" 20 | t.datetime "updated_at" 21 | end 22 | 23 | add_index "messages", ["user_id"], name: "index_messages_on_user_id" 24 | 25 | create_table "users", force: true do |t| 26 | t.string "email" 27 | t.string "password_digest" 28 | t.datetime "created_at" 29 | t.datetime "updated_at" 30 | end 31 | 32 | end 33 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | #ignore node_modules, as the node project is not "deployed" per se: http://www.mikealrogers.com/posts/nodemodules-in-git.html 4 | /node_modules 5 | 6 | /dist 7 | /generated 8 | 9 | .sass-cache 10 | -------------------------------------------------------------------------------- /frontend/.npmignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | #ignore node_modules, as the node project is not "deployed" per se: http://www.mikealrogers.com/posts/nodemodules-in-git.html 4 | /node_modules 5 | 6 | /dist 7 | /generated 8 | 9 | .sass-cache 10 | -------------------------------------------------------------------------------- /frontend/.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.10 4 | script: "lineman spec-ci" 5 | -------------------------------------------------------------------------------- /frontend/Gruntfile.js: -------------------------------------------------------------------------------- 1 | /*global module:false*/ 2 | module.exports = function(grunt) { 3 | require('./config/lineman').config.grunt.run(grunt); 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # My Lineman Application -------------------------------------------------------------------------------- /frontend/app/css/main.less: -------------------------------------------------------------------------------- 1 | /********** 2 | * COLORS 3 | *********/ 4 | @white: #FFFFFF; 5 | @green: #9EC03B; 6 | @orange: #F48744; 7 | @blue: #189ECA; 8 | @gray: #777574; 9 | @lightGray: #ececec; 10 | @black: #1C1C1C; 11 | @darkBlue: #106E8D; 12 | @darkGray: #4A4A4A; // paragraphs 13 | @red: #D16565; // error 14 | 15 | .jumbotron { 16 | text-align: center; 17 | } 18 | 19 | .jumbotron h2 { 20 | font-size: 1.5em; 21 | letter-spacing: -1px; 22 | margin-bottom: 30px; 23 | text-align: center; 24 | font-weight: normal; 25 | color: gray; 26 | } 27 | 28 | .form-signin { 29 | max-width: 30%; 30 | padding: 19px 29px 29px; 31 | margin: 0 auto 20px; 32 | margin-top: 20px; 33 | background-color: @lightGray; 34 | border: 1px solid @green; 35 | -webkit-border-radius: 15px; 36 | -moz-border-radius: 15px; 37 | border-radius: 15px; 38 | 39 | h2 { 40 | text-align: center; 41 | margin-top: 0px; 42 | } 43 | } 44 | 45 | .form-signin input[type="text"], 46 | .form-signin input[type="email"], 47 | .form-signin input[type="password"] { 48 | font-size: 16px; 49 | height: auto; 50 | margin-bottom: 15px; 51 | padding: 7px 9px; 52 | } 53 | 54 | .alert { 55 | max-width: 30%; 56 | margin: 0 auto 20px; 57 | } 58 | 59 | .delete-button { 60 | cursor: pointer; 61 | }// Main less file for your application. 62 | // Use `@import` to use other less files 63 | // relative to 'app/css' or 'vendor/css'. 64 | -------------------------------------------------------------------------------- /frontend/app/css/style.css: -------------------------------------------------------------------------------- 1 | .hello { 2 | background-color: #efefef; 3 | border: 1px solid #dedede; 4 | } 5 | -------------------------------------------------------------------------------- /frontend/app/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/frontend/app/img/.gitkeep -------------------------------------------------------------------------------- /frontend/app/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/frontend/app/js/.gitkeep -------------------------------------------------------------------------------- /frontend/app/js/app.js: -------------------------------------------------------------------------------- 1 | angular.module('app', ['ui.bootstrap', 'ui.router', 'ngMessages']) 2 | .run(function($rootScope, $state, Auth) { 3 | $rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams) { 4 | if (!Auth.authorize(toState.data.access)) { 5 | event.preventDefault(); 6 | 7 | $state.go('anon.login'); 8 | } 9 | }); 10 | }); -------------------------------------------------------------------------------- /frontend/app/js/controllers/loginController.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .controller('LoginController', function($scope, $state, Auth) { 3 | $scope.errors = []; 4 | 5 | $scope.login = function() { 6 | if ($scope.loginForm.$valid) { 7 | $scope.errors = []; 8 | Auth.login($scope.user).success(function(result) { 9 | $state.go('user.messages'); 10 | }).error(function(err) { 11 | $scope.errors.push(err); 12 | }); 13 | } 14 | }; 15 | }); -------------------------------------------------------------------------------- /frontend/app/js/controllers/messagesController.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .controller('MessagesController', function($scope, Messages, LocalService) { 3 | Messages.getAll().then(function(result) { 4 | $scope.messages = result.data; 5 | }); 6 | 7 | $scope.createMessage = function() { 8 | if ($scope.newMessageForm.$valid) { 9 | Messages.create($scope.newMessage).then(function(result) { 10 | $scope.messages.push(result.data); 11 | $scope.newMessage = ''; 12 | }); 13 | } 14 | }; 15 | 16 | $scope.deleteMessage = function(message) { 17 | Messages.remove(message).success(function() { 18 | var messageIndex = $scope.messages.indexOf(message); 19 | $scope.messages.splice(messageIndex, 1); 20 | }); 21 | }; 22 | }); -------------------------------------------------------------------------------- /frontend/app/js/controllers/navController.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .controller('NavController', function($scope, Auth, CurrentUser) { 3 | $scope.isCollapsed = true; 4 | $scope.auth = Auth; 5 | $scope.user = CurrentUser.user; 6 | 7 | $scope.logout = function() { 8 | Auth.logout(); 9 | }; 10 | }); -------------------------------------------------------------------------------- /frontend/app/js/controllers/registerController.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .controller('RegisterController', function($scope, $state, Auth) { 3 | $scope.register = function() { 4 | Auth.register($scope.user).then(function() { 5 | $state.go('anon.home'); 6 | }); 7 | }; 8 | }); -------------------------------------------------------------------------------- /frontend/app/js/routes.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .config(function($stateProvider, $urlRouterProvider, AccessLevels) { 3 | 4 | $stateProvider 5 | .state('anon', { 6 | abstract: true, 7 | template: '', 8 | data: { 9 | access: AccessLevels.anon 10 | } 11 | }) 12 | .state('anon.home', { 13 | url: '/', 14 | templateUrl: 'home.html' 15 | }) 16 | .state('anon.login', { 17 | url: '/login', 18 | templateUrl: 'auth/login.html', 19 | controller: 'LoginController' 20 | }) 21 | .state('anon.register', { 22 | url: '/register', 23 | templateUrl: 'auth/register.html', 24 | controller: 'RegisterController' 25 | }); 26 | 27 | $stateProvider 28 | .state('user', { 29 | abstract: true, 30 | template: '', 31 | data: { 32 | access: AccessLevels.user 33 | } 34 | }) 35 | .state('user.messages', { 36 | url: '/messages', 37 | templateUrl: 'user/messages.html', 38 | controller: 'MessagesController' 39 | }); 40 | 41 | $urlRouterProvider.otherwise('/'); 42 | }); -------------------------------------------------------------------------------- /frontend/app/js/services/accessLevels.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .constant('AccessLevels', { 3 | anon: 0, 4 | user: 1 5 | }); -------------------------------------------------------------------------------- /frontend/app/js/services/auth.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .factory('Auth', function($http, LocalService, AccessLevels) { 3 | function checkTokenStatus(token) { 4 | $http.get('/auth/token_status?token=' + token); 5 | } 6 | 7 | var token = LocalService.get('auth_token'); 8 | 9 | if (token) { 10 | token = angular.fromJson(LocalService.get('auth_token')).token; 11 | checkTokenStatus(token); 12 | } 13 | 14 | return { 15 | authorize: function(access) { 16 | if (access === AccessLevels.user) { 17 | return this.isAuthenticated(); 18 | } else { 19 | return true; 20 | } 21 | }, 22 | isAuthenticated: function() { 23 | return LocalService.get('auth_token'); 24 | }, 25 | login: function(credentials) { 26 | var login = $http.post('/auth/authenticate', credentials); 27 | login.success(function(result) { 28 | LocalService.set('auth_token', JSON.stringify(result)); 29 | }); 30 | return login; 31 | }, 32 | logout: function() { 33 | // The backend doesn't care about logouts, delete the token and you're good to go. 34 | LocalService.unset('auth_token'); 35 | }, 36 | register: function(formData) { 37 | LocalService.unset('auth_token'); 38 | var register = $http.post('/auth/register', formData); 39 | register.success(function(result) { 40 | LocalService.set('auth_token', JSON.stringify(result)); 41 | }); 42 | return register; 43 | } 44 | }; 45 | }) 46 | .factory('AuthInterceptor', function($q, $injector) { 47 | var LocalService = $injector.get('LocalService'); 48 | 49 | return { 50 | request: function(config) { 51 | var token; 52 | if (LocalService.get('auth_token')) { 53 | token = angular.fromJson(LocalService.get('auth_token')).token; 54 | } 55 | if (token) { 56 | config.headers.Authorization = 'Bearer ' + token; 57 | } 58 | return config; 59 | }, 60 | responseError: function(response) { 61 | if (response.status === 401 || response.status === 403) { 62 | LocalService.unset('auth_token'); 63 | $injector.get('$state').go('anon.login'); 64 | } 65 | return $q.reject(response); 66 | } 67 | }; 68 | }) 69 | .config(function($httpProvider) { 70 | $httpProvider.interceptors.push('AuthInterceptor'); 71 | }); -------------------------------------------------------------------------------- /frontend/app/js/services/currentUser.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .factory('CurrentUser', function(LocalService) { 3 | return { 4 | user: function() { 5 | if (LocalService.get('auth_token')) { 6 | return angular.fromJson(LocalService.get('auth_token')).user; 7 | } else { 8 | return {}; 9 | } 10 | } 11 | }; 12 | }); -------------------------------------------------------------------------------- /frontend/app/js/services/localStorage.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .factory('LocalService', function() { 3 | return { 4 | get: function(key) { 5 | return localStorage.getItem(key); 6 | }, 7 | set: function(key, val) { 8 | return localStorage.setItem(key, val); 9 | }, 10 | unset: function(key) { 11 | return localStorage.removeItem(key); 12 | } 13 | }; 14 | }); -------------------------------------------------------------------------------- /frontend/app/js/services/messagesRepository.js: -------------------------------------------------------------------------------- 1 | angular.module('app') 2 | .factory('Messages', function($http) { 3 | return { 4 | getAll: function() { 5 | return $http.get('/api/messages'); 6 | }, 7 | create: function(message) { 8 | return $http.post('/api/messages', {body: message}); 9 | }, 10 | remove: function(message) { 11 | return $http.delete('/api/messages/' + message.id); 12 | } 13 | }; 14 | }); -------------------------------------------------------------------------------- /frontend/app/pages/index.us: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= pkg.name %> 5 | 6 | 7 | 8 | 9 | 36 | 37 |
38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /frontend/app/static/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/frontend/app/static/favicon.ico -------------------------------------------------------------------------------- /frontend/app/templates/auth/login.html: -------------------------------------------------------------------------------- 1 | {{error.error}} 2 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/app/templates/auth/register.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/app/templates/home.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |

Angular messages!

4 | 5 |

Welcome to our demo of Sails + Angular + JWT

6 | Try it! 7 |
8 |
-------------------------------------------------------------------------------- /frontend/app/templates/user/messages.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
    4 |
  • 5 | {{message.body}} 6 | 7 | X 8 | 9 |
  • 10 |
11 | 12 |
13 |
14 | 15 | 16 | 17 | 18 |
19 |
20 |
21 |
-------------------------------------------------------------------------------- /frontend/config/application.js: -------------------------------------------------------------------------------- 1 | /* Exports a function which returns an object that overrides the default & 2 | * plugin grunt configuration object. 3 | * 4 | * You can familiarize yourself with Lineman's defaults by checking out: 5 | * 6 | * - https://github.com/linemanjs/lineman/blob/master/config/application.coffee 7 | * - https://github.com/linemanjs/lineman/blob/master/config/plugins 8 | * 9 | * You can also ask Lineman's about config from the command line: 10 | * 11 | * $ lineman config #=> to print the entire config 12 | * $ lineman config concat_sourcemap.js #=> to see the JS config for the concat task. 13 | */ 14 | module.exports = function(lineman) { 15 | //Override application configuration here. Common examples follow in the comments. 16 | return { 17 | 18 | // API Proxying 19 | // 20 | // During development, you'll likely want to make XHR (AJAX) requests to an API on the same 21 | // port as your lineman development server. By enabling the API proxy and setting the port, all 22 | // requests for paths that don't match a static asset in ./generated will be forwarded to 23 | // whatever service might be running on the specified port. 24 | // 25 | server: { 26 | apiProxy: { 27 | enabled: true, 28 | host: 'localhost', 29 | port: 3000 30 | } 31 | } 32 | 33 | // Sass 34 | // 35 | // Lineman supports Sass via grunt-contrib-sass, which requires you first 36 | // have Ruby installed as well as the `sass` gem. To enable it, comment out the 37 | // following line: 38 | // 39 | // enableSass: true 40 | 41 | // Asset Fingerprints 42 | // 43 | // Lineman can fingerprint your static assets by appending a hash to the filename 44 | // and logging a manifest of logical-to-hashed filenames in dist/assets.json 45 | // via grunt-asset-fingerprint 46 | // 47 | // enableAssetFingerprint: true 48 | 49 | }; 50 | }; 51 | -------------------------------------------------------------------------------- /frontend/config/files.js: -------------------------------------------------------------------------------- 1 | /* Exports a function which returns an object that overrides the default & 2 | * plugin file patterns (used widely through the app configuration) 3 | * 4 | * To see the default definitions for Lineman's file paths and globs, see: 5 | * 6 | * - https://github.com/linemanjs/lineman/blob/master/config/files.coffee 7 | */ 8 | module.exports = function(lineman) { 9 | //Override file patterns here 10 | return { 11 | js: { 12 | vendor: [ 13 | "vendor/js/angular.js", 14 | "vendor/js/**/*.js" 15 | ], 16 | app: [ 17 | "app/js/app.js", 18 | "app/js/**/*.js" 19 | ] 20 | }, 21 | less: { 22 | compile: { 23 | options: { 24 | paths: ["vendor/css/normalize.css", "vendor/css/**/*.css", "app/css/**/*.less"] 25 | } 26 | } 27 | } 28 | }; 29 | }; 30 | -------------------------------------------------------------------------------- /frontend/config/lineman.js: -------------------------------------------------------------------------------- 1 | module.exports = require(process.env['LINEMAN_MAIN']); 2 | -------------------------------------------------------------------------------- /frontend/config/server.js: -------------------------------------------------------------------------------- 1 | /* Define custom server-side HTTP routes for lineman's development server 2 | * These might be as simple as stubbing a little JSON to 3 | * facilitate development of code that interacts with an HTTP service 4 | * (presumably, mirroring one that will be reachable in a live environment). 5 | * 6 | * It's important to remember that any custom endpoints defined here 7 | * will only be available in development, as lineman only builds 8 | * static assets, it can't run server-side code. 9 | * 10 | * This file can be very useful for rapid prototyping or even organically 11 | * defining a spec based on the needs of the client code that emerge. 12 | * 13 | */ 14 | 15 | module.exports = { 16 | drawRoutes: function(app) { 17 | // app.get('/api/greeting/:message', function(req, res){ 18 | // res.json({ message: "OK, "+req.params.message }); 19 | // }); 20 | } 21 | }; -------------------------------------------------------------------------------- /frontend/config/spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework" : "jasmine", 3 | "launch_in_dev" : ["Chrome"], 4 | "launch_in_ci" : ["PhantomJS"], 5 | "src_files" : [ 6 | "generated/js/app.js", 7 | "generated/js/spec.js" 8 | ] 9 | } -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "jwt-example", 3 | "description": "An Angular frontend for JWT example", 4 | "version": "0.0.1", 5 | "private": true, 6 | "author": "Jesus Rodriguez", 7 | "devDependencies": { 8 | "lineman": "~0.30.0", 9 | "lineman-angular": "~0.2.0", 10 | "lineman-less": "0.0.1" 11 | }, 12 | "scripts": { 13 | "start": "lineman run", 14 | "test": "lineman spec-ci" 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /frontend/spec/hello-spec.js: -------------------------------------------------------------------------------- 1 | describe(".helloText", function(){ 2 | When(function(){ this.result = helloText(); }); 3 | Then(function(){ expect(this.result).toEqual("Hello, World!"); }); 4 | }); 5 | -------------------------------------------------------------------------------- /frontend/spec/helpers/helper.js: -------------------------------------------------------------------------------- 1 | var root = this; 2 | 3 | root.context = root.describe; 4 | root.xcontext = root.xdescribe; -------------------------------------------------------------------------------- /frontend/spec/helpers/jasmine-fixture.js: -------------------------------------------------------------------------------- 1 | /* jasmine-fixture - 1.2.0 2 | * Makes injecting HTML snippets into the DOM easy & clean! 3 | * https://github.com/searls/jasmine-fixture 4 | */ 5 | (function() { 6 | var createHTMLBlock, 7 | __slice = [].slice; 8 | 9 | (function($) { 10 | var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref; 11 | root = this; 12 | originalJasmineFixture = root.jasmineFixture; 13 | originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0; 14 | originalAffix = root.affix; 15 | _ = function(list) { 16 | return { 17 | inject: function(iterator, memo) { 18 | var item, _i, _len, _results; 19 | _results = []; 20 | for (_i = 0, _len = list.length; _i < _len; _i++) { 21 | item = list[_i]; 22 | _results.push(memo = iterator(memo, item)); 23 | } 24 | return _results; 25 | } 26 | }; 27 | }; 28 | root.jasmineFixture = function($) { 29 | var $whatsTheRootOf, affix, create, jasmineFixture, noConflict; 30 | affix = function(selectorOptions) { 31 | return create.call(this, selectorOptions, true); 32 | }; 33 | create = function(selectorOptions, attach) { 34 | var $top; 35 | $top = null; 36 | _(selectorOptions.split(/[ ](?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) { 37 | var $el; 38 | if (elementSelector === ">") { 39 | return $parent; 40 | } 41 | $el = createHTMLBlock($, elementSelector); 42 | if (attach || $top) { 43 | $el.appendTo($parent); 44 | } 45 | $top || ($top = $el); 46 | return $el; 47 | }, $whatsTheRootOf(this)); 48 | return $top; 49 | }; 50 | noConflict = function() { 51 | var currentJasmineFixture, _ref1; 52 | currentJasmineFixture = jasmine.fixture; 53 | root.jasmineFixture = originalJasmineFixture; 54 | if ((_ref1 = root.jasmine) != null) { 55 | _ref1.fixture = originalJasmineDotFixture; 56 | } 57 | root.affix = originalAffix; 58 | return currentJasmineFixture; 59 | }; 60 | $whatsTheRootOf = function(that) { 61 | if (that.jquery != null) { 62 | return that; 63 | } else if ($('#jasmine_content').length > 0) { 64 | return $('#jasmine_content'); 65 | } else { 66 | return $('
').appendTo('body'); 67 | } 68 | }; 69 | jasmineFixture = { 70 | affix: affix, 71 | create: create, 72 | noConflict: noConflict 73 | }; 74 | ewwSideEffects(jasmineFixture); 75 | return jasmineFixture; 76 | }; 77 | ewwSideEffects = function(jasmineFixture) { 78 | var _ref1; 79 | if ((_ref1 = root.jasmine) != null) { 80 | _ref1.fixture = jasmineFixture; 81 | } 82 | $.fn.affix = root.affix = jasmineFixture.affix; 83 | return afterEach(function() { 84 | return $('#jasmine_content').remove(); 85 | }); 86 | }; 87 | if ($) { 88 | return jasmineFixture = root.jasmineFixture($); 89 | } else { 90 | return root.affix = function() { 91 | var nowJQueryExists; 92 | nowJQueryExists = window.jQuery || window.$; 93 | if (nowJQueryExists != null) { 94 | jasmineFixture = root.jasmineFixture(nowJQueryExists); 95 | return affix.call.apply(affix, [this].concat(__slice.call(arguments))); 96 | } else { 97 | throw new Error("jasmine-fixture requires jQuery to be defined at window.jQuery or window.$"); 98 | } 99 | }; 100 | } 101 | })(window.jQuery || window.$); 102 | 103 | createHTMLBlock = (function() { 104 | var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn; 105 | createHTMLBlock = function($, ZenObject, data, functions, indexes) { 106 | var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo; 107 | if ($.isPlainObject(ZenObject)) { 108 | ZenCode = ZenObject.main; 109 | } else { 110 | ZenCode = ZenObject; 111 | ZenObject = { 112 | main: ZenCode 113 | }; 114 | } 115 | origZenCode = ZenCode; 116 | if (indexes === undefined) { 117 | indexes = {}; 118 | } 119 | if (ZenCode.charAt(0) === "!" || $.isArray(data)) { 120 | if ($.isArray(data)) { 121 | forScope = ZenCode; 122 | } else { 123 | obj = parseEnclosure(ZenCode, "!"); 124 | obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1); 125 | forScope = parseVariableScope(ZenCode); 126 | } 127 | while (forScope.charAt(0) === "@") { 128 | forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject)); 129 | } 130 | zo = ZenObject; 131 | zo.main = forScope; 132 | el = $(); 133 | if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) { 134 | if (!$.isArray(data) && obj.indexOf(":") > 0) { 135 | indexName = obj.substring(0, obj.indexOf(":")); 136 | obj = obj.substr(obj.indexOf(":") + 1); 137 | } 138 | arr = ($.isArray(data) ? data : data[obj]); 139 | zc = zo.main; 140 | if ($.isArray(arr) || $.isPlainObject(arr)) { 141 | $.map(arr, function(value, index) { 142 | var next; 143 | zo.main = zc; 144 | if (indexName !== undefined) { 145 | indexes[indexName] = index; 146 | } 147 | if (!$.isPlainObject(value)) { 148 | value = { 149 | value: value 150 | }; 151 | } 152 | next = createHTMLBlock($, zo, value, functions, indexes); 153 | if (el.length !== 0) { 154 | return $.each(next, function(index, value) { 155 | return el.push(value); 156 | }); 157 | } 158 | }); 159 | } 160 | if (!$.isArray(data)) { 161 | ZenCode = ZenCode.substr(obj.length + 6 + forScope.length); 162 | } else { 163 | ZenCode = ""; 164 | } 165 | } else if (ZenCode.substring(0, 4) === "!if:") { 166 | result = parseContents("!" + obj + "!", data, indexes); 167 | if (result !== "undefined" || result !== "false" || result !== "") { 168 | el = createHTMLBlock($, zo, data, functions, indexes); 169 | } 170 | ZenCode = ZenCode.substr(obj.length + 5 + forScope.length); 171 | } 172 | ZenObject.main = ZenCode; 173 | } else if (ZenCode.charAt(0) === "(") { 174 | paren = parseEnclosure(ZenCode, "(", ")"); 175 | inner = paren.substring(1, paren.length - 1); 176 | ZenCode = ZenCode.substr(paren.length); 177 | zo = ZenObject; 178 | zo.main = inner; 179 | el = createHTMLBlock($, zo, data, functions, indexes); 180 | } else { 181 | blocks = ZenCode.match(regZenTagDfn); 182 | block = blocks[0]; 183 | if (block.length === 0) { 184 | return ""; 185 | } 186 | if (block.indexOf("@") >= 0) { 187 | ZenCode = parseReferences(ZenCode, ZenObject); 188 | zo = ZenObject; 189 | zo.main = ZenCode; 190 | return createHTMLBlock($, zo, data, functions, indexes); 191 | } 192 | block = parseContents(block, data, indexes); 193 | blockClasses = parseClasses($, block); 194 | if (regId.test(block)) { 195 | blockId = regId.exec(block)[1]; 196 | } 197 | blockAttrs = parseAttributes(block, data); 198 | blockTag = (block.charAt(0) === "{" ? "span" : "div"); 199 | if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") { 200 | blockTag = regTag.exec(block)[1]; 201 | } 202 | if (block.search(regCBrace) !== -1) { 203 | blockHTML = block.match(regCBrace)[1]; 204 | } 205 | blockAttrs = $.extend(blockAttrs, { 206 | id: blockId, 207 | "class": blockClasses, 208 | html: blockHTML 209 | }); 210 | el = $("<" + blockTag + ">", blockAttrs); 211 | el.attr(blockAttrs); 212 | el = bindEvents(block, el, functions); 213 | el = bindData(block, el, data); 214 | ZenCode = ZenCode.substr(blocks[0].length); 215 | ZenObject.main = ZenCode; 216 | } 217 | if (ZenCode.length > 0) { 218 | if (ZenCode.charAt(0) === ">") { 219 | if (ZenCode.charAt(1) === "(") { 220 | zc = parseEnclosure(ZenCode.substr(1), "(", ")"); 221 | ZenCode = ZenCode.substr(zc.length + 1); 222 | } else if (ZenCode.charAt(1) === "!") { 223 | obj = parseEnclosure(ZenCode.substr(1), "!"); 224 | forScope = parseVariableScope(ZenCode.substr(1)); 225 | zc = obj + forScope; 226 | ZenCode = ZenCode.substr(zc.length + 1); 227 | } else { 228 | len = Math.max(ZenCode.indexOf("+"), ZenCode.length); 229 | zc = ZenCode.substring(1, len); 230 | ZenCode = ZenCode.substr(len); 231 | } 232 | zo = ZenObject; 233 | zo.main = zc; 234 | els = $(createHTMLBlock($, zo, data, functions, indexes)); 235 | els.appendTo(el); 236 | } 237 | if (ZenCode.charAt(0) === "+") { 238 | zo = ZenObject; 239 | zo.main = ZenCode.substr(1); 240 | el2 = createHTMLBlock($, zo, data, functions, indexes); 241 | $.each(el2, function(index, value) { 242 | return el.push(value); 243 | }); 244 | } 245 | } 246 | ret = el; 247 | return ret; 248 | }; 249 | bindData = function(ZenCode, el, data) { 250 | var datas, i, split; 251 | if (ZenCode.search(regDatas) === 0) { 252 | return el; 253 | } 254 | datas = ZenCode.match(regDatas); 255 | if (datas === null) { 256 | return el; 257 | } 258 | i = 0; 259 | while (i < datas.length) { 260 | split = regData.exec(datas[i]); 261 | if (split[3] === undefined) { 262 | $(el).data(split[1], data[split[1]]); 263 | } else { 264 | $(el).data(split[1], data[split[3]]); 265 | } 266 | i++; 267 | } 268 | return el; 269 | }; 270 | bindEvents = function(ZenCode, el, functions) { 271 | var bindings, fn, i, split; 272 | if (ZenCode.search(regEvents) === 0) { 273 | return el; 274 | } 275 | bindings = ZenCode.match(regEvents); 276 | if (bindings === null) { 277 | return el; 278 | } 279 | i = 0; 280 | while (i < bindings.length) { 281 | split = regEvent.exec(bindings[i]); 282 | if (split[2] === undefined) { 283 | fn = functions[split[1]]; 284 | } else { 285 | fn = functions[split[2]]; 286 | } 287 | $(el).bind(split[1], fn); 288 | i++; 289 | } 290 | return el; 291 | }; 292 | parseAttributes = function(ZenBlock, data) { 293 | var attrStrs, attrs, i, parts; 294 | if (ZenBlock.search(regAttrDfn) === -1) { 295 | return undefined; 296 | } 297 | attrStrs = ZenBlock.match(regAttrDfn); 298 | attrs = {}; 299 | i = 0; 300 | while (i < attrStrs.length) { 301 | parts = regAttr.exec(attrStrs[i]); 302 | attrs[parts[1]] = ""; 303 | if (parts[3] !== undefined) { 304 | attrs[parts[1]] = parseContents(parts[3], data); 305 | } 306 | i++; 307 | } 308 | return attrs; 309 | }; 310 | parseClasses = function($, ZenBlock) { 311 | var classes, clsString, i; 312 | ZenBlock = ZenBlock.match(regTagNotContent)[0]; 313 | if (ZenBlock.search(regClasses) === -1) { 314 | return undefined; 315 | } 316 | classes = ZenBlock.match(regClasses); 317 | clsString = ""; 318 | i = 0; 319 | while (i < classes.length) { 320 | clsString += " " + regClass.exec(classes[i])[1]; 321 | i++; 322 | } 323 | return $.trim(clsString); 324 | }; 325 | parseContents = function(ZenBlock, data, indexes) { 326 | var html; 327 | if (indexes === undefined) { 328 | indexes = {}; 329 | } 330 | html = ZenBlock; 331 | if (data === undefined) { 332 | return html; 333 | } 334 | while (regExclamation.test(html)) { 335 | html = html.replace(regExclamation, function(str, str2) { 336 | var begChar, fn, val; 337 | begChar = ""; 338 | if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) { 339 | return str; 340 | } 341 | if (str.charAt(0) !== "!") { 342 | begChar = str.charAt(0); 343 | str = str.substring(2, str.length - 1); 344 | } 345 | fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;"); 346 | val = unescape(fn(data, indexes)); 347 | return begChar + val; 348 | }); 349 | } 350 | html = html.replace(/\\./g, function(str) { 351 | return str.charAt(1); 352 | }); 353 | return unescape(html); 354 | }; 355 | parseEnclosure = function(ZenCode, open, close, count) { 356 | var index, ret; 357 | if (close === undefined) { 358 | close = open; 359 | } 360 | index = 1; 361 | if (count === undefined) { 362 | count = (ZenCode.charAt(0) === open ? 1 : 0); 363 | } 364 | if (count === 0) { 365 | return; 366 | } 367 | while (count > 0 && index < ZenCode.length) { 368 | if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") { 369 | count--; 370 | } else { 371 | if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") { 372 | count++; 373 | } 374 | } 375 | index++; 376 | } 377 | ret = ZenCode.substring(0, index); 378 | return ret; 379 | }; 380 | parseReferences = function(ZenCode, ZenObject) { 381 | ZenCode = ZenCode.replace(regReference, function(str) { 382 | var fn; 383 | str = str.substr(1); 384 | fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;"); 385 | return fn(ZenObject, parseReferences); 386 | }); 387 | return ZenCode; 388 | }; 389 | parseVariableScope = function(ZenCode) { 390 | var forCode, rest, tag; 391 | if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") { 392 | return undefined; 393 | } 394 | forCode = parseEnclosure(ZenCode, "!"); 395 | ZenCode = ZenCode.substr(forCode.length); 396 | if (ZenCode.charAt(0) === "(") { 397 | return parseEnclosure(ZenCode, "(", ")"); 398 | } 399 | tag = ZenCode.match(regZenTagDfn)[0]; 400 | ZenCode = ZenCode.substr(tag.length); 401 | if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") { 402 | return tag; 403 | } else if (ZenCode.charAt(0) === ">") { 404 | rest = ""; 405 | rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1); 406 | return tag + ">" + rest; 407 | } 408 | return undefined; 409 | }; 410 | regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i; 411 | regTag = /(\w+)/i; 412 | regId = /(?:^|\b)#([\w-!]+)/i; 413 | regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i; 414 | regClasses = /(\.[\w-]+)/g; 415 | regClass = /\.([\w-]+)/i; 416 | regReference = /(@[\w$_][\w$_\d]+)/i; 417 | regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig; 418 | regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g; 419 | regAttr = /([\w-!]+)(="?((([\w]+\[.*?\])|[^"\]]|\\")+)"?)?/i; 420 | regCBrace = /\{(([^\}]|\\\})+)\}/i; 421 | regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g; 422 | regEvents = /\~[\w$]+(=[\w$]+)?/g; 423 | regEvent = /\~([\w$]+)=([\w$]+)/i; 424 | regDatas = /&[\w$]+(=[\w$]+)?/g; 425 | regData = /&([\w$]+)(=([\w$]+))?/i; 426 | return createHTMLBlock; 427 | })(); 428 | 429 | }).call(this); 430 | -------------------------------------------------------------------------------- /frontend/spec/helpers/jasmine-given.js: -------------------------------------------------------------------------------- 1 | /* jasmine-given - 2.6.1 2 | * Adds a Given-When-Then DSL to jasmine as an alternative style for specs 3 | * https://github.com/searls/jasmine-given 4 | */ 5 | /* jasmine-matcher-wrapper - 0.0.3 6 | * Wraps Jasmine 1.x matchers for use with Jasmine 2 7 | * https://github.com/testdouble/jasmine-matcher-wrapper 8 | */ 9 | (function() { 10 | var __hasProp = {}.hasOwnProperty, 11 | __slice = [].slice; 12 | 13 | (function(jasmine) { 14 | var comparatorFor, createMatcher; 15 | if (jasmine == null) { 16 | return typeof console !== "undefined" && console !== null ? console.warn("jasmine was not found. Skipping jasmine-matcher-wrapper. Verify your script load order.") : void 0; 17 | } 18 | if (jasmine.matcherWrapper != null) { 19 | return; 20 | } 21 | jasmine.matcherWrapper = { 22 | wrap: function(matchers) { 23 | var matcher, name, wrappedMatchers; 24 | if (jasmine.addMatchers == null) { 25 | return matchers; 26 | } 27 | wrappedMatchers = {}; 28 | for (name in matchers) { 29 | if (!__hasProp.call(matchers, name)) continue; 30 | matcher = matchers[name]; 31 | wrappedMatchers[name] = createMatcher(name, matcher); 32 | } 33 | return wrappedMatchers; 34 | } 35 | }; 36 | createMatcher = function(name, matcher) { 37 | return function() { 38 | return { 39 | compare: comparatorFor(matcher, false), 40 | negativeCompare: comparatorFor(matcher, true) 41 | }; 42 | }; 43 | }; 44 | return comparatorFor = function(matcher, isNot) { 45 | return function() { 46 | var actual, context, message, params, pass, _ref; 47 | actual = arguments[0], params = 2 <= arguments.length ? __slice.call(arguments, 1) : []; 48 | context = { 49 | actual: actual, 50 | isNot: isNot 51 | }; 52 | pass = matcher.apply(context, params); 53 | if (isNot) { 54 | pass = !pass; 55 | } 56 | if (!pass) { 57 | message = (_ref = context.message) != null ? _ref.apply(context, params) : void 0; 58 | } 59 | return { 60 | pass: pass, 61 | message: message 62 | }; 63 | }; 64 | }; 65 | })(jasmine || getJasmineRequireObj()); 66 | 67 | }).call(this); 68 | 69 | (function() { 70 | var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; 71 | 72 | (function(jasmine) { 73 | var Waterfall, additionalInsightsForErrorMessage, apparentReferenceError, attemptedEquality, comparisonInsight, currentSpec, declareJasmineSpec, deepEqualsNotice, doneWrapperFor, evalInContextOfSpec, finalStatementFrom, getBlock, invariantList, mostRecentExpectations, mostRecentlyUsed, o, root, stringifyExpectation, wasComparison, whenList, wrapAsExpectations; 74 | mostRecentlyUsed = null; 75 | root = this; 76 | currentSpec = null; 77 | beforeEach(function() { 78 | return currentSpec = this; 79 | }); 80 | root.Given = function() { 81 | mostRecentlyUsed = root.Given; 82 | return beforeEach(getBlock(arguments)); 83 | }; 84 | whenList = []; 85 | root.When = function() { 86 | var b; 87 | mostRecentlyUsed = root.When; 88 | b = getBlock(arguments); 89 | beforeEach(function() { 90 | return whenList.push(b); 91 | }); 92 | return afterEach(function() { 93 | return whenList.pop(); 94 | }); 95 | }; 96 | invariantList = []; 97 | root.Invariant = function() { 98 | var invariantBehavior; 99 | mostRecentlyUsed = root.Invariant; 100 | invariantBehavior = getBlock(arguments); 101 | beforeEach(function() { 102 | return invariantList.push(invariantBehavior); 103 | }); 104 | return afterEach(function() { 105 | return invariantList.pop(); 106 | }); 107 | }; 108 | getBlock = function(thing) { 109 | var assignResultTo, setupFunction; 110 | setupFunction = o(thing).firstThat(function(arg) { 111 | return o(arg).isFunction(); 112 | }); 113 | assignResultTo = o(thing).firstThat(function(arg) { 114 | return o(arg).isString(); 115 | }); 116 | return doneWrapperFor(setupFunction, function(done) { 117 | var context, result; 118 | context = currentSpec; 119 | result = setupFunction.call(context, done); 120 | if (assignResultTo) { 121 | if (!context[assignResultTo]) { 122 | return context[assignResultTo] = result; 123 | } else { 124 | throw new Error("Unfortunately, the variable '" + assignResultTo + "' is already assigned to: " + context[assignResultTo]); 125 | } 126 | } 127 | }); 128 | }; 129 | mostRecentExpectations = null; 130 | declareJasmineSpec = function(specArgs, itFunction) { 131 | var expectationFunction, expectations, label; 132 | if (itFunction == null) { 133 | itFunction = it; 134 | } 135 | label = o(specArgs).firstThat(function(arg) { 136 | return o(arg).isString(); 137 | }); 138 | expectationFunction = o(specArgs).firstThat(function(arg) { 139 | return o(arg).isFunction(); 140 | }); 141 | mostRecentlyUsed = root.subsequentThen; 142 | mostRecentExpectations = expectations = [expectationFunction]; 143 | itFunction("then " + (label != null ? label : stringifyExpectation(expectations)), function(jasmineDone) { 144 | var userCommands; 145 | userCommands = [].concat(whenList, invariantList, wrapAsExpectations(expectations)); 146 | return new Waterfall(userCommands, jasmineDone).flow(); 147 | }); 148 | return { 149 | Then: subsequentThen, 150 | And: subsequentThen 151 | }; 152 | }; 153 | wrapAsExpectations = function(expectations) { 154 | var expectation, i, _i, _len, _results; 155 | _results = []; 156 | for (i = _i = 0, _len = expectations.length; _i < _len; i = ++_i) { 157 | expectation = expectations[i]; 158 | _results.push((function(expectation, i) { 159 | return doneWrapperFor(expectation, function(maybeDone) { 160 | return expect(expectation).not.toHaveReturnedFalseFromThen(currentSpec, i + 1, maybeDone); 161 | }); 162 | })(expectation, i)); 163 | } 164 | return _results; 165 | }; 166 | doneWrapperFor = function(func, toWrap) { 167 | if (func.length === 0) { 168 | return function() { 169 | return toWrap(); 170 | }; 171 | } else { 172 | return function(done) { 173 | return toWrap(done); 174 | }; 175 | } 176 | }; 177 | root.Then = function() { 178 | return declareJasmineSpec(arguments); 179 | }; 180 | root.Then.only = function() { 181 | return declareJasmineSpec(arguments, it.only); 182 | }; 183 | root.subsequentThen = function(additionalExpectation) { 184 | mostRecentExpectations.push(additionalExpectation); 185 | return this; 186 | }; 187 | mostRecentlyUsed = root.Given; 188 | root.And = function() { 189 | return mostRecentlyUsed.apply(this, jasmine.util.argsToArray(arguments)); 190 | }; 191 | o = function(thing) { 192 | return { 193 | isFunction: function() { 194 | return Object.prototype.toString.call(thing) === "[object Function]"; 195 | }, 196 | isString: function() { 197 | return Object.prototype.toString.call(thing) === "[object String]"; 198 | }, 199 | firstThat: function(test) { 200 | var i; 201 | i = 0; 202 | while (i < thing.length) { 203 | if (test(thing[i]) === true) { 204 | return thing[i]; 205 | } 206 | i++; 207 | } 208 | return void 0; 209 | } 210 | }; 211 | }; 212 | jasmine._given = { 213 | matchers: { 214 | toHaveReturnedFalseFromThen: function(context, n, done) { 215 | var e, exception, result; 216 | result = false; 217 | exception = void 0; 218 | try { 219 | result = this.actual.call(context, done); 220 | } catch (_error) { 221 | e = _error; 222 | exception = e; 223 | } 224 | this.message = function() { 225 | var msg, stringyExpectation; 226 | stringyExpectation = stringifyExpectation(this.actual); 227 | msg = "Then clause" + (n > 1 ? " #" + n : "") + " `" + stringyExpectation + "` failed by "; 228 | if (exception) { 229 | msg += "throwing: " + exception.toString(); 230 | } else { 231 | msg += "returning false"; 232 | } 233 | msg += additionalInsightsForErrorMessage(stringyExpectation); 234 | return msg; 235 | }; 236 | return result === false; 237 | } 238 | } 239 | }; 240 | stringifyExpectation = function(expectation) { 241 | var matches; 242 | matches = expectation.toString().replace(/\n/g, '').match(/function\s?\(.*\)\s?{\s*(return\s+)?(.*?)(;)?\s*}/i); 243 | if (matches && matches.length >= 3) { 244 | return matches[2].replace(/\s+/g, ' '); 245 | } else { 246 | return ""; 247 | } 248 | }; 249 | additionalInsightsForErrorMessage = function(expectationString) { 250 | var comparison, expectation; 251 | expectation = finalStatementFrom(expectationString); 252 | if (comparison = wasComparison(expectation)) { 253 | return comparisonInsight(expectation, comparison); 254 | } else { 255 | return ""; 256 | } 257 | }; 258 | finalStatementFrom = function(expectationString) { 259 | var multiStatement; 260 | if (multiStatement = expectationString.match(/.*return (.*)/)) { 261 | return multiStatement[multiStatement.length - 1]; 262 | } else { 263 | return expectationString; 264 | } 265 | }; 266 | wasComparison = function(expectation) { 267 | var comparator, comparison, left, right, s; 268 | if (comparison = expectation.match(/(.*) (===|!==|==|!=|>|>=|<|<=) (.*)/)) { 269 | s = comparison[0], left = comparison[1], comparator = comparison[2], right = comparison[3]; 270 | return { 271 | left: left, 272 | comparator: comparator, 273 | right: right 274 | }; 275 | } 276 | }; 277 | comparisonInsight = function(expectation, comparison) { 278 | var left, msg, right; 279 | left = evalInContextOfSpec(comparison.left); 280 | right = evalInContextOfSpec(comparison.right); 281 | if (apparentReferenceError(left) && apparentReferenceError(right)) { 282 | return ""; 283 | } 284 | msg = "\n\nThis comparison was detected:\n " + expectation + "\n " + left + " " + comparison.comparator + " " + right; 285 | if (attemptedEquality(left, right, comparison.comparator)) { 286 | msg += "\n\n" + (deepEqualsNotice(comparison.left, comparison.right)); 287 | } 288 | return msg; 289 | }; 290 | apparentReferenceError = function(result) { 291 | return /^"; 302 | } 303 | }; 304 | attemptedEquality = function(left, right, comparator) { 305 | var _ref; 306 | if (!(comparator === "==" || comparator === "===")) { 307 | return false; 308 | } 309 | if (((_ref = jasmine.matchersUtil) != null ? _ref.equals : void 0) != null) { 310 | return jasmine.matchersUtil.equals(left, right); 311 | } else { 312 | return jasmine.getEnv().equals_(left, right); 313 | } 314 | }; 315 | deepEqualsNotice = function(left, right) { 316 | return "However, these items are deeply equal! Try an expectation like this instead:\n expect(" + left + ").toEqual(" + right + ")"; 317 | }; 318 | Waterfall = (function() { 319 | function Waterfall(functions, finalCallback) { 320 | var func, _i, _len, _ref; 321 | if (functions == null) { 322 | functions = []; 323 | } 324 | this.flow = __bind(this.flow, this); 325 | this.invokeFinalCallbackIfNecessary = __bind(this.invokeFinalCallbackIfNecessary, this); 326 | this.asyncTaskCompleted = __bind(this.asyncTaskCompleted, this); 327 | this.functions = functions.slice(0); 328 | this.finalCallback = finalCallback; 329 | this.asyncCount = 0; 330 | _ref = this.functions; 331 | for (_i = 0, _len = _ref.length; _i < _len; _i++) { 332 | func = _ref[_i]; 333 | if (func.length > 0) { 334 | this.asyncCount += 1; 335 | } 336 | } 337 | } 338 | 339 | Waterfall.prototype.asyncTaskCompleted = function() { 340 | this.asyncCount -= 1; 341 | return this.flow(); 342 | }; 343 | 344 | Waterfall.prototype.invokeFinalCallbackIfNecessary = function() { 345 | if (this.asyncCount === 0) { 346 | if (typeof this.finalCallback === "function") { 347 | this.finalCallback(); 348 | } 349 | return this.finalCallback = void 0; 350 | } 351 | }; 352 | 353 | Waterfall.prototype.flow = function() { 354 | var func; 355 | if (this.functions.length === 0) { 356 | return this.invokeFinalCallbackIfNecessary(); 357 | } 358 | func = this.functions.shift(); 359 | if (func.length > 0) { 360 | return func(this.asyncTaskCompleted); 361 | } else { 362 | func(); 363 | return this.flow(); 364 | } 365 | }; 366 | 367 | return Waterfall; 368 | 369 | })(); 370 | return beforeEach(function() { 371 | if (jasmine.addMatchers != null) { 372 | return jasmine.addMatchers(jasmine.matcherWrapper.wrap(jasmine._given.matchers)); 373 | } else { 374 | return this.addMatchers(jasmine._given.matchers); 375 | } 376 | }); 377 | })(jasmine); 378 | 379 | }).call(this); 380 | -------------------------------------------------------------------------------- /frontend/spec/helpers/jasmine-only.js: -------------------------------------------------------------------------------- 1 | /* jasmine-only - 0.1.0 2 | * Exclusivity spec helpers for jasmine: `describe.only` and `it.only` 3 | * https://github.com/davemo/jasmine-only 4 | */ 5 | (function() { 6 | var __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | (function(jasmine) { 10 | var describeOnly, env, itOnly, root; 11 | 12 | root = this; 13 | env = jasmine.getEnv(); 14 | describeOnly = function(description, specDefinitions) { 15 | var suite; 16 | 17 | suite = new jasmine.Suite(this, description, null, this.currentSuite); 18 | suite.exclusive_ = 1; 19 | this.exclusive_ = Math.max(this.exclusive_, 1); 20 | return this.describe_(suite, specDefinitions); 21 | }; 22 | itOnly = function(description, func) { 23 | var spec; 24 | 25 | spec = this.it(description, func); 26 | spec.exclusive_ = 2; 27 | this.exclusive_ = 2; 28 | return spec; 29 | }; 30 | env.exclusive_ = 0; 31 | env.describe = function(description, specDefinitions) { 32 | var suite; 33 | 34 | suite = new jasmine.Suite(this, description, null, this.currentSuite); 35 | return this.describe_(suite, specDefinitions); 36 | }; 37 | env.describe_ = function(suite, specDefinitions) { 38 | var declarationError, e, parentSuite; 39 | 40 | parentSuite = this.currentSuite; 41 | if (parentSuite) { 42 | parentSuite.add(suite); 43 | } else { 44 | this.currentRunner_.add(suite); 45 | } 46 | this.currentSuite = suite; 47 | declarationError = null; 48 | try { 49 | specDefinitions.call(suite); 50 | } catch (_error) { 51 | e = _error; 52 | declarationError = e; 53 | } 54 | if (declarationError) { 55 | this.it("encountered a declaration exception", function() { 56 | throw declarationError; 57 | }); 58 | } 59 | this.currentSuite = parentSuite; 60 | return suite; 61 | }; 62 | env.specFilter = function(spec) { 63 | return this.exclusive_ <= spec.exclusive_; 64 | }; 65 | env.describe.only = function() { 66 | return describeOnly.apply(env, arguments); 67 | }; 68 | env.it.only = function() { 69 | return itOnly.apply(env, arguments); 70 | }; 71 | root.describe.only = function(description, specDefinitions) { 72 | return env.describe.only(description, specDefinitions); 73 | }; 74 | root.it.only = function(description, func) { 75 | return env.it.only(description, func); 76 | }; 77 | root.iit = root.it.only; 78 | root.ddescribe = root.describe.only; 79 | jasmine.Spec = (function(_super) { 80 | __extends(Spec, _super); 81 | 82 | function Spec(env, suite, description) { 83 | this.exclusive_ = suite.exclusive_; 84 | Spec.__super__.constructor.call(this, env, suite, description); 85 | } 86 | 87 | return Spec; 88 | 89 | })(jasmine.Spec); 90 | return jasmine.Suite = (function(_super) { 91 | __extends(Suite, _super); 92 | 93 | function Suite(env, suite, specDefinitions, parentSuite) { 94 | this.exclusive_ = parentSuite && parentSuite.exclusive_ || 0; 95 | Suite.__super__.constructor.call(this, env, suite, specDefinitions, parentSuite); 96 | } 97 | 98 | return Suite; 99 | 100 | })(jasmine.Suite); 101 | })(jasmine); 102 | 103 | }).call(this); 104 | -------------------------------------------------------------------------------- /frontend/spec/helpers/jasmine-stealth.js: -------------------------------------------------------------------------------- 1 | /* jasmine-stealth - 0.0.16 2 | * Makes Jasmine spies a bit more robust 3 | * https://github.com/searls/jasmine-stealth 4 | */ 5 | (function() { 6 | var __hasProp = {}.hasOwnProperty, 7 | __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 8 | 9 | (function() { 10 | var Captor, fake, root, stubChainer, unfakes, whatToDoWhenTheSpyGetsCalled, _; 11 | root = this; 12 | _ = function(obj) { 13 | return { 14 | each: function(iterator) { 15 | var item, _i, _len, _results; 16 | _results = []; 17 | for (_i = 0, _len = obj.length; _i < _len; _i++) { 18 | item = obj[_i]; 19 | _results.push(iterator(item)); 20 | } 21 | return _results; 22 | }, 23 | isFunction: function() { 24 | return Object.prototype.toString.call(obj) === "[object Function]"; 25 | }, 26 | isString: function() { 27 | return Object.prototype.toString.call(obj) === "[object String]"; 28 | } 29 | }; 30 | }; 31 | root.spyOnConstructor = function(owner, classToFake, methodsToSpy) { 32 | var fakeClass, spies; 33 | if (methodsToSpy == null) { 34 | methodsToSpy = []; 35 | } 36 | if (_(methodsToSpy).isString()) { 37 | methodsToSpy = [methodsToSpy]; 38 | } 39 | spies = { 40 | constructor: jasmine.createSpy("" + classToFake + "'s constructor") 41 | }; 42 | fakeClass = (function() { 43 | function _Class() { 44 | spies.constructor.apply(this, arguments); 45 | } 46 | 47 | return _Class; 48 | 49 | })(); 50 | _(methodsToSpy).each(function(methodName) { 51 | spies[methodName] = jasmine.createSpy("" + classToFake + "#" + methodName); 52 | return fakeClass.prototype[methodName] = function() { 53 | return spies[methodName].apply(this, arguments); 54 | }; 55 | }); 56 | fake(owner, classToFake, fakeClass); 57 | return spies; 58 | }; 59 | unfakes = []; 60 | afterEach(function() { 61 | _(unfakes).each(function(u) { 62 | return u(); 63 | }); 64 | return unfakes = []; 65 | }); 66 | fake = function(owner, thingToFake, newThing) { 67 | var originalThing; 68 | originalThing = owner[thingToFake]; 69 | owner[thingToFake] = newThing; 70 | return unfakes.push(function() { 71 | return owner[thingToFake] = originalThing; 72 | }); 73 | }; 74 | root.stubFor = root.spyOn; 75 | jasmine.createStub = jasmine.createSpy; 76 | jasmine.createStubObj = function(baseName, stubbings) { 77 | var name, obj, stubbing; 78 | if (stubbings.constructor === Array) { 79 | return jasmine.createSpyObj(baseName, stubbings); 80 | } else { 81 | obj = {}; 82 | for (name in stubbings) { 83 | stubbing = stubbings[name]; 84 | obj[name] = jasmine.createSpy(baseName + "." + name); 85 | if (_(stubbing).isFunction()) { 86 | obj[name].andCallFake(stubbing); 87 | } else { 88 | obj[name].andReturn(stubbing); 89 | } 90 | } 91 | return obj; 92 | } 93 | }; 94 | whatToDoWhenTheSpyGetsCalled = function(spy) { 95 | var matchesStub, priorPlan; 96 | matchesStub = function(stubbing, args, context) { 97 | switch (stubbing.type) { 98 | case "args": 99 | return jasmine.getEnv().equals_(stubbing.ifThis, jasmine.util.argsToArray(args)); 100 | case "context": 101 | return jasmine.getEnv().equals_(stubbing.ifThis, context); 102 | } 103 | }; 104 | priorPlan = spy.plan; 105 | return spy.andCallFake(function() { 106 | var i, stubbing; 107 | i = 0; 108 | while (i < spy._stealth_stubbings.length) { 109 | stubbing = spy._stealth_stubbings[i]; 110 | if (matchesStub(stubbing, arguments, this)) { 111 | if (stubbing.satisfaction === "callFake") { 112 | return stubbing.thenThat.apply(stubbing, arguments); 113 | } else { 114 | return stubbing.thenThat; 115 | } 116 | } 117 | i++; 118 | } 119 | return priorPlan.apply(spy, arguments); 120 | }); 121 | }; 122 | jasmine.Spy.prototype.whenContext = function(context) { 123 | var spy; 124 | spy = this; 125 | spy._stealth_stubbings || (spy._stealth_stubbings = []); 126 | whatToDoWhenTheSpyGetsCalled(spy); 127 | return stubChainer(spy, "context", context); 128 | }; 129 | jasmine.Spy.prototype.when = function() { 130 | var ifThis, spy; 131 | spy = this; 132 | ifThis = jasmine.util.argsToArray(arguments); 133 | spy._stealth_stubbings || (spy._stealth_stubbings = []); 134 | whatToDoWhenTheSpyGetsCalled(spy); 135 | return stubChainer(spy, "args", ifThis); 136 | }; 137 | stubChainer = function(spy, type, ifThis) { 138 | var addStubbing; 139 | addStubbing = function(satisfaction) { 140 | return function(thenThat) { 141 | spy._stealth_stubbings.unshift({ 142 | type: type, 143 | ifThis: ifThis, 144 | satisfaction: satisfaction, 145 | thenThat: thenThat 146 | }); 147 | return spy; 148 | }; 149 | }; 150 | return { 151 | thenReturn: addStubbing("return"), 152 | thenCallFake: addStubbing("callFake") 153 | }; 154 | }; 155 | jasmine.Spy.prototype.mostRecentCallThat = function(callThat, context) { 156 | var i; 157 | i = this.calls.length - 1; 158 | while (i >= 0) { 159 | if (callThat.call(context || this, this.calls[i]) === true) { 160 | return this.calls[i]; 161 | } 162 | i--; 163 | } 164 | }; 165 | jasmine.Matchers.ArgThat = (function(_super) { 166 | __extends(ArgThat, _super); 167 | 168 | function ArgThat(matcher) { 169 | this.matcher = matcher; 170 | } 171 | 172 | ArgThat.prototype.jasmineMatches = function(actual) { 173 | return this.matcher(actual); 174 | }; 175 | 176 | return ArgThat; 177 | 178 | })(jasmine.Matchers.Any); 179 | jasmine.Matchers.ArgThat.prototype.matches = jasmine.Matchers.ArgThat.prototype.jasmineMatches; 180 | jasmine.argThat = function(expected) { 181 | return new jasmine.Matchers.ArgThat(expected); 182 | }; 183 | jasmine.Matchers.Capture = (function(_super) { 184 | __extends(Capture, _super); 185 | 186 | function Capture(captor) { 187 | this.captor = captor; 188 | } 189 | 190 | Capture.prototype.jasmineMatches = function(actual) { 191 | this.captor.value = actual; 192 | return true; 193 | }; 194 | 195 | return Capture; 196 | 197 | })(jasmine.Matchers.Any); 198 | jasmine.Matchers.Capture.prototype.matches = jasmine.Matchers.Capture.prototype.jasmineMatches; 199 | Captor = (function() { 200 | function Captor() {} 201 | 202 | Captor.prototype.capture = function() { 203 | return new jasmine.Matchers.Capture(this); 204 | }; 205 | 206 | return Captor; 207 | 208 | })(); 209 | return jasmine.captor = function() { 210 | return new Captor(); 211 | }; 212 | })(); 213 | 214 | }).call(this); 215 | -------------------------------------------------------------------------------- /frontend/tasks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/frontend/tasks/.gitkeep -------------------------------------------------------------------------------- /frontend/vendor/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/frontend/vendor/css/.gitkeep -------------------------------------------------------------------------------- /frontend/vendor/img/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/frontend/vendor/img/.gitkeep -------------------------------------------------------------------------------- /frontend/vendor/js/angular-messages.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license AngularJS v1.3.0-beta.9 3 | * (c) 2010-2014 Google, Inc. http://angularjs.org 4 | * License: MIT 5 | */ 6 | (function(window, angular, undefined) {'use strict'; 7 | 8 | /** 9 | * @ngdoc module 10 | * @name ngMessages 11 | * @description 12 | * 13 | * The `ngMessages` module provides enhanced support for displaying messages within templates 14 | * (typically within forms or when rendering message objects that return key/value data). 15 | * Instead of relying on JavaScript code and/or complex ng-if statements within your form template to 16 | * show and hide error messages specific to the state of an input field, the `ngMessages` and 17 | * `ngMessage` directives are designed to handle the complexity, inheritance and priority 18 | * sequencing based on the order of how the messages are defined in the template. 19 | * 20 | * Currently, the ngMessages module only contains the code for the `ngMessages` 21 | * and `ngMessage` directives. 22 | * 23 | * # Usage 24 | * The `ngMessages` directive listens on a key/value collection which is set on the ngMessages attribute. 25 | * Since the {@link ngModel ngModel} directive exposes an `$error` object, this error object can be 26 | * used with `ngMessages` to display control error messages in an easier way than with just regular angular 27 | * template directives. 28 | * 29 | * ```html 30 | *
31 | * 32 | *
33 | *
You did not enter a field
34 | *
The value entered is too short
35 | *
36 | *
37 | * ``` 38 | * 39 | * Now whatever key/value entries are present within the provided object (in this case `$error`) then 40 | * the ngMessages directive will render the inner first ngMessage directive (depending if the key values 41 | * match the attribute value present on each ngMessage directive). In other words, if your errors 42 | * object contains the following data: 43 | * 44 | * ```javascript 45 | * 46 | * myField.$error = { minlength : true, required : false }; 47 | * ``` 48 | * 49 | * Then the `required` message will be displayed first. When required is false then the `minlength` message 50 | * will be displayed right after (since these messages are ordered this way in the template HTML code). 51 | * The prioritization of each message is determined by what order they're present in the DOM. 52 | * Therefore, instead of having custom JavaScript code determine the priority of what errors are 53 | * present before others, the presentation of the errors are handled within the template. 54 | * 55 | * By default, ngMessages will only display one error at a time. However, if you wish to display all 56 | * messages then the `ng-messages-multiple` attribute flag can be used on the element containing the 57 | * ngMessages directive to make this happen. 58 | * 59 | * ```html 60 | *
...
61 | * ``` 62 | * 63 | * ## Reusing and Overriding Messages 64 | * In addition to prioritization, ngMessages also allows for including messages from a remote or an inline 65 | * template. This allows for generic collection of messages to be reused across multiple parts of an 66 | * application. 67 | * 68 | * ```html 69 | * 73 | *
74 | * ``` 75 | * 76 | * However, including generic messages may not be useful enough to match all input fields, therefore, 77 | * `ngMessages` provides the ability to override messages defined in the remote template by redefining 78 | * then within the directive container. 79 | * 80 | * ```html 81 | * 82 | * 86 | * 87 | *
88 | * 94 | * 95 | *
96 | * 97 | *
You did not enter your email address
98 | * 99 | * 100 | *
Your email address is invalid
101 | *
102 | *
103 | * ``` 104 | * 105 | * In the example HTML code above the message that is set on required will override the corresponding 106 | * required message defined within the remote template. Therefore, with particular input fields (such 107 | * email addresses, date fields, autocomplete inputs, etc...), specialized error messages can be applied 108 | * while more generic messages can be used to handle other, more general input errors. 109 | * 110 | * ## Animations 111 | * If the `ngAnimate` module is active within the application then both the `ngMessages` and 112 | * `ngMessage` directives will trigger animations whenever any messages are added and removed 113 | * from the DOM by the `ngMessages` directive. 114 | * 115 | * Whenever the `ngMessages` directive contains one or more visible messages then the `.ng-active` CSS 116 | * class will be added to the element. The `.ng-inactive` CSS class will be applied when there are no 117 | * animations present. Therefore, CSS transitions and keyframes as well as JavaScript animations can 118 | * hook into the animations whenever these classes are added/removed. 119 | * 120 | * Let's say that our HTML code for our messages container looks like so: 121 | * 122 | * ```html 123 | *
124 | *
...
125 | *
...
126 | *
127 | * ``` 128 | * 129 | * Then the CSS animation code for the message container looks like so: 130 | * 131 | * ```css 132 | * .my-messages { 133 | * transition:1s linear all; 134 | * } 135 | * .my-messages.ng-active { 136 | * // messages are visible 137 | * } 138 | * .my-messages.ng-inactive { 139 | * // messages are hidden 140 | * } 141 | * ``` 142 | * 143 | * Whenever an inner message is attached (becomes visible) or removed (becomes hidden) then the enter 144 | * and leave animation is triggered for each particular element bound to the `ngMessage` directive. 145 | * 146 | * Therefore, the CSS code for the inner messages looks like so: 147 | * 148 | * ```css 149 | * .some-message { 150 | * transition:1s linear all; 151 | * } 152 | * 153 | * .some-message.ng-enter {} 154 | * .some-message.ng-enter.ng-enter-active {} 155 | * 156 | * .some-message.ng-leave {} 157 | * .some-message.ng-leave.ng-leave-active {} 158 | * ``` 159 | * 160 | * {@link ngAnimate Click here} to learn how to use JavaScript animations or to learn more about ngAnimate. 161 | */ 162 | angular.module('ngMessages', []) 163 | 164 | /** 165 | * @ngdoc directive 166 | * @module ngMessages 167 | * @name ngMessages 168 | * @restrict AE 169 | * 170 | * @description 171 | * `ngMessages` is a directive that is designed to show and hide messages based on the state 172 | * of a key/value object that is listens on. The directive itself compliments error message 173 | * reporting with the `ngModel` $error object (which stores a key/value state of validation errors). 174 | * 175 | * `ngMessages` manages the state of internal messages within its container element. The internal 176 | * messages use the `ngMessage` directive and will be inserted/removed from the page depending 177 | * on if they're present within the key/value object. By default, only one message will be displayed 178 | * at a time and this depends on the prioritization of the messages within the template. (This can 179 | * be changed by using the ng-messages-multiple on the directive container.) 180 | * 181 | * A remote template can also be used to promote message reuseability and messages can also be 182 | * overridden. 183 | * 184 | * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 185 | * 186 | * @usage 187 | * ```html 188 | * 189 | * 190 | * ... 191 | * ... 192 | * ... 193 | * 194 | * 195 | * 196 | * 197 | * ... 198 | * ... 199 | * ... 200 | * 201 | * ``` 202 | * 203 | * @param {string} ngMessages an angular expression evaluating to a key/value object 204 | * (this is typically the $error object on an ngModel instance). 205 | * @param {string=} ngMessagesMultiple|multiple when set, all messages will be displayed with true 206 | * @param {string=} ngMessagesInclude|include when set, the specified template will be included into the ng-messages container 207 | * 208 | * @example 209 | * 212 | * 213 | *
214 | * 215 | * 221 | * 222 | *
myForm.myName.$error = {{ myForm.myName.$error | json }}
223 | * 224 | *
225 | *
You did not enter a field
226 | *
Your field is too short
227 | *
Your field is too long
228 | *
229 | *
230 | *
231 | * 232 | * angular.module('ngMessagesExample', ['ngMessages']); 233 | * 234 | *
235 | */ 236 | .directive('ngMessages', ['$compile', '$animate', '$http', '$templateCache', 237 | function($compile, $animate, $http, $templateCache) { 238 | var ACTIVE_CLASS = 'ng-active'; 239 | var INACTIVE_CLASS = 'ng-inactive'; 240 | 241 | return { 242 | restrict: 'AE', 243 | controller: ['$scope', function($scope) { 244 | this.$renderNgMessageClasses = angular.noop; 245 | 246 | var messages = []; 247 | this.registerMessage = function(index, message) { 248 | for(var i = 0; i < messages.length; i++) { 249 | if(messages[i].type == message.type) { 250 | if(index != i) { 251 | var temp = messages[index]; 252 | messages[index] = messages[i]; 253 | if(index < messages.length) { 254 | messages[i] = temp; 255 | } else { 256 | messages.splice(0, i); //remove the old one (and shift left) 257 | } 258 | } 259 | return; 260 | } 261 | } 262 | messages.splice(index, 0, message); //add the new one (and shift right) 263 | }; 264 | 265 | this.renderMessages = function(values, multiple) { 266 | values = values || {}; 267 | 268 | var found; 269 | angular.forEach(messages, function(message) { 270 | if((!found || multiple) && truthyVal(values[message.type])) { 271 | message.attach(); 272 | found = true; 273 | } else { 274 | message.detach(); 275 | } 276 | }); 277 | 278 | this.renderElementClasses(found); 279 | 280 | function truthyVal(value) { 281 | return value !== null && value !== false && value; 282 | } 283 | }; 284 | }], 285 | require: 'ngMessages', 286 | link: function($scope, element, $attrs, ctrl) { 287 | ctrl.renderElementClasses = function(bool) { 288 | bool ? $animate.setClass(element, ACTIVE_CLASS, INACTIVE_CLASS) 289 | : $animate.setClass(element, INACTIVE_CLASS, ACTIVE_CLASS); 290 | }; 291 | 292 | //JavaScript treats empty strings as false, but ng-message-multiple by itself is an empty string 293 | var multiple = angular.isString($attrs.ngMessagesMultiple) || 294 | angular.isString($attrs.multiple); 295 | 296 | var cachedValues, watchAttr = $attrs.ngMessages || $attrs['for']; //for is a reserved keyword 297 | $scope.$watchCollection(watchAttr, function(values) { 298 | cachedValues = values; 299 | ctrl.renderMessages(values, multiple); 300 | }); 301 | 302 | var tpl = $attrs.ngMessagesInclude || $attrs.include; 303 | if(tpl) { 304 | $http.get(tpl, { cache: $templateCache }) 305 | .success(function processTemplate(html) { 306 | var after, container = angular.element('
').html(html); 307 | angular.forEach(container.children(), function(elm) { 308 | elm = angular.element(elm); 309 | after ? after.after(elm) 310 | : element.prepend(elm); //start of the container 311 | after = elm; 312 | $compile(elm)($scope); 313 | }); 314 | ctrl.renderMessages(cachedValues, multiple); 315 | }); 316 | } 317 | } 318 | }; 319 | }]) 320 | 321 | 322 | /** 323 | * @ngdoc directive 324 | * @name ngMessage 325 | * @restrict AE 326 | * @scope 327 | * 328 | * @description 329 | * `ngMessage` is a directive with the purpose to show and hide a particular message. 330 | * For `ngMessage` to operate, a parent `ngMessages` directive on a parent DOM element 331 | * must be situated since it determines which messages are visible based on the state 332 | * of the provided key/value map that `ngMessages` listens on. 333 | * 334 | * @usage 335 | * ```html 336 | * 337 | * 338 | * ... 339 | * ... 340 | * ... 341 | * 342 | * 343 | * 344 | * 345 | * ... 346 | * ... 347 | * ... 348 | * 349 | * ``` 350 | * 351 | * {@link module:ngMessages Click here} to learn more about `ngMessages` and `ngMessage`. 352 | * 353 | * @param {string} ngMessage a string value corresponding to the message key. 354 | */ 355 | .directive('ngMessage', ['$animate', function($animate) { 356 | var COMMENT_NODE = 8; 357 | return { 358 | require: '^ngMessages', 359 | transclude: 'element', 360 | terminal: true, 361 | restrict: 'AE', 362 | link: function($scope, $element, $attrs, ngMessages, $transclude) { 363 | var index, element; 364 | 365 | var commentNode = $element[0]; 366 | var parentNode = commentNode.parentNode; 367 | for(var i = 0, j = 0; i < parentNode.childNodes.length; i++) { 368 | var node = parentNode.childNodes[i]; 369 | if(node.nodeType == COMMENT_NODE && node.nodeValue.indexOf('ngMessage') >= 0) { 370 | if(node === commentNode) { 371 | index = j; 372 | break; 373 | } 374 | j++; 375 | } 376 | } 377 | 378 | ngMessages.registerMessage(index, { 379 | type : $attrs.ngMessage || $attrs.when, 380 | attach : function() { 381 | if(!element) { 382 | $transclude($scope, function(clone) { 383 | $animate.enter(clone, null, $element); 384 | element = clone; 385 | }); 386 | } 387 | }, 388 | detach : function(now) { 389 | if(element) { 390 | $animate.leave(element); 391 | element = null; 392 | } 393 | } 394 | }); 395 | } 396 | }; 397 | }]); 398 | 399 | 400 | })(window, window.angular); 401 | -------------------------------------------------------------------------------- /frontend/vendor/js/underscore.js: -------------------------------------------------------------------------------- 1 | // Underscore.js 1.6.0 2 | // http://underscorejs.org 3 | // (c) 2009-2014 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 4 | // Underscore may be freely distributed under the MIT license. 5 | 6 | (function() { 7 | 8 | // Baseline setup 9 | // -------------- 10 | 11 | // Establish the root object, `window` in the browser, or `exports` on the server. 12 | var root = this; 13 | 14 | // Save the previous value of the `_` variable. 15 | var previousUnderscore = root._; 16 | 17 | // Establish the object that gets returned to break out of a loop iteration. 18 | var breaker = {}; 19 | 20 | // Save bytes in the minified (but not gzipped) version: 21 | var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; 22 | 23 | // Create quick reference variables for speed access to core prototypes. 24 | var 25 | push = ArrayProto.push, 26 | slice = ArrayProto.slice, 27 | concat = ArrayProto.concat, 28 | toString = ObjProto.toString, 29 | hasOwnProperty = ObjProto.hasOwnProperty; 30 | 31 | // All **ECMAScript 5** native function implementations that we hope to use 32 | // are declared here. 33 | var 34 | nativeForEach = ArrayProto.forEach, 35 | nativeMap = ArrayProto.map, 36 | nativeReduce = ArrayProto.reduce, 37 | nativeReduceRight = ArrayProto.reduceRight, 38 | nativeFilter = ArrayProto.filter, 39 | nativeEvery = ArrayProto.every, 40 | nativeSome = ArrayProto.some, 41 | nativeIndexOf = ArrayProto.indexOf, 42 | nativeLastIndexOf = ArrayProto.lastIndexOf, 43 | nativeIsArray = Array.isArray, 44 | nativeKeys = Object.keys, 45 | nativeBind = FuncProto.bind; 46 | 47 | // Create a safe reference to the Underscore object for use below. 48 | var _ = function(obj) { 49 | if (obj instanceof _) return obj; 50 | if (!(this instanceof _)) return new _(obj); 51 | this._wrapped = obj; 52 | }; 53 | 54 | // Export the Underscore object for **Node.js**, with 55 | // backwards-compatibility for the old `require()` API. If we're in 56 | // the browser, add `_` as a global object via a string identifier, 57 | // for Closure Compiler "advanced" mode. 58 | if (typeof exports !== 'undefined') { 59 | if (typeof module !== 'undefined' && module.exports) { 60 | exports = module.exports = _; 61 | } 62 | exports._ = _; 63 | } else { 64 | root._ = _; 65 | } 66 | 67 | // Current version. 68 | _.VERSION = '1.6.0'; 69 | 70 | // Collection Functions 71 | // -------------------- 72 | 73 | // The cornerstone, an `each` implementation, aka `forEach`. 74 | // Handles objects with the built-in `forEach`, arrays, and raw objects. 75 | // Delegates to **ECMAScript 5**'s native `forEach` if available. 76 | var each = _.each = _.forEach = function(obj, iterator, context) { 77 | if (obj == null) return obj; 78 | if (nativeForEach && obj.forEach === nativeForEach) { 79 | obj.forEach(iterator, context); 80 | } else if (obj.length === +obj.length) { 81 | for (var i = 0, length = obj.length; i < length; i++) { 82 | if (iterator.call(context, obj[i], i, obj) === breaker) return; 83 | } 84 | } else { 85 | var keys = _.keys(obj); 86 | for (var i = 0, length = keys.length; i < length; i++) { 87 | if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return; 88 | } 89 | } 90 | return obj; 91 | }; 92 | 93 | // Return the results of applying the iterator to each element. 94 | // Delegates to **ECMAScript 5**'s native `map` if available. 95 | _.map = _.collect = function(obj, iterator, context) { 96 | var results = []; 97 | if (obj == null) return results; 98 | if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); 99 | each(obj, function(value, index, list) { 100 | results.push(iterator.call(context, value, index, list)); 101 | }); 102 | return results; 103 | }; 104 | 105 | var reduceError = 'Reduce of empty array with no initial value'; 106 | 107 | // **Reduce** builds up a single result from a list of values, aka `inject`, 108 | // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. 109 | _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { 110 | var initial = arguments.length > 2; 111 | if (obj == null) obj = []; 112 | if (nativeReduce && obj.reduce === nativeReduce) { 113 | if (context) iterator = _.bind(iterator, context); 114 | return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); 115 | } 116 | each(obj, function(value, index, list) { 117 | if (!initial) { 118 | memo = value; 119 | initial = true; 120 | } else { 121 | memo = iterator.call(context, memo, value, index, list); 122 | } 123 | }); 124 | if (!initial) throw new TypeError(reduceError); 125 | return memo; 126 | }; 127 | 128 | // The right-associative version of reduce, also known as `foldr`. 129 | // Delegates to **ECMAScript 5**'s native `reduceRight` if available. 130 | _.reduceRight = _.foldr = function(obj, iterator, memo, context) { 131 | var initial = arguments.length > 2; 132 | if (obj == null) obj = []; 133 | if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { 134 | if (context) iterator = _.bind(iterator, context); 135 | return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); 136 | } 137 | var length = obj.length; 138 | if (length !== +length) { 139 | var keys = _.keys(obj); 140 | length = keys.length; 141 | } 142 | each(obj, function(value, index, list) { 143 | index = keys ? keys[--length] : --length; 144 | if (!initial) { 145 | memo = obj[index]; 146 | initial = true; 147 | } else { 148 | memo = iterator.call(context, memo, obj[index], index, list); 149 | } 150 | }); 151 | if (!initial) throw new TypeError(reduceError); 152 | return memo; 153 | }; 154 | 155 | // Return the first value which passes a truth test. Aliased as `detect`. 156 | _.find = _.detect = function(obj, predicate, context) { 157 | var result; 158 | any(obj, function(value, index, list) { 159 | if (predicate.call(context, value, index, list)) { 160 | result = value; 161 | return true; 162 | } 163 | }); 164 | return result; 165 | }; 166 | 167 | // Return all the elements that pass a truth test. 168 | // Delegates to **ECMAScript 5**'s native `filter` if available. 169 | // Aliased as `select`. 170 | _.filter = _.select = function(obj, predicate, context) { 171 | var results = []; 172 | if (obj == null) return results; 173 | if (nativeFilter && obj.filter === nativeFilter) return obj.filter(predicate, context); 174 | each(obj, function(value, index, list) { 175 | if (predicate.call(context, value, index, list)) results.push(value); 176 | }); 177 | return results; 178 | }; 179 | 180 | // Return all the elements for which a truth test fails. 181 | _.reject = function(obj, predicate, context) { 182 | return _.filter(obj, function(value, index, list) { 183 | return !predicate.call(context, value, index, list); 184 | }, context); 185 | }; 186 | 187 | // Determine whether all of the elements match a truth test. 188 | // Delegates to **ECMAScript 5**'s native `every` if available. 189 | // Aliased as `all`. 190 | _.every = _.all = function(obj, predicate, context) { 191 | predicate || (predicate = _.identity); 192 | var result = true; 193 | if (obj == null) return result; 194 | if (nativeEvery && obj.every === nativeEvery) return obj.every(predicate, context); 195 | each(obj, function(value, index, list) { 196 | if (!(result = result && predicate.call(context, value, index, list))) return breaker; 197 | }); 198 | return !!result; 199 | }; 200 | 201 | // Determine if at least one element in the object matches a truth test. 202 | // Delegates to **ECMAScript 5**'s native `some` if available. 203 | // Aliased as `any`. 204 | var any = _.some = _.any = function(obj, predicate, context) { 205 | predicate || (predicate = _.identity); 206 | var result = false; 207 | if (obj == null) return result; 208 | if (nativeSome && obj.some === nativeSome) return obj.some(predicate, context); 209 | each(obj, function(value, index, list) { 210 | if (result || (result = predicate.call(context, value, index, list))) return breaker; 211 | }); 212 | return !!result; 213 | }; 214 | 215 | // Determine if the array or object contains a given value (using `===`). 216 | // Aliased as `include`. 217 | _.contains = _.include = function(obj, target) { 218 | if (obj == null) return false; 219 | if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; 220 | return any(obj, function(value) { 221 | return value === target; 222 | }); 223 | }; 224 | 225 | // Invoke a method (with arguments) on every item in a collection. 226 | _.invoke = function(obj, method) { 227 | var args = slice.call(arguments, 2); 228 | var isFunc = _.isFunction(method); 229 | return _.map(obj, function(value) { 230 | return (isFunc ? method : value[method]).apply(value, args); 231 | }); 232 | }; 233 | 234 | // Convenience version of a common use case of `map`: fetching a property. 235 | _.pluck = function(obj, key) { 236 | return _.map(obj, _.property(key)); 237 | }; 238 | 239 | // Convenience version of a common use case of `filter`: selecting only objects 240 | // containing specific `key:value` pairs. 241 | _.where = function(obj, attrs) { 242 | return _.filter(obj, _.matches(attrs)); 243 | }; 244 | 245 | // Convenience version of a common use case of `find`: getting the first object 246 | // containing specific `key:value` pairs. 247 | _.findWhere = function(obj, attrs) { 248 | return _.find(obj, _.matches(attrs)); 249 | }; 250 | 251 | // Return the maximum element or (element-based computation). 252 | // Can't optimize arrays of integers longer than 65,535 elements. 253 | // See [WebKit Bug 80797](https://bugs.webkit.org/show_bug.cgi?id=80797) 254 | _.max = function(obj, iterator, context) { 255 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 256 | return Math.max.apply(Math, obj); 257 | } 258 | var result = -Infinity, lastComputed = -Infinity; 259 | each(obj, function(value, index, list) { 260 | var computed = iterator ? iterator.call(context, value, index, list) : value; 261 | if (computed > lastComputed) { 262 | result = value; 263 | lastComputed = computed; 264 | } 265 | }); 266 | return result; 267 | }; 268 | 269 | // Return the minimum element (or element-based computation). 270 | _.min = function(obj, iterator, context) { 271 | if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { 272 | return Math.min.apply(Math, obj); 273 | } 274 | var result = Infinity, lastComputed = Infinity; 275 | each(obj, function(value, index, list) { 276 | var computed = iterator ? iterator.call(context, value, index, list) : value; 277 | if (computed < lastComputed) { 278 | result = value; 279 | lastComputed = computed; 280 | } 281 | }); 282 | return result; 283 | }; 284 | 285 | // Shuffle an array, using the modern version of the 286 | // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle). 287 | _.shuffle = function(obj) { 288 | var rand; 289 | var index = 0; 290 | var shuffled = []; 291 | each(obj, function(value) { 292 | rand = _.random(index++); 293 | shuffled[index - 1] = shuffled[rand]; 294 | shuffled[rand] = value; 295 | }); 296 | return shuffled; 297 | }; 298 | 299 | // Sample **n** random values from a collection. 300 | // If **n** is not specified, returns a single random element. 301 | // The internal `guard` argument allows it to work with `map`. 302 | _.sample = function(obj, n, guard) { 303 | if (n == null || guard) { 304 | if (obj.length !== +obj.length) obj = _.values(obj); 305 | return obj[_.random(obj.length - 1)]; 306 | } 307 | return _.shuffle(obj).slice(0, Math.max(0, n)); 308 | }; 309 | 310 | // An internal function to generate lookup iterators. 311 | var lookupIterator = function(value) { 312 | if (value == null) return _.identity; 313 | if (_.isFunction(value)) return value; 314 | return _.property(value); 315 | }; 316 | 317 | // Sort the object's values by a criterion produced by an iterator. 318 | _.sortBy = function(obj, iterator, context) { 319 | iterator = lookupIterator(iterator); 320 | return _.pluck(_.map(obj, function(value, index, list) { 321 | return { 322 | value: value, 323 | index: index, 324 | criteria: iterator.call(context, value, index, list) 325 | }; 326 | }).sort(function(left, right) { 327 | var a = left.criteria; 328 | var b = right.criteria; 329 | if (a !== b) { 330 | if (a > b || a === void 0) return 1; 331 | if (a < b || b === void 0) return -1; 332 | } 333 | return left.index - right.index; 334 | }), 'value'); 335 | }; 336 | 337 | // An internal function used for aggregate "group by" operations. 338 | var group = function(behavior) { 339 | return function(obj, iterator, context) { 340 | var result = {}; 341 | iterator = lookupIterator(iterator); 342 | each(obj, function(value, index) { 343 | var key = iterator.call(context, value, index, obj); 344 | behavior(result, key, value); 345 | }); 346 | return result; 347 | }; 348 | }; 349 | 350 | // Groups the object's values by a criterion. Pass either a string attribute 351 | // to group by, or a function that returns the criterion. 352 | _.groupBy = group(function(result, key, value) { 353 | _.has(result, key) ? result[key].push(value) : result[key] = [value]; 354 | }); 355 | 356 | // Indexes the object's values by a criterion, similar to `groupBy`, but for 357 | // when you know that your index values will be unique. 358 | _.indexBy = group(function(result, key, value) { 359 | result[key] = value; 360 | }); 361 | 362 | // Counts instances of an object that group by a certain criterion. Pass 363 | // either a string attribute to count by, or a function that returns the 364 | // criterion. 365 | _.countBy = group(function(result, key) { 366 | _.has(result, key) ? result[key]++ : result[key] = 1; 367 | }); 368 | 369 | // Use a comparator function to figure out the smallest index at which 370 | // an object should be inserted so as to maintain order. Uses binary search. 371 | _.sortedIndex = function(array, obj, iterator, context) { 372 | iterator = lookupIterator(iterator); 373 | var value = iterator.call(context, obj); 374 | var low = 0, high = array.length; 375 | while (low < high) { 376 | var mid = (low + high) >>> 1; 377 | iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; 378 | } 379 | return low; 380 | }; 381 | 382 | // Safely create a real, live array from anything iterable. 383 | _.toArray = function(obj) { 384 | if (!obj) return []; 385 | if (_.isArray(obj)) return slice.call(obj); 386 | if (obj.length === +obj.length) return _.map(obj, _.identity); 387 | return _.values(obj); 388 | }; 389 | 390 | // Return the number of elements in an object. 391 | _.size = function(obj) { 392 | if (obj == null) return 0; 393 | return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; 394 | }; 395 | 396 | // Array Functions 397 | // --------------- 398 | 399 | // Get the first element of an array. Passing **n** will return the first N 400 | // values in the array. Aliased as `head` and `take`. The **guard** check 401 | // allows it to work with `_.map`. 402 | _.first = _.head = _.take = function(array, n, guard) { 403 | if (array == null) return void 0; 404 | if ((n == null) || guard) return array[0]; 405 | if (n < 0) return []; 406 | return slice.call(array, 0, n); 407 | }; 408 | 409 | // Returns everything but the last entry of the array. Especially useful on 410 | // the arguments object. Passing **n** will return all the values in 411 | // the array, excluding the last N. The **guard** check allows it to work with 412 | // `_.map`. 413 | _.initial = function(array, n, guard) { 414 | return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); 415 | }; 416 | 417 | // Get the last element of an array. Passing **n** will return the last N 418 | // values in the array. The **guard** check allows it to work with `_.map`. 419 | _.last = function(array, n, guard) { 420 | if (array == null) return void 0; 421 | if ((n == null) || guard) return array[array.length - 1]; 422 | return slice.call(array, Math.max(array.length - n, 0)); 423 | }; 424 | 425 | // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. 426 | // Especially useful on the arguments object. Passing an **n** will return 427 | // the rest N values in the array. The **guard** 428 | // check allows it to work with `_.map`. 429 | _.rest = _.tail = _.drop = function(array, n, guard) { 430 | return slice.call(array, (n == null) || guard ? 1 : n); 431 | }; 432 | 433 | // Trim out all falsy values from an array. 434 | _.compact = function(array) { 435 | return _.filter(array, _.identity); 436 | }; 437 | 438 | // Internal implementation of a recursive `flatten` function. 439 | var flatten = function(input, shallow, output) { 440 | if (shallow && _.every(input, _.isArray)) { 441 | return concat.apply(output, input); 442 | } 443 | each(input, function(value) { 444 | if (_.isArray(value) || _.isArguments(value)) { 445 | shallow ? push.apply(output, value) : flatten(value, shallow, output); 446 | } else { 447 | output.push(value); 448 | } 449 | }); 450 | return output; 451 | }; 452 | 453 | // Flatten out an array, either recursively (by default), or just one level. 454 | _.flatten = function(array, shallow) { 455 | return flatten(array, shallow, []); 456 | }; 457 | 458 | // Return a version of the array that does not contain the specified value(s). 459 | _.without = function(array) { 460 | return _.difference(array, slice.call(arguments, 1)); 461 | }; 462 | 463 | // Split an array into two arrays: one whose elements all satisfy the given 464 | // predicate, and one whose elements all do not satisfy the predicate. 465 | _.partition = function(array, predicate) { 466 | var pass = [], fail = []; 467 | each(array, function(elem) { 468 | (predicate(elem) ? pass : fail).push(elem); 469 | }); 470 | return [pass, fail]; 471 | }; 472 | 473 | // Produce a duplicate-free version of the array. If the array has already 474 | // been sorted, you have the option of using a faster algorithm. 475 | // Aliased as `unique`. 476 | _.uniq = _.unique = function(array, isSorted, iterator, context) { 477 | if (_.isFunction(isSorted)) { 478 | context = iterator; 479 | iterator = isSorted; 480 | isSorted = false; 481 | } 482 | var initial = iterator ? _.map(array, iterator, context) : array; 483 | var results = []; 484 | var seen = []; 485 | each(initial, function(value, index) { 486 | if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { 487 | seen.push(value); 488 | results.push(array[index]); 489 | } 490 | }); 491 | return results; 492 | }; 493 | 494 | // Produce an array that contains the union: each distinct element from all of 495 | // the passed-in arrays. 496 | _.union = function() { 497 | return _.uniq(_.flatten(arguments, true)); 498 | }; 499 | 500 | // Produce an array that contains every item shared between all the 501 | // passed-in arrays. 502 | _.intersection = function(array) { 503 | var rest = slice.call(arguments, 1); 504 | return _.filter(_.uniq(array), function(item) { 505 | return _.every(rest, function(other) { 506 | return _.contains(other, item); 507 | }); 508 | }); 509 | }; 510 | 511 | // Take the difference between one array and a number of other arrays. 512 | // Only the elements present in just the first array will remain. 513 | _.difference = function(array) { 514 | var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); 515 | return _.filter(array, function(value){ return !_.contains(rest, value); }); 516 | }; 517 | 518 | // Zip together multiple lists into a single array -- elements that share 519 | // an index go together. 520 | _.zip = function() { 521 | var length = _.max(_.pluck(arguments, 'length').concat(0)); 522 | var results = new Array(length); 523 | for (var i = 0; i < length; i++) { 524 | results[i] = _.pluck(arguments, '' + i); 525 | } 526 | return results; 527 | }; 528 | 529 | // Converts lists into objects. Pass either a single array of `[key, value]` 530 | // pairs, or two parallel arrays of the same length -- one of keys, and one of 531 | // the corresponding values. 532 | _.object = function(list, values) { 533 | if (list == null) return {}; 534 | var result = {}; 535 | for (var i = 0, length = list.length; i < length; i++) { 536 | if (values) { 537 | result[list[i]] = values[i]; 538 | } else { 539 | result[list[i][0]] = list[i][1]; 540 | } 541 | } 542 | return result; 543 | }; 544 | 545 | // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), 546 | // we need this function. Return the position of the first occurrence of an 547 | // item in an array, or -1 if the item is not included in the array. 548 | // Delegates to **ECMAScript 5**'s native `indexOf` if available. 549 | // If the array is large and already in sort order, pass `true` 550 | // for **isSorted** to use binary search. 551 | _.indexOf = function(array, item, isSorted) { 552 | if (array == null) return -1; 553 | var i = 0, length = array.length; 554 | if (isSorted) { 555 | if (typeof isSorted == 'number') { 556 | i = (isSorted < 0 ? Math.max(0, length + isSorted) : isSorted); 557 | } else { 558 | i = _.sortedIndex(array, item); 559 | return array[i] === item ? i : -1; 560 | } 561 | } 562 | if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); 563 | for (; i < length; i++) if (array[i] === item) return i; 564 | return -1; 565 | }; 566 | 567 | // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. 568 | _.lastIndexOf = function(array, item, from) { 569 | if (array == null) return -1; 570 | var hasIndex = from != null; 571 | if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { 572 | return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); 573 | } 574 | var i = (hasIndex ? from : array.length); 575 | while (i--) if (array[i] === item) return i; 576 | return -1; 577 | }; 578 | 579 | // Generate an integer Array containing an arithmetic progression. A port of 580 | // the native Python `range()` function. See 581 | // [the Python documentation](http://docs.python.org/library/functions.html#range). 582 | _.range = function(start, stop, step) { 583 | if (arguments.length <= 1) { 584 | stop = start || 0; 585 | start = 0; 586 | } 587 | step = arguments[2] || 1; 588 | 589 | var length = Math.max(Math.ceil((stop - start) / step), 0); 590 | var idx = 0; 591 | var range = new Array(length); 592 | 593 | while(idx < length) { 594 | range[idx++] = start; 595 | start += step; 596 | } 597 | 598 | return range; 599 | }; 600 | 601 | // Function (ahem) Functions 602 | // ------------------ 603 | 604 | // Reusable constructor function for prototype setting. 605 | var ctor = function(){}; 606 | 607 | // Create a function bound to a given object (assigning `this`, and arguments, 608 | // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if 609 | // available. 610 | _.bind = function(func, context) { 611 | var args, bound; 612 | if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); 613 | if (!_.isFunction(func)) throw new TypeError; 614 | args = slice.call(arguments, 2); 615 | return bound = function() { 616 | if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments))); 617 | ctor.prototype = func.prototype; 618 | var self = new ctor; 619 | ctor.prototype = null; 620 | var result = func.apply(self, args.concat(slice.call(arguments))); 621 | if (Object(result) === result) return result; 622 | return self; 623 | }; 624 | }; 625 | 626 | // Partially apply a function by creating a version that has had some of its 627 | // arguments pre-filled, without changing its dynamic `this` context. _ acts 628 | // as a placeholder, allowing any combination of arguments to be pre-filled. 629 | _.partial = function(func) { 630 | var boundArgs = slice.call(arguments, 1); 631 | return function() { 632 | var position = 0; 633 | var args = boundArgs.slice(); 634 | for (var i = 0, length = args.length; i < length; i++) { 635 | if (args[i] === _) args[i] = arguments[position++]; 636 | } 637 | while (position < arguments.length) args.push(arguments[position++]); 638 | return func.apply(this, args); 639 | }; 640 | }; 641 | 642 | // Bind a number of an object's methods to that object. Remaining arguments 643 | // are the method names to be bound. Useful for ensuring that all callbacks 644 | // defined on an object belong to it. 645 | _.bindAll = function(obj) { 646 | var funcs = slice.call(arguments, 1); 647 | if (funcs.length === 0) throw new Error('bindAll must be passed function names'); 648 | each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); 649 | return obj; 650 | }; 651 | 652 | // Memoize an expensive function by storing its results. 653 | _.memoize = function(func, hasher) { 654 | var memo = {}; 655 | hasher || (hasher = _.identity); 656 | return function() { 657 | var key = hasher.apply(this, arguments); 658 | return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); 659 | }; 660 | }; 661 | 662 | // Delays a function for the given number of milliseconds, and then calls 663 | // it with the arguments supplied. 664 | _.delay = function(func, wait) { 665 | var args = slice.call(arguments, 2); 666 | return setTimeout(function(){ return func.apply(null, args); }, wait); 667 | }; 668 | 669 | // Defers a function, scheduling it to run after the current call stack has 670 | // cleared. 671 | _.defer = function(func) { 672 | return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); 673 | }; 674 | 675 | // Returns a function, that, when invoked, will only be triggered at most once 676 | // during a given window of time. Normally, the throttled function will run 677 | // as much as it can, without ever going more than once per `wait` duration; 678 | // but if you'd like to disable the execution on the leading edge, pass 679 | // `{leading: false}`. To disable execution on the trailing edge, ditto. 680 | _.throttle = function(func, wait, options) { 681 | var context, args, result; 682 | var timeout = null; 683 | var previous = 0; 684 | options || (options = {}); 685 | var later = function() { 686 | previous = options.leading === false ? 0 : _.now(); 687 | timeout = null; 688 | result = func.apply(context, args); 689 | context = args = null; 690 | }; 691 | return function() { 692 | var now = _.now(); 693 | if (!previous && options.leading === false) previous = now; 694 | var remaining = wait - (now - previous); 695 | context = this; 696 | args = arguments; 697 | if (remaining <= 0) { 698 | clearTimeout(timeout); 699 | timeout = null; 700 | previous = now; 701 | result = func.apply(context, args); 702 | context = args = null; 703 | } else if (!timeout && options.trailing !== false) { 704 | timeout = setTimeout(later, remaining); 705 | } 706 | return result; 707 | }; 708 | }; 709 | 710 | // Returns a function, that, as long as it continues to be invoked, will not 711 | // be triggered. The function will be called after it stops being called for 712 | // N milliseconds. If `immediate` is passed, trigger the function on the 713 | // leading edge, instead of the trailing. 714 | _.debounce = function(func, wait, immediate) { 715 | var timeout, args, context, timestamp, result; 716 | 717 | var later = function() { 718 | var last = _.now() - timestamp; 719 | if (last < wait) { 720 | timeout = setTimeout(later, wait - last); 721 | } else { 722 | timeout = null; 723 | if (!immediate) { 724 | result = func.apply(context, args); 725 | context = args = null; 726 | } 727 | } 728 | }; 729 | 730 | return function() { 731 | context = this; 732 | args = arguments; 733 | timestamp = _.now(); 734 | var callNow = immediate && !timeout; 735 | if (!timeout) { 736 | timeout = setTimeout(later, wait); 737 | } 738 | if (callNow) { 739 | result = func.apply(context, args); 740 | context = args = null; 741 | } 742 | 743 | return result; 744 | }; 745 | }; 746 | 747 | // Returns a function that will be executed at most one time, no matter how 748 | // often you call it. Useful for lazy initialization. 749 | _.once = function(func) { 750 | var ran = false, memo; 751 | return function() { 752 | if (ran) return memo; 753 | ran = true; 754 | memo = func.apply(this, arguments); 755 | func = null; 756 | return memo; 757 | }; 758 | }; 759 | 760 | // Returns the first function passed as an argument to the second, 761 | // allowing you to adjust arguments, run code before and after, and 762 | // conditionally execute the original function. 763 | _.wrap = function(func, wrapper) { 764 | return _.partial(wrapper, func); 765 | }; 766 | 767 | // Returns a function that is the composition of a list of functions, each 768 | // consuming the return value of the function that follows. 769 | _.compose = function() { 770 | var funcs = arguments; 771 | return function() { 772 | var args = arguments; 773 | for (var i = funcs.length - 1; i >= 0; i--) { 774 | args = [funcs[i].apply(this, args)]; 775 | } 776 | return args[0]; 777 | }; 778 | }; 779 | 780 | // Returns a function that will only be executed after being called N times. 781 | _.after = function(times, func) { 782 | return function() { 783 | if (--times < 1) { 784 | return func.apply(this, arguments); 785 | } 786 | }; 787 | }; 788 | 789 | // Object Functions 790 | // ---------------- 791 | 792 | // Retrieve the names of an object's properties. 793 | // Delegates to **ECMAScript 5**'s native `Object.keys` 794 | _.keys = function(obj) { 795 | if (!_.isObject(obj)) return []; 796 | if (nativeKeys) return nativeKeys(obj); 797 | var keys = []; 798 | for (var key in obj) if (_.has(obj, key)) keys.push(key); 799 | return keys; 800 | }; 801 | 802 | // Retrieve the values of an object's properties. 803 | _.values = function(obj) { 804 | var keys = _.keys(obj); 805 | var length = keys.length; 806 | var values = new Array(length); 807 | for (var i = 0; i < length; i++) { 808 | values[i] = obj[keys[i]]; 809 | } 810 | return values; 811 | }; 812 | 813 | // Convert an object into a list of `[key, value]` pairs. 814 | _.pairs = function(obj) { 815 | var keys = _.keys(obj); 816 | var length = keys.length; 817 | var pairs = new Array(length); 818 | for (var i = 0; i < length; i++) { 819 | pairs[i] = [keys[i], obj[keys[i]]]; 820 | } 821 | return pairs; 822 | }; 823 | 824 | // Invert the keys and values of an object. The values must be serializable. 825 | _.invert = function(obj) { 826 | var result = {}; 827 | var keys = _.keys(obj); 828 | for (var i = 0, length = keys.length; i < length; i++) { 829 | result[obj[keys[i]]] = keys[i]; 830 | } 831 | return result; 832 | }; 833 | 834 | // Return a sorted list of the function names available on the object. 835 | // Aliased as `methods` 836 | _.functions = _.methods = function(obj) { 837 | var names = []; 838 | for (var key in obj) { 839 | if (_.isFunction(obj[key])) names.push(key); 840 | } 841 | return names.sort(); 842 | }; 843 | 844 | // Extend a given object with all the properties in passed-in object(s). 845 | _.extend = function(obj) { 846 | each(slice.call(arguments, 1), function(source) { 847 | if (source) { 848 | for (var prop in source) { 849 | obj[prop] = source[prop]; 850 | } 851 | } 852 | }); 853 | return obj; 854 | }; 855 | 856 | // Return a copy of the object only containing the whitelisted properties. 857 | _.pick = function(obj) { 858 | var copy = {}; 859 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 860 | each(keys, function(key) { 861 | if (key in obj) copy[key] = obj[key]; 862 | }); 863 | return copy; 864 | }; 865 | 866 | // Return a copy of the object without the blacklisted properties. 867 | _.omit = function(obj) { 868 | var copy = {}; 869 | var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); 870 | for (var key in obj) { 871 | if (!_.contains(keys, key)) copy[key] = obj[key]; 872 | } 873 | return copy; 874 | }; 875 | 876 | // Fill in a given object with default properties. 877 | _.defaults = function(obj) { 878 | each(slice.call(arguments, 1), function(source) { 879 | if (source) { 880 | for (var prop in source) { 881 | if (obj[prop] === void 0) obj[prop] = source[prop]; 882 | } 883 | } 884 | }); 885 | return obj; 886 | }; 887 | 888 | // Create a (shallow-cloned) duplicate of an object. 889 | _.clone = function(obj) { 890 | if (!_.isObject(obj)) return obj; 891 | return _.isArray(obj) ? obj.slice() : _.extend({}, obj); 892 | }; 893 | 894 | // Invokes interceptor with the obj, and then returns obj. 895 | // The primary purpose of this method is to "tap into" a method chain, in 896 | // order to perform operations on intermediate results within the chain. 897 | _.tap = function(obj, interceptor) { 898 | interceptor(obj); 899 | return obj; 900 | }; 901 | 902 | // Internal recursive comparison function for `isEqual`. 903 | var eq = function(a, b, aStack, bStack) { 904 | // Identical objects are equal. `0 === -0`, but they aren't identical. 905 | // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal). 906 | if (a === b) return a !== 0 || 1 / a == 1 / b; 907 | // A strict comparison is necessary because `null == undefined`. 908 | if (a == null || b == null) return a === b; 909 | // Unwrap any wrapped objects. 910 | if (a instanceof _) a = a._wrapped; 911 | if (b instanceof _) b = b._wrapped; 912 | // Compare `[[Class]]` names. 913 | var className = toString.call(a); 914 | if (className != toString.call(b)) return false; 915 | switch (className) { 916 | // Strings, numbers, dates, and booleans are compared by value. 917 | case '[object String]': 918 | // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is 919 | // equivalent to `new String("5")`. 920 | return a == String(b); 921 | case '[object Number]': 922 | // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for 923 | // other numeric values. 924 | return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); 925 | case '[object Date]': 926 | case '[object Boolean]': 927 | // Coerce dates and booleans to numeric primitive values. Dates are compared by their 928 | // millisecond representations. Note that invalid dates with millisecond representations 929 | // of `NaN` are not equivalent. 930 | return +a == +b; 931 | // RegExps are compared by their source patterns and flags. 932 | case '[object RegExp]': 933 | return a.source == b.source && 934 | a.global == b.global && 935 | a.multiline == b.multiline && 936 | a.ignoreCase == b.ignoreCase; 937 | } 938 | if (typeof a != 'object' || typeof b != 'object') return false; 939 | // Assume equality for cyclic structures. The algorithm for detecting cyclic 940 | // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. 941 | var length = aStack.length; 942 | while (length--) { 943 | // Linear search. Performance is inversely proportional to the number of 944 | // unique nested structures. 945 | if (aStack[length] == a) return bStack[length] == b; 946 | } 947 | // Objects with different constructors are not equivalent, but `Object`s 948 | // from different frames are. 949 | var aCtor = a.constructor, bCtor = b.constructor; 950 | if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && 951 | _.isFunction(bCtor) && (bCtor instanceof bCtor)) 952 | && ('constructor' in a && 'constructor' in b)) { 953 | return false; 954 | } 955 | // Add the first object to the stack of traversed objects. 956 | aStack.push(a); 957 | bStack.push(b); 958 | var size = 0, result = true; 959 | // Recursively compare objects and arrays. 960 | if (className == '[object Array]') { 961 | // Compare array lengths to determine if a deep comparison is necessary. 962 | size = a.length; 963 | result = size == b.length; 964 | if (result) { 965 | // Deep compare the contents, ignoring non-numeric properties. 966 | while (size--) { 967 | if (!(result = eq(a[size], b[size], aStack, bStack))) break; 968 | } 969 | } 970 | } else { 971 | // Deep compare objects. 972 | for (var key in a) { 973 | if (_.has(a, key)) { 974 | // Count the expected number of properties. 975 | size++; 976 | // Deep compare each member. 977 | if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; 978 | } 979 | } 980 | // Ensure that both objects contain the same number of properties. 981 | if (result) { 982 | for (key in b) { 983 | if (_.has(b, key) && !(size--)) break; 984 | } 985 | result = !size; 986 | } 987 | } 988 | // Remove the first object from the stack of traversed objects. 989 | aStack.pop(); 990 | bStack.pop(); 991 | return result; 992 | }; 993 | 994 | // Perform a deep comparison to check if two objects are equal. 995 | _.isEqual = function(a, b) { 996 | return eq(a, b, [], []); 997 | }; 998 | 999 | // Is a given array, string, or object empty? 1000 | // An "empty" object has no enumerable own-properties. 1001 | _.isEmpty = function(obj) { 1002 | if (obj == null) return true; 1003 | if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; 1004 | for (var key in obj) if (_.has(obj, key)) return false; 1005 | return true; 1006 | }; 1007 | 1008 | // Is a given value a DOM element? 1009 | _.isElement = function(obj) { 1010 | return !!(obj && obj.nodeType === 1); 1011 | }; 1012 | 1013 | // Is a given value an array? 1014 | // Delegates to ECMA5's native Array.isArray 1015 | _.isArray = nativeIsArray || function(obj) { 1016 | return toString.call(obj) == '[object Array]'; 1017 | }; 1018 | 1019 | // Is a given variable an object? 1020 | _.isObject = function(obj) { 1021 | return obj === Object(obj); 1022 | }; 1023 | 1024 | // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. 1025 | each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { 1026 | _['is' + name] = function(obj) { 1027 | return toString.call(obj) == '[object ' + name + ']'; 1028 | }; 1029 | }); 1030 | 1031 | // Define a fallback version of the method in browsers (ahem, IE), where 1032 | // there isn't any inspectable "Arguments" type. 1033 | if (!_.isArguments(arguments)) { 1034 | _.isArguments = function(obj) { 1035 | return !!(obj && _.has(obj, 'callee')); 1036 | }; 1037 | } 1038 | 1039 | // Optimize `isFunction` if appropriate. 1040 | if (typeof (/./) !== 'function') { 1041 | _.isFunction = function(obj) { 1042 | return typeof obj === 'function'; 1043 | }; 1044 | } 1045 | 1046 | // Is a given object a finite number? 1047 | _.isFinite = function(obj) { 1048 | return isFinite(obj) && !isNaN(parseFloat(obj)); 1049 | }; 1050 | 1051 | // Is the given value `NaN`? (NaN is the only number which does not equal itself). 1052 | _.isNaN = function(obj) { 1053 | return _.isNumber(obj) && obj != +obj; 1054 | }; 1055 | 1056 | // Is a given value a boolean? 1057 | _.isBoolean = function(obj) { 1058 | return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; 1059 | }; 1060 | 1061 | // Is a given value equal to null? 1062 | _.isNull = function(obj) { 1063 | return obj === null; 1064 | }; 1065 | 1066 | // Is a given variable undefined? 1067 | _.isUndefined = function(obj) { 1068 | return obj === void 0; 1069 | }; 1070 | 1071 | // Shortcut function for checking if an object has a given property directly 1072 | // on itself (in other words, not on a prototype). 1073 | _.has = function(obj, key) { 1074 | return hasOwnProperty.call(obj, key); 1075 | }; 1076 | 1077 | // Utility Functions 1078 | // ----------------- 1079 | 1080 | // Run Underscore.js in *noConflict* mode, returning the `_` variable to its 1081 | // previous owner. Returns a reference to the Underscore object. 1082 | _.noConflict = function() { 1083 | root._ = previousUnderscore; 1084 | return this; 1085 | }; 1086 | 1087 | // Keep the identity function around for default iterators. 1088 | _.identity = function(value) { 1089 | return value; 1090 | }; 1091 | 1092 | _.constant = function(value) { 1093 | return function () { 1094 | return value; 1095 | }; 1096 | }; 1097 | 1098 | _.property = function(key) { 1099 | return function(obj) { 1100 | return obj[key]; 1101 | }; 1102 | }; 1103 | 1104 | // Returns a predicate for checking whether an object has a given set of `key:value` pairs. 1105 | _.matches = function(attrs) { 1106 | return function(obj) { 1107 | if (obj === attrs) return true; //avoid comparing an object to itself. 1108 | for (var key in attrs) { 1109 | if (attrs[key] !== obj[key]) 1110 | return false; 1111 | } 1112 | return true; 1113 | } 1114 | }; 1115 | 1116 | // Run a function **n** times. 1117 | _.times = function(n, iterator, context) { 1118 | var accum = Array(Math.max(0, n)); 1119 | for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); 1120 | return accum; 1121 | }; 1122 | 1123 | // Return a random integer between min and max (inclusive). 1124 | _.random = function(min, max) { 1125 | if (max == null) { 1126 | max = min; 1127 | min = 0; 1128 | } 1129 | return min + Math.floor(Math.random() * (max - min + 1)); 1130 | }; 1131 | 1132 | // A (possibly faster) way to get the current timestamp as an integer. 1133 | _.now = Date.now || function() { return new Date().getTime(); }; 1134 | 1135 | // List of HTML entities for escaping. 1136 | var entityMap = { 1137 | escape: { 1138 | '&': '&', 1139 | '<': '<', 1140 | '>': '>', 1141 | '"': '"', 1142 | "'": ''' 1143 | } 1144 | }; 1145 | entityMap.unescape = _.invert(entityMap.escape); 1146 | 1147 | // Regexes containing the keys and values listed immediately above. 1148 | var entityRegexes = { 1149 | escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), 1150 | unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') 1151 | }; 1152 | 1153 | // Functions for escaping and unescaping strings to/from HTML interpolation. 1154 | _.each(['escape', 'unescape'], function(method) { 1155 | _[method] = function(string) { 1156 | if (string == null) return ''; 1157 | return ('' + string).replace(entityRegexes[method], function(match) { 1158 | return entityMap[method][match]; 1159 | }); 1160 | }; 1161 | }); 1162 | 1163 | // If the value of the named `property` is a function then invoke it with the 1164 | // `object` as context; otherwise, return it. 1165 | _.result = function(object, property) { 1166 | if (object == null) return void 0; 1167 | var value = object[property]; 1168 | return _.isFunction(value) ? value.call(object) : value; 1169 | }; 1170 | 1171 | // Add your own custom functions to the Underscore object. 1172 | _.mixin = function(obj) { 1173 | each(_.functions(obj), function(name) { 1174 | var func = _[name] = obj[name]; 1175 | _.prototype[name] = function() { 1176 | var args = [this._wrapped]; 1177 | push.apply(args, arguments); 1178 | return result.call(this, func.apply(_, args)); 1179 | }; 1180 | }); 1181 | }; 1182 | 1183 | // Generate a unique integer id (unique within the entire client session). 1184 | // Useful for temporary DOM ids. 1185 | var idCounter = 0; 1186 | _.uniqueId = function(prefix) { 1187 | var id = ++idCounter + ''; 1188 | return prefix ? prefix + id : id; 1189 | }; 1190 | 1191 | // By default, Underscore uses ERB-style template delimiters, change the 1192 | // following template settings to use alternative delimiters. 1193 | _.templateSettings = { 1194 | evaluate : /<%([\s\S]+?)%>/g, 1195 | interpolate : /<%=([\s\S]+?)%>/g, 1196 | escape : /<%-([\s\S]+?)%>/g 1197 | }; 1198 | 1199 | // When customizing `templateSettings`, if you don't want to define an 1200 | // interpolation, evaluation or escaping regex, we need one that is 1201 | // guaranteed not to match. 1202 | var noMatch = /(.)^/; 1203 | 1204 | // Certain characters need to be escaped so that they can be put into a 1205 | // string literal. 1206 | var escapes = { 1207 | "'": "'", 1208 | '\\': '\\', 1209 | '\r': 'r', 1210 | '\n': 'n', 1211 | '\t': 't', 1212 | '\u2028': 'u2028', 1213 | '\u2029': 'u2029' 1214 | }; 1215 | 1216 | var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; 1217 | 1218 | // JavaScript micro-templating, similar to John Resig's implementation. 1219 | // Underscore templating handles arbitrary delimiters, preserves whitespace, 1220 | // and correctly escapes quotes within interpolated code. 1221 | _.template = function(text, data, settings) { 1222 | var render; 1223 | settings = _.defaults({}, settings, _.templateSettings); 1224 | 1225 | // Combine delimiters into one regular expression via alternation. 1226 | var matcher = new RegExp([ 1227 | (settings.escape || noMatch).source, 1228 | (settings.interpolate || noMatch).source, 1229 | (settings.evaluate || noMatch).source 1230 | ].join('|') + '|$', 'g'); 1231 | 1232 | // Compile the template source, escaping string literals appropriately. 1233 | var index = 0; 1234 | var source = "__p+='"; 1235 | text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { 1236 | source += text.slice(index, offset) 1237 | .replace(escaper, function(match) { return '\\' + escapes[match]; }); 1238 | 1239 | if (escape) { 1240 | source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; 1241 | } 1242 | if (interpolate) { 1243 | source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; 1244 | } 1245 | if (evaluate) { 1246 | source += "';\n" + evaluate + "\n__p+='"; 1247 | } 1248 | index = offset + match.length; 1249 | return match; 1250 | }); 1251 | source += "';\n"; 1252 | 1253 | // If a variable is not specified, place data values in local scope. 1254 | if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; 1255 | 1256 | source = "var __t,__p='',__j=Array.prototype.join," + 1257 | "print=function(){__p+=__j.call(arguments,'');};\n" + 1258 | source + "return __p;\n"; 1259 | 1260 | try { 1261 | render = new Function(settings.variable || 'obj', '_', source); 1262 | } catch (e) { 1263 | e.source = source; 1264 | throw e; 1265 | } 1266 | 1267 | if (data) return render(data, _); 1268 | var template = function(data) { 1269 | return render.call(this, data, _); 1270 | }; 1271 | 1272 | // Provide the compiled function source as a convenience for precompilation. 1273 | template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; 1274 | 1275 | return template; 1276 | }; 1277 | 1278 | // Add a "chain" function, which will delegate to the wrapper. 1279 | _.chain = function(obj) { 1280 | return _(obj).chain(); 1281 | }; 1282 | 1283 | // OOP 1284 | // --------------- 1285 | // If Underscore is called as a function, it returns a wrapped object that 1286 | // can be used OO-style. This wrapper holds altered versions of all the 1287 | // underscore functions. Wrapped objects may be chained. 1288 | 1289 | // Helper function to continue chaining intermediate results. 1290 | var result = function(obj) { 1291 | return this._chain ? _(obj).chain() : obj; 1292 | }; 1293 | 1294 | // Add all of the Underscore functions to the wrapper object. 1295 | _.mixin(_); 1296 | 1297 | // Add all mutator Array functions to the wrapper. 1298 | each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { 1299 | var method = ArrayProto[name]; 1300 | _.prototype[name] = function() { 1301 | var obj = this._wrapped; 1302 | method.apply(obj, arguments); 1303 | if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; 1304 | return result.call(this, obj); 1305 | }; 1306 | }); 1307 | 1308 | // Add all accessor Array functions to the wrapper. 1309 | each(['concat', 'join', 'slice'], function(name) { 1310 | var method = ArrayProto[name]; 1311 | _.prototype[name] = function() { 1312 | return result.call(this, method.apply(this._wrapped, arguments)); 1313 | }; 1314 | }); 1315 | 1316 | _.extend(_.prototype, { 1317 | 1318 | // Start chaining a wrapped Underscore object. 1319 | chain: function() { 1320 | this._chain = true; 1321 | return this; 1322 | }, 1323 | 1324 | // Extracts the result from a wrapped and chained object. 1325 | value: function() { 1326 | return this._wrapped; 1327 | } 1328 | 1329 | }); 1330 | 1331 | // AMD registration happens at the end for compatibility with AMD loaders 1332 | // that may not enforce next-turn semantics on modules. Even though general 1333 | // practice for AMD registration is to be anonymous, underscore registers 1334 | // as a named module because, like jQuery, it is a base library that is 1335 | // popular enough to be bundled in a third party lib, but not be part of 1336 | // an AMD load request. Those cases could generate an error when an 1337 | // anonymous define() is called outside of a loader request. 1338 | if (typeof define === 'function' && define.amd) { 1339 | define('underscore', [], function() { 1340 | return _; 1341 | }); 1342 | } 1343 | }).call(this); 1344 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/lib/assets/.keep -------------------------------------------------------------------------------- /lib/auth_token.rb: -------------------------------------------------------------------------------- 1 | require 'jwt' 2 | 3 | module AuthToken 4 | def AuthToken.issue_token(payload) 5 | # Set expiration to 24 hours. 6 | JWT.encode(payload, Rails.application.secrets.secret_key_base, claims: { exp: 86400 }) 7 | end 8 | 9 | def AuthToken.valid?(token) 10 | begin 11 | JWT.decode(token, Rails.application.secrets.secret_key_base) 12 | rescue 13 | false 14 | end 15 | end 16 | end -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/lib/tasks/.keep -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/log/.keep -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The page you were looking for doesn't exist.

62 |

You may have mistyped the address or the page may have moved.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

The change you wanted was rejected.

62 |

Maybe you tried to change something you didn't have access to.

63 |
64 |

If you are the application owner check the logs for more information.

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

If you are the application owner check the logs for more information.

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/public/favicon.ico -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Foxandxss/rails-angular-jwt-example/69c0df72f598c6f9834cb7cd33de988e66b101c4/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------