+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Debitis minima natus assumenda, iste nesciunt odio dolorum. Eveniet accusamus, quis, esse dignissimos ex fuga est, excepturi ab non itaque nostrum quae.
+
+
About Me
+
+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas viverra tellus nisi, id condimentum dui tristique sed. Donec luctus eros quis ante consequat sagittis. Phasellus consectetur lectus ac lectus mattis vulputate. Maecenas non risus tellus. Proin ullamcorper, ex id iaculis scelerisque, odio dui posuere purus, sit amet viverra mi nibh sit amet risus. Duis tempor pretium arcu, ut dictum justo interdum pharetra. Suspendisse vulputate pellentesque massa, eu gravida diam semper ut. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam pharetra erat eget vulputate lacinia. Aenean in porta elit. Aenean egestas in urna id bibendum. Integer nec placerat metus.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/views/layouts/about_edit.html b/app/views/layouts/about_edit.html
new file mode 100644
index 000000000..4ad4f9907
--- /dev/null
+++ b/app/views/layouts/about_edit.html
@@ -0,0 +1,148 @@
+
+
+
+
+
+
+
+
+
+ Danebook About Me
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Asperiores harum quisquam nostrum veritatis, provident maiores deleniti vero earum sunt ex quam modi magni, sed hic atque rerum eveniet? Nam, voluptatum.
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Asperiores harum quisquam nostrum veritatis, provident maiores deleniti vero earum sunt ex quam modi magni, sed hic atque rerum eveniet? Nam, voluptatum.
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Asperiores harum quisquam nostrum veritatis, provident maiores deleniti vero earum sunt ex quam modi magni, sed hic atque rerum eveniet? Nam, voluptatum.
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut at, necessitatibus accusamus quo impedit, laboriosam nemo aut facere iure eos ex omnis nihil nesciunt! Placeat unde animi vitae dolore mollitia. ipsum dolor sit amet, consectetur adipisicing elit. Iusto dicta tenetur, est nemo in tempore obcaecati commodi architecto provident necessitatibus reiciendis delectus veritatis rerum ipsum ipsam corrupti, doloribus possimus a. ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur, veritatis, accusamus! Laboriosam ducimus error dolor natus neque quis consectetur deleniti magni sapiente expedita, iure animi in corporis inventore architecto. A.
+
+ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut at, necessitatibus accusamus quo impedit, laboriosam nemo aut facere iure eos ex omnis nihil nesciunt! Placeat unde animi vitae dolore mollitia. ipsum dolor sit amet, consectetur adipisicing elit. Iusto dicta tenetur, est nemo in tempore obcaecati commodi architecto provident necessitatibus reiciendis delectus veritatis rerum ipsum ipsam corrupti, doloribus possimus a. ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur, veritatis, accusamus! Laboriosam ducimus error dolor natus neque quis consectetur deleniti magni sapiente expedita, iure animi in corporis inventore architecto. A.
+
diff --git a/app/views/profile_mailer/welcome.html.erb b/app/views/profile_mailer/welcome.html.erb
new file mode 100644
index 000000000..9f0d7bbb7
--- /dev/null
+++ b/app/views/profile_mailer/welcome.html.erb
@@ -0,0 +1,67 @@
+
+
Hi <%= @profile.first_name %>!
+
+
+
Thank you for your interest in Danebook, the number 1 nordic social network. To get the most from your new account, here's what to do:
+
+
+
The first item on your list should be to finish filling out your profile.
+
+
+
+
+
1.
+
<%= link_to "Log in", login_url %> to the Danebook
+
+
+
+
+
+
+
2.
+
Click the Edit Profile Link
+
+
+
+
+
+
+
3.
+
Fill out any additional info you want your friends to see, and add a slogan
+
+
+
+
+
+
+
4.
+
Select a profile image to use with your account
+
+
+
+
+
+
+
5.
+
Save!
+
+
+
+
+
Once you've finished editing your profile, you'll want to find some friends. You can search for users to friend using the search bar at the top of any page. You can browse through all of Danebooks vikings by leaving the search bar blank. "enter"
+
+
+
Press the "enter" key on your keyboard while focused in the search bar to start your search.
+
+
+
Once you're done adding friends, make sure to add some posts so your friends can see what's on your mind!
+
+
+
That's it! you now have a solid start on your new social network. We hope you enjoy sharing with your fellow plunderers.
+
+
+
Cheers,
+
+
+
Sampson Crowley
+
diff --git a/app/views/profile_mailer/welcome.text.erb b/app/views/profile_mailer/welcome.text.erb
new file mode 100644
index 000000000..e69de29bb
diff --git a/app/views/profiles/edit.html.erb b/app/views/profiles/edit.html.erb
new file mode 100644
index 000000000..1c39a9659
--- /dev/null
+++ b/app/views/profiles/edit.html.erb
@@ -0,0 +1,75 @@
+<%= content_for :usernav, true %>
+
+
+
diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb
new file mode 100644
index 000000000..3d4fd76ad
--- /dev/null
+++ b/app/views/users/new.html.erb
@@ -0,0 +1,126 @@
+
+
Connect with all your friends!
+
+
See photos and updates in your newsfeed.
+
Post your status for the world to see from your profile.
+
Get in touch with your friends by "friending" them.
+
Like things because you're a positive person!
+
+
+
+
Sign Up
+
+
+
+
+
+
+
+
+ <%= form_for @user, html: { class: 'cf-main-form' } do |f| %>
+ <%= f.fields_for :profile do |pf| %>
+
+
+
+
+
+ <%= pf.label :first_name, "First Name:", class:'sr-only' %>
+ <%= pf.text_field :first_name, class: "form-control", placeholder: "First Name", required: "true",
+ "cf-questions" => "Please tell me your First Name." %>
+
+
+
+
+ <%= pf.label :last_name, "Last Name:", class:'sr-only' %>
+ <%= pf.text_field :last_name, class: "form-control", placeholder: "Last Name", required: "true",
+ "cf-questions" => "Thanks {previous-answer}! Now please tell me your Last Name." %>
+
+
+
+
+
+
+ <%= f.label :email, "Your Email:", class:'sr-only' %>
+ <%= f.email_field :email, class: "form-control", placeholder: "Your Email", required: "true", "cf-validation-email" => "",
+ "cf-questions" => "Thanks! What is the best email to reach you at?" %>
+
+
+ <%= f.label :password, "Your New Password:", class:'sr-only' %>
+ <%= f.password_field :password, class: "form-control",
+ placeholder: "Your New Password",
+ required: "true",
+ pattern: '(?=.*\d)(?=.*[a-z])(?=.*[A-Z]).{12,}',
+ :"cf-error" => "Min strength: 12 characters, 1 Uppercase, 1 Lowercase, 1 Digit",
+ "cf-questions" => "Now enter the password you would like to use for your new account. Don't worry, it won't be displayed and I can't see it." %>
+
+
+ <%= f.label :password_confirmation, "Confirm Your Password:", class:'sr-only' %>
+ <%= f.password_field :password_confirmation, class: "form-control", placeholder: "Confirm Your Password", required: "true",
+ "cf-questions" => "Please enter your password again to make sure you typed it correctly. If you need to change what you entered, click the \"Normal Form\" Button" %>
+
+
+
diff --git a/bin/bundle b/bin/bundle
new file mode 100755
index 000000000..66e9889e8
--- /dev/null
+++ b/bin/bundle
@@ -0,0 +1,3 @@
+#!/usr/bin/env ruby
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
+load Gem.bin_path('bundler', 'bundle')
diff --git a/bin/delayed_job b/bin/delayed_job
new file mode 100755
index 000000000..edf195985
--- /dev/null
+++ b/bin/delayed_job
@@ -0,0 +1,5 @@
+#!/usr/bin/env ruby
+
+require File.expand_path(File.join(File.dirname(__FILE__), '..', 'config', 'environment'))
+require 'delayed/command'
+Delayed::Command.new(ARGV).daemonize
diff --git a/bin/rails b/bin/rails
new file mode 100755
index 000000000..5badb2fde
--- /dev/null
+++ b/bin/rails
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+APP_PATH = File.expand_path('../config/application', __dir__)
+require_relative '../config/boot'
+require 'rails/commands'
diff --git a/bin/rake b/bin/rake
new file mode 100755
index 000000000..d87d5f578
--- /dev/null
+++ b/bin/rake
@@ -0,0 +1,9 @@
+#!/usr/bin/env ruby
+begin
+ load File.expand_path('../spring', __FILE__)
+rescue LoadError => e
+ raise unless e.message.include?('spring')
+end
+require_relative '../config/boot'
+require 'rake'
+Rake.application.run
diff --git a/bin/setup b/bin/setup
new file mode 100755
index 000000000..e620b4dad
--- /dev/null
+++ b/bin/setup
@@ -0,0 +1,34 @@
+#!/usr/bin/env ruby
+require 'pathname'
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a starting point to setup your application.
+ # Add necessary setup steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
+
+ # puts "\n== Copying sample files =="
+ # unless File.exist?('config/database.yml')
+ # cp 'config/database.yml.sample', 'config/database.yml'
+ # end
+
+ puts "\n== Preparing database =="
+ system! 'bin/rails db:setup'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rails log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rails restart'
+end
diff --git a/bin/spring b/bin/spring
new file mode 100755
index 000000000..9bc076b9e
--- /dev/null
+++ b/bin/spring
@@ -0,0 +1,16 @@
+#!/usr/bin/env ruby
+
+# This file loads spring without using Bundler, in order to be fast.
+# It gets overwritten when you run the `spring binstub` command.
+
+unless defined?(Spring)
+ require 'rubygems'
+ require 'bundler'
+
+ lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read)
+ if spring = lockfile.specs.detect { |spec| spec.name == "spring" }
+ Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path
+ gem 'spring', spring.version
+ require 'spring/binstub'
+ end
+end
diff --git a/bin/update b/bin/update
new file mode 100755
index 000000000..a8e4462f2
--- /dev/null
+++ b/bin/update
@@ -0,0 +1,29 @@
+#!/usr/bin/env ruby
+require 'pathname'
+require 'fileutils'
+include FileUtils
+
+# path to your application root.
+APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
+
+def system!(*args)
+ system(*args) || abort("\n== Command #{args} failed ==")
+end
+
+chdir APP_ROOT do
+ # This script is a way to update your development environment automatically.
+ # Add necessary update steps to this file.
+
+ puts '== Installing dependencies =='
+ system! 'gem install bundler --conservative'
+ system('bundle check') || system!('bundle install')
+
+ puts "\n== Updating database =="
+ system! 'bin/rails db:migrate'
+
+ puts "\n== Removing old logs and tempfiles =="
+ system! 'bin/rails log:clear tmp:clear'
+
+ puts "\n== Restarting application server =="
+ system! 'bin/rails restart'
+end
diff --git a/config.ru b/config.ru
new file mode 100644
index 000000000..f7ba0b527
--- /dev/null
+++ b/config.ru
@@ -0,0 +1,5 @@
+# This file is used by Rack-based servers to start the application.
+
+require_relative 'config/environment'
+
+run Rails.application
diff --git a/config/application.rb b/config/application.rb
new file mode 100644
index 000000000..98b6bae70
--- /dev/null
+++ b/config/application.rb
@@ -0,0 +1,23 @@
+require_relative 'boot'
+
+require 'rails/all'
+
+# Require the gems listed in Gemfile, including any gems
+# you've limited to :test, :development, or :production.
+Bundler.require(*Rails.groups)
+
+module ProjectDanebook
+ class Application < Rails::Application
+ # Settings in config/environments/* take precedence over those specified here.
+ # Application configuration should go into files in config/initializers
+ # -- all .rb files in that directory are automatically loaded.
+ # Do not swallow errors in after_commit/after_rollback callbacks.
+ config.active_record.raise_in_transactional_callbacks = true
+ config.active_job.queue_adapter = :delayed_job
+
+ config.after_initialize do
+ FixCountersJob.perform_later if Delayed::Job.all.none? {|job| !!(job.handler =~ /FixCountersJob/) }
+ end
+
+ end
+end
diff --git a/config/boot.rb b/config/boot.rb
new file mode 100644
index 000000000..30f5120df
--- /dev/null
+++ b/config/boot.rb
@@ -0,0 +1,3 @@
+ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
+
+require 'bundler/setup' # Set up gems listed in the Gemfile.
diff --git a/config/cable.yml b/config/cable.yml
new file mode 100644
index 000000000..0bbde6f74
--- /dev/null
+++ b/config/cable.yml
@@ -0,0 +1,9 @@
+development:
+ adapter: async
+
+test:
+ adapter: async
+
+production:
+ adapter: redis
+ url: redis://localhost:6379/1
diff --git a/config/database.yml b/config/database.yml
new file mode 100644
index 000000000..e7c6a08cf
--- /dev/null
+++ b/config/database.yml
@@ -0,0 +1,85 @@
+# PostgreSQL. Versions 9.1 and up are supported.
+#
+# Install the pg driver:
+# gem install pg
+# On OS X with Homebrew:
+# gem install pg -- --with-pg-config=/usr/local/bin/pg_config
+# On OS X with MacPorts:
+# gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config
+# On Windows:
+# gem install pg
+# Choose the win32 build.
+# Install PostgreSQL and put its /bin directory on your path.
+#
+# Configure Using Gemfile
+# gem 'pg'
+#
+default: &default
+ adapter: postgresql
+ encoding: unicode
+ # For details on connection pooling, see rails configuration guide
+ # http://guides.rubyonrails.org/configuring.html#database-pooling
+ pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
+
+development:
+ <<: *default
+ database: project_danebook_development
+
+ # The specified database role being used to connect to postgres.
+ # To create additional roles in postgres see `$ createuser --help`.
+ # When left blank, postgres will use the default role. This is
+ # the same name as the operating system user that initialized the database.
+ #username: project_danebook
+
+ # The password associated with the postgres role (username).
+ #password:
+
+ # Connect on a TCP socket. Omitted by default since the client uses a
+ # domain socket that doesn't need configuration. Windows does not have
+ # domain sockets, so uncomment these lines.
+ #host: localhost
+
+ # The TCP port the server listens on. Defaults to 5432.
+ # If your server runs on a different port number, change accordingly.
+ #port: 5432
+
+ # Schema search path. The server defaults to $user,public
+ #schema_search_path: myapp,sharedapp,public
+
+ # Minimum log levels, in increasing order:
+ # debug5, debug4, debug3, debug2, debug1,
+ # log, notice, warning, error, fatal, and panic
+ # Defaults to warning.
+ #min_messages: notice
+
+# Warning: The database defined as "test" will be erased and
+# re-generated from your development database when you run "rake".
+# Do not set this db to the same as development or production.
+test:
+ <<: *default
+ database: project_danebook_test
+
+# As with config/secrets.yml, you never want to store sensitive information,
+# like your database password, in your source code. If your source code is
+# ever seen by anyone, they now have access to your database.
+#
+# Instead, provide the password as a unix environment variable when you boot
+# the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
+# for a full rundown on how to provide these environment variables in a
+# production deployment.
+#
+# On Heroku and other platform providers, you may have a full connection URL
+# available as an environment variable. For example:
+#
+# DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
+#
+# You can use this database configuration with:
+#
+# production:
+# url: <%= ENV['DATABASE_URL'] %>
+#
+production:
+ <<: *default
+ database: project_danebook_production
+ username: project_danebook
+ password: <%= ENV['PROJECT_DANEBOOK_DATABASE_PASSWORD'] %>
diff --git a/config/environment.rb b/config/environment.rb
new file mode 100644
index 000000000..426333bb4
--- /dev/null
+++ b/config/environment.rb
@@ -0,0 +1,5 @@
+# Load the Rails application.
+require_relative 'application'
+
+# Initialize the Rails application.
+Rails.application.initialize!
diff --git a/config/environments/development.rb b/config/environments/development.rb
new file mode 100644
index 000000000..9c801f95b
--- /dev/null
+++ b/config/environments/development.rb
@@ -0,0 +1,92 @@
+Rails.configuration.aws_images = true
+
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # In the development environment your application's code is reloaded on
+ # every request. This slows down response time but is perfect for development
+ # since you don't have to restart the web server when you make code changes.
+ config.cache_classes = false
+
+ # Do not eager load code on boot.
+ config.eager_load = false
+
+ # Show full error reports.
+ config.consider_all_requests_local = true
+
+ # Enable/disable caching. By default caching is disabled.
+ if Rails.root.join('tmp/caching-dev.txt').exist?
+ config.action_controller.perform_caching = true
+
+ config.cache_store = :memory_store
+ config.public_file_server.headers = {
+ 'Cache-Control' => 'public, max-age=172800'
+ }
+ else
+ config.action_controller.perform_caching = false
+
+ config.cache_store = :null_store
+ end
+
+ config.action_mailer.default_url_options = { :host => 'localhost:3000' }
+ config.action_mailer.delivery_method = :letter_opener
+
+ # Don't care if the mailer can't send.
+ config.action_mailer.raise_delivery_errors = false
+
+ config.action_mailer.perform_caching = false
+
+ # Print deprecation notices to the Rails logger.
+ config.active_support.deprecation = :log
+
+ # Raise an error on page load if there are pending migrations.
+ config.active_record.migration_error = :page_load
+
+ # Debug mode disables concatenation and preprocessing of assets.
+ # This option may cause significant delays in view rendering with a large
+ # number of complex assets.
+ config.assets.debug = true
+
+ # Suppress logger output for asset requests.
+ config.assets.quiet = true
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
+
+ # Use an evented file watcher to asynchronously detect changes in source code,
+ # routes, locales, etc. This feature depends on the listen gem.
+ config.file_watcher = ActiveSupport::EventedFileUpdateChecker
+
+ config.after_initialize do
+ Bullet.enable = true
+ Bullet.alert = false
+ Bullet.bullet_logger = true
+ Bullet.console = true
+ Bullet.rails_logger = false
+ Bullet.add_footer = true
+ end
+
+
+ config.paperclip_defaults = {
+
+ # Don't forget to make S3 your storage option!
+ storage: :s3,
+
+ s3_region: ENV['AWS_REGION'],
+
+ s3_credentials: {
+
+ # # put your host name here if needed
+ # # see the reading below for more details
+ # # NOTE: This must be the correct region for YOU
+ :s3_host_name => "s3-#{Rails.application.secrets.aws_region}.amazonaws.com",
+
+ # NOTE: these lines are changed to use secrets.yml
+ # from the examples (which use ENV vars instead)
+ bucket: Rails.application.secrets.s3_bucket_name,
+ access_key_id: Rails.application.secrets.aws_access_key_id,
+ secret_access_key: Rails.application.secrets.aws_secret_access_key
+ }
+ }
+
+end
diff --git a/config/environments/production.rb b/config/environments/production.rb
new file mode 100644
index 000000000..670728b10
--- /dev/null
+++ b/config/environments/production.rb
@@ -0,0 +1,126 @@
+Rails.configuration.aws_images = ENV["AWS_ENABLE"] == "enable"
+Rails.application.configure do
+ config.force_ssl = true
+
+ config.action_mailer.smtp_settings = {
+ :address => 'smtp.sendgrid.net',
+ :port => '587',
+ :authentication => :plain,
+ :user_name => ENV['SENDGRID_USERNAME'],
+ :password => ENV['SENDGRID_PASSWORD'],
+ :domain => 'heroku.com',
+ :enable_starttls_auto => true
+ }
+ config.action_mailer.delivery_method ||= :smtp
+
+ # Rails also needs to know where your app is
+ # located to properly configure sending of emails
+ config.action_mailer.default_url_options = {
+ :host => 'https://danebook-sampson-crowley.herokuapp.com',
+ }
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # Code is not reloaded between requests.
+ config.cache_classes = true
+
+ # Eager load code on boot. This eager loads most of Rails and
+ # your application in memory, allowing both threaded web servers
+ # and those relying on copy on write to perform better.
+ # Rake tasks automatically ignore this option for performance.
+ config.eager_load = true
+
+ # Full error reports are disabled and caching is turned on.
+ config.consider_all_requests_local = false
+ config.action_controller.perform_caching = true
+
+ # Disable serving static files from the `/public` folder by default since
+ # Apache or NGINX already handles this.
+ config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
+
+ # Compress JavaScripts and CSS.
+ config.assets.js_compressor = :uglifier
+ # config.assets.css_compressor = :sass
+
+ # Do not fallback to assets pipeline if a precompiled asset is missed.
+ config.assets.compile = false
+
+ # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
+
+ # Enable serving of images, stylesheets, and JavaScripts from an asset server.
+ # config.action_controller.asset_host = 'http://assets.example.com'
+
+ # Specifies the header that your server uses for sending files.
+ # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
+ # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
+
+ # Mount Action Cable outside main process or domain
+ # config.action_cable.mount_path = nil
+ # config.action_cable.url = 'wss://example.com/cable'
+ # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
+
+ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
+ # config.force_ssl = true
+
+ # Use the lowest log level to ensure availability of diagnostic information
+ # when problems arise.
+ config.log_level = :debug
+
+ # Prepend all log lines with the following tags.
+ config.log_tags = [ :request_id ]
+
+ # Use a different cache store in production.
+ # config.cache_store = :mem_cache_store
+
+ # Use a real queuing backend for Active Job (and separate queues per environment)
+ # config.active_job.queue_adapter = :resque
+ # config.active_job.queue_name_prefix = "project_danebook_#{Rails.env}"
+ config.action_mailer.perform_caching = false
+
+ # Ignore bad email addresses and do not raise email delivery errors.
+ # Set this to true and configure the email server for immediate delivery to raise delivery errors.
+ # config.action_mailer.raise_delivery_errors = false
+
+ # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
+ # the I18n.default_locale when a translation cannot be found).
+ config.i18n.fallbacks = true
+
+ # Send deprecation notices to registered listeners.
+ config.active_support.deprecation = :notify
+
+ # Use default logging formatter so that PID and timestamp are not suppressed.
+ config.log_formatter = ::Logger::Formatter.new
+
+ # Use a different logger for distributed setups.
+ # require 'syslog/logger'
+ # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
+
+ if ENV["RAILS_LOG_TO_STDOUT"].present?
+ logger = ActiveSupport::Logger.new(STDOUT)
+ logger.formatter = config.log_formatter
+ config.logger = ActiveSupport::TaggedLogging.new(logger)
+ end
+
+ # Do not dump schema after migrations.
+ config.active_record.dump_schema_after_migration = false
+
+ config.paperclip_defaults = {
+
+ # Don't forget to make S3 your storage option!
+ storage: :s3,
+
+ s3_credentials: {
+
+ # # put your host name here if needed
+ # # see the reading below for more details
+ # # NOTE: This must be the correct region for YOU
+ :s3_host_name => "s3-#{Rails.application.secrets.aws_region}.amazonaws.com",
+
+ # NOTE: these lines are changed to use secrets.yml
+ # from the examples (which use ENV vars instead)
+ s3_region: Rails.application.secrets.aws_region,
+ bucket: Rails.application.secrets.s3_bucket_name,
+ access_key_id: Rails.application.secrets.aws_access_key_id,
+ secret_access_key: Rails.application.secrets.aws_secret_access_key
+ }
+ }
+end
diff --git a/config/environments/test.rb b/config/environments/test.rb
new file mode 100644
index 000000000..d5c39235b
--- /dev/null
+++ b/config/environments/test.rb
@@ -0,0 +1,44 @@
+Rails.configuration.aws_images = false
+
+Rails.application.configure do
+ # Settings specified here will take precedence over those in config/application.rb.
+
+ # The test environment is used exclusively to run your application's
+ # test suite. You never need to work with it otherwise. Remember that
+ # your test database is "scratch space" for the test suite and is wiped
+ # and recreated between test runs. Don't rely on the data there!
+ config.cache_classes = true
+
+ # Do not eager load code on boot. This avoids loading your whole application
+ # just for the purpose of running a single test. If you are using a tool that
+ # preloads Rails for running tests, you may have to set it to true.
+ config.eager_load = false
+
+ # Configure public file server for tests with Cache-Control for performance.
+ config.public_file_server.enabled = true
+ config.public_file_server.headers = {
+ 'Cache-Control' => 'public, max-age=3600'
+ }
+
+ # Show full error reports and disable caching.
+ config.consider_all_requests_local = true
+ config.action_controller.perform_caching = false
+
+ # Raise exceptions instead of rendering exception templates.
+ config.action_dispatch.show_exceptions = false
+
+ # Disable request forgery protection in test environment.
+ config.action_controller.allow_forgery_protection = false
+ config.action_mailer.perform_caching = false
+
+ # Tell Action Mailer not to deliver emails to the real world.
+ # The :test delivery method accumulates sent emails in the
+ # ActionMailer::Base.deliveries array.
+ config.action_mailer.delivery_method = :test
+
+ # Print deprecation notices to the stderr.
+ config.active_support.deprecation = :stderr
+
+ # Raises error for missing translations
+ # config.action_view.raise_on_missing_translations = true
+end
diff --git a/config/initializers/application_controller_renderer.rb b/config/initializers/application_controller_renderer.rb
new file mode 100644
index 000000000..51639b67a
--- /dev/null
+++ b/config/initializers/application_controller_renderer.rb
@@ -0,0 +1,6 @@
+# Be sure to restart your server when you modify this file.
+
+# ApplicationController.renderer.defaults.merge!(
+# http_host: 'example.org',
+# https: false
+# )
diff --git a/config/initializers/assets.rb b/config/initializers/assets.rb
new file mode 100644
index 000000000..01ef3e663
--- /dev/null
+++ b/config/initializers/assets.rb
@@ -0,0 +1,11 @@
+# Be sure to restart your server when you modify this file.
+
+# Version of your assets, change this if you want to expire all your assets.
+Rails.application.config.assets.version = '1.0'
+
+# Add additional assets to the asset load path
+# Rails.application.config.assets.paths << Emoji.images_path
+
+# Precompile additional assets.
+# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
+# Rails.application.config.assets.precompile += %w( search.js )
diff --git a/config/initializers/aws_sdk.rb b/config/initializers/aws_sdk.rb
new file mode 100644
index 000000000..6d1d15eff
--- /dev/null
+++ b/config/initializers/aws_sdk.rb
@@ -0,0 +1,15 @@
+unless Rails.env.production?
+ AWS_Config = YAML.load_file("config/aws.yml")[Rails.env]
+ ENV['AWS_BUCKET_NAME'] = AWS_Config['bucket']
+ ENV['AWS_REGION'] = AWS_Config['region']
+ ENV['AWS_ACCESS_KEY_ID'] = AWS_Config['access_key_id']
+ ENV['AWS_SECRET_ACCESS_KEY'] = AWS_Config['secret_access_key']
+end
+
+Aws.config.update({
+ region: ENV['AWS_REGION'],
+ credentials: Aws::Credentials.new(ENV['AWS_ACCESS_KEY_ID'], ENV['AWS_SECRET_ACCESS_KEY']),
+ })
+
+
+S3_BUCKET = Aws::S3::Resource.new.bucket(ENV['AWS_BUCKET_NAME'])
diff --git a/config/initializers/backtrace_silencers.rb b/config/initializers/backtrace_silencers.rb
new file mode 100644
index 000000000..59385cdf3
--- /dev/null
+++ b/config/initializers/backtrace_silencers.rb
@@ -0,0 +1,7 @@
+# Be sure to restart your server when you modify this file.
+
+# You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
+# Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
+
+# You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
+# Rails.backtrace_cleaner.remove_silencers!
diff --git a/config/initializers/cookies_serializer.rb b/config/initializers/cookies_serializer.rb
new file mode 100644
index 000000000..5a6a32d37
--- /dev/null
+++ b/config/initializers/cookies_serializer.rb
@@ -0,0 +1,5 @@
+# Be sure to restart your server when you modify this file.
+
+# Specify a serializer for the signed and encrypted cookie jars.
+# Valid options are :json, :marshal, and :hybrid.
+Rails.application.config.action_dispatch.cookies_serializer = :json
diff --git a/config/initializers/delayed_job_config.rb b/config/initializers/delayed_job_config.rb
new file mode 100644
index 000000000..a6f45ef56
--- /dev/null
+++ b/config/initializers/delayed_job_config.rb
@@ -0,0 +1,10 @@
+Rails.logger.level = Logger::INFO
+Delayed::Worker.destroy_failed_jobs = true
+Delayed::Worker.sleep_delay = 1
+Delayed::Worker.max_attempts = 3
+Delayed::Worker.max_run_time = 5.minutes
+Delayed::Worker.read_ahead = 10
+Delayed::Worker.default_queue_name = 'default'
+Delayed::Worker.delay_jobs = !Rails.env.test?
+Delayed::Worker.raise_signal_exceptions = :term
+Delayed::Worker.logger = Logger.new(File.join(Rails.root, 'log', 'delayed_job.log'))
diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb
new file mode 100644
index 000000000..4a994e1e7
--- /dev/null
+++ b/config/initializers/filter_parameter_logging.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Configure sensitive parameters which will be filtered from the log file.
+Rails.application.config.filter_parameters += [:password]
diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb
new file mode 100644
index 000000000..ac033bf9d
--- /dev/null
+++ b/config/initializers/inflections.rb
@@ -0,0 +1,16 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new inflection rules using the following format. Inflections
+# are locale specific, and you may define rules for as many different
+# locales as you wish. All of these examples are active by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.plural /^(ox)$/i, '\1en'
+# inflect.singular /^(ox)en/i, '\1'
+# inflect.irregular 'person', 'people'
+# inflect.uncountable %w( fish sheep )
+# end
+
+# These inflection rules are supported but not enabled by default:
+# ActiveSupport::Inflector.inflections(:en) do |inflect|
+# inflect.acronym 'RESTful'
+# end
diff --git a/config/initializers/jazz_fingers.rb b/config/initializers/jazz_fingers.rb
new file mode 100644
index 000000000..399b15033
--- /dev/null
+++ b/config/initializers/jazz_fingers.rb
@@ -0,0 +1,9 @@
+if defined?(JazzFingers)
+ JazzFingers.configure do |config|
+ config.colored_prompt = true
+ config.awesome_print = true
+ config.coolline = false
+ end
+
+ require 'jazz_fingers/setup'
+end
diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb
new file mode 100644
index 000000000..dc1899682
--- /dev/null
+++ b/config/initializers/mime_types.rb
@@ -0,0 +1,4 @@
+# Be sure to restart your server when you modify this file.
+
+# Add new mime types for use in respond_to blocks:
+# Mime::Type.register "text/richtext", :rtf
diff --git a/config/initializers/new_framework_defaults.rb b/config/initializers/new_framework_defaults.rb
new file mode 100644
index 000000000..0706cafd4
--- /dev/null
+++ b/config/initializers/new_framework_defaults.rb
@@ -0,0 +1,24 @@
+# Be sure to restart your server when you modify this file.
+#
+# This file contains migration options to ease your Rails 5.0 upgrade.
+#
+# Read the Rails 5.0 release notes for more info on each option.
+
+# Enable per-form CSRF tokens. Previous versions had false.
+Rails.application.config.action_controller.per_form_csrf_tokens = true
+
+# Enable origin-checking CSRF mitigation. Previous versions had false.
+Rails.application.config.action_controller.forgery_protection_origin_check = true
+
+# Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`.
+# Previous versions had false.
+ActiveSupport.to_time_preserves_timezone = true
+
+# Require `belongs_to` associations by default. Previous versions had false.
+Rails.application.config.active_record.belongs_to_required_by_default = true
+
+# Do not halt callback chains when a callback returns false. Previous versions had true.
+ActiveSupport.halt_callback_chains_on_return_false = false
+
+# Configure SSL options to enable HSTS with subdomains. Previous versions had false.
+Rails.application.config.ssl_options = { hsts: { subdomains: true } }
diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb
new file mode 100644
index 000000000..f2d118131
--- /dev/null
+++ b/config/initializers/session_store.rb
@@ -0,0 +1,3 @@
+# Be sure to restart your server when you modify this file.
+
+Rails.application.config.session_store :cookie_store, key: '_project_danebook_session'
diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb
new file mode 100644
index 000000000..bbfc3961b
--- /dev/null
+++ b/config/initializers/wrap_parameters.rb
@@ -0,0 +1,14 @@
+# Be sure to restart your server when you modify this file.
+
+# This file contains settings for ActionController::ParamsWrapper which
+# is enabled by default.
+
+# Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array.
+ActiveSupport.on_load(:action_controller) do
+ wrap_parameters format: [:json]
+end
+
+# To enable root element in JSON for ActiveRecord objects.
+# ActiveSupport.on_load(:active_record) do
+# self.include_root_in_json = true
+# end
diff --git a/config/locales/en.yml b/config/locales/en.yml
new file mode 100644
index 000000000..065395716
--- /dev/null
+++ b/config/locales/en.yml
@@ -0,0 +1,23 @@
+# Files in the config/locales directory are used for internationalization
+# and are automatically loaded by Rails. If you want to use locales other
+# than English, add the necessary files in this directory.
+#
+# To use the locales, use `I18n.t`:
+#
+# I18n.t 'hello'
+#
+# In views, this is aliased to just `t`:
+#
+# <%= t('hello') %>
+#
+# To use a different locale, set it with `I18n.locale`:
+#
+# I18n.locale = :es
+#
+# This would use the information in config/locales/es.yml.
+#
+# To learn more, please read the Rails Internationalization guide
+# available at http://guides.rubyonrails.org/i18n.html.
+
+en:
+ hello: "Hello world"
diff --git a/config/puma.rb b/config/puma.rb
new file mode 100644
index 000000000..c7f311f81
--- /dev/null
+++ b/config/puma.rb
@@ -0,0 +1,47 @@
+# Puma can serve each request in a thread from an internal thread pool.
+# The `threads` method setting takes two numbers a minimum and maximum.
+# Any libraries that use thread pools should be configured to match
+# the maximum value specified for Puma. Default is set to 5 threads for minimum
+# and maximum, this matches the default thread size of Active Record.
+#
+threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }.to_i
+threads threads_count, threads_count
+
+# Specifies the `port` that Puma will listen on to receive requests, default is 3000.
+#
+port ENV.fetch("PORT") { 3000 }
+
+# Specifies the `environment` that Puma will run in.
+#
+environment ENV.fetch("RAILS_ENV") { "development" }
+
+# Specifies the number of `workers` to boot in clustered mode.
+# Workers are forked webserver processes. If using threads and workers together
+# the concurrency of the application would be max `threads` * `workers`.
+# Workers do not work on JRuby or Windows (both of which do not support
+# processes).
+#
+# workers ENV.fetch("WEB_CONCURRENCY") { 2 }
+
+# Use the `preload_app!` method when specifying a `workers` number.
+# This directive tells Puma to first boot the application and load code
+# before forking the application. This takes advantage of Copy On Write
+# process behavior so workers use less memory. If you use this option
+# you need to make sure to reconnect any threads in the `on_worker_boot`
+# block.
+#
+# preload_app!
+
+# The code in the `on_worker_boot` will be called if you are using
+# clustered mode by specifying a number of `workers`. After each worker
+# process is booted this block will be run, if you are using `preload_app!`
+# option you will want to use this block to reconnect to any threads
+# or connections that may have been created at application boot, Ruby
+# cannot share connections between processes.
+#
+# on_worker_boot do
+# ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
+# end
+
+# Allow puma to be restarted by `rails restart` command.
+plugin :tmp_restart
diff --git a/config/routes.rb b/config/routes.rb
new file mode 100644
index 000000000..85702a19a
--- /dev/null
+++ b/config/routes.rb
@@ -0,0 +1,21 @@
+Rails.application.routes.draw do
+ # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
+ root to: 'users#new'
+ resource :session
+ resources :notices
+ resources :users do
+ resource :profile
+ resources :galleries
+ resource :friends, only: [:show, :create, :destroy]
+ resources :posts do
+ resource :comment, only: [:create, :new]
+ end
+ end
+ resources :likes, only: [:update, :destroy]
+
+ get '/search' => 'search#show'
+ get '/signup' => 'users#new'
+ get '/logout' => 'sessions#destroy'
+ get '/login' => 'sessions#new'
+ match via: [:get, :post], "*path" => redirect("/")
+end
diff --git a/config/secrets.yml b/config/secrets.yml
new file mode 100644
index 000000000..c49229fbd
--- /dev/null
+++ b/config/secrets.yml
@@ -0,0 +1,32 @@
+# Be sure to restart your server when you modify this file.
+
+# Your secret key is used for verifying the integrity of signed cookies.
+# If you change this key, all old signed cookies will become invalid!
+
+# Make sure the secret is at least 30 characters and all random,
+# no regular words or you'll be exposed to dictionary attacks.
+# You can use `rails secret` to generate a secure secret key.
+
+# Make sure the secrets in this file are kept private
+# if you're sharing your code publicly.
+
+development:
+ secret_key_base: 2cca00c02db4d5f66ac00c16bedb1b590086df57ab19f1562085c53ccd136fa3cd3fc7db7200cc7506cba2c0c0f6813894918397d5d87fdd8cc10625b6dded78
+ s3_bucket_name: <%= ENV["S3_BUCKET_NAME"] %>
+ aws_region: <%= ENV['AWS_REGION'] %>
+ aws_access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
+ aws_secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>
+
+test:
+ secret_key_base: 5f3fd4a2322dbfe790f95cba98d8100e76d646f16fbc1019026895142ad7916dde92ced039b45d5185b645ccdf3c7f3732fa8a80fb7a570e33b12fc92fba3bea
+
+# Do not keep production secrets in the repository,
+# instead read values from the environment.
+production:
+ secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
+ s3_bucket_name: <%= ENV["S3_BUCKET_NAME"] %>
+ aws_region: <%= ENV['AWS_REGION'] %>
+ aws_access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %>
+ aws_secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %>
+ sendgrid_username: <%= ENV["SENDGRID_USERNAME"] %>
+ sendgrid_password: <%= ENV["SENDGRID_PASSWORD"] %>
diff --git a/config/spring.rb b/config/spring.rb
new file mode 100644
index 000000000..c9119b40c
--- /dev/null
+++ b/config/spring.rb
@@ -0,0 +1,6 @@
+%w(
+ .ruby-version
+ .rbenv-vars
+ tmp/restart.txt
+ tmp/caching-dev.txt
+).each { |path| Spring.watch(path) }
diff --git a/db/migrate/20161207193754_create_users.rb b/db/migrate/20161207193754_create_users.rb
new file mode 100644
index 000000000..d501dcad6
--- /dev/null
+++ b/db/migrate/20161207193754_create_users.rb
@@ -0,0 +1,13 @@
+class CreateUsers < ActiveRecord::Migration[5.0]
+ def change
+ create_table :users do |t|
+ t.string :email, unique:true, null: false
+ t.string :password_digest, null: false
+ t.string :token
+ t.integer :failed
+ t.datetime :last_attempt
+ t.timestamps
+ end
+ add_index :users, :email, :unique => true
+ end
+end
diff --git a/db/migrate/20161207193850_create_profiles.rb b/db/migrate/20161207193850_create_profiles.rb
new file mode 100644
index 000000000..de3420933
--- /dev/null
+++ b/db/migrate/20161207193850_create_profiles.rb
@@ -0,0 +1,20 @@
+class CreateProfiles < ActiveRecord::Migration[5.0]
+ def change
+ create_table :profiles do |t|
+ t.references :user
+ t.string :first_name
+ t.string :last_name
+ t.date :birthday
+ t.string :gender
+ t.string :college
+ t.string :hometown
+ t.string :current_home
+ t.string :phone
+ t.integer :image_id
+ t.integer :cover_id
+ t.boolean :edited, default: false
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20161209170807_create_posts.rb b/db/migrate/20161209170807_create_posts.rb
new file mode 100644
index 000000000..38fe03e53
--- /dev/null
+++ b/db/migrate/20161209170807_create_posts.rb
@@ -0,0 +1,14 @@
+class CreatePosts < ActiveRecord::Migration[5.0]
+ def change
+ create_table :posts do |t|
+ t.references :user, foreign_key: true
+ t.string :post_type, default: "Post"
+ t.integer :post_id
+ t.text :body
+ t.integer :likes_count, default: 0
+ t.integer :comments_count, default: 0
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20161209195940_create_bios.rb b/db/migrate/20161209195940_create_bios.rb
new file mode 100644
index 000000000..c868fa8cb
--- /dev/null
+++ b/db/migrate/20161209195940_create_bios.rb
@@ -0,0 +1,11 @@
+class CreateBios < ActiveRecord::Migration[5.0]
+ def change
+ create_table :bios do |t|
+ t.references :profile
+ t.string :slogan
+ t.text :about
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20161209221424_create_likes.rb b/db/migrate/20161209221424_create_likes.rb
new file mode 100644
index 000000000..2884b210f
--- /dev/null
+++ b/db/migrate/20161209221424_create_likes.rb
@@ -0,0 +1,11 @@
+class CreateLikes < ActiveRecord::Migration[5.0]
+ def change
+ create_table :likes do |t|
+ t.references :user, foreign_key: true
+ t.references :post, foreign_key: true
+
+ t.timestamps
+ end
+ add_index :likes, [:post_id, :user_id], :unique => true
+ end
+end
diff --git a/db/migrate/20161209235756_create_friends_users.rb b/db/migrate/20161209235756_create_friends_users.rb
new file mode 100644
index 000000000..68543f19b
--- /dev/null
+++ b/db/migrate/20161209235756_create_friends_users.rb
@@ -0,0 +1,10 @@
+class CreateFriendsUsers < ActiveRecord::Migration[5.0]
+ def change
+ create_table :friends_users do |t|
+ t.references :user, foreign_key: true
+ t.references :friend, references: :users
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20161210212836_create_galleries.rb b/db/migrate/20161210212836_create_galleries.rb
new file mode 100644
index 000000000..ccb76cf34
--- /dev/null
+++ b/db/migrate/20161210212836_create_galleries.rb
@@ -0,0 +1,11 @@
+class CreateGalleries < ActiveRecord::Migration[5.0]
+ def change
+ create_table :galleries do |t|
+ t.references :user, foreign_key: true
+ t.string :title
+ t.string :description
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20161210212954_create_images.rb b/db/migrate/20161210212954_create_images.rb
new file mode 100644
index 000000000..8b6ece504
--- /dev/null
+++ b/db/migrate/20161210212954_create_images.rb
@@ -0,0 +1,11 @@
+class CreateImages < ActiveRecord::Migration[5.0]
+ def change
+ create_table :images do |t|
+ t.references :gallery, foreign_key: true
+ t.string :url
+ t.string :description
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20161211173051_create_friend_requests.rb b/db/migrate/20161211173051_create_friend_requests.rb
new file mode 100644
index 000000000..74d672cda
--- /dev/null
+++ b/db/migrate/20161211173051_create_friend_requests.rb
@@ -0,0 +1,10 @@
+class CreateFriendRequests < ActiveRecord::Migration[5.0]
+ def change
+ create_table :friend_requests do |t|
+ t.references :user, foreign_key: true
+ t.integer :request_id
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20161215233239_add_picture_to_images.rb b/db/migrate/20161215233239_add_picture_to_images.rb
new file mode 100644
index 000000000..c761bfc47
--- /dev/null
+++ b/db/migrate/20161215233239_add_picture_to_images.rb
@@ -0,0 +1,5 @@
+class AddPictureToImages < ActiveRecord::Migration[5.0]
+ def change
+ add_attachment :images, :picture
+ end
+end
diff --git a/db/migrate/20161216035452_create_delayed_jobs.rb b/db/migrate/20161216035452_create_delayed_jobs.rb
new file mode 100644
index 000000000..27fdcf6cc
--- /dev/null
+++ b/db/migrate/20161216035452_create_delayed_jobs.rb
@@ -0,0 +1,22 @@
+class CreateDelayedJobs < ActiveRecord::Migration
+ def self.up
+ create_table :delayed_jobs, force: true do |table|
+ table.integer :priority, default: 0, null: false # Allows some jobs to jump to the front of the queue
+ table.integer :attempts, default: 0, null: false # Provides for retries, but still fail eventually.
+ table.text :handler, null: false # YAML-encoded string of the object that will do work
+ table.text :last_error # reason for last failure (See Note below)
+ table.datetime :run_at # When to run. Could be Time.zone.now for immediately, or sometime in the future.
+ table.datetime :locked_at # Set when a client is working on this object
+ table.datetime :failed_at # Set when all retries have failed (actually, by default, the record is deleted instead)
+ table.string :locked_by # Who is working on this object (if locked)
+ table.string :queue # The name of the queue this job is in
+ table.timestamps null: true
+ end
+
+ add_index :delayed_jobs, [:priority, :run_at], name: "delayed_jobs_priority"
+ end
+
+ def self.down
+ drop_table :delayed_jobs
+ end
+end
diff --git a/db/migrate/20161216045136_create_notices.rb b/db/migrate/20161216045136_create_notices.rb
new file mode 100644
index 000000000..e857ac44c
--- /dev/null
+++ b/db/migrate/20161216045136_create_notices.rb
@@ -0,0 +1,12 @@
+class CreateNotices < ActiveRecord::Migration[5.0]
+ def change
+ create_table :notices do |t|
+ t.references :user, foreign_key: true
+ t.boolean :viewed, default: false
+ t.string :title
+ t.string :messages, array: true, default: []
+
+ t.timestamps
+ end
+ end
+end
diff --git a/db/migrate/20161216214852_add_notice_counter_to_users.rb b/db/migrate/20161216214852_add_notice_counter_to_users.rb
new file mode 100644
index 000000000..6eba417b4
--- /dev/null
+++ b/db/migrate/20161216214852_add_notice_counter_to_users.rb
@@ -0,0 +1,5 @@
+class AddNoticeCounterToUsers < ActiveRecord::Migration[5.0]
+ def change
+ add_column :users, :notice_count, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20161217172449_add_post_to_images.rb b/db/migrate/20161217172449_add_post_to_images.rb
new file mode 100644
index 000000000..52fd93308
--- /dev/null
+++ b/db/migrate/20161217172449_add_post_to_images.rb
@@ -0,0 +1,5 @@
+class AddPostToImages < ActiveRecord::Migration[5.0]
+ def change
+ add_reference :images, :post, foreign_key: true
+ end
+end
diff --git a/db/migrate/20161217191858_add_picture_processing_to_image.rb b/db/migrate/20161217191858_add_picture_processing_to_image.rb
new file mode 100644
index 000000000..d4dd66237
--- /dev/null
+++ b/db/migrate/20161217191858_add_picture_processing_to_image.rb
@@ -0,0 +1,9 @@
+class AddPictureProcessingToImage < ActiveRecord::Migration[5.0]
+ def self.up
+ add_column :images, :picture_processing, :boolean
+ end
+
+ def self.down
+ remove_column :images, :picture_processing
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
new file mode 100644
index 000000000..6baa0b502
--- /dev/null
+++ b/db/schema.rb
@@ -0,0 +1,155 @@
+# This file is auto-generated from the current state of the database. Instead
+# of editing this file, please use the migrations feature of Active Record to
+# incrementally modify your database, and then regenerate this schema definition.
+#
+# Note that this schema.rb definition is the authoritative source for your
+# database schema. If you need to create the application database on another
+# system, you should be using db:schema:load, not running all the migrations
+# from scratch. The latter is a flawed and unsustainable approach (the more migrations
+# you'll amass, the slower it'll run and the greater likelihood for issues).
+#
+# It's strongly recommended that you check this file into your version control system.
+
+ActiveRecord::Schema.define(version: 20161217191858) do
+
+ # These are extensions that must be enabled in order to support this database
+ enable_extension "plpgsql"
+
+ create_table "bios", force: :cascade do |t|
+ t.integer "profile_id"
+ t.string "slogan"
+ t.text "about"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["profile_id"], name: "index_bios_on_profile_id", using: :btree
+ end
+
+ create_table "delayed_jobs", force: :cascade do |t|
+ t.integer "priority", default: 0, null: false
+ t.integer "attempts", default: 0, null: false
+ t.text "handler", null: false
+ t.text "last_error"
+ t.datetime "run_at"
+ t.datetime "locked_at"
+ t.datetime "failed_at"
+ t.string "locked_by"
+ t.string "queue"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.index ["priority", "run_at"], name: "delayed_jobs_priority", using: :btree
+ end
+
+ create_table "friend_requests", force: :cascade do |t|
+ t.integer "user_id"
+ t.integer "request_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_friend_requests_on_user_id", using: :btree
+ end
+
+ create_table "friends_users", force: :cascade do |t|
+ t.integer "user_id"
+ t.integer "friend_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["friend_id"], name: "index_friends_users_on_friend_id", using: :btree
+ t.index ["user_id"], name: "index_friends_users_on_user_id", using: :btree
+ end
+
+ create_table "galleries", force: :cascade do |t|
+ t.integer "user_id"
+ t.string "title"
+ t.string "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_galleries_on_user_id", using: :btree
+ end
+
+ create_table "images", force: :cascade do |t|
+ t.integer "gallery_id"
+ t.string "url"
+ t.string "description"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.string "picture_file_name"
+ t.string "picture_content_type"
+ t.integer "picture_file_size"
+ t.datetime "picture_updated_at"
+ t.integer "post_id"
+ t.boolean "picture_processing"
+ t.index ["gallery_id"], name: "index_images_on_gallery_id", using: :btree
+ t.index ["post_id"], name: "index_images_on_post_id", using: :btree
+ end
+
+ create_table "likes", force: :cascade do |t|
+ t.integer "user_id"
+ t.integer "post_id"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["post_id", "user_id"], name: "index_likes_on_post_id_and_user_id", unique: true, using: :btree
+ t.index ["post_id"], name: "index_likes_on_post_id", using: :btree
+ t.index ["user_id"], name: "index_likes_on_user_id", using: :btree
+ end
+
+ create_table "notices", force: :cascade do |t|
+ t.integer "user_id"
+ t.boolean "viewed", default: false
+ t.string "title", null: false
+ t.string "messages", default: [], array: true
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_notices_on_user_id", using: :btree
+ end
+
+ create_table "posts", force: :cascade do |t|
+ t.integer "user_id"
+ t.string "post_type", default: "Post"
+ t.integer "post_id"
+ t.text "body"
+ t.integer "likes_count", default: 0
+ t.integer "comments_count", default: 0
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_posts_on_user_id", using: :btree
+ end
+
+ create_table "profiles", force: :cascade do |t|
+ t.integer "user_id"
+ t.string "first_name"
+ t.string "last_name"
+ t.date "birthday"
+ t.string "gender"
+ t.string "college"
+ t.string "hometown"
+ t.string "current_home"
+ t.string "phone"
+ t.integer "image_id"
+ t.integer "cover_id"
+ t.boolean "edited", default: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.index ["user_id"], name: "index_profiles_on_user_id", using: :btree
+ end
+
+ create_table "users", force: :cascade do |t|
+ t.string "email", null: false
+ t.string "password_digest", null: false
+ t.string "token"
+ t.integer "failed"
+ t.datetime "last_attempt"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ t.integer "notice_count", default: 0
+ t.index ["email"], name: "index_users_on_email", unique: true, using: :btree
+ end
+
+ add_foreign_key "friend_requests", "users"
+ add_foreign_key "friends_users", "users"
+ add_foreign_key "galleries", "users"
+ add_foreign_key "images", "galleries"
+ add_foreign_key "images", "posts"
+ add_foreign_key "likes", "posts"
+ add_foreign_key "likes", "users"
+ add_foreign_key "notices", "users"
+ add_foreign_key "posts", "users"
+end
diff --git a/db/seeds.rb b/db/seeds.rb
new file mode 100644
index 000000000..87cca2848
--- /dev/null
+++ b/db/seeds.rb
@@ -0,0 +1,105 @@
+# This file should contain all the record creation needed to seed the database with its default values.
+# The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
+#
+# Examples:
+#
+# movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
+# Character.create(name: 'Luke', movie: movies.first)
+p "Destroying Users"
+
+User.destroy_all
+
+genders = {0 => "Male", 1 => "Female", 2 => "Other"}
+
+p "Creating Users"
+def randomize?
+ rand(9) > 4
+end
+20.times do |i|
+ hometown = randomize? ? nil : Faker::Hipster.word
+ p User.create(
+ email: "foo#{i}@bar.com",
+ password: "1234Qwerasdfzxcv",
+ password_confirmation: "1234Qwerasdfzxcv",
+ profile_attributes:
+ {
+ first_name: Faker::Name.first_name,
+ last_name: Faker::Name.last_name,
+ birthday: rand(10000).days.ago.to_date,
+ gender: genders[rand(3)],
+ college: randomize? ? nil : Faker::University.name,
+ hometown: hometown,
+ current_home: randomize? ? hometown : Faker::Hipster.word,
+ phone: randomize? ? hometown : Faker::Hipster.word
+ }
+
+ )
+end
+
+user_ids = User.pluck(:id)
+
+p "creating posts"
+user_ids.each do |u_id|
+ rand(20).times do
+ Post.create(
+ user_id: u_id,
+ post_type: "Post",
+ body: randomize? ? Faker::Hipster.paragraph(2, false, 4) : Faker::Hacker.say_something_smart,
+ likes_count: 0,
+ comments_count: 0,
+ )
+ end
+end
+
+image_urls = ["https://s-media-cache-ak0.pinimg.com/736x/9b/79/52/9b795278d51497222d70722e3ab110ca.jpg",
+ "https://s-media-cache-ak0.pinimg.com/736x/01/0b/68/010b68214bf1eeb91060732aa58bed1e.jpg",
+ "http://www.funny-meme-pictures.com/wp-content/uploads/2013/11/11162013-funny-memes-105.jpg",
+ "https://s-media-cache-ak0.pinimg.com/originals/77/92/00/779200532083a9b899047e361e055658.jpg",
+ "http://i0.wp.com/pictures.jokofy.com/wp/wp-content/uploads/2015/12/Cute-and-funny-fat-child-meme.jpg?fit=600%2C384",
+ "http://www.dumpaday.com/wp-content/uploads/2016/04/funny-25.png"
+
+ ]
+
+p "creating images and friends"
+user_ids.each do |u_id|
+ rand(3).times do
+ gal = Gallery.create(
+ user_id: u_id,
+ title: Faker::Hipster.words(rand(1..5)).join(" "),
+ description: Faker::Hacker.say_something_smart
+ )
+ image_urls.each do |url|
+ Image.create(
+ gallery_id: gal.id,
+ url: url,
+ description: Faker::Hacker.say_something_smart,
+ )
+ end
+ img = gal.images.sample
+ img.set_profile_photo = "1"
+ end
+ 10.times do
+ Friendify.friendship(User.find_by(id: u_id), User.find_by(id: user_ids.sample))
+ end
+end
+
+p "SLEEPING UNTIL ALL JOBS COMPLETE"
+sleep(1) until Delayed::Job.count < 2
+
+post_ids = Post.pluck(:id)
+
+p "creating comments"
+Post.all.each do |post|
+ if post.user.friends && post.user.friends.count > 0
+ rand(2..10).times do
+ p Post.create(
+ user_id: post.user.friend_ids.sample,
+ post_id: post.id,
+ post_type: "Comment",
+ body: randomize? ? Faker::Hipster.paragraph(2, false, 4) : Faker::Hacker.say_something_smart,
+ likes_count: 0,
+ comments_count: 0,
+ )
+ end
+ end
+end
diff --git a/jquery.fileupload.js b/jquery.fileupload.js
new file mode 100644
index 000000000..4d41f1e95
--- /dev/null
+++ b/jquery.fileupload.js
@@ -0,0 +1,1482 @@
+/*
+ * jQuery File Upload Plugin
+ * https://github.com/blueimp/jQuery-File-Upload
+ *
+ * Copyright 2010, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * http://www.opensource.org/licenses/MIT
+ */
+
+/* jshint nomen:false */
+/* global define, require, window, document, location, Blob, FormData */
+
+;(function (factory) {
+ 'use strict';
+ if (typeof define === 'function' && define.amd) {
+ // Register as an anonymous AMD module:
+ define([
+ 'jquery',
+ 'jquery-ui/widget'
+ ], factory);
+ } else if (typeof exports === 'object') {
+ // Node/CommonJS:
+ factory(
+ require('jquery'),
+ require('./vendor/jquery.ui.widget')
+ );
+ } else {
+ // Browser globals:
+ factory(window.jQuery);
+ }
+}(function ($) {
+ 'use strict';
+
+ // Detect file input support, based on
+ // http://viljamis.com/blog/2012/file-upload-support-on-mobile/
+ $.support.fileInput = !(new RegExp(
+ // Handle devices which give false positives for the feature detection:
+ '(Android (1\\.[0156]|2\\.[01]))' +
+ '|(Windows Phone (OS 7|8\\.0))|(XBLWP)|(ZuneWP)|(WPDesktop)' +
+ '|(w(eb)?OSBrowser)|(webOS)' +
+ '|(Kindle/(1\\.0|2\\.[05]|3\\.0))'
+ ).test(window.navigator.userAgent) ||
+ // Feature detection for all other devices:
+ $('').prop('disabled'));
+
+ // The FileReader API is not actually used, but works as feature detection,
+ // as some Safari versions (5?) support XHR file uploads via the FormData API,
+ // but not non-multipart XHR file uploads.
+ // window.XMLHttpRequestUpload is not available on IE10, so we check for
+ // window.ProgressEvent instead to detect XHR2 file upload capability:
+ $.support.xhrFileUpload = !!(window.ProgressEvent && window.FileReader);
+ $.support.xhrFormDataFileUpload = !!window.FormData;
+
+ // Detect support for Blob slicing (required for chunked uploads):
+ $.support.blobSlice = window.Blob && (Blob.prototype.slice ||
+ Blob.prototype.webkitSlice || Blob.prototype.mozSlice);
+
+ // Helper function to create drag handlers for dragover/dragenter/dragleave:
+ function getDragHandler(type) {
+ var isDragOver = type === 'dragover';
+ return function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var dataTransfer = e.dataTransfer;
+ if (dataTransfer && $.inArray('Files', dataTransfer.types) !== -1 &&
+ this._trigger(
+ type,
+ $.Event(type, {delegatedEvent: e})
+ ) !== false) {
+ e.preventDefault();
+ if (isDragOver) {
+ dataTransfer.dropEffect = 'copy';
+ }
+ }
+ };
+ }
+
+ // The fileupload widget listens for change events on file input fields defined
+ // via fileInput setting and paste or drop events of the given dropZone.
+ // In addition to the default jQuery Widget methods, the fileupload widget
+ // exposes the "add" and "send" methods, to add or directly send files using
+ // the fileupload API.
+ // By default, files added via file input selection, paste, drag & drop or
+ // "add" method are uploaded immediately, but it is possible to override
+ // the "add" callback option to queue file uploads.
+ $.widget('blueimp.fileupload', {
+
+ options: {
+ // The drop target element(s), by the default the complete document.
+ // Set to null to disable drag & drop support:
+ dropZone: $(document),
+ // The paste target element(s), by the default undefined.
+ // Set to a DOM node or jQuery object to enable file pasting:
+ pasteZone: undefined,
+ // The file input field(s), that are listened to for change events.
+ // If undefined, it is set to the file input fields inside
+ // of the widget element on plugin initialization.
+ // Set to null to disable the change listener.
+ fileInput: undefined,
+ // By default, the file input field is replaced with a clone after
+ // each input field change event. This is required for iframe transport
+ // queues and allows change events to be fired for the same file
+ // selection, but can be disabled by setting the following option to false:
+ replaceFileInput: true,
+ // The parameter name for the file form data (the request argument name).
+ // If undefined or empty, the name property of the file input field is
+ // used, or "files[]" if the file input name property is also empty,
+ // can be a string or an array of strings:
+ paramName: undefined,
+ // By default, each file of a selection is uploaded using an individual
+ // request for XHR type uploads. Set to false to upload file
+ // selections in one request each:
+ singleFileUploads: true,
+ // To limit the number of files uploaded with one XHR request,
+ // set the following option to an integer greater than 0:
+ limitMultiFileUploads: undefined,
+ // The following option limits the number of files uploaded with one
+ // XHR request to keep the request size under or equal to the defined
+ // limit in bytes:
+ limitMultiFileUploadSize: undefined,
+ // Multipart file uploads add a number of bytes to each uploaded file,
+ // therefore the following option adds an overhead for each file used
+ // in the limitMultiFileUploadSize configuration:
+ limitMultiFileUploadSizeOverhead: 512,
+ // Set the following option to true to issue all file upload requests
+ // in a sequential order:
+ sequentialUploads: false,
+ // To limit the number of concurrent uploads,
+ // set the following option to an integer greater than 0:
+ limitConcurrentUploads: undefined,
+ // Set the following option to true to force iframe transport uploads:
+ forceIframeTransport: false,
+ // Set the following option to the location of a redirect url on the
+ // origin server, for cross-domain iframe transport uploads:
+ redirect: undefined,
+ // The parameter name for the redirect url, sent as part of the form
+ // data and set to 'redirect' if this option is empty:
+ redirectParamName: undefined,
+ // Set the following option to the location of a postMessage window,
+ // to enable postMessage transport uploads:
+ postMessage: undefined,
+ // By default, XHR file uploads are sent as multipart/form-data.
+ // The iframe transport is always using multipart/form-data.
+ // Set to false to enable non-multipart XHR uploads:
+ multipart: true,
+ // To upload large files in smaller chunks, set the following option
+ // to a preferred maximum chunk size. If set to 0, null or undefined,
+ // or the browser does not support the required Blob API, files will
+ // be uploaded as a whole.
+ maxChunkSize: undefined,
+ // When a non-multipart upload or a chunked multipart upload has been
+ // aborted, this option can be used to resume the upload by setting
+ // it to the size of the already uploaded bytes. This option is most
+ // useful when modifying the options object inside of the "add" or
+ // "send" callbacks, as the options are cloned for each file upload.
+ uploadedBytes: undefined,
+ // By default, failed (abort or error) file uploads are removed from the
+ // global progress calculation. Set the following option to false to
+ // prevent recalculating the global progress data:
+ recalculateProgress: true,
+ // Interval in milliseconds to calculate and trigger progress events:
+ progressInterval: 100,
+ // Interval in milliseconds to calculate progress bitrate:
+ bitrateInterval: 500,
+ // By default, uploads are started automatically when adding files:
+ autoUpload: true,
+
+ // Error and info messages:
+ messages: {
+ uploadedBytes: 'Uploaded bytes exceed file size'
+ },
+
+ // Translation function, gets the message key to be translated
+ // and an object with context specific data as arguments:
+ i18n: function (message, context) {
+ message = this.messages[message] || message.toString();
+ if (context) {
+ $.each(context, function (key, value) {
+ message = message.replace('{' + key + '}', value);
+ });
+ }
+ return message;
+ },
+
+ // Additional form data to be sent along with the file uploads can be set
+ // using this option, which accepts an array of objects with name and
+ // value properties, a function returning such an array, a FormData
+ // object (for XHR file uploads), or a simple object.
+ // The form of the first fileInput is given as parameter to the function:
+ formData: function (form) {
+ return form.serializeArray();
+ },
+
+ // The add callback is invoked as soon as files are added to the fileupload
+ // widget (via file input selection, drag & drop, paste or add API call).
+ // If the singleFileUploads option is enabled, this callback will be
+ // called once for each file in the selection for XHR file uploads, else
+ // once for each file selection.
+ //
+ // The upload starts when the submit method is invoked on the data parameter.
+ // The data object contains a files property holding the added files
+ // and allows you to override plugin options as well as define ajax settings.
+ //
+ // Listeners for this callback can also be bound the following way:
+ // .bind('fileuploadadd', func);
+ //
+ // data.submit() returns a Promise object and allows to attach additional
+ // handlers using jQuery's Deferred callbacks:
+ // data.submit().done(func).fail(func).always(func);
+ add: function (e, data) {
+ if (e.isDefaultPrevented()) {
+ return false;
+ }
+ if (data.autoUpload || (data.autoUpload !== false &&
+ $(this).fileupload('option', 'autoUpload'))) {
+ data.process().done(function () {
+ data.submit();
+ });
+ }
+ },
+
+ // Other callbacks:
+
+ // Callback for the submit event of each file upload:
+ // submit: function (e, data) {}, // .bind('fileuploadsubmit', func);
+
+ // Callback for the start of each file upload request:
+ // send: function (e, data) {}, // .bind('fileuploadsend', func);
+
+ // Callback for successful uploads:
+ // done: function (e, data) {}, // .bind('fileuploaddone', func);
+
+ // Callback for failed (abort or error) uploads:
+ // fail: function (e, data) {}, // .bind('fileuploadfail', func);
+
+ // Callback for completed (success, abort or error) requests:
+ // always: function (e, data) {}, // .bind('fileuploadalways', func);
+
+ // Callback for upload progress events:
+ // progress: function (e, data) {}, // .bind('fileuploadprogress', func);
+
+ // Callback for global upload progress events:
+ // progressall: function (e, data) {}, // .bind('fileuploadprogressall', func);
+
+ // Callback for uploads start, equivalent to the global ajaxStart event:
+ // start: function (e) {}, // .bind('fileuploadstart', func);
+
+ // Callback for uploads stop, equivalent to the global ajaxStop event:
+ // stop: function (e) {}, // .bind('fileuploadstop', func);
+
+ // Callback for change events of the fileInput(s):
+ // change: function (e, data) {}, // .bind('fileuploadchange', func);
+
+ // Callback for paste events to the pasteZone(s):
+ // paste: function (e, data) {}, // .bind('fileuploadpaste', func);
+
+ // Callback for drop events of the dropZone(s):
+ // drop: function (e, data) {}, // .bind('fileuploaddrop', func);
+
+ // Callback for dragover events of the dropZone(s):
+ // dragover: function (e) {}, // .bind('fileuploaddragover', func);
+
+ // Callback for the start of each chunk upload request:
+ // chunksend: function (e, data) {}, // .bind('fileuploadchunksend', func);
+
+ // Callback for successful chunk uploads:
+ // chunkdone: function (e, data) {}, // .bind('fileuploadchunkdone', func);
+
+ // Callback for failed (abort or error) chunk uploads:
+ // chunkfail: function (e, data) {}, // .bind('fileuploadchunkfail', func);
+
+ // Callback for completed (success, abort or error) chunk upload requests:
+ // chunkalways: function (e, data) {}, // .bind('fileuploadchunkalways', func);
+
+ // The plugin options are used as settings object for the ajax calls.
+ // The following are jQuery ajax settings required for the file uploads:
+ processData: false,
+ contentType: false,
+ cache: false,
+ timeout: 0
+ },
+
+ // A list of options that require reinitializing event listeners and/or
+ // special initialization code:
+ _specialOptions: [
+ 'fileInput',
+ 'dropZone',
+ 'pasteZone',
+ 'multipart',
+ 'forceIframeTransport'
+ ],
+
+ _blobSlice: $.support.blobSlice && function () {
+ var slice = this.slice || this.webkitSlice || this.mozSlice;
+ return slice.apply(this, arguments);
+ },
+
+ _BitrateTimer: function () {
+ this.timestamp = ((Date.now) ? Date.now() : (new Date()).getTime());
+ this.loaded = 0;
+ this.bitrate = 0;
+ this.getBitrate = function (now, loaded, interval) {
+ var timeDiff = now - this.timestamp;
+ if (!this.bitrate || !interval || timeDiff > interval) {
+ this.bitrate = (loaded - this.loaded) * (1000 / timeDiff) * 8;
+ this.loaded = loaded;
+ this.timestamp = now;
+ }
+ return this.bitrate;
+ };
+ },
+
+ _isXHRUpload: function (options) {
+ return !options.forceIframeTransport &&
+ ((!options.multipart && $.support.xhrFileUpload) ||
+ $.support.xhrFormDataFileUpload);
+ },
+
+ _getFormData: function (options) {
+ var formData;
+ if ($.type(options.formData) === 'function') {
+ return options.formData(options.form);
+ }
+ if ($.isArray(options.formData)) {
+ return options.formData;
+ }
+ if ($.type(options.formData) === 'object') {
+ formData = [];
+ $.each(options.formData, function (name, value) {
+ formData.push({name: name, value: value});
+ });
+ return formData;
+ }
+ return [];
+ },
+
+ _getTotal: function (files) {
+ var total = 0;
+ $.each(files, function (index, file) {
+ total += file.size || 1;
+ });
+ return total;
+ },
+
+ _initProgressObject: function (obj) {
+ var progress = {
+ loaded: 0,
+ total: 0,
+ bitrate: 0
+ };
+ if (obj._progress) {
+ $.extend(obj._progress, progress);
+ } else {
+ obj._progress = progress;
+ }
+ },
+
+ _initResponseObject: function (obj) {
+ var prop;
+ if (obj._response) {
+ for (prop in obj._response) {
+ if (obj._response.hasOwnProperty(prop)) {
+ delete obj._response[prop];
+ }
+ }
+ } else {
+ obj._response = {};
+ }
+ },
+
+ _onProgress: function (e, data) {
+ if (e.lengthComputable) {
+ var now = ((Date.now) ? Date.now() : (new Date()).getTime()),
+ loaded;
+ if (data._time && data.progressInterval &&
+ (now - data._time < data.progressInterval) &&
+ e.loaded !== e.total) {
+ return;
+ }
+ data._time = now;
+ loaded = Math.floor(
+ e.loaded / e.total * (data.chunkSize || data._progress.total)
+ ) + (data.uploadedBytes || 0);
+ // Add the difference from the previously loaded state
+ // to the global loaded counter:
+ this._progress.loaded += (loaded - data._progress.loaded);
+ this._progress.bitrate = this._bitrateTimer.getBitrate(
+ now,
+ this._progress.loaded,
+ data.bitrateInterval
+ );
+ data._progress.loaded = data.loaded = loaded;
+ data._progress.bitrate = data.bitrate = data._bitrateTimer.getBitrate(
+ now,
+ loaded,
+ data.bitrateInterval
+ );
+ // Trigger a custom progress event with a total data property set
+ // to the file size(s) of the current upload and a loaded data
+ // property calculated accordingly:
+ this._trigger(
+ 'progress',
+ $.Event('progress', {delegatedEvent: e}),
+ data
+ );
+ // Trigger a global progress event for all current file uploads,
+ // including ajax calls queued for sequential file uploads:
+ this._trigger(
+ 'progressall',
+ $.Event('progressall', {delegatedEvent: e}),
+ this._progress
+ );
+ }
+ },
+
+ _initProgressListener: function (options) {
+ var that = this,
+ xhr = options.xhr ? options.xhr() : $.ajaxSettings.xhr();
+ // Accesss to the native XHR object is required to add event listeners
+ // for the upload progress event:
+ if (xhr.upload) {
+ $(xhr.upload).bind('progress', function (e) {
+ var oe = e.originalEvent;
+ // Make sure the progress event properties get copied over:
+ e.lengthComputable = oe.lengthComputable;
+ e.loaded = oe.loaded;
+ e.total = oe.total;
+ that._onProgress(e, options);
+ });
+ options.xhr = function () {
+ return xhr;
+ };
+ }
+ },
+
+ _isInstanceOf: function (type, obj) {
+ // Cross-frame instanceof check
+ return Object.prototype.toString.call(obj) === '[object ' + type + ']';
+ },
+
+ _initXHRData: function (options) {
+ var that = this,
+ formData,
+ file = options.files[0],
+ // Ignore non-multipart setting if not supported:
+ multipart = options.multipart || !$.support.xhrFileUpload,
+ paramName = $.type(options.paramName) === 'array' ?
+ options.paramName[0] : options.paramName;
+ options.headers = $.extend({}, options.headers);
+ if (options.contentRange) {
+ options.headers['Content-Range'] = options.contentRange;
+ }
+ if (!multipart || options.blob || !this._isInstanceOf('File', file)) {
+ options.headers['Content-Disposition'] = 'attachment; filename="' +
+ encodeURI(file.name) + '"';
+ }
+ if (!multipart) {
+ options.contentType = file.type || 'application/octet-stream';
+ options.data = options.blob || file;
+ } else if ($.support.xhrFormDataFileUpload) {
+ if (options.postMessage) {
+ // window.postMessage does not allow sending FormData
+ // objects, so we just add the File/Blob objects to
+ // the formData array and let the postMessage window
+ // create the FormData object out of this array:
+ formData = this._getFormData(options);
+ if (options.blob) {
+ formData.push({
+ name: paramName,
+ value: options.blob
+ });
+ } else {
+ $.each(options.files, function (index, file) {
+ formData.push({
+ name: ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) || paramName,
+ value: file
+ });
+ });
+ }
+ } else {
+ if (that._isInstanceOf('FormData', options.formData)) {
+ formData = options.formData;
+ } else {
+ formData = new FormData();
+ $.each(this._getFormData(options), function (index, field) {
+ formData.append(field.name, field.value);
+ });
+ }
+ if (options.blob) {
+ formData.append(paramName, options.blob, file.name);
+ } else {
+ $.each(options.files, function (index, file) {
+ // This check allows the tests to run with
+ // dummy objects:
+ if (that._isInstanceOf('File', file) ||
+ that._isInstanceOf('Blob', file)) {
+ formData.append(
+ ($.type(options.paramName) === 'array' &&
+ options.paramName[index]) || paramName,
+ file,
+ file.uploadName || file.name
+ );
+ }
+ });
+ }
+ }
+ options.data = formData;
+ }
+ // Blob reference is not needed anymore, free memory:
+ options.blob = null;
+ },
+
+ _initIframeSettings: function (options) {
+ var targetHost = $('').prop('href', options.url).prop('host');
+ // Setting the dataType to iframe enables the iframe transport:
+ options.dataType = 'iframe ' + (options.dataType || '');
+ // The iframe transport accepts a serialized array as form data:
+ options.formData = this._getFormData(options);
+ // Add redirect url to form data on cross-domain uploads:
+ if (options.redirect && targetHost && targetHost !== location.host) {
+ options.formData.push({
+ name: options.redirectParamName || 'redirect',
+ value: options.redirect
+ });
+ }
+ },
+
+ _initDataSettings: function (options) {
+ if (this._isXHRUpload(options)) {
+ if (!this._chunkedUpload(options, true)) {
+ if (!options.data) {
+ this._initXHRData(options);
+ }
+ this._initProgressListener(options);
+ }
+ if (options.postMessage) {
+ // Setting the dataType to postmessage enables the
+ // postMessage transport:
+ options.dataType = 'postmessage ' + (options.dataType || '');
+ }
+ } else {
+ this._initIframeSettings(options);
+ }
+ },
+
+ _getParamName: function (options) {
+ var fileInput = $(options.fileInput),
+ paramName = options.paramName;
+ if (!paramName) {
+ paramName = [];
+ fileInput.each(function () {
+ var input = $(this),
+ name = input.prop('name') || 'files[]',
+ i = (input.prop('files') || [1]).length;
+ while (i) {
+ paramName.push(name);
+ i -= 1;
+ }
+ });
+ if (!paramName.length) {
+ paramName = [fileInput.prop('name') || 'files[]'];
+ }
+ } else if (!$.isArray(paramName)) {
+ paramName = [paramName];
+ }
+ return paramName;
+ },
+
+ _initFormSettings: function (options) {
+ // Retrieve missing options from the input field and the
+ // associated form, if available:
+ if (!options.form || !options.form.length) {
+ options.form = $(options.fileInput.prop('form'));
+ // If the given file input doesn't have an associated form,
+ // use the default widget file input's form:
+ if (!options.form.length) {
+ options.form = $(this.options.fileInput.prop('form'));
+ }
+ }
+ options.paramName = this._getParamName(options);
+ if (!options.url) {
+ options.url = options.form.prop('action') || location.href;
+ }
+ // The HTTP request method must be "POST" or "PUT":
+ options.type = (options.type ||
+ ($.type(options.form.prop('method')) === 'string' &&
+ options.form.prop('method')) || ''
+ ).toUpperCase();
+ if (options.type !== 'POST' && options.type !== 'PUT' &&
+ options.type !== 'PATCH') {
+ options.type = 'POST';
+ }
+ if (!options.formAcceptCharset) {
+ options.formAcceptCharset = options.form.attr('accept-charset');
+ }
+ },
+
+ _getAJAXSettings: function (data) {
+ var options = $.extend({}, this.options, data);
+ this._initFormSettings(options);
+ this._initDataSettings(options);
+ return options;
+ },
+
+ // jQuery 1.6 doesn't provide .state(),
+ // while jQuery 1.8+ removed .isRejected() and .isResolved():
+ _getDeferredState: function (deferred) {
+ if (deferred.state) {
+ return deferred.state();
+ }
+ if (deferred.isResolved()) {
+ return 'resolved';
+ }
+ if (deferred.isRejected()) {
+ return 'rejected';
+ }
+ return 'pending';
+ },
+
+ // Maps jqXHR callbacks to the equivalent
+ // methods of the given Promise object:
+ _enhancePromise: function (promise) {
+ promise.success = promise.done;
+ promise.error = promise.fail;
+ promise.complete = promise.always;
+ return promise;
+ },
+
+ // Creates and returns a Promise object enhanced with
+ // the jqXHR methods abort, success, error and complete:
+ _getXHRPromise: function (resolveOrReject, context, args) {
+ var dfd = $.Deferred(),
+ promise = dfd.promise();
+ context = context || this.options.context || promise;
+ if (resolveOrReject === true) {
+ dfd.resolveWith(context, args);
+ } else if (resolveOrReject === false) {
+ dfd.rejectWith(context, args);
+ }
+ promise.abort = dfd.promise;
+ return this._enhancePromise(promise);
+ },
+
+ // Adds convenience methods to the data callback argument:
+ _addConvenienceMethods: function (e, data) {
+ var that = this,
+ getPromise = function (args) {
+ return $.Deferred().resolveWith(that, args).promise();
+ };
+ data.process = function (resolveFunc, rejectFunc) {
+ if (resolveFunc || rejectFunc) {
+ data._processQueue = this._processQueue =
+ (this._processQueue || getPromise([this])).then(
+ function () {
+ if (data.errorThrown) {
+ return $.Deferred()
+ .rejectWith(that, [data]).promise();
+ }
+ return getPromise(arguments);
+ }
+ ).then(resolveFunc, rejectFunc);
+ }
+ return this._processQueue || getPromise([this]);
+ };
+ data.submit = function () {
+ if (this.state() !== 'pending') {
+ data.jqXHR = this.jqXHR =
+ (that._trigger(
+ 'submit',
+ $.Event('submit', {delegatedEvent: e}),
+ this
+ ) !== false) && that._onSend(e, this);
+ }
+ return this.jqXHR || that._getXHRPromise();
+ };
+ data.abort = function () {
+ if (this.jqXHR) {
+ return this.jqXHR.abort();
+ }
+ this.errorThrown = 'abort';
+ that._trigger('fail', null, this);
+ return that._getXHRPromise(false);
+ };
+ data.state = function () {
+ if (this.jqXHR) {
+ return that._getDeferredState(this.jqXHR);
+ }
+ if (this._processQueue) {
+ return that._getDeferredState(this._processQueue);
+ }
+ };
+ data.processing = function () {
+ return !this.jqXHR && this._processQueue && that
+ ._getDeferredState(this._processQueue) === 'pending';
+ };
+ data.progress = function () {
+ return this._progress;
+ };
+ data.response = function () {
+ return this._response;
+ };
+ },
+
+ // Parses the Range header from the server response
+ // and returns the uploaded bytes:
+ _getUploadedBytes: function (jqXHR) {
+ var range = jqXHR.getResponseHeader('Range'),
+ parts = range && range.split('-'),
+ upperBytesPos = parts && parts.length > 1 &&
+ parseInt(parts[1], 10);
+ return upperBytesPos && upperBytesPos + 1;
+ },
+
+ // Uploads a file in multiple, sequential requests
+ // by splitting the file up in multiple blob chunks.
+ // If the second parameter is true, only tests if the file
+ // should be uploaded in chunks, but does not invoke any
+ // upload requests:
+ _chunkedUpload: function (options, testOnly) {
+ options.uploadedBytes = options.uploadedBytes || 0;
+ var that = this,
+ file = options.files[0],
+ fs = file.size,
+ ub = options.uploadedBytes,
+ mcs = options.maxChunkSize || fs,
+ slice = this._blobSlice,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ upload;
+ if (!(this._isXHRUpload(options) && slice && (ub || mcs < fs)) ||
+ options.data) {
+ return false;
+ }
+ if (testOnly) {
+ return true;
+ }
+ if (ub >= fs) {
+ file.error = options.i18n('uploadedBytes');
+ return this._getXHRPromise(
+ false,
+ options.context,
+ [null, 'error', file.error]
+ );
+ }
+ // The chunk upload method:
+ upload = function () {
+ // Clone the options object for each chunk upload:
+ var o = $.extend({}, options),
+ currentLoaded = o._progress.loaded;
+ o.blob = slice.call(
+ file,
+ ub,
+ ub + mcs,
+ file.type
+ );
+ // Store the current chunk size, as the blob itself
+ // will be dereferenced after data processing:
+ o.chunkSize = o.blob.size;
+ // Expose the chunk bytes position range:
+ o.contentRange = 'bytes ' + ub + '-' +
+ (ub + o.chunkSize - 1) + '/' + fs;
+ // Process the upload data (the blob and potential form data):
+ that._initXHRData(o);
+ // Add progress listeners for this chunk upload:
+ that._initProgressListener(o);
+ jqXHR = ((that._trigger('chunksend', null, o) !== false && $.ajax(o)) ||
+ that._getXHRPromise(false, o.context))
+ .done(function (result, textStatus, jqXHR) {
+ ub = that._getUploadedBytes(jqXHR) ||
+ (ub + o.chunkSize);
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered
+ // for this chunk:
+ if (currentLoaded + o.chunkSize - o._progress.loaded) {
+ that._onProgress($.Event('progress', {
+ lengthComputable: true,
+ loaded: ub - o.uploadedBytes,
+ total: ub - o.uploadedBytes
+ }), o);
+ }
+ options.uploadedBytes = o.uploadedBytes = ub;
+ o.result = result;
+ o.textStatus = textStatus;
+ o.jqXHR = jqXHR;
+ that._trigger('chunkdone', null, o);
+ that._trigger('chunkalways', null, o);
+ if (ub < fs) {
+ // File upload not yet complete,
+ // continue with the next chunk:
+ upload();
+ } else {
+ dfd.resolveWith(
+ o.context,
+ [result, textStatus, jqXHR]
+ );
+ }
+ })
+ .fail(function (jqXHR, textStatus, errorThrown) {
+ o.jqXHR = jqXHR;
+ o.textStatus = textStatus;
+ o.errorThrown = errorThrown;
+ that._trigger('chunkfail', null, o);
+ that._trigger('chunkalways', null, o);
+ dfd.rejectWith(
+ o.context,
+ [jqXHR, textStatus, errorThrown]
+ );
+ });
+ };
+ this._enhancePromise(promise);
+ promise.abort = function () {
+ return jqXHR.abort();
+ };
+ upload();
+ return promise;
+ },
+
+ _beforeSend: function (e, data) {
+ if (this._active === 0) {
+ // the start callback is triggered when an upload starts
+ // and no other uploads are currently running,
+ // equivalent to the global ajaxStart event:
+ this._trigger('start');
+ // Set timer for global bitrate progress calculation:
+ this._bitrateTimer = new this._BitrateTimer();
+ // Reset the global progress values:
+ this._progress.loaded = this._progress.total = 0;
+ this._progress.bitrate = 0;
+ }
+ // Make sure the container objects for the .response() and
+ // .progress() methods on the data object are available
+ // and reset to their initial state:
+ this._initResponseObject(data);
+ this._initProgressObject(data);
+ data._progress.loaded = data.loaded = data.uploadedBytes || 0;
+ data._progress.total = data.total = this._getTotal(data.files) || 1;
+ data._progress.bitrate = data.bitrate = 0;
+ this._active += 1;
+ // Initialize the global progress values:
+ this._progress.loaded += data.loaded;
+ this._progress.total += data.total;
+ },
+
+ _onDone: function (result, textStatus, jqXHR, options) {
+ var total = options._progress.total,
+ response = options._response;
+ if (options._progress.loaded < total) {
+ // Create a progress event if no final progress event
+ // with loaded equaling total has been triggered:
+ this._onProgress($.Event('progress', {
+ lengthComputable: true,
+ loaded: total,
+ total: total
+ }), options);
+ }
+ response.result = options.result = result;
+ response.textStatus = options.textStatus = textStatus;
+ response.jqXHR = options.jqXHR = jqXHR;
+ this._trigger('done', null, options);
+ },
+
+ _onFail: function (jqXHR, textStatus, errorThrown, options) {
+ var response = options._response;
+ if (options.recalculateProgress) {
+ // Remove the failed (error or abort) file upload from
+ // the global progress calculation:
+ this._progress.loaded -= options._progress.loaded;
+ this._progress.total -= options._progress.total;
+ }
+ response.jqXHR = options.jqXHR = jqXHR;
+ response.textStatus = options.textStatus = textStatus;
+ response.errorThrown = options.errorThrown = errorThrown;
+ this._trigger('fail', null, options);
+ },
+
+ _onAlways: function (jqXHRorResult, textStatus, jqXHRorError, options) {
+ // jqXHRorResult, textStatus and jqXHRorError are added to the
+ // options object via done and fail callbacks
+ this._trigger('always', null, options);
+ },
+
+ _onSend: function (e, data) {
+ if (!data.submit) {
+ this._addConvenienceMethods(e, data);
+ }
+ var that = this,
+ jqXHR,
+ aborted,
+ slot,
+ pipe,
+ options = that._getAJAXSettings(data),
+ send = function () {
+ that._sending += 1;
+ // Set timer for bitrate progress calculation:
+ options._bitrateTimer = new that._BitrateTimer();
+ jqXHR = jqXHR || (
+ ((aborted || that._trigger(
+ 'send',
+ $.Event('send', {delegatedEvent: e}),
+ options
+ ) === false) &&
+ that._getXHRPromise(false, options.context, aborted)) ||
+ that._chunkedUpload(options) || $.ajax(options)
+ ).done(function (result, textStatus, jqXHR) {
+ that._onDone(result, textStatus, jqXHR, options);
+ }).fail(function (jqXHR, textStatus, errorThrown) {
+ that._onFail(jqXHR, textStatus, errorThrown, options);
+ }).always(function (jqXHRorResult, textStatus, jqXHRorError) {
+ that._onAlways(
+ jqXHRorResult,
+ textStatus,
+ jqXHRorError,
+ options
+ );
+ that._sending -= 1;
+ that._active -= 1;
+ if (options.limitConcurrentUploads &&
+ options.limitConcurrentUploads > that._sending) {
+ // Start the next queued upload,
+ // that has not been aborted:
+ var nextSlot = that._slots.shift();
+ while (nextSlot) {
+ if (that._getDeferredState(nextSlot) === 'pending') {
+ nextSlot.resolve();
+ break;
+ }
+ nextSlot = that._slots.shift();
+ }
+ }
+ if (that._active === 0) {
+ // The stop callback is triggered when all uploads have
+ // been completed, equivalent to the global ajaxStop event:
+ that._trigger('stop');
+ }
+ });
+ return jqXHR;
+ };
+ this._beforeSend(e, options);
+ if (this.options.sequentialUploads ||
+ (this.options.limitConcurrentUploads &&
+ this.options.limitConcurrentUploads <= this._sending)) {
+ if (this.options.limitConcurrentUploads > 1) {
+ slot = $.Deferred();
+ this._slots.push(slot);
+ pipe = slot.then(send);
+ } else {
+ this._sequence = this._sequence.then(send, send);
+ pipe = this._sequence;
+ }
+ // Return the piped Promise object, enhanced with an abort method,
+ // which is delegated to the jqXHR object of the current upload,
+ // and jqXHR callbacks mapped to the equivalent Promise methods:
+ pipe.abort = function () {
+ aborted = [undefined, 'abort', 'abort'];
+ if (!jqXHR) {
+ if (slot) {
+ slot.rejectWith(options.context, aborted);
+ }
+ return send();
+ }
+ return jqXHR.abort();
+ };
+ return this._enhancePromise(pipe);
+ }
+ return send();
+ },
+
+ _onAdd: function (e, data) {
+ var that = this,
+ result = true,
+ options = $.extend({}, this.options, data),
+ files = data.files,
+ filesLength = files.length,
+ limit = options.limitMultiFileUploads,
+ limitSize = options.limitMultiFileUploadSize,
+ overhead = options.limitMultiFileUploadSizeOverhead,
+ batchSize = 0,
+ paramName = this._getParamName(options),
+ paramNameSet,
+ paramNameSlice,
+ fileSet,
+ i,
+ j = 0;
+ if (!filesLength) {
+ return false;
+ }
+ if (limitSize && files[0].size === undefined) {
+ limitSize = undefined;
+ }
+ if (!(options.singleFileUploads || limit || limitSize) ||
+ !this._isXHRUpload(options)) {
+ fileSet = [files];
+ paramNameSet = [paramName];
+ } else if (!(options.singleFileUploads || limitSize) && limit) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i += limit) {
+ fileSet.push(files.slice(i, i + limit));
+ paramNameSlice = paramName.slice(i, i + limit);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ }
+ } else if (!options.singleFileUploads && limitSize) {
+ fileSet = [];
+ paramNameSet = [];
+ for (i = 0; i < filesLength; i = i + 1) {
+ batchSize += files[i].size + overhead;
+ if (i + 1 === filesLength ||
+ ((batchSize + files[i + 1].size + overhead) > limitSize) ||
+ (limit && i + 1 - j >= limit)) {
+ fileSet.push(files.slice(j, i + 1));
+ paramNameSlice = paramName.slice(j, i + 1);
+ if (!paramNameSlice.length) {
+ paramNameSlice = paramName;
+ }
+ paramNameSet.push(paramNameSlice);
+ j = i + 1;
+ batchSize = 0;
+ }
+ }
+ } else {
+ paramNameSet = paramName;
+ }
+ data.originalFiles = files;
+ $.each(fileSet || files, function (index, element) {
+ var newData = $.extend({}, data);
+ newData.files = fileSet ? element : [element];
+ newData.paramName = paramNameSet[index];
+ that._initResponseObject(newData);
+ that._initProgressObject(newData);
+ that._addConvenienceMethods(e, newData);
+ result = that._trigger(
+ 'add',
+ $.Event('add', {delegatedEvent: e}),
+ newData
+ );
+ return result;
+ });
+ return result;
+ },
+
+ _replaceFileInput: function (data) {
+ var input = data.fileInput,
+ inputClone = input.clone(true),
+ restoreFocus = input.is(document.activeElement);
+ // Add a reference for the new cloned file input to the data argument:
+ data.fileInputClone = inputClone;
+ $('').append(inputClone)[0].reset();
+ // Detaching allows to insert the fileInput on another form
+ // without loosing the file input value:
+ input.after(inputClone).detach();
+ // If the fileInput had focus before it was detached,
+ // restore focus to the inputClone.
+ if (restoreFocus) {
+ inputClone.focus();
+ }
+ // Avoid memory leaks with the detached file input:
+ $.cleanData(input.unbind('remove'));
+ // Replace the original file input element in the fileInput
+ // elements set with the clone, which has been copied including
+ // event handlers:
+ this.options.fileInput = this.options.fileInput.map(function (i, el) {
+ if (el === input[0]) {
+ return inputClone[0];
+ }
+ return el;
+ });
+ // If the widget has been initialized on the file input itself,
+ // override this.element with the file input clone:
+ if (input[0] === this.element[0]) {
+ this.element = inputClone;
+ }
+ },
+
+ _handleFileTreeEntry: function (entry, path) {
+ var that = this,
+ dfd = $.Deferred(),
+ entries = [],
+ dirReader,
+ errorHandler = function (e) {
+ if (e && !e.entry) {
+ e.entry = entry;
+ }
+ // Since $.when returns immediately if one
+ // Deferred is rejected, we use resolve instead.
+ // This allows valid files and invalid items
+ // to be returned together in one set:
+ dfd.resolve([e]);
+ },
+ successHandler = function (entries) {
+ that._handleFileTreeEntries(
+ entries,
+ path + entry.name + '/'
+ ).done(function (files) {
+ dfd.resolve(files);
+ }).fail(errorHandler);
+ },
+ readEntries = function () {
+ dirReader.readEntries(function (results) {
+ if (!results.length) {
+ successHandler(entries);
+ } else {
+ entries = entries.concat(results);
+ readEntries();
+ }
+ }, errorHandler);
+ };
+ path = path || '';
+ if (entry.isFile) {
+ if (entry._file) {
+ // Workaround for Chrome bug #149735
+ entry._file.relativePath = path;
+ dfd.resolve(entry._file);
+ } else {
+ entry.file(function (file) {
+ file.relativePath = path;
+ dfd.resolve(file);
+ }, errorHandler);
+ }
+ } else if (entry.isDirectory) {
+ dirReader = entry.createReader();
+ readEntries();
+ } else {
+ // Return an empy list for file system items
+ // other than files or directories:
+ dfd.resolve([]);
+ }
+ return dfd.promise();
+ },
+
+ _handleFileTreeEntries: function (entries, path) {
+ var that = this;
+ return $.when.apply(
+ $,
+ $.map(entries, function (entry) {
+ return that._handleFileTreeEntry(entry, path);
+ })
+ ).then(function () {
+ return Array.prototype.concat.apply(
+ [],
+ arguments
+ );
+ });
+ },
+
+ _getDroppedFiles: function (dataTransfer) {
+ dataTransfer = dataTransfer || {};
+ var items = dataTransfer.items;
+ if (items && items.length && (items[0].webkitGetAsEntry ||
+ items[0].getAsEntry)) {
+ return this._handleFileTreeEntries(
+ $.map(items, function (item) {
+ var entry;
+ if (item.webkitGetAsEntry) {
+ entry = item.webkitGetAsEntry();
+ if (entry) {
+ // Workaround for Chrome bug #149735:
+ entry._file = item.getAsFile();
+ }
+ return entry;
+ }
+ return item.getAsEntry();
+ })
+ );
+ }
+ return $.Deferred().resolve(
+ $.makeArray(dataTransfer.files)
+ ).promise();
+ },
+
+ _getSingleFileInputFiles: function (fileInput) {
+ fileInput = $(fileInput);
+ var entries = fileInput.prop('webkitEntries') ||
+ fileInput.prop('entries'),
+ files,
+ value;
+ if (entries && entries.length) {
+ return this._handleFileTreeEntries(entries);
+ }
+ files = $.makeArray(fileInput.prop('files'));
+ if (!files.length) {
+ value = fileInput.prop('value');
+ if (!value) {
+ return $.Deferred().resolve([]).promise();
+ }
+ // If the files property is not available, the browser does not
+ // support the File API and we add a pseudo File object with
+ // the input value as name with path information removed:
+ files = [{name: value.replace(/^.*\\/, '')}];
+ } else if (files[0].name === undefined && files[0].fileName) {
+ // File normalization for Safari 4 and Firefox 3:
+ $.each(files, function (index, file) {
+ file.name = file.fileName;
+ file.size = file.fileSize;
+ });
+ }
+ return $.Deferred().resolve(files).promise();
+ },
+
+ _getFileInputFiles: function (fileInput) {
+ if (!(fileInput instanceof $) || fileInput.length === 1) {
+ return this._getSingleFileInputFiles(fileInput);
+ }
+ return $.when.apply(
+ $,
+ $.map(fileInput, this._getSingleFileInputFiles)
+ ).then(function () {
+ return Array.prototype.concat.apply(
+ [],
+ arguments
+ );
+ });
+ },
+
+ _onChange: function (e) {
+ var that = this,
+ data = {
+ fileInput: $(e.target),
+ form: $(e.target.form)
+ };
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ if (that.options.replaceFileInput) {
+ that._replaceFileInput(data);
+ }
+ if (that._trigger(
+ 'change',
+ $.Event('change', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ that._onAdd(e, data);
+ }
+ });
+ },
+
+ _onPaste: function (e) {
+ var items = e.originalEvent && e.originalEvent.clipboardData &&
+ e.originalEvent.clipboardData.items,
+ data = {files: []};
+ if (items && items.length) {
+ $.each(items, function (index, item) {
+ var file = item.getAsFile && item.getAsFile();
+ if (file) {
+ data.files.push(file);
+ }
+ });
+ if (this._trigger(
+ 'paste',
+ $.Event('paste', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ this._onAdd(e, data);
+ }
+ }
+ },
+
+ _onDrop: function (e) {
+ e.dataTransfer = e.originalEvent && e.originalEvent.dataTransfer;
+ var that = this,
+ dataTransfer = e.dataTransfer,
+ data = {};
+ if (dataTransfer && dataTransfer.files && dataTransfer.files.length) {
+ e.preventDefault();
+ this._getDroppedFiles(dataTransfer).always(function (files) {
+ data.files = files;
+ if (that._trigger(
+ 'drop',
+ $.Event('drop', {delegatedEvent: e}),
+ data
+ ) !== false) {
+ that._onAdd(e, data);
+ }
+ });
+ }
+ },
+
+ _onDragOver: getDragHandler('dragover'),
+
+ _onDragEnter: getDragHandler('dragenter'),
+
+ _onDragLeave: getDragHandler('dragleave'),
+
+ _initEventHandlers: function () {
+ if (this._isXHRUpload(this.options)) {
+ this._on(this.options.dropZone, {
+ dragover: this._onDragOver,
+ drop: this._onDrop,
+ // event.preventDefault() on dragenter is required for IE10+:
+ dragenter: this._onDragEnter,
+ // dragleave is not required, but added for completeness:
+ dragleave: this._onDragLeave
+ });
+ this._on(this.options.pasteZone, {
+ paste: this._onPaste
+ });
+ }
+ if ($.support.fileInput) {
+ this._on(this.options.fileInput, {
+ change: this._onChange
+ });
+ }
+ },
+
+ _destroyEventHandlers: function () {
+ this._off(this.options.dropZone, 'dragenter dragleave dragover drop');
+ this._off(this.options.pasteZone, 'paste');
+ this._off(this.options.fileInput, 'change');
+ },
+
+ _destroy: function () {
+ this._destroyEventHandlers();
+ },
+
+ _setOption: function (key, value) {
+ var reinit = $.inArray(key, this._specialOptions) !== -1;
+ if (reinit) {
+ this._destroyEventHandlers();
+ }
+ this._super(key, value);
+ if (reinit) {
+ this._initSpecialOptions();
+ this._initEventHandlers();
+ }
+ },
+
+ _initSpecialOptions: function () {
+ var options = this.options;
+ if (options.fileInput === undefined) {
+ options.fileInput = this.element.is('input[type="file"]') ?
+ this.element : this.element.find('input[type="file"]');
+ } else if (!(options.fileInput instanceof $)) {
+ options.fileInput = $(options.fileInput);
+ }
+ if (!(options.dropZone instanceof $)) {
+ options.dropZone = $(options.dropZone);
+ }
+ if (!(options.pasteZone instanceof $)) {
+ options.pasteZone = $(options.pasteZone);
+ }
+ },
+
+ _getRegExp: function (str) {
+ var parts = str.split('/'),
+ modifiers = parts.pop();
+ parts.shift();
+ return new RegExp(parts.join('/'), modifiers);
+ },
+
+ _isRegExpOption: function (key, value) {
+ return key !== 'url' && $.type(value) === 'string' &&
+ /^\/.*\/[igm]{0,3}$/.test(value);
+ },
+
+ _initDataAttributes: function () {
+ var that = this,
+ options = this.options,
+ data = this.element.data();
+ // Initialize options set via HTML5 data-attributes:
+ $.each(
+ this.element[0].attributes,
+ function (index, attr) {
+ var key = attr.name.toLowerCase(),
+ value;
+ if (/^data-/.test(key)) {
+ // Convert hyphen-ated key to camelCase:
+ key = key.slice(5).replace(/-[a-z]/g, function (str) {
+ return str.charAt(1).toUpperCase();
+ });
+ value = data[key];
+ if (that._isRegExpOption(key, value)) {
+ value = that._getRegExp(value);
+ }
+ options[key] = value;
+ }
+ }
+ );
+ },
+
+ _create: function () {
+ this._initDataAttributes();
+ this._initSpecialOptions();
+ this._slots = [];
+ this._sequence = this._getXHRPromise(true);
+ this._sending = this._active = 0;
+ this._initProgressObject(this);
+ this._initEventHandlers();
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the number of active uploads:
+ active: function () {
+ return this._active;
+ },
+
+ // This method is exposed to the widget API and allows to query
+ // the widget upload progress.
+ // It returns an object with loaded, total and bitrate properties
+ // for the running uploads:
+ progress: function () {
+ return this._progress;
+ },
+
+ // This method is exposed to the widget API and allows adding files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files property and can contain additional options:
+ // .fileupload('add', {files: filesList});
+ add: function (data) {
+ var that = this;
+ if (!data || this.options.disabled) {
+ return;
+ }
+ if (data.fileInput && !data.files) {
+ this._getFileInputFiles(data.fileInput).always(function (files) {
+ data.files = files;
+ that._onAdd(null, data);
+ });
+ } else {
+ data.files = $.makeArray(data.files);
+ this._onAdd(null, data);
+ }
+ },
+
+ // This method is exposed to the widget API and allows sending files
+ // using the fileupload API. The data parameter accepts an object which
+ // must have a files or fileInput property and can contain additional options:
+ // .fileupload('send', {files: filesList});
+ // The method returns a Promise object for the file upload call.
+ send: function (data) {
+ if (data && !this.options.disabled) {
+ if (data.fileInput && !data.files) {
+ var that = this,
+ dfd = $.Deferred(),
+ promise = dfd.promise(),
+ jqXHR,
+ aborted;
+ promise.abort = function () {
+ aborted = true;
+ if (jqXHR) {
+ return jqXHR.abort();
+ }
+ dfd.reject(null, 'abort', 'abort');
+ return promise;
+ };
+ this._getFileInputFiles(data.fileInput).always(
+ function (files) {
+ if (aborted) {
+ return;
+ }
+ if (!files.length) {
+ dfd.reject();
+ return;
+ }
+ data.files = files;
+ jqXHR = that._onSend(null, data);
+ jqXHR.then(
+ function (result, textStatus, jqXHR) {
+ dfd.resolve(result, textStatus, jqXHR);
+ },
+ function (jqXHR, textStatus, errorThrown) {
+ dfd.reject(jqXHR, textStatus, errorThrown);
+ }
+ );
+ }
+ );
+ return this._enhancePromise(promise);
+ }
+ data.files = $.makeArray(data.files);
+ if (data.files.length) {
+ return this._onSend(null, data);
+ }
+ }
+ return this._getXHRPromise(false, data && data.context);
+ }
+
+ });
+
+}));
diff --git a/lib/assets/.keep b/lib/assets/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/lib/tasks/.keep b/lib/tasks/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/log/.keep b/log/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/public/404.html b/public/404.html
new file mode 100644
index 000000000..b612547fc
--- /dev/null
+++ b/public/404.html
@@ -0,0 +1,67 @@
+
+
+
+ The page you were looking for doesn't exist (404)
+
+
+
+
+
+
+
+
+
The page you were looking for doesn't exist.
+
You may have mistyped the address or the page may have moved.
+
+
If you are the application owner check the logs for more information.
+
+
+
diff --git a/public/422.html b/public/422.html
new file mode 100644
index 000000000..a21f82b3b
--- /dev/null
+++ b/public/422.html
@@ -0,0 +1,67 @@
+
+
+
+ The change you wanted was rejected (422)
+
+
+
+
+
+
+
+
+
The change you wanted was rejected.
+
Maybe you tried to change something you didn't have access to.
+
+
If you are the application owner check the logs for more information.
+
+
+
diff --git a/public/500.html b/public/500.html
new file mode 100644
index 000000000..061abc587
--- /dev/null
+++ b/public/500.html
@@ -0,0 +1,66 @@
+
+
+
+ We're sorry, but something went wrong (500)
+
+
+
+
+
+
+
+
+
We're sorry, but something went wrong.
+
+
If you are the application owner check the logs for more information.
+
+
+
diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png
new file mode 100644
index 000000000..e69de29bb
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
new file mode 100644
index 000000000..e69de29bb
diff --git a/public/favicon.ico b/public/favicon.ico
new file mode 100644
index 000000000..e69de29bb
diff --git a/public/robots.txt b/public/robots.txt
new file mode 100644
index 000000000..3c9c7c01f
--- /dev/null
+++ b/public/robots.txt
@@ -0,0 +1,5 @@
+# See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
+#
+# To ban all spiders from the entire site uncomment the next two lines:
+# User-agent: *
+# Disallow: /
diff --git a/spec/controllers/.keep b/spec/controllers/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/spec/controllers/comments_controller_spec.rb b/spec/controllers/comments_controller_spec.rb
new file mode 100644
index 000000000..63f198bcc
--- /dev/null
+++ b/spec/controllers/comments_controller_spec.rb
@@ -0,0 +1 @@
+require 'rails_helper'
diff --git a/spec/controllers/friends_controller_spec.rb b/spec/controllers/friends_controller_spec.rb
new file mode 100644
index 000000000..63f198bcc
--- /dev/null
+++ b/spec/controllers/friends_controller_spec.rb
@@ -0,0 +1 @@
+require 'rails_helper'
diff --git a/spec/controllers/galleries_controller_spec.rb b/spec/controllers/galleries_controller_spec.rb
new file mode 100644
index 000000000..63f198bcc
--- /dev/null
+++ b/spec/controllers/galleries_controller_spec.rb
@@ -0,0 +1 @@
+require 'rails_helper'
diff --git a/spec/controllers/likes_controller_spec.rb b/spec/controllers/likes_controller_spec.rb
new file mode 100644
index 000000000..63f198bcc
--- /dev/null
+++ b/spec/controllers/likes_controller_spec.rb
@@ -0,0 +1 @@
+require 'rails_helper'
diff --git a/spec/controllers/notices_controller_spec.rb b/spec/controllers/notices_controller_spec.rb
new file mode 100644
index 000000000..4bcfaae43
--- /dev/null
+++ b/spec/controllers/notices_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe NoticesController, type: :controller do
+
+end
diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb
new file mode 100644
index 000000000..63f198bcc
--- /dev/null
+++ b/spec/controllers/posts_controller_spec.rb
@@ -0,0 +1 @@
+require 'rails_helper'
diff --git a/spec/controllers/profiles_controller_spec.rb b/spec/controllers/profiles_controller_spec.rb
new file mode 100644
index 000000000..63f198bcc
--- /dev/null
+++ b/spec/controllers/profiles_controller_spec.rb
@@ -0,0 +1 @@
+require 'rails_helper'
diff --git a/spec/controllers/search_controller_spec.rb b/spec/controllers/search_controller_spec.rb
new file mode 100644
index 000000000..63f198bcc
--- /dev/null
+++ b/spec/controllers/search_controller_spec.rb
@@ -0,0 +1 @@
+require 'rails_helper'
diff --git a/spec/controllers/sessions_controller_spec.rb b/spec/controllers/sessions_controller_spec.rb
new file mode 100644
index 000000000..215da05e2
--- /dev/null
+++ b/spec/controllers/sessions_controller_spec.rb
@@ -0,0 +1,89 @@
+require 'rails_helper'
+# spec/controllers/users_controller_spec.rb
+
+describe SessionsController do
+ let(:profile){ create(:profile)}
+ let(:user){ profile.user }
+
+ before do
+ user
+ end
+
+ describe 'GET #new' do
+ context "has token" do
+
+ before do
+ user.regenerate_auth_token
+ cookies[:token] = user.token
+ end
+
+
+ it "signs the user in and redirects" do
+ process :new
+
+ expect(session[:user_id]).to_not be_nil
+ expect(response).to redirect_to(users_path)
+ end
+
+ end
+
+
+ context "authenticated" do
+
+ before do
+ create_session(user)
+ end
+
+
+ it "redirects to the user index" do
+ process :new
+ expect(response).to redirect_to(users_path)
+ end
+
+ end
+ end
+
+ describe 'POST #create' do
+
+ it "logs in a user with valid credentials" do
+ process :create, params: {email: user.email, password: user.password}
+ expect(session[:user_id]).to_not be_nil
+ assert_response :redirect
+ end
+
+ it "does not log in a user with invalid credentials" do
+ process :create, params: {email: user.email + "x", password: user.password}
+ expect(session[:user_id]).to be_nil
+ assert_response :success
+ end
+
+ it "flashes a danger method on invalid credentials" do
+ process :create, params: {email: user.email + "x", password: user.password}
+ expect(flash[:danger]).to_not be_nil
+ end
+
+ end
+
+ describe 'POST #destroy' do
+ before do
+ create_session(user)
+ end
+
+ it "logs out a user and redirects to the homepage" do
+ process :destroy
+
+ expect(session[:user_id]).to be_nil
+ expect(response).to redirect_to(signup_path)
+ end
+
+ it "flashes a message to inform the user" do
+ process :destroy
+
+ expect(flash[:success]).to_not be_nil
+ end
+
+ end
+
+
+
+end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
new file mode 100644
index 000000000..a9a0647e3
--- /dev/null
+++ b/spec/controllers/users_controller_spec.rb
@@ -0,0 +1,94 @@
+require 'rails_helper'
+# spec/controllers/users_controller_spec.rb
+
+describe UsersController do
+ let(:profile){ create(:profile)}
+ let(:user){ profile.user }
+ let(:another){create(:user)}
+
+ before do
+ another
+ user
+ end
+
+ context "authenticated" do
+ describe 'user access' do
+
+ before :each do
+ create_session(user)
+ end
+
+ describe "#set_user" do
+
+ end
+
+ describe 'GET #index' do
+
+ it "renders the :index template" do
+ process :index
+ assert_response :success
+ end
+
+ end
+
+ describe 'GET #new' do
+
+ it "GET #new redirects to index" do
+ process :new
+ expect(response).to redirect_to users_path
+ end
+
+ end
+
+ describe 'GET #edit' do
+ it "allows viewing the edit page" do
+ process :edit, params: {id: user.id }
+ assert_response :success
+ end
+
+ it "does not allow viewing another user's edit page" do
+ process :edit, params: {id: another.id }
+ assert_response :redirect
+ end
+ end
+
+ describe 'POST #update' do
+
+ it_has_behavior 'valid_update', :user, { current_password: "!23456Yuiopasdf", email: "foo@email.com" }, :user_path do
+ let(:checked) { user }
+ end
+
+ it_has_behavior 'invalid_update', :user, { email: "new@email.com" } do
+ let(:checked) { user }
+ end
+
+ it_has_behavior 'unauthorized_update', :user, { current_password: "!23456Yuiopasdf", email: "foo@email.com" } do
+ let(:checked) { another }
+ end
+ end
+
+ end
+
+ end
+
+ describe 'GET #new' do
+
+ it "does not require authentication" do
+ process :new
+ assert_response :success
+ end
+
+ end
+
+ describe 'POST #create' do
+
+ it_has_behavior 'valid_create', :user, :users_path, 1
+
+ it_has_behavior 'invalid_create', :user, { user: {
+ email: "newemail.com",
+ password: "asdf"
+ }
+ }
+
+ end
+end
diff --git a/spec/factories.rb b/spec/factories.rb
new file mode 100644
index 000000000..9c3b2db89
--- /dev/null
+++ b/spec/factories.rb
@@ -0,0 +1,69 @@
+FactoryGirl.define do
+ factory :notice do
+ user nil
+ message "MyString"
+ end
+ factory :user do
+ sequence(:email) { |n| "foo#{n}@bar.com" }
+ password "!23456Yuiopasdf"
+ token nil
+ failed nil
+ last_attempt nil
+ end
+
+ factory :bio do
+ association :profile
+ slogan "I'm a Prude"
+ about "Catch Me if you can"
+ end
+
+ factory :friend_request do
+ association :user
+ association :request, factory: :user
+ end
+
+ factory :friends_user do
+ association :user
+ association :friend, factory: :user
+ end
+
+ factory :gallery do
+ association :user
+ title "gallery title"
+ description "graph"
+ end
+
+ factory :image do
+ association :gallery
+ url "http://img.com"
+ description "a picture"
+ end
+
+ factory :like do
+ association :user
+ association :post
+ end
+
+ factory :post do
+ association :user
+ post_type "Post"
+ body "Post Body"
+ likes_count 0
+ comments_count 0
+ end
+
+ factory :profile do
+ association :user
+ first_name "Hermione"
+ last_name "Granger"
+ birthday Date.today
+ gender "Female"
+ college "Hogwarts"
+ hometown "Heathgate, London"
+ current_home "Ron's Pad"
+ phone "555-555-5555"
+ association :profile_img, factory: :image
+ cover nil
+ edited false
+ end
+end
diff --git a/spec/features/.keep b/spec/features/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/spec/features/posts_spec.rb b/spec/features/posts_spec.rb
new file mode 100644
index 000000000..59b5358ed
--- /dev/null
+++ b/spec/features/posts_spec.rb
@@ -0,0 +1,53 @@
+require 'rails_helper'
+
+describe "Timeline" do
+ let(:profile){ create(:profile)}
+ let(:second_profile){ create(:profile, first_name: "Ron", last_name: "Weasley")}
+ let(:user){ profile.user }
+ let(:second_user){ second_profile.user }
+
+ feature "Create Post" do
+ context "Authenticated" do
+ before do
+ sign_in(user)
+ end
+ context "Current User" do
+ before do
+ visit user_path(user)
+ end
+ it "displays a new post form on the timeline" do
+ expect(page).to have_css("form#new_post textarea")
+ end
+
+ it "creates a new post" do
+ within "form#new_post" do
+ fill_in "post_body", with: "Test New Post"
+
+ expect{click_button "Post"}.to change(Post, :count).by(1)
+ end
+ end
+
+ it "redirects you to view the newly created post" do
+ create_new_post
+ expect(page).to have_current_path(user_post_path(user, Post.last))
+ end
+ end
+ context "Other User" do
+ before do
+ visit user_path(second_user)
+ end
+
+ it "does not display the new post form" do
+ expect(page).to_not have_css("form#new_post textarea")
+ end
+
+ end
+ end
+ context "Unauthenticated" do
+ it "redirects to the login path" do
+ visit user_path(user)
+ expect(page).to have_current_path(login_path)
+ end
+ end
+ end
+end
diff --git a/spec/features/profiles_spec.rb b/spec/features/profiles_spec.rb
new file mode 100644
index 000000000..76a54cf4b
--- /dev/null
+++ b/spec/features/profiles_spec.rb
@@ -0,0 +1,139 @@
+require 'rails_helper'
+
+describe "Profiles" do
+ let(:profile){ create(:profile)}
+ let(:second_profile){ create(:profile, first_name: "Ron", last_name: "Weasley")}
+ let(:user){ profile.user }
+ let(:second_user){ second_profile.user }
+
+
+
+ feature "View Profile" do
+
+ context "Authenticated" do
+
+ before do
+ profile
+ sign_in(user)
+ visit root_path
+ click_link "About"
+ end
+
+ it "renders the about page" do
+ expect(page).to have_current_path(user_profile_path(user))
+ expect(page).to have_css "section#about"
+ end
+
+ context "Current User" do
+ it "shows the edit profile link" do
+ expect(page).to have_css "li.edit > a", text: "Edit Profile"
+ expect(page).to have_link "Edit Profile"
+ end
+ end
+
+ context "Other User" do
+ it "does not show the edit profile link" do
+ second_profile.save
+ visit user_profile_path(second_user)
+ expect(page).to have_css ".profile-username", text: "#{second_profile.first_name} #{second_profile.last_name}"
+ expect(page).to_not have_css "li.edit > a"
+ end
+ end
+
+ end
+ context "Unauthenticated User" do
+ it "redirects to login path" do
+ visit user_profile_path(user)
+ expect(page).to have_current_path(login_path)
+ end
+ end
+ end
+
+ feature "Edit Profile" do
+
+ context "Authenticated" do
+
+ before do
+ profile
+ sign_in(user)
+ visit root_path
+ click_link "Edit Profile"
+ end
+
+ it "renders the Edit-About page" do
+ expect(page).to have_current_path(edit_user_profile_path(user))
+ expect(page).to have_css "section#about-edit"
+ end
+
+ context "Current User" do
+ it "shows the edit profile form" do
+ expect(page).to have_css "form#edit_profile_#{user.profile.id}"
+ end
+
+ it "can update the current user's info" do
+ expect(page).to have_css "select#profile_birthday_1i"
+ expect(page).to have_css "form#edit_profile_#{user.profile.id}"
+ new_date = 10.years.ago
+ within "form#edit_profile_#{user.profile.id}" do
+ fill_in "profile_college", with: "New College"
+ fill_in "profile_hometown", with: "New Hometown"
+ fill_in "profile_current_home", with: "New Current"
+ fill_in "profile_phone", with: "555-555-5555"
+ select new_date.year, from: "profile_birthday_1i"
+ select new_date.strftime('%B'), from: "profile_birthday_2i"
+ select new_date.day, from: "profile_birthday_3i"
+ click_button "Save Changes"
+ end
+
+ profile.reload
+ expect(profile.college).to eq("New College")
+ expect(profile.hometown).to eq("New Hometown")
+ expect(profile.birthday).to eq(new_date.to_date)
+ end
+
+ it "creates a bio if a slogan is filled out for the first time" do
+ fill_in "profile_bio_attributes_slogan", with: "Words Words Words"
+ expect{click_button "Save Changes"}.to change(Bio, :count).by(1)
+ end
+
+ it "creates a bio if the About Me section is filled out for the first time" do
+ fill_in "profile_bio_attributes_about", with: "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco"
+ expect{click_button "Save Changes"}.to change(Bio, :count).by(1)
+ end
+
+ it "Updates an existing bio" do
+ bio = create(:bio, profile: profile)
+ slogan = bio.slogan
+
+ visit edit_user_profile_path(user)
+
+ fill_in "profile_bio_attributes_slogan", with: "#{slogan} Words"
+
+ expect{click_button "Save Changes"}.to_not change(Bio, :count)
+ expect(bio.slogan).to_not eq("#{slogan} Words")
+ end
+
+ end
+
+ context "Other User" do
+ it "does not allow editing other user profiles" do
+ visit edit_user_profile_path(second_user)
+ expect(page).to_not have_current_path(edit_user_profile_path(second_user))
+ end
+
+ it "redirects back to the logged in user's profile" do
+ visit edit_user_profile_path(second_user)
+ expect(page).to have_current_path(user_profile_path(user))
+ end
+ end
+ end
+
+ context "Unauthenticated User" do
+ it "redirects to the login path" do
+ visit edit_user_profile_path(second_user)
+ expect(page).to have_current_path(login_path)
+ end
+ end
+ end
+
+end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
new file mode 100644
index 000000000..e9e653b1c
--- /dev/null
+++ b/spec/features/users_spec.rb
@@ -0,0 +1,99 @@
+require 'rails_helper'
+
+# `feature` is an alias for `describe`
+describe "Users" do
+ let(:profile){ create(:profile)}
+ let(:user){ profile.user }
+
+ feature 'Sign up' do
+
+ before do
+ visit root_path
+ end
+
+ # `scenario` is an alias for `it`
+ context "New user" do
+ it "creates a new user" do
+ # fill in the form for a new user
+ within "form#new_user" do
+ fill_in "user_profile_attributes_first_name", with: "Test"
+ fill_in "user_profile_attributes_last_name", with: "User"
+ fill_in "user_email", with: "foo@bar.com"
+ fill_in "user_password", with: "!23456Yuiopasdf"
+ fill_in "user_password_confirmation", with: "!23456Yuiopasdf"
+ select "2012", from: "user_profile_attributes_birthday_1i"
+ select "March", from: "user_profile_attributes_birthday_2i"
+ select "13", from: "user_profile_attributes_birthday_3i"
+ choose "Male"
+ end
+
+ # submit the form and verify it created a user
+ expect{ click_button "Sign Up!" }.to change(User, :count).by(1)
+
+ # verify that we've been logged in
+ expect(page).to have_css ".profile-username", text: "Test User"
+
+ # verify the flash is working properly
+ expect(page).to have_content "Successfully signed up!"
+ end
+
+ end
+
+ context "Existing email" do
+ it "rejects the attempt and informs the user" do
+ user.save
+ # fill in the form for a new user
+ within "form#new_user" do
+ fill_in "user_profile_attributes_first_name", with: "Test"
+ fill_in "user_profile_attributes_last_name", with: "User"
+ fill_in "user_email", with: user.email
+ fill_in "user_password", with: "!23456Yuiopasdf"
+ fill_in "user_password_confirmation", with: "!23456Yuiopasdf"
+ select "2012", from: "user_profile_attributes_birthday_1i"
+ select "March", from: "user_profile_attributes_birthday_2i"
+ select "13", from: "user_profile_attributes_birthday_3i"
+ choose "Male"
+ end
+
+ expect{ click_button "Sign Up!" }.to_not change(User, :count)
+ expect(page).to have_content "Email has already been taken"
+ end
+ end
+ end
+
+ feature 'Login' do
+ before do
+ visit login_path
+ end
+
+ context "with improper credentials" do
+ before do
+ user.email = user.email + "x"
+ sign_in(user)
+ end
+
+ scenario "does not allow sign in" do
+ expect(page).to have_content "Incorrect Credentials"
+ end
+ end
+
+ context "with proper credentials" do
+ before do
+ sign_in(user)
+ end
+ scenario "successfully signs in an existing user" do
+ # verify the user nav is shown
+ expect(page).to have_css ".profile-username", text: "Hermione Granger"
+ end
+
+ context "after signing out" do
+ before do
+ sign_out
+ end
+ scenario "the user should be logged out" do
+ expect(page).to have_css "div.alert-success p", text: "Signed Out"
+ end
+ end
+ end
+ end
+end
diff --git a/spec/helpers/notices_helper_spec.rb b/spec/helpers/notices_helper_spec.rb
new file mode 100644
index 000000000..79a616059
--- /dev/null
+++ b/spec/helpers/notices_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the NoticesHelper. For example:
+#
+# describe NoticesHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe NoticesHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/jobs/add_to_profile_photos_job_spec.rb b/spec/jobs/add_to_profile_photos_job_spec.rb
new file mode 100644
index 000000000..a677eac1d
--- /dev/null
+++ b/spec/jobs/add_to_profile_photos_job_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe AddToProfilePhotosJob, type: :job do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/jobs/fix_counters_job_spec.rb b/spec/jobs/fix_counters_job_spec.rb
new file mode 100644
index 000000000..df9a66f82
--- /dev/null
+++ b/spec/jobs/fix_counters_job_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe FixCountersJob, type: :job do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/jobs/process_image_job_spec.rb b/spec/jobs/process_image_job_spec.rb
new file mode 100644
index 000000000..088b2e6d4
--- /dev/null
+++ b/spec/jobs/process_image_job_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe ProcessImageJob, type: :job do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/mailers/.keep b/spec/mailers/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/spec/mailers/previews/profile_mailer_preview.rb b/spec/mailers/previews/profile_mailer_preview.rb
new file mode 100644
index 000000000..eb6ebface
--- /dev/null
+++ b/spec/mailers/previews/profile_mailer_preview.rb
@@ -0,0 +1,4 @@
+# Preview all emails at http://localhost:3000/rails/mailers/profile_mailer
+class ProfileMailerPreview < ActionMailer::Preview
+
+end
diff --git a/spec/mailers/profile_mailer_spec.rb b/spec/mailers/profile_mailer_spec.rb
new file mode 100644
index 000000000..925e534eb
--- /dev/null
+++ b/spec/mailers/profile_mailer_spec.rb
@@ -0,0 +1,5 @@
+require "rails_helper"
+
+RSpec.describe ProfileMailerMailer, type: :mailer do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/.keep b/spec/models/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/spec/models/bio_spec.rb b/spec/models/bio_spec.rb
new file mode 100644
index 000000000..e2cd115f1
--- /dev/null
+++ b/spec/models/bio_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+describe Bio do
+ it { is_expected.to belong_to(:profile) }
+end
diff --git a/spec/models/friend_request_spec.rb b/spec/models/friend_request_spec.rb
new file mode 100644
index 000000000..dfbebbe72
--- /dev/null
+++ b/spec/models/friend_request_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+describe FriendRequest do
+
+ let(:user){build(:user)}
+ let(:user2){build(:user)}
+ let(:user3){build(:user)}
+ let(:request){build(:friend_request, user: user, request: user2)}
+ let(:request_dup){build(:friend_request, user: user, request: user2)}
+
+ it "validates users can only request the same friend once" do
+ request.save
+ expect(request_dup).to_not be_valid
+ request_dup.update_attributes(user: user3, request: user2)
+ expect(request_dup).to be_valid
+ request_dup.update_attributes(user: user2, request: user)
+ expect(request_dup).to be_valid
+ end
+
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:request) }
+end
diff --git a/spec/models/friendify_spec.rb b/spec/models/friendify_spec.rb
new file mode 100644
index 000000000..e74867bd1
--- /dev/null
+++ b/spec/models/friendify_spec.rb
@@ -0,0 +1,64 @@
+require 'rails_helper'
+
+describe Friendify do
+
+ let(:friendify){ Friendify }
+ let(:user){build(:user)}
+ let(:friend){build(:user)}
+
+ before do
+ user.save
+ friend.save
+ end
+
+ describe '.friendship' do
+
+ it "processes a new friend request and returns a status message" do
+ expect(friendify.friendship(user, friend)[0]).to eq(:success)
+ expect(friend.friend_requests.length).to be > 0
+ end
+
+ it "rejects an already sent friend request and returns a status message" do
+ friendify.friendship(user, friend)
+ requests = friend.friend_requests.length
+ expect(friendify.friendship(user, friend)[0]).to eq(:danger)
+ expect(friend.friend_requests.length).to eq(requests)
+ end
+
+ it "rejects a self friend request and returns a status message" do
+ requests = user.friend_requests.length
+ expect(friendify.friendship(user, user)[0]).to eq(:danger)
+ expect(user.friend_requests.length).to eq(requests)
+ end
+
+ it "creates a friendship when a friend request is reciprocated and returns a status message" do
+ friends = user.friends.size
+ friendify.friendship(user, friend)
+ expect(friendify.friendship(friend, user)[0]).to eq(:success)
+ expect(user.friends.size).to be > friends
+ expect(friend.friends).to include(user)
+ end
+
+ it "rejects friend request if users are already friends and returns a status message" do
+ friendify.friendship(user, friend)
+ friendify.friendship(friend, user)
+ friends = user.friends.size
+ expect(friendify.friendship(user, friend)[0]).to eq(:danger)
+ expect(user.friends.size).to eq(friends)
+ end
+
+ end
+
+ describe '.clear_request' do
+
+ it "cancels a friend request and returns a status message" do
+ friendify.friendship(user, friend)
+ requests = friend.friend_requests.length
+ friendify.clear_request(user, friend)
+ friend.reload
+ expect(friend.friend_requests.length).to be < requests
+ end
+
+ end
+
+end
diff --git a/spec/models/friends_user_spec.rb b/spec/models/friends_user_spec.rb
new file mode 100644
index 000000000..c25ad0369
--- /dev/null
+++ b/spec/models/friends_user_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+describe FriendsUser do
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:friend) }
+end
diff --git a/spec/models/gallery_spec.rb b/spec/models/gallery_spec.rb
new file mode 100644
index 000000000..eada10f0c
--- /dev/null
+++ b/spec/models/gallery_spec.rb
@@ -0,0 +1,21 @@
+require 'rails_helper'
+
+describe Gallery do
+
+ let(:user){build(:user)}
+ let(:user2){build(:user)}
+ let(:gallery){build(:gallery, user: user, title: "test")}
+ let(:gallery_dup){build(:gallery, user: user, title: "test")}
+
+ it "validates unique gallery titles per user" do
+ gallery.save
+ expect(gallery_dup).to_not be_valid
+ gallery_dup.update_attributes(user: user, title: "not dup")
+ expect(gallery_dup).to be_valid
+ gallery_dup.update_attributes(user: user2, title: "test")
+ expect(gallery_dup).to be_valid
+ end
+
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to have_many(:images) }
+end
diff --git a/spec/models/image_spec.rb b/spec/models/image_spec.rb
new file mode 100644
index 000000000..37ecebb90
--- /dev/null
+++ b/spec/models/image_spec.rb
@@ -0,0 +1,6 @@
+require 'rails_helper'
+
+describe Image do
+
+ it { is_expected.to belong_to(:gallery) }
+end
diff --git a/spec/models/like_spec.rb b/spec/models/like_spec.rb
new file mode 100644
index 000000000..be3785dcf
--- /dev/null
+++ b/spec/models/like_spec.rb
@@ -0,0 +1,22 @@
+require 'rails_helper'
+
+describe Like do
+ let(:user){build(:user)}
+ let(:post){build(:post)}
+ let(:user2){build(:user)}
+ let(:post2){build(:post)}
+ let(:like){build(:like, user: user, post: post)}
+ let(:like_dup){build(:like, user: user, post: post)}
+
+ it "validates unique users per post" do
+ like.save
+ expect(like_dup).to_not be_valid
+ like_dup.update_attributes(user: user, post: post2)
+ expect(like_dup).to be_valid
+ like_dup.update_attributes(user: user2, post: post)
+ expect(like_dup).to be_valid
+ end
+
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:post) }
+end
diff --git a/spec/models/notice_spec.rb b/spec/models/notice_spec.rb
new file mode 100644
index 000000000..71510b2bb
--- /dev/null
+++ b/spec/models/notice_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Notice, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb
new file mode 100644
index 000000000..a20344377
--- /dev/null
+++ b/spec/models/post_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+describe Post do
+
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to belong_to(:post) }
+ it { is_expected.to have_many(:likes) }
+ it { is_expected.to have_many(:liked_users) }
+ it { is_expected.to have_many(:comments) }
+
+ it "includes User with all posts to avoid N+1" do
+ create(:post)
+ expect { Post.first.user }.to_not exceed_query_limit(2)
+ end
+end
diff --git a/spec/models/profile_spec.rb b/spec/models/profile_spec.rb
new file mode 100644
index 000000000..0dac5213c
--- /dev/null
+++ b/spec/models/profile_spec.rb
@@ -0,0 +1,71 @@
+require 'rails_helper'
+
+describe Profile do
+
+ let(:profile){build(:profile)}
+
+ it "requires a first_name" do
+ profile.update_attributes(first_name: nil)
+ expect(profile).to_not be_valid
+ end
+
+ it "requires a last_name" do
+ profile.update_attributes(last_name: nil)
+ expect(profile).to_not be_valid
+ end
+
+ it "requires a birthday" do
+ profile.update_attributes(birthday: nil)
+ expect(profile).to_not be_valid
+ end
+
+ it "requires a gender" do
+ profile.update_attributes(gender: nil)
+ expect(profile).to_not be_valid
+ end
+
+ it "does not discriminate gender roles" do
+ profile.update_attributes(gender: "Androgyne")
+ expect(profile).to be_valid
+ end
+
+ it "validates a phone number min and max length are within international formating standards" do
+ profile.update_attributes(phone: "#{'1'*4}")
+ expect(profile).to be_valid
+ profile.update_attributes(phone: "#{'1'*30}")
+ expect(profile).to be_valid
+ profile.update_attributes(phone: "123")
+ expect(profile).to_not be_valid
+ profile.update_attributes(phone: "#{'1'*31}")
+ expect(profile).to_not be_valid
+ end
+
+ it "validates a phone has at least one number" do
+ profile.update_attributes(phone: "asdf")
+ expect(profile).to_not be_valid
+ end
+
+ it "allows your choice of formating your phone number" do
+ profile.update_attributes(phone: "+61 13 15 17")
+ expect(profile).to be_valid
+ end
+
+ it "capitalizes First and Last Name before saving" do
+ profile.update_attributes(first_name: "foo", last_name: "bar")
+ expect(profile.first_name).to eq("Foo")
+ expect(profile.last_name).to eq("Bar")
+ end
+
+ describe ".genders" do
+ it "returns an array of genders for select options" do
+ expect(Profile.genders).to be_a(Array)
+ expect(Profile.genders[0]).to eq(["Male", "Male"])
+ end
+ end
+
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to have_one(:bio) }
+ it { is_expected.to have_one(:profile_gallery) }
+ it { is_expected.to have_many(:images) }
+ it { is_expected.to belong_to(:profile_img) }
+end
diff --git a/spec/models/sessionizer_spec.rb b/spec/models/sessionizer_spec.rb
new file mode 100644
index 000000000..1cda66245
--- /dev/null
+++ b/spec/models/sessionizer_spec.rb
@@ -0,0 +1,47 @@
+require 'rails_helper'
+
+describe Sessionizer do
+ let(:user){build(:user)}
+ let(:bad_params){{password: "23456Yuiopasdf"}}
+ let(:sess){ Sessionizer.validate_credentials(user, "!23456Yuiopasdf") }
+ let(:sess_bad){ Sessionizer.validate_credentials(user, "23456Yuiopasdf") }
+
+ before do
+ user
+ end
+
+ describe "#validate_credentials" do
+ it "logs in a user with valid credentials" do
+ expect(sess).to eq("valid")
+ end
+
+ it "rejects a user with invalid credentials" do
+ expect(sess_bad).to eq("invalid")
+ end
+
+ it "locks you out if you have 5 incorrect logins within an hour" do
+ 5.times do
+ Sessionizer.validate_credentials(user, "!23456Yuiopasdfa")
+ user.reload
+ end
+
+ expect(sess_bad).to eq("locked")
+ end
+
+ it "lockout count resets after an hour" do
+ 3.times do
+ Sessionizer.validate_credentials(user, "!23456Yuiopasdfa")
+ end
+ user.last_attempt = 1.hour.ago
+ expect(sess_bad).to eq("invalid")
+ expect(user.failed).to eq(1)
+
+ 5.times do
+ Sessionizer.validate_credentials(user, "!23456Yuiopasdfa")
+ end
+ user.last_attempt = 1.hour.ago
+ expect(sess_bad).to eq("invalid")
+ end
+ end
+
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
new file mode 100644
index 000000000..d6a78a437
--- /dev/null
+++ b/spec/models/user_spec.rb
@@ -0,0 +1,97 @@
+require 'rails_helper'
+
+describe User do
+
+ let(:user){build(:user)}
+ let(:full_pass){build(:user, password: "#{'a'*10}1A")}
+ let(:short_pass){build(:user, password: "#{'a'*9}1A")}
+ let(:numberless_pass){build(:user, password: "#{'a'*10}A")}
+ let(:no_cap_pass){build(:user, password: "#{'a'*12}1")}
+ let(:crazy_pass){build(:user, password: "!@$%^&*()\##{'a'*12}A1")}
+ let(:dup_email){build(:user, email: user.email)}
+ let(:double_a_email){build(:user, email: "22@@a.com")}
+ let(:no_domain_email){build(:user, email: "22@.com")}
+ let(:no_u_email){build(:user, email: "@a.com")}
+ let(:no_tld_email){build(:user, email: "22@a.")}
+
+ it "does not allow nil passwords on create" do
+ user.update_attributes(password: nil)
+ expect(user).to_not be_valid
+ end
+
+ it "requires a strong password" do
+ expect(full_pass).to be_valid
+ expect(short_pass).to_not be_valid
+ expect(numberless_pass).to_not be_valid
+ expect(no_cap_pass).to_not be_valid
+ end
+
+ it "allows any character in the password" do
+ expect(crazy_pass).to be_valid
+ end
+
+ it "requires a unique email" do
+ user.save
+ expect(dup_email).to_not be_valid
+ end
+
+ it "skips password validation if nil on update" do
+ user.save
+ user.update_attributes(email: "asdf@asdf.com")
+ expect(user).to be_valid
+ user.update_attributes(email: "asdf@asdf.com", password: "f")
+ expect(user).to_not be_valid
+ end
+
+ it "requires emails to be emails" do
+ expect(double_a_email).to_not be_valid
+ expect(no_domain_email).to_not be_valid
+ expect(no_u_email).to_not be_valid
+ expect(no_tld_email).to_not be_valid
+ end
+
+ it 'downcases all emails' do
+ user.update_attributes(email: "AsDf@ASDF.coM")
+ expect(user.email).to eq("asdf@asdf.com")
+ end
+
+ it 'creates a profile images gallery for new users' do
+ expect(user.galleries.size).to eq(0)
+ user.save
+ expect(user.galleries.size).to eq(1)
+ user.update_attributes(email: "AsDf@ASDF.coM")
+ expect(user.galleries.size).to eq(1)
+ end
+
+ describe "#regenerate_auth_token" do
+ it "destroys the old token and generates a new one" do
+ user.save
+ user.regenerate_auth_token
+ old_token = user.token
+ user.regenerate_auth_token
+ expect(user.token).to_not eq(old_token)
+ end
+ end
+
+ describe "#destroy_token" do
+ it "destroys the old token" do
+ user.save
+ user.regenerate_auth_token
+ user.destroy_token
+ expect(user.token).to be_nil
+ end
+ end
+
+ it { is_expected.to have_secure_password }
+ it { is_expected.to have_one(:profile) }
+ it { is_expected.to have_many(:f_requests) }
+ it { is_expected.to have_many(:friend_requests) }
+ it { is_expected.to have_many(:r_friends) }
+ it { is_expected.to have_many(:requested_friends) }
+ it { is_expected.to have_many(:friend_requests) }
+ it { is_expected.to have_many(:friends) }
+ it { is_expected.to have_many(:posts) }
+ it { is_expected.to have_many(:likes) }
+ it { is_expected.to have_many(:liked_posts) }
+ it { is_expected.to have_many(:galleries) }
+end
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
new file mode 100644
index 000000000..487191989
--- /dev/null
+++ b/spec/rails_helper.rb
@@ -0,0 +1,68 @@
+# This file is copied to spec/ when you run 'rails generate rspec:install'
+ENV['RAILS_ENV'] ||= 'test'
+require File.expand_path('../../config/environment', __FILE__)
+# Prevent database truncation if the environment is production
+abort("The Rails environment is running in production mode!") if Rails.env.production?
+require 'spec_helper'
+require 'rspec/rails'
+require 'factory_girl_rails'
+require 'capybara/rails'
+# Add additional requires below this line. Rails is not loaded until this point!
+
+# Requires supporting ruby files with custom matchers and macros, etc, in
+# spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are
+# run as spec files by default. This means that files in spec/support that end
+# in _spec.rb will both be required and run as specs, causing the specs to be
+# run twice. It is recommended that you do not name files matching this glob to
+# end with _spec.rb. You can configure this pattern with the --pattern
+# option on the command line or in ~/.rspec, .rspec or `.rspec-local`.
+#
+# The following line is provided for convenience purposes. It has the downside
+# of increasing the boot-up time by auto-requiring all files in the support
+# directory. Alternatively, in the individual `*_spec.rb` files, manually
+# require only the support files necessary.
+#
+Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }
+
+# Checks for pending migration and applies them before tests are run.
+# If you are not using ActiveRecord, you can remove this line.
+ActiveRecord::Migration.maintain_test_schema!
+
+RSpec.configure do |config|
+ config.include Rails.application.routes.url_helpers
+ config.alias_it_should_behave_like_to :it_has_behavior, 'has behavior:'
+
+ config.include FactoryGirl::Syntax::Methods
+
+ config.include LoginMacros
+ config.include PostMacros
+ config.include UserMacros
+
+ # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures
+ config.fixture_path = "#{::Rails.root}/spec/fixtures"
+
+ # If you're not using ActiveRecord, or you'd prefer not to run each of your
+ # examples within a transaction, remove the following line or assign false
+ # instead of true.
+ config.use_transactional_fixtures = true
+
+ # RSpec Rails can automatically mix in different behaviours to your tests
+ # based on their file location, for example enabling you to call `get` and
+ # `post` in specs under `spec/controllers`.
+ #
+ # You can disable this behaviour by removing the line below, and instead
+ # explicitly tag your specs with their type, e.g.:
+ #
+ # RSpec.describe UsersController, :type => :controller do
+ # # ...
+ # end
+ #
+ # The different available types are documented in the features, such as in
+ # https://relishapp.com/rspec/rspec-rails/docs
+ config.infer_spec_type_from_file_location!
+
+ # Filter lines from Rails gems in backtraces.
+ config.filter_rails_from_backtrace!
+ # arbitrary gems may also be filtered via:
+ # config.filter_gems_from_backtrace("gem name")
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
new file mode 100644
index 000000000..8f698be46
--- /dev/null
+++ b/spec/spec_helper.rb
@@ -0,0 +1,99 @@
+# This file was generated by the `rails generate rspec:install` command. Conventionally, all
+# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
+# The generated `.rspec` file contains `--require spec_helper` which will cause
+# this file to always be loaded, without a need to explicitly require it in any
+# files.
+#
+# Given that it is always loaded, you are encouraged to keep this file as
+# light-weight as possible. Requiring heavyweight dependencies from this file
+# will add to the boot time of your test suite on EVERY test run, even for an
+# individual file that may not need all of that loaded. Instead, consider making
+# a separate helper file that requires the additional dependencies and performs
+# the additional setup, and require it from the spec files that actually need
+# it.
+#
+# The `.rspec` file also contains a few flags that are not defaults but that
+# users commonly want.
+#
+# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
+RSpec.configure do |config|
+ # rspec-expectations config goes here. You can use an alternate
+ # assertion/expectation library such as wrong or the stdlib/minitest
+ # assertions if you prefer.
+ config.expect_with :rspec do |expectations|
+ # This option will default to `true` in RSpec 4. It makes the `description`
+ # and `failure_message` of custom matchers include text for helper methods
+ # defined using `chain`, e.g.:
+ # be_bigger_than(2).and_smaller_than(4).description
+ # # => "be bigger than 2 and smaller than 4"
+ # ...rather than:
+ # # => "be bigger than 2"
+ expectations.include_chain_clauses_in_custom_matcher_descriptions = true
+ end
+
+ # rspec-mocks config goes here. You can use an alternate test double
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
+ config.mock_with :rspec do |mocks|
+ # Prevents you from mocking or stubbing a method that does not exist on
+ # a real object. This is generally recommended, and will default to
+ # `true` in RSpec 4.
+ mocks.verify_partial_doubles = true
+ end
+
+ # This option will default to `:apply_to_host_groups` in RSpec 4 (and will
+ # have no way to turn it off -- the option exists only for backwards
+ # compatibility in RSpec 3). It causes shared context metadata to be
+ # inherited by the metadata hash of host groups and examples, rather than
+ # triggering implicit auto-inclusion in groups with matching metadata.
+ config.shared_context_metadata_behavior = :apply_to_host_groups
+
+# The settings below are suggested to provide a good initial experience
+# with RSpec, but feel free to customize to your heart's content.
+=begin
+ # This allows you to limit a spec run to individual examples or groups
+ # you care about by tagging them with `:focus` metadata. When nothing
+ # is tagged with `:focus`, all examples get run. RSpec also provides
+ # aliases for `it`, `describe`, and `context` that include `:focus`
+ # metadata: `fit`, `fdescribe` and `fcontext`, respectively.
+ config.filter_run_when_matching :focus
+
+ # Allows RSpec to persist some state between runs in order to support
+ # the `--only-failures` and `--next-failure` CLI options. We recommend
+ # you configure your source control system to ignore this file.
+ config.example_status_persistence_file_path = "spec/examples.txt"
+
+ # Limits the available syntax to the non-monkey patched syntax that is
+ # recommended. For more details, see:
+ # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/
+ # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
+ # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode
+ config.disable_monkey_patching!
+
+ # Many RSpec users commonly either run the entire suite or an individual
+ # file, and it's useful to allow more verbose output when running an
+ # individual spec file.
+ if config.files_to_run.one?
+ # Use the documentation formatter for detailed output,
+ # unless a formatter has already been configured
+ # (e.g. via a command-line flag).
+ config.default_formatter = 'doc'
+ end
+
+ # Print the 10 slowest examples and example groups at the
+ # end of the spec run, to help surface which specs are running
+ # particularly slow.
+ config.profile_examples = 10
+
+ # Run specs in random order to surface order dependencies. If you find an
+ # order dependency and want to debug it, you can fix the order by providing
+ # the seed, which is printed after each run.
+ # --seed 1234
+ config.order = :random
+
+ # Seed global randomization in this process using the `--seed` CLI option.
+ # Setting this allows you to use `--seed` to deterministically reproduce
+ # test failures related to randomization by passing the same `--seed` value
+ # as the one that triggered the failure.
+ Kernel.srand config.seed
+=end
+end
diff --git a/spec/support/examples/post_requests.rb b/spec/support/examples/post_requests.rb
new file mode 100644
index 000000000..fb44c6ac2
--- /dev/null
+++ b/spec/support/examples/post_requests.rb
@@ -0,0 +1,97 @@
+shared_examples_for "valid_create" do |model, redirect, amount|
+ it "should be successful" do
+ expect{process :create, params: {model => attributes_for(model)} }.to change(model.to_s.classify.constantize, :count).by(amount)
+ end
+
+ it "should set success flash messages" do
+ process :create, params: {model => attributes_for(model)}
+
+ expect(flash[:success]).to_not be_nil
+ end
+
+ it "should redirect to #{redirect}" do
+ process :create, params: {model => attributes_for(model)}
+
+ expect(response).to redirect_to(send(redirect))
+ end
+end
+
+shared_examples_for "invalid_create" do |model, params|
+ it "should be rejected" do
+ expect{process :create, params: params}.to_not change(model.to_s.classify.constantize, :count)
+ end
+
+ it "should set danger flash messages" do
+ process :create, params: params
+
+ expect(flash[:danger]).to_not be_nil
+ end
+
+ it "should not redirect" do
+ process :create, params: params
+
+ assert_response :success
+ end
+end
+
+shared_examples_for "valid_update" do |model, params, redirect|
+
+ it "should be successful" do
+ process :update, params: { id: checked.id, model => params }
+ checked.reload
+ params.keys.each do |k|
+ next if k.to_s.match(/password/)
+ expect(checked.send(k)).to eq(params[k])
+ end
+ end
+
+ it "should set success flash messages" do
+ process :update, params: {id: checked.id, model => params}
+
+ expect(flash[:success]).to_not be_nil
+ end
+
+ it "should redirect to #{redirect}" do
+ process :update, params: { id: checked.id, model => params }
+
+ expect(response).to redirect_to(send(redirect, checked.id))
+ end
+end
+
+shared_examples_for "invalid_update" do |model, params|
+ it "should be rejected" do
+ before = checked
+ process :update, params: { id: checked.id, model => params }
+ end
+
+ it "should set danger flash messages" do
+ process :update, params: {id: checked.id, model => params }
+
+ expect(flash[:danger]).to_not be_nil
+ end
+
+ it "should not redirect" do
+ process :update, params: { id: checked.id, model => params }
+
+ assert_response :success
+ end
+end
+
+shared_examples_for "unauthorized_update" do |model, params|
+ it "should be rejected" do
+ before = checked
+ process :update, params: { id: checked.id, model => params }
+ end
+
+ it "should set danger flash messages" do
+ process :update, params: {id: checked.id, model => params }
+
+ expect(flash[:danger]).to_not be_nil
+ end
+
+ it "should redirect" do
+ process :update, params: { id: checked.id, model => params }
+
+ assert_response :redirect
+ end
+end
diff --git a/spec/support/login_macros.rb b/spec/support/login_macros.rb
new file mode 100644
index 000000000..7e3cdd853
--- /dev/null
+++ b/spec/support/login_macros.rb
@@ -0,0 +1,14 @@
+module LoginMacros
+ def sign_in(user)
+ visit root_path
+ within "#login-dp" do
+ fill_in 'Email', with: user.email
+ fill_in 'Password', with: user.password
+ click_button 'Log In'
+ end
+ end
+
+ def sign_out
+ visit logout_path
+ end
+end
diff --git a/spec/support/matchers/exceed_query_limit.rb b/spec/support/matchers/exceed_query_limit.rb
new file mode 100644
index 000000000..0ea731c19
--- /dev/null
+++ b/spec/support/matchers/exceed_query_limit.rb
@@ -0,0 +1,17 @@
+RSpec::Matchers.define :exceed_query_limit do |expected|
+ supports_block_expectations
+
+ match do |block|
+ query_count(&block) > expected
+ end
+
+ failure_message_when_negated do |actual|
+ "Expected to run maximum #{expected} queries, got #{@counter.query_count}"
+ end
+
+ def query_count(&block)
+ @counter = ActiveRecord::QueryCounter.new
+ ActiveSupport::Notifications.subscribed(@counter.to_proc, 'sql.active_record', &block)
+ @counter.query_count
+ end
+end
diff --git a/spec/support/post_macros.rb b/spec/support/post_macros.rb
new file mode 100644
index 000000000..7c3ea095b
--- /dev/null
+++ b/spec/support/post_macros.rb
@@ -0,0 +1,7 @@
+module PostMacros
+ def create_new_post
+ visit root_path
+ fill_in "post_body", with: "Test New Post"
+ click_button "Post"
+ end
+end
diff --git a/spec/support/query_counter.rb b/spec/support/query_counter.rb
new file mode 100644
index 000000000..5bf273f7b
--- /dev/null
+++ b/spec/support/query_counter.rb
@@ -0,0 +1,17 @@
+module ActiveRecord
+ class QueryCounter
+ attr_reader :query_count
+
+ def initialize
+ @query_count = 0
+ end
+
+ def to_proc
+ lambda(&method(:callback))
+ end
+
+ def callback(name, start, finish, message_id, values)
+ @query_count += 1 unless %w(CACHE SCHEMA).include?(values[:name])
+ end
+ end
+end
diff --git a/spec/support/user_macros.rb b/spec/support/user_macros.rb
new file mode 100644
index 000000000..0a31cc2b9
--- /dev/null
+++ b/spec/support/user_macros.rb
@@ -0,0 +1,20 @@
+module UserMacros
+ def create_session(user)
+ session[:user_id] = Crypt.encrypt(user.id)
+ end
+
+ def create_friendship(user, friend)
+ create(:friends_user, user: user, friend: friend)
+ create(:friends_user, user: friend, friend: user)
+ end
+
+ def destroy_friendship(user, friend)
+ FriendsUser.
+ where(user_id: user.id, friend_id: friend.id).
+ or(
+ FriendsUser.
+ where(user_id: friend.id, friend_id: user.id)
+ ).
+ destroy_all
+ end
+end
diff --git a/tmp/.keep b/tmp/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/vendor/assets/javascripts/.keep b/vendor/assets/javascripts/.keep
new file mode 100644
index 000000000..e69de29bb
diff --git a/vendor/assets/stylesheets/.keep b/vendor/assets/stylesheets/.keep
new file mode 100644
index 000000000..e69de29bb
+ Ron Weasley + + Said on Thrsday 5/21/2014 + +
++ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut at, necessitatibus accusamus quo impedit, laboriosam nemo aut facere iure eos ex omnis nihil nesciunt! Placeat unde animi vitae dolore mollitia. ipsum dolor sit amet, consectetur adipisicing elit. Iusto dicta tenetur, est nemo in tempore obcaecati commodi architecto provident necessitatibus reiciendis delectus veritatis rerum ipsum ipsam corrupti, doloribus possimus a. ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur, veritatis, accusamus! Laboriosam ducimus error dolor natus neque quis consectetur deleniti magni sapiente expedita, iure animi in corporis inventore architecto. A. +
++ Like + 3 other people like this +
++ Harry Potter + + Said on Thrsday 5/21/2014 + +
++ Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ut at, necessitatibus accusamus quo impedit, laboriosam nemo aut facere iure eos ex omnis nihil nesciunt! Placeat unde animi vitae dolore mollitia. ipsum dolor sit amet, consectetur adipisicing elit. Iusto dicta tenetur, est nemo in tempore obcaecati commodi architecto provident necessitatibus reiciendis delectus veritatis rerum ipsum ipsam corrupti, doloribus possimus a. ipsum dolor sit amet, consectetur adipisicing elit. Aspernatur, veritatis, accusamus! Laboriosam ducimus error dolor natus neque quis consectetur deleniti magni sapiente expedita, iure animi in corporis inventore architecto. A. +
++ Like + 3 other people like this + Delete +
+