diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 000000000..bfae81647 Binary files /dev/null and b/.DS_Store differ diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..43950c4e6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# See https://help.github.com/articles/ignoring-files for more about ignoring files. +# +# If you find yourself ignoring temporary files generated by your text editor +# or operating system, you probably want to add a global ignore instead: +# git config --global core.excludesfile '~/.gitignore_global' + +# Ignore bundler config. +/.bundle + +# Ignore the default SQLite database. +/db/*.sqlite3 +/db/*.sqlite3-journal + +# Ignore all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore Byebug command history file. +.byebug_history + +# Notes +notes.md +tasks.md + +# Ignore application configuration +/config/application.yml + +# Ignore test files +/spec/test_files/* + +# Ignore these since we don't want to push them to heroku +public/assets \ No newline at end of file diff --git a/.rspec b/.rspec new file mode 100644 index 000000000..7e3eb5c7c --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--color +--require spec_helper +--format doc diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..e6317ac2e --- /dev/null +++ b/Gemfile @@ -0,0 +1,80 @@ +source 'https://rubygems.org' + +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + + +# Bundle edge Rails instead: gem 'rails', github: 'rails/rails' +gem 'rails', '~> 5.0.1' +# Use sqlite3 as the database for Active Record +# Use Puma as the app server +gem 'puma', '~> 3.0' +# Use SCSS for stylesheets +gem 'sass-rails', '~> 5.0' +# Use Uglifier as compressor for JavaScript assets +gem 'uglifier', '>= 1.3.0' +# Use CoffeeScript for .coffee assets and views +gem 'coffee-rails', '~> 4.2' +# See https://github.com/rails/execjs#readme for more supported runtimes +# gem 'therubyracer', platforms: :ruby + +# Use jquery as the JavaScript library +gem 'jquery-rails' +# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks +gem 'turbolinks', '~> 5' +# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder +gem 'jbuilder', '~> 2.5' +# Use Redis adapter to run Action Cable in production +# gem 'redis', '~> 3.0' +# Use ActiveModel has_secure_password +gem 'bcrypt', '~> 3.1.7' +gem "twitter-bootstrap-rails" +gem 'simple_form' +gem 'devise' +gem 'paperclip' +gem 'aws-sdk' +gem 'figaro' +gem 'delayed_job_active_record' +gem 'daemons' +gem 'will_paginate', '~> 3.1.0' + + +group :production do + gem 'pg' +end + +# Use Capistrano for deployment +# gem 'capistrano-rails', group: :development + +group :development, :test do + # Call 'byebug' anywhere in the code to stop execution and get a debugger console + gem 'byebug', platform: :mri + gem 'sqlite3' + gem 'rspec-rails' + gem 'factory_girl_rails', '~> 4.0' + gem 'capybara' + gem 'launchy' + gem 'faker' + +end + + +group :development do + # Access an IRB console on exception pages or by using <%= console %> anywhere in the code. + gem 'web-console', '>= 3.3.0' + gem 'listen', '~> 3.0.5' + # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring + gem 'spring' + gem 'spring-watcher-listen', '~> 2.0.0' + gem 'bullet' + gem 'jazz_fingers' + gem 'better_errors' + gem 'binding_of_caller' + gem 'guard-rspec', require: false + gem 'letter_opener' +end + +# Windows does not include zoneinfo files, so bundle the tzinfo-data gem +gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..ef13e017d --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,358 @@ +GEM + remote: https://rubygems.org/ + specs: + actioncable (5.0.2) + actionpack (= 5.0.2) + nio4r (>= 1.2, < 3.0) + websocket-driver (~> 0.6.1) + actionmailer (5.0.2) + actionpack (= 5.0.2) + actionview (= 5.0.2) + activejob (= 5.0.2) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.0.2) + actionview (= 5.0.2) + activesupport (= 5.0.2) + rack (~> 2.0) + rack-test (~> 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionview (5.0.2) + activesupport (= 5.0.2) + builder (~> 3.1) + erubis (~> 2.7.0) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + activejob (5.0.2) + activesupport (= 5.0.2) + globalid (>= 0.3.6) + activemodel (5.0.2) + activesupport (= 5.0.2) + activerecord (5.0.2) + activemodel (= 5.0.2) + activesupport (= 5.0.2) + arel (~> 7.0) + activesupport (5.0.2) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (~> 0.7) + minitest (~> 5.1) + tzinfo (~> 1.1) + addressable (2.5.1) + public_suffix (~> 2.0, >= 2.0.2) + arel (7.1.4) + awesome_print (1.7.0) + aws-sdk (2.9.15) + aws-sdk-resources (= 2.9.15) + aws-sdk-core (2.9.15) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-resources (2.9.15) + aws-sdk-core (= 2.9.15) + aws-sigv4 (1.0.0) + bcrypt (3.1.11) + better_errors (2.1.1) + coderay (>= 1.0.0) + erubis (>= 2.6.6) + rack (>= 0.9.0) + bindex (0.5.0) + binding_of_caller (0.7.2) + debug_inspector (>= 0.0.1) + builder (3.2.3) + bullet (5.5.1) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.10.0) + byebug (9.0.6) + capybara (2.14.0) + addressable + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + climate_control (0.1.0) + cocaine (0.5.8) + climate_control (>= 0.0.3, < 1.0) + coderay (1.1.1) + coffee-rails (4.2.1) + coffee-script (>= 2.2.0) + railties (>= 4.0.0, < 5.2.x) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + commonjs (0.2.7) + concurrent-ruby (1.0.5) + coolline (0.5.0) + unicode_utils (~> 1.4) + daemons (1.2.4) + debug_inspector (0.0.2) + delayed_job (4.1.2) + activesupport (>= 3.0, < 5.1) + delayed_job_active_record (4.1.1) + activerecord (>= 3.0, < 5.1) + delayed_job (>= 3.0, < 5) + devise (4.2.1) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0, < 5.1) + responders + warden (~> 1.2.3) + diff-lcs (1.3) + erubis (2.7.0) + execjs (2.7.0) + factory_girl (4.8.0) + activesupport (>= 3.0.0) + factory_girl_rails (4.8.0) + factory_girl (~> 4.8.0) + railties (>= 3.0.0) + faker (1.7.3) + i18n (~> 0.5) + ffi (1.9.18) + figaro (1.1.1) + thor (~> 0.14) + formatador (0.2.5) + globalid (0.4.0) + activesupport (>= 4.2.0) + guard (2.14.1) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (~> 1.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) + hirb (0.7.3) + i18n (0.8.1) + jazz_fingers (4.0.1) + awesome_print (~> 1.6) + hirb (~> 0.7) + pry (~> 0.10) + pry-byebug (~> 3.4) + pry-coolline (~> 0.2) + pry-doc (~> 0.6) + jbuilder (2.6.3) + activesupport (>= 3.0.0, < 5.2) + multi_json (~> 1.2) + jmespath (1.3.1) + jquery-rails (4.3.1) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + launchy (2.4.3) + addressable (~> 2.3) + less (2.6.0) + commonjs (~> 0.2.7) + less-rails (2.8.0) + actionpack (>= 4.0) + less (~> 2.6.0) + sprockets (> 2, < 4) + tilt + letter_opener (1.4.1) + launchy (~> 2.2) + listen (3.0.8) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + loofah (2.0.3) + nokogiri (>= 1.5.9) + lumberjack (1.0.11) + mail (2.6.5) + mime-types (>= 1.16, < 4) + method_source (0.8.2) + mime-types (3.1) + mime-types-data (~> 3.2015) + mime-types-data (3.2016.0521) + mimemagic (0.3.2) + mini_portile2 (2.1.0) + minitest (5.10.1) + multi_json (1.12.1) + nenv (0.3.0) + nio4r (2.0.0) + nokogiri (1.7.1) + mini_portile2 (~> 2.1.0) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) + orm_adapter (0.5.0) + paperclip (5.1.0) + activemodel (>= 4.2.0) + activesupport (>= 4.2.0) + cocaine (~> 0.5.5) + mime-types + mimemagic (~> 0.3.0) + pg (0.20.0) + pry (0.10.4) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + pry-byebug (3.4.2) + byebug (~> 9.0) + pry (~> 0.10) + pry-coolline (0.2.5) + coolline (~> 0.5) + pry-doc (0.10.0) + pry (~> 0.9) + yard (~> 0.9) + public_suffix (2.0.5) + puma (3.8.2) + rack (2.0.1) + rack-test (0.6.3) + rack (>= 1.0) + rails (5.0.2) + actioncable (= 5.0.2) + actionmailer (= 5.0.2) + actionpack (= 5.0.2) + actionview (= 5.0.2) + activejob (= 5.0.2) + activemodel (= 5.0.2) + activerecord (= 5.0.2) + activesupport (= 5.0.2) + bundler (>= 1.3.0, < 2.0) + railties (= 5.0.2) + sprockets-rails (>= 2.0.0) + rails-dom-testing (2.0.2) + activesupport (>= 4.2.0, < 6.0) + nokogiri (~> 1.6) + rails-html-sanitizer (1.0.3) + loofah (~> 2.0) + railties (5.0.2) + actionpack (= 5.0.2) + activesupport (= 5.0.2) + method_source + rake (>= 0.8.7) + thor (>= 0.18.1, < 2.0) + rake (12.0.0) + rb-fsevent (0.9.8) + rb-inotify (0.9.8) + ffi (>= 0.5.0) + responders (2.4.0) + actionpack (>= 4.2.0, < 5.3) + railties (>= 4.2.0, < 5.3) + rspec (3.6.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-core (3.6.0) + rspec-support (~> 3.6.0) + rspec-expectations (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-mocks (3.6.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.6.0) + rspec-rails (3.6.0) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.6.0) + rspec-expectations (~> 3.6.0) + rspec-mocks (~> 3.6.0) + rspec-support (~> 3.6.0) + rspec-support (3.6.0) + sass (3.4.23) + sass-rails (5.0.6) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + shellany (0.0.1) + simple_form (3.4.0) + actionpack (> 4, < 5.1) + activemodel (> 4, < 5.1) + slop (3.6.0) + spring (2.0.1) + activesupport (>= 4.2) + spring-watcher-listen (2.0.1) + listen (>= 2.7, < 4.0) + spring (>= 1.2, < 3.0) + sprockets (3.7.1) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.0) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + sqlite3 (1.3.13) + thor (0.19.4) + thread_safe (0.3.6) + tilt (2.0.7) + turbolinks (5.0.1) + turbolinks-source (~> 5) + turbolinks-source (5.0.3) + twitter-bootstrap-rails (4.0.0) + actionpack (~> 5.0, >= 5.0.1) + execjs (~> 2.7) + less-rails (~> 2.8, >= 2.8.0) + railties (~> 5.0, >= 5.0.1) + tzinfo (1.2.3) + thread_safe (~> 0.1) + uglifier (3.2.0) + execjs (>= 0.3.0, < 3) + unicode_utils (1.4.0) + uniform_notifier (1.10.0) + warden (1.2.7) + rack (>= 1.0) + web-console (3.5.0) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + websocket-driver (0.6.5) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.2) + will_paginate (3.1.5) + xpath (2.0.0) + nokogiri (~> 1.3) + yard (0.9.9) + +PLATFORMS + ruby + +DEPENDENCIES + aws-sdk + bcrypt (~> 3.1.7) + better_errors + binding_of_caller + bullet + byebug + capybara + coffee-rails (~> 4.2) + daemons + delayed_job_active_record + devise + factory_girl_rails (~> 4.0) + faker + figaro + guard-rspec + jazz_fingers + jbuilder (~> 2.5) + jquery-rails + launchy + letter_opener + listen (~> 3.0.5) + paperclip + pg + puma (~> 3.0) + rails (~> 5.0.1) + rspec-rails + sass-rails (~> 5.0) + simple_form + spring + spring-watcher-listen (~> 2.0.0) + sqlite3 + turbolinks (~> 5) + twitter-bootstrap-rails + tzinfo-data + uglifier (>= 1.3.0) + web-console (>= 3.3.0) + will_paginate (~> 3.1.0) + +BUNDLED WITH + 1.14.6 diff --git a/Guardfile b/Guardfile new file mode 100644 index 000000000..3215f0137 --- /dev/null +++ b/Guardfile @@ -0,0 +1,70 @@ +# A sample Guardfile +# More info at https://github.com/guard/guard#readme + +## Uncomment and set this to only include directories you want to watch +# directories %w(app lib config test spec features) \ +# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} + +## Note: if you are using the `directories` clause above and you are not +## watching the project directory ('.'), then you will want to move +## the Guardfile to a watched dir and symlink it back, e.g. +# +# $ mkdir config +# $ mv Guardfile config/ +# $ ln -s config/Guardfile . +# +# and, you'll have to watch "config/Guardfile" instead of "Guardfile" + +# Note: The cmd option is now required due to the increasing number of ways +# rspec may be run, below are examples of the most common uses. +# * bundler: 'bundle exec rspec' +# * bundler binstubs: 'bin/rspec' +# * spring: 'bin/rspec' (This will use spring if running and you have +# installed the spring binstubs per the docs) +# * zeus: 'zeus rspec' (requires the server to be started separately) +# * 'just' rspec: 'rspec' + +guard :rspec, cmd: "bundle exec rspec" do + require "guard/rspec/dsl" + dsl = Guard::RSpec::Dsl.new(self) + + # Feel free to open issues for suggestions and improvements + + # RSpec files + rspec = dsl.rspec + watch(rspec.spec_helper) { rspec.spec_dir } + watch(rspec.spec_support) { rspec.spec_dir } + watch(rspec.spec_files) + + # Ruby files + ruby = dsl.ruby + dsl.watch_spec_files_for(ruby.lib_files) + + # Rails files + rails = dsl.rails(view_extensions: %w(erb haml slim)) + dsl.watch_spec_files_for(rails.app_files) + dsl.watch_spec_files_for(rails.views) + + watch(rails.controllers) do |m| + [ + rspec.spec.call("routing/#{m[1]}_routing"), + rspec.spec.call("controllers/#{m[1]}_controller"), + rspec.spec.call("acceptance/#{m[1]}") + ] + end + + # Rails config changes + watch(rails.spec_helper) { rspec.spec_dir } + watch(rails.routes) { "#{rspec.spec_dir}/routing" } + watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } + + # Capybara features specs + watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") } + watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") } + + # Turnip features and steps + watch(%r{^spec/acceptance/(.+)\.feature$}) + watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m| + Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance" + end +end diff --git a/README.md b/README.md index 24602872a..d6ecb198b 100644 --- a/README.md +++ b/README.md @@ -2,3 +2,31 @@ danebook ======== This is the Real Dane Deal. + +A [Viking Code School](http://vikingcodeschool.com) Project + +--- + +## Features + +- User Authentication and registration with Devise gem +- Web and local image file uploads to Amazon S3 +- Delayed welcome emails with Delayed Job gem. Welcome emails include friend recommendations. +- Lightbox for viewing photos in the gallery +- Limited access to photos for logged out users +- Limited posting and commenting ability for logged out users +- Notification emails for comments +- + +### Notes +- Set `ENV['USE_DELAYED_EMAILS']` to `'true'` to enable delayed emails, then do the other stuff to get them sent out. + +### To-Dos +- [ ] Set up delayed uploads +- [ ] Infinite scrolling! +- [ ] Use lightbox for photo posts in timeline and newsfeed? +- [ ] + +Yi-Xuan Lau + + diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..e85f91391 --- /dev/null +++ b/Rakefile @@ -0,0 +1,6 @@ +# Add your own tasks in files placed in lib/tasks ending in .rake, +# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. + +require_relative 'config/application' + +Rails.application.load_tasks diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100644 index 000000000..26b601cbf Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/assets/.DS_Store b/app/assets/.DS_Store new file mode 100644 index 000000000..c8de6835f Binary files /dev/null and b/app/assets/.DS_Store differ diff --git a/app/assets/config/manifest.js b/app/assets/config/manifest.js new file mode 100644 index 000000000..b16e53d6d --- /dev/null +++ b/app/assets/config/manifest.js @@ -0,0 +1,3 @@ +//= link_tree ../images +//= link_directory ../javascripts .js +//= link_directory ../stylesheets .css diff --git a/app/assets/images/.keep b/app/assets/images/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/images/avatar_missing.png b/app/assets/images/avatar_missing.png new file mode 100644 index 000000000..e52535f55 Binary files /dev/null and b/app/assets/images/avatar_missing.png differ diff --git a/app/assets/images/cover_missing.png b/app/assets/images/cover_missing.png new file mode 100644 index 000000000..286d8cfc0 Binary files /dev/null and b/app/assets/images/cover_missing.png differ diff --git a/app/assets/images/icon_photo_small.png b/app/assets/images/icon_photo_small.png new file mode 100644 index 000000000..0a900d16f Binary files /dev/null and b/app/assets/images/icon_photo_small.png differ diff --git a/app/assets/images/medium_missing.png b/app/assets/images/medium_missing.png new file mode 100644 index 000000000..e52535f55 Binary files /dev/null and b/app/assets/images/medium_missing.png differ diff --git a/app/assets/images/thumb_missing.png b/app/assets/images/thumb_missing.png new file mode 100644 index 000000000..e52535f55 Binary files /dev/null and b/app/assets/images/thumb_missing.png differ diff --git a/app/assets/images/user_silhouette_generic.png b/app/assets/images/user_silhouette_generic.png new file mode 100644 index 000000000..e52535f55 Binary files /dev/null and b/app/assets/images/user_silhouette_generic.png differ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js new file mode 100644 index 000000000..c20e88760 --- /dev/null +++ b/app/assets/javascripts/application.js @@ -0,0 +1,17 @@ +// This is a manifest file that'll be compiled into application.js, which will include all the files +// listed below. +// +// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, +// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. +// +// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the +// compiled file. JavaScript code in this file should be added after the last require_* statement. +// +// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details +// about supported directives. +// +//= require jquery +//= require jquery_ujs +//= require twitter/bootstrap +//= require turbolinks +//= require_tree . \ No newline at end of file diff --git a/app/assets/javascripts/bootstrap.js.coffee b/app/assets/javascripts/bootstrap.js.coffee new file mode 100644 index 000000000..e97fa7b8e --- /dev/null +++ b/app/assets/javascripts/bootstrap.js.coffee @@ -0,0 +1,3 @@ +# jQuery -> + # $("a[rel~=popover], .has-popover").popover() + # $("a[rel~=tooltip], .has-tooltip").tooltip() diff --git a/app/assets/javascripts/cable.js b/app/assets/javascripts/cable.js new file mode 100644 index 000000000..71ee1e66d --- /dev/null +++ b/app/assets/javascripts/cable.js @@ -0,0 +1,13 @@ +// Action Cable provides the framework to deal with WebSockets in Rails. +// You can generate new channels where WebSocket features live using the rails generate channel command. +// +//= require action_cable +//= require_self +//= require_tree ./channels + +(function() { + this.App || (this.App = {}); + + App.cable = ActionCable.createConsumer(); + +}).call(this); diff --git a/app/assets/javascripts/channels/.keep b/app/assets/javascripts/channels/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/assets/javascripts/comments.coffee b/app/assets/javascripts/comments.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/comments.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/friendships.coffee b/app/assets/javascripts/friendships.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/friendships.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/lightbox.js b/app/assets/javascripts/lightbox.js new file mode 100644 index 000000000..ad6400a9f --- /dev/null +++ b/app/assets/javascripts/lightbox.js @@ -0,0 +1,68 @@ +var LIGHTBOX = LIGHTBOX || {} + +LIGHTBOX.Main = (function() { + var _$lightbox; + var _$contents; + var _$frame; + var _isHovering; + + function init(contents) { + _isHovering = false; + _$contents = contents; + _setUpContents(); + _voila(); + _setUpClose(); + + } + + function _setUpContents() { + var promise = $('body').append(_$contents).promise(); + promise.done(function() { + _setUpFrame(); + }); + } + + function _setUpClose() { + $('.close-toggle').on('click', function() { + $('#lightbox').remove(); + }); + $('#lightbox .frame').on('mouseenter', function() { + isHovering = true; + }).on('mouseleave', function() { + isHovering = false; + }); + $('#lightbox').on('click', function() { + if (!isHovering) { + $('#lightbox').remove(); + } + }) + + } + + function _voila() { + $('#lightbox .contents, #lightbox .frame').css({ + visibility: 'visible' + }) + } + + + function _setUpFrame() { + var windowHeight = $(window).height(); + var windowWidth = $(window).width(); + var contentWidth = _$contents.width() > 800 ? 800 : _$contents.width(); + var contentHeight = $('#lightbox .frame').height(); + console.log(windowHeight, windowWidth, contentWidth, contentHeight); + + $('#lightbox .frame').css({ + top: (windowHeight - contentHeight) / 2 + 'px', + left: (windowWidth - contentWidth) / 2 + 'px', + }); + + } + + + return { + init: init + } + +})(); \ No newline at end of file diff --git a/app/assets/javascripts/likes.coffee b/app/assets/javascripts/likes.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/likes.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/newsfeed.coffee b/app/assets/javascripts/newsfeed.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/newsfeed.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/photos.coffee b/app/assets/javascripts/photos.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/photos.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/posts.coffee b/app/assets/javascripts/posts.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/posts.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/profiles.coffee b/app/assets/javascripts/profiles.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/profiles.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/site.js b/app/assets/javascripts/site.js new file mode 100644 index 000000000..c3f2301e4 --- /dev/null +++ b/app/assets/javascripts/site.js @@ -0,0 +1,33 @@ +var APP = APP || {} + +APP.Main = (function() { + var init = function() { + _setCommentWidth(); + _listenForCommentToggle(); + } + + function _setCommentWidth() { + // we add a width here to prevent that slide jump glitch + $('[data-toggle-target=comments]').width($('.post').width()); + + } + + function _listenForCommentToggle() { + console.log('listen for comment toggle'); + $('a[data-toggle="comments"]').on('click', function() { + console.log('comments clicked'); + var $post = $('article[data-post-id=' + $(this).data('post-id') + ']'); + $post.find('[data-toggle-target=comments]').slideToggle(); + }) + } + + + return { + init: init + } + +})(); + +$(document).on('turbolinks:load', function() { + APP.Main.init(); +}); \ No newline at end of file diff --git a/app/assets/javascripts/static_pages.coffee b/app/assets/javascripts/static_pages.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/static_pages.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/javascripts/users.coffee b/app/assets/javascripts/users.coffee new file mode 100644 index 000000000..24f83d18b --- /dev/null +++ b/app/assets/javascripts/users.coffee @@ -0,0 +1,3 @@ +# Place all the behaviors and hooks related to the matching controller here. +# All this logic will automatically be available in application.js. +# You can use CoffeeScript in this file: http://coffeescript.org/ diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css new file mode 100644 index 000000000..0ebd7fe82 --- /dev/null +++ b/app/assets/stylesheets/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css b/app/assets/stylesheets/bootstrap_and_overrides.css new file mode 100644 index 000000000..5b887c7b5 --- /dev/null +++ b/app/assets/stylesheets/bootstrap_and_overrides.css @@ -0,0 +1,6 @@ +/* + =require twitter-bootstrap-static/bootstrap + + Static version of css will use Glyphicons sprites by default + =require twitter-bootstrap-static/sprites +*/ \ No newline at end of file diff --git a/app/assets/stylesheets/comments.scss b/app/assets/stylesheets/comments.scss new file mode 100644 index 000000000..3722c124e --- /dev/null +++ b/app/assets/stylesheets/comments.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the comments controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/custom.scss b/app/assets/stylesheets/custom.scss new file mode 100644 index 000000000..bba5799a1 --- /dev/null +++ b/app/assets/stylesheets/custom.scss @@ -0,0 +1,605 @@ +// Place all the styles related to the StaticPages controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ + +/* VARS */ +$brand-color:#2B78E4; +$border-color-light: #ccc; +$light-gray: #f9f9f9; + +/* NAVBAR */ +.navbar-form { + border:0; +} +.navbar-default .navbar-collapse, .navbar-default .navbar-form { + border: 0; +} +.navbar-collapse { + box-shadow: none; +} +.navbar-default .navbar-text { + color: #fff; +} +/* NAVBAR DROPDOWN */ +.navbar-default .navbar-nav>li>a, .navbar-default .navbar-nav>li>a:hover { + color: #fff; +} +.navbar-default .navbar-nav>.open>a, .navbar-default .navbar-nav>.open>a:hover, .navbar-default .navbar-nav>.open>a:focus { + background-color: #2561b3; + color: #fff; +} +.navbar-default .navbar-toggle .icon-bar { + background-color: #fff; +} +.navbar-default .navbar-toggle:hover, .navbar-default .navbar-toggle:focus { + background-color: transparent; +} + +@media (max-width: 767px) { +.navbar-default .navbar-nav .open .dropdown-menu>li>a { + color: #fff; +} +} + +.dropdown-menu.friend-requests { + li { + padding: 3px 20px 10px; + border-bottom: 1px solid #ccc; + } + li:last-child { + border-bottom: 0; + } + li a { + color: #000; + } + li a.btn { + display:inline-block; + margin:0px 5px; + } +} + +/* NAVBAR SIGN UP */ +.navbar.sign-up { + .btn { + position: relative; + top:35px; + } +} + + /* MAIN */ + + /* compensate for fixed navbar */ + .main { + position: relative; + top: 50px; + } + +.logged-out { + .navbar-brand { + color: #fff; + font-size: 36px; + } + &.navbar-default .navbar-brand:hover, &.navbar-default .navbar-brand:focus { + color: #fff; + } + &.navbar { + background-color: $brand-color; + border-radius: 0; + border: 0; + margin-bottom:0; + } + .navbar-default .navbar-toggle .icon-bar { + color: #fff; + background-color: #fff; + } + .navbar-default .navbar-toggle { + border-color: #fff; + } + .navbar-default .navbar-collapse, .navbar-default .navbar-form { + border-color: $brand-color; + margin: 0; + padding-top: 5px; + } + .navbar-form label { + display: block; + color: #fff; + } + .navbar-form .form-button { + vertical-align: bottom; + } + .navbar-form .btn { + vertical-align: bottom; + } + + + /* MAIN */ + + .connect-message { + margin-top: 50px; + } + + /* SIGN UP LIST */ + .signup-list li, .signup-list { + margin: 16px 0; + font-size: 16px; + } + + /* BUTTON MOD */ + .btn.btn-action { + background-color: #9FC5F8; + border: 1px solid #9FC5F8; + } + + + /* MEDIA QUERIES */ + @media screen and (max-width:991px) { + .connect-message, .main { + margin-top: 0; + } + .signup-list { + margin-bottom: 40px; + } + } + +} + +.logged-in { + a { + color: #fff; + } + + /* NAVBAR */ + &.navbar { + background-color: $brand-color; + border: 0; + } + .navbar-logo { + width: 20px; + height: 20px; + float: left; + margin-right: 10px; + } + .navbar-default .navbar-brand, .navbar-default .navbar-brand:hover, .navbar-default a, .navbar-default a:hover { + color: #fff; + } + .navbar-collapse { + border: 0; + } + .navbar-form { + width: 100%; + border: 0; + + .form-group, .form-control { + width: 100%; + } + } + .navbar-right { + margin-right: 0; + } + + /* WIDE SCREEN SETTINGS */ + @media screen and (min-width:768px) { + .navbar-form { + width: 60%; + margin-top: 10px; + } + } +} + +/* DEDICATED LOG IN PAGE */ +.login-box { + max-width:500px; + margin: 0 auto; +} + + /* FRIEND GRID */ + .friend-grid { + padding: 10px; + } + .friend-item { + border: 1px solid $border-color-light; + padding: 10px; + margin: 10px 0; + + .media-left { + min-width: 80px; + } + + .img-thumbnail { + width: 80px; + height: 80px; + } + } + + /* PHOTO COMMENTS */ + .photo-comments { + .comment-form { + background: transparent; + margin-top: 15px; + padding-left: 0; + } + .post-comment { + background: $light-gray; + // padding-top: 10px; + margin-right:10px; + padding: 10px 0; + } + } + + + /* PHOTO GRID */ + .photo-grid { + .photo-item { + position: relative; + overflow: hidden; + margin: 10px auto; + + figcaption { + position: absolute; + opacity: 0; + bottom: 0; + background: rgba(102, 102, 102, .7); + color: #fff; + padding: 5px; + transition: all 0.3s ease; + width: 100%; + } + } + .photo-item:hover { + figcaption { + opacity: 1; + } + } + .photo-bg { + height: 150px; + background-size: cover; + background-position: 50% 50%; + background-repeat: no-repeat; + } + } + + /* TIMELINE POST */ + .post-footer { + border-top: 1px solid $border-color-light; + background-color: $light-gray; + margin-top: 10px; + padding: 5px 0; + + .post-action a { + padding-right: 10px; + } + } + .post-form { + margin-bottom: 0; + } + .post { + border: 1px solid $border-color-light; + padding: 10px 0 0; + margin-bottom: 20px; + + .post-footer { + padding: 10px; + } + } + .post-reaction { + margin: 10px 0 0; + } + .post-comments { + margin-top: 0; + + .post-comment { + margin-bottom: 10px; + } + } + .post .img-thumbnail { + width: 40px; + height: 40px; + } + .post .media-heading { + margin-top: 5px; + } + + /* COMMENT CONTAINER */ + div[data-toggle-target=comments] { + display:none; + } + + + + + /* COMMENT FORM */ + .comment-form { + background: $light-gray; + margin-bottom: 0; + padding-bottom: 10px; + + textarea { + margin-bottom: 10px; + } + } + + /* PROFILE */ + .profile-top { + border: 1px solid $border-color-light; + position: relative; + border-top: 0; + margin-bottom: 20px; + padding: 0; + } + .profile-text { + position: absolute; + left: 170px; + margin: 0; + bottom: 50px; + color: #fff; + .btn { + vertical-align: top; + } + } + .profile-name { + display: inline; + } + .profile-pic { + width: 150px; + height: 150px; + position: absolute; + bottom: 10px; + left: 10px; + z-index: 1; + } + .profile-nav { + padding-left: 170px; + border-top: 1px solid $border-color-light; + position: relative; + + .nav > li > a { + display: inline-block; + padding: 10px 0; + } + + .nav>li>a:focus { + background-color: transparent; + } + + .nav-pills a:hover { + border-radius: 0; + background-color: transparent; + } + .nav-pills li { + border-left: 1px solid $border-color-light; + margin-left: 0; + padding: 0 10px; + } + .nav-pills li:last-of-type { + border-right: 1px solid $border-color-light; + } + .current { + background-color: $light-gray; + } + .current a { + color: #333; + cursor: default; + } + } + .profile-cover { + height: 300px; + width: auto; + background-repeat: no-repeat; + background-size: cover; + background-position: 50% 50%; + } + .profile-edit { + position: absolute; + right: 10px; + bottom: 0; + } + + + /* ABOUT FORM */ + .about-form { + button { + margin: 40px auto; + max-width: 400px; + } + } + .bio { + dd { + margin-bottom: 10px; + margin-top: 10px; + } + dt { + text-align: left; + } + } + + + + /* TIMELINE */ + +.timeline-feed { + margin-left: 15px; + } + + .info-box { + border: 1px solid $border-color-light; + margin-bottom: 20px; + + header { + background-color: $light-gray; + border-bottom: 1px solid $border-color-light; + margin-bottom: 10px; + text-align: center; + } + .module-title { + display: inline-block; + margin-top: 10px; + } + .module-edit { + right: 10px; + @extend .vertical-middle; + } + .thumbnail-grid .img-thumbnail { + margin-bottom: 10px; + } + .thumbnail-grid .name { + margin-top: -5px; + line-height: 1.2; + } + } + + /* GLOBAL SETTINGS */ + a:hover { + text-decoration: none; + } + + .container { + max-width: 900px; + } + .img-thumbnail { + border-radius: 0; + min-width: 40px; + min-height: 40px; + } + + /* BUTTON */ + .btn-plain { + padding: 0; + } + + /* NEWSFEED */ + .newsfeed-sidebar { + .media-object.small { + width:50px; + height:50px; + } + } + .friend-feed { + .media { + padding-left:0 + } + } + + /* SEARCH RESULTS */ +.search-item { + .media-body p { + margin-bottom:0; + } +} + + + /* MISSING BOOTSTRAP CLASS?! */ + .media-body, .media-left, .media-right { + display: table-cell; + vertical-align: top; + } + .media-left { + padding-right: 10px; + } + .media-body { + width: 100%; + } + + .btn-dull:hover, .btn-dull { + background: #999; + color: #fff; + } + .btn-dull:hover { + background: #666; + } + +/* PAGINATION */ +.custom-pagination { + margin: 1.5em auto; + text-align: center; + a, em, span { + margin-left: -5px; + position: relative; + padding: 6px 12px; + line-height: 1.42857143; + color: #337ab7; + text-decoration: none; + background-color: #fff; + border: 1px solid #ddd; + &:hover, &:focus { + z-index: 2; + color: #23527c; + background-color: #eee; + border-color: #ddd; + } + } + .next_page { + border-top-right-radius: 4px; + border-bottom-right-radius: 4px; + } + .previous_page { + border-top-left-radius: 4px; + border-bottom-left-radius: 4px; + } + .current, .current:hover { + z-index: 3; + color: #fff; + cursor: default; + background-color: #337ab7; + border-color: #337ab7; + } + .disabled, .disabled:hover, .disabled:focus { + color: #777; + cursor: not-allowed; + background-color: #fff; + border-color: #ddd; + } + +} + + + /* HELPER CLASS */ + .text-bold { + font-weight: bold; + } + .text-light { + color: #999; + } + .pan { + padding: 0; + } +.vertical-middle { + position: absolute; + top: 50%; + transform: translateY(-50%); +} +.absolute-right { + right: 10px; +} +.display-inline { + display: inline; +} +.full-width { + width: 100%; +} +.mts { +margin-top:1em; +} +.mbs { +margin-bottom: 1em; +} +.mbm { +margin-bottom: 1.5em; +} +.mtm { +margin-top: 1.5em; +} +.background-light { +background-color: $light-gray; +} +.pbs { +margin-bottom: 1em; +} +.mbn { +margin-bottom:0; +} +.i { + font-style: italic; +} +.fake-link { + cursor: pointer; +} \ No newline at end of file diff --git a/app/assets/stylesheets/friendships.scss b/app/assets/stylesheets/friendships.scss new file mode 100644 index 000000000..8c5a6c82d --- /dev/null +++ b/app/assets/stylesheets/friendships.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the friendships controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/lightbox.scss b/app/assets/stylesheets/lightbox.scss new file mode 100644 index 000000000..d1aca7083 --- /dev/null +++ b/app/assets/stylesheets/lightbox.scss @@ -0,0 +1,39 @@ +#lightbox { + position:absolute; + width:100%; + height:100%; + background-color: rgba(51, 51, 51, 0.8); + z-index:1000; + top:0; + + .frame { + width:100%; + max-width: 800px; + position: absolute; + z-index: 2000; + background: #fff; + visibility:hidden; + } + + .contents { + background: #fff; + opacity: 1; + } + + .photo-comments { + padding: 20px; + } + + .photo-photo { + padding-left: 0; + } + .close-toggle { + position:absolute; + right: 5px; + top: 0; + z-index:9999; + } + .close-toggle:hover { + cursor: pointer; + } +} diff --git a/app/assets/stylesheets/likes.scss b/app/assets/stylesheets/likes.scss new file mode 100644 index 000000000..1f08d329f --- /dev/null +++ b/app/assets/stylesheets/likes.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the likes controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/newsfeed.scss b/app/assets/stylesheets/newsfeed.scss new file mode 100644 index 000000000..a6665bf4b --- /dev/null +++ b/app/assets/stylesheets/newsfeed.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the newsfeed controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/photos.scss b/app/assets/stylesheets/photos.scss new file mode 100644 index 000000000..1a3e082cd --- /dev/null +++ b/app/assets/stylesheets/photos.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the photos controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/posts.scss b/app/assets/stylesheets/posts.scss new file mode 100644 index 000000000..1a7e15390 --- /dev/null +++ b/app/assets/stylesheets/posts.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the posts controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/profiles.scss b/app/assets/stylesheets/profiles.scss new file mode 100644 index 000000000..287733f3e --- /dev/null +++ b/app/assets/stylesheets/profiles.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the profiles controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/assets/stylesheets/users.scss b/app/assets/stylesheets/users.scss new file mode 100644 index 000000000..1efc835cc --- /dev/null +++ b/app/assets/stylesheets/users.scss @@ -0,0 +1,3 @@ +// Place all the styles related to the users controller here. +// They will automatically be included in application.css. +// You can use Sass (SCSS) here: http://sass-lang.com/ diff --git a/app/channels/application_cable/channel.rb b/app/channels/application_cable/channel.rb new file mode 100644 index 000000000..d67269728 --- /dev/null +++ b/app/channels/application_cable/channel.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Channel < ActionCable::Channel::Base + end +end diff --git a/app/channels/application_cable/connection.rb b/app/channels/application_cable/connection.rb new file mode 100644 index 000000000..0ff5442f4 --- /dev/null +++ b/app/channels/application_cable/connection.rb @@ -0,0 +1,4 @@ +module ApplicationCable + class Connection < ActionCable::Connection::Base + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb new file mode 100644 index 000000000..a24349deb --- /dev/null +++ b/app/controllers/application_controller.rb @@ -0,0 +1,32 @@ +class ApplicationController < ActionController::Base + include ApplicationHelper + protect_from_forgery with: :exception + before_action :authenticate_user! + before_action :configure_permitted_parameters, if: :devise_controller? + + protected + + def configure_permitted_parameters + devise_parameter_sanitizer.permit(:sign_up, keys: [:username, :email, profile_attributes: [:sex, :birthdate]]) + end + + def authenticate_user! + if user_signed_in? + super + else + flash[:error] = "You must log in to continue." + redirect_to new_user_session_path + end + end + + private + + def after_sign_out_path_for(resource_or_scope) + root_path + end + + def after_sign_in_path_for(resource_or_scope) + root_path + end + +end diff --git a/app/controllers/comments_controller.rb b/app/controllers/comments_controller.rb new file mode 100644 index 000000000..ad2e6a069 --- /dev/null +++ b/app/controllers/comments_controller.rb @@ -0,0 +1,77 @@ +class CommentsController < ApplicationController + before_action :commentable_set_up, only: [:create] + + def create + @comment = current_user.comments.build(comment_params.merge({commentable_id: @params, commentable_type: params[:commentable]})) + if @comment.save + respond_to do |format| + format.html do + redirect_to @redirection_path + flash[:success] = "Comment posted!" + end + format.js + end + else + respond_to do |format| + format.html do + flash[:error] = "We couldn't post that comment" + redirect_to @redirection_path + end + format.js + end + end + end + + def destroy + @comment = Comment.find(params[:id]) + @post_author = @comment.commentable.user + @user = @comment.user + if is_self? + if @comment.destroy + respond_to do |format| + format.html do + redirect_to :back + flash[:success] = "Your comment has been deleted" + end + format.js + end + else + respond_to do |format| + format.html {redirect_to :back} + format.js + end + flash[:error] = "That comment cannot be deleted" + end + # if trying to delete someone else's comment: + else + flash[:error] = "Sorry, you can't delete that comment" + respond_to do |format| + format.html { redirect_to :back} + format.js + end + end + end + + private + + def comment_params + if params[:comment] + return params.require(:comment).permit(:body) + end + {} + end + + def commentable_set_up + if params[:commentable] == 'Post' + @params = params[:post_id] + @post = Post.find(@params) + @redirection_path = user_profile_path(@post.user) + elsif params[:commentable] == 'Photo' + @params = params[:photo_id] + @post = Photo.find(@params) + @redirection_path = photo_path(@post) + end + + end + +end diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/controllers/friendships_controller.rb b/app/controllers/friendships_controller.rb new file mode 100644 index 000000000..4e2468520 --- /dev/null +++ b/app/controllers/friendships_controller.rb @@ -0,0 +1,72 @@ +class FriendshipsController < ApplicationController + layout 'single_column' + + def create + @friendship = current_user.initiated_friendships.where(friendee_id: params[:user_id]).first_or_initialize + @recipient = User.find(params[:user_id]) + if @friendship.save + flash[:success] = "Your request to add #{@recipient.full_name} as a friend has been sent" + else + flash[:error] = "Sorry, but you can't send a friend invite to #{@recipient.full_name}" + end + redirect_to user_profile_path(@recipient) + end + + def update + @friender = User.find(params[:id]) + @friendship = current_user.received_friendships.find_by(friender_id: @friender.id) + case request.path + when reject_friend_path(@friender) + @friendship.update(rejected: true, friender_id: @friender.id) + flash[:notice] = "You have rejected #{@friender.full_name}'s friend request" + when accept_friend_path(@friender) + @friendship.update(rejected: false, friender_id: @friender.id) + flash[:success] = "You are now friends with #{@friender.full_name}" + when cancel_friend_path(@friender) + @friendship = current_user.initiated_friendships.find_by(friendee_id: @friender.id) + if @friendship.destroy + flash[:success] = "Your friendship request has been cancelled" + else + flash[:error] = "We couldn't cancel your friendship request" + end + end + redirect_to user_profile_path(@friender) + end + + def destroy + @friend = User.find(params[:id]) + @friendships = current_user.initiated_friendships.find_by(friendee_id: @friend.id) + if @friendships.destroy + flash[:success] = "You're no longer friends with #{@friend.full_name}" + else + flash[:error] = "You couldn't unfriend #{@friend.full_name}" + end + redirect_to user_profile_path(@friend) + end + + def index + @user = User.find(params[:user_id]) + @friends = @user.friendees + end + + def cancel + @friendee = User.find(params[:id]) + @friendship = current_user.initiated_friendships.find_by(friendee_id: @friendee) + if @friendship.destroy + flash[:success] = "Your friendship request has been cancelled" + else + flash[:error] = "We couldn't cancel your friendship request" + end + redirect_to user_profile_path(@friendee) + end + + private + + + def accepted_params + params.permit(:status).merge(friender_id: @friender.id) + end + + + +end diff --git a/app/controllers/likes_controller.rb b/app/controllers/likes_controller.rb new file mode 100644 index 000000000..33f24d641 --- /dev/null +++ b/app/controllers/likes_controller.rb @@ -0,0 +1,63 @@ +class LikesController < ApplicationController + before_action :authenticate_user! + before_action :likeable_set_up, only: [:create, :destroy] + + def create + @like = current_user.likes.build(likeable_id: @id, likeable_type: params[:likeable]) + if @like.save + respond_to do |format| + format.html do + flash[:success] = "Post liked" + redirect_to :back + end + format.js { set_render_template } + end + else + flash[:error] = "You've already liked that post" + end + end + + def destroy + @like = Like.find_by(user_id: current_user.id, likeable_id: @id, likeable_type: params[:likeable]) + if @like.destroy + respond_to do |format| + format.html do + flash[:success] = "Post unliked" + redirect_to :back + end + format.js { set_render_template} + end + else + flash[:error] = "Couldn't unlike" + end + # redirect_to :back + end + + private + + def set_render_template + case params[:likeable] + when 'Post' + render :update_post + when 'Photo' + render :update_photo + when 'Comment' + render :update_comment + end + end + + def likeable_set_up + if params[:likeable] == 'Post' + @id = params[:post_id] + @poster = Post.find(@id).user + @post = Post.find(@id) + elsif params[:likeable] == 'Photo' + @id = params[:photo_id] + @post = Photo.find(@id) + elsif params[:likeable] == 'Comment' + @id = params[:comment_id] + @comment = Comment.find(@id) + end + end + +end diff --git a/app/controllers/newsfeed_controller.rb b/app/controllers/newsfeed_controller.rb new file mode 100644 index 000000000..2392dd8a7 --- /dev/null +++ b/app/controllers/newsfeed_controller.rb @@ -0,0 +1,9 @@ +class NewsfeedController < ApplicationController + + def index + @user = current_user + @posts = Activity.newsfeed(@user).paginate(page: params[:page], per_page: 10) + @post = current_user.posts.build + @feed = Activity.recently_active(@user) + end +end diff --git a/app/controllers/photos_controller.rb b/app/controllers/photos_controller.rb new file mode 100644 index 000000000..db17f35e1 --- /dev/null +++ b/app/controllers/photos_controller.rb @@ -0,0 +1,65 @@ +class PhotosController < ApplicationController + before_action :set_user, only: [:index, :new, :create] + skip_before_action :authenticate_user!, only: [:new, :index, :show, :create] + layout 'single_column' + + def index + @photos = @user.photos + end + + def new + return redirect_to user_photos_path(@user) unless is_self? + @photo = Photo.new + end + + def create + return redirect_to user_photos_path(@user) unless is_self? + @photo = current_user.photos.build(photo_params) + if @photo.save + flash[:success] = "Your photo was successfully added" + redirect_to user_photos_path(@user) + else + flash[:error] = "Your photo could not be added" + redirect_to upload_user_photos_path(@user) + end + end + + def show + @photo = Photo.find(params[:id]) + @user = @photo.user + return redirect_to user_photos_path(@user) unless @user.friendees.include?(current_user) || is_self? + + respond_to do |format| + format.html + format.js + end + @comment = @photo.comments.build + end + + def destroy + @photo = Photo.find(params[:id]) + @user = @photo.user + return redirect_to photo_path(@photo) unless is_self? + if @photo.destroy + flash[:success] = 'Success! Your photo was deleted!' + redirect_to user_photos_path(@user) + else + flash[:error] = "You cannot delete that photo" + redirect_to photo_path(@photo) + end + end + + private + + def set_user + @user = User.find(params[:user_id]) + end + + def photo_params + if params[:photo] + params.require(:photo).permit(:image, :user_id) + end + end + + +end diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb new file mode 100644 index 000000000..c1d54b3f5 --- /dev/null +++ b/app/controllers/posts_controller.rb @@ -0,0 +1,63 @@ +class PostsController < ApplicationController + skip_before_action :authenticate_user!, only: [:index] + layout 'timeline' + + def index + @user = params[:user_id] ? User.find(params[:user_id]) : current_user + @posts = @user.posts.order('created_at DESC') + @post = @user.posts.build + end + + def create + @user = User.find(params[:user_id]) + @post = @user.posts.build(post_params) + fallback = request.origin == root_path ? root_path : user_profile_path(@user) + if is_self? + if @post.save + flash.now[:success] = "Yay! Posted." + respond_to do |format| + format.html { redirect_back(fallback_location: fallback) } + format.js + end + else + flash.now[:error] = "Sorry, but you can't post nothing." + respond_to do |format| + format.html { redirect_back(fallback_location: fallback)} + format.js { render :create_fail } + end + end + else + flash[:error] = "Sorry, you can only post on your own timeline for now" + redirect_to user_profile_path(@user) + end + end + + def destroy + @post = Post.find(params[:id]) + @user = @post.user + if is_self? + if @post.destroy + respond_to do |format| + format.html { redirect_to user_profile_path(@post.user) } + format.js + end + flash.now[:success] = "Your post has been deleted" + else + respond_to do |format| + format.html { render :index} + format.js { render :destroy_fail } + end + flash.now[:error] = "Sorry, we couldn't delete that post" + end + else + flash[:error] = "Sorry, you can't do that" + redirect_to user_profile_path(@user) + end + end + + private + + def post_params + params.require(:post).permit(:body) + end +end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb new file mode 100644 index 000000000..4747765fd --- /dev/null +++ b/app/controllers/profiles_controller.rb @@ -0,0 +1,26 @@ +class ProfilesController < ApplicationController + def edit + @user = User.find(params[:user_id]) + end + + def update + @photo = Photo.find(params[:id]) + @user = @photo.user + return redirect_to photo_path(@photo) unless is_self? + type = params[:photo_type].downcase + if current_user.profile.update(type.to_sym => @photo.image) + flash[:success] = "Your #{type} photo has been updated" + else + flash[:error] = "You can't use that as your #{type} photo" + end + redirect_to user_photos_path(@user) + + end + + private + + def profile_params + params.require(:user).permit(profile_attributes: [:id, :college, :hometown, :current_city, :telephone, :about, :quote]) + end + +end diff --git a/app/controllers/static_pages_controller.rb b/app/controllers/static_pages_controller.rb new file mode 100644 index 000000000..080a52729 --- /dev/null +++ b/app/controllers/static_pages_controller.rb @@ -0,0 +1,19 @@ +class StaticPagesController < ApplicationController + def home + end + + def timeline + end + + def friends + end + + def about + end + + def photos + end + + def about_edit + end +end diff --git a/app/controllers/users/confirmations_controller.rb b/app/controllers/users/confirmations_controller.rb new file mode 100644 index 000000000..1126e23aa --- /dev/null +++ b/app/controllers/users/confirmations_controller.rb @@ -0,0 +1,28 @@ +class Users::ConfirmationsController < Devise::ConfirmationsController + # GET /resource/confirmation/new + # def new + # super + # end + + # POST /resource/confirmation + # def create + # super + # end + + # GET /resource/confirmation?confirmation_token=abcdef + # def show + # super + # end + + # protected + + # The path used after resending confirmation instructions. + # def after_resending_confirmation_instructions_path_for(resource_name) + # super(resource_name) + # end + + # The path used after confirmation. + # def after_confirmation_path_for(resource_name, resource) + # super(resource_name, resource) + # end +end diff --git a/app/controllers/users/omniauth_callbacks_controller.rb b/app/controllers/users/omniauth_callbacks_controller.rb new file mode 100644 index 000000000..1907e5b1b --- /dev/null +++ b/app/controllers/users/omniauth_callbacks_controller.rb @@ -0,0 +1,28 @@ +class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController + # You should configure your model like this: + # devise :omniauthable, omniauth_providers: [:twitter] + + # You should also create an action method in this controller like this: + # def twitter + # end + + # More info at: + # https://github.com/plataformatec/devise#omniauth + + # GET|POST /resource/auth/twitter + # def passthru + # super + # end + + # GET|POST /users/auth/twitter/callback + # def failure + # super + # end + + # protected + + # The path used when OmniAuth fails + # def after_omniauth_failure_path_for(scope) + # super(scope) + # end +end diff --git a/app/controllers/users/passwords_controller.rb b/app/controllers/users/passwords_controller.rb new file mode 100644 index 000000000..53cc34e39 --- /dev/null +++ b/app/controllers/users/passwords_controller.rb @@ -0,0 +1,32 @@ +class Users::PasswordsController < Devise::PasswordsController + # GET /resource/password/new + # def new + # super + # end + + # POST /resource/password + # def create + # super + # end + + # GET /resource/password/edit?reset_password_token=abcdef + # def edit + # super + # end + + # PUT /resource/password + # def update + # super + # end + + # protected + + # def after_resetting_password_path_for(resource) + # super(resource) + # end + + # The path used after sending reset password instructions + # def after_sending_reset_password_instructions_path_for(resource_name) + # super(resource_name) + # end +end diff --git a/app/controllers/users/registrations_controller.rb b/app/controllers/users/registrations_controller.rb new file mode 100644 index 000000000..4d6fbadaf --- /dev/null +++ b/app/controllers/users/registrations_controller.rb @@ -0,0 +1,60 @@ +class Users::RegistrationsController < Devise::RegistrationsController + # before_action :configure_sign_up_params, only: [:create] + # before_action :configure_account_update_params, only: [:update] + + # GET /resource/sign_up + # def new + # super + # end + + # POST /resource + # def create + # super + # end + + # GET /resource/edit + # def edit + # super + # end + + # PUT /resource + # def update + # super + # end + + # DELETE /resource + # def destroy + # super + # end + + # GET /resource/cancel + # Forces the session data which is usually expired after sign + # in to be expired now. This is useful if the user wants to + # cancel oauth signing in/up in the middle of the process, + # removing all OAuth session data. + # def cancel + # super + # end + + # protected + + # If you have extra params to permit, append them to the sanitizer. + # def configure_sign_up_params + # devise_parameter_sanitizer.permit(:sign_up, keys: [:attribute]) + # end + + # If you have extra params to permit, append them to the sanitizer. + # def configure_account_update_params + # devise_parameter_sanitizer.permit(:account_update, keys: [:attribute]) + # end + + # The path used after sign up. + # def after_sign_up_path_for(resource) + # super(resource) + # end + + # The path used after sign up for inactive accounts. + # def after_inactive_sign_up_path_for(resource) + # super(resource) + # end +end diff --git a/app/controllers/users/sessions_controller.rb b/app/controllers/users/sessions_controller.rb new file mode 100644 index 000000000..4d71e53e0 --- /dev/null +++ b/app/controllers/users/sessions_controller.rb @@ -0,0 +1,26 @@ +class Users::SessionsController < Devise::SessionsController + # before_action :configure_sign_in_params, only: [:create] + skip_before_action :authenticate_user!, only: [:new, :create] + + # GET /resource/sign_in + # def new + # super + # end + + # POST /resource/sign_in + # def create + # super + # end + + # DELETE /resource/sign_out + # def destroy + # super + # end + + # protected + + # If you have extra params to permit, append them to the sanitizer. + # def configure_sign_in_params + # devise_parameter_sanitizer.permit(:sign_in, keys: [:attribute]) + # end +end diff --git a/app/controllers/users/unlocks_controller.rb b/app/controllers/users/unlocks_controller.rb new file mode 100644 index 000000000..8b9ef8612 --- /dev/null +++ b/app/controllers/users/unlocks_controller.rb @@ -0,0 +1,28 @@ +class Users::UnlocksController < Devise::UnlocksController + # GET /resource/unlock/new + # def new + # super + # end + + # POST /resource/unlock + # def create + # super + # end + + # GET /resource/unlock?unlock_token=abcdef + # def show + # super + # end + + # protected + + # The path used after sending unlock password instructions + # def after_sending_unlock_instructions_path_for(resource) + # super(resource) + # end + + # The path used after unlocking the resource + # def after_unlock_path_for(resource) + # super(resource) + # end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb new file mode 100644 index 000000000..9beec988a --- /dev/null +++ b/app/controllers/users_controller.rb @@ -0,0 +1,78 @@ +class UsersController < ApplicationController + before_action :set_user, only: [:edit, :update, :destroy] + skip_before_action :authenticate_user!, only: [:new, :create, :show] + layout :set_layout + + def index + if params[:q] + @term = params[:q].to_s + @users = User.search(@term).includes(:profile) + else + @users = [] + end + end + + def new + @user = User.new + @user.build_profile + render layout: 'application', template: 'users/new' + end + + def create + @user = User.new(user_params) + if @user.save + flash[:success] = "Success! Welcome to Danebook, #{@user.first_name}" + sign_in(@user) + redirect_to user_about_path(@user) + else + flash[:error] = "We couldn't sign you up. Please check the form for errors" + render layout: 'application', template: 'users/new' + end + end + + def edit + @user = User.find(params[:id]) + end + + def update + @user = User.find(params[:id]) + if @user.update_attributes(profile_params) + flash[:success] = "You profile has been updated" + redirect_to user_about_path(@user) + else + flash[:error] = "Update fail" + render :edit + end + end + + def show + @user = User.find(params[:user_id]) + end + + private + + def set_user + @user = User.find(params[:id]) + end + + def user_params + params.require(:user).permit(:email, :password, :password_confirmation, :first_name, :last_name, profile_attributes: [:id, :sex, :'birthdate(1i)', :'birthdate(2i)', :'birthdate(3i)']) + end + + def profile_params + params.require(:user).permit(profile_attributes: [:id, :college, :hometown, :current_city, :telephone, :about, :quote]) + end + + private + + def set_layout + case action_name + when 'index' + 'application' + else + 'single_column' + end + end + + +end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb new file mode 100644 index 000000000..d66d31737 --- /dev/null +++ b/app/helpers/application_helper.rb @@ -0,0 +1,45 @@ +module ApplicationHelper + + def form_error(resource, field) + unless resource.errors[field].empty? + html = '' + resource.errors[field].first.capitalize + '' + html.html_safe + end + end + + def form_error_class(resource, field) + 'has-error' unless resource.errors[field].empty? + end + + def flash_class(flash_type) + case flash_type + when 'success' then 'alert-success' + when 'error', 'alert' then 'alert-danger' + when 'notice' then 'alert-info' + else + flash_type.to_s + end + end + + private + + + def resource_name + :user + end + + def resource + @resource ||= User.new + end + + def devise_mapping + @devise_mapping ||= Devise.mappings[:user] + end + + def is_self? + return false unless @user + user_signed_in? && @user.id == current_user.id + end + + +end diff --git a/app/helpers/comments_helper.rb b/app/helpers/comments_helper.rb new file mode 100644 index 000000000..0ec9ca5f2 --- /dev/null +++ b/app/helpers/comments_helper.rb @@ -0,0 +1,2 @@ +module CommentsHelper +end diff --git a/app/helpers/friendships_helper.rb b/app/helpers/friendships_helper.rb new file mode 100644 index 000000000..64f89ba62 --- /dev/null +++ b/app/helpers/friendships_helper.rb @@ -0,0 +1,2 @@ +module FriendshipsHelper +end diff --git a/app/helpers/likes_helper.rb b/app/helpers/likes_helper.rb new file mode 100644 index 000000000..a78a75964 --- /dev/null +++ b/app/helpers/likes_helper.rb @@ -0,0 +1,2 @@ +module LikesHelper +end diff --git a/app/helpers/newsfeed_helper.rb b/app/helpers/newsfeed_helper.rb new file mode 100644 index 000000000..bf0034e96 --- /dev/null +++ b/app/helpers/newsfeed_helper.rb @@ -0,0 +1,2 @@ +module NewsfeedHelper +end diff --git a/app/helpers/photos_helper.rb b/app/helpers/photos_helper.rb new file mode 100644 index 000000000..0a10d472b --- /dev/null +++ b/app/helpers/photos_helper.rb @@ -0,0 +1,2 @@ +module PhotosHelper +end diff --git a/app/helpers/posts_helper.rb b/app/helpers/posts_helper.rb new file mode 100644 index 000000000..a7b8cec89 --- /dev/null +++ b/app/helpers/posts_helper.rb @@ -0,0 +1,2 @@ +module PostsHelper +end diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb new file mode 100644 index 000000000..4e430508f --- /dev/null +++ b/app/helpers/profiles_helper.rb @@ -0,0 +1,2 @@ +module ProfilesHelper +end diff --git a/app/helpers/static_pages_helper.rb b/app/helpers/static_pages_helper.rb new file mode 100644 index 000000000..2d63e79e6 --- /dev/null +++ b/app/helpers/static_pages_helper.rb @@ -0,0 +1,2 @@ +module StaticPagesHelper +end diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb new file mode 100644 index 000000000..44e7820a1 --- /dev/null +++ b/app/helpers/users_helper.rb @@ -0,0 +1,3 @@ +module UsersHelper + +end diff --git a/app/jobs/application_job.rb b/app/jobs/application_job.rb new file mode 100644 index 000000000..a009ace51 --- /dev/null +++ b/app/jobs/application_job.rb @@ -0,0 +1,2 @@ +class ApplicationJob < ActiveJob::Base +end diff --git a/app/mailers/application_mailer.rb b/app/mailers/application_mailer.rb new file mode 100644 index 000000000..286b2239d --- /dev/null +++ b/app/mailers/application_mailer.rb @@ -0,0 +1,4 @@ +class ApplicationMailer < ActionMailer::Base + default from: 'from@example.com' + layout 'mailer' +end diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb new file mode 100644 index 000000000..0aaacf267 --- /dev/null +++ b/app/mailers/user_mailer.rb @@ -0,0 +1,17 @@ +class UserMailer < ApplicationMailer + + default from: 'no-reply@danebook.com' + + def welcome(user) + @user = user + @friends = User.recommended_friends(@user) + mail(to: @user.email, subject: 'Welcome to Danebook!') + end + + def comment_notification(comment) + @comment = comment + @commenter = @comment.user + @recipient = @comment.commentable.user + mail(to: @recipient.email, subject: "New Comment from #{@commenter.full_name}") + end +end diff --git a/app/models/activity.rb b/app/models/activity.rb new file mode 100644 index 000000000..61f1722b0 --- /dev/null +++ b/app/models/activity.rb @@ -0,0 +1,33 @@ +class Activity < ApplicationRecord + belongs_to :user + belongs_to :activable, polymorphic: true + validates :user, :activable, presence: true + + def self.recently_active(user=nil) + return nil unless user + Activity.limit(10).order('created_at DESC').where('user_id IN (?)', user.friendee_ids).includes(user: [:profile]) + end + + def self.newsfeed(user=nil) + return nil unless user + Activity.where('(activable_type = ? OR activable_type = ? ) AND user_id IN (?)', 'Photo', 'Post', user.friendee_ids.clone << user.id ).order('created_at DESC').includes(activable: [:user]) + end + + def date + self.created_at.strftime('%A, %-d %B %Y') if self.created_at + end + + def to_text + case self.activable_type + when 'Post' + 'Created a post' + when 'Photo' + 'Uploaded a photo' + when 'Comment' + 'Wrote a comment' + when 'Like' + "Liked a #{self.activable.likeable_type.downcase}" + end + end + +end diff --git a/app/models/application_record.rb b/app/models/application_record.rb new file mode 100644 index 000000000..10a4cba84 --- /dev/null +++ b/app/models/application_record.rb @@ -0,0 +1,3 @@ +class ApplicationRecord < ActiveRecord::Base + self.abstract_class = true +end diff --git a/app/models/comment.rb b/app/models/comment.rb new file mode 100644 index 000000000..0200558d4 --- /dev/null +++ b/app/models/comment.rb @@ -0,0 +1,26 @@ +class Comment < ApplicationRecord + belongs_to :commentable, polymorphic: true + belongs_to :user + has_many :likes, as: :likeable, dependent: :destroy + has_one :activity, as: :activable, dependent: :destroy + validates :body, presence: true + validates :user, presence: true + after_create :create_activity_feed_record + + after_create :send_notification_email, unless: Proc.new{ self.user_id == self.commentable.user_id } + + include Reusable + + private + + def send_notification_email + # if we set ENV['USE_DELAYED_EMAILS'] to 'true', it will use delayed emails + if Rails.application.secrets.use_delayed_emails == 'true' + UserMailer.comment_notification(self).deliver_later + else + UserMailer.comment_notification(self).deliver! + end + end + + +end diff --git a/app/models/comment_like.rb b/app/models/comment_like.rb new file mode 100644 index 000000000..aa52d2ba8 --- /dev/null +++ b/app/models/comment_like.rb @@ -0,0 +1,6 @@ +class CommentLike < ApplicationRecord + belongs_to :user + belongs_to :comment, counter_cache: :likes_count + + validates_uniqueness_of :user_id, scope: :comment_id +end diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/app/models/concerns/activable.rb b/app/models/concerns/activable.rb new file mode 100644 index 000000000..1645727dd --- /dev/null +++ b/app/models/concerns/activable.rb @@ -0,0 +1,6 @@ +require 'active_support/concern' + +module Activable + extend ActiveSupport::Concern + +end diff --git a/app/models/concerns/reusable.rb b/app/models/concerns/reusable.rb new file mode 100644 index 000000000..d0392963f --- /dev/null +++ b/app/models/concerns/reusable.rb @@ -0,0 +1,52 @@ +require 'active_support/concern' + +module Reusable + extend ActiveSupport::Concern + + def create_activity_feed_record + self.create_activity!(user: self.user) + end + + def date + self.created_at.strftime('%A, %d %B %Y') if self.created_at + end + + def liked_by?(user) + return false unless user + ! self.likes.where('user_id = ?', user.id).blank? + end + + def post_likes(user) + self.reload # if we don't reload this, the likes_count won't be updated and the code below will blow up + return '' unless self.likes_count + msg = '' + if self.liked_by?(user) + msg += 'You' + remaining_likes = self.likes.where('user_id != ?', user.id).order('created_at DESC') + case + when self.likes_count == 1 + msg += ' like this' + when self.likes_count == 2 + msg += ' and ' + remaining_likes.first.user.full_name + 'like this' + when self.likes_count == 3 + msg += ', ' + remaining_likes.first.user.full_name + ' ' + 'and ' + remaining_likes.second.user.full_name + ' like this' + when self.likes_count > 3 + msg += ', ' + remaining_likes.first.user.full_name + ' and ' + (self.likes_count - 3).to_s + ' others like this' + end + else + likes = self.likes.includes(:user) + case + when self.likes_count == 1 + msg += likes.first.user.full_name + ' likes this' + when self.likes_count == 2 + msg += likes.first.user.full_name + ' and ' + likes.second.user.full_name + ' like this' + else + msg += self.likes_count.to_s + ' likes'.pluralize(self.likes_count) if self.likes_count > 0 + end + end + msg + end + + + +end diff --git a/app/models/friendship.rb b/app/models/friendship.rb new file mode 100644 index 000000000..e6b18a5dc --- /dev/null +++ b/app/models/friendship.rb @@ -0,0 +1,47 @@ +class Friendship < ApplicationRecord + belongs_to :friend_initiator, class_name: 'User', foreign_key: :friender_id + belongs_to :friend_recipient, class_name: 'User', foreign_key: :friendee_id + after_update :reciprocate_friendship, if: :friendship_accepted?, on: [:update] + after_destroy :reciprocate_breakup, :update_user_friendships_count + after_create :update_user_friendships_count + validates_uniqueness_of :friend_initiator, scope: :friend_recipient + + validate :no_friending_of_self + validates :rejected, inclusion: { in: [nil, true, false]} + + def no_friending_of_self + if self.friender_id == self.friendee_id + errors.add(:friender_id, "Can't friend self") + end + end + + + def update_user_friendships_count + # we use this instead of the built-in rails counter_cache because we don't want to count friendships where rejected is true + friender = self.friend_initiator + friender.friendships_count = Friendship.where('rejected = ? AND friender_id = ?', false, friender.id).count + friender.save + end + + private + + def friendship_accepted? + self.persisted? && self.rejected == false + end + + def reciprocate_breakup + f = Friendship.find_by(friender_id: self.friendee_id, friendee_id: self.friender_id) + if f.present? + f.destroy + end + end + + def reciprocate_friendship + unless self.rejected + f = Friendship.new(friender_id: self.friendee_id, friendee_id: self.friender_id, rejected: false) + f.save + end + end + + +end diff --git a/app/models/like.rb b/app/models/like.rb new file mode 100644 index 000000000..6b1ea79ff --- /dev/null +++ b/app/models/like.rb @@ -0,0 +1,11 @@ +class Like < ApplicationRecord + belongs_to :likeable, polymorphic: true, counter_cache: :likes_count + belongs_to :user + has_one :activity, as: :activable, dependent: :destroy + after_create :create_activity_feed_record + + validates :user, uniqueness: { scope: [:likeable_type, :likeable_id]} + + include Reusable + +end diff --git a/app/models/photo.rb b/app/models/photo.rb new file mode 100644 index 000000000..cf6ba3722 --- /dev/null +++ b/app/models/photo.rb @@ -0,0 +1,29 @@ +class Photo < ApplicationRecord + attr_reader :image_remote_url + belongs_to :user + has_many :comments, as: :commentable, dependent: :destroy + has_attached_file :image, + styles: { small: '200x150', medium: '476x259'}, + default_url: ':style_missing.png' + has_many :likes, as: :likeable, dependent: :destroy + has_many :likers, through: :likes, source: :user + has_one :activity, as: :activable, dependent: :destroy + validates :user, presence: true + after_create :create_activity_feed_record + + + validates_attachment :image, + presence: true, + content_type: { content_type: /\Aimage\/.*\Z/}, + size: { in: 0..3.megabytes} + + include Reusable + + def image_remote_url=(url_value) + self.image = URI.parse(url_value) + end + + def upload_date + self.image_updated_at.strftime('%-d %B %Y') + end +end diff --git a/app/models/post.rb b/app/models/post.rb new file mode 100644 index 000000000..e7db49232 --- /dev/null +++ b/app/models/post.rb @@ -0,0 +1,20 @@ +class Post < ApplicationRecord + belongs_to :user + has_many :likes, as: :likeable, dependent: :destroy + has_many :likers, through: :likes, source: :user + has_many :comments, as: :commentable, dependent: :destroy + has_many :comment_likes, through: :comments, source: :likes + has_one :activity, as: :activable, dependent: :destroy + validates :body, presence: true + validates :user, presence: true + after_create :create_activity_feed_record + + include Reusable + + def self.newsfeed_posts(user=nil) + return nil unless user + friend_ids = user.friendee_ids.clone + Post.order('created_at DESC').where('user_id IN (?)', friend_ids << user.id).includes(user: [:profile]) + end + +end diff --git a/app/models/profile.rb b/app/models/profile.rb new file mode 100644 index 000000000..c3df6e05b --- /dev/null +++ b/app/models/profile.rb @@ -0,0 +1,46 @@ +class Profile < ApplicationRecord + attr_reader :image_remote_url + + belongs_to :user, inverse_of: :profile + + has_many :initiated_friendships, through: :user + + has_attached_file :avatar, + styles: { medium: '300x300#', thumb: '26x36', small: '150x150#' }, + default_url: 'http://yxlau-danebook.s3.amazonaws.com/defaults/avatar_missing.png' + + has_attached_file :cover, + styles: { medium: '900x300'}, + default_url: 'http://yxlau-danebook.s3.amazonaws.com/defaults/cover_missing.png' + + validates_attachment :cover, + content_type: { content_type: /\Aimage\/.*\Z/}, + size: { in: 0..3.5.megabytes} + + validates_attachment :avatar, + content_type: { content_type: /\Aimage\/.*\Z/}, + size: { in: 0..2.megabytes} + validates :sex, :birthdate, presence: true, on: :create + validate :birthdate_not_in_future, on: :create + validates :sex, inclusion: {in: %w(female male) }, on: :create + + + def image_remote_url=(url_value) + self.image = URI.parse(url_value) + end + + def birthday + self.birthdate.strftime('%-d %B %Y') if self.created_at + end + + private + + def birthdate_not_in_future + if birthdate.present? && birthdate > Date.today + errors.add(:birthdate, "It seems you're from the future. Please contact us directly.") + end + end + + + +end diff --git a/app/models/user.rb b/app/models/user.rb new file mode 100644 index 000000000..c97424968 --- /dev/null +++ b/app/models/user.rb @@ -0,0 +1,81 @@ +class User < ApplicationRecord + # Include default devise modules. Others available are: + # :confirmable, :lockable, :timeoutable and :omniauthable + + after_create :send_welcome_email + + devise :database_authenticatable, :registerable, + :recoverable, :rememberable, :trackable, :validatable + has_one :profile, inverse_of: :user, dependent: :destroy + has_many :posts, dependent: :destroy + has_many :likes, dependent: :destroy + has_many :comments, dependent: :destroy + has_many :comment_likes, through: :comments, source: :likes, dependent: :destroy + has_many :photos, dependent: :destroy + has_many :activities, as: :activable + + # self join for friendship + has_many :initiated_friendships, class_name: 'Friendship', foreign_key: :friender_id, dependent: :destroy + has_many :received_friendships, class_name: 'Friendship', foreign_key: :friendee_id, dependent: :destroy + has_many :friendees, through: :initiated_friendships, source: :friend_recipient + has_many :frienders, through: :received_friendships, source: :friend_initiator + + + validates :email, presence: true, uniqueness: true, length: { minimum: 6}, on: [:create] + validates :password, :password_confirmation, presence: true, length: {minimum: 12 }, on: :create + validates :first_name, :last_name, presence: true + accepts_nested_attributes_for :profile + validates_associated :profile + + def self.search(term) + User.where("first_name ILIKE ? OR last_name ILIKE ?", "%#{term}%", "%#{term}%") + end + + def cover(size=:medium) + self.profile.cover.url(size) + end + + def avatar(size=:medium) + self.profile.avatar.url(size) + end + + def self.recommended_friends(user) + User.limit(5).order('random()').where('id != ?', user.id) + end + + + def friendship_status(user) + return nil unless user + friendship = user.initiated_friendships.find_by(friendee_id: self.id) + return friendship.rejected if friendship.present? + return 'received' if user.friend_requests.present? + return 'create' + end + + def friend_requests + self.frienders.where('friendee_id = ? AND rejected IS ?', self.id, nil) + end + + def full_name + first_name + ' ' + last_name + end + + def birthday + self.profile.birthday + end + + private + + def send_welcome_email + # if we set ENV['USE_DELAYED_EMAILS'] to true, it will use delayed emails + if Rails.application.secrets.use_delayed_emails == 'true' + UserMailer.welcome(self).deliver_later + else + UserMailer.welcome(self).deliver! + end + end + + + + +end diff --git a/app/views/.DS_Store b/app/views/.DS_Store new file mode 100644 index 000000000..45ef9371b Binary files /dev/null and b/app/views/.DS_Store differ diff --git a/app/views/comments/create.js.erb b/app/views/comments/create.js.erb new file mode 100644 index 000000000..028c1f7d8 --- /dev/null +++ b/app/views/comments/create.js.erb @@ -0,0 +1,19 @@ + +var $comment = $("<%= j(render partial: 'shared/comment', locals: { comment: @comment}) %>").hide(); +var $flash = $("<%= j(render partial: 'shared/flash') %>"); +var $article; +if ("<%= j(@comment.commentable_type) %>" == 'Post'){ + $article = $('article[data-post-id="<%= @comment.commentable_id %>"'); +$article.find('div[data-content="comments"]').append($comment); +} +else { + $article = $('.photo-comments'); + $article.append($comment); +} + + +$comment.delay(300).slideDown(1000); +$('main').prepend($flash); + +// clear comment form +$article.find('[data-for-clearance=comment]').val(null); diff --git a/app/views/comments/destroy.js.erb b/app/views/comments/destroy.js.erb new file mode 100644 index 000000000..24406de67 --- /dev/null +++ b/app/views/comments/destroy.js.erb @@ -0,0 +1,4 @@ +var $comment = $('article[data-comment-id="<%= @comment.id %>"]'); + +$comment.delay(300).fadeOut(800); + diff --git a/app/views/friendships/_friend.html.erb b/app/views/friendships/_friend.html.erb new file mode 100644 index 000000000..8fe3467cb --- /dev/null +++ b/app/views/friendships/_friend.html.erb @@ -0,0 +1,20 @@ +
+
+
+
+ <%= link_to user_profile_path(friend) do %> + <%= image_tag friend.avatar(:small), class: 'media-object img-thumbnail' %> + <% end %> +
+
+
+

<%= link_to friend.full_name, user_profile_path(friend) %>

+

<%= friend.friendships_count %> Friends

+
+
+
+ <%= render partial: 'shared/friend_action_button', locals: { friend: friend } %> +
+
+
+
\ No newline at end of file diff --git a/app/views/friendships/index.html.erb b/app/views/friendships/index.html.erb new file mode 100644 index 000000000..0b79d5d90 --- /dev/null +++ b/app/views/friendships/index.html.erb @@ -0,0 +1,6 @@ +<% content_for :module_title do + 'Friends' + end %> +<%= content_for :module_body do +render partial: 'friend', collection: @friends.includes(:profile), as: :friend + end %> \ No newline at end of file diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb new file mode 100644 index 000000000..2f346356f --- /dev/null +++ b/app/views/layouts/application.html.erb @@ -0,0 +1,18 @@ + + + + Danebook + + <%= csrf_meta_tags %> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + <%= render partial: 'shared/nav_bar' %> +
+ <%= render partial: 'shared/flash' %> + <%= yield %> +
+ + + \ No newline at end of file diff --git a/app/views/layouts/mailer.html.erb b/app/views/layouts/mailer.html.erb new file mode 100644 index 000000000..cbd34d2e9 --- /dev/null +++ b/app/views/layouts/mailer.html.erb @@ -0,0 +1,13 @@ + + + + + + + + + <%= yield %> + + diff --git a/app/views/layouts/mailer.text.erb b/app/views/layouts/mailer.text.erb new file mode 100644 index 000000000..37f0bddbd --- /dev/null +++ b/app/views/layouts/mailer.text.erb @@ -0,0 +1 @@ +<%= yield %> diff --git a/app/views/layouts/single_column.html.erb b/app/views/layouts/single_column.html.erb new file mode 100644 index 000000000..0c64ab840 --- /dev/null +++ b/app/views/layouts/single_column.html.erb @@ -0,0 +1,28 @@ + + + + Danebook + + <%= csrf_meta_tags %> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + <%= render partial: 'shared/nav_bar' %> +
+ <%= render partial: 'shared/flash' %> + <%= render partial: 'shared/profile_header' %> +
+
+
+
+

<%= content_for :module_title %>

+
+ <%= content_for :module_body %> +
+
+
+
+ + + \ No newline at end of file diff --git a/app/views/layouts/timeline.html.erb b/app/views/layouts/timeline.html.erb new file mode 100644 index 000000000..a315cfd66 --- /dev/null +++ b/app/views/layouts/timeline.html.erb @@ -0,0 +1,28 @@ + + + + Danebook + + <%= csrf_meta_tags %> + <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %> + <%= javascript_include_tag 'application', 'data-turbolinks-track': 'reload' %> + + + <%= render partial: 'shared/nav_bar' %> +
+ <%= render partial: 'shared/flash' %> + <%= render partial: 'shared/profile_header' %> +
+ +
+
+ <%= content_for :main %> +
+
+
+
+ + + \ No newline at end of file diff --git a/app/views/likes/update_comment.js.erb b/app/views/likes/update_comment.js.erb new file mode 100644 index 000000000..a0068db4b --- /dev/null +++ b/app/views/likes/update_comment.js.erb @@ -0,0 +1,5 @@ +var $newButton = $('<%= j(render partial: "shared/comment_likes_button", locals: {comment: @comment}) %>'); + +var $originalButton = $('a[data-content="comment-like-button"][data-comment-id=<%= @comment.id %>]'); + +$originalButton.replaceWith($newButton); diff --git a/app/views/likes/update_photo.js.erb b/app/views/likes/update_photo.js.erb new file mode 100644 index 000000000..cc4e14123 --- /dev/null +++ b/app/views/likes/update_photo.js.erb @@ -0,0 +1,5 @@ +var $newButton = $('<%= j(render partial: "shared/photo_like_button", locals: {post: @post} ) %>'); + +var $originalButton = $('[data-content="photo-reaction"]') + +$originalButton.html($newButton); diff --git a/app/views/likes/update_post.js.erb b/app/views/likes/update_post.js.erb new file mode 100644 index 000000000..d4bf368a0 --- /dev/null +++ b/app/views/likes/update_post.js.erb @@ -0,0 +1,4 @@ + var $newContent= $('<%= j(render partial: "shared/post_reaction", locals: {post: @post} ) %>'); +var $originalContent = $('article[data-post-id=<%= @post.id %>] [data-content="post-reaction"]'); + +$originalContent.html($newContent); diff --git a/app/views/newsfeed/_activity_feed.html.erb b/app/views/newsfeed/_activity_feed.html.erb new file mode 100644 index 000000000..46cdc4388 --- /dev/null +++ b/app/views/newsfeed/_activity_feed.html.erb @@ -0,0 +1,3 @@ +
+ <%= render partial: 'activity_feed_item', locals: {activity: activity } %> +
\ No newline at end of file diff --git a/app/views/newsfeed/_activity_feed_item.html.erb b/app/views/newsfeed/_activity_feed_item.html.erb new file mode 100644 index 000000000..671a83c46 --- /dev/null +++ b/app/views/newsfeed/_activity_feed_item.html.erb @@ -0,0 +1,9 @@ +
+
+ <%= link_to image_tag(activity.user.avatar(:thumb), class: 'img-thumbnail media-object', alt: 'Missing Avatar'), user_profile_path(activity.user) %> +
+
+
<%= link_to activity.user.full_name, user_profile_path(activity.user) %>
+

<%= "#{activity.to_text} on #{activity.date}" %>

+
+
\ No newline at end of file diff --git a/app/views/newsfeed/_newsfeed.html.erb b/app/views/newsfeed/_newsfeed.html.erb new file mode 100644 index 000000000..e9df17f51 --- /dev/null +++ b/app/views/newsfeed/_newsfeed.html.erb @@ -0,0 +1 @@ + <%= render partial: 'shared/post', locals: { post: post.activable} %> \ No newline at end of file diff --git a/app/views/newsfeed/index.html.erb b/app/views/newsfeed/index.html.erb new file mode 100644 index 000000000..97b40ec52 --- /dev/null +++ b/app/views/newsfeed/index.html.erb @@ -0,0 +1,31 @@ +
+ +
+
+ <%= render partial: 'shared/post_form', locals: { user: @user, post: @post} %> +
+ <%= render partial: 'newsfeed', collection: @posts, as: :post %> +
+ <%= will_paginate @posts, class: 'custom-pagination', previous_label: '«', next_label: '»' %> +
+
+
\ No newline at end of file diff --git a/app/views/photos/_comment_form.html.erb b/app/views/photos/_comment_form.html.erb new file mode 100644 index 000000000..ab66b965e --- /dev/null +++ b/app/views/photos/_comment_form.html.erb @@ -0,0 +1,4 @@ +<%= simple_form_for [@photo, @comment], html: {class: 'col-xs-12 comment-form mtm mbm' }, remote: true do |f| %> + <%= f.input :body, label: false, placeholder: 'Write a comment', required: true %> + <%= f.button :submit, 'Comment', class: 'btn btn-primary pull-right' %> +<% end %> \ No newline at end of file diff --git a/app/views/photos/_photo_grid_item.html.erb b/app/views/photos/_photo_grid_item.html.erb new file mode 100644 index 000000000..c2ca222cc --- /dev/null +++ b/app/views/photos/_photo_grid_item.html.erb @@ -0,0 +1,12 @@ +
+
+ <% if @user.friendees.include?(current_user) || is_self? %> + <%= link_to photo_path(photo) do %> +
+ <% end %> + <% else %> +
+ <% end %> +
Uploaded <%= photo.upload_date %>
+
+
\ No newline at end of file diff --git a/app/views/photos/_photo_lightbox.html.erb b/app/views/photos/_photo_lightbox.html.erb new file mode 100644 index 000000000..83e730d60 --- /dev/null +++ b/app/views/photos/_photo_lightbox.html.erb @@ -0,0 +1,46 @@ + \ No newline at end of file diff --git a/app/views/photos/_photo_show.html.erb b/app/views/photos/_photo_show.html.erb new file mode 100644 index 000000000..1ee9a36d6 --- /dev/null +++ b/app/views/photos/_photo_show.html.erb @@ -0,0 +1,35 @@ +
+ <%= image_tag @photo.image.url(:original), class: 'full-width pbs', id: 'photo' %> +
+ <% if is_self? %> + <%= link_to 'Set as Profile', avatar_photo_path(@photo), method: :patch %>
+ <%= link_to 'Set as Cover', cover_photo_path(@photo), method: :patch %>
+ <%= link_to 'Delete Photo', photo_path(@photo), method: :delete %> + <% end %> +
+
+
+
+
+
+ <%= link_to image_tag(@user.avatar(:thumb), class: 'img-thumbnail media-object', alt: 'Generic User Image'), user_profile_path(@user) %> +
+
+
<%= link_to @user.full_name, user_profile_path(@user) %>
+

<%= @photo.upload_date %>

+
+
+
+
+
+ <%= render partial: 'shared/photo_like_button', locals: {post: @photo} %> +
+
+
+
+ <%= render partial: 'shared/comment_form', locals: { post: @photo, comment: @comment} %> +
+
+ <%= render partial: 'shared/comment', collection: @photo.comments.includes(:user), as: :comment, locals: { user: @user} %> +
+ \ No newline at end of file diff --git a/app/views/photos/index.html.erb b/app/views/photos/index.html.erb new file mode 100644 index 000000000..b4a98a61b --- /dev/null +++ b/app/views/photos/index.html.erb @@ -0,0 +1,15 @@ +<%= content_for :module_title do %> + Photos + <%= link_to "Add Photos", upload_user_photos_path(@user), {:class => "btn btn-primary btn-sm module-edit"} if is_self? %> +<% end %> +<%= content_for :module_body do %> +
+
+ <% if @photos.blank? %> +

No photos yet.

+ <% else %> + <%= render partial: 'photo_grid_item', collection: @photos, as: :photo %> + <% end %> +
+
+<% end %> \ No newline at end of file diff --git a/app/views/photos/new.html.erb b/app/views/photos/new.html.erb new file mode 100644 index 000000000..a34278c79 --- /dev/null +++ b/app/views/photos/new.html.erb @@ -0,0 +1,17 @@ +<% content_for :module_title do %> + Photos +<% end %> +<%= content_for :module_body do %> +
+

Paste the URL to a photo on the web...

+ <%= form_for [@user, @photo] do |f| %> + <%= f.text_field :image, class: 'form-control', value: '' %> + <%= f.submit 'Use Web Photo', class: 'btn btn-primary mts' %> + <% end %> +

Select a photo from your hard drive...

+ <%= simple_form_for [@user, @photo] do |f| %> + <%= f.file_field :image, accept: 'image/png,image/gif,image/jpeg' %> + <%= f.submit 'Upload Photo', class: 'btn btn-success mbs' %> + <% end %> +
+<% end %> \ No newline at end of file diff --git a/app/views/photos/show.html.erb b/app/views/photos/show.html.erb new file mode 100644 index 000000000..7953bbdc0 --- /dev/null +++ b/app/views/photos/show.html.erb @@ -0,0 +1,6 @@ +<%= content_for :module_title do + 'Photos' +end %> +<%= content_for :module_body do %> + <%= render partial: 'photo_show' %> +<% end %> \ No newline at end of file diff --git a/app/views/photos/show.js.erb b/app/views/photos/show.js.erb new file mode 100644 index 000000000..ab6aeece4 --- /dev/null +++ b/app/views/photos/show.js.erb @@ -0,0 +1,5 @@ +console.log('photo show') + +var $contents = $('<%= j(render partial: "photo_lightbox") %>'); + +LIGHTBOX.Main.init($contents); \ No newline at end of file diff --git a/app/views/posts/_friend.html.erb b/app/views/posts/_friend.html.erb new file mode 100644 index 000000000..22442003a --- /dev/null +++ b/app/views/posts/_friend.html.erb @@ -0,0 +1,6 @@ +<% index = friend_counter + 1 %> +<%= '
'.html_safe if index % 3 == 0 %> +
<%= image_tag('user_silhouette_generic.png', class: 'img-thumbnail') %> +

<%= link_to friend.full_name, user_profile_path(friend) %>

+
+<%= '
'.html_safe if index % 3 == 0 %> \ No newline at end of file diff --git a/app/views/posts/_info_box_about.html.erb b/app/views/posts/_info_box_about.html.erb new file mode 100644 index 000000000..53b448039 --- /dev/null +++ b/app/views/posts/_info_box_about.html.erb @@ -0,0 +1,11 @@ +
+
+

About

+
+
+

Born on: <%= profile.birthday %>

+

Went to school at: <%= profile.college %>

+

Hometown: <%= profile.hometown %>

+

Currently Lives: <%= profile.current_city %>

+
+
\ No newline at end of file diff --git a/app/views/posts/_info_box_friends.html.erb b/app/views/posts/_info_box_friends.html.erb new file mode 100644 index 000000000..bca3c03b1 --- /dev/null +++ b/app/views/posts/_info_box_friends.html.erb @@ -0,0 +1,12 @@ +
+
+

Friends (<%= user.friendships_count %>)

+
+
+ <% if friends.empty? %> +

This user has no friends yet

+ <% else %> + <%= render partial: 'friend', collection: friends, as: :friend %> + <%= '

See More Friends

'.html_safe if user.friendships_count > 9 %> + <% end %> +
\ No newline at end of file diff --git a/app/views/posts/_info_box_photos.html.erb b/app/views/posts/_info_box_photos.html.erb new file mode 100644 index 000000000..bd91ee035 --- /dev/null +++ b/app/views/posts/_info_box_photos.html.erb @@ -0,0 +1,14 @@ +
+
+

Photos (<%= photos.count %>)

+
+
+ <%= render partial: 'photo_grid_small', collection: photos, as: :photo %> + <% if photos.count > 9 %> +

<%= link_to "See More Photos", user_photos_path(user) %>

+ <% elsif photos.count == 0 %> +

This user has no photos yet

+ <% end %> +
+
+
\ No newline at end of file diff --git a/app/views/posts/_like_form.html.erb b/app/views/posts/_like_form.html.erb new file mode 100644 index 000000000..b41d2164d --- /dev/null +++ b/app/views/posts/_like_form.html.erb @@ -0,0 +1,3 @@ +<%= simple_form_for post.likes.build, url: post_likes_path(post), method: :create, html: {class: 'display-inline'} do |f| %> + <%= f.button :submit, 'Like', class: 'btn btn-link btn-pan' %> +<% end %> \ No newline at end of file diff --git a/app/views/posts/_photo_grid_small.html.erb b/app/views/posts/_photo_grid_small.html.erb new file mode 100644 index 000000000..5f144aae9 --- /dev/null +++ b/app/views/posts/_photo_grid_small.html.erb @@ -0,0 +1,10 @@ + <% index = photo_counter + 1 %> +<% if index % 3 == 0 %> +
+ <% end %> +
+ <%= link_to image_tag(photo.image.url(:small), class: 'img-thumbnail'), photo_path(photo) %> +
+ <% if index % 3 == 0 %> +
+<% end %> \ No newline at end of file diff --git a/app/views/posts/create.js.erb b/app/views/posts/create.js.erb new file mode 100644 index 000000000..f640dcbe8 --- /dev/null +++ b/app/views/posts/create.js.erb @@ -0,0 +1,11 @@ +console.log('create.js.erb'); + +var $post = $("<%= j(render partial: 'shared/post', locals: { post: @post}) %>").hide(); +var $flash = $("<%= j(render partial: 'shared/flash') %>"); +$('div[data-content="posts"]').prepend($post); + +$post.delay(300).slideDown(1000); +$('main').prepend($flash); + +// clear form +$('#post-form').find('[data-for-clearance=true]').val(null); diff --git a/app/views/posts/create_fail.js.erb b/app/views/posts/create_fail.js.erb new file mode 100644 index 000000000..e7306c4e1 --- /dev/null +++ b/app/views/posts/create_fail.js.erb @@ -0,0 +1,2 @@ +var $flash = $("<%= j(render partial: 'shared/flash') %>"); +$('main').prepend($flash); diff --git a/app/views/posts/destroy.js.erb b/app/views/posts/destroy.js.erb new file mode 100644 index 000000000..62467647b --- /dev/null +++ b/app/views/posts/destroy.js.erb @@ -0,0 +1,5 @@ +var $post = $('article[data-post-id=<%= @post.id %>]'); +var $flash = $("<%= j(render partial: 'shared/flash') %>"); +$('main').prepend($flash); + +$post.delay(300).fadeOut(); \ No newline at end of file diff --git a/app/views/posts/index.html.erb b/app/views/posts/index.html.erb new file mode 100644 index 000000000..4120a2aed --- /dev/null +++ b/app/views/posts/index.html.erb @@ -0,0 +1,11 @@ +<% content_for :sidebar do %> + <%= render 'info_box_about', profile: @user.profile %> + <%= render partial: 'info_box_photos', locals: {photos: @user.photos.limit(9), user: @user } %> + <%= render 'info_box_friends', friends: @user.friendees, user: @user %> +<% end %> +<% content_for :main do %> + <%= render partial: 'shared/post_form', locals: { user: @user, post: @post} %> +
+ <%= render partial: 'shared/post', collection: @posts, as: :post %> +
+<% end %> \ No newline at end of file diff --git a/app/views/shared/_comment.html.erb b/app/views/shared/_comment.html.erb new file mode 100644 index 000000000..22a70d677 --- /dev/null +++ b/app/views/shared/_comment.html.erb @@ -0,0 +1,20 @@ +<% if comment.persisted? %> +
+
+
+ <%= link_to image_tag('user_silhouette_generic.png', class: 'img-thumbnail media-object', alt: 'Generic User Image') %> +
+
+ <%= link_to comment.user.full_name, user_profile_path(comment.user) %> + Posted on <%= comment.date %> +

<%= comment.body %>

+ +
+
+
+<% end %> \ No newline at end of file diff --git a/app/views/shared/_comment_form.html.erb b/app/views/shared/_comment_form.html.erb new file mode 100644 index 000000000..9c72cd2b6 --- /dev/null +++ b/app/views/shared/_comment_form.html.erb @@ -0,0 +1,4 @@ +<%= simple_form_for [post, comment], html: {class: 'col-xs-12 comment-form' }, remote: true do |f| %> + <%= f.input :body, label: false, placeholder: 'Write a comment', required: true, input_html: {data: {'for-clearance': 'comment'}} %> + <%= f.button :submit, 'Comment', class: 'btn btn-primary pull-right' %> +<% end %> \ No newline at end of file diff --git a/app/views/shared/_comment_likes_button.html.erb b/app/views/shared/_comment_likes_button.html.erb new file mode 100644 index 000000000..71a806877 --- /dev/null +++ b/app/views/shared/_comment_likes_button.html.erb @@ -0,0 +1,6 @@ + <%= if comment.liked_by?(current_user) + link_to "Unlike", comment_like_path(comment, current_user.id), method: :delete, remote: true, data: { 'comment-id': comment.id, content: 'comment-like-button'} + else + link_to 'Like', comment_likes_path(comment), method: :post, remote: true, class: 'btn btn-link pan', data: { 'comment-id': comment.id, content: 'comment-like-button'} + end +%> \ No newline at end of file diff --git a/app/views/shared/_comment_likes_form.html.erb b/app/views/shared/_comment_likes_form.html.erb new file mode 100644 index 000000000..0bed7f785 --- /dev/null +++ b/app/views/shared/_comment_likes_form.html.erb @@ -0,0 +1,3 @@ + <%= simple_form_for comment.comment_likes.build, url: comment_comment_likes_path(comment), method: :create, html: {class: 'display-inline'} do |f| %> + <%= f.button :submit, 'Like', class: 'btn btn-link pan' %> +<% end %> \ No newline at end of file diff --git a/app/views/shared/_flash.html.erb b/app/views/shared/_flash.html.erb new file mode 100644 index 000000000..2a84669d5 --- /dev/null +++ b/app/views/shared/_flash.html.erb @@ -0,0 +1,8 @@ +<% unless flash.empty? %> + <% flash.each do |key, value| %> + + <% end %> +<% end %> diff --git a/app/views/shared/_friend_action_button.html.erb b/app/views/shared/_friend_action_button.html.erb new file mode 100644 index 000000000..e07661f8d --- /dev/null +++ b/app/views/shared/_friend_action_button.html.erb @@ -0,0 +1,19 @@ +<%= unless is_self? + if current_user.nil? + link_to 'Add Friend', new_user_path, class: 'btn btn-primary' + else + case friend.friendship_status(current_user) + when nil + link_to 'Cancel Request', cancel_friend_path(friend), method: :patch, class: 'btn btn-dull' + when false + link_to "Remove Friend", friend_path(friend, current_user), method: :delete, class: 'btn btn-dull' + when true + '
Add Friend
'.html_safe + when 'received' + link_to 'Accept Request', accept_friend_path(friend), method: :patch, class: 'btn btn-success' + when 'create' + link_to 'Add Friend', user_friends_path(friend), method: :post, class: 'btn btn-primary' + end + end + end +%> \ No newline at end of file diff --git a/app/views/shared/_friend_requests.html.erb b/app/views/shared/_friend_requests.html.erb new file mode 100644 index 000000000..0fd17b18c --- /dev/null +++ b/app/views/shared/_friend_requests.html.erb @@ -0,0 +1,2 @@ +
  • <%= link_to friender.full_name, user_profile_path(friender) %> + <%= link_to 'Accept', accept_friend_path(friender), method: :patch, class: 'btn btn-primary' %> <%= link_to 'Reject', reject_friend_path(friender), method: :patch, class: 'btn btn-primary' %>
  • \ No newline at end of file diff --git a/app/views/shared/_nav_bar.html.erb b/app/views/shared/_nav_bar.html.erb new file mode 100644 index 000000000..6879b8260 --- /dev/null +++ b/app/views/shared/_nav_bar.html.erb @@ -0,0 +1,9 @@ +<% if user_signed_in? %> + <%= render partial: 'shared/nav_logged_in' %> +<% else %> + <% if request.path == new_user_session_path %> + <%= render partial: 'shared/nav_sign_up' %> + <% else %> + <%= render partial: 'shared/nav_login' %> + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/shared/_nav_home.html.erb b/app/views/shared/_nav_home.html.erb new file mode 100644 index 000000000..33fa0101c --- /dev/null +++ b/app/views/shared/_nav_home.html.erb @@ -0,0 +1,26 @@ + \ No newline at end of file diff --git a/app/views/shared/_nav_logged_in.html.erb b/app/views/shared/_nav_logged_in.html.erb new file mode 100644 index 000000000..0faf35af6 --- /dev/null +++ b/app/views/shared/_nav_logged_in.html.erb @@ -0,0 +1,34 @@ + \ No newline at end of file diff --git a/app/views/shared/_nav_login.html.erb b/app/views/shared/_nav_login.html.erb new file mode 100644 index 000000000..c9e48aa4d --- /dev/null +++ b/app/views/shared/_nav_login.html.erb @@ -0,0 +1,16 @@ + \ No newline at end of file diff --git a/app/views/shared/_nav_login_form.html.erb b/app/views/shared/_nav_login_form.html.erb new file mode 100644 index 000000000..e9fdeccb4 --- /dev/null +++ b/app/views/shared/_nav_login_form.html.erb @@ -0,0 +1,5 @@ +<%= simple_form_for(resource, as: resource_name, url: new_user_session_path, html: { class: 'navbar-form navbar-right'}) do |f| %> + <%= f.input :email, required: false, autofocus: true %> + <%= f.input :password, required: false %> + <%= f.button :submit, "Sign in", class: 'btn btn-action' %> +<% end %> \ No newline at end of file diff --git a/app/views/shared/_nav_sign_up.html.erb b/app/views/shared/_nav_sign_up.html.erb new file mode 100644 index 000000000..87537564c --- /dev/null +++ b/app/views/shared/_nav_sign_up.html.erb @@ -0,0 +1,11 @@ + \ No newline at end of file diff --git a/app/views/shared/_photo_like_button.html.erb b/app/views/shared/_photo_like_button.html.erb new file mode 100644 index 000000000..7d43ea869 --- /dev/null +++ b/app/views/shared/_photo_like_button.html.erb @@ -0,0 +1,7 @@ +<%= if post.liked_by?(current_user) + link_to 'Unlike', photo_like_path(post, current_user), method: :delete, data: {'post-id': post.id, 'content': 'post-like-button'}, remote: true +else + link_to 'Like', photo_likes_path(post), method: :post, remote: true, data: {'post-id': post.id, 'content': 'post-like-button'} +end +%> +<%= post.post_likes(current_user) %> \ No newline at end of file diff --git a/app/views/shared/_post.html.erb b/app/views/shared/_post.html.erb new file mode 100644 index 000000000..87450cbb0 --- /dev/null +++ b/app/views/shared/_post.html.erb @@ -0,0 +1,21 @@ +
    + <%= render partial: 'shared/post_profile', locals: {post: post} %> +
    +

    + <%= (link_to (image_tag post.image.url(:medium)), photo_path(post)) if post.class.name == 'Photo' %> + <%= post.body if post.class.name == 'Post' %> +

    +
    + +
    + + <%= render partial: 'shared/comment_form', locals: { post: post, comment: post.comments.build} %> +
    +
    \ No newline at end of file diff --git a/app/views/shared/_post_form.html.erb b/app/views/shared/_post_form.html.erb new file mode 100644 index 000000000..e3b4e9a2e --- /dev/null +++ b/app/views/shared/_post_form.html.erb @@ -0,0 +1,13 @@ +
    +
    +

    Post

    +
    + <%= simple_form_for [user, post], html: {class: 'form-group post-form col-xs-12', id: 'post-form'}, remote: true do |f| %> + <%= f.input :body, label: false, input_html: {data: { 'for-clearance': 'post'}} %> + + <% end %> +
    \ No newline at end of file diff --git a/app/views/shared/_post_profile.html.erb b/app/views/shared/_post_profile.html.erb new file mode 100644 index 000000000..2f5663617 --- /dev/null +++ b/app/views/shared/_post_profile.html.erb @@ -0,0 +1,9 @@ +
    +
    + <%= link_to image_tag(post.user.avatar(:thumb), class: 'img-thumbnail media-object', alt: 'Missing Avatar'), user_profile_path(post.user) %> +
    +
    +
    <%= link_to post.user.full_name, user_profile_path(post.user) %>
    +

    <%= post.date %>

    +
    +
    \ No newline at end of file diff --git a/app/views/shared/_post_reaction.html.erb b/app/views/shared/_post_reaction.html.erb new file mode 100644 index 000000000..ebd453653 --- /dev/null +++ b/app/views/shared/_post_reaction.html.erb @@ -0,0 +1,12 @@ +<% unlike_path = post.class.name == 'Photo' ? photo_like_path(post, current_user) : post_like_path(post, current_user) %> +<% like_path = post.class.name == 'Photo' ? photo_likes_path(post, current_user) : post_likes_path(post) %> + + <%= if post.liked_by?(current_user) + link_to 'Unlike', unlike_path, method: :delete, data: {'post-id': post.id, 'content': 'post-like-button'}, remote: true +else + link_to 'Like', like_path, method: :post, remote: true, data: {'post-id': post.id, 'content': 'post-like-button'} +end +%> + Comment + +<%= post.post_likes(current_user) %> \ No newline at end of file diff --git a/app/views/shared/_profile_header.html.erb b/app/views/shared/_profile_header.html.erb new file mode 100644 index 000000000..7ac51c2a9 --- /dev/null +++ b/app/views/shared/_profile_header.html.erb @@ -0,0 +1,20 @@ +
    +
    +
    +
    +
    +

    <%= @user.full_name %>

    + <%= render partial: 'shared/friend_action_button', locals: { friend: @user } %> +
    + <%= image_tag("#{@user.avatar(:medium)}", alt: 'Generic User Silhouette', class: 'img-thumbnail profile-pic') %> + +
    +
    \ No newline at end of file diff --git a/app/views/templates/single_column.html.erb b/app/views/templates/single_column.html.erb new file mode 100644 index 000000000..11fd30411 --- /dev/null +++ b/app/views/templates/single_column.html.erb @@ -0,0 +1,10 @@ +
    +
    +
    +
    +

    <%= content_for :module_title %>

    +
    + <%= content_for :module_body %> +
    +
    +
    \ No newline at end of file diff --git a/app/views/user_mailer/comment_notification.html.erb b/app/views/user_mailer/comment_notification.html.erb new file mode 100644 index 000000000..ab09f5414 --- /dev/null +++ b/app/views/user_mailer/comment_notification.html.erb @@ -0,0 +1,3 @@ +

    Hi, <%= @recipient.first_name %>,

    +

    <%= link_to @commenter.full_name, user_profile_url(@commenter) %> left the following comment on your post:

    +
    <%= @comment.body %>
    \ No newline at end of file diff --git a/app/views/user_mailer/comment_notification.text.erb b/app/views/user_mailer/comment_notification.text.erb new file mode 100644 index 000000000..ab09f5414 --- /dev/null +++ b/app/views/user_mailer/comment_notification.text.erb @@ -0,0 +1,3 @@ +

    Hi, <%= @recipient.first_name %>,

    +

    <%= link_to @commenter.full_name, user_profile_url(@commenter) %> left the following comment on your post:

    +
    <%= @comment.body %>
    \ No newline at end of file diff --git a/app/views/user_mailer/welcome.html.erb b/app/views/user_mailer/welcome.html.erb new file mode 100644 index 000000000..a7ac6a8c9 --- /dev/null +++ b/app/views/user_mailer/welcome.html.erb @@ -0,0 +1,16 @@ +

    Welcome to Danebook!

    +

    Hi <%= @user.first_name %>, welcome to Danebook!

    +<% if @friends.present? %> +

    Here are some people we thought you might like to be friends with:

    + +<% end %> +

    View your account at <%= link_to user_profile_url(@user.id), user_profile_url(@user.id) %>

    \ No newline at end of file diff --git a/app/views/user_mailer/welcome.text.erb b/app/views/user_mailer/welcome.text.erb new file mode 100644 index 000000000..e3bfd3e96 --- /dev/null +++ b/app/views/user_mailer/welcome.text.erb @@ -0,0 +1,17 @@ +

    Welcome to Danebook!

    +

    Hi <%= @user.first_name %>, welcome to Danebook!

    + +<% if @friends.present? %> +

    Here are some people we thought you might like to be friends with:

    + +<% end %> +

    View your account at <%= link_to user_profile_url(@user.id), user_profile_url(@user.id) %>

    \ No newline at end of file diff --git a/app/views/users/_profile.html.erb b/app/views/users/_profile.html.erb new file mode 100644 index 000000000..9d883c953 --- /dev/null +++ b/app/views/users/_profile.html.erb @@ -0,0 +1,30 @@ +
    +
    +
    +

    Basic Information

    +
    +
    Birthday:
    +
    <%= user.birthday %>
    +
    College:
    +
    <%= user.profile.college %>
    +
    Hometown:
    +
    <%= user.profile.hometown %>
    +
    Currently Lives:
    +
    <%= user.profile.current_city %>
    +
    +

    Contact Information

    +
    +
    Email:
    +
    <%= user.email %>
    +
    Telephone:
    +
    <%= user.profile.telephone %>
    +
    +
    +
    +

    Word to Live By

    +

    <%= user.profile.quote %>

    +

    About Me

    +

    <%= user.profile.about %>

    +
    +
    +
    \ No newline at end of file diff --git a/app/views/users/_profile_form.html.erb b/app/views/users/_profile_form.html.erb new file mode 100644 index 000000000..e2da83470 --- /dev/null +++ b/app/views/users/_profile_form.html.erb @@ -0,0 +1,33 @@ + <%= form_for user do |f| %> + <%= f.fields_for :profile do |profile| %> +
    +

    Basic Information

    +
    +
    Birthday:
    +
    <%= user.birthday %>
    +
    <%= profile.label :college, "College:" %>
    +
    <%= profile.text_field :college, class: 'form-control' %>
    +
    <%= profile.label :hometown, "Hometown:" %>
    +
    <%= profile.text_field :hometown, class: 'form-control' %>
    +
    <%= profile.label :current_city, "Currently lives:" %>
    +
    <%= profile.text_field :current_city, class: 'form-control' %>
    +
    +

    Contact Information

    +
    +
    Email:
    +
    harry_potter@hogwarts.edu
    +
    <%= profile.label :telephone, "Telephone:" %>
    +
    <%= profile.text_field :telephone, class: 'form-control' %>
    +
    +
    +
    +

    <%= profile.label :quote, "Words to Live By" %>

    + <%= profile.text_area :quote, rows: 3, class: 'form-control' %> +

    <%= profile.label :about %>

    + <%= profile.text_area :about, rows: 7, class: 'form-control' %> +
    +
    + +
    + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/users/_search_results.html.erb b/app/views/users/_search_results.html.erb new file mode 100644 index 000000000..bd3ab94d5 --- /dev/null +++ b/app/views/users/_search_results.html.erb @@ -0,0 +1,23 @@ +
    +
    +
    +
    + <%= link_to user_profile_path(friend) do %> + <%= image_tag friend.avatar(:small), class: 'media-object img-thumbnail' %> + <% end %> +
    +
    +
    +

    <%= link_to friend.full_name, user_profile_path(friend) %>

    +

    <%= friend.friendships_count %> Friends

    +

    Currently lives in <%= friend.profile.current_city %>

    +

    From <%= friend.profile.hometown %>

    +

    Born on <%= friend.profile.birthday %>

    +
    +
    +
    + <%= render partial: 'shared/friend_action_button', locals: { friend: friend} %> +
    +
    +
    +
    \ No newline at end of file diff --git a/app/views/users/confirmations/new.html.erb b/app/views/users/confirmations/new.html.erb new file mode 100644 index 000000000..52e625282 --- /dev/null +++ b/app/views/users/confirmations/new.html.erb @@ -0,0 +1,16 @@ +

    Resend confirmation instructions

    + +<%= simple_form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| %> + <%= f.error_notification %> + <%= f.full_error :confirmation_token %> + +
    + <%= f.input :email, required: true, autofocus: true %> +
    + +
    + <%= f.button :submit, "Resend confirmation instructions" %> +
    +<% end %> + +<%= render "users/shared/links" %> diff --git a/app/views/users/edit.html.erb b/app/views/users/edit.html.erb new file mode 100644 index 000000000..21398d6b8 --- /dev/null +++ b/app/views/users/edit.html.erb @@ -0,0 +1,7 @@ +<%= content_for :module_title do %> + About + <%= link_to "Edit Your Profile", edit_user_path(@user), {class: 'btn btn-primary btn-sm module-edit'} %> +<% end %> +<%= content_for :module_body do %> + <%= render partial: 'profile_form', locals: {user: @user} %> +<% end %> \ No newline at end of file diff --git a/app/views/users/index.html.erb b/app/views/users/index.html.erb new file mode 100644 index 000000000..05826ae1d --- /dev/null +++ b/app/views/users/index.html.erb @@ -0,0 +1,15 @@ +
    +
    +

    Showing <%= @users.count ? pluralize(@users.count, 'users') : '0 users' %> whose first or last names contain '<%= @term %>'

    +
    +
    +

    Search Results

    +
    +
    +
    + <%= render partial: 'search_results', collection: @users, as: :friend %> +
    +
    +
    +
    +
    \ No newline at end of file diff --git a/app/views/users/mailer/confirmation_instructions.html.erb b/app/views/users/mailer/confirmation_instructions.html.erb new file mode 100644 index 000000000..dc55f64f6 --- /dev/null +++ b/app/views/users/mailer/confirmation_instructions.html.erb @@ -0,0 +1,5 @@ +

    Welcome <%= @email %>!

    + +

    You can confirm your account email through the link below:

    + +

    <%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

    diff --git a/app/views/users/mailer/email_changed.html.erb b/app/views/users/mailer/email_changed.html.erb new file mode 100644 index 000000000..32f4ba803 --- /dev/null +++ b/app/views/users/mailer/email_changed.html.erb @@ -0,0 +1,7 @@ +

    Hello <%= @email %>!

    + +<% if @resource.try(:unconfirmed_email?) %> +

    We're contacting you to notify you that your email is being changed to <%= @resource.unconfirmed_email %>.

    +<% else %> +

    We're contacting you to notify you that your email has been changed to <%= @resource.email %>.

    +<% end %> diff --git a/app/views/users/mailer/password_change.html.erb b/app/views/users/mailer/password_change.html.erb new file mode 100644 index 000000000..b41daf476 --- /dev/null +++ b/app/views/users/mailer/password_change.html.erb @@ -0,0 +1,3 @@ +

    Hello <%= @resource.email %>!

    + +

    We're contacting you to notify you that your password has been changed.

    diff --git a/app/views/users/mailer/reset_password_instructions.html.erb b/app/views/users/mailer/reset_password_instructions.html.erb new file mode 100644 index 000000000..f667dc12f --- /dev/null +++ b/app/views/users/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +

    Hello <%= @resource.email %>!

    + +

    Someone has requested a link to change your password. You can do this through the link below.

    + +

    <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

    + +

    If you didn't request this, please ignore this email.

    +

    Your password won't change until you access the link above and create a new one.

    diff --git a/app/views/users/mailer/unlock_instructions.html.erb b/app/views/users/mailer/unlock_instructions.html.erb new file mode 100644 index 000000000..41e148bf2 --- /dev/null +++ b/app/views/users/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +

    Hello <%= @resource.email %>!

    + +

    Your account has been locked due to an excessive number of unsuccessful sign in attempts.

    + +

    Click the link below to unlock your account:

    + +

    <%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

    diff --git a/app/views/users/new.html.erb b/app/views/users/new.html.erb new file mode 100644 index 000000000..548936660 --- /dev/null +++ b/app/views/users/new.html.erb @@ -0,0 +1,17 @@ +
    +
    +
    +

    Connect with all your friends!

    +
    + +
    +
    +

    Sign Up

    + <%= render template: 'users/registrations/new', locals: {resource: @user} %> +
    +
    \ No newline at end of file diff --git a/app/views/users/passwords/edit.html.erb b/app/views/users/passwords/edit.html.erb new file mode 100644 index 000000000..3a697926f --- /dev/null +++ b/app/views/users/passwords/edit.html.erb @@ -0,0 +1,19 @@ +

    Change your password

    + +<%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| %> + <%= f.error_notification %> + + <%= f.input :reset_password_token, as: :hidden %> + <%= f.full_error :reset_password_token %> + +
    + <%= f.input :password, label: "New password", required: true, autofocus: true, hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length) %> + <%= f.input :password_confirmation, label: "Confirm your new password", required: true %> +
    + +
    + <%= f.button :submit, "Change my password" %> +
    +<% end %> + +<%= render "users/shared/links" %> diff --git a/app/views/users/passwords/new.html.erb b/app/views/users/passwords/new.html.erb new file mode 100644 index 000000000..bd50b25c7 --- /dev/null +++ b/app/views/users/passwords/new.html.erb @@ -0,0 +1,15 @@ +

    Forgot your password?

    + +<%= simple_form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| %> + <%= f.error_notification %> + +
    + <%= f.input :email, required: true, autofocus: true %> +
    + +
    + <%= f.button :submit, "Send me reset password instructions" %> +
    +<% end %> + +<%= render "users/shared/links" %> diff --git a/app/views/users/registrations/edit.html.erb b/app/views/users/registrations/edit.html.erb new file mode 100644 index 000000000..5db350b5c --- /dev/null +++ b/app/views/users/registrations/edit.html.erb @@ -0,0 +1,27 @@ +

    Edit <%= resource_name.to_s.humanize %>

    + +<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name), html: { method: :put }) do |f| %> + <%= f.error_notification %> + +
    + <%= f.input :email, required: true, autofocus: true %> + + <% if devise_mapping.confirmable? && resource.pending_reconfirmation? %> +

    Currently waiting confirmation for: <%= resource.unconfirmed_email %>

    + <% end %> + + <%= f.input :password, autocomplete: "off", hint: "leave it blank if you don't want to change it", required: false %> + <%= f.input :password_confirmation, required: false %> + <%= f.input :current_password, hint: "we need your current password to confirm your changes", required: true %> +
    + +
    + <%= f.button :submit, "Update" %> +
    +<% end %> + +

    Cancel my account

    + +

    Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>

    + +<%= link_to "Back", :back %> diff --git a/app/views/users/registrations/new.html.erb b/app/views/users/registrations/new.html.erb new file mode 100644 index 000000000..535d17000 --- /dev/null +++ b/app/views/users/registrations/new.html.erb @@ -0,0 +1,19 @@ +<%= simple_form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %> + <%= f.error_notification %> +
    + <%= f.input :first_name, label: false, placeholder: 'First Name', wrapper_html: { class: 'col-md-6' } %> + <%= f.input :last_name, placeholder: 'Last Name', label: false, wrapper_html: {class: 'col-md-6'} %> +
    + <%= f.input :email, required: true, autofocus: true, label: false, placeholder: 'Email' %> + <%= f.input :password, required: true, hint: ("#{@minimum_password_length} characters minimum" if @minimum_password_length), placeholder: 'Your New Password', label: false %> + <%= f.input :password_confirmation, required: true, placeholder: 'Confirm Your Password', label: false %> + <%= f.simple_fields_for :profile do |s| %> + <%= s.input :birthdate, as: :date, end_year: Date.today.year - 90, start_year: Date.today.year - 12, order: [:day, :month, :year], required: false, prompt: true, label: 'Birthday' %> +
    + <%= s.input :sex, as: :radio_buttons, collection: [['Male', 'male'], ['Female', 'female']], item_wrapper_class: 'inline', class: 'radio-inline', required: false %> +
    +
    + <%= f.button :submit, "Sign up", class: 'btn btn-success btn-block' %> +
    + <% end %> +<% end %> \ No newline at end of file diff --git a/app/views/users/sessions/new.html.erb b/app/views/users/sessions/new.html.erb new file mode 100644 index 000000000..0de7a4220 --- /dev/null +++ b/app/views/users/sessions/new.html.erb @@ -0,0 +1,11 @@ +
    +

    Log in to Danebook

    + <%= simple_form_for(resource, as: resource_name, url: new_user_session_path, html: { }) do |f| %> + <%= f.input :email, required: false, autofocus: true, label: false, placeholder: 'Email Address' %> + <%= f.input :password, required: false, label: false, placeholder: 'Password' %> + <%= f.button :submit, "Sign in", class: 'btn btn-primary btn-block' %> + <% end %> +

    + <%= link_to 'Sign up for Danebook', new_user_path %> +

    +
    \ No newline at end of file diff --git a/app/views/users/shared/_links.html.erb b/app/views/users/shared/_links.html.erb new file mode 100644 index 000000000..e6a3e4196 --- /dev/null +++ b/app/views/users/shared/_links.html.erb @@ -0,0 +1,25 @@ +<%- if controller_name != 'sessions' %> + <%= link_to "Log in", new_session_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.registerable? && controller_name != 'registrations' %> + <%= link_to "Sign up", new_registration_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.recoverable? && controller_name != 'passwords' && controller_name != 'registrations' %> + <%= link_to "Forgot your password?", new_password_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.confirmable? && controller_name != 'confirmations' %> + <%= link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.lockable? && resource_class.unlock_strategy_enabled?(:email) && controller_name != 'unlocks' %> + <%= link_to "Didn't receive unlock instructions?", new_unlock_path(resource_name) %>
    +<% end -%> + +<%- if devise_mapping.omniauthable? %> + <%- resource_class.omniauth_providers.each do |provider| %> + <%= link_to "Sign in with #{OmniAuth::Utils.camelize(provider)}", omniauth_authorize_path(resource_name, provider) %>
    + <% end -%> +<% end -%> diff --git a/app/views/users/show.html.erb b/app/views/users/show.html.erb new file mode 100644 index 000000000..6ddb76264 --- /dev/null +++ b/app/views/users/show.html.erb @@ -0,0 +1,7 @@ +<%= content_for :module_title do %> + About + <%= link_to "Edit Your Profile", edit_user_path(@user), {class: 'btn btn-primary btn-sm module-edit'} if is_self? %> +<% end %> +<% content_for :module_body do %> + <%= render partial: 'profile', locals: {user: @user} %> +<% end %> \ No newline at end of file diff --git a/app/views/users/unlocks/new.html.erb b/app/views/users/unlocks/new.html.erb new file mode 100644 index 000000000..9fa02cfe8 --- /dev/null +++ b/app/views/users/unlocks/new.html.erb @@ -0,0 +1,16 @@ +

    Resend unlock instructions

    + +<%= simple_form_for(resource, as: resource_name, url: unlock_path(resource_name), html: { method: :post }) do |f| %> + <%= f.error_notification %> + <%= f.full_error :unlock_token %> + +
    + <%= f.input :email, required: true, autofocus: true %> +
    + +
    + <%= f.button :submit, "Resend unlock instructions" %> +
    +<% end %> + +<%= render "users/shared/links" %> diff --git a/assets/.DS_Store b/assets/.DS_Store new file mode 100644 index 000000000..e40fedc92 Binary files /dev/null and b/assets/.DS_Store differ diff --git a/assets/images/medium_missing.png b/assets/images/medium_missing.png new file mode 100644 index 000000000..e6e112bde Binary files /dev/null and b/assets/images/medium_missing.png differ 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..073966023 --- /dev/null +++ b/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +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..17240489f --- /dev/null +++ b/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +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..fb2ec2ebb --- /dev/null +++ b/bin/spring @@ -0,0 +1,17 @@ +#!/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) + spring = lockfile.specs.detect { |spec| spec.name == "spring" } + if 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..859eb8f88 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,17 @@ +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 Danebook + 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. + config.active_job.queue_adapter = :delayed_job + + 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..1f2cfffb3 --- /dev/null +++ b/config/database.yml @@ -0,0 +1,28 @@ +# SQLite version 3.x +# gem install sqlite3 +# +# Ensure the SQLite 3 gem is defined in your Gemfile +# gem 'sqlite3' +# +default: &default + adapter: postgresql + pool: 5 + timeout: 5000 + +development: + <<: *default + adapter: postgresql + database: project_danebook_development + +# 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 + +production: + <<: *default + adapter: postgresql + pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> + database: project_danebook 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..9e8933adf --- /dev/null +++ b/config/environments/development.rb @@ -0,0 +1,93 @@ +Rails.application.configure do + # Settings specified here will take precedence over those in config/application.rb. + + # Letter Opener + config.action_mailer.delivery_method = :letter_opener + + # Paperclip Amazon S3 + Paperclip.options[:command_path] = "/usr/local/bin" + + 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: These must be the correct region for YOUR bucket + :s3_host_name => "s3-ap-southeast-1.amazonaws.com", + :s3_region => "ap-southeast-1", + + # 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 + } + } + + # 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 + + # 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.action_mailer.default_url_options = { host: 'localhost', port: 3000 } + config.action_mailer.asset_host = 'http://localhost:3000' + + + # Bullet Gem + config.after_initialize do + Bullet.enable = true + Bullet.alert = true + Bullet.bullet_logger = true + Bullet.console = true + end +end diff --git a/config/environments/production.rb b/config/environments/production.rb new file mode 100644 index 000000000..6f847dc7d --- /dev/null +++ b/config/environments/production.rb @@ -0,0 +1,128 @@ +Rails.application.configure do + + # mailer host + config.action_mailer.default_url_options = { :host => 'https://tranquil-basin-42173.herokuapp.com' } + + #sendgrid set up + 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 + + + # Paperclip Amazon S3 + Paperclip.options[:command_path] = "/usr/local/bin" + + 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: These must be the correct region for YOUR bucket + :s3_host_name => "s3-ap-southeast-1.amazonaws.com", + :s3_region => "ap-southeast-1", + + # 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 + } + } + + + # 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 = "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 +end diff --git a/config/environments/test.rb b/config/environments/test.rb new file mode 100644 index 000000000..a9dfefd90 --- /dev/null +++ b/config/environments/test.rb @@ -0,0 +1,47 @@ +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! + Paperclip::Attachment.default_options[:path] = "#{Rails.root}/spec/test_files/:class/:id_partition/:style.:extension" + + + config.action_mailer.default_url_options = { :host => 'localhost:3000' } + + 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/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/devise.rb b/config/initializers/devise.rb new file mode 100644 index 000000000..a75ec5d37 --- /dev/null +++ b/config/initializers/devise.rb @@ -0,0 +1,277 @@ +# Use this hook to configure devise mailer, warden hooks and so forth. +# Many of these configuration options can be set straight in your model. +Devise.setup do |config| + # The secret key used by Devise. Devise uses this key to generate + # random tokens. Changing this key will render invalid all existing + # confirmation, reset password and unlock tokens in the database. + # Devise will use the `secret_key_base` as its `secret_key` + # by default. You can change it below and use your own secret key. + # config.secret_key = 'e3b77c3136356773e1423eeca05f4e182867e5e69f0d74ff339254941d3f4fcd18a62416b54e9872c859c24980ce7b58f4506c96d44b44aa6176a8a6df370a40' + + # ==> Mailer Configuration + # Configure the e-mail address which will be shown in Devise::Mailer, + # note that it will be overwritten if you use your own mailer class + # with default "from" parameter. + config.mailer_sender = 'please-change-me-at-config-initializers-devise@example.com' + + # Configure the class responsible to send e-mails. + # config.mailer = 'Devise::Mailer' + + # Configure the parent class responsible to send e-mails. + # config.parent_mailer = 'ActionMailer::Base' + + # ==> ORM configuration + # Load and configure the ORM. Supports :active_record (default) and + # :mongoid (bson_ext recommended) by default. Other ORMs may be + # available as additional gems. + require 'devise/orm/active_record' + + # ==> Configuration for any authentication mechanism + # Configure which keys are used when authenticating a user. The default is + # just :email. You can configure it to use [:username, :subdomain], so for + # authenticating a user, both parameters are required. Remember that those + # parameters are used only when authenticating and not when retrieving from + # session. If you need permissions, you should implement that in a before filter. + # You can also supply a hash where the value is a boolean determining whether + # or not authentication should be aborted when the value is not present. + # config.authentication_keys = [:email] + + # Configure parameters from the request object used for authentication. Each entry + # given should be a request method and it will automatically be passed to the + # find_for_authentication method and considered in your model lookup. For instance, + # if you set :request_keys to [:subdomain], :subdomain will be used on authentication. + # The same considerations mentioned for authentication_keys also apply to request_keys. + # config.request_keys = [] + + # Configure which authentication keys should be case-insensitive. + # These keys will be downcased upon creating or modifying a user and when used + # to authenticate or find a user. Default is :email. + config.case_insensitive_keys = [:email] + + # Configure which authentication keys should have whitespace stripped. + # These keys will have whitespace before and after removed upon creating or + # modifying a user and when used to authenticate or find a user. Default is :email. + config.strip_whitespace_keys = [:email] + + # Tell if authentication through request.params is enabled. True by default. + # It can be set to an array that will enable params authentication only for the + # given strategies, for example, `config.params_authenticatable = [:database]` will + # enable it only for database (email + password) authentication. + # config.params_authenticatable = true + + # Tell if authentication through HTTP Auth is enabled. False by default. + # It can be set to an array that will enable http authentication only for the + # given strategies, for example, `config.http_authenticatable = [:database]` will + # enable it only for database authentication. The supported strategies are: + # :database = Support basic authentication with authentication key + password + # config.http_authenticatable = false + + # If 401 status code should be returned for AJAX requests. True by default. + # config.http_authenticatable_on_xhr = true + + # The realm used in Http Basic Authentication. 'Application' by default. + # config.http_authentication_realm = 'Application' + + # It will change confirmation, password recovery and other workflows + # to behave the same regardless if the e-mail provided was right or wrong. + # Does not affect registerable. + # config.paranoid = true + + # By default Devise will store the user in session. You can skip storage for + # particular strategies by setting this option. + # Notice that if you are skipping storage for all authentication paths, you + # may want to disable generating routes to Devise's sessions controller by + # passing skip: :sessions to `devise_for` in your config/routes.rb + config.skip_session_storage = [:http_auth] + + # By default, Devise cleans up the CSRF token on authentication to + # avoid CSRF token fixation attacks. This means that, when using AJAX + # requests for sign in and sign up, you need to get a new CSRF token + # from the server. You can disable this option at your own risk. + # config.clean_up_csrf_token_on_authentication = true + + # When false, Devise will not attempt to reload routes on eager load. + # This can reduce the time taken to boot the app but if your application + # requires the Devise mappings to be loaded during boot time the application + # won't boot properly. + # config.reload_routes = true + + # ==> Configuration for :database_authenticatable + # For bcrypt, this is the cost for hashing the password and defaults to 11. If + # using other algorithms, it sets how many times you want the password to be hashed. + # + # Limiting the stretches to just one in testing will increase the performance of + # your test suite dramatically. However, it is STRONGLY RECOMMENDED to not use + # a value less than 10 in other environments. Note that, for bcrypt (the default + # algorithm), the cost increases exponentially with the number of stretches (e.g. + # a value of 20 is already extremely slow: approx. 60 seconds for 1 calculation). + config.stretches = Rails.env.test? ? 1 : 11 + + # Set up a pepper to generate the hashed password. + # config.pepper = 'c7c99d741d66d3168c1786ade67ae21e36f6c47dc726e69c7b6076d65b48e16ddccb78511eb28bf2e5766fb669a8ff04c31686ee12a4123c1de750b72bbf6851' + + # Send a notification to the original email when the user's email is changed. + # config.send_email_changed_notification = false + + # Send a notification email when the user's password is changed. + # config.send_password_change_notification = false + + # ==> Configuration for :confirmable + # A period that the user is allowed to access the website even without + # confirming their account. For instance, if set to 2.days, the user will be + # able to access the website for two days without confirming their account, + # access will be blocked just in the third day. Default is 0.days, meaning + # the user cannot access the website without confirming their account. + # config.allow_unconfirmed_access_for = 2.days + + # A period that the user is allowed to confirm their account before their + # token becomes invalid. For example, if set to 3.days, the user can confirm + # their account within 3 days after the mail was sent, but on the fourth day + # their account can't be confirmed with the token any more. + # Default is nil, meaning there is no restriction on how long a user can take + # before confirming their account. + # config.confirm_within = 3.days + + # If true, requires any email changes to be confirmed (exactly the same way as + # initial account confirmation) to be applied. Requires additional unconfirmed_email + # db field (see migrations). Until confirmed, new email is stored in + # unconfirmed_email column, and copied to email column on successful confirmation. + config.reconfirmable = true + + # Defines which key will be used when confirming an account + # config.confirmation_keys = [:email] + + # ==> Configuration for :rememberable + # The time the user will be remembered without asking for credentials again. + # config.remember_for = 2.weeks + + # Invalidates all the remember me tokens when the user signs out. + config.expire_all_remember_me_on_sign_out = true + + # If true, extends the user's remember period when remembered via cookie. + # config.extend_remember_period = false + + # Options to be passed to the created cookie. For instance, you can set + # secure: true in order to force SSL only cookies. + # config.rememberable_options = {} + + # ==> Configuration for :validatable + # Range for password length. + config.password_length = 6..128 + + # Email regex used to validate email formats. It simply asserts that + # one (and only one) @ exists in the given string. This is mainly + # to give user feedback and not to assert the e-mail validity. + config.email_regexp = /\A[^@\s]+@[^@\s]+\z/ + + # ==> Configuration for :timeoutable + # The time you want to timeout the user session without activity. After this + # time the user will be asked for credentials again. Default is 30 minutes. + # config.timeout_in = 30.minutes + + # ==> Configuration for :lockable + # Defines which strategy will be used to lock an account. + # :failed_attempts = Locks an account after a number of failed attempts to sign in. + # :none = No lock strategy. You should handle locking by yourself. + # config.lock_strategy = :failed_attempts + + # Defines which key will be used when locking and unlocking an account + # config.unlock_keys = [:email] + + # Defines which strategy will be used to unlock an account. + # :email = Sends an unlock link to the user email + # :time = Re-enables login after a certain amount of time (see :unlock_in below) + # :both = Enables both strategies + # :none = No unlock strategy. You should handle unlocking by yourself. + # config.unlock_strategy = :both + + # Number of authentication tries before locking an account if lock_strategy + # is failed attempts. + # config.maximum_attempts = 20 + + # Time interval to unlock the account if :time is enabled as unlock_strategy. + # config.unlock_in = 1.hour + + # Warn on the last attempt before the account is locked. + # config.last_attempt_warning = true + + # ==> Configuration for :recoverable + # + # Defines which key will be used when recovering the password for an account + # config.reset_password_keys = [:email] + + # Time interval you can reset your password with a reset password key. + # Don't put a too small interval or your users won't have the time to + # change their passwords. + config.reset_password_within = 6.hours + + # When set to false, does not sign a user in automatically after their password is + # reset. Defaults to true, so a user is signed in automatically after a reset. + # config.sign_in_after_reset_password = true + + # ==> Configuration for :encryptable + # Allow you to use another hashing or encryption algorithm besides bcrypt (default). + # You can use :sha1, :sha512 or algorithms from others authentication tools as + # :clearance_sha1, :authlogic_sha512 (then you should set stretches above to 20 + # for default behavior) and :restful_authentication_sha1 (then you should set + # stretches to 10, and copy REST_AUTH_SITE_KEY to pepper). + # + # Require the `devise-encryptable` gem when using anything other than bcrypt + # config.encryptor = :sha512 + + # ==> Scopes configuration + # Turn scoped views on. Before rendering "sessions/new", it will first check for + # "users/sessions/new". It's turned off by default because it's slower if you + # are using only default views. + # config.scoped_views = false + + # Configure the default scope given to Warden. By default it's the first + # devise role declared in your routes (usually :user). + # config.default_scope = :user + + # Set this configuration to false if you want /users/sign_out to sign out + # only the current scope. By default, Devise signs out all scopes. + # config.sign_out_all_scopes = true + + # ==> Navigation configuration + # Lists the formats that should be treated as navigational. Formats like + # :html, should redirect to the sign in page when the user does not have + # access, but formats like :xml or :json, should return 401. + # + # If you have any extra navigational formats, like :iphone or :mobile, you + # should add them to the navigational formats lists. + # + # The "*/*" below is required to match Internet Explorer requests. + # config.navigational_formats = ['*/*', :html] + + # The default HTTP method used to sign out a resource. Default is :delete. + config.sign_out_via = :delete + + # ==> OmniAuth + # Add a new OmniAuth provider. Check the wiki for more information on setting + # up on your models and hooks. + # config.omniauth :github, 'APP_ID', 'APP_SECRET', scope: 'user,public_repo' + + # ==> Warden configuration + # If you want to use other strategies, that are not supported by Devise, or + # change the failure app, you can configure them inside the config.warden block. + # + # config.warden do |manager| + # manager.intercept_401 = false + # manager.default_strategies(scope: :user).unshift :some_external_strategy + # end + + # ==> Mountable engine configurations + # When using Devise inside an engine, let's call it `MyEngine`, and this engine + # is mountable, there are some extra configurations to be taken into account. + # The following options are available, assuming the engine is mounted as: + # + # mount MyEngine, at: '/my_engine' + # + # The router that invoked `devise_for`, in the example above, would be: + # config.router_name = :my_engine + # + # When using OmniAuth, Devise cannot automatically set OmniAuth path, + # so you need to do it manually. For the users scope, it would be: + # config.omniauth_path_prefix = '/my_engine/users/auth' +end 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/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..671abb69a --- /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 Guide for Upgrading Ruby on Rails 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..f7b5ca3e4 --- /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: '_danebook_session' diff --git a/config/initializers/simple_form.rb b/config/initializers/simple_form.rb new file mode 100644 index 000000000..f306c195a --- /dev/null +++ b/config/initializers/simple_form.rb @@ -0,0 +1,169 @@ +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + # Wrappers are used by the form builder to generate a + # complete input. You can remove any component from the + # wrapper, change the order or even add your own to the + # stack. The options given below are used to wrap the + # whole input. + config.wrappers :default, class: :input, + hint_class: :field_with_hint, error_class: :field_with_errors do |b| + ## Extensions enabled by default + # Any of these extensions can be disabled for a + # given input by passing: `f.input EXTENSION_NAME => false`. + # You can make any of these extensions optional by + # renaming `b.use` to `b.optional`. + + # Determines whether to use HTML5 (:email, :url, ...) + # and required attributes + b.use :html5 + + # Calculates placeholders automatically from I18n + # You can also pass a string as f.input placeholder: "Placeholder" + b.use :placeholder + + ## Optional extensions + # They are disabled unless you pass `f.input EXTENSION_NAME => true` + # to the input. If so, they will retrieve the values from the model + # if any exists. If you want to enable any of those + # extensions by default, you can change `b.optional` to `b.use`. + + # Calculates maxlength from length validations for string inputs + # and/or database column lengths + b.optional :maxlength + + # Calculate minlength from length validations for string inputs + b.optional :minlength + + # Calculates pattern from format validations for string inputs + b.optional :pattern + + # Calculates min and max from length validations for numeric inputs + b.optional :min_max + + # Calculates readonly automatically from readonly attributes + b.optional :readonly + + ## Inputs + b.use :label_input + b.use :hint, wrap_with: { tag: :span, class: :hint } + b.use :error, wrap_with: { tag: :span, class: :error } + + ## full_messages_for + # If you want to display the full error message for the attribute, you can + # use the component :full_error, like: + # + # b.use :full_error, wrap_with: { tag: :span, class: :error } + end + + # The default wrapper to be used by the FormBuilder. + config.default_wrapper = :default + + # Define the way to render check boxes / radio buttons with labels. + # Defaults to :nested for bootstrap config. + # inline: input + label + # nested: label > input + config.boolean_style = :nested + + # Default class for buttons + config.button_class = 'btn' + + # Method used to tidy up errors. Specify any Rails Array method. + # :first lists the first message for each field. + # Use :to_sentence to list all errors for each field. + # config.error_method = :first + + # Default tag used for error notification helper. + config.error_notification_tag = :div + + # CSS class to add for error notification helper. + config.error_notification_class = 'error_notification' + + # ID to add for error notification helper. + # config.error_notification_id = nil + + # Series of attempts to detect a default label method for collection. + # config.collection_label_methods = [ :to_label, :name, :title, :to_s ] + + # Series of attempts to detect a default value method for collection. + # config.collection_value_methods = [ :id, :to_s ] + + # You can wrap a collection of radio/check boxes in a pre-defined tag, defaulting to none. + # config.collection_wrapper_tag = nil + + # You can define the class to use on all collection wrappers. Defaulting to none. + # config.collection_wrapper_class = nil + + # You can wrap each item in a collection of radio/check boxes with a tag, + # defaulting to :span. + # config.item_wrapper_tag = :span + + # You can define a class to use in all item wrappers. Defaulting to none. + # config.item_wrapper_class = nil + + # How the label text should be generated altogether with the required text. + # config.label_text = lambda { |label, required, explicit_label| "#{required} #{label}" } + + # You can define the class to use on all labels. Default is nil. + # config.label_class = nil + + # You can define the default class to be used on forms. Can be overriden + # with `html: { :class }`. Defaulting to none. + # config.default_form_class = nil + + # You can define which elements should obtain additional classes + # config.generate_additional_classes_for = [:wrapper, :label, :input] + + # Whether attributes are required by default (or not). Default is true. + # config.required_by_default = true + + # Tell browsers whether to use the native HTML5 validations (novalidate form option). + # These validations are enabled in SimpleForm's internal config but disabled by default + # in this configuration, which is recommended due to some quirks from different browsers. + # To stop SimpleForm from generating the novalidate option, enabling the HTML5 validations, + # change this configuration to true. + config.browser_validations = false + + # Collection of methods to detect if a file type was given. + # config.file_methods = [ :mounted_as, :file?, :public_filename ] + + # Custom mappings for input types. This should be a hash containing a regexp + # to match as key, and the input type that will be used when the field name + # matches the regexp as value. + # config.input_mappings = { /count/ => :integer } + + # Custom wrappers for input types. This should be a hash containing an input + # type as key and the wrapper that will be used for all inputs with specified type. + # config.wrapper_mappings = { string: :prepend } + + # Namespaces where SimpleForm should look for custom input classes that + # override default inputs. + # config.custom_inputs_namespaces << "CustomInputs" + + # Default priority for time_zone inputs. + # config.time_zone_priority = nil + + # Default priority for country inputs. + # config.country_priority = nil + + # When false, do not use translations for labels. + # config.translate_labels = true + + # Automatically discover new inputs in Rails' autoload path. + # config.inputs_discovery = true + + # Cache SimpleForm inputs discovery + # config.cache_discovery = !Rails.env.development? + + # Default class for inputs + # config.input_class = nil + + # Define the default class of the input wrapper of the boolean input. + config.boolean_label_class = 'checkbox' + + # Defines if the default input wrapper class should be included in radio + # collection wrappers. + # config.include_default_input_wrapper_class = true + + # Defines which i18n scope will be used in Simple Form. + # config.i18n_scope = 'simple_form' +end diff --git a/config/initializers/simple_form_bootstrap.rb b/config/initializers/simple_form_bootstrap.rb new file mode 100644 index 000000000..c7705e8e7 --- /dev/null +++ b/config/initializers/simple_form_bootstrap.rb @@ -0,0 +1,154 @@ +# Use this setup block to configure all options available in SimpleForm. +SimpleForm.setup do |config| + config.error_notification_class = 'alert alert-danger' + config.button_class = 'btn btn-default' + config.boolean_label_class = nil + + config.wrappers :vertical_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'control-label' + + b.use :input, class: 'form-control' + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :vertical_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :readonly + b.use :label, class: 'control-label' + + b.use :input + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :vertical_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + + b.wrapper tag: 'div', class: 'checkbox' do |ba| + ba.use :label_input + end + + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :vertical_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'control-label' + b.use :input + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :horizontal_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'col-sm-3 control-label' + + b.wrapper tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input, class: 'form-control' + ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } + ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + + config.wrappers :horizontal_file_input, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :readonly + b.use :label, class: 'col-sm-3 control-label' + + b.wrapper tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input + ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } + ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + + config.wrappers :horizontal_boolean, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + + b.wrapper tag: 'div', class: 'col-sm-offset-3 col-sm-9' do |wr| + wr.wrapper tag: 'div', class: 'checkbox' do |ba| + ba.use :label_input + end + + wr.use :error, wrap_with: { tag: 'span', class: 'help-block' } + wr.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + + config.wrappers :horizontal_radio_and_checkboxes, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + + b.use :label, class: 'col-sm-3 control-label' + + b.wrapper tag: 'div', class: 'col-sm-9' do |ba| + ba.use :input + ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } + ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + + config.wrappers :inline_form, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.use :placeholder + b.optional :maxlength + b.optional :minlength + b.optional :pattern + b.optional :min_max + b.optional :readonly + b.use :label, class: 'sr-only' + + b.use :input, class: 'form-control' + b.use :error, wrap_with: { tag: 'span', class: 'help-block' } + b.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + + config.wrappers :multi_select, tag: 'div', class: 'form-group', error_class: 'has-error' do |b| + b.use :html5 + b.optional :readonly + b.use :label, class: 'control-label' + b.wrapper tag: 'div', class: 'form-inline' do |ba| + ba.use :input, class: 'form-control' + ba.use :error, wrap_with: { tag: 'span', class: 'help-block' } + ba.use :hint, wrap_with: { tag: 'p', class: 'help-block' } + end + end + # Wrappers for forms and inputs using the Bootstrap toolkit. + # Check the Bootstrap docs (http://getbootstrap.com) + # to learn about the different styles for forms and inputs, + # buttons and other elements. + config.default_wrapper = :vertical_form + config.wrapper_mappings = { + check_boxes: :vertical_radio_and_checkboxes, + radio_buttons: :vertical_radio_and_checkboxes, + file: :vertical_file_input, + boolean: :vertical_boolean, + datetime: :multi_select, + date: :multi_select, + time: :multi_select + } +end 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/devise.en.yml b/config/locales/devise.en.yml new file mode 100644 index 000000000..0b8f13027 --- /dev/null +++ b/config/locales/devise.en.yml @@ -0,0 +1,64 @@ +# Additional translations at https://github.com/plataformatec/devise/wiki/I18n + +en: + devise: + confirmations: + confirmed: "Your email address has been successfully confirmed." + send_instructions: "You will receive an email with instructions for how to confirm your email address in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive an email with instructions for how to confirm your email address in a few minutes." + failure: + already_authenticated: "You are already signed in." + inactive: "Your account is not activated yet." + invalid: "Invalid %{authentication_keys} or password." + locked: "Your account is locked." + last_attempt: "You have one more attempt before your account is locked." + not_found_in_database: "Invalid %{authentication_keys} or password." + timeout: "Your session expired. Please sign in again to continue." + unauthenticated: "You need to sign in or sign up before continuing." + unconfirmed: "You have to confirm your email address before continuing." + mailer: + confirmation_instructions: + subject: "Confirmation instructions" + reset_password_instructions: + subject: "Reset password instructions" + unlock_instructions: + subject: "Unlock instructions" + email_changed: + subject: "Email Changed" + password_change: + subject: "Password Changed" + omniauth_callbacks: + failure: "Could not authenticate you from %{kind} because \"%{reason}\"." + success: "Successfully authenticated from %{kind} account." + passwords: + no_token: "You can't access this page without coming from a password reset email. If you do come from a password reset email, please make sure you used the full URL provided." + send_instructions: "You will receive an email with instructions on how to reset your password in a few minutes." + send_paranoid_instructions: "If your email address exists in our database, you will receive a password recovery link at your email address in a few minutes." + updated: "Your password has been changed successfully. You are now signed in." + updated_not_active: "Your password has been changed successfully." + registrations: + destroyed: "Bye! Your account has been successfully cancelled. We hope to see you again soon." + signed_up: "Welcome! You have signed up successfully." + signed_up_but_inactive: "You have signed up successfully. However, we could not sign you in because your account is not yet activated." + signed_up_but_locked: "You have signed up successfully. However, we could not sign you in because your account is locked." + signed_up_but_unconfirmed: "A message with a confirmation link has been sent to your email address. Please follow the link to activate your account." + update_needs_confirmation: "You updated your account successfully, but we need to verify your new email address. Please check your email and follow the confirm link to confirm your new email address." + updated: "Your account has been updated successfully." + sessions: + signed_in: "Signed in successfully." + signed_out: "Signed out successfully." + already_signed_out: "Signed out successfully." + unlocks: + send_instructions: "You will receive an email with instructions for how to unlock your account in a few minutes." + send_paranoid_instructions: "If your account exists, you will receive an email with instructions for how to unlock it in a few minutes." + unlocked: "Your account has been unlocked successfully. Please sign in to continue." + errors: + messages: + already_confirmed: "was already confirmed, please try signing in" + confirmation_period_expired: "needs to be confirmed within %{period}, please request a new one" + expired: "has expired, please request a new one" + not_found: "not found" + not_locked: "was not locked" + not_saved: + one: "1 error prohibited this %{resource} from being saved:" + other: "%{count} errors prohibited this %{resource} from being saved:" diff --git a/config/locales/en.bootstrap.yml b/config/locales/en.bootstrap.yml new file mode 100644 index 000000000..8d7511904 --- /dev/null +++ b/config/locales/en.bootstrap.yml @@ -0,0 +1,23 @@ +# Sample localization file for English. Add more files in this directory for other locales. +# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. + +en: + breadcrumbs: + application: + root: "Index" + pages: + pages: "Pages" + helpers: + actions: "Actions" + links: + back: "Back" + cancel: "Cancel" + confirm: "Are you sure?" + destroy: "Delete" + new: "New" + edit: "Edit" + titles: + edit: "Edit %{model}" + save: "Save %{model}" + new: "New %{model}" + delete: "Delete %{model}" 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/locales/simple_form.en.yml b/config/locales/simple_form.en.yml new file mode 100644 index 000000000..237438334 --- /dev/null +++ b/config/locales/simple_form.en.yml @@ -0,0 +1,31 @@ +en: + simple_form: + "yes": 'Yes' + "no": 'No' + required: + text: 'required' + mark: '*' + # You can uncomment the line below if you need to overwrite the whole required html. + # When using html, text and mark won't be used. + # html: '*' + error_notification: + default_message: "Please review the problems below:" + # Examples + # labels: + # defaults: + # password: 'Password' + # user: + # new: + # email: 'E-mail to sign in.' + # edit: + # email: 'E-mail.' + # hints: + # defaults: + # username: 'User name to sign in.' + # password: 'No special characters, please.' + # include_blanks: + # defaults: + # age: 'Rather not say' + # prompts: + # defaults: + # age: 'Select your age' 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..cdd528046 --- /dev/null +++ b/config/routes.rb @@ -0,0 +1,45 @@ +Rails.application.routes.draw do + devise_for :users, + controllers: { + sessions: 'users/sessions', + registrations: 'users' + } + authenticated :user do + root 'newsfeed#index' + end + + # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html + root to: 'users#new' + get 'newsfeed', to: 'newsfeed#index' + resources :users, only: [:new, :create, :update, :edit, :index] do + get 'about' => 'users#show' + get 'profile' => 'posts#index' + resources :photos, only: [:create, :index, :destroy] do + get 'upload' => 'photos#new', on: :collection + end + resources :posts, only: [:new, :create, :destroy] + resources :friendships, path: 'friends', as: 'friends', only: [:create, :destroy, :index] do + end + end + resources :photos, only: [:show, :destroy] do + patch 'cover' => 'profiles#update', on: :member, defaults: {photo_type: 'Cover'} + patch 'avatar' => 'profiles#update', on: :member, defaults: {photo_type: 'Avatar'} + resources :comments, only: [:create], defaults: { commentable: 'Photo'} + resources :likes, only: [:create, :destroy], defaults: { likeable: 'Photo'} + end + resources :posts, only: [] do + resources :likes, only: [:create, :destroy], defaults: { likeable: 'Post'} + resources :comments, only: [:create], defaults: { commentable: 'Post'} + end + resources :comments, only: [:destroy] do + resources :likes, only: [:create, :destroy], defaults: { likeable: 'Comment'} + # resources :comment_likes, only: [:create, :destroy] + end + resources :friendships, path: 'friends', as: 'friends', only: [:destroy] do + patch 'accept' => 'friendships#update', on: :member + patch 'reject' => 'friendships#update', on: :member + patch 'cancel' => 'friendships#update', on: :member + end + + +end diff --git a/config/secrets.yml b/config/secrets.yml new file mode 100644 index 000000000..9d80296dd --- /dev/null +++ b/config/secrets.yml @@ -0,0 +1,31 @@ +# 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: e16e3fcac8c0e42fad4f0347225290317fbb4474f8998db3d78e2823f9c5ee9feaf78cc368aec147b4fcc048eee55d070c4ca1f8b4cbb6fd08c71b1af86d90c8 + s3_bucket_name: <%= ENV["S3_BUCKET_NAME"] %> + aws_access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %> + aws_secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %> + use_delayed_emails: <%= ENV["USE_DELAYED_EMAILS"] %> + + +test: + secret_key_base: 8bb3f40fd84a5da397dcbaecd68b4b2930497e80614816901a3ad88a21c659232f3f48b2816ebaef397218eeb9b4464c26e28ca64cb54f48699ac69ad05427ed + +# 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_access_key_id: <%= ENV["AWS_ACCESS_KEY_ID"] %> + aws_secret_access_key: <%= ENV["AWS_SECRET_ACCESS_KEY"] %> + use_delayed_emails: <%= ENV["USE_DELAYED_EMAILS"] %> 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/20170407140601_create_users.rb b/db/migrate/20170407140601_create_users.rb new file mode 100644 index 000000000..58b29760c --- /dev/null +++ b/db/migrate/20170407140601_create_users.rb @@ -0,0 +1,9 @@ +class CreateUsers < ActiveRecord::Migration[5.0] + def change + create_table :users do |t| + t.string :email, null: false, index: true, unique: true + + t.timestamps + end + end +end diff --git a/db/migrate/20170407141035_create_profiles.rb b/db/migrate/20170407141035_create_profiles.rb new file mode 100644 index 000000000..03810bab3 --- /dev/null +++ b/db/migrate/20170407141035_create_profiles.rb @@ -0,0 +1,13 @@ +class CreateProfiles < ActiveRecord::Migration[5.0] + def change + create_table :profiles do |t| + t.string :first_name + t.string :last_name + t.string :sex + t.date :birthdate + t.references :user, foreign_key: true + + t.timestamps + end + end +end diff --git a/db/migrate/20170409100012_add_auth_token_to_users.rb b/db/migrate/20170409100012_add_auth_token_to_users.rb new file mode 100644 index 000000000..c60089e21 --- /dev/null +++ b/db/migrate/20170409100012_add_auth_token_to_users.rb @@ -0,0 +1,5 @@ +class AddAuthTokenToUsers < ActiveRecord::Migration[5.0] + def change + add_column :users, :auth_token, :string, index: true + end +end diff --git a/db/migrate/20170410135052_add_columns_to_profile.rb b/db/migrate/20170410135052_add_columns_to_profile.rb new file mode 100644 index 000000000..d91490970 --- /dev/null +++ b/db/migrate/20170410135052_add_columns_to_profile.rb @@ -0,0 +1,10 @@ +class AddColumnsToProfile < ActiveRecord::Migration[5.0] + def change + add_column :profiles, :college, :string + add_column :profiles, :hometown, :string + add_column :profiles, :current_city, :string + add_column :profiles, :telephone, :string + add_column :profiles, :quote, :string + add_column :profiles, :about, :text + end +end diff --git a/db/migrate/20170415030309_add_devise_to_users.rb b/db/migrate/20170415030309_add_devise_to_users.rb new file mode 100644 index 000000000..d9e3fd0d2 --- /dev/null +++ b/db/migrate/20170415030309_add_devise_to_users.rb @@ -0,0 +1,49 @@ +class AddDeviseToUsers < ActiveRecord::Migration[5.0] + def self.up + change_table :users do |t| + ## Database authenticatable + # t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + + ## Rememberable + t.datetime :remember_created_at + + ## Trackable + t.integer :sign_in_count, default: 0, null: false + t.datetime :current_sign_in_at + t.datetime :last_sign_in_at + t.string :current_sign_in_ip + t.string :last_sign_in_ip + + ## Confirmable + # t.string :confirmation_token + # t.datetime :confirmed_at + # t.datetime :confirmation_sent_at + # t.string :unconfirmed_email # Only if using reconfirmable + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + + # Uncomment below if timestamps were not included in your original model. + # t.timestamps null: false + end + + # add_index :users, :email, unique: true + add_index :users, :reset_password_token, unique: true + # add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end + + def self.down + # By default, we don't want to make any assumption about how to roll back a migration when your + # model already existed. Please edit below which fields you would like to remove in this migration. + raise ActiveRecord::IrreversibleMigration + end +end diff --git a/db/migrate/20170416024248_create_posts.rb b/db/migrate/20170416024248_create_posts.rb new file mode 100644 index 000000000..b8bcdd177 --- /dev/null +++ b/db/migrate/20170416024248_create_posts.rb @@ -0,0 +1,10 @@ +class CreatePosts < ActiveRecord::Migration[5.0] + def change + create_table :posts do |t| + t.text :body, null: false + t.references :user, foreign_key: true, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20170416090137_add_likes_to_post.rb b/db/migrate/20170416090137_add_likes_to_post.rb new file mode 100644 index 000000000..14bcab57d --- /dev/null +++ b/db/migrate/20170416090137_add_likes_to_post.rb @@ -0,0 +1,5 @@ +class AddLikesToPost < ActiveRecord::Migration[5.0] + def change + add_column :posts, :likes_count, :integer, default: 0, null: false + end +end diff --git a/db/migrate/20170416090324_create_likes.rb b/db/migrate/20170416090324_create_likes.rb new file mode 100644 index 000000000..8a9f56189 --- /dev/null +++ b/db/migrate/20170416090324_create_likes.rb @@ -0,0 +1,9 @@ +class CreateLikes < ActiveRecord::Migration[5.0] + def change + create_table :likes do |t| + t.references :post, foreign_key: true, null: false + t.references :user, foreign_key: true, null: false + t.timestamps + end + end +end diff --git a/db/migrate/20170416131110_create_comments.rb b/db/migrate/20170416131110_create_comments.rb new file mode 100644 index 000000000..c7b3d879c --- /dev/null +++ b/db/migrate/20170416131110_create_comments.rb @@ -0,0 +1,12 @@ +class CreateComments < ActiveRecord::Migration[5.0] + def change + create_table :comments do |t| + t.text :body, null: false + t.integer :comment_likes_count, default: 0 + t.references :user, foreign_key: true, null: false + t.references :post, foreign_key: true, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20170417012705_create_comment_likes.rb b/db/migrate/20170417012705_create_comment_likes.rb new file mode 100644 index 000000000..c80effd98 --- /dev/null +++ b/db/migrate/20170417012705_create_comment_likes.rb @@ -0,0 +1,10 @@ +class CreateCommentLikes < ActiveRecord::Migration[5.0] + def change + create_table :comment_likes do |t| + t.references :user, foreign_key: true + t.references :comment, foreign_key: true + t.index [:user_id, :comment_id], unique: true + t.timestamps + end + end +end diff --git a/db/migrate/20170417055705_create_friendships.rb b/db/migrate/20170417055705_create_friendships.rb new file mode 100644 index 000000000..6fb4eec17 --- /dev/null +++ b/db/migrate/20170417055705_create_friendships.rb @@ -0,0 +1,10 @@ +class CreateFriendships < ActiveRecord::Migration[5.0] + def change + create_table :friendships do |t| + t.integer :friender_id, null: false + t.integer :friendee_id, null: false + t.index [:friender_id, :friendee_id], unique: true + t.timestamps + end + end +end diff --git a/db/migrate/20170417110545_add_friend_count_to_user.rb b/db/migrate/20170417110545_add_friend_count_to_user.rb new file mode 100644 index 000000000..f03ac9ccf --- /dev/null +++ b/db/migrate/20170417110545_add_friend_count_to_user.rb @@ -0,0 +1,5 @@ +class AddFriendCountToUser < ActiveRecord::Migration[5.0] + def change + add_column :users, :friendships_count, :integer, default: 0 + end +end diff --git a/db/migrate/20170424083456_add_sessions_table.rb b/db/migrate/20170424083456_add_sessions_table.rb new file mode 100644 index 000000000..e0b41c7d7 --- /dev/null +++ b/db/migrate/20170424083456_add_sessions_table.rb @@ -0,0 +1,12 @@ +class AddSessionsTable < ActiveRecord::Migration + def change + create_table :sessions do |t| + t.string :session_id, :null => false + t.text :data + t.timestamps + end + + add_index :sessions, :session_id, :unique => true + add_index :sessions, :updated_at + end +end diff --git a/db/migrate/20170428031223_create_photos.rb b/db/migrate/20170428031223_create_photos.rb new file mode 100644 index 000000000..412669fa6 --- /dev/null +++ b/db/migrate/20170428031223_create_photos.rb @@ -0,0 +1,9 @@ +class CreatePhotos < ActiveRecord::Migration[5.0] + def change + create_table :photos do |t| + t.references :user, foreign_key: true + t.timestamps + end + add_attachment :photos, :image + end +end diff --git a/db/migrate/20170429061502_add_status_to_friendship.rb b/db/migrate/20170429061502_add_status_to_friendship.rb new file mode 100644 index 000000000..0ecd083a6 --- /dev/null +++ b/db/migrate/20170429061502_add_status_to_friendship.rb @@ -0,0 +1,5 @@ +class AddStatusToFriendship < ActiveRecord::Migration[5.0] + def change + add_column :friendships, :rejected, :boolean + end +end diff --git a/db/migrate/20170502032831_make_comment_polymorphic.rb b/db/migrate/20170502032831_make_comment_polymorphic.rb new file mode 100644 index 000000000..b971edd31 --- /dev/null +++ b/db/migrate/20170502032831_make_comment_polymorphic.rb @@ -0,0 +1,6 @@ +class MakeCommentPolymorphic < ActiveRecord::Migration[5.0] + def change + add_reference :comments, :commentable, polymorphic: true, index: true + remove_column :comments, :post_id, :integer + end +end diff --git a/db/migrate/20170502064943_add_likes_to_photos.rb b/db/migrate/20170502064943_add_likes_to_photos.rb new file mode 100644 index 000000000..87069ad7a --- /dev/null +++ b/db/migrate/20170502064943_add_likes_to_photos.rb @@ -0,0 +1,5 @@ +class AddLikesToPhotos < ActiveRecord::Migration[5.0] + def change + add_column :photos, :likes_count, :integer, default: 0 + end +end diff --git a/db/migrate/20170502070041_make_likes_polymorphic.rb b/db/migrate/20170502070041_make_likes_polymorphic.rb new file mode 100644 index 000000000..818edc82c --- /dev/null +++ b/db/migrate/20170502070041_make_likes_polymorphic.rb @@ -0,0 +1,7 @@ +class MakeLikesPolymorphic < ActiveRecord::Migration[5.0] + def change + remove_column :likes, :post_id, :integer + add_reference :likes, :likeable, polymorphic: true, index: false + add_index :likes, [:user_id, :likeable_id, :likeable_type], unique: true + end +end diff --git a/db/migrate/20170503032533_add_photos_to_profile.rb b/db/migrate/20170503032533_add_photos_to_profile.rb new file mode 100644 index 000000000..0688d67a2 --- /dev/null +++ b/db/migrate/20170503032533_add_photos_to_profile.rb @@ -0,0 +1,6 @@ +class AddPhotosToProfile < ActiveRecord::Migration[5.0] + def change + add_reference :profiles, :cover, references: :photos, index: true + add_reference :profiles, :avatar, references: :photos, index: true + end +end diff --git a/db/migrate/20170504080233_create_delayed_jobs.rb b/db/migrate/20170504080233_create_delayed_jobs.rb new file mode 100644 index 000000000..27fdcf6cc --- /dev/null +++ b/db/migrate/20170504080233_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/20170505021349_add_attachments_to_profile.rb b/db/migrate/20170505021349_add_attachments_to_profile.rb new file mode 100644 index 000000000..e2cf45a08 --- /dev/null +++ b/db/migrate/20170505021349_add_attachments_to_profile.rb @@ -0,0 +1,8 @@ +class AddAttachmentsToProfile < ActiveRecord::Migration[5.0] + def change + remove_column :profiles, :avatar_id + remove_column :profiles, :cover_id + add_attachment :profiles, :avatar + add_attachment :profiles, :cover + end +end diff --git a/db/migrate/20170506020348_drop_c_omment_likes_table.rb b/db/migrate/20170506020348_drop_c_omment_likes_table.rb new file mode 100644 index 000000000..3ac3b3d37 --- /dev/null +++ b/db/migrate/20170506020348_drop_c_omment_likes_table.rb @@ -0,0 +1,9 @@ +class DropCOmmentLikesTable < ActiveRecord::Migration[5.0] + def change + drop_table :comment_likes do |c| + c.integer :user_id + c.integer :comment_id + end + rename_column :comments, :comment_likes_count, :likes_count + end +end diff --git a/db/migrate/20170506031628_move_names_to_user.rb b/db/migrate/20170506031628_move_names_to_user.rb new file mode 100644 index 000000000..706dceb80 --- /dev/null +++ b/db/migrate/20170506031628_move_names_to_user.rb @@ -0,0 +1,8 @@ +class MoveNamesToUser < ActiveRecord::Migration[5.0] + def change + remove_column :profiles, :first_name, :string + remove_column :profiles, :last_name, :string + add_column :users, :first_name, :string + add_column :users, :last_name, :string + end +end diff --git a/db/migrate/20170508020353_create_activities.rb b/db/migrate/20170508020353_create_activities.rb new file mode 100644 index 000000000..e5262c576 --- /dev/null +++ b/db/migrate/20170508020353_create_activities.rb @@ -0,0 +1,10 @@ +class CreateActivities < ActiveRecord::Migration[5.0] + def change + create_table :activities do |t| + t.references :user, foreign_key: true + t.references :activable, polymorphic: true + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 000000000..a3fd7fe63 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,154 @@ +# 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: 20170508020353) do + + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "activities", force: :cascade do |t| + t.integer "user_id" + t.string "activable_type" + t.integer "activable_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["activable_type", "activable_id"], name: "index_activities_on_activable_type_and_activable_id", using: :btree + t.index ["user_id"], name: "index_activities_on_user_id", using: :btree + end + + create_table "comments", force: :cascade do |t| + t.text "body", null: false + t.integer "likes_count", default: 0 + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "commentable_type" + t.integer "commentable_id" + t.index ["commentable_type", "commentable_id"], name: "index_comments_on_commentable_type_and_commentable_id", using: :btree + t.index ["user_id"], name: "index_comments_on_user_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 "friendships", force: :cascade do |t| + t.integer "friender_id", null: false + t.integer "friendee_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "rejected" + t.index ["friender_id", "friendee_id"], name: "index_friendships_on_friender_id_and_friendee_id", unique: true, using: :btree + end + + create_table "likes", force: :cascade do |t| + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "likeable_type" + t.integer "likeable_id" + t.index ["user_id", "likeable_id", "likeable_type"], name: "index_likes_on_user_id_and_likeable_id_and_likeable_type", unique: true, using: :btree + t.index ["user_id"], name: "index_likes_on_user_id", using: :btree + end + + create_table "photos", force: :cascade do |t| + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "image_file_name" + t.string "image_content_type" + t.integer "image_file_size" + t.datetime "image_updated_at" + t.integer "likes_count", default: 0 + t.index ["user_id"], name: "index_photos_on_user_id", using: :btree + end + + create_table "posts", force: :cascade do |t| + t.text "body", null: false + t.integer "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.integer "likes_count", default: 0, null: false + t.index ["user_id"], name: "index_posts_on_user_id", using: :btree + end + + create_table "profiles", force: :cascade do |t| + t.string "sex" + t.date "birthdate" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "college" + t.string "hometown" + t.string "current_city" + t.string "telephone" + t.string "quote" + t.text "about" + t.string "avatar_file_name" + t.string "avatar_content_type" + t.integer "avatar_file_size" + t.datetime "avatar_updated_at" + t.string "cover_file_name" + t.string "cover_content_type" + t.integer "cover_file_size" + t.datetime "cover_updated_at" + t.index ["user_id"], name: "index_profiles_on_user_id", using: :btree + end + + create_table "sessions", force: :cascade do |t| + t.string "session_id", null: false + t.text "data" + t.datetime "created_at" + t.datetime "updated_at" + t.index ["session_id"], name: "index_sessions_on_session_id", unique: true, using: :btree + t.index ["updated_at"], name: "index_sessions_on_updated_at", using: :btree + end + + create_table "users", force: :cascade do |t| + t.string "email", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "auth_token" + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", default: 0, null: false + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.integer "friendships_count", default: 0 + t.string "first_name" + t.string "last_name" + t.index ["email"], name: "index_users_on_email", using: :btree + t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree + end + + add_foreign_key "activities", "users" + add_foreign_key "comments", "users" + add_foreign_key "likes", "users" + add_foreign_key "photos", "users" + add_foreign_key "posts", "users" + add_foreign_key "profiles", "users" +end diff --git a/db/seeds.rb b/db/seeds.rb new file mode 100644 index 000000000..7e7636a47 --- /dev/null +++ b/db/seeds.rb @@ -0,0 +1,59 @@ +# 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) + +# disable mailing +ActionMailer::Base.perform_deliveries = false + +puts "Destroying existing records..." +User.destroy_all + + +puts "Populating users..." +('a' .. 'g').each do |a| + u = User.new(email: "#{a}@#{a}.com", password: a * 12, password_confirmation: a * 12, first_name: Faker::Name.first_name, last_name: Faker::Name.last_name, ) + u.build_profile(sex: ['female', 'male'].sample, birthdate:Faker::Date.birthday(13, 99), college: Faker::University.name, hometown: Faker::Address.city, current_city: Faker::Address.city, telephone: Faker::PhoneNumber.phone_number, quote: Faker::Hacker.say_something_smart, about: Faker::Hipster.sentence(3)) + u.save! +end + +puts 'Populating posts...' +User.all.each do |u| + rand(1..5).times do + u.posts.create(body: Faker::Hacker.say_something_smart, created_at: Faker::Date.between(5.months.ago, Date.today)) + end +end + +puts 'Populating comments and likes...' +Post.all.each do |po| + rand(1..5).times do + po.likes.create(user_id: User.all.pluck(:id).sample, likeable_type: 'Post') + po.comments.create(user_id: User.all.pluck(:id).sample, body: Faker::Hacker.say_something_smart, created_at: Faker::Date.between(5.months.ago, Date.today)) + end +end + +puts 'Populating comment likes...' +Comment.all.each do |co| + rand(0..5).times do + co.likes.create(user_id: User.all.pluck(:id).sample, created_at: Faker::Date.between(5.months.ago, Date.today) ) + end +end + +puts 'Populating friends...' + +User.all.each do |u| + ids = User.all.pluck(:id) + rand(1..5).times do + u.initiated_friendships.create(friendee_id: ids.sample) + end +end + +Friendship.all.each do |f| + f.update(rejected: false) +end + + +puts "Done!" 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/lib/templates/erb/scaffold/_form.html.erb b/lib/templates/erb/scaffold/_form.html.erb new file mode 100644 index 000000000..201a069e2 --- /dev/null +++ b/lib/templates/erb/scaffold/_form.html.erb @@ -0,0 +1,13 @@ +<%%= simple_form_for(@<%= singular_table_name %>) do |f| %> + <%%= f.error_notification %> + +
    + <%- attributes.each do |attribute| -%> + <%%= f.<%= attribute.reference? ? :association : :input %> :<%= attribute.name %> %> + <%- end -%> +
    + +
    + <%%= f.button :submit %> +
    +<%% end %> 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/.DS_Store b/spec/.DS_Store new file mode 100644 index 000000000..de0830c9d Binary files /dev/null and b/spec/.DS_Store differ diff --git a/spec/factories.rb b/spec/factories.rb new file mode 100644 index 000000000..40d9a19f8 --- /dev/null +++ b/spec/factories.rb @@ -0,0 +1,173 @@ + +FactoryGirl.define do + + factory :photo do + user + image { Rack::Test::UploadedFile.new(Rails.root.join('spec/requests/medium_missing.png'), 'image/png')} + end + + factory :user, aliases: [:friender, :friendee, :friend] do + sequence(:first_name){ |n| "Foo#{n}"} + sequence(:last_name){ |n| "Bar#{n}"} + email { "#{first_name}@#{last_name}.com"} + password 'foobarfoobar' + password_confirmation 'foobarfoobar' + friendships_count 0 + + after(:build) do |user| + class << user + def send_welcome_email; true; end + end + end + + trait :with_welcome_email do + after(:build) do |user| + class << user + def send_welcome_email; super; end + end + end + end + + trait :with_profile do + association :profile + end + trait :with_pending_friend_request do + after(:create) do |user| + friend = create(:friendee, :with_profile) + user.initiated_friendships.create(friendee_id: friend.id) + end + end + trait :with_rejected_friend_request do + after(:create) do |user| + friend = create(:user, :with_profile) + create(:friendship, rejected: true, friendee_id: friend.id, friender_id: user.id) + end + end + trait :with_accepted_friend_request do + after(:create) do |user| + friend = create(:friend, :with_profile) + create(:friendship, rejected: false, friendee_id: friend.id, friender_id: user.id) + end + end + trait :with_friends do + transient do + friend_count 3 + end + after(:create) do |u, evaluator| + friends = create_list(:friend, evaluator.friend_count, :with_profile) + u.friendees << friends + end + end + + end + + factory :profile do + sex 'female' + birthdate { Faker::Date.birthday(13,99)} + college { Faker::University.name } + hometown { Faker::Address.city } + current_city { Faker::Address.city } + telephone { Faker::PhoneNumber.phone_number } + quote { Faker::Hacker.say_something_smart } + about { Faker::Hipster.sentence(3) } + trait :with_user do + user + end + trait :with_images do + association :avatar, factory: :photo + association :cover, factory: :photo + end + + trait :male do + sex 'male' + end + trait :without_images do + avatar_id nil + cover_id nil + end + end + + factory :friendship do + association :friend_initiator, factory: [:friender, :with_profile] + association :friend_recipient, factory: [:friendee, :with_profile] + rejected nil + trait :accepted do + rejected false + end + trait :pending do + rejected nil + end + + end + + factory :post do + body { Faker::Hacker.say_something_smart } + user + + after(:create) do |post| + create(:profile, user: post.user) + end + + trait :with_likes do + transient do + likes_count 3 + end + after(:create) do |post, evaluator| + create_list(:like, evaluator.likes_count, :for_post, likeable: post) + end + + end + + end + factory :like do + user + trait :for_post do + association :likeable, factory: :post + end + trait :for_comment do + association :likeable, factory: :comment + end + end + + factory :comment do + body Faker::Hacker.say_something_smart + user + trait :for_post do + # commentable_type 'Post' + association :commentable, factory: :post + end + trait :for_photo do + # commentable_type 'Photo' + association :commentable, factory: :photo + end + + # this is so we don't send notification emails automatically when testing + after(:build) do |comment| + class << comment + def send_notification_email; true; end + end + end + + trait :with_notification_email do + after(:build) do |comment| + class << comment + def send_notification_email; super; end + end + end + end + + end + + factory :activity do + user + trait :for_photo do + association :activable, factory: :photo + end + trait :for_post do + association :activable, factory: :post + end + end + + + +end diff --git a/spec/features/authentication_spec.rb b/spec/features/authentication_spec.rb new file mode 100644 index 000000000..761179a80 --- /dev/null +++ b/spec/features/authentication_spec.rb @@ -0,0 +1,32 @@ +require 'rails_helper' + +feature 'Authentication' do + let(:user){ create(:user, :with_profile)} + before do + user + end + scenario 'cannot sign in with improper credentials' do + visit root_path + user.password = 'xxx' + log_in(user) + expect(page).to have_content('Log in to Danebook') + end + scenario 'can sign in with proper credentials' do + visit root_path + log_in(user) + expect(page).to have_content('Signed in successfully.') + end + scenario 'can sign in from another page' do + user + visit user_about_path(user) + log_in(user) + expect(page).to have_content('Signed in successfully.') + end + scenario 'successful sign ins redirects users to their newsfeed' do + user + visit root_path + log_in(user) + expect(page).to have_selector('.newsfeed-sidebar') + end + +end diff --git a/spec/features/friending_spec.rb b/spec/features/friending_spec.rb new file mode 100644 index 000000000..d18e9ef2e --- /dev/null +++ b/spec/features/friending_spec.rb @@ -0,0 +1,43 @@ +require 'rails_helper' + +feature 'Friending' do + let(:users){ create_list(:user, 3, :with_profile)} + let(:user){ create(:user, :with_profile)} + let(:friend){ create(:user, :with_profile)} + let(:friend_invite){ create(:friendship, friender_id: friend.id, friendee_id: user.id)} + context 'signed out' do + before do + users + end + scenario 'cannot add friend' do + visit user_about_path(users.first) + click_link 'Add Friend' + expect(page).to have_content('Connect with all your friends!') + end + end + context 'signed in' do + before do + visit root_path + log_in(user) + end + scenario 'can send a friend invite' do + visit user_profile_path(friend) + click_link 'Add Friend' + expect(Friendship.last.friend_initiator).to eq(user) + expect(Friendship.last.friend_recipient).to eq(friend) + expect(Friendship.last.rejected).to be_nil + end + scenario 'can accept friend requests' do + friend_invite + visit user_profile_path(friend) + expect{ click_link 'Accept'}.to change(Friendship, :count).by(1) + end + scenario 'can remove friends' do + create(:friendship, friender_id: user.id, friendee_id: friend.id, rejected: false) + create(:friendship, friender_id: friend.id, friendee_id: user.id, rejected: false) + visit user_profile_path(friend) + expect{ click_link 'Remove Friend'}.to change(Friendship, :count).by(-2) + end + + end +end diff --git a/spec/features/newsfeed_spec.rb b/spec/features/newsfeed_spec.rb new file mode 100644 index 000000000..50776082a --- /dev/null +++ b/spec/features/newsfeed_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +feature 'Newsfeed Browsing' do + let(:user){ create(:user, :with_profile)} + context 'logged out' do + it 'shows the log in page' do + visit root_path + expect(page).to have_text('Connect with') + end + end + context 'logged in' do + before do + visit root_path + log_in(user) + end + it 'shows the newsfeed' do + expect(page).to have_selector('.newsfeed-sidebar') + end + it 'posting to newsfeed reloads page on success' do + within ('.post-form') do + fill_in 'post[body]', with: :'The quick brown fox' + end + click_button 'Post' + expect(page).to have_text('The quick brown fox') + expect(page).to have_selector('.newsfeed-sidebar') + end + end + +end diff --git a/spec/features/photo_spec.rb b/spec/features/photo_spec.rb new file mode 100644 index 000000000..99ffec7bc --- /dev/null +++ b/spec/features/photo_spec.rb @@ -0,0 +1,71 @@ +require 'rails_helper' + +feature 'Photo Actions' do + let(:user){ create(:user, :with_profile, :with_accepted_friend_request)} + let(:friend){ create(:user, :with_profile)} + let(:photo){ create(:photo, user: user)} + let(:test_photo){ create(:photo, user: user, image: Rack::Test::UploadedFile.new(Rails.root.join('spec/images/thumbnail_missing.png'), 'image/png'))} + let(:cover){create(:photo, user: user, image: Rack::Test::UploadedFile.new(Rails.root.join('spec/images/cover.png'), 'image/png')) } + context 'logged out' + context 'Logged in' do + before do + login_as(user, scope: :user) + photo + end + scenario 'Clicking \'Add Photos\' leads to photo upload page' do + visit user_photos_path(user) + click_link 'Add Photos' + expect(page).to have_button('Upload Photo') + expect(page).to have_button('Use Web Photo') + end + scenario 'Can only add photos to own account' do + visit user_photos_path(create(:user, :with_profile)) + expect{ click_button 'Add Photo' }.to raise_error(Capybara::ElementNotFound) + end + scenario 'Can upload photo from hard drive' do + visit upload_user_photos_path(user) + page.attach_file('photo[image]', Rails.root + 'spec/images/thumbnail_missing.png') + expect{ click_button 'Upload Photo'}.to change(Photo, :count).by(1) + end + scenario 'Can upload photo from web' do + visit upload_user_photos_path(user) + fill_in 'photo[image]', with: 'https://68.media.tumblr.com/avatar_b4b457657a2d_128.png' + expect{ click_button 'Use Web Photo'}.to change(Photo, :count).by(1) + end + scenario 'Can view own photo' do + visit user_photos_path(user) + first('figure a').click + expect(page).to have_css('img[id="photo"]') + end + scenario 'Can set profile photo' do + visit photo_path(test_photo) + click_link 'Set as Profile' + expect(page.find('.profile-pic')['src']).to have_content('thumbnail_missing.png') + end + scenario 'Can set cover photo' do + visit photo_path(cover) + click_link 'Set as Cover' + expect(page.find('.profile-cover')['style']).to have_content('cover.png') + end + scenario 'Can delete photo' do + visit photo_path(test_photo) + expect{ click_link 'Delete Photo' }.to change(Photo, :count).by(-1) + end + + end + context 'logged in as friend' do + before do + login_as(user.friendees.first, scope: :user) + photo + end + it 'Can view photo' do + visit user_photos_path(user) + first('figure a').click + expect(page).to have_css('img[id="photo"]') + end + it 'Cannot delete photo' do + visit photo_path(cover) + expect{ click_link 'Delete Photo'}.to raise_error(Capybara::ElementNotFound) + end + end +end diff --git a/spec/features/posting_spec.rb b/spec/features/posting_spec.rb new file mode 100644 index 000000000..9f50e67d8 --- /dev/null +++ b/spec/features/posting_spec.rb @@ -0,0 +1,35 @@ +require 'rails_helper' + +feature 'Posting' do + let(:user){ create(:user, :with_profile) } + let(:friend){ create(:user, :with_profile)} + + context 'logged in' do + before do + visit root_path + log_in(user) + end + scenario 'can post on own timeline' do + write_post(user) + expect{ click_button 'Post' }.to change(user.posts, :count).by(1) + end + scenario 'successful post refreshes timeline page with latest post' do + create_post(user) + expect(page).to have_content('The quick brown fox') + end + scenario 'cannot post anothers\' timeline' do + create_post(friend) + expect(page).to have_content 'Sorry, you can only post on your own timeline' + end + end + context 'logged out' do + scenario 'attempt to post redirects user to log in page' do + create_post(user) + expect(page).to have_content 'Log in to Danebook' + end + scenario 'tells user they can\'t post' do + create_post(user) + expect(page).to have_content 'You must log in to continue' + end + end +end diff --git a/spec/features/registration_spec.rb b/spec/features/registration_spec.rb new file mode 100644 index 000000000..aae20770f --- /dev/null +++ b/spec/features/registration_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +feature 'Registration' do + let(:user){ {:first_name => 'A', :last_name => 'B', :password => 'foobarfoobar'} } + before do + visit root_path + end + context 'with valid inputs' do + after do + ActiveJob::Base.queue_adapter.enqueued_jobs.clear + end + scenario 'can register when inputs are valid' do + expect{ sign_up(user) }.to change(User, :count).by(1) + end + scenario 'displays welcome message' do + sign_up(user) + expect(page).to have_content "Success! Welcome to Danebook," + end + end + context 'with invalid inputs' do + scenario 'displays error if registration form inputs are invalid' do + expect{ click_button 'Sign up'}.to change(User, :count).by(0) + expect(page).to have_content 'We couldn\'t sign you up.' + end + end +end diff --git a/spec/features/update_profile_spec.rb b/spec/features/update_profile_spec.rb new file mode 100644 index 000000000..213df0d23 --- /dev/null +++ b/spec/features/update_profile_spec.rb @@ -0,0 +1,26 @@ +require 'rails_helper' + +feature 'Update User Profile' do + let(:user){ create(:user, :with_profile)} + context 'logged in' do + before do + visit root_path + log_in(user) + visit user_profile_path(user) + click_link 'Edit Profile' + end + scenario 'clicking "edit profile" takes user to edit profile page' do + expect(page).to have_link('Edit Profile') + end + scenario 'can update profile information' do + fill_in 'Currently lives', with: 'Qwaszx' + click_button 'Save Changes' + expect(page).to have_content('Qwaszx') + end + end + context 'logged out' + scenario 'user cannot see "edit profile" link available' do + visit user_about_path(user) + expect(page).not_to have_content 'Edit Your Profile' + end +end diff --git a/spec/images/cover.png b/spec/images/cover.png new file mode 100644 index 000000000..6b98149f8 Binary files /dev/null and b/spec/images/cover.png differ diff --git a/spec/images/thumbnail_missing.png b/spec/images/thumbnail_missing.png new file mode 100644 index 000000000..6b98149f8 Binary files /dev/null and b/spec/images/thumbnail_missing.png differ diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb new file mode 100644 index 000000000..9b7f220e1 --- /dev/null +++ b/spec/mailers/previews/user_mailer_preview.rb @@ -0,0 +1,6 @@ +# Preview all emails at http://localhost:3000/rails/mailers/user_mailer +class UserMailerPreview < ActionMailer::Preview + def welcome + UserMailer.welcome(User.last) + end +end diff --git a/spec/mailers/user_mailer_spec.rb b/spec/mailers/user_mailer_spec.rb new file mode 100644 index 000000000..9550ceed8 --- /dev/null +++ b/spec/mailers/user_mailer_spec.rb @@ -0,0 +1,62 @@ +require "rails_helper" + +describe UserMailer, type: :mailer do + let(:user){ create(:user, :with_profile, :with_welcome_email)} + let(:comment){ create(:comment, :for_post, user: user)} + let(:mail){ ActionMailer::Base.deliveries} + let(:friends){ create_list(:user, 3, :with_profile)} + + describe 'welcome email' do + context 'without friends' do + it 'creates an email' do + user + expect(mail.count).to eq(1) + end + end + context 'with friends' do + before do + user.email = 'test@gmail.com' + user.friendees << friends + UserMailer.welcome(user).deliver + end + + it 'should have the right recipient' do + expect(mail.last.to.first).to eq('test@gmail.com') + end + it 'should have the correct subject line' do + expect(mail.last.subject).to eq('Welcome to Danebook!') + end + it 'should be sent from the correct address' do + expect(mail.last.from).to eq(['no-reply@danebook.com']) + end + it 'should include friends' do + expect(mail.last.body.encoded).to match(friends.first.full_name) + end + it 'should have link to friend\'s page' do + expect(mail.last.body.encoded).to match(user_profile_url(friends.first)) + end + end + end + describe 'comment notification email' do + before do + user.email = 'test@gmail.com' + UserMailer.comment_notification(comment).deliver + end + it 'should have the right recipient' do + expect(mail.last.to.first).to eq(comment.commentable.user.email) + end + it 'should have the right subject line' do + expect(mail.last.subject).to eq("New Comment from #{comment.user.full_name}") + end + it 'should be sent from the correct address' do + expect(mail.last.from).to eq(['no-reply@danebook.com']) + end + it 'should contain the comment body' do + expect(mail.last.body.encoded).to have_text(comment.body) + end + it 'should have a link to the commenter\'s profile page' do + expect(mail.last.body.encoded).to match(user_profile_url(comment.user)) + end + end + +end diff --git a/spec/models/activity_spec.rb b/spec/models/activity_spec.rb new file mode 100644 index 000000000..1937cbb21 --- /dev/null +++ b/spec/models/activity_spec.rb @@ -0,0 +1,41 @@ +require 'rails_helper' + +describe Activity do + let(:activity){ build(:activity)} + let(:user){ create(:user, :with_friends)} + context 'instance methods' do + describe '#to_text' do + it 'returns the correct string' do + expect(build(:activity, :for_post).to_text).to eq('Created a post') + expect(build(:activity, activable: build(:comment, :for_post)).to_text).to eq('Wrote a comment') + expect(build(:activity, :for_photo).to_text).to eq('Uploaded a photo') + expect(build(:activity, activable: build(:like, :for_post)).to_text).to eq('Liked a post') + end + end + end + context 'class methods' do + describe 'recently_active' do + it 'does not throw an error if argument missing' do + expect{Activity.recently_active}.not_to raise_error + end + it 'returns activities in reverse chronological order' do + user.friendees.each do |f| + create(:activity, :for_post, user: f) + end + expect(Activity.recently_active(user).first.created_at).to be > (Activity.recently_active(user).last.created_at) + end + end + describe 'newsfeed' do + it 'does not throw error if argument missing' do + expect{ Activity.newsfeed}.not_to raise_error + end + it 'returns posts in reverse chronological order' do + user.friendees.each do |f| + create(:activity, :for_post, user: f) + end + expect(Activity.newsfeed(user).first.created_at).to be > (Activity.recently_active(user).last.created_at) + end + end + end + +end diff --git a/spec/models/comment_like_spec.rb b/spec/models/comment_like_spec.rb new file mode 100644 index 000000000..f5ec7d638 --- /dev/null +++ b/spec/models/comment_like_spec.rb @@ -0,0 +1,21 @@ +require 'rails_helper' + +describe Like do + let(:user){ build(:user, :with_profile)} + let(:comment){ build(:comment, :for_post)} + let(:like){ build(:like)} + let(:posting){ build(:post) } + context 'validations' do + it 'does not allow duplicate likes' do + like = create(:like, user: user, likeable: comment) + expect{ create(:like, user: user, likeable: comment)}.to raise_error(ActiveRecord::RecordInvalid) + end + end + context 'associations' do + it 'automatically updates like counts on parent ' do + expect{ create_list(:like, 2, likeable: comment)}.to change(comment, :likes_count).by(2) + comment.likes.destroy_all + expect{ comment.reload }.to change(comment, :likes_count).by(-2) + end + end +end diff --git a/spec/models/comment_spec.rb b/spec/models/comment_spec.rb new file mode 100644 index 000000000..0ad50c633 --- /dev/null +++ b/spec/models/comment_spec.rb @@ -0,0 +1,67 @@ +require 'rails_helper' + +describe Comment do + let(:comment){ create(:comment, :for_post)} + let(:like){ create(:comment_like, comment: comment)} + let(:user){ create(:user, :with_profile)} + let(:friend){ build(:user ,:with_profile)} + let(:posting){ create(:post, user: user)} + context 'validations' do + it 'is invalid without body' do + comment.body = nil + expect(comment).to be_invalid + end + it 'is invalid without user' do + comment.user = nil + expect(comment).to be_invalid + end + end + it 'likes count is zero by default' do + expect(comment.likes_count).to eq(0) + end + + context '#liked_by?' do + it 'correctly tells us if comment is liked by a user' do + user = create(:user, id: 77) + like = create(:like, user_id: 77, likeable: comment) + comment.likes << like + expect(comment.liked_by?(like.user)).to eq(true) + expect(comment.liked_by?(build(:user))).to eq(false) + end + end + context 'associations' do + it 'responds to comment_likes' do + expect(comment).to respond_to(:likes) + end + end + describe 'notification emails' do + context 'delayed emails on' do + before do + Rails.application.secrets.use_delayed_emails = 'true' + posting + friend + end + it 'queues a notification email if comment created by friend' do + + expect{create(:comment, :with_notification_email, user: friend, commentable: posting)}.to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :count).by(1) + end + it 'does not queue a notification email if comment created by self' do + expect{ create(:comment, :with_notification_email, user: user, commentable: posting)}.not_to change(ActiveJob::Base.queue_adapter.enqueued_jobs, :count) + end + end + end + context 'delayed emails off' do + before do + Rails.application.secrets.use_delayed_emails = nil + posting + friend + end + it 'sends a notification email if comment created by friend' do + expect{create(:comment, :for_post, :with_notification_email, user: friend)}.to change(ActionMailer::Base.deliveries, :count).by(1) + end + it 'does not send a notification email if comment created by self' do + expect{ create(:comment, :with_notification_email, commentable: posting, user: user)}.not_to change(ActionMailer::Base.deliveries, :count) + end + end + +end diff --git a/spec/models/friendship_spec.rb b/spec/models/friendship_spec.rb new file mode 100644 index 000000000..203010b51 --- /dev/null +++ b/spec/models/friendship_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +describe Friendship do + let(:user){ create(:user, :with_profile)} + context 'validations' do + it 'raises error if friendship already exists' do + friend = create(:user) + expect{user.friendee_ids = ([friend.id, friend.id])}.to raise_error(ActiveRecord::RecordInvalid) + end + it 'does not raise error if friendship doesn\'t already exist' do + friend = create(:user) + expect{user.friendee_ids = [friend.id]}.not_to raise_error + end + it 'raises error if user tries to friend self' do + expect{user.friendee_ids = [user.id]}.to raise_error(ActiveRecord::RecordInvalid) + end + end + + +end diff --git a/spec/models/photo_spec.rb b/spec/models/photo_spec.rb new file mode 100644 index 000000000..ecf021a01 --- /dev/null +++ b/spec/models/photo_spec.rb @@ -0,0 +1,50 @@ +require 'rails_helper' + +describe Photo do + let(:photo){ create(:photo)} + let(:friend){ create(:user, :with_profile)} + context 'validations' do + it 'is valid with default atts' do + expect(photo).to be_valid + end + it 'invalid without image' do + photo.image = nil + expect(photo).to be_invalid + end + it 'is invalid without user' do + photo.user = nil + expect(photo).to be_invalid + end + it 'does not allow non-image files' do + photo.image = Rack::Test::UploadedFile.new(Rails.root.join('README.md'), 'text/markdown') + expect(photo).to be_invalid + end + end + context 'instance methods' do + describe '#upload_date' do + it 'returns a properly formatted upload date' do + photo.image_updated_at = Date.new(2017,1,12) + expect(photo.upload_date).to eq('12 January 2017') + end + end + describe '#liked_by?' do + it 'returns true/false correctly' do + photo.likers << friend + expect(photo.liked_by?(friend)).to eq(true) + expect(photo.liked_by?(create(:user, :with_profile))).to eq(false) + end + end + + end + context 'associations' do + it 'responds to image' do + expect(photo).to respond_to(:image) + end + it 'responds to likes' do + expect(photo).to respond_to(:likes) + end + it 'responds to comments' do + expect(photo).to respond_to(:comments) + end + end +end diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb new file mode 100644 index 000000000..f77eccc71 --- /dev/null +++ b/spec/models/post_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +describe Post do + let(:post){ create(:post)} + let(:user){ create(:user)} + context 'validations' do + it 'is valid with body and user' do + expect(post).to be_valid + end + it 'is invalid without body' do + post.body = nil + expect(post).to be_invalid + end + it 'is invalid without user' do + post.user = nil + expect(post).to be_invalid + end + end + context 'associations' do + it 'responds to likes' do + expect(post).to respond_to(:likes) + end + it 'responds to comments' do + expect(post).to respond_to(:comments) + end + end + context '#liked_by?' do + it 'correctly tells us if post is liked by a user' do + post.likers << user + expect(post.liked_by?(user)).to eq(true) + expect(post.liked_by?(create(:user))).to eq(false) + end + end + context 'class methods' do + describe 'newsfeed_posts' do + let(:user){ create(:user, :with_friends)} + before do + user.friendees.each do |f| + f.posts << create(:post, user: f) + end + end + it 'returns posts in reverse chronological order' do + expect(Post.newsfeed_posts(user).first.created_at).to be > (Post.newsfeed_posts(user).last.created_at) + end + it 'does not throw an error if argument missing' do + expect{Post.newsfeed_posts}.not_to raise_error + end + + end + end +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb new file mode 100644 index 000000000..7da640ad6 --- /dev/null +++ b/spec/models/user_spec.rb @@ -0,0 +1,137 @@ +require 'rails_helper' + +describe User do + let(:user){ build(:user)} + let(:full_user){ build(:user, :with_profile)} + it 'is valid with default attributes' do + expect(user).to be_valid + end + it 'saves with default attributes' do + expect{user.save}.not_to raise_error + end + it 'is invalid without email' do + user.email = nil + expect(user).to be_invalid + end + it 'is invalid without password' do + user.password = nil + expect(user).to be_invalid + end + it 'is invalid if email is fewer than 6 chars' do + user.email = 'xxx' + expect(user).to be_invalid + end + it 'is invalid if password password is fewer than 12 chars' do + user.password = 'xxx' + expect(user).to be_invalid + end + it 'does not allow duplicate emails' do + u = create(:user, email: 'a@a.com') + copycat = build(:user, email: 'a@a.com') + expect(copycat).to be_invalid + end + it 'is invalid without first or last name' do + expect(build(:user, first_name: nil)).to be_invalid + expect(build(:user, last_name: nil)).to be_invalid + expect(build(:user, first_name: nil, last_name: nil)).to be_invalid + end + + describe 'class methods' do + describe 'search' do + it 'returns the correct search results' do + create(:user, first_name: 'afa') + create(:user, first_name: 'faa') + create(:user, last_name: 'aaf') + expect(User.search('f').count).to eq(3) + expect(User.search('g').count).to eq(0) + end + end + end + + describe 'instance methods' do + let(:user){ create(:user, :with_profile)} + let(:friend){ create(:user, :with_profile)} + def add_friend(user, friend) + user.friendees << friend + end + def add_rejected_friend(user, friend) + user.friendees << friend + f = Friendship.last.update(rejected: true) + end + describe '#full_name' do + it 'returns a user\'s full name' do + expect(user.full_name).to eq(user.first_name + ' ' + user.last_name) + end + end + + describe '#friendship_status' do + it 'returns "create" if user not logged in' do + expect(friend.friendship_status(user)).to eq('create') + end + context 'logged in' do + before do + login_as(user, user: :user) + end + it 'returns nil if request not accepted' do + create(:friendship, friender_id: user.id, friendee_id: friend.id, rejected: nil) + expect(friend.friendship_status(user)).to be_nil + end + it 'returns true if request rejected' do + create(:friendship, friender_id: user.id, friendee_id: friend.id, rejected: true) + expect(friend.friendship_status(user)).to eq(true) + end + it 'returns friends if request accepted' do + user = create(:user, :with_accepted_friend_request) + friend = user.friendees.last + expect(friend.friendship_status(user)).to eq(false) + end + it "returns 'create' if no friendship record" do + expect(friend.friendship_status(user)).to eq('create') + end + end + end + end + + context 'associations' do + it 'responds to posts associations' do + expect(user).to respond_to(:posts) + end + it 'responses to likes associations' do + expect(user).to respond_to(:likes) + end + it 'responeds to comments associations' do + expect(user).to respond_to(:comments) + end + it 'responds to comment_likes associations' do + expect(user).to respond_to (:comment_likes) + end + it 'responds to initiated_friendships' do + expect(user).to respond_to (:initiated_friendships) + end + end + + describe 'emails' do + let(:queue){ ActiveJob::Base.queue_adapter.enqueued_jobs } + context 'delayed emails on' do + before do + Rails.application.secrets.use_delayed_emails = 'true' + end + it 'queues a welcome email on creation' do + expect{ create(:user, :with_profile, :with_welcome_email)}.to change(queue, :count).by(1) + end + end + context 'immediate email notification' do + before do + Rails.application.secrets.use_delayed_emails = nil + end + it 'sends a welcome email on creation' do + expect{ create(:user, :with_profile, :with_welcome_email)}.to change(ActionMailer::Base.deliveries, :count).by(1) + end + end + end + + + + + +end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb new file mode 100644 index 000000000..e38d7219d --- /dev/null +++ b/spec/rails_helper.rb @@ -0,0 +1,75 @@ +# 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' +require 'pry' + +# 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| + # Remove this line if you're not using ActiveRecord or ActiveRecord fixtures + # config.fixture_path = "#{::Rails.root}/spec/fixtures" + config.include FactoryGirl::Syntax::Methods + config.include LoginMacros + config.include PostMacros + config.include Warden::Test::Helpers + config.include ActionDispatch::TestProcess::FixtureFile + config.include ActiveJob::TestHelper + + config.before :suite do + Warden.test_mode! + end + + config.after(:suite) do + FileUtils.rm_rf(Dir["#{Rails.root}/spec/test_files/"]) + end + + # 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/requests/.DS_Store b/spec/requests/.DS_Store new file mode 100644 index 000000000..5008ddfcf Binary files /dev/null and b/spec/requests/.DS_Store differ diff --git a/spec/requests/comment_request_spec.rb b/spec/requests/comment_request_spec.rb new file mode 100644 index 000000000..2cb3ac091 --- /dev/null +++ b/spec/requests/comment_request_spec.rb @@ -0,0 +1,37 @@ +require 'rails_helper' + +describe 'CommentsRequests' do + let(:user){ create(:user, :with_profile)} + let(:posting){ create(:post, user: user)} + let(:comment){ create(:comment, :for_post)} + before do + def create_comment(user) + post post_comments_path(posting), params: { comment: attributes_for(:comment, user: user) } + end + end + describe 'POST #create' do + it 'requires logged in user' do + # works in dev env. + expect{ post post_comments_path(posting) }.not_to change(Comment, :count) + expect(response).to redirect_to new_user_session_path + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'creates a comment' do + expect{ create_comment(user) }.to change(Comment, :count).by(1) + end + end + end + describe 'DELETE #destroy' do + it 'requires user to be logged in' do + comment + expect{ delete comment_path(comment)}.not_to change(Comment, :count) + end + it 'can only delete own comment' do + comment = create(:comment, :for_post, user: create(:user)) + expect{ delete comment_path(comment)}.not_to change(Comment, :count) + end + end +end diff --git a/spec/requests/friendships_request_spec.rb b/spec/requests/friendships_request_spec.rb new file mode 100644 index 000000000..68edd9eb8 --- /dev/null +++ b/spec/requests/friendships_request_spec.rb @@ -0,0 +1,92 @@ +require 'rails_helper' + +describe 'FriendshipsRequest', type: :request do + let(:user){ create(:user, :with_profile)} + let(:friend){ create(:user, :with_profile)} + let(:send_friend_request){post user_friends_path(friend) } + let(:friend_request_received){ create(:friendship, friender_id: friend.id, friendee_id: user.id) } + let(:friend_request_sent){ create(:friendship, friender_id: user.id, friendee_id: friend.id)} + + describe 'POST #create' do + it 'requires logged in user' do + send_friend_request + expect(flash[:error]).not_to be_nil + expect(response).to redirect_to new_user_session_path + end + + context 'logged in' do + + before do + login_as(user, scope: :user) + send_friend_request + end + + it 'sends a friend invite' do + expect(flash[:success]).not_to be_nil + end + it 'redirects to friend profile' do + expect(response).to redirect_to user_profile_path(friend) + end + end + end + + describe 'PATCH #accept' do + it 'requires logged in user' do + patch accept_friend_path(friend) + expect(response).to redirect_to new_user_session_path + end + context 'logged in' do + before do + login_as(user, scope: :user) + friend_request_received + end + it 'updates user and friend\'s friendship status' do + expect{ patch accept_friend_path(friend) }.to change(Friendship, :count).by(1) + expect(Friendship.first.rejected).to be false + expect(Friendship.second.rejected).to be false + end + it 'updates a user\'s friend count' do + patch accept_friend_path(friend) + user.reload + expect(user.friendships_count).to eq(1) + end + end + end + + describe 'PATCH #reject' do + it 'requires logged in user' do + patch reject_friend_path(friend) + expect(response).to redirect_to new_user_session_path + end + context 'logged in' do + before do + login_as(user, scope: :user) + friend_request_received + end + it 'rejects a friend request' do + expect{ patch reject_friend_path(friend)}.not_to change(Friendship, :count) + expect(Friendship.first.rejected).to be true + end + end + end + + describe 'PATCH #cancel' do + it 'requires logged in user' do + friend_request_sent + patch cancel_friend_path(friend) + expect(response).to redirect_to new_user_session_path + end + context 'logged in' do + before do + login_as(user, scope: :user) + friend_request_sent + end + it 'cancels a friend request' do + expect{ patch cancel_friend_path(friend) }.to change(Friendship, :count).by(-1) + end + end + end + + + +end diff --git a/spec/requests/likes_request_spec.rb b/spec/requests/likes_request_spec.rb new file mode 100644 index 000000000..7b4686ef0 --- /dev/null +++ b/spec/requests/likes_request_spec.rb @@ -0,0 +1,38 @@ +require 'rails_helper' +require 'pry' + +describe 'LikesRequests' do + let(:user){ create(:user, :with_profile) } + let(:posting){ create(:post)} + let(:like){ create(:like, :for_post, likeable: posting, user: user)} + describe 'POST #create' do + it 'must be logged in to like' do + expect { post post_likes_path(posting)}.to change(posting, :likes_count).by(0) + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'creates a like for posts' do + expect{ post post_likes_path(posting)}.to change(Like, :count ).by(1) + end + end + end + describe 'DELETE #destroy' do + it 'must be logged in to destroy' do + like + expect{ delete post_like_path(posting, like) }.to change(Like, :count).by(0) + end + context 'logged in' do + before do + login_as(user, scope: :user) + like + end + it 'destroys like' do + expect{ delete post_like_path(posting, like) }.to change(Like, :count).by(-1) + expect{ posting.reload }.to change(posting, :likes_count).by(-1) + end + end + end + +end diff --git a/spec/requests/medium_missing.png b/spec/requests/medium_missing.png new file mode 100644 index 000000000..2e60d7824 Binary files /dev/null and b/spec/requests/medium_missing.png differ diff --git a/spec/requests/newsfeed_request_spec.rb b/spec/requests/newsfeed_request_spec.rb new file mode 100644 index 000000000..123ef4b2e --- /dev/null +++ b/spec/requests/newsfeed_request_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +describe 'NewsfeedRequests' do + let(:user){ create(:user, :with_profile, :with_friends)} + describe 'GET #index' do + context 'logged out' do + it 'redirects to sign in page' do + get newsfeed_path + expect(response).to redirect_to new_user_session_path + end + end + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'loads the newsfeed page' do + get newsfeed_path + expect(response).to be_success + end + end + +end diff --git a/spec/requests/photos_request_spec.rb b/spec/requests/photos_request_spec.rb new file mode 100644 index 000000000..dbc8ca4d3 --- /dev/null +++ b/spec/requests/photos_request_spec.rb @@ -0,0 +1,109 @@ +require 'rails_helper' + +describe 'PhotosRequests' do + let(:user){ create(:user, :with_profile)} + let(:friend){ create(:user, :with_profile)} + let(:image){ fixture_file_upload(Rails.root.join('spec/requests/medium_missing.png'), 'image/png')} + let(:photo_upload){ post user_photos_path(user), params: { photo: { image: image}} } + let(:friend_photo){ create(:photo, user: friend)} + let(:photo){ create(:photo, user: user)} + + + describe 'POST #create' do + it 'redirects user if not logged in' do + user + expect{ photo_upload }.not_to change(Photo, :count) + expect(response).to redirect_to user_photos_path(user) + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'creates a new photo' do + expect{ photo_upload }.to change(Photo, :count).by(1) + end + it 'redirects user to all their photos' do + photo_upload + expect(response).to redirect_to user_photos_path(user) + end + it 'redirects to upload path if upload fails' do + post user_photos_path(user) + expect(response).to redirect_to upload_user_photos_path(user) + expect(flash[:error]).not_to be_nil + end + + end + end + + describe 'GET #new' do + it 'redirects to photos index if not logged in' do + get upload_user_photos_path(user) + expect(response).to redirect_to user_photos_path(user) + end + + context 'logged in' do + before do + login_as(user, scope: :user) + end + + it 'redirects to photos index if is not current user' do + friend = create(:user, :with_profile) + get upload_user_photos_path(friend) + expect(response).to redirect_to user_photos_path(friend) + end + + it 'loads if is current user' do + get upload_user_photos_path(user) + expect(response).to be_success + end + end + end + + + describe 'GET #show' do + it 'requires login' do + get photo_path(photo) + expect(response).to have_http_status(302) + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'only friends can view' do + get photo_path(friend_photo) + expect(response).to have_http_status(302) + end + end + end + + describe 'DELETE #destroy' do + + it 'requires current user' do + friend_photo + expect{ delete photo_path(friend_photo) }.not_to change(Photo, :count) + expect(response).to redirect_to new_user_session_path + end + + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'destroys a user\'s photo' do + photo + expect{ delete photo_path(photo) }.to change(Photo, :count).by(-1) + end + it 'cannot destroy a friend\'s photo' do + friend_photo + expect{ delete photo_path(friend_photo)}.not_to change(Photo, :count) + expect(response).to redirect_to photo_path(friend_photo) + end + it 'redirects user upon successful deletion' do + delete photo_path(photo) + expect(response).to redirect_to user_photos_path(user) + end + end + end + + + +end diff --git a/spec/requests/post_request_spec.rb b/spec/requests/post_request_spec.rb new file mode 100644 index 000000000..02b3d6df9 --- /dev/null +++ b/spec/requests/post_request_spec.rb @@ -0,0 +1,67 @@ +require 'rails_helper' + +describe 'PostsRequests' do + let(:user){ create(:user, :with_profile)} + def create_post(user) + post user_posts_path(user), params: {post: attributes_for(:post, user: user)} + end + def create_invalid_post(user) + post user_posts_path(user), params: {post: attributes_for(:post, user: user, body: nil)} + end + describe 'GET #index' do + it 'does not require login to show posts' do + get user_profile_path(user) + expect(response).to be_success + end + end + describe 'POST #create' do + context 'logged out' do + it 'requires login' do + create_post(user) + expect(response).to have_http_status(:redirect) + end + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'creates a post' do + expect{ create_post(user) }.to change(user.posts, :count).by(1) + end + it 'create invalid post' do + expect{ create_invalid_post(user) }.not_to change(user.posts, :count) + end + it 'refreshes the page on post creation' do + create_post(user) + expect(response).to have_http_status(:redirect) + end + it 'does not allow posting on another person\'s timeline' do + friend = create(:user) + expect{ create_post(friend) }.not_to change(friend.posts, :count) + end + end + end + describe 'DELETE #destroy' do + context 'logged out' do + it 'does not destroy posts' do + post = create(:post, user: user) + expect{ delete user_post_path(user, post) }.not_to change(user.posts, :count) + expect(flash[:error]).not_to be_nil + end + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'destroys own posts' do + post = create(:post, user: user) + expect{ delete user_post_path(user, post)}.to change(user.posts, :count).by(-1) + end + it 'does not destroy another\'s post' do + friend = create(:user) + post = create(:post, user: friend) + expect{ delete user_post_path(user, post)}.not_to change(friend.posts, :count) + end + end + end +end diff --git a/spec/requests/profiles_request_spec.rb b/spec/requests/profiles_request_spec.rb new file mode 100644 index 000000000..18d1f7e8c --- /dev/null +++ b/spec/requests/profiles_request_spec.rb @@ -0,0 +1,22 @@ +require 'rails_helper' +require 'pry' + +describe 'ProfilesRequest' do + let(:user){ create(:user, :with_profile) } + let(:profile_atts){ attributes_for(:profile, id: user.profile.id, quote: 'This is a quote')} + context 'logged_in' do + before do + login_as(user, scope: :user) + end + describe 'PATCH #update' do + it 'can update' do + + # patch user_path(user), params: {user: attributes_for(:user, profile_attributes: profile_atts) } + patch user_path(user), params: {user: attributes_for(:user, profile_attributes: attributes_for(:profile, id: user.profile.id, quote: 'This is a quote'))} + user.reload + + expect(user.profile.quote).to eq('This is a quote') + end + end + end +end diff --git a/spec/requests/sessions_request_spec.rb b/spec/requests/sessions_request_spec.rb new file mode 100644 index 000000000..1fae2d59e --- /dev/null +++ b/spec/requests/sessions_request_spec.rb @@ -0,0 +1,7 @@ +require 'rails_helper' + +describe 'SessionsRequest' do + describe 'POST #create' do + + end +end diff --git a/spec/requests/users_request_spec.rb b/spec/requests/users_request_spec.rb new file mode 100644 index 000000000..d210028d8 --- /dev/null +++ b/spec/requests/users_request_spec.rb @@ -0,0 +1,62 @@ +require 'rails_helper' +require 'pry' + +describe 'UsersRequests' do + let(:user){ create(:user, :with_profile) } + let(:create_user){ post user_registration_path(user), params: {user: attributes_for(:user, profile_attributes: attributes_for(:profile))}} + describe 'POST #create' do + it 'creates a new user' do + expect{create_user}.to change(User, :count).by(1) + end + + end + describe 'PUT #update' do + context 'logged out' do + it 'cannot update' do + patch user_path(user), params: {user: attributes_for(:user, profile_attributes: attributes_for(:profile, quote: 'This is a quote'))} + user.reload + expect(user.profile.quote).not_to eq('This is a quote') + end + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'can update' do + user + patch user_path(user), params: { + user: attributes_for( + :user, + profile_attributes: attributes_for( + :profile, + id: user.profile.id, + quote: 'This is a quote'))} + user.reload + expect(user.profile.quote).to eq('This is a quote') + end + end + end + describe 'GET #show' do + it 'allows anyone to visit' do + get user_about_path(user) + expect(response).to be_success + end + end + + describe 'GET #index' do + it 'must be logged in to visit' do + get users_path, params: {q: 'blah'} + expect(response).to redirect_to new_user_session_path + end + context 'logged in' do + before do + login_as(user, scope: :user) + end + it 'responds to request' do + get users_path, params: {q: 'Foo'} + expect(response).to be_success + end + end + + end +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/friending_macros.rb b/spec/support/friending_macros.rb new file mode 100644 index 000000000..e69de29bb diff --git a/spec/support/login_macros.rb b/spec/support/login_macros.rb new file mode 100644 index 000000000..858d7c32a --- /dev/null +++ b/spec/support/login_macros.rb @@ -0,0 +1,29 @@ +module LoginMacros + def log_in(user) + within('.navbar-form') do + fill_in 'Email', with: user.email + fill_in 'Password', with: user.password + end + click_button 'Sign in' + end + + + def log_out + click_link "Sign out" + end + + def sign_up(user) + within '.new_user' do + fill_in 'First Name', with: user[:first_name] + fill_in 'Last Name', with: user[:last_name] + fill_in 'Email', with: "fatcat@fatcat.com" + fill_in 'Your New Password', with: user[:password] + fill_in 'Confirm Your Password', with: user[:password] + select('3', from: 'user[profile_attributes][birthdate(3i)]') + select('May', from: 'user[profile_attributes][birthdate(2i)]') + select('1980', from: 'user[profile_attributes][birthdate(1i)]') + choose('Male') + end + click_button 'Sign up' + end +end diff --git a/spec/support/post_macros.rb b/spec/support/post_macros.rb new file mode 100644 index 000000000..d70819190 --- /dev/null +++ b/spec/support/post_macros.rb @@ -0,0 +1,11 @@ +module PostMacros + def write_post(user) + visit user_profile_path(user) + fill_in 'post[body]', with: 'The quick brown fox' + end + def create_post(user) + visit user_profile_path(user) + fill_in 'post[body]', with: 'The quick brown fox' + click_button 'Post' + end +end diff --git a/spec/views/newsfeed/index_spec.rb b/spec/views/newsfeed/index_spec.rb new file mode 100644 index 000000000..8d4f32f42 --- /dev/null +++ b/spec/views/newsfeed/index_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +describe 'newsfeed/index.html.erb' do + let(:user){ build(:user, :with_profile, :with_friends)} + context 'logged in' do + before do + assign(:user, user) + def view.current_user + @user + end + def view.user_signed_in? + true + end + end + end +end diff --git a/spec/views/photos/index_spec.rb b/spec/views/photos/index_spec.rb new file mode 100644 index 000000000..0224adb7e --- /dev/null +++ b/spec/views/photos/index_spec.rb @@ -0,0 +1,35 @@ +require 'rails_helper' + +describe 'photos/index.html.erb' do + let(:user){ create(:user, :with_profile) } + context 'logged in' do + before do + assign(:user, user) + def view.current_user + @user + end + def view.user_signed_in? + true + end + end + it 'has button to upload photos if logged in' do + render + expect(view.content_for(:module_title)).to have_link('Add Photos') + end + end + context 'logged out' do + before do + assign(:user, user) + def view.current_user + nil + end + def view.user_signed_in? + false + end + end + it 'does not have button to upload photos' do + render + expect(rendered).not_to have_link('Add Photos') + end + end +end diff --git a/spec/views/photos/new_spec.rb b/spec/views/photos/new_spec.rb new file mode 100644 index 000000000..7fdf54e45 --- /dev/null +++ b/spec/views/photos/new_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +describe 'photos/new.html.erb' do + let(:user){ create(:user, :with_profile)} + context 'logged in' do + before do + assign(:user, user) + assign(:photo, Photo.new) + def view.current_user + @user + end + def view.user_signed_in? + true + end + end + it 'has button to add photo from web' do + render + expect(view.content_for(:module_body)).to match('Use Web Photo') + end + it 'has button to upload photo' do + render + expect(view.content_for(:module_body)).to match('Upload Photo') + end + end +end diff --git a/spec/views/photos/show_spec.rb b/spec/views/photos/show_spec.rb new file mode 100644 index 000000000..abd0bc682 --- /dev/null +++ b/spec/views/photos/show_spec.rb @@ -0,0 +1,54 @@ +require 'rails_helper' + +describe 'photos/show.html.erb' do + let(:user){ create(:user, :with_profile)} + let(:friend){ create(:user, :with_profile)} + let(:photo){ create(:photo, user: user)} + let(:posting){ create(:post, user: user)} + before do + assign(:user, user) + assign(:photo, photo) + assign(:comment, photo.comments.build) + def view.current_user + nil + end + def view.user_signed_in? + false + end + end + it 'displays poster\'s name' do + render + expect(view.content_for(:module_body)).to have_link(user.full_name) + end + it 'does not show action buttons' do + render + expect(rendered).not_to have_link('Set as Profile') + expect(rendered).not_to have_link('Set as Cover') + expect(rendered).not_to have_link('Delete Photo') + end + context 'logged in and current user' do + before do + assign(:user, friend) + assign(:current_user, user) + def view.current_user + @current_user + end + def view.user_signed_in? + true + end + end + it 'does not show action buttons on friend\'s photo page' do + render + expect(view.content_for(:module_body)).not_to have_link('Set as Profile') + expect(view.content_for(:module_body)).not_to have_link('Set as Cover') + expect(view.content_for(:module_body)).not_to have_link('Delete Photo') + end + it 'shows action buttons on own photo page' do + assign(:user, user) + render + expect(view.content_for(:module_body)).to have_link('Set as Profile') + expect(view.content_for(:module_body)).to have_link('Set as Cover') + expect(view.content_for(:module_body)).to have_link('Delete Photo') + end + end +end diff --git a/spec/views/posts/index_spec.rb b/spec/views/posts/index_spec.rb new file mode 100644 index 000000000..cc7304cbb --- /dev/null +++ b/spec/views/posts/index_spec.rb @@ -0,0 +1,31 @@ +require 'rails_helper' + +describe 'posts/index.html.erb' do + let(:user){ create(:user, :with_profile) } + let(:posting){ create(:post, user: user)} + context 'logged out' do + before do + assign(:user, user) + assign(:post, build(:post, user: user)) + def view.current_user + nil + end + def view.user_signed_in? + false + end + end + it 'shows the new form post' do + render + expect(view.content_for(:main)).to match('name="post\[body\]"') + + end + it 'shows a user\'s posts' do + assign(:posts, create_list(:post, 3, user: user)) + render + expect(view.content_for(:main)).to have_text(user.posts.last.body) + expect(view.content_for(:main)).to have_text(user.posts.first.body) + expect(view.content_for(:main)).to have_text(user.posts.second.body) + end + + end +end diff --git a/spec/views/posts/info_box_friends_spec.rb b/spec/views/posts/info_box_friends_spec.rb new file mode 100644 index 000000000..4dc333eb4 --- /dev/null +++ b/spec/views/posts/info_box_friends_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +describe 'posts/_info_box_friends.html.erb' do + let(:user){ create(:user, :with_profile, :with_accepted_friend_request)} + let(:friend){ create(:user, :with_profile)} + before do + user.reload + render partial: 'posts/info_box_friends', locals: { user: user, friends: user.friendees} + end + it 'shows the correct number of friends' do + create(:friendship, :accepted, friender_id: user.id) + user.reload + render partial: 'posts/info_box_friends', locals: { user: user, friends: user.friendees} + expect(rendered).to have_content('Friends (2)') + end + it 'shows friends' do + friends = user.friendees + expect(rendered).to have_content(friends.first.full_name) + end +end diff --git a/spec/views/posts/info_box_photos_spec.rb b/spec/views/posts/info_box_photos_spec.rb new file mode 100644 index 000000000..23f7ab22e --- /dev/null +++ b/spec/views/posts/info_box_photos_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +describe 'posts/_info_box_photos.html.erb' do + let(:user){ build(:user, :with_profile)} + let(:photos){ create_list(:photo, 3, user: user)} + it 'displays the correct number of photos' do + render partial: 'posts/info_box_photos', locals: { photos: photos} + expect(rendered).to have_text('Photos (3)') + end + it 'says there are no photos if user has none' do + render partial: 'posts/info_box_photos', locals: { photos: []} + expect(rendered).to have_text('no photos yet') + end + it 'has link to see more photos if user has more than 9 photos' do + user.save + photos = create_list(:photo, 10, user: user) + render partial: 'posts/info_box_photos', locals: { photos: photos, user: user} + expect(rendered).to have_link('See More Photos') + end +end diff --git a/spec/views/posts/post_spec.rb b/spec/views/posts/post_spec.rb new file mode 100644 index 000000000..38d1d5d4a --- /dev/null +++ b/spec/views/posts/post_spec.rb @@ -0,0 +1,52 @@ +require 'rails_helper' + +describe 'shared/_post.html.erb' do + let(:user){ create(:user, :with_profile)} + let(:friend){ create(:user, :with_profile)} + let(:popular_post){ create(:post, :with_likes, likes_count: 5, )} + let(:post){ create(:post, user: create(:user, :with_profile))} + before do + def view.current_user + nil + end + def view.user_signed_in? + false + end + end + it 'displays the total number of likes' do + popular_post + render partial: 'shared/post', locals: {post: popular_post} + expect(rendered).to have_content('5 likes') + end + it 'displays the first few names of those who liked the post' do + post.likers << [user, friend] + post.reload + render partial: 'shared/post', locals: {post: post} + expect(rendered).to have_content(friend.first_name) + expect(rendered).to have_content(user.first_name) + end + context 'logged in' do + before do + assign(:user, user) + def view.current_user + @user + end + def view.user_signed_in? + true + end + end + it 'displays the unlike link if post is liked by user' do + post.likers << [user, friend] + post.reload + render partial: 'shared/post', locals: {post: post} + expect(rendered).to have_content("You and #{friend.full_name}") + end + it 'has link to photo page if post is a photo' do + post = create(:photo, user: user) + render partial: 'shared/post', locals: {post: post} + expect(rendered).to match(photo_path(post)) + end + end + + +end diff --git a/spec/views/shared/nav_bar_spec.rb b/spec/views/shared/nav_bar_spec.rb new file mode 100644 index 000000000..24a906812 --- /dev/null +++ b/spec/views/shared/nav_bar_spec.rb @@ -0,0 +1,42 @@ +require 'rails_helper' + +describe 'shared/_nav_bar.html.erb' do + let(:user){ create(:user, :with_profile)} + let(:friend){ create(:user, :with_profile)} + let(:request_received){ create(:friendship, friender_id: friend.id, friendee_id: user.id)} + context 'logged in' do + before do + assign(:user, user) + def view.user_signed_in? + true + end + def view.current_user + @user + end + end + it 'shows user\'s first name' do + render + expect(rendered).to have_link(user.first_name) + end + it 'shows accept and reject options if friend requuest received' do + request_received + render + expect(rendered).to have_link(user.first_name) + expect(rendered).to have_link('Accept') + expect(rendered).to have_link('Reject') + end + end + context 'logged out' do + before do + def view.user_signed_in? + false + end + end + it 'shows login form ' do + assign(:user, user) + render + expect(rendered).to have_button('Sign in') + end + end + +end diff --git a/spec/views/shared/profile_header_spec.rb b/spec/views/shared/profile_header_spec.rb new file mode 100644 index 000000000..808317c10 --- /dev/null +++ b/spec/views/shared/profile_header_spec.rb @@ -0,0 +1,76 @@ +require 'rails_helper' + +describe 'shared/_profile_header.html.erb' do + let(:user){ create(:user, :with_profile) } + + before do + def view.current_user + nil + end + def view.user_signed_in? + false + end + assign(:user, user) + end + it 'shows the user\'s name' do + render + expect(render).to have_content(user.full_name) + end + it 'shows button do add friend' do + render + expect(render).to have_content('Add Friend') + end + it 'has links to user\'s other pages' do + render + expect(rendered).to match(user_profile_path(user)) + expect(rendered).to match(user_about_path(user)) + expect(rendered).to match(user_photos_path(user)) + expect(rendered).to match(user_friends_path(user)) + end + + context 'logged in' do + let(:friend){ create(:user, :with_profile)} + let(:user){ create(:user, :with_profile)} + let(:request_received){ create(:friendship, friender_id: friend.id, friendee_id: user.id )} + let(:request_sent){ create(:friendship, :pending, friender_id: user.id, friendee_id: friend.id) } + before do + assign(:user, friend) + @current_user = user + def view.current_user + @current_user + end + def view.user_signed_in? + true + end + end + it 'shows button to add friend' do + render + expect(rendered).to have_content('Add Friend') + end + it 'shows button to remove friend if friends' do + create(:friendship, :accepted, friender_id: user.id, friendee_id: friend.id) + render + expect(rendered).to have_content('Remove Friend') + end + it 'shows button to cancel friend request if one has been sent' do + request_sent + render + expect(rendered).to have_content('Cancel Request') + end + it 'shows button to accept request if one has been received' do + request_received + render + expect(rendered).to have_content('Accept Request') + end + it 'does not show friend action button if on own page' do + def view.current_user + @user + end + render + expect(rendered).not_to have_content('Add Friend') + expect(rendered).not_to have_content('Remove Friend') + expect(rendered).not_to have_content('Cancel Request') + end + end + +end diff --git a/spec/views/users/index_spec.rb b/spec/views/users/index_spec.rb new file mode 100644 index 000000000..a49e8fb41 --- /dev/null +++ b/spec/views/users/index_spec.rb @@ -0,0 +1,23 @@ +require 'rails_helper' + +describe 'users/index.html.erb' do + let(:user){ create(:user, :with_profile)} + before do + assign(:user, user) + assign(:term, 'f') + def view.current_user + @user + end + def view.user_signed_in? + true + end + end + it 'shows the correct number of search results' do + assign(:users, [create(:user, :with_profile, first_name: 'afa'), create(:user, :with_profile, last_name: 'faa'), create(:user, :with_profile, first_name: 'aaf')]) + render + expect(rendered).to have_content('Showing 3 users') + assign(:users, []) + render + expect(rendered).to have_content('Showing 0 users') + end +end diff --git a/spec/views/users/show_spec.rb b/spec/views/users/show_spec.rb new file mode 100644 index 000000000..e8481c14c --- /dev/null +++ b/spec/views/users/show_spec.rb @@ -0,0 +1,45 @@ +require 'rails_helper' + +describe "users/show.html.erb" do + let(:user){ create(:user, :with_profile) } + context 'logged in' do + before do + assign(:user, user) + def view.user_signed_in? + true + end + def view.current_user + @user + end + end + it 'can see button to edit profile' do + render + expect(view.content_for(:module_title)).to have_text('Edit Your Profile') + end + it 'cannot see button to edit profile if is not current user' do + @not_current = create(:user, :with_profile) + @user = create(:user, :with_profile) + def view.current_user + @not_current + end + assign(:user, create(:user, :with_profile)) + render + expect(view.content_for(:module_title)).not_to have_text('Edit Your Profile') + end + end + context 'logged out' do + before do + assign(:user, user) + def view.user_signed_in? + false + end + def view.current_user + nil + end + end + it 'can\'t see button to edit profile' do + render + expect(view.content_for(:module_title)).not_to have_text('Edit Your Profile') + end + 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