├── log └── .keep ├── app ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── concerns │ │ └── .keep │ ├── restart command.txt │ ├── vote.rb │ ├── image.rb │ ├── comment.rb │ ├── post.rb │ └── user.rb ├── assets │ ├── images │ │ ├── .keep │ │ ├── logo.png │ │ ├── downvote.png │ │ ├── logo-2.png │ │ ├── senate.jpg │ │ ├── upload.png │ │ ├── upvote.png │ │ ├── loading-2.gif │ │ ├── Gir_dogsuit.png │ │ ├── github_icon.png │ │ ├── linkedin_icon.png │ │ ├── mck-loading.gif │ │ ├── resume_icon.png │ │ ├── search_icon.png │ │ ├── upvote-hollow.png │ │ ├── downvote-hollow.png │ │ ├── upload-giraffe.png │ │ ├── upload-pointer.png │ │ └── gir-back-to-show.png │ ├── repo_images │ │ ├── Comment.png │ │ ├── Upvote.png │ │ ├── new_post.png │ │ ├── Comment-reply.png │ │ ├── Login_Signup.png │ │ ├── New Comment.png │ │ ├── Sign-In Page.png │ │ ├── sign_in_modal.png │ │ ├── upload_modal.png │ │ ├── Image_show_page.png │ │ ├── Slide_Out_Menu.png │ │ └── User_Page_Column.png │ ├── stylesheets │ │ ├── static_pages.scss │ │ ├── components │ │ │ ├── delete_button.scss │ │ │ ├── back_to_top.scss │ │ │ ├── sidebar.scss │ │ │ ├── index.scss │ │ │ ├── login_buttons.scss │ │ │ ├── drop_down.scss │ │ │ ├── search.scss │ │ │ ├── navbar.scss │ │ │ └── user.scss │ │ └── application.scss │ └── javascripts │ │ ├── api │ │ ├── images.coffee │ │ ├── posts.coffee │ │ ├── search.coffee │ │ ├── users.coffee │ │ └── comments.coffee │ │ ├── static_pages.coffee │ │ └── application.js ├── controllers │ ├── concerns │ │ └── .keep │ ├── static_pages_controller.rb │ ├── api │ │ ├── search_controller.rb │ │ ├── users_controller.rb │ │ ├── sessions_controller.rb │ │ ├── images_controller.rb │ │ ├── comments_controller.rb │ │ ├── posts_controller.rb │ │ └── votes_controller.rb │ └── application_controller.rb ├── views │ ├── api │ │ ├── posts │ │ │ ├── new.json.jbuilder │ │ │ ├── show.json.jbuilder │ │ │ ├── index.json.jbuilder │ │ │ └── _post.json.jbuilder │ │ ├── images │ │ │ ├── _image.json.jbuilder │ │ │ ├── show.json.jbuilder │ │ │ └── index.json.jbuilder │ │ ├── users │ │ │ ├── show.json.jbuilder │ │ │ └── _user.json.jbuilder │ │ ├── comments │ │ │ ├── show.json.jbuilder │ │ │ ├── _comment.json.jbuilder │ │ │ ├── index.json.jbuilder │ │ │ ├── user_comment.json.jbuilder │ │ │ └── _show.json.jbuilder │ │ └── search │ │ │ └── index.json.jbuilder │ ├── layouts │ │ └── application.html.erb │ └── static_pages │ │ └── root.html.erb └── helpers │ ├── api │ ├── posts_helper.rb │ ├── users_helper.rb │ ├── comments_helper.rb │ ├── images_helper.rb │ └── search_helper.rb │ ├── application_helper.rb │ └── static_pages_helper.rb ├── lib ├── assets │ └── .keep └── tasks │ └── .keep ├── test ├── helpers │ └── .keep ├── mailers │ └── .keep ├── models │ ├── .keep │ ├── vote_test.rb │ ├── comment_test.rb │ ├── post_test.rb │ ├── user_test.rb │ └── image_test.rb ├── controllers │ ├── .keep │ ├── api │ │ ├── images_controller_test.rb │ │ ├── posts_controller_test.rb │ │ ├── search_controller_test.rb │ │ ├── users_controller_test.rb │ │ └── comments_controller_test.rb │ └── static_pages_controller_test.rb ├── fixtures │ ├── .keep │ ├── create_votes.yml │ ├── votes.yml │ ├── comments.yml │ ├── posts.yml │ ├── users.yml │ └── images.yml ├── integration │ └── .keep └── test_helper.rb ├── vendor └── assets │ ├── javascripts │ └── .keep │ └── stylesheets │ └── .keep ├── config ├── initializers │ ├── aws.rb │ ├── cookies_serializer.rb │ ├── session_store.rb │ ├── mime_types.rb │ ├── filter_parameter_logging.rb │ ├── impression.rb │ ├── backtrace_silencers.rb │ ├── assets.rb │ ├── to_time_preserves_timezone.rb │ ├── wrap_parameters.rb │ └── inflections.rb ├── boot.rb ├── environment.rb ├── routes.rb ├── locales │ └── en.yml ├── secrets.yml ├── application.rb ├── environments │ ├── test.rb │ ├── development.rb │ └── production.rb └── database.yml ├── public ├── favicon.ico ├── robots.txt ├── 500.html ├── 422.html └── 404.html ├── docs ├── wireframes │ ├── NavBar.png │ ├── Comments.png │ ├── Login Page.png │ ├── User Page.png │ ├── Sign Up Page.png │ ├── Upload Modal.png │ ├── Post Index Page.png │ ├── Post Show Page.png │ └── Post Index pt II (when comments button clicked).png ├── api-endpoints.md ├── schema.md ├── README.md ├── sample-state.md └── component-hierarchy.md ├── bin ├── bundle ├── rake ├── rails ├── spring └── setup ├── db ├── migrate │ ├── 20170625180555_remove_image_table.rb │ ├── 20170803044131_add_votes_to_user.rb │ ├── 20170629012237_add_vote_type_to_votes.rb │ ├── 20170629014012_remove_title_from_images.rb │ ├── 20170625204107_remove_post_id_from_images.rb │ ├── 20170803044426_add_votes_to_user2.rb │ ├── 20170629013354_add_post_id_to_comments.rb │ ├── 20170630090028_changlenullofcomments.rb │ ├── 20170622202806_correctly_spell_description.rb │ ├── 20170814033140_add_impressions_count_to_posts.rb │ ├── 20170629203207_remove_votesfrom_posts.rb │ ├── 20170629011753_create_votes.rb │ ├── 20170625182600_add_attachment_image_to_images.rb │ ├── 20170627024159_create_comments.rb │ ├── 20170624152145_create_images.rb │ ├── 20170622190143_create_posts.rb │ ├── 20170625180922_create_imagesv2.rb │ ├── 20170620134602_create_users.rb │ └── 20170814031059_create_impressions_table.rb ├── 20170803045937_rename_author_column_to_user.rb └── seeds.rb ├── config.ru ├── frontend ├── components │ ├── user │ │ ├── trophies.jsx │ │ ├── user_gallery.jsx │ │ ├── notoriety.jsx │ │ ├── user_container.js │ │ ├── user_comments.jsx │ │ └── user.jsx │ ├── posts │ │ ├── post_zoom.jsx │ │ ├── action_buttons.jsx │ │ ├── side_bar_item.jsx │ │ ├── post_show_container.js │ │ ├── post_index_item_container.js │ │ ├── post_index_container.js │ │ ├── image_show.jsx │ │ ├── post_show.jsx │ │ ├── post_index.jsx │ │ ├── side_bar.jsx │ │ ├── post_index_item.jsx │ │ └── post_hover.jsx │ ├── nav_bar │ │ ├── back_to.jsx │ │ ├── search_bar_input.jsx │ │ ├── upload_button_contents.jsx │ │ ├── menu_dropdown_contents.jsx │ │ ├── nav_bar.jsx │ │ ├── header.jsx │ │ ├── menu_drop_down.jsx │ │ ├── left_side.jsx │ │ ├── upload_button.jsx │ │ └── search_bar_container.jsx │ ├── root.jsx │ ├── main_container.js │ ├── session_form │ │ ├── social_buttons.jsx │ │ └── session_form_container.js │ ├── greeting │ │ ├── greeting_container.js │ │ └── greeting.jsx │ ├── delete │ │ └── delete_button.jsx │ ├── comments │ │ ├── comments_index_container.js │ │ ├── create_comment_container.js │ │ ├── new_comment_form_container.js │ │ ├── comments_index.jsx │ │ ├── comments_index_item_container.js │ │ ├── new_comment_form.jsx │ │ ├── create_comments.jsx │ │ └── reply_form.jsx │ ├── upload_button │ │ ├── upload_page.jsx │ │ └── upload_button_container.js │ ├── modal.jsx │ ├── main.jsx │ └── app.jsx ├── util │ ├── search_api_util.js │ ├── comment_api_util.js │ ├── vote_api_util.js │ ├── image_api_util.js │ ├── session_api_util.js │ ├── post_api_util.js │ └── route_util.jsx ├── actions │ ├── modal_actions.js │ ├── dropdown_actions.js │ ├── search_actions.js │ ├── session_actions.js │ ├── image_actions.js │ ├── comment_actions.js │ ├── vote_actions.js │ ├── post_actions.js │ └── user_actions.js ├── reducers │ ├── dropdown_reducer.js │ ├── selectors.js │ ├── root_reducer.js │ ├── modal_reducer.js │ ├── search_reducer.js │ ├── image_reducer.js │ ├── session_reducer.js │ ├── user_reducer.js │ ├── comment_reducer.js │ └── post_reducer.js ├── store │ └── store.js └── invaderGir.jsx ├── Rakefile ├── .gitignore ├── webpack.config.js ├── Gemfile ├── json.js └── package.json /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/tasks/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/mailers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/controllers/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/fixtures/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/api/posts/new.json.jbuilder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/api/images/_image.json.jbuilder: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/helpers/api/posts_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::PostsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/users_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::UsersHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/comments_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::CommentsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/images_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::ImagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/api/search_helper.rb: -------------------------------------------------------------------------------- 1 | module Api::SearchHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/application_helper.rb: -------------------------------------------------------------------------------- 1 | module ApplicationHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/static_pages_helper.rb: -------------------------------------------------------------------------------- 1 | module StaticPagesHelper 2 | end 3 | -------------------------------------------------------------------------------- /config/initializers/aws.rb: -------------------------------------------------------------------------------- 1 | Aws::VERSION = Gem.loaded_specs["aws-sdk"].version -------------------------------------------------------------------------------- /app/views/api/users/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! "api/users/user", user: @user 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /app/views/api/comments/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! 'api/comments/show', comment: @comment 2 | -------------------------------------------------------------------------------- /app/views/api/users/_user.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! user, :id, :username, :created_at, :votes 2 | -------------------------------------------------------------------------------- /app/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/logo.png -------------------------------------------------------------------------------- /app/views/api/posts/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.partial! 'api/posts/post', post: @post, comments: @comments 2 | -------------------------------------------------------------------------------- /docs/wireframes/NavBar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/NavBar.png -------------------------------------------------------------------------------- /app/assets/images/downvote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/downvote.png -------------------------------------------------------------------------------- /app/assets/images/logo-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/logo-2.png -------------------------------------------------------------------------------- /app/assets/images/senate.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/senate.jpg -------------------------------------------------------------------------------- /app/assets/images/upload.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/upload.png -------------------------------------------------------------------------------- /app/assets/images/upvote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/upvote.png -------------------------------------------------------------------------------- /docs/wireframes/Comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/Comments.png -------------------------------------------------------------------------------- /docs/wireframes/Login Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/Login Page.png -------------------------------------------------------------------------------- /docs/wireframes/User Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/User Page.png -------------------------------------------------------------------------------- /app/assets/images/loading-2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/loading-2.gif -------------------------------------------------------------------------------- /docs/wireframes/Sign Up Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/Sign Up Page.png -------------------------------------------------------------------------------- /docs/wireframes/Upload Modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/Upload Modal.png -------------------------------------------------------------------------------- /app/assets/images/Gir_dogsuit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/Gir_dogsuit.png -------------------------------------------------------------------------------- /app/assets/images/github_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/github_icon.png -------------------------------------------------------------------------------- /app/assets/images/linkedin_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/linkedin_icon.png -------------------------------------------------------------------------------- /app/assets/images/mck-loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/mck-loading.gif -------------------------------------------------------------------------------- /app/assets/images/resume_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/resume_icon.png -------------------------------------------------------------------------------- /app/assets/images/search_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/search_icon.png -------------------------------------------------------------------------------- /app/assets/images/upvote-hollow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/upvote-hollow.png -------------------------------------------------------------------------------- /app/assets/repo_images/Comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/Comment.png -------------------------------------------------------------------------------- /app/assets/repo_images/Upvote.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/Upvote.png -------------------------------------------------------------------------------- /app/assets/repo_images/new_post.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/new_post.png -------------------------------------------------------------------------------- /docs/wireframes/Post Index Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/Post Index Page.png -------------------------------------------------------------------------------- /docs/wireframes/Post Show Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/Post Show Page.png -------------------------------------------------------------------------------- /app/assets/images/downvote-hollow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/downvote-hollow.png -------------------------------------------------------------------------------- /app/assets/images/upload-giraffe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/upload-giraffe.png -------------------------------------------------------------------------------- /app/assets/images/upload-pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/upload-pointer.png -------------------------------------------------------------------------------- /app/assets/images/gir-back-to-show.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/images/gir-back-to-show.png -------------------------------------------------------------------------------- /app/assets/repo_images/Comment-reply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/Comment-reply.png -------------------------------------------------------------------------------- /app/assets/repo_images/Login_Signup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/Login_Signup.png -------------------------------------------------------------------------------- /app/assets/repo_images/New Comment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/New Comment.png -------------------------------------------------------------------------------- /app/assets/repo_images/Sign-In Page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/Sign-In Page.png -------------------------------------------------------------------------------- /app/assets/repo_images/sign_in_modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/sign_in_modal.png -------------------------------------------------------------------------------- /app/assets/repo_images/upload_modal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/upload_modal.png -------------------------------------------------------------------------------- /app/controllers/static_pages_controller.rb: -------------------------------------------------------------------------------- 1 | class StaticPagesController < ApplicationController 2 | def root 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /app/assets/repo_images/Image_show_page.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/Image_show_page.png -------------------------------------------------------------------------------- /app/assets/repo_images/Slide_Out_Menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/Slide_Out_Menu.png -------------------------------------------------------------------------------- /app/assets/repo_images/User_Page_Column.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/app/assets/repo_images/User_Page_Column.png -------------------------------------------------------------------------------- /app/views/api/images/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @image, :id, :title, :description, :created_at, :updated_at, :image, :imageable_id 2 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /db/migrate/20170625180555_remove_image_table.rb: -------------------------------------------------------------------------------- 1 | class RemoveImageTable < ActiveRecord::Migration 2 | def change 3 | drop_table :images 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/models/restart command.txt: -------------------------------------------------------------------------------- 1 | heroku restart; heroku pg:reset DATABASE --confirm imgirrrrr; heroku run bundle exec rake db:migrate; heroku run bundle exec rake db:seed -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.action_dispatch.cookies_serializer = :json 4 | -------------------------------------------------------------------------------- /db/migrate/20170803044131_add_votes_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddVotesToUser < ActiveRecord::Migration 2 | def change 3 | add_column :users, :votes, :integer 4 | 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Rails.application.config.session_store :cookie_store, key: '_ZIMnGir_session' 4 | -------------------------------------------------------------------------------- /db/migrate/20170629012237_add_vote_type_to_votes.rb: -------------------------------------------------------------------------------- 1 | class AddVoteTypeToVotes < ActiveRecord::Migration 2 | def change 3 | add_column :votes, :vote_type, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170629014012_remove_title_from_images.rb: -------------------------------------------------------------------------------- 1 | class RemoveTitleFromImages < ActiveRecord::Migration 2 | def change 3 | remove_column :images, :title 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /docs/wireframes/Post Index pt II (when comments button clicked).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dawah-wadah/imGir/HEAD/docs/wireframes/Post Index pt II (when comments button clicked).png -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /db/migrate/20170625204107_remove_post_id_from_images.rb: -------------------------------------------------------------------------------- 1 | class RemovePostIdFromImages < ActiveRecord::Migration 2 | def change 3 | remove_column :images, :post_id 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170803044426_add_votes_to_user2.rb: -------------------------------------------------------------------------------- 1 | class AddVotesToUser2 < ActiveRecord::Migration 2 | def change 3 | change_column :users, :votes, :integer, :default => 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /db/migrate/20170629013354_add_post_id_to_comments.rb: -------------------------------------------------------------------------------- 1 | class AddPostIdToComments < ActiveRecord::Migration 2 | def change 3 | add_column :comments, :post_id, :integer, null: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170630090028_changlenullofcomments.rb: -------------------------------------------------------------------------------- 1 | class Changlenullofcomments < ActiveRecord::Migration 2 | def change 3 | change_column :comments, :post_id, :integer, null: true 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170622202806_correctly_spell_description.rb: -------------------------------------------------------------------------------- 1 | class CorrectlySpellDescription < ActiveRecord::Migration 2 | def change 3 | rename_column :posts, :descripton, :description 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170814033140_add_impressions_count_to_posts.rb: -------------------------------------------------------------------------------- 1 | class AddImpressionsCountToPosts < ActiveRecord::Migration 2 | def change 3 | add_column :posts, :impression_count, :integer 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/controllers/api/images_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::ImagesControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/api/posts_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::PostsControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/api/search_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::SearchControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/api/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::UsersControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/controllers/static_pages_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class StaticPagesControllerTest < ActionController::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20170629203207_remove_votesfrom_posts.rb: -------------------------------------------------------------------------------- 1 | class RemoveVotesfromPosts < ActiveRecord::Migration 2 | def change 3 | remove_column :posts, :upvotes 4 | remove_column :posts, :downvotes 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /frontend/components/user/trophies.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | const Trophies = () => ( 5 |
6 |

Walaa Loves this class

7 |
8 | ); 9 | 10 | export default Trophies; 11 | -------------------------------------------------------------------------------- /frontend/util/search_api_util.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const fetchSearch = query => ( 4 | $.ajax({ 5 | method: 'get', 6 | url: '/api/search', 7 | data: { query } 8 | }) 9 | ); 10 | -------------------------------------------------------------------------------- /app/assets/stylesheets/static_pages.scss: -------------------------------------------------------------------------------- 1 | // Place all the styles related to the StaticPages controller here. 2 | // They will automatically be included in application.css. 3 | // You can use Sass (SCSS) here: http://sass-lang.com/ 4 | -------------------------------------------------------------------------------- /frontend/components/posts/post_zoom.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const PostZoom = ({img}) => ( 4 |
5 | 6 |
7 | ); 8 | 9 | export default PostZoom; 10 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /app/assets/javascripts/api/images.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/api/posts.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/api/search.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/api/users.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /db/20170803045937_rename_author_column_to_user.rb: -------------------------------------------------------------------------------- 1 | # class RenameAuthorColumnToUser < ActiveRecord::Migration 2 | # def change 3 | # rename_column :posts, :author_id, :user_id 4 | # rename_column :votes, :author_id, :user_id 5 | # end 6 | # end 7 | -------------------------------------------------------------------------------- /app/assets/javascripts/api/comments.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /app/assets/javascripts/static_pages.coffee: -------------------------------------------------------------------------------- 1 | # Place all the behaviors and hooks related to the matching controller here. 2 | # All this logic will automatically be available in application.js. 3 | # You can use CoffeeScript in this file: http://coffeescript.org/ 4 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /config/initializers/impression.rb: -------------------------------------------------------------------------------- 1 | # Use this hook to configure impressionist parameters 2 | #Impressionist.setup do |config| 3 | # Define ORM. Could be :active_record (default), :mongo_mapper or :mongoid 4 | # config.orm = :active_record 5 | #end 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | -------------------------------------------------------------------------------- /db/migrate/20170629011753_create_votes.rb: -------------------------------------------------------------------------------- 1 | class CreateVotes < ActiveRecord::Migration 2 | def change 3 | create_table :votes do |t| 4 | t.integer :user_id, null: false 5 | t.references :voteable, polymorphic: true 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../../config/application', __FILE__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /db/migrate/20170625182600_add_attachment_image_to_images.rb: -------------------------------------------------------------------------------- 1 | class AddAttachmentImageToImages < ActiveRecord::Migration 2 | def self.up 3 | change_table :images do |t| 4 | t.attachment :image 5 | end 6 | end 7 | 8 | def self.down 9 | remove_attachment :images, :image 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/api/images/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @posts do |post| 2 | 3 | json.title post.title 4 | json.description post.description 5 | json.upvotes post.upvotes 6 | json.downvotes post.downvotes 7 | json.account_id post.account_id 8 | json.main_image post.main_image 9 | json.images post.images 10 | end 11 | -------------------------------------------------------------------------------- /frontend/actions/modal_actions.js: -------------------------------------------------------------------------------- 1 | export const DISPLAY_MODAL = "DISPLAY_MODAL"; 2 | export const CLEAR_MODALS = "CLEAR_MODALS"; 3 | 4 | export const displayModal = (Component) => ({ 5 | type: DISPLAY_MODAL, 6 | component: Component, 7 | }); 8 | 9 | export const clearModals = () => ({ 10 | type: CLEAR_MODALS 11 | }); 12 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/back_to.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link} from 'react-router-dom'; 3 | 4 | 5 | const backTo = () => ( 6 |
7 | 8 | 9 | back to imGir 10 | 11 |
12 | ); 13 | 14 | export default backTo; 15 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/search_bar_input.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SearchBarInput = () => ( 4 |
e.stopPropagation()}> 6 | 7 |
8 | ); 9 | export default SearchBarInput; 10 | -------------------------------------------------------------------------------- /db/migrate/20170627024159_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration 2 | def change 3 | create_table :comments do |t| 4 | t.integer :user_id, null: false, index: true 5 | t.references :parent, polymorphic: true, index: true 6 | t.string :body 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /frontend/actions/dropdown_actions.js: -------------------------------------------------------------------------------- 1 | export const DISPLAY_DROPDOWN = "DISPLAY_DROPDOWN"; 2 | export const CLEAR_DROPDOWNS = "CLEAR_DROPDOWNS"; 3 | 4 | export const displayDropdown = (menu) => ({ 5 | type: DISPLAY_DROPDOWN, 6 | menu 7 | }); 8 | 9 | export const clearDropdowns = () => { 10 | return ({ 11 | type: CLEAR_DROPDOWNS 12 | }); 13 | }; 14 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV['RAILS_ENV'] ||= 'test' 2 | require File.expand_path('../../config/environment', __FILE__) 3 | require 'rails/test_help' 4 | 5 | class ActiveSupport::TestCase 6 | # Setup all fixtures in test/fixtures/*.yml for all tests in alphabetical order. 7 | fixtures :all 8 | 9 | # Add more helper methods to be used by all tests here... 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20170624152145_create_images.rb: -------------------------------------------------------------------------------- 1 | class CreateImages < ActiveRecord::Migration 2 | def change 3 | create_table :images do |t| 4 | t.integer :post_id, null: false 5 | t.string :title, null: false 6 | t.boolean :main_image, null: false, default: true 7 | t.text :description 8 | 9 | t.timestamps null: false 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /frontend/components/root.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Provider } from 'react-redux'; 3 | import { HashRouter } from 'react-router-dom'; 4 | 5 | import App from './app'; 6 | 7 | const Root = ({ store }) => ( 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | 15 | export default Root; 16 | -------------------------------------------------------------------------------- /frontend/actions/search_actions.js: -------------------------------------------------------------------------------- 1 | export const RECEIVE_SEARCH = 'RECEIVE_SEARCH'; 2 | import * as SearchUtil from '../util/search_api_util'; 3 | 4 | export const receiveSearch = results => ({ 5 | type: RECEIVE_SEARCH, 6 | results 7 | }); 8 | 9 | export const fetchSearch = search => dispatch => ( 10 | SearchUtil.fetchSearch(search) 11 | .then(results => dispatch(receiveSearch(results))) 12 | ); 13 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/upload_button_contents.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const UploadButtonContent = () => ( 4 |
e.stopPropagation}> 5 | Upload an Image 6 | Make a Meme 7 | Visit Yaakov's Site 8 |
9 | ); 10 | 11 | export default UploadButtonContent; 12 | -------------------------------------------------------------------------------- /frontend/components/main_container.js: -------------------------------------------------------------------------------- 1 | import { clearDropdowns } from '../actions/dropdown_actions'; 2 | import {connect} from 'react-redux'; 3 | import { withRouter } from 'react-router-dom'; 4 | import Main from './main'; 5 | 6 | 7 | const mapDispatchToProps = (dispatch) => ({ 8 | clearDropdowns: () => dispatch(clearDropdowns()) 9 | }); 10 | 11 | 12 | export default withRouter(connect(null, mapDispatchToProps)(Main)); 13 | -------------------------------------------------------------------------------- /frontend/components/session_form/social_buttons.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const SocialButtons = () => ( 4 |
5 | 8 | 9 | 12 |
13 | ); 14 | 15 | export default SocialButtons; 16 | -------------------------------------------------------------------------------- /test/fixtures/create_votes.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /db/migrate/20170622190143_create_posts.rb: -------------------------------------------------------------------------------- 1 | class CreatePosts < ActiveRecord::Migration 2 | def change 3 | create_table :posts do |t| 4 | t.string :title, null: false 5 | t.integer :upvotes, null: false, default: 0 6 | t.integer :downvotes, null: false, default: 0 7 | t.text :descripton 8 | t.integer :user_id, null: false 9 | 10 | t.timestamps null: false 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/layouts/application.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | ZIMnGir 5 | <%= stylesheet_link_tag 'application', media: 'all' %> 6 | <%= javascript_include_tag 'application' %> 7 | <%= csrf_meta_tags %> 8 | 9 | 10 | 11 | 12 | <%= yield %> 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/controllers/api/search_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::SearchController < ApplicationController 2 | def index 3 | @posts = if params[:query].present? 4 | Post.includes(:user, :main_image) 5 | .where('lower(title) LIKE (?)', "%#{params[:query].downcase}%") 6 | else 7 | Post.includes(:user, :main_image).all 8 | end 9 | render 'api/posts/index' 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/api/comments/_comment.json.jbuilder: -------------------------------------------------------------------------------- 1 | 2 | json.id comment.id 3 | json.user_id comment.user_id 4 | json.parent_id = comment.parent_id 5 | json.parent_type = comment.parent_type 6 | json.user_name comment.user.username 7 | json.body comment.body 8 | json.post_id comment.post_id 9 | json.reply_count comment.replies.count 10 | json.replies_id comment.replies.map(&:id) 11 | 12 | json.set! :time_since, (comment.created_at.to_f * 1000).floor 13 | -------------------------------------------------------------------------------- /db/migrate/20170625180922_create_imagesv2.rb: -------------------------------------------------------------------------------- 1 | class CreateImagesv2 < ActiveRecord::Migration 2 | def change 3 | create_table :images do |t| 4 | t.integer :post_id, null: false 5 | t.text :description 6 | t.string :title, null: false 7 | t.boolean :main_image, null: false, default: true 8 | t.references :imageable, polymorphic: true, index: true 9 | t.timestamps null: false 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20170620134602_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def change 3 | create_table :users do |t| 4 | t.string :username, null: false 5 | t.string :password_digest, null: false 6 | t.string :session_token, null: false 7 | t.timestamps null: false 8 | end 9 | 10 | add_index :users, :username, unique: true 11 | add_index :users, :session_token, unique: true 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/delete_button.scss: -------------------------------------------------------------------------------- 1 | svg { 2 | cursor: pointer; 3 | height: 23px; 4 | width: 23px; 5 | } 6 | svg > circle { 7 | stroke: #1bb76e; 8 | fill: #34373c; 9 | } 10 | svg > path { 11 | stroke: #1bb76e; 12 | } 13 | svg:hover > circle { 14 | fill: red; 15 | } 16 | svg:hover > path { 17 | stroke: #34373c; 18 | } 19 | 20 | .deleteButton { 21 | z-index: 5; 22 | position: absolute; 23 | right: -18px; 24 | top: -5px; 25 | } 26 | -------------------------------------------------------------------------------- /frontend/reducers/dropdown_reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | DISPLAY_DROPDOWN, 3 | CLEAR_DROPDOWNS 4 | } from '../actions/dropdown_actions'; 5 | 6 | const DropdownReducer = (state = {}, action) => { 7 | Object.freeze(state); 8 | switch(action.type) { 9 | case DISPLAY_DROPDOWN: 10 | return Object.assign({}, state, action.menu); 11 | case CLEAR_DROPDOWNS: 12 | return {}; 13 | default: 14 | return state; 15 | } 16 | }; 17 | 18 | export default DropdownReducer; 19 | -------------------------------------------------------------------------------- /frontend/components/greeting/greeting_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | 3 | import { logout } from '../../actions/session_actions'; 4 | import Greeting from './greeting'; 5 | 6 | const mapStateToProps = ({ session }) => ({ 7 | currentUser: session.currentUser 8 | }); 9 | 10 | const mapDispatchToProps = dispatch => ({ 11 | logout: () => dispatch(logout()) 12 | }); 13 | 14 | export default connect( 15 | mapStateToProps, 16 | mapDispatchToProps 17 | )(Greeting); 18 | -------------------------------------------------------------------------------- /frontend/util/comment_api_util.js: -------------------------------------------------------------------------------- 1 | export const fetchComments = (id) => ( 2 | $.ajax({ 3 | method: 'GET', 4 | url: `/api/posts/${id}/comments`, 5 | }) 6 | ); 7 | 8 | export const fetchComment = (id) => ( 9 | $.ajax({ 10 | method: 'GET', 11 | url: `/api/comments/${id}`, 12 | }) 13 | ); 14 | 15 | export const createComment = (props) => { 16 | 17 | return ( 18 | $.ajax({ 19 | method: 'POST', 20 | url: 'api/comments/', 21 | data: props, 22 | }) 23 | );}; 24 | -------------------------------------------------------------------------------- /frontend/util/vote_api_util.js: -------------------------------------------------------------------------------- 1 | 2 | export const editVote = vote => { 3 | return ( 4 | $.ajax({ 5 | method: 'PATCH', 6 | url: `/api/votes/${vote.vote.id}`, 7 | data: vote 8 | }) 9 | );}; 10 | 11 | export const createVote = vote => ( 12 | $.ajax({ 13 | method: 'POST', 14 | url: '/api/votes', 15 | data: vote 16 | }) 17 | ); 18 | 19 | export const deleteVote = ({id}) => ( 20 | $.ajax({ 21 | method: 'DELETE', 22 | url: `/api/votes/${id}` 23 | }) 24 | ); 25 | -------------------------------------------------------------------------------- /frontend/components/posts/action_buttons.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | 4 | const Footer = () => ( 5 |
6 |
7 | 8 |
9 |
10 | 11 |
12 |
13 | ); 14 | 15 | export default Footer; 16 | -------------------------------------------------------------------------------- /app/controllers/api/users_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::UsersController < ApplicationController 2 | 3 | def create 4 | @user = User.new(user_params) 5 | if @user.save 6 | login(@user) 7 | render "api/users/show" 8 | else 9 | render json: @user.errors.full_messages, status: 422 10 | end 11 | end 12 | 13 | def show 14 | @user = User.find(params[:id]) 15 | end 16 | 17 | private 18 | 19 | def user_params 20 | params.require(:user).permit(:username, :password) 21 | end 22 | 23 | end 24 | -------------------------------------------------------------------------------- /frontend/components/delete/delete_button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export const DeleteButton = ({deleteFunction}) => { 4 | return ( 5 | deleteFunction()} className='deleteButton'> 6 | 7 | 8 | 9 | 10 | 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/util/image_api_util.js: -------------------------------------------------------------------------------- 1 | export const fetchImages = () => ( 2 | $.ajax({ 3 | method: 'GET', 4 | url: '/api/images', 5 | }) 6 | ); 7 | 8 | export const fetchImage = (id) => ( 9 | $.ajax({ 10 | method: 'GET', 11 | url: `/api/images/${id}`, 12 | }) 13 | ); 14 | 15 | export const uploadImage = (props) => { 16 | return ( 17 | $.ajax({ 18 | method: 'POST', 19 | url: 'api/images/', 20 | data: props, 21 | contentType: false, 22 | processData: false, 23 | }) 24 | );}; 25 | -------------------------------------------------------------------------------- /frontend/components/posts/side_bar_item.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import {Link} from 'react-router-dom'; 4 | 5 | 6 | const SideBarItem = ({post}) => ( 7 | 8 |
9 |
10 | 11 |
12 |
13 |

{post.title}

14 |
15 |
16 | 17 | ); 18 | 19 | export default SideBarItem; 20 | -------------------------------------------------------------------------------- /frontend/reducers/selectors.js: -------------------------------------------------------------------------------- 1 | import values from 'lodash/values'; 2 | 3 | export const selectAllPosts = (posts) => ( 4 | values(posts).sort((a, b) => (b.id - a.id)) 5 | ); 6 | 7 | export const selectAllComments = ({ comment }, commentIds ) => { 8 | if( comment.entities && values(comment.entities).length && commentIds) { 9 | return commentIds.map( id => comment.entities[id]); 10 | } else { return null ;} 11 | }; 12 | 13 | export const selectAllResults = (search) => { 14 | 15 | return ( 16 | values(search) 17 | )}; 18 | -------------------------------------------------------------------------------- /test/models/vote_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: votes 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer not null 7 | # voteable_id :integer 8 | # voteable_type :string 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # vote_type :string 12 | # 13 | 14 | require 'test_helper' 15 | 16 | class VoteTest < ActiveSupport::TestCase 17 | # test "the truth" do 18 | # assert true 19 | # end 20 | end 21 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /frontend/util/session_api_util.js: -------------------------------------------------------------------------------- 1 | import { 2 | receiveCurrentUser, 3 | receiveErrors 4 | } from '../actions/session_actions'; 5 | 6 | export const login = user => ( 7 | $.ajax({ 8 | method: 'POST', 9 | url: '/api/session', 10 | data: user 11 | }) 12 | ); 13 | 14 | export const signup = user => ( 15 | $.ajax({ 16 | method: 'POST', 17 | url: '/api/users', 18 | data: user 19 | }) 20 | ); 21 | 22 | export const logout = () => ( 23 | $.ajax({ 24 | method: 'DELETE', 25 | url: '/api/session' 26 | }) 27 | ); 28 | -------------------------------------------------------------------------------- /frontend/components/posts/post_show_container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PostShow from './post_show'; 3 | import {connect} from 'react-redux'; 4 | import { requestOnePost } from '../../actions/post_actions'; 5 | 6 | const mapStateToProps = (state, {match}) => { 7 | return({ 8 | post: state.post.entities, 9 | id: match.params.id 10 | });}; 11 | 12 | const mapDispatchToProps = dispatch => ({ 13 | requestOnePost: (id) => dispatch(requestOnePost(id)) 14 | }); 15 | 16 | export default connect(mapStateToProps, mapDispatchToProps)(PostShow); 17 | -------------------------------------------------------------------------------- /test/models/comment_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: comments 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer not null 7 | # parent_id :integer 8 | # parent_type :string 9 | # body :string 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # post_id :integer 13 | # 14 | 15 | require 'test_helper' 16 | 17 | class CommentTest < ActiveSupport::TestCase 18 | # test "the truth" do 19 | # assert true 20 | # end 21 | end 22 | -------------------------------------------------------------------------------- /app/views/api/comments/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @comments.each do |comment| 2 | json.set! comment.id do 3 | json.id comment.id 4 | json.user_id comment.user_id 5 | json.parent_id = comment.parent_id 6 | json.parent_type = comment.parent_type 7 | json.user_name comment.user.username 8 | json.body comment.body 9 | json.post_id comment.post_id 10 | json.replies_id comment.replies.map {|el| el.id } 11 | json.reply_count comment.replies.count 12 | json.set! :time_since, (comment.created_at.to_f * 1000).floor 13 | 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/views/api/comments/user_comment.json.jbuilder: -------------------------------------------------------------------------------- 1 | @comments.each do |comment| 2 | json.set! comment.id do 3 | json.id comment.id 4 | json.user_id comment.user_id 5 | json.user_name comment.user.username 6 | json.body comment.body 7 | json.reply_count comment.replies.count 8 | json.set! :time_since, (comment.created_at.to_f * 1000).floor 9 | json.post_id comment.post_id 10 | json.points comment.upvotes.count - comment.downvotes.count 11 | json.main_image asset_path(comment.main_image.image.url(:thumb)) 12 | 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/models/post_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: posts 4 | # 5 | # id :integer not null, primary key 6 | # title :string not null 7 | # description :text 8 | # user_id :integer not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # impression_count :integer 12 | # 13 | 14 | require 'test_helper' 15 | 16 | class PostTest < ActiveSupport::TestCase 17 | # test "the truth" do 18 | # assert true 19 | # end 20 | end 21 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config/initializers/to_time_preserves_timezone.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Preserve the timezone of the receiver when calling to `to_time`. 4 | # Ruby 2.4 will change the behavior of `to_time` to preserve the timezone 5 | # when converting to an instance of `Time` instead of the previous behavior 6 | # of converting to the local system timezone. 7 | # 8 | # Rails 5.0 introduced this config option so that apps made with earlier 9 | # versions of Rails are not affected when upgrading. 10 | ActiveSupport.to_time_preserves_timezone = true 11 | -------------------------------------------------------------------------------- /app/views/api/search/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @posts.each do |post| 2 | json.set! post.id do 3 | json.title post.title 4 | json.id post.id 5 | json.user_id post.user.id 6 | json.user_name post.user.username 7 | json.main_image asset_path(post.main_image.image.url) 8 | if current_user 9 | vote = Vote.where('user_id = :id and voteable_id = :post and voteable_type = :type', {id: current_user.id, post: post.id, type: 'Post'}) 10 | 11 | json.vote vote[0] ? vote[0] : nil 12 | else 13 | json.voted false 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /app/views/api/comments/_show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! comment, :id, :body, :user_id, :parent_type, :parent_id, :created_at 2 | json.username comment.user.username 3 | json.post_id comment.post_id 4 | json.points comment.upvotes.count - comment.downvotes.count 5 | json.comment_ids comment.replies.map { |child_comment| child_comment.id } 6 | 7 | 8 | if current_user 9 | vote = Vote.where('user_id = :id and voteable_id = :comment and voteable_type = :type', id: current_user.id, comment: comment.id, type: 'Comment') 10 | 11 | json.vote vote[0] ? vote[0] : nil 12 | else 13 | json.voted false 14 | end 15 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # username :string not null 7 | # password_digest :string not null 8 | # session_token :string not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # votes :integer default(0) 12 | # 13 | 14 | require 'test_helper' 15 | 16 | class UserTest < ActiveSupport::TestCase 17 | # test "the truth" do 18 | # assert true 19 | # end 20 | end 21 | -------------------------------------------------------------------------------- /frontend/reducers/root_reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import session from './session_reducer'; 3 | import dropdown from './dropdown_reducer'; 4 | import post from './post_reducer'; 5 | import modal from './modal_reducer'; 6 | import image from './image_reducer'; 7 | import comment from './comment_reducer'; 8 | import search from './search_reducer'; 9 | import user from './user_reducer'; 10 | 11 | const RootReducer = combineReducers({ 12 | session, 13 | dropdown, 14 | post, 15 | modal, 16 | image, 17 | comment, 18 | search, 19 | user, 20 | }); 21 | 22 | export default RootReducer; 23 | -------------------------------------------------------------------------------- /frontend/components/comments/comments_index_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import CommentsIndex from './comments_index' 3 | import { requestAllComments } from '../../actions/comment_actions'; 4 | import { selectAllComments } from '../../reducers/selectors'; 5 | 6 | const mapStateToProps = (state, ownProps) => { 7 | 8 | return { 9 | comments: selectAllComments(state, ownProps.commentIds) 10 | }; 11 | }; 12 | 13 | const mapDispatchToProps = dispatch => ({ 14 | requestAllComments: (id) => dispatch(requestAllComments(id)) 15 | }); 16 | 17 | export default connect( 18 | mapStateToProps, 19 | null 20 | )(CommentsIndex) 21 | -------------------------------------------------------------------------------- /frontend/components/upload_button/upload_page.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | class UploadPage { 4 | constructor(props) { 5 | this.images = this.props.images; 6 | this.state = { 7 | postTitle: this.props.postTitle, 8 | description: this.props.description 9 | }; 10 | } 11 | 12 | handleSubmit(e) { 13 | e.preventDefault(); 14 | } 15 | 16 | 17 | 18 | 19 | render(){ 20 | 21 | return( 22 | // {this.props.images.length} 23 | // {this.state.postTitle} 24 | 25 |

26 | We are now on the New Post page 27 |

28 | ); 29 | } 30 | } 31 | 32 | 33 | export default UploadPage; 34 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | namespace :api, defaults: { format: :json } do 3 | # resources :posts, only: %i[inde show] 4 | resources :posts do 5 | resources :comments, only: %i[index] 6 | end 7 | resources :votes, only: %i[create update destroy] 8 | resources :comments, only: %i[index create edit destroy show] 9 | resources :images 10 | resources :users, only: %i[create show] do 11 | resources :comments, only: [:index] 12 | end 13 | resource :session, only: %i[create destroy show] 14 | resources :search, only: %i[index] 15 | end 16 | 17 | root 'static_pages#root' 18 | end 19 | -------------------------------------------------------------------------------- /frontend/components/posts/post_index_item_container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PostIndexItem from './post_index_item'; 4 | import { deletePost } from '../../actions/post_actions' 5 | 6 | 7 | const mapStateToProps = ({session}) => { 8 | let userId = 0; 9 | if (session.currentUser){ userId = session.currentUser.id} 10 | return { 11 | currentUser: userId 12 | }; 13 | }; 14 | 15 | const mapDispatchToProps = dispatch => ({ 16 | deletePost: (id) => dispatch(deletePost(id)) 17 | }); 18 | 19 | 20 | export default connect( 21 | mapStateToProps, 22 | mapDispatchToProps 23 | )(PostIndexItem); 24 | -------------------------------------------------------------------------------- /frontend/store/store.js: -------------------------------------------------------------------------------- 1 | import { 2 | createStore, 3 | applyMiddleware 4 | } from 'redux'; 5 | import rootReducer from '../reducers/root_reducer'; 6 | import thunk from 'redux-thunk'; 7 | 8 | const configureStore = (preloadedState = {}) => { 9 | 10 | const middlewares = [thunk]; 11 | if (process.env.NODE_ENV !== 'production') { 12 | const { 13 | createLogger 14 | } = require('redux-logger'); 15 | middlewares.push(createLogger()); 16 | } 17 | 18 | return ( 19 | createStore( 20 | rootReducer, 21 | preloadedState, 22 | applyMiddleware(...middlewares) 23 | ) 24 | ); 25 | }; 26 | 27 | export default configureStore; 28 | -------------------------------------------------------------------------------- /frontend/util/post_api_util.js: -------------------------------------------------------------------------------- 1 | export const fetchPosts = (page) => { 2 | 3 | return ( 4 | $.ajax({ 5 | method: 'GET', 6 | url: '/api/posts?page=' + page, 7 | }) 8 | );}; 9 | 10 | export const fetchPost = (id) => { 11 | return ( 12 | $.ajax({ 13 | method: 'GET', 14 | url: `/api/posts/${id}`, 15 | }) 16 | );}; 17 | 18 | export const createPost = (props) => { 19 | return ( 20 | $.ajax({ 21 | method: 'POST', 22 | url: 'api/posts/', 23 | data: props , 24 | }) 25 | );}; 26 | 27 | export const deletePost = (id) => { 28 | return ( 29 | $.ajax({ 30 | method: 'DELETE', 31 | url: `api/posts/${id}`, 32 | }) 33 | );}; 34 | -------------------------------------------------------------------------------- /test/controllers/api/comments_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class Api::CommentsControllerTest < ActionController::TestCase 4 | test "should get index" do 5 | get :index 6 | assert_response :success 7 | end 8 | 9 | test "should get show" do 10 | get :show 11 | assert_response :success 12 | end 13 | 14 | test "should get create" do 15 | get :create 16 | assert_response :success 17 | end 18 | 19 | test "should get destroy" do 20 | get :destroy 21 | assert_response :success 22 | end 23 | 24 | test "should get update" do 25 | get :update 26 | assert_response :success 27 | end 28 | 29 | end 30 | -------------------------------------------------------------------------------- /frontend/components/user/user_gallery.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PostIndexItem from '../posts/post_index_item_container'; 3 | import { DeleteButton } from '../delete/delete_button'; 4 | 5 | export default class UserGallery extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | render() { 11 | const allPosts = this.props.posts.sort((a, b) => (b.created_at - a.created_at)).map((post) => ( 12 | 13 | 14 | 15 | )); 16 | return ( 17 |
18 | {allPosts} 19 |
20 | ); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/controllers/api/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::SessionsController < ApplicationController 2 | 3 | def create 4 | @user = User.find_by_credentials( 5 | params[:user][:username], 6 | params[:user][:password] 7 | ) 8 | 9 | if @user 10 | login(@user) 11 | render "api/users/show" 12 | else 13 | render( 14 | json: ["Invalid username/password combination"], 15 | status: 401 16 | ) 17 | end 18 | end 19 | 20 | def destroy 21 | @user = current_user 22 | if @user 23 | logout 24 | render "api/users/show" 25 | else 26 | render( 27 | json: ["Nobody signed in"], 28 | status: 404 29 | ) 30 | end 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /app/models/vote.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: votes 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer not null 7 | # voteable_id :integer 8 | # voteable_type :string 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # vote_type :string 12 | # 13 | 14 | class Vote < ActiveRecord::Base 15 | validates :vote_type, :user_id, :voteable_type, presence: true 16 | validates :user_id, uniqueness: { scope: %i[voteable_id voteable_type] } 17 | 18 | 19 | 20 | belongs_to :user 21 | belongs_to :voteable, polymorphic: true 22 | 23 | scope :upvotes, -> { where(vote_type: 'Upvote')} 24 | end 25 | -------------------------------------------------------------------------------- /frontend/reducers/modal_reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | DISPLAY_MODAL, 3 | CLEAR_MODALS 4 | } from '../actions/modal_actions'; 5 | 6 | import { RECEIVE_ONE_POST } from '../actions/post_actions'; 7 | 8 | 9 | const defaultState = { 10 | isOpen: false, 11 | component: null 12 | }; 13 | const ModalReducer = (state = defaultState, action) => { 14 | Object.freeze(state); 15 | switch(action.type) { 16 | case DISPLAY_MODAL: 17 | return Object.assign({}, {isOpen: true, component: action.component}); 18 | case CLEAR_MODALS: 19 | return defaultState; 20 | case RECEIVE_ONE_POST: 21 | return defaultState; 22 | default: 23 | return state; 24 | } 25 | }; 26 | 27 | export default ModalReducer; 28 | -------------------------------------------------------------------------------- /test/models/image_test.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: images 4 | # 5 | # id :integer not null, primary key 6 | # description :text 7 | # main_image :boolean default(TRUE), not null 8 | # imageable_id :integer 9 | # imageable_type :string 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # image_file_name :string 13 | # image_content_type :string 14 | # image_file_size :integer 15 | # image_updated_at :datetime 16 | # 17 | 18 | require 'test_helper' 19 | 20 | class ImageTest < ActiveSupport::TestCase 21 | # test "the truth" do 22 | # assert true 23 | # end 24 | end 25 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/menu_dropdown_contents.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const MenuDropdownContents = ({clearDropdowns}) => ( 5 |
e.stopPropagation()}> 6 | Visit My LinkedIn 7 | Visit My Github 8 | Visit My Portfolio 9 | Login 10 | clearDropdowns()}>Sign Up 11 | 12 |
13 | ); 14 | 15 | 16 | export default MenuDropdownContents; 17 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/nav_bar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import UploadButton from './upload_button'; 3 | import MenuDrop from './menu_drop_down'; 4 | import LeftSide from './left_side'; 5 | 6 | 7 | 8 | const NavBar = () => { 9 | return ( 10 |
11 | 18 |
19 | 20 |
21 |
22 | );}; 23 | 24 | 25 | export default NavBar; 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore the default SQLite database. 11 | /db/*.sqlite3 12 | /db/*.sqlite3-journal 13 | 14 | # Ignore all logfiles and tempfiles. 15 | /log/* 16 | !/log/.keep 17 | /tmp 18 | 19 | 20 | node_modules/ 21 | bundle.js 22 | bundle.js.map 23 | .byebug_history 24 | .DS_Store 25 | npm-debug.log 26 | 27 | # Ignore application configuration 28 | /config/application.yml 29 | -------------------------------------------------------------------------------- /frontend/components/comments/create_comment_container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { createComment } from '../../actions/comment_actions'; 4 | import NewComment from './create_comments'; 5 | import {displayModal} from '../../actions/modal_actions'; 6 | 7 | 8 | const mapStateToProps = ({ session }) => { 9 | return { 10 | loggedIn: Boolean(session.currentUser), 11 | }; 12 | }; 13 | 14 | const mapDispatchToProps = (dispatch) => ({ 15 | createComment: (comment) => dispatch(createComment(comment)), 16 | displayModal: (component) => dispatch(displayModal(component)), 17 | }); 18 | 19 | export default connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(NewComment); 23 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # To learn more, please read the Rails Internationalization guide 20 | # available at http://guides.rubyonrails.org/i18n.html. 21 | 22 | en: 23 | hello: "Hello world" 24 | -------------------------------------------------------------------------------- /frontend/components/greeting/greeting.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | 4 | const sessionLinks = () => ( 5 | 10 | ); 11 | 12 | const personalGreeting = (currentUser, logout) => ( 13 |
14 |

Hi, {currentUser.username}!

15 | 16 |
17 | ); 18 | 19 | const Greeting = ({ currentUser, logout }) => ( 20 | currentUser ? personalGreeting(currentUser, logout) : sessionLinks() 21 | ); 22 | 23 | export default Greeting; 24 | -------------------------------------------------------------------------------- /frontend/components/user/notoriety.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Notoriety = ( { 4 | points 5 | } ) => { 6 | let status; 7 | 8 | if ( points >= 150 ) { 9 | status = 'Glorious'; 10 | } else if ( points >= 120 ) { 11 | status = 'Renowned'; 12 | } else if ( points >= 100 ) { 13 | status = 'Idolized'; 14 | } else if ( points >= 80 ) { 15 | status = 'Trusted'; 16 | } else if ( points >= 40 ) { 17 | status = 'Liked'; 18 | } else if ( points >= 20 ) { 19 | status = 'Accepted'; 20 | } else { 21 | status = 'Neutral'; 22 | } 23 | return ( 24 |
25 |

Notoriety:

{status}

26 |
27 | ); 28 | }; 29 | 30 | export default Notoriety; 31 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/header.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Router, Route, Link} from 'react-router-dom'; 3 | import GreetingContainer from '../greeting/greeting_container'; 4 | import backTo from './back_to'; 5 | import NavBar from './nav_bar'; 6 | 7 | const Header = (props) => { 8 | const location = window.location.href.split('#/')[1]; 9 | switch (location) { 10 | case 'login': 11 | return ( 12 |
13 | {backTo()} 14 |
15 | ); 16 | case 'signup': 17 | return (
18 | {backTo()} 19 |
); 20 | default: 21 | return( 22 |
23 | 24 |
25 | ); 26 | } 27 | }; 28 | 29 | export default Header; 30 | -------------------------------------------------------------------------------- /test/fixtures/votes.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: votes 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer not null 7 | # voteable_id :integer 8 | # voteable_type :string 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # vote_type :string 12 | # 13 | 14 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 15 | 16 | # This model initially had no columns defined. If you add columns to the 17 | # model remove the '{}' from the fixture names and add the columns immediately 18 | # below each fixture, per the syntax in the comments below 19 | # 20 | one: {} 21 | # column: value 22 | # 23 | two: {} 24 | # column: value 25 | -------------------------------------------------------------------------------- /frontend/reducers/search_reducer.js: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | 3 | import { 4 | RECEIVE_SEARCH, 5 | } from '../actions/search_actions'; 6 | 7 | import { 8 | DISPLAY_DROPDOWN, 9 | CLEAR_DROPDOWNS 10 | } from '../actions/dropdown_actions'; 11 | 12 | 13 | const defaultState = Object.freeze({ 14 | search: {}, 15 | }); 16 | 17 | const SearchReducer = (state = defaultState, action) => { 18 | Object.freeze(state); 19 | switch(action.type) { 20 | case RECEIVE_SEARCH: 21 | return Object.assign({}, state, { 22 | results: action.results 23 | }); 24 | case DISPLAY_DROPDOWN: 25 | return Object.assign({}, state, { 26 | results: {} 27 | }); 28 | default: 29 | return state; 30 | } 31 | }; 32 | 33 | export default SearchReducer; 34 | -------------------------------------------------------------------------------- /frontend/components/comments/new_comment_form_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { withRouter } from 'react-router-dom'; 3 | import NewCommentForm from './new_comment_form'; 4 | import { createComment } from '../../actions/comment_actions'; 5 | 6 | const mapStateToProps = ({ post, session }) => { 7 | const currentaccountId = session.currentaccount ? session.currentaccount.id : null 8 | return { 9 | currentPost: post.currentPost, 10 | loggedIn: Boolean(session.currentaccount), 11 | currentaccountId, 12 | } 13 | }; 14 | 15 | const mapDispatchToProps = dispatch => ({ 16 | createComment: (comment) => dispatch(createComment(comment)) 17 | }); 18 | 19 | export default withRouter(connect( 20 | mapStateToProps, 21 | mapDispatchToProps 22 | )(NewCommentForm)) 23 | -------------------------------------------------------------------------------- /test/fixtures/comments.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: comments 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer not null 7 | # parent_id :integer 8 | # parent_type :string 9 | # body :string 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # post_id :integer 13 | # 14 | 15 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 16 | 17 | # This model initially had no columns defined. If you add columns to the 18 | # model remove the '{}' from the fixture names and add the columns immediately 19 | # below each fixture, per the syntax in the comments below 20 | # 21 | one: {} 22 | # column: value 23 | # 24 | two: {} 25 | # column: value 26 | -------------------------------------------------------------------------------- /frontend/reducers/image_reducer.js: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | 3 | import { 4 | RECEIVE_ONE_IMAGE, 5 | RECEIVE_IMAGES, 6 | } from '../actions/image_actions'; 7 | 8 | const defaultState = Object.freeze({ 9 | entities: {}, 10 | currentImage: null 11 | }); 12 | 13 | const ImageReducer = (state = defaultState, action) => { 14 | Object.freeze(state); 15 | switch(action.type) { 16 | case RECEIVE_IMAGES: 17 | return merge({}, state, { 18 | entities: action.images 19 | }); 20 | case RECEIVE_ONE_IMAGE: 21 | const image = action.image; 22 | 23 | return merge({}, state, { 24 | entities: { [image.id]: image }, 25 | currentImage: image.id 26 | }); 27 | default: 28 | return state; 29 | } 30 | }; 31 | 32 | export default ImageReducer; 33 | -------------------------------------------------------------------------------- /test/fixtures/posts.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: posts 4 | # 5 | # id :integer not null, primary key 6 | # title :string not null 7 | # description :text 8 | # user_id :integer not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # impression_count :integer 12 | # 13 | 14 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 15 | 16 | # This model initially had no columns defined. If you add columns to the 17 | # model remove the '{}' from the fixture names and add the columns immediately 18 | # below each fixture, per the syntax in the comments below 19 | # 20 | one: {} 21 | # column: value 22 | # 23 | two: {} 24 | # column: value 25 | -------------------------------------------------------------------------------- /frontend/reducers/session_reducer.js: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | 3 | import { 4 | RECEIVE_CURRENT_USER, 5 | RECEIVE_ERRORS, CLEAR_ERRORS 6 | } from '../actions/session_actions'; 7 | 8 | const nullUser = Object.freeze({ 9 | currentUser: null, 10 | errors: [] 11 | }); 12 | 13 | const SessionReducer = (state = nullUser, action) => { 14 | Object.freeze(state); 15 | switch(action.type) { 16 | case RECEIVE_CURRENT_USER: 17 | const currentUser = action.currentUser; 18 | return merge({}, nullUser, { 19 | currentUser 20 | }); 21 | case RECEIVE_ERRORS: 22 | return merge({}, nullUser, { 23 | errors: action.errors 24 | }); 25 | case CLEAR_ERRORS: 26 | return merge({}, nullUser); 27 | default: 28 | return state; 29 | } 30 | }; 31 | 32 | export default SessionReducer; 33 | -------------------------------------------------------------------------------- /frontend/components/user/user_container.js: -------------------------------------------------------------------------------- 1 | import User from './user'; 2 | import values from 'lodash/values'; 3 | import { connect } from 'react-redux'; 4 | import {withRouter} from 'react-router'; 5 | import { requestOneUser, requestUserPosts, requestUserComments } from '../../actions/user_actions'; 6 | 7 | 8 | 9 | const mapStateToProps = ({user}) => ({ 10 | user: user.user, 11 | posts: values(user.posts), 12 | comments: values(user.comments) 13 | }); 14 | 15 | const mapDispatchToProps = (dispatch) => ({ 16 | requestOneUser: (id) => dispatch(requestOneUser(id)), 17 | requestUserComments: (id, parent_type) => dispatch(requestUserComments(id, parent_type)), 18 | requestUserPosts: (id, type) => dispatch(requestUserPosts(id, type)) 19 | }); 20 | 21 | export default withRouter(connect( 22 | mapStateToProps, 23 | mapDispatchToProps 24 | )(User)); 25 | -------------------------------------------------------------------------------- /test/fixtures/users.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # username :string not null 7 | # password_digest :string not null 8 | # session_token :string not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # votes :integer default(0) 12 | # 13 | 14 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 15 | 16 | # This model initially had no columns defined. If you add columns to the 17 | # model remove the '{}' from the fixture names and add the columns immediately 18 | # below each fixture, per the syntax in the comments below 19 | # 20 | one: {} 21 | # column: value 22 | # 23 | two: {} 24 | # column: value 25 | -------------------------------------------------------------------------------- /app/views/api/posts/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | @posts.each do |post| 2 | json.set! post.id do 3 | json.title post.title 4 | json.id post.id 5 | json.view_count post.impressionist_count(:filter => :ip_address) 6 | json.description post.description 7 | json.user_id post.user.id 8 | json.user_name post.user.username 9 | json.totalvotes post.upvote_count - post.downvote_count 10 | json.main_image post.main_image ? asset_path(post.main_image.image.url(:thumb)) : image_path('logo.png') 11 | json.created_at (post.created_at.to_f * 1000).floor 12 | if current_user 13 | vote = Vote.where('user_id = :id and voteable_id = :post and voteable_type = :type', {id: current_user.id, post: post.id, type: 'Post'}) 14 | 15 | json.vote vote[0] ? vote[0] : nil 16 | else 17 | json.voted false 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /frontend/components/posts/post_index_container.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PostIndex from './post_index'; 4 | import { requestAllPosts, requestOnePost } from '../../actions/post_actions'; 5 | import { selectAllPosts } from '../../reducers/selectors'; 6 | 7 | const mapStateToProps = (state) => { 8 | let userId = 0; 9 | if (state.session.currentUser){ userId = state.session.currentUser.id} 10 | 11 | return { 12 | posts: selectAllPosts(state.post.entities), 13 | currentUser: userId 14 | }; 15 | }; 16 | 17 | const mapDispatchToProps = (dispatch, ownProps) => { 18 | return { 19 | requestAllPosts: (page) => dispatch(requestAllPosts(page)), 20 | requestOnePost: (id) => dispatch(requestOnePost(id)) 21 | }; 22 | }; 23 | 24 | export default connect( 25 | mapStateToProps, 26 | mapDispatchToProps 27 | )(PostIndex); 28 | -------------------------------------------------------------------------------- /frontend/components/posts/image_show.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import PostZoom from './post_zoom'; 5 | 6 | const ImageShow = (props) => ( 7 |
8 | props.displayModal()}/> 10 |
11 | ); 12 | 13 | import { displayModal } from '../../actions/modal_actions'; 14 | 15 | const mapStateToProps = (state) => { 16 | return { 17 | modal: Boolean(state.dropdown.uploadModal) 18 | }; 19 | }; 20 | 21 | const mapDispatchToProps = (dispatch) => { 22 | return { 23 | displayModal: (component) => dispatch(displayModal(component)) 24 | }; 25 | }; 26 | 27 | 28 | 29 | export default connect( 30 | mapStateToProps, 31 | mapDispatchToProps 32 | )(ImageShow); 33 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/back_to_top.scss: -------------------------------------------------------------------------------- 1 | #myBtn { 2 | display: none; 3 | /* Hidden by default */ 4 | position: fixed; 5 | /* Fixed/sticky position */ 6 | bottom: 20px; 7 | /* Place the button at the bottom of the page */ 8 | right: 30px; 9 | /* Place the button 30px from the right */ 10 | z-index: 99; 11 | /* Make sure it does not overlap */ 12 | outline: none; 13 | /* Remove outline */ 14 | background-color: red; 15 | /* Set a background color */ 16 | color: white; 17 | /* Text color */ 18 | cursor: pointer; 19 | /* Add a mouse pointer on hover */ 20 | border: none; 21 | /* Remove borders */ 22 | border-radius: 8px 8px 8px 0; 23 | background-color: #53565f; 24 | } 25 | // padding: 15px; /* Some padding */ 26 | // border-radius: 10px; /* Rounded corners */ 27 | 28 | #myBtn:hover { 29 | background-color: #555; 30 | /* Add a dark-grey background on hover */ 31 | } 32 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | 4 | # path to your application root. 5 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 6 | 7 | Dir.chdir APP_ROOT do 8 | # This script is a starting point to setup your application. 9 | # Add necessary setup steps to this file: 10 | 11 | puts "== Installing dependencies ==" 12 | system "gem install bundler --conservative" 13 | system "bundle check || bundle install" 14 | 15 | # puts "\n== Copying sample files ==" 16 | # unless File.exist?("config/database.yml") 17 | # system "cp config/database.yml.sample config/database.yml" 18 | # end 19 | 20 | puts "\n== Preparing database ==" 21 | system "bin/rake db:setup" 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system "rm -f log/*" 25 | system "rm -rf tmp/cache" 26 | 27 | puts "\n== Restarting application server ==" 28 | system "touch tmp/restart.txt" 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/api/images_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::ImagesController < ApplicationController 2 | 3 | def create 4 | @image = Image.new(image_params) 5 | klass = image_params[:imageable_type] == "Post" ? Post : Comment 6 | @imageable_item = klass.find(image_params[:imageable_id]) 7 | 8 | if @image.save 9 | instance_variable_set("@#{klass}".downcase, @imageable_item) 10 | 11 | render "/api/#{"#{klass}".downcase}s/show" 12 | else 13 | render json: @image.errors.full_messages, status: 422 14 | end 15 | 16 | end 17 | 18 | def index 19 | @images = Image.all 20 | end 21 | 22 | def show 23 | @image = Image.find(params[:id]) 24 | end 25 | 26 | def destroy 27 | @image = Image.find(params[:id]) 28 | end 29 | 30 | private 31 | 32 | def image_params 33 | params.require(:image).permit(:image, :description, :main_image, :imageable_id, :imageable_type) 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /frontend/components/posts/post_show.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Link} from 'react-router-dom'; 3 | import PostDetail from './post_detail'; 4 | 5 | 6 | class PostShow extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | 11 | 12 | componentDidMount(){ 13 | this.props.requestOnePost(this.props.id); 14 | } 15 | 16 | componentWillReceiveProps(nextProps) { 17 | if (this.props.match.params.id !== nextProps.match.params.id) { 18 | this.props.requestOnePost(nextProps.match.params.id); 19 | } 20 | } 21 | 22 | 23 | render(){ 24 | 25 | const {post, id} = this.props; 26 | if (post[id]){ 27 | return ( 28 | 29 | ); 30 | } else { 31 | return (
32 | 33 |
); 34 | 35 | } 36 | } 37 | 38 | } 39 | 40 | export default PostShow; 41 | -------------------------------------------------------------------------------- /app/models/image.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: images 4 | # 5 | # id :integer not null, primary key 6 | # description :text 7 | # main_image :boolean default(TRUE), not null 8 | # imageable_id :integer 9 | # imageable_type :string 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # image_file_name :string 13 | # image_content_type :string 14 | # image_file_size :integer 15 | # image_updated_at :datetime 16 | # 17 | 18 | class Image < ActiveRecord::Base 19 | validates :imageable_id, :imageable_type, presence: true 20 | belongs_to :imageable, polymorphic: true 21 | 22 | 23 | 24 | has_attached_file :image, styles: { 25 | thumb: { geometry: "280>", animated: false } }, 26 | default_url: 'senante.jpg' 27 | validates_attachment_content_type :image, content_type: /\Aimage\/.*\z/ 28 | end 29 | -------------------------------------------------------------------------------- /frontend/util/route_util.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { Route, Redirect, withRouter } from 'react-router-dom'; 4 | 5 | const Auth = ({ component: Component, path, loggedIn }) => ( 6 | ( 7 | !loggedIn ? ( 8 | 9 | ) : ( 10 | 11 | ) 12 | )} /> 13 | ); 14 | 15 | const Protected = ({ component: Component, path, loggedIn }) => ( 16 | ( 17 | loggedIn ? ( 18 | 19 | ) : ( 20 | 21 | ) 22 | )} /> 23 | ); 24 | 25 | const mapStateToProps = state => ( 26 | {loggedIn: Boolean(state.session.currentUser)} 27 | ); 28 | 29 | export const AuthRoute = withRouter(connect(mapStateToProps, null)(Auth)); 30 | 31 | export const ProtectedRoute = withRouter(connect(mapStateToProps, null)(Protected)); 32 | -------------------------------------------------------------------------------- /frontend/components/modal.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import {displayModal, clearModals} from '../actions/modal_actions'; 4 | 5 | class Modal extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | } 9 | 10 | render(){ 11 | if (this.props.isOpen) { 12 | return( 13 |
this.props.clearModals()}> 15 |
16 | {this.props.component} 17 |
18 |
19 | ); 20 | } else { return null;} 21 | } 22 | } 23 | 24 | const mapStateToProps = (state) => ({ 25 | isOpen: state.modal.isOpen, 26 | component: state.modal.component 27 | }); 28 | 29 | const mapDispatchToProps = (dispatch) => ({ 30 | clearModals: () => dispatch(clearModals()) 31 | }); 32 | 33 | 34 | export default connect(mapStateToProps, mapDispatchToProps)(Modal); 35 | -------------------------------------------------------------------------------- /test/fixtures/images.yml: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: images 4 | # 5 | # id :integer not null, primary key 6 | # description :text 7 | # main_image :boolean default(TRUE), not null 8 | # imageable_id :integer 9 | # imageable_type :string 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # image_file_name :string 13 | # image_content_type :string 14 | # image_file_size :integer 15 | # image_updated_at :datetime 16 | # 17 | 18 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 19 | 20 | # This model initially had no columns defined. If you add columns to the 21 | # model remove the '{}' from the fixture names and add the columns immediately 22 | # below each fixture, per the syntax in the comments below 23 | # 24 | one: {} 25 | # column: value 26 | # 27 | two: {} 28 | # column: value 29 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | # Prevent CSRF attacks by raising an exception. 3 | # For APIs, you may want to use :null_session instead. 4 | protect_from_forgery with: :exception 5 | helper_method :current_user, :logged_in? 6 | 7 | private 8 | 9 | def current_user 10 | return nil unless session[:session_token] 11 | @current_user ||= User.find_by(session_token: session[:session_token]) 12 | end 13 | 14 | def logged_in? 15 | !!current_user 16 | end 17 | 18 | def login(user) 19 | user.reset_session_token! 20 | session[:session_token] = user.session_token 21 | @current_user = user 22 | end 23 | 24 | def logout 25 | current_user.reset_session_token! 26 | session[:session_token] = nil 27 | @current_user = nil 28 | end 29 | 30 | def require_logged_in 31 | render json: {base: ['invalid credentials']}, status: 401 if !current_user 32 | end 33 | 34 | end 35 | -------------------------------------------------------------------------------- /frontend/components/comments/comments_index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Comment from './comments_index_item_container'; 3 | 4 | 5 | class CommentsIndex extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | } 9 | 10 | render(){ 11 | if (this.props.comments) { 12 | const comments = this.props.comments.sort((a, b) => (b.id - a.id)); 13 | return ( 14 |
15 |
16 | {comments.map( (comment) => { 17 | if(comment) { 18 | return ( 19 | ); 21 | } 22 | } 23 | )} 24 |
25 |
26 | ); 27 | } else { 28 | return null; 29 | } 30 | } 31 | } 32 | 33 | export default CommentsIndex; 34 | -------------------------------------------------------------------------------- /frontend/components/upload_button/upload_button_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import {withRouter} from 'react-router'; 3 | import UploadModalContent from './upload_button'; 4 | import { uploadImage } from '../../actions/image_actions'; 5 | import { createPost, deletePost } from '../../actions/post_actions'; 6 | import {clearModals} from '../../actions/modal_actions'; 7 | 8 | const mapStateToProps = ({ session }) => { 9 | return { 10 | loggedIn: Boolean(session.currentUser), 11 | errors: session.errors 12 | }; 13 | }; 14 | 15 | const mapDispatchToProps = (dispatch) => { 16 | return { 17 | uploadImage: (image) => dispatch(uploadImage(image)), 18 | createPost: (post, image) => dispatch(createPost(post, image)), 19 | clearModals: () => dispatch(clearModals()), 20 | deletePost: (id) => dispatch(deletePost(id)) 21 | }; 22 | }; 23 | 24 | export default withRouter(connect( 25 | mapStateToProps, 26 | mapDispatchToProps 27 | )(UploadModalContent)); 28 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: fbb19dcf10d0d7c13c4b2f592cd0d7484234c5169740b0309d3aefe99a3e7058f71a4ac3f06af4b32df9fabae0c3114f45108c6678c0de102c1534c59444efbb 15 | 16 | test: 17 | secret_key_base: 1b2459d630e19d595eb14bd91d12c05cd9157a698079b1124f02d9c90a1f1db40cc215b757bbbe74462c8d58faff3b2613da1746756dd7950c72e5ce006c3a8f 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /frontend/components/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Switch, Route } from 'react-router-dom'; 3 | import { AuthRoute } from '../util/route_util'; 4 | import SessionForm from './session_form/session_form_container'; 5 | import PostIndex from './posts/post_index_container'; 6 | import PostShow from './posts/post_show_container'; 7 | import UploadPage from './upload_button/upload_page'; 8 | import User from './user/user_container'; 9 | 10 | 11 | const Main = ({clearDropdowns}) => ( 12 |
clearDropdowns()}> 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | ); 24 | 25 | 26 | export default Main; 27 | -------------------------------------------------------------------------------- /frontend/invaderGir.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | 4 | import Root from './components/root'; 5 | import configureStore from './store/store'; 6 | import * as SessionActions from './actions/session_actions'; 7 | import * as PostActions from './actions/post_actions'; 8 | import * as UserActions from './actions/user_actions'; 9 | 10 | 11 | document.addEventListener('DOMContentLoaded', () => { 12 | let store; 13 | window.logout = SessionActions.logout; 14 | if (window.currentUser) { 15 | const preloadedState = { session: { currentUser: window.currentUser } }; 16 | store = configureStore(preloadedState); 17 | delete window.currentUser; 18 | } else { 19 | store = configureStore(); 20 | } 21 | 22 | // window.createPost = PostActions.createPost; 23 | // window.requestUserPosts = UserActions.requestUserPosts; 24 | // window.requestUserComments = UserActions.requestUserComments; 25 | // window.dispatch = store.dispatch; 26 | const root = document.getElementById('root'); 27 | ReactDOM.render(, root); 28 | }); 29 | -------------------------------------------------------------------------------- /frontend/components/comments/comments_index_item_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import { withRouter } from 'react-router-dom'; 3 | import CommentsIndexItem from './comments_index_item'; 4 | import { requestOneComment } from '../../actions/comment_actions'; 5 | import { displayModal } from '../../actions/modal_actions'; 6 | import {editVote, createVote, deleteVote } from '../../actions/vote_actions'; 7 | 8 | 9 | const mapStateToProps = ({ session }) => { 10 | 11 | return { 12 | loggedIn: Boolean(session.currentUser), 13 | }; 14 | }; 15 | 16 | const mapDispatchToProps = dispatch => { 17 | 18 | return { 19 | displayModal: (component) => dispatch(displayModal(component)), 20 | requestOneComment: (id) => dispatch(requestOneComment(id)), 21 | createVote: (voteData) => dispatch(createVote(voteData)), 22 | editVote: (voteData) => dispatch(editVote(voteData)), 23 | deleteVote: (id) => dispatch(deleteVote(id)) 24 | 25 | }; 26 | }; 27 | 28 | export default withRouter(connect( 29 | mapStateToProps, 30 | mapDispatchToProps 31 | )(CommentsIndexItem)); 32 | -------------------------------------------------------------------------------- /app/views/api/posts/_post.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.title post.title 2 | json.id post.id 3 | json.description post.description 4 | if current_user 5 | vote = Vote.where('user_id = :id and voteable_id = :post and voteable_type = :type', id: current_user.id, post: post.id, type: 'Post') 6 | json.vote vote[0] ? vote[0] : nil 7 | else 8 | json.voted false 9 | end 10 | json.user_id post.user.id 11 | json.user_name post.user.username 12 | json.upvotes post.upvote_count 13 | json.downvotes post.downvote_count 14 | json.totalvotes post.upvote_count - post.downvote_count 15 | if post.images 16 | post.images.each do |_image| 17 | json.main_image asset_path(post.main_image.image.url) 18 | json.images post.images.each do |image| 19 | json.image_url asset_path(image.image.url) 20 | end 21 | end 22 | end 23 | 24 | if comments 25 | json.comment_ids comments.map(&:id) 26 | json.comments do 27 | @post.comments.map do |comment| 28 | json.set! comment.id do 29 | json.partial! 'api/comments/show', comment: comment 30 | end 31 | end 32 | end 33 | 34 | else 35 | json.comment_ids [] 36 | end 37 | -------------------------------------------------------------------------------- /frontend/components/session_form/session_form_container.js: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux'; 2 | import {withRouter} from 'react-router-dom'; 3 | 4 | import { login, logout, signup, receiveErrors, receiveCurrentUser, clearErrors } from '../../actions/session_actions'; 5 | import SessionForm from './session_form'; 6 | 7 | 8 | const mapStateToProps = ({ session }) => { 9 | return { 10 | loggedIn: Boolean(session.currentUser), 11 | errors: session.errors 12 | }; 13 | }; 14 | 15 | const mapDispatchToProps = (dispatch, { location }) => { 16 | const formType = location.pathname.slice(1); 17 | const processForm = (formType === 'login') ? login : signup; 18 | return { 19 | receiveErrors: errors => dispatch(receiveErrors(errors)), 20 | receiveCurrentUser: (id) => dispatch(receiveCurrentUser(id)), 21 | clearErrors: () => dispatch(clearErrors()), 22 | login: (user) => dispatch(login(user)), 23 | processForm: user => dispatch(processForm(user)), 24 | formType 25 | }; 26 | }; 27 | 28 | export default withRouter(connect( 29 | mapStateToProps, 30 | mapDispatchToProps 31 | )(SessionForm)); 32 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: comments 4 | # 5 | # id :integer not null, primary key 6 | # user_id :integer not null 7 | # parent_id :integer 8 | # parent_type :string 9 | # body :string 10 | # created_at :datetime not null 11 | # updated_at :datetime not null 12 | # post_id :integer 13 | # 14 | 15 | class Comment < ActiveRecord::Base 16 | validates :user, :body, presence: true 17 | after_create :self_vote 18 | 19 | def self_vote 20 | self.user.increment!(:votes) 21 | Vote.create!(user_id: self.user.id, voteable_type: 'Comment', voteable_id: self.id, vote_type: %w[Upvote].sample) 22 | end 23 | belongs_to :user 24 | belongs_to :post 25 | has_one :main_image, 26 | through: :post 27 | belongs_to :parent, polymorphic: true 28 | has_many :upvotes, -> { where vote_type: 'Upvote' }, as: :voteable, class_name: 'Vote' 29 | has_many :downvotes, -> { where vote_type: 'Downvote' }, as: :voteable, class_name: 'Vote' 30 | has_many :replies, as: :parent, dependent: :destroy, 31 | class_name: 'Comment' 32 | end 33 | -------------------------------------------------------------------------------- /frontend/reducers/user_reducer.js: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | 3 | import { 4 | RECEIVE_ONE_USER, 5 | RECEIVE_USER_POSTS, 6 | RECEIVE_USER_COMMENTS 7 | } from '../actions/user_actions'; 8 | 9 | import { 10 | DESTROY_POST 11 | } from '../actions/post_actions'; 12 | 13 | const nullUser = Object.freeze({ 14 | user: null, 15 | }); 16 | 17 | const UserReducer = (state = nullUser, action) => { 18 | const newState = Object.assign({}, state); 19 | switch (action.type) { 20 | case RECEIVE_ONE_USER: 21 | const user = action.user; 22 | return merge({}, state, { 23 | user 24 | }); 25 | case DESTROY_POST: 26 | if (newState.posts && newState.posts[action.post.id]){ 27 | delete newState.posts[action.post.id] 28 | } 29 | return newState 30 | case RECEIVE_USER_POSTS: 31 | const posts = action.posts; 32 | return Object.assign({}, newState, { posts }); 33 | case RECEIVE_USER_COMMENTS: 34 | const comments = action.comments; 35 | return Object.assign({}, newState, { comments }); 36 | default: 37 | return state; 38 | } 39 | }; 40 | 41 | export default UserReducer; 42 | -------------------------------------------------------------------------------- /frontend/actions/session_actions.js: -------------------------------------------------------------------------------- 1 | import * as APIUtil from '../util/session_api_util'; 2 | 3 | export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER'; 4 | export const RECEIVE_ERRORS = 'RECEIVE_ERRORS'; 5 | export const CLEAR_ERRORS = 'CLEAR_ERRORS'; 6 | 7 | export const receiveCurrentUser = currentUser => ({ 8 | type: RECEIVE_CURRENT_USER, 9 | currentUser 10 | }); 11 | 12 | export const receiveErrors = errors => ({ 13 | type: RECEIVE_ERRORS, 14 | errors 15 | }); 16 | export const clearErrors = () => ({ 17 | type: CLEAR_ERRORS, 18 | }); 19 | 20 | 21 | export const signup = userData => dispatch => ( 22 | APIUtil.signup(userData).then(user => ( 23 | dispatch(receiveCurrentUser(user)) 24 | ), err => ( 25 | dispatch(receiveErrors(err.responseJSON)) 26 | )) 27 | ); 28 | 29 | export const login = userData => dispatch => ( 30 | APIUtil.login(userData).then(user => ( 31 | dispatch(receiveCurrentUser(user)) 32 | ), err => ( 33 | dispatch(receiveErrors(err.responseJSON)) 34 | )) 35 | ); 36 | 37 | export const logout = () => dispatch => ( 38 | APIUtil.logout().then(user => ( 39 | dispatch(receiveCurrentUser(null)) 40 | )) 41 | ); 42 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/sidebar.scss: -------------------------------------------------------------------------------- 1 | .side-bar{ 2 | height: 400px; 3 | box-sizing: border-box; 4 | overflow: auto; 5 | } 6 | 7 | .side-bar-items{ 8 | border-radius: 15px; 9 | overflow-y: scroll hidden; 10 | border: 0; 11 | margin: 0; 12 | padding: 0; 13 | } 14 | 15 | .bottom-fade{ 16 | height: 50px; 17 | width: 100%;; 18 | bottom: 0; 19 | position: sticky; 20 | background: linear-gradient(to bottom,transparent 0,#141518 100%); 21 | pointer-events: none; 22 | } 23 | 24 | .side-bar-item-pic { 25 | max-width: 90px; 26 | max-height: 90px; 27 | 28 | } 29 | .side-bar-item-pic img{ 30 | height: 78px; 31 | width: 90px; 32 | object-fit: cover; 33 | overflow: hidden; 34 | 35 | } 36 | 37 | .side-bar-item{ 38 | border-radius: 5px; 39 | background: #34373c; 40 | display: flex; 41 | justify-content: space-between; 42 | width: 300px; 43 | padding: 8px; 44 | margin: 5px 20px; 45 | height: 80px; 46 | } 47 | 48 | .side-bar-item-info { 49 | width: 210px; 50 | overflow: hidden; 51 | } 52 | 53 | .side-bar-item-info p{ 54 | word-wrap: break-word; 55 | font-size: 15px; 56 | color: white; 57 | margin-left: 20px; 58 | } 59 | 60 | 61 | ::-webkit-scrollbar { 62 | display: none; 63 | } 64 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/index.scss: -------------------------------------------------------------------------------- 1 | .image-index-item { 2 | display: inline-block; 3 | box-sizing: border-box; 4 | position: relative; 5 | margin: 8px 4px; 6 | background-color: #181817; 7 | line-height: 0; 8 | } 9 | 10 | div.gallery div.image-index-item { 11 | width: 180px; 12 | height: 180px; 13 | } 14 | 15 | div.user div.image-index-item { 16 | width: 150px; 17 | height: 150px; 18 | } 19 | 20 | .index-no-overflow { 21 | height: inherit; 22 | width: inherit; 23 | } 24 | 25 | div.gallery div.image-list-link {} 26 | 27 | .image-list-link { 28 | width: 200px; 29 | height: 200px; 30 | object-fit: fill; 31 | overflow: hidden; 32 | outline: #121211 solid 1px; 33 | outline-offset: -1px; 34 | } 35 | 36 | div.user div.image-list-link { 37 | width: 100px; 38 | height: 100px; 39 | } 40 | 41 | .index-no-overflow { 42 | height: inherit; 43 | width: inherit; 44 | } 45 | 46 | .five-three { 47 | width: 1000px; 48 | } 49 | 50 | .top-90 { 51 | top: 90px; 52 | } 53 | 54 | .post-index-container { 55 | border-radius: 5px; 56 | padding: 10px; 57 | background-color: #34373c; 58 | margin: 0 auto; 59 | position: relative; 60 | display: flex; 61 | flex-wrap: wrap; 62 | justify-content: space-around; 63 | } 64 | -------------------------------------------------------------------------------- /frontend/reducers/comment_reducer.js: -------------------------------------------------------------------------------- 1 | import merge from 'lodash/merge'; 2 | 3 | import { 4 | RECEIVE_ONE_COMMENT, 5 | RECEIVE_ALL_COMMENTS, 6 | } from '../actions/comment_actions'; 7 | 8 | import { 9 | RECEIVE_ONE_POST 10 | } from '../actions/post_actions'; 11 | 12 | const defaultState = Object.freeze({ 13 | entities: {}, 14 | }); 15 | 16 | const CommentReducer = (state = defaultState, action) => { 17 | Object.freeze(state); 18 | switch (action.type) { 19 | case RECEIVE_ONE_POST: 20 | const comments = action.post.comments; 21 | let newState = merge({}, state, { 22 | entities: comments 23 | }); 24 | return newState; 25 | case RECEIVE_ONE_COMMENT: 26 | const comment = action.comment; 27 | const parentType = comment.parent_type; 28 | newState = merge({}, state, { 29 | entities: {} 30 | }); 31 | if (parentType === 'Comment' && !newState.entities[comment.parent_id].comment_ids.includes(comment.id) ) { 32 | 33 | newState.entities[comment.parent_id].comment_ids.push(comment.id); 34 | } 35 | newState.entities[comment.id] = comment; 36 | 37 | return newState; 38 | default: 39 | return state; 40 | } 41 | }; 42 | 43 | export default CommentReducer; 44 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | // webpack.config.js 2 | var path = require("path"); 3 | var webpack = require("webpack"); 4 | 5 | var plugins = []; // if using any plugins for both dev and production 6 | var devPlugins = []; // if using any plugins for development 7 | 8 | var prodPlugins = [ 9 | new webpack.DefinePlugin({ 10 | 'process.env': { 11 | 'NODE_ENV': JSON.stringify('production') 12 | } 13 | }), 14 | new webpack.optimize.UglifyJsPlugin({ 15 | compress: { 16 | warnings: true 17 | } 18 | }) 19 | ]; 20 | 21 | plugins = plugins.concat( 22 | process.env.NODE_ENV === 'production' ? prodPlugins : devPlugins 23 | ); 24 | 25 | // include plugins config 26 | module.exports = { 27 | context: __dirname, 28 | entry: "./frontend/invaderGir.jsx", 29 | output: { 30 | path: path.resolve(__dirname, "app", "assets", "javascripts"), 31 | filename: "bundle.js" 32 | }, 33 | plugins: plugins, 34 | module: { 35 | loaders: [ 36 | { 37 | test: /\.jsx?$/, 38 | exclude: /node_modules/, 39 | loader: 'babel-loader', 40 | query: { 41 | presets: ['react', 'es2015'] 42 | } 43 | } 44 | ] 45 | }, 46 | devtool: 'source-map', 47 | resolve: { 48 | extensions: [".js", ".jsx", "*"] 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /app/views/static_pages/root.html.erb: -------------------------------------------------------------------------------- 1 | 27 |
its not working
28 | -------------------------------------------------------------------------------- /frontend/actions/image_actions.js: -------------------------------------------------------------------------------- 1 | import * as ImageUtil from '../util/image_api_util'; 2 | 3 | export const RECEIVE_ALL_IMAGES = "RECEIVE_ALL_IMAGES"; 4 | export const RECEIVE_ONE_IMAGE = "RECEIVE_ONE_IMAGE"; 5 | export const RECEIVE_IMAGE_ERRORS = 'RECEIVE_IMAGE_ERRORS'; 6 | 7 | export const receiveAllImages = (images) => ({ 8 | type: RECEIVE_ALL_IMAGES, 9 | images 10 | }); 11 | 12 | export const receiveOneImage = (image) => ({ 13 | type: RECEIVE_ONE_IMAGE, 14 | image 15 | }); 16 | 17 | export const receiveImageErrors = errors => ({ 18 | type: RECEIVE_IMAGE_ERRORS, 19 | errors 20 | }); 21 | 22 | 23 | export const requestAllImages = () => (dispatch) => { 24 | return ImageUtil.fetchImages() 25 | .then(images => dispatch(receiveAllImages(images))); 26 | }; 27 | 28 | export const requestOneImage = (id) => (dispatch) => { 29 | return ImageUtil.fetchImage(id).then(image => { 30 | dispatch(receiveOneImage(image)); 31 | return image; 32 | }); 33 | }; 34 | 35 | export const uploadImage = imageData => dispatch => { 36 | 37 | return( 38 | ImageUtil.uploadImage(imageData).then(image => { 39 | dispatch(receiveOneImage(image)); 40 | return image; 41 | }).fail( 42 | err => { 43 | return( 44 | dispatch(receiveImageErrors(err.responseJSON)) 45 | ); 46 | }) 47 | );}; 48 | -------------------------------------------------------------------------------- /frontend/actions/comment_actions.js: -------------------------------------------------------------------------------- 1 | import * as CommentUtil from '../util/comment_api_util'; 2 | 3 | export const RECEIVE_ALL_COMMENTS = "RECEIVE_ALL_COMMENTS"; 4 | export const RECEIVE_ONE_COMMENT = "RECEIVE_ONE_COMMENT"; 5 | export const CREATE_COMMENT = "CREATE_COMMENT"; 6 | export const RECEIVE_COMMENT_ERRORS = 'RECEIVE_COMMENT_ERRORS'; 7 | 8 | export const receiveAllComments = (comments) => { 9 | 10 | return ({ 11 | type: RECEIVE_ALL_COMMENTS, 12 | comments 13 | }); 14 | }; 15 | 16 | 17 | export const receiveOneComment = (comment) => ({ 18 | type: RECEIVE_ONE_COMMENT, 19 | comment 20 | }); 21 | 22 | export const receiveCommentErrors = errors => ({ 23 | type: RECEIVE_COMMENT_ERRORS, 24 | errors 25 | }); 26 | 27 | 28 | export const requestAllComments = id => dispatch => { 29 | return CommentUtil.fetchComments(id) 30 | .then(comments => dispatch(receiveAllComments(comments))); 31 | }; 32 | 33 | export const requestOneComment = (id) => (dispatch) => { 34 | return CommentUtil.fetchComment(id).then(comment => { 35 | dispatch(receiveOneComment(comment)); 36 | return comment; 37 | }); 38 | }; 39 | 40 | export const createComment = commentdata => dispatch => ( 41 | CommentUtil.createComment(commentdata).then(comment => { 42 | dispatch(receiveOneComment(comment)); 43 | return comment; 44 | }).fail(err => dispatch(receiveCommentErrors(err.responseJSON))) 45 | ); 46 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/menu_drop_down.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | 4 | import MenuDropdownContents from './menu_dropdown_contents.jsx'; 5 | import { displayDropdown, clearDropdowns } 6 | from '../../actions/dropdown_actions'; 7 | 8 | 9 | class MenuDrop extends React.Component { 10 | constructor(props){ 11 | super(props); 12 | this.handleClick = this.handleClick.bind(this); 13 | } 14 | 15 | 16 | handleClick(e) { 17 | e.preventDefault(); 18 | e.stopPropagation(); 19 | this.props.displayDropdown({menuDropDown: !this.props.visible}); 20 | } 21 | 22 | render() { 23 | return ( 24 | 25 |
26 |
27 | 29 | { this.props.visible ? : null } 30 |
31 |
32 |
33 | ); 34 | } 35 | 36 | } 37 | 38 | 39 | const mapStateToProps = (state) => { 40 | return { 41 | visible: Boolean(state.dropdown.menuDropDown) 42 | }; 43 | }; 44 | 45 | const mapDispatchToProps = (dispatch) => { 46 | return { 47 | displayDropdown: (obj) => dispatch(displayDropdown(obj)), 48 | clearDropdowns: () => dispatch(clearDropdowns()) 49 | }; 50 | }; 51 | 52 | export default connect( 53 | mapStateToProps, 54 | mapDispatchToProps 55 | )(MenuDrop); 56 | -------------------------------------------------------------------------------- /frontend/reducers/post_reducer.js: -------------------------------------------------------------------------------- 1 | import { 2 | merge 3 | } from 'lodash'; 4 | 5 | import { 6 | RECEIVE_ONE_COMMENT, 7 | RECEIVE_ALL_COMMENTS, 8 | } from '../actions/comment_actions'; 9 | 10 | import { 11 | RECEIVE_ONE_POST, 12 | RECEIVE_ALL_POSTS, 13 | DESTROY_POST 14 | } from '../actions/post_actions'; 15 | 16 | import { 17 | RECEIVE_SEARCH 18 | } from '../actions/search_actions'; 19 | 20 | 21 | const defaultState = () => ({ 22 | entities: {}, 23 | currentPost: null 24 | }); 25 | 26 | const PostReducer = (state = defaultState(), action) => { 27 | const newState = Object.assign({}, state); 28 | switch (action.type) { 29 | case RECEIVE_ALL_POSTS: 30 | return merge({}, state, { 31 | entities: action.posts, 32 | }); 33 | case RECEIVE_ONE_POST: 34 | const post = action.post; 35 | return merge({}, state, { 36 | entities: { 37 | [post.id]: post 38 | }, 39 | currentPost: post.id 40 | }); 41 | case DESTROY_POST: 42 | delete newState.entities[action.post.id] 43 | return newState 44 | case RECEIVE_ONE_COMMENT: 45 | const comment = action.comment; 46 | const parentType = comment.parent_type; 47 | if (parentType === 'Post') { 48 | 49 | let newState = merge({}, state, { 50 | entities: {} 51 | }); 52 | newState.entities[comment.parent_id].comment_ids.push(comment.id); 53 | return newState; 54 | } 55 | 56 | default: 57 | return state; 58 | } 59 | }; 60 | 61 | export default PostReducer; 62 | -------------------------------------------------------------------------------- /frontend/actions/vote_actions.js: -------------------------------------------------------------------------------- 1 | import * as VoteUtil from '../util/vote_api_util'; 2 | import { 3 | receiveOnePost 4 | } from './post_actions'; 5 | import { 6 | receiveOneComment 7 | } from './comment_actions'; 8 | 9 | 10 | export const RECEIVE_ALL_POSTS = "RECEIVE_ALL_POSTS"; 11 | export const RECEIVE_ONE_POST = "RECEIVE_ONE_POST"; 12 | export const RECEIVE_VOTE_ERRORS = 'RECEIVE_VOTE_ERRORS'; 13 | export const RECEIVE_ERRORS = 'RECEIVE_ERRORS'; 14 | 15 | 16 | export const receiveErrors = errors => ({ 17 | type: RECEIVE_ERRORS, 18 | errors 19 | }); 20 | 21 | export const createVote = voteData => dispatch => { 22 | let action; 23 | voteData.vote.voteable_type === 'Post' ? 24 | action = receiveOnePost : 25 | action = receiveOneComment; 26 | 27 | return VoteUtil.createVote(voteData).then(votedItem => { 28 | dispatch(action(votedItem)); 29 | return votedItem; 30 | }); 31 | }; 32 | 33 | export const editVote = voteData => dispatch => { 34 | let action; 35 | 36 | voteData.vote.voteable_type === 'Post' ? 37 | action = receiveOnePost : 38 | action = receiveOneComment; 39 | 40 | VoteUtil.editVote(voteData).then(votedItem => { 41 | dispatch(action(votedItem)); 42 | return votedItem; 43 | }); 44 | }; 45 | export const deleteVote = (params) => dispatch => { 46 | let action; 47 | params.voteable_type === 'Post' ? 48 | action = receiveOnePost : 49 | action = receiveOneComment; 50 | 51 | VoteUtil.deleteVote(params).then(votedItem => { 52 | dispatch(action(votedItem)); 53 | // return votedItem; 54 | }); 55 | }; 56 | -------------------------------------------------------------------------------- /frontend/actions/post_actions.js: -------------------------------------------------------------------------------- 1 | import * as PostUtil from '../util/post_api_util'; 2 | 3 | export const RECEIVE_ALL_POSTS = "RECEIVE_ALL_POSTS"; 4 | export const RECEIVE_ONE_POST = "RECEIVE_ONE_POST"; 5 | export const RECEIVE_POST_ERRORS = 'RECEIVE_POST_ERRORS'; 6 | export const DESTROY_POST = 'DESTROY_POST' 7 | 8 | export const receiveAllPosts = (posts) => ({ 9 | type: RECEIVE_ALL_POSTS, 10 | posts 11 | }); 12 | 13 | export const destroyPost = (post) => ({ 14 | type: DESTROY_POST, 15 | post 16 | }) 17 | 18 | export const receiveOnePost = (post) => ({ 19 | type: RECEIVE_ONE_POST, 20 | post 21 | }); 22 | 23 | export const receivePostErrors = errors => ({ 24 | type: RECEIVE_POST_ERRORS, 25 | errors 26 | }); 27 | 28 | 29 | export const requestAllPosts = (page) => (dispatch) => { 30 | 31 | return PostUtil.fetchPosts(page) 32 | .then(posts => dispatch(receiveAllPosts(posts))); 33 | }; 34 | 35 | export const requestOnePost = (id) => (dispatch) => { 36 | return PostUtil.fetchPost(id).then(post => { 37 | dispatch(receiveOnePost(post)); 38 | return post; 39 | }); 40 | }; 41 | 42 | export const createPost = postdata => dispatch => ( 43 | PostUtil.createPost(postdata).then(post => { 44 | dispatch(receiveOnePost(post)); 45 | return post; 46 | }).fail(err => dispatch(receivePostErrors(err.responseJSON))) 47 | ); 48 | export const deletePost = id => dispatch => { 49 | return ( 50 | PostUtil.deletePost(id).then(post => { 51 | dispatch(destroyPost(post)); 52 | }).fail(err => dispatch(receivePostErrors(err.responseJSON))) 53 | )}; 54 | -------------------------------------------------------------------------------- /docs/api-endpoints.md: -------------------------------------------------------------------------------- 1 | # API Endpoints 2 | 3 | ## HTML 4 | 5 | ### Root 6 | 7 | Method | URI | Description 8 | ------ | --- | ------------------ 9 | `GET` | `/` | Loads Index (Feed) 10 | 11 | ### Users 12 | 13 | Method | URI | Description 14 | ------- | ---------------- | -------------------- 15 | `POST` | `/api/users` | Create new user 16 | `GET` | `/api/users/:id` | Get user id 17 | `PATCH` | `/api/users/:id` | Edit User attributes 18 | 19 | ## JSON 20 | 21 | ### Session 22 | 23 | Method | URI | Description 24 | -------- | -------------- | ----------- 25 | `POST` | `/api/session` | Log in 26 | `DELETE` | `/api/session` | Log out 27 | 28 | ### Posts 29 | 30 | Method | URI | Description 31 | -------- | --------------------------- | ------------------------ 32 | `GET` | `/api/posts` | Get all posts 33 | `POST` | `/api/posts` | Create new post 34 | `GET` | `/api/posts/:id` | Get post by id 35 | `DELETE` | `/api/posts/:id` | Delete post by id 36 | `GET` | `/api/users/:user_id/posts` | Get all posts by user id 37 | 38 | ### Comments 39 | 40 | Method | URI | Description 41 | -------- | ------------------------------ | --------------------------- 42 | `GET` | `/api/posts/:post_id/comments` | Get all comments for a post 43 | `POST` | `/api/posts/comments` | Create new comment 44 | `PATCH` | `/api/comments/:comment_id` | Edit comment by id 45 | `DELETE` | `/api/comments/:comment_id` | Delete comment by id 46 | `GET` | `/api/users/:user_id/comments` | Get all comments by user id 47 | -------------------------------------------------------------------------------- /frontend/components/comments/new_comment_form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createComment } from '../../actions/comment_actions'; 3 | 4 | class NewCommentForm extends React.Component{ 5 | constructor(props){ 6 | super(props); 7 | this.state = { 8 | body: '', 9 | } 10 | this.handleSubmit = this.handleSubmit.bind(this); 11 | 12 | } 13 | 14 | handleSubmit(e) { 15 | 16 | e.preventDefault(); 17 | if(this.props.loggedIn) { 18 | const comment = { body: this.state.body, 19 | commenter_id: this.props.currentaccountId, 20 | commentable_id: this.props.commentableId, 21 | commentable_type: this.props.commentableType, 22 | post_id: this.props.match.params.id, 23 | }; 24 | this.props.createComment(comment); 25 | } else { 26 | this.props.history.push('/login'); 27 | } 28 | } 29 | 30 | update(field) { 31 | return e => this.setState({ 32 | [field]: e.currentTarget.value 33 | }); 34 | } 35 | 36 | render() { 37 | return ( 38 |
39 |
40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 | ) 50 | } 51 | 52 | } 53 | 54 | export default NewCommentForm; 55 | -------------------------------------------------------------------------------- /app/controllers/api/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::CommentsController < ApplicationController 2 | def index 3 | if params[:user_id].present? && params[:parent_type].present? 4 | @comments = Comment.includes(:user, :main_image).where("user_id =(?) AND parent_type=(?)", params[:user_id], params[:parent_type]) 5 | render :user_comment 6 | else 7 | post = Post.find(params[:post_id]) 8 | @comments = post.comments.order(created_at: :desc) 9 | end 10 | end 11 | 12 | def show 13 | 14 | @comment = Comment.includes(:replies, :user).find(params[:id]) 15 | end 16 | 17 | def create 18 | 19 | @comment = Comment.new(comment_params) 20 | @comment.user_id = current_user.id 21 | klass = @comment.parent_type == "Post" ? Post : Comment 22 | 23 | @parent = klass.find(@comment.parent_id) 24 | @user = User.find(@comment.user_id) 25 | @user.increment!(:votes) 26 | @parent.increment!(:votes) 27 | if @comment.save 28 | render :show 29 | else 30 | render json: @comment.errors.full_messages, status: 422 31 | end 32 | end 33 | 34 | def destroy 35 | @comment = Comment.includes(:replies).find(params[:id]) 36 | @comment.destroy! 37 | end 38 | 39 | def update 40 | @comment = Comment.find(params[:id]) 41 | if @comment.update(comment_params) 42 | render :show 43 | else 44 | render json: @comment.errors.full_messages, status: 422 45 | end 46 | end 47 | 48 | 49 | private 50 | 51 | def comment_params 52 | params.require(:comment).permit(:body, :user_id, :post_id, :parent_id, :parent_type) 53 | end 54 | end -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path('../boot', __FILE__) 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module ZIMnGir 10 | class Application < Rails::Application 11 | # Settings in config/environments/* take precedence over those specified here. 12 | # Application configuration should go into files in config/initializers 13 | # -- all .rb files in that directory are automatically loaded. 14 | 15 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 16 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 17 | # config.time_zone = 'Central Time (US & Canada)' 18 | 19 | 20 | config.paperclip_defaults = { 21 | :storage => :s3, 22 | :s3_credentials => { 23 | :bucket => ENV['s3_bucket'], 24 | :s3_region => ENV['s3_region'], 25 | :access_key_id => ENV['s3_access_key_id'], 26 | :secret_access_key => ENV['s3_secret_access_key'], 27 | :s3_host_name => "s3-#{ENV["s3_region"]}.amazonaws.com", 28 | :url => ":s3_host_name" 29 | } 30 | } 31 | 32 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 33 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 34 | # config.i18n.default_locale = :de 35 | 36 | # Do not swallow errors in after_commit/after_rollback callbacks. 37 | config.active_record.raise_in_transactional_callbacks = true 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/models/post.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: posts 4 | # 5 | # id :integer not null, primary key 6 | # title :string not null 7 | # description :text 8 | # user_id :integer not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # impression_count :integer 12 | # 13 | 14 | class Post < ActiveRecord::Base 15 | is_impressionable :counter_cache => true, :column_name => :impression_count 16 | validates :title, :user, presence: true 17 | after_create :self_vote 18 | belongs_to :user 19 | 20 | has_one :main_image, 21 | -> { where main_image: true }, 22 | class_name: 'Image', 23 | foreign_key: :imageable_id, 24 | dependent: :destroy 25 | has_many :votes 26 | 27 | has_many :upvotes, -> { where vote_type: 'Upvote' }, as: :voteable, class_name: "Vote", dependent: :destroy 28 | has_many :downvotes, -> { where vote_type: 'Downvote' }, as: :voteable, class_name: "Vote", dependent: :destroy 29 | has_many :comments, as: :parent, dependent: :destroy 30 | has_many :images, as: :imageable, dependent: :destroy 31 | 32 | 33 | def downvote_count 34 | Vote.where(voteable_id: self.id, voteable_type: 'Post', vote_type: 'Downvote').count 35 | end 36 | def upvote_count 37 | Vote.where(voteable_id: self.id, voteable_type: 'Post', vote_type: 'Upvote').count 38 | end 39 | 40 | def self_vote 41 | self.user.increment!(:votes) 42 | Vote.create!(user_id: self.user.id, voteable_type: 'Post', voteable_id: self.id, vote_type: %w[Upvote].sample) 43 | end 44 | 45 | end 46 | -------------------------------------------------------------------------------- /frontend/components/app.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import {Switch, Route, withRouter } from 'react-router-dom'; 4 | import Header from './nav_bar/header'; 5 | import Modal from './modal'; 6 | import Main from './main_container'; 7 | import UploadModalContent from './upload_button/upload_button_container'; 8 | 9 | const App = (props) => { 10 | window.onscroll = function() { 11 | scrollFunction(); 12 | }; 13 | 14 | function scrollFunction() { 15 | if (document.body.scrollTop > 20 || document.documentElement.scrollTop > 20) { 16 | document 17 | .getElementById("myBtn") 18 | .style 19 | .display = "block"; 20 | } else { 21 | document 22 | .getElementById("myBtn") 23 | .style 24 | .display = "none"; 25 | } 26 | } 27 | 28 | function topFunction() { 29 | document.body.scrollTop = 0; // For Chrome, Safari and Opera 30 | document.documentElement.scrollTop = 0; // For IE and Firefox 31 | } 32 | 33 | return ( 34 |
35 | 36 | 37 |
38 |
39 |
); 40 | }; 41 | 42 | import { displayModal } from '../actions/modal_actions'; 43 | 44 | const mapStateToProps = (state) => { 45 | return { 46 | loggedIn: Boolean(state.session.currentUser), 47 | visible: Boolean(state.dropdown.uploadDropdown), 48 | modal: Boolean(state.dropdown.uploadModal) 49 | }; 50 | }; 51 | 52 | const mapDispatchToProps = dispatch => ({ 53 | displayModal: (content) => dispatch(displayModal(content)) 54 | }); 55 | 56 | export default withRouter(connect(null, mapDispatchToProps)(App)); 57 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
60 |
61 |

We're sorry, but something went wrong.

62 |
63 |

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

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/left_side.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import SearchBar from './search_bar_container'; 4 | import { Router, Route, Link} from 'react-router-dom'; 5 | 6 | 7 | const LeftSide = ({loggedIn, logout, user}) => { 8 | if (!loggedIn) { 9 | return ( 10 |
    11 |
  • 12 | 13 |
  • 14 |
  • 15 | sign in 16 |
  • 17 |
  • 18 | sign up 19 |
  • 20 |
21 | ); 22 | } else { 23 | return ( 24 |
    25 |
  • 26 | 27 |
  • 28 |
  • logout()}> 29 | Logout 30 |
  • 31 |
  • 32 | {user.username} 34 |
  • 35 |
36 | ); 37 | } 38 | }; 39 | 40 | import {logout} from '../../actions/session_actions'; 41 | 42 | 43 | const mapStateToProps = ({ session }) => { 44 | return { 45 | loggedIn: Boolean(session.currentUser), 46 | user: session.currentUser, 47 | errors: session.errors 48 | }; 49 | }; 50 | const mapDispatchToProps = (dispatch) => { 51 | return { 52 | logout: () => dispatch(logout()) 53 | }; 54 | }; 55 | 56 | export default connect(mapStateToProps, mapDispatchToProps)(LeftSide); 57 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | 4 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails' 5 | gem 'rails', '4.2.8' 6 | # Use postgresql as the database for Active Record 7 | gem 'figaro' 8 | gem 'aws-sdk', '>= 2.0' 9 | gem 'paperclip', '5.0.0.beta1' 10 | gem 'pg', '~> 0.20' 11 | gem 'rails_12factor' 12 | gem 'pry-rails' 13 | gem 'faker' 14 | gem 'annotate' 15 | gem 'impressionist' 16 | # Use SCSS for stylesheets 17 | gem 'sass-rails', '~> 5.0' 18 | # Use Uglifier as compressor for JavaScript assets 19 | gem 'uglifier', '>= 1.3.0' 20 | # Use CoffeeScript for .coffee assets and views 21 | gem 'coffee-rails', '~> 4.1.0' 22 | # See https://github.com/rails/execjs#readme for more supported runtimes 23 | # gem 'therubyracer', platforms: :ruby 24 | 25 | # Use jquery as the JavaScript library 26 | gem 'jquery-rails' 27 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder 28 | gem 'jbuilder', '~> 2.0' 29 | # bundle exec rake doc:rails generates the API under doc/api. 30 | gem 'sdoc', '~> 0.4.0', group: :doc 31 | 32 | # Use ActiveModel has_secure_password 33 | gem 'bcrypt', platforms: :ruby 34 | # Use Unicorn as the app server 35 | # gem 'unicorn' 36 | 37 | # Use Capistrano for deployment 38 | # gem 'capistrano-rails', group: :development 39 | 40 | group :development, :test do 41 | # Call 'byebug' anywhere in the code to stop execution and get a // console 42 | gem 'bullet' 43 | gem 'byebug' 44 | gem 'slack-notifier' 45 | end 46 | 47 | group :development do 48 | # Access an IRB console on exception pages or by using <%= console %> in views 49 | gem 'web-console', '~> 2.0' 50 | 51 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring 52 | gem 'spring' 53 | end 54 | -------------------------------------------------------------------------------- /frontend/components/user/user_comments.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | NavLink, 4 | Link 5 | } from 'react-router-dom'; 6 | import Moment from 'react-moment'; 7 | 8 | 9 | export default class UserComments extends React.Component { 10 | constructor( props ) { 11 | super( props ); 12 | this.displayInfo = this.displayInfo.bind( this ); 13 | } 14 | 15 | 16 | displayInfo() { 17 | 18 | if ( this.props.comments.length ) { 19 | return this.props.comments.sort( ( a, b ) => ( b.time_since - a.time_since ) ) 20 | .map( ( el ) => { 21 | 22 | return ( 23 | 24 |
  • 25 |
    26 | 27 |
    28 |
    29 |
    30 |
    {el.user_name}
    31 |
    {el.points} pts
    32 |
    33 | 34 | {el.time_since} 35 | 36 |
    37 |
    38 |

    {el.body}

    39 |
    40 |
  • 41 | 42 | ); 43 | } ); 44 | } else { 45 | return ( 46 |
    47 | ; 48 |
    49 | ); 50 | } 51 | 52 | } 53 | 54 | render() { 55 | 56 | return ( 57 |
      58 | {this.displayInfo()} 59 |
    60 | 61 | ); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /frontend/components/posts/post_index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import PostIndexItem from './post_index_item_container'; 4 | 5 | 6 | class PostIndex extends React.Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { page: 0 } 10 | 11 | } 12 | 13 | componentDidMount() { 14 | const { page } = this.state 15 | this.props.requestAllPosts(page); 16 | window.addEventListener("scroll", this.handleScroll.bind(this)); 17 | } 18 | 19 | componentWillUpdate(nextProps) { 20 | if (this.props.posts.length != nextProps.posts.length) { 21 | 22 | } 23 | } 24 | 25 | handleScroll(event) { 26 | const winHeight = window.innerHeight; 27 | 28 | const body = document.body; 29 | const html = document.documentElement; 30 | const docHeight = Math.max( 31 | body.scrollHeight, 32 | body.offsetHeight, 33 | html.clientHeight, 34 | html.scrollHeight, 35 | html.offsetHeight 36 | ); 37 | 38 | const scrollTop = window.pageYOffset || (document.documentElement || document.body.parentNode || document.body).scrollTop; 39 | const scrollPercent = scrollTop / (docHeight - winHeight); 40 | const scrollPercentRounded = Math.round(scrollPercent * 100); 41 | if (scrollPercentRounded > 90) { 42 | const page = this.state.page + 1 43 | 44 | this.setState({ page }, this.getPosts) 45 | } 46 | 47 | } 48 | 49 | getPosts() { 50 | const { page } = this.state 51 | 52 | this.props.requestAllPosts(page) 53 | 54 | } 55 | 56 | render() { 57 | const allPosts = this.props.posts.map((post) => ( 58 | 59 | )); 60 | return ( 61 |
    62 | {allPosts} 63 |
    64 | ); 65 | } 66 | } 67 | 68 | export default PostIndex; 69 | -------------------------------------------------------------------------------- /public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The change you wanted was rejected.

    62 |

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

    63 |
    64 |

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

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /docs/schema.md: -------------------------------------------------------------------------------- 1 | # Schema Information 2 | 3 | ## accounts 4 | column name | data type | details 5 | ----------------|-----------|----------------------- 6 | id | integer | not null, primary key 7 | username | string | not null, indexed, unique 8 | password_digest | string | not null 9 | session_token | string | not null, indexed, unique 10 | 11 | ## posts 12 | column name | data type | details 13 | ------------|-----------|----------------------- 14 | id | integer | not null, primary key 15 | title | string | not null 16 | description | text | not null 17 | points | integer | not null, default: 0 18 | user_id | integer | not null, foreign key (references users), indexed 19 | 20 | ## comments 21 | column name | data type | details 22 | ------------------|-----------|----------------------- 23 | id | integer | not null, primary key 24 | body | string | not null 25 | points | integer | not null, default: 0 26 | post_id | integer | not null, foreign key, indexed 27 | parent_id | integer | 28 | commenter_id | integer | not null, foreign key (references accounts), indexed 29 | 30 | ## images 31 | column name | data type | details 32 | ------------|-----------|----------------------- 33 | id | integer | not null, primary key 34 | post_id | string | not null, foreign key (references posts), indexed 35 | image_url | string | not null 36 | title | string | not null 37 | description | text | 38 | 39 | ## votes(join table) 40 | column name | data type | details 41 | ----------------|-----------|----------------------- 42 | user_id | integer | not null, foreign key 43 | vote_id | integer | not null, foreign key 44 | vote_type | integer | not null 45 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 6 | 55 | 56 | 57 | 58 | 59 |
    60 |
    61 |

    The page you were looking for doesn't exist.

    62 |

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

    63 |
    64 |

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

    65 |
    66 | 67 | 68 | -------------------------------------------------------------------------------- /frontend/actions/user_actions.js: -------------------------------------------------------------------------------- 1 | export const fetchUser = (id) => { 2 | return ( 3 | $.ajax({ 4 | method: 'GET', 5 | url: `/api/users/${id}`, 6 | }) 7 | );}; 8 | 9 | 10 | export const fetchUserPosts = (user_id, type) => { 11 | 12 | return ( 13 | $.ajax({ 14 | method: 'get', 15 | url: '/api/posts', 16 | data: { user_id, type } 17 | }) 18 | );}; 19 | export const fetchUserComments = (user_id, parent_type) => { 20 | return( 21 | $.ajax({ 22 | method: 'get', 23 | url: '/api/comments', 24 | data: { user_id, parent_type } 25 | }) 26 | );}; 27 | 28 | export const fetchComments = (id) => ( 29 | $.ajax({ 30 | method: 'GET', 31 | url: `/api/posts/${id}/comments`, 32 | }) 33 | ); 34 | 35 | export const RECEIVE_ONE_USER = "RECEIVE_ONE_USER"; 36 | export const RECEIVE_USER_POSTS = 'RECEIVE_USER_POSTS'; 37 | export const RECEIVE_USER_COMMENTS = 'RECEIVE_USER_COMMENTS'; 38 | 39 | export const receiveOneUser = (user) => ({ 40 | type: RECEIVE_ONE_USER, 41 | user 42 | }); 43 | 44 | export const receiveUserPosts = posts => ({ 45 | type: RECEIVE_USER_POSTS, 46 | posts 47 | }); 48 | export const receiveUserComments = comments => ({ 49 | type: RECEIVE_USER_COMMENTS, 50 | comments 51 | }); 52 | 53 | 54 | export const requestOneUser = (id) => (dispatch) => { 55 | return fetchUser(id).then(user => { 56 | dispatch(receiveOneUser(user)); 57 | return user; 58 | }); 59 | }; 60 | export const requestUserPosts = (id, type) => (dispatch) => { 61 | 62 | return fetchUserPosts(id, type).then(posts => { 63 | dispatch(receiveUserPosts(posts)); 64 | return posts; 65 | }); 66 | }; 67 | export const requestUserComments = (id, parentType) => (dispatch) => { 68 | return fetchUserComments(id, parentType).then(comments => { 69 | dispatch(receiveUserComments(comments)); 70 | return comments; 71 | }); 72 | }; 73 | -------------------------------------------------------------------------------- /json.js: -------------------------------------------------------------------------------- 1 | const imgur = require("./imgur.json"); 2 | const fetch = require("node-fetch"); 3 | const fs = require("fs"); 4 | 5 | let info = []; 6 | 7 | imgur.data.forEach(post => { 8 | let foo = {}; 9 | foo.title = post.title; 10 | foo.id = post.id; 11 | if (post.description) { 12 | foo.description = post.description; 13 | } 14 | 15 | foo.images = []; 16 | 17 | if (post.is_album) { 18 | post.images.forEach(image => { 19 | foo.images.push(image.link); 20 | if (image.description) { 21 | post.description = image.description; 22 | } 23 | }); 24 | } else { 25 | foo.images.push(post.link); 26 | } 27 | info.push(foo); 28 | }); 29 | 30 | extractComment = comment => { 31 | let foo = {}; 32 | foo.comment = comment.comment; 33 | if (comment.children.length) { 34 | foo.children = comment.children.map(el => extractComment(el)); 35 | } 36 | return foo; 37 | }; 38 | 39 | const headers = { 40 | Authorization: "Client-ID e1fc1d9931bccbe" 41 | }; 42 | 43 | let crap = {}; 44 | 45 | let shite = info.map(post => { 46 | let comments = []; 47 | 48 | return new Promise((resolve, reject) => { 49 | fetch(`https://api.imgur.com/3/gallery/${post.id}/comments/`, { 50 | headers 51 | }) 52 | .then(results => results.json()) 53 | .then(({ data }) => { 54 | comments = data.map(comment => extractComment(comment)); 55 | }) 56 | .then(() => { 57 | post.comments = comments; 58 | resolve(post); 59 | }) 60 | .catch(err => { 61 | console.log(err.message); 62 | reject(post); 63 | }); 64 | }); 65 | }); 66 | 67 | Promise.all(shite).then(res => { 68 | fs.writeFile("message.json", JSON.stringify(res), err => { 69 | if (err) throw err; 70 | console.log("The file has been saved!"); 71 | }); 72 | }); 73 | 74 | // console.log(info); 75 | -------------------------------------------------------------------------------- /db/migrate/20170814031059_create_impressions_table.rb: -------------------------------------------------------------------------------- 1 | class CreateImpressionsTable < ActiveRecord::Migration 2 | def self.up 3 | create_table :impressions, :force => true do |t| 4 | t.string :impressionable_type 5 | t.integer :impressionable_id 6 | t.integer :user_id 7 | t.string :controller_name 8 | t.string :action_name 9 | t.string :view_name 10 | t.string :request_hash 11 | t.string :ip_address 12 | t.string :session_hash 13 | t.text :message 14 | t.text :referrer 15 | t.text :params 16 | t.timestamps 17 | end 18 | add_index :impressions, [:impressionable_type, :message, :impressionable_id], :name => "impressionable_type_message_index", :unique => false, :length => {:message => 255 } 19 | add_index :impressions, [:impressionable_type, :impressionable_id, :request_hash], :name => "poly_request_index", :unique => false 20 | add_index :impressions, [:impressionable_type, :impressionable_id, :ip_address], :name => "poly_ip_index", :unique => false 21 | add_index :impressions, [:impressionable_type, :impressionable_id, :session_hash], :name => "poly_session_index", :unique => false 22 | add_index :impressions, [:controller_name,:action_name,:request_hash], :name => "controlleraction_request_index", :unique => false 23 | add_index :impressions, [:controller_name,:action_name,:ip_address], :name => "controlleraction_ip_index", :unique => false 24 | add_index :impressions, [:controller_name,:action_name,:session_hash], :name => "controlleraction_session_index", :unique => false 25 | add_index :impressions, [:impressionable_type, :impressionable_id, :params], :name => "poly_params_request_index", :unique => false, :length => {:params => 255 } 26 | add_index :impressions, :user_id 27 | end 28 | 29 | def self.down 30 | drop_table :impressions 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Invader-Gir", 3 | "version": "1.0.0", 4 | "description": "== README", 5 | "main": "index.js", 6 | "directories": { 7 | "doc": "docs", 8 | "lib": "lib", 9 | "test": "test" 10 | }, 11 | "engines": { 12 | "node": "8.1.2", 13 | "npm": "5.0.3" 14 | }, 15 | "scripts": { 16 | "test": "echo \"Error: no test specified\" && exit 1", 17 | "start": "webpack --watch", 18 | "postinstall": "webpack" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/dawah-wadah/Invader-Gir.git" 23 | }, 24 | "keywords": [], 25 | "user": "", 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/dawah-wadah/Invader-Gir/issues" 29 | }, 30 | "homepage": "https://github.com/dawah-wadah/Invader-Gir#readme", 31 | "dependencies": { 32 | "babel-core": "^6.25.0", 33 | "babel-loader": "^7.1.0", 34 | "babel-preset-es2015": "^6.24.1", 35 | "babel-preset-react": "^6.24.1", 36 | "css-loader": "^0.28.4", 37 | "fetch": "^1.1.0", 38 | "heroku-cli": "^6.11.17", 39 | "lodash": "^4.17.4", 40 | "material-ui": "^0.18.3", 41 | "moment": "^2.18.1", 42 | "moment-timezone": "^0.5.13", 43 | "node": "0.0.0", 44 | "node-fetch": "^1.7.3", 45 | "normalize.css": "^7.0.0", 46 | "react": "^15.6.1", 47 | "react-bootstrap": "^0.31.0", 48 | "react-dnd": "^2.4.0", 49 | "react-dom": "^15.6.1", 50 | "react-dropzone": "^3.13.3", 51 | "react-dropzone-component": "^2.0.0", 52 | "react-image": "^1.0.1", 53 | "react-moment": "^0.2.4", 54 | "react-redux": "^5.0.5", 55 | "react-router-dom": "^4.1.1", 56 | "redux": "^3.7.0", 57 | "redux-thunk": "^2.2.0", 58 | "style-loader": "^0.18.2", 59 | "webpack": "^3.0.0" 60 | }, 61 | "devDependencies": { 62 | "redux-logger": "^3.0.6" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | # imGIR 4 | 5 | Minimum Viable Product 6 | 7 | imGIR is a web application inspired by Imgur built using Ruby on Rails and React/Redux. By the end of Week 9, this app will, at a minimum, satisfy the following criteria with smooth, bug-free navigation, adequate seed data and sufficient CSS styling: 8 | 9 | 1. Hosting on Heroku 10 | 2. Create production README 11 | 3. New account creation, login, and guest/demo login 12 | 4. Create posts 13 | 5. Comments on posts 14 | 6. Upvote/Downvote posts and comments 15 | 7. Search Bar 16 | 17 | Bonus: 18 | 1. Upload Modal displays loading 19 | 2. Navigating by Keypresses 20 | 3. Voting animations 21 | 22 | 23 | ##Design Docs 24 | 25 | * [View Wireframes][views] 26 | * [React Components][components] 27 | * [API endpoints][endpoints] 28 | * [Sample-State][sample-state] 29 | * [DB schema][schema] 30 | 31 | [views]: ./wireframes 32 | [components]: ./component-heirarchy.md 33 | [endpoints]: ./api-endpoints.md 34 | [sample-state]: ./sample-state.md 35 | [schema]: ./schema.md 36 | 37 | # Phase 1: Back-end setup and Front End User Authentication (2 days) 38 | 39 | Objective: Functioning rails project with front-end authentication 40 | 41 | # Phase 2: Account Model, API, and components (2 days) 42 | 43 | Objective: Users can make, destroy, or edit accounts using api 44 | 45 | # Phase 3: Posts (2 days) 46 | 47 | Objective: Users can post. 48 | 49 | # Phase 4: Comments (2 days) 50 | 51 | Objective: Users can comment. 52 | 53 | # Phase 5: Upvote/Downvote (1 day) 54 | 55 | Objective: Users will be able to upvote/downvote and favorite posts. 56 | 57 | # Phase 6: - Search Bar (1 day) 58 | 59 | Objective: Users can search for posts. 60 | 61 | # Bonus Features (TBD) 62 | Infinite Scroll 63 | Vote on Comments 64 | Navigate Site using Keypresses, as well as upvote/downvote/favorite 65 | Upvote/Downvote/Favorite animations 66 | -------------------------------------------------------------------------------- /docs/sample-state.md: -------------------------------------------------------------------------------- 1 | ```javascript 2 | { 3 | currentUser: { 4 | 2: { 5 | id: 2, 6 | username: 'GirSaws', 7 | password_digest: 'l22kafklajsklajfjnkjnsjhaw', 8 | session_token: 'askjhajfbncbjkhefEBFKJB' 9 | } 10 | } 11 | 12 | forms; { 13 | createPost: { errors: ['Title cant be blank'] } 14 | createComment: { errors: ['Body cant be blank'] } 15 | signUp: { errors: [] } 16 | logIn: { errors: [] } 17 | } 18 | posts { 19 | 1: { 20 | id: 1, 21 | title: 'Check out my applesauce', 22 | description: 'mmmmm AppleSaws is my favorite', 23 | points: '1', 24 | userid: 'Gir' 25 | imageId: 1 26 | } 27 | 2: { 28 | id: 2, 29 | title: 'Why does Zim hate me', 30 | description: 'Zim is always mad at me', 31 | points: '-5', 32 | userid: 'Gir' 33 | imageId: 2 34 | } 35 | } 36 | images { 37 | 1: { 38 | id: 1, 39 | postId: 1, 40 | imageUrl: 'www.imGir.com/zzSDknncs221', 41 | title: 'Check out my applesauce', 42 | description: 'mmm AppleSaws is my favorite' 43 | } 44 | 3: { 45 | id: 3, 46 | postId: 1, 47 | imageUrl: 'www.imGir.com/zkjjkh2cs233', 48 | title: 'Doom Song', 49 | description: 'Doom-do-doom-doom' 50 | } 51 | } 52 | 53 | comments: { 54 | 1: { 55 | id: 1, 56 | body: 'Its because you always mess up my plans', 57 | postId: 1 58 | parent: null, 59 | points: 3, 60 | commentorId: 3, 61 | } 62 | 2: { 63 | id: 1, 64 | body: 'Friendly reminder not Im taking over the world tomorrow', 65 | parent: null, 66 | postId: 1, 67 | points: 6, 68 | commentorId: 3, 69 | } 70 | } 71 | 72 | votes: { 73 | 1: { 74 | id: 1, 75 | voterId: 1, 76 | voteId: 1, 77 | voteType: 2, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /frontend/components/comments/create_comments.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Moment from 'react-moment'; 3 | import SessionFormModal from '../session_form/session_form_modal'; 4 | 5 | class NewComment extends React.Component { 6 | constructor( props ) { 7 | super( props ); 8 | 9 | this.state = { 10 | body: '', 11 | parent_id: this.props.parentId, 12 | parent_type: this.props.parentType, 13 | post_id: this.props.parentId, 14 | charsLeft: 140 15 | }; 16 | this.update = this 17 | .update 18 | .bind( this ); 19 | this.handleSubmit = this 20 | .handleSubmit 21 | .bind( this ); 22 | } 23 | 24 | 25 | update( field ) { 26 | return e => this.setState( { 27 | [ field ]: e.currentTarget.value 28 | } ); 29 | } 30 | 31 | handleSubmit( e ) { 32 | e.preventDefault(); 33 | if ( this.props.loggedIn ) { 34 | let commentData = { 35 | comment: { 36 | body: this.state.body, 37 | parent_id: this.props.parentId, 38 | parent_type: this.state.parent_type, 39 | post_id: this.props.parentId 40 | } 41 | }; 42 | this 43 | .props 44 | .createComment( commentData ) 45 | .then( () => this.setState( { 46 | body: '' 47 | } ) ); 48 | } else { 49 | this.props.displayModal( ); 50 | } 51 | } 52 | 53 | render() { 54 | return ( 55 | 65 | ); 66 | } 67 | 68 | } 69 | 70 | export default NewComment; 71 | -------------------------------------------------------------------------------- /app/models/user.rb: -------------------------------------------------------------------------------- 1 | # == Schema Information 2 | # 3 | # Table name: users 4 | # 5 | # id :integer not null, primary key 6 | # username :string not null 7 | # password_digest :string not null 8 | # session_token :string not null 9 | # created_at :datetime not null 10 | # updated_at :datetime not null 11 | # votes :integer default(0) 12 | # 13 | 14 | class User < ActiveRecord::Base 15 | attr_reader :password 16 | 17 | validates :username, :password_digest, :session_token, presence: true 18 | validates :username, uniqueness: true 19 | validates :password, length: { minimum: 6 }, allow_nil: :true 20 | 21 | after_initialize :ensure_session_token 22 | before_validation :ensure_session_token_uniqueness 23 | 24 | has_many :posts, 25 | foreign_key: :user_id 26 | 27 | has_many :comments 28 | 29 | def password=(password) 30 | self.password_digest = BCrypt::Password.create(password) 31 | @password = password 32 | end 33 | 34 | def self.find_by_credentials(username, password) 35 | user = User.find_by(username: username) 36 | return nil unless user 37 | user.password_is?(password) ? user : nil 38 | end 39 | 40 | def password_is?(password) 41 | BCrypt::Password.new(password_digest).is_password?(password) 42 | end 43 | 44 | def reset_session_token! 45 | self.session_token = new_session_token 46 | ensure_session_token_uniqueness 47 | save 48 | session_token 49 | end 50 | 51 | private 52 | 53 | def ensure_session_token 54 | self.session_token ||= new_session_token 55 | end 56 | 57 | def new_session_token 58 | SecureRandom.base64 59 | end 60 | 61 | def ensure_session_token_uniqueness 62 | while User.find_by(session_token: self.session_token) 63 | self.session_token = new_session_token 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static file server for tests with Cache-Control for performance. 16 | config.serve_static_files = true 17 | config.static_cache_control = 'public, max-age=3600' 18 | 19 | # Show full error reports and disable caching. 20 | config.consider_all_requests_local = true 21 | config.action_controller.perform_caching = false 22 | 23 | # Raise exceptions instead of rendering exception templates. 24 | config.action_dispatch.show_exceptions = false 25 | 26 | # Disable request forgery protection in test environment. 27 | config.action_controller.allow_forgery_protection = false 28 | 29 | # Tell Action Mailer not to deliver emails to the real world. 30 | # The :test delivery method accumulates sent emails in the 31 | # ActionMailer::Base.deliveries array. 32 | config.action_mailer.delivery_method = :test 33 | 34 | # Randomize the order test cases are executed. 35 | config.active_support.test_order = :random 36 | 37 | # Print deprecation notices to the stderr. 38 | config.active_support.deprecation = :stderr 39 | 40 | # Raises error for missing translations 41 | # config.action_view.raise_on_missing_translations = true 42 | end 43 | -------------------------------------------------------------------------------- /app/controllers/api/posts_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::PostsController < ApplicationController 2 | 3 | def index 4 | page = params[:page].to_i 5 | 6 | @posts = if params[:user_id].present? 7 | if params[:type] == 'favorites' 8 | Post.includes(:user, :main_image) 9 | .joins("INNER JOIN votes ON votes.voteable_id = posts.id AND votes.voteable_type = 'Post' AND votes.vote_type = 'Upvote'") 10 | .joins("INNER JOIN users ON votes.user_id = #{params[:user_id]}") 11 | .uniq.order(created_at: :desc).limit(25).offset(page * 25) 12 | else 13 | Post.includes(:user, :main_image) 14 | .where('user_id =(?)', params[:user_id]).order(created_at: :desc).limit(25).offset(page * 25) 15 | end 16 | else 17 | # Post.includes(:user, :main_image).all.limit(25) 18 | Post.includes(:user, :main_image).order(created_at: :desc).limit(25).offset(page * 25) 19 | end 20 | render 'api/posts/index' 21 | end 22 | 23 | 24 | def show 25 | @post = Post.includes(:user, :images).find(params[:id]) 26 | impressionist(@post) 27 | @comments = @post.comments.includes(:replies, :user) 28 | end 29 | 30 | def new 31 | 32 | end 33 | 34 | def create 35 | @post = Post.new(post_params) 36 | @post.user_id = current_user.id 37 | @user = User.find(@post.user_id) 38 | if @post.save 39 | render :show 40 | else 41 | render json: @post.errors.full_messages, status: 422 42 | end 43 | end 44 | 45 | def destroy 46 | @post = Post.find(params[:id]) 47 | if @post.destroy 48 | render :show 49 | else 50 | render json: @post.errors.full_messages, status: 422 51 | end 52 | end 53 | 54 | 55 | 56 | 57 | private 58 | 59 | def post_params 60 | params.require(:post).permit(:title, :points, :user_id, :description, :type) 61 | end 62 | end 63 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/login_buttons.scss: -------------------------------------------------------------------------------- 1 | 2 | .social-wrap{ 3 | display: flex; 4 | justify-content: space-between; 5 | } 6 | 7 | .socialButtons { 8 | padding-top: 4px; 9 | padding-left: 2px; 10 | padding-bottom: 5px; 11 | margin-bottom: -10px; 12 | } 13 | 14 | /* Shared */ 15 | .loginBtn { 16 | box-sizing: border-box; 17 | position: relative; 18 | margin: 0.2em; 19 | padding: 0 15px 0 40px; 20 | border: none; 21 | text-align: left; 22 | line-height: 34px; 23 | white-space: nowrap; 24 | border-radius: 0.2em; 25 | font-size: 13px; 26 | color: #FFF; 27 | } 28 | .loginBtn:before { 29 | content: ""; 30 | box-sizing: border-box; 31 | position: absolute; 32 | top: 0; 33 | left: 0; 34 | width: 34px; 35 | height: 100%; 36 | } 37 | .loginBtn:focus { 38 | outline: none; 39 | } 40 | .loginBtn:active { 41 | box-shadow: inset 0 0 0 32px rgba(0,0,0,0.1); 42 | } 43 | 44 | 45 | /* Facebook */ 46 | .loginBtn--facebook { 47 | background-color: #4C69BA; 48 | background-image: linear-gradient(#4C69BA, #3B55A0); 49 | /*font-family: "Helvetica neue", Helvetica Neue, Helvetica, Arial, sans-serif;*/ 50 | text-shadow: 0 -1px 0 #354C8C; 51 | } 52 | .loginBtn--facebook:before { 53 | border-right: #364e92 1px solid; 54 | background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_facebook.png') 6px 6px no-repeat; 55 | } 56 | .loginBtn--facebook:hover, 57 | .loginBtn--facebook:focus { 58 | background-color: #5B7BD5; 59 | background-image: linear-gradient(#5B7BD5, #4864B1); 60 | } 61 | 62 | 63 | /* Google */ 64 | .loginBtn--google { 65 | /*font-family: "Roboto", Roboto, arial, sans-serif;*/ 66 | background: #DD4B39; 67 | } 68 | .loginBtn--google:before { 69 | border-right: #BB3F30 1px solid; 70 | background: url('https://s3-us-west-2.amazonaws.com/s.cdpn.io/14082/icon_google.png') 6px 6px no-repeat; 71 | } 72 | .loginBtn--google:hover, 73 | .loginBtn--google:focus { 74 | background: #E74B37; 75 | } 76 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/drop_down.scss: -------------------------------------------------------------------------------- 1 | /* Dropdown Button */ 2 | 3 | /* Dropdown button on hover & focus */ 4 | 5 | .dropbtn:focus, 6 | .dropbtn:hover { 7 | border-radius : 9px; 8 | background-color : #3e8e41; 9 | } 10 | 11 | /* The container
    - needed to position the dropdown content */ 12 | 13 | .dropdown { 14 | display : inline-block; 15 | position : relative; 16 | } 17 | 18 | #myDropdown { 19 | position : absolute; 20 | top : 31px; 21 | left : -136px; 22 | border-radius : 5px; 23 | } 24 | 25 | #myMenuDropdown { 26 | position : absolute; 27 | top : 31px; 28 | left : -148px; 29 | width : 100px; 30 | border-radius : 5px; 31 | } 32 | 33 | /* Dropdown Content (Hidden by Default) */ 34 | 35 | .dropdown-content { 36 | // display: none; 37 | z-index : 1; 38 | position : absolute; 39 | min-width : 160px; 40 | background-color : #f9f9f9; 41 | box-shadow : 0px 8px 16px 0px rgba(0,0,0,0.2); 42 | } 43 | 44 | #myMenuDropdown .dropdown-content { 45 | // display: none; 46 | z-index : 1; 47 | position : absolute; 48 | min-width : 130px; 49 | background-color : #f9f9f9; 50 | box-shadow : 0px 8px 16px 0px rgba(0,0,0,0.2); 51 | } 52 | 53 | /* Links inside the dropdown */ 54 | 55 | .dropdown-content a { 56 | display : block; 57 | padding : 12px 16px; 58 | color : black; 59 | text-decoration : none; 60 | } 61 | 62 | /* Change color of dropdown links on hover */ 63 | 64 | .dropdown-content a:hover { 65 | background-color : #f1f1f1; 66 | } 67 | 68 | 69 | 70 | 71 | .navbar-container *{ 72 | position: relative; 73 | } 74 | 75 | .search-input:focus { 76 | border : none; 77 | } 78 | 79 | /** 80 | * Show the dropdown menu (use JS to add this class to the 81 | * .dropdown-content container when the user clicks on the dropdown 82 | * button) 83 | */ 84 | 85 | .show { 86 | display : block; 87 | } 88 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require_self 14 | */ 15 | // 16 | // @import "components/login_buttons.css"; 17 | // @import "components/login_style.css"; 18 | 19 | @import "components/*"; 20 | 21 | html * { 22 | font-family: 'Open Sans', sans-serif; 23 | } 24 | body { 25 | font-family: 'Open Sans',sans-serif; 26 | color: #f2f2f2; 27 | height: 100%; 28 | margin: 0; 29 | font-size: 14px; 30 | } 31 | 32 | body, html { 33 | background-color: #141518; 34 | } 35 | 36 | ul, menu, dir { 37 | display: block; 38 | list-style-type: disc; 39 | -webkit-margin-before: 0em; 40 | -webkit-margin-after: 0em; 41 | -webkit-margin-start: 0px; 42 | -webkit-margin-end: 0px; 43 | -webkit-padding-start: 15px; 44 | } 45 | 46 | // .full-width { 47 | // background-size: cover; 48 | // background-position: center center; 49 | // opacity: .35; 50 | // width: 100%; 51 | // height: 100%; 52 | // z-index: 1; 53 | // background-position-x: center; 54 | // background-position-y: center; 55 | // background-size: cover; 56 | // background-repeat-x: initial; 57 | // background-repeat-y: initial; 58 | // background-attachment: initial; 59 | // background-origin: initial; 60 | // background-clip: initial; 61 | // background-color: initial; 62 | // } 63 | .test-stuff{ 64 | font-size: 160px; 65 | color: white; 66 | } 67 | -------------------------------------------------------------------------------- /frontend/components/posts/side_bar.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { connect } from "react-redux"; 3 | import { selectAllPosts } from "../../reducers/selectors"; 4 | import SideBarItem from "./side_bar_item"; 5 | import { Link, withRouter } from "react-router-dom"; 6 | 7 | class SideBar extends React.Component { 8 | constructor(props) { 9 | super(props); 10 | this.scroll = this.scroll.bind(this); 11 | } 12 | 13 | componentDidMount() { 14 | if (this.props.posts.length < 2) { 15 | this.props.requestAllPosts().then(() => { 16 | this.scroll(); 17 | }); 18 | } 19 | } 20 | 21 | componentDidUpdate(prevProps, prevState) { 22 | if (this.props.id !== prevProps.id) { 23 | this.scroll(); 24 | } 25 | } 26 | 27 | scroll() { 28 | let id = this.props.id; 29 | this.refs[id].scrollIntoView({ 30 | block: "start", 31 | nearest: "inline", 32 | behavior: "smooth" 33 | }); 34 | } 35 | 36 | render() { 37 | const posts = this.props.posts.map(post => ( 38 | // 39 | 40 |
    41 |
    42 | 43 |
    44 |
    45 |

    {post.title}

    46 |
    47 |
    48 | 49 | )); 50 | return
    {posts}
    ; 51 | } 52 | } 53 | 54 | import { requestAllPosts } from "../../actions/post_actions"; 55 | 56 | const mapStateToProps = (state, ownProps) => { 57 | return { 58 | posts: selectAllPosts(state.post.entities) 59 | }; 60 | }; 61 | 62 | const mapDispatchToProps = (dispatch, ownProps) => { 63 | return { 64 | requestAllPosts: () => dispatch(requestAllPosts()), 65 | id: ownProps.match.params.id 66 | }; 67 | }; 68 | 69 | export default withRouter( 70 | connect(mapStateToProps, mapDispatchToProps)(SideBar) 71 | ); 72 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/search.scss: -------------------------------------------------------------------------------- 1 | .search-bar { 2 | padding: 4px; 3 | display: inline-block; 4 | position: relative; 5 | } 6 | 7 | #mySearchBar { 8 | top: 0; 9 | right: 32px; 10 | width: 500px; 11 | height: 88px; 12 | border-radius: 3px; 13 | background-color: #50545c; 14 | // background-color: white; 15 | } 16 | 17 | .search-bar { 18 | background-color: #50545c; 19 | outline: none; 20 | border: 1px transparent; 21 | width: 343px; 22 | height: 48px; 23 | border-bottom: 3px solid #1bb76e; 24 | color: white; 25 | position: relative; 26 | } 27 | 28 | .search-bar-addition { 29 | border-bottom: none; 30 | display: flex; 31 | color: white; 32 | min-height: 48px; 33 | max-height: 300px; 34 | overflow: auto; 35 | height: auto; 36 | flex-direction: column; 37 | font-weight: lighter; 38 | font-size: 16px; 39 | position: absolute; 40 | } 41 | 42 | .search-input { 43 | width: 487px; 44 | height: 42px; 45 | margin: 0 5px; 46 | border: none; 47 | background-color: transparent; 48 | transition: border 0.1s ease-in; 49 | } 50 | 51 | #search-icon-menu { 52 | position: absolute; 53 | top: 31px; 54 | left: -335px; 55 | } 56 | 57 | .search-item { 58 | border-radius: 5px; 59 | display: flex; 60 | height: 60px; 61 | } 62 | 63 | .search-item-pic { 64 | max-width: 60px; 65 | max-height: 60px; 66 | } 67 | 68 | .search-item-info { 69 | padding-left: 10px; 70 | } 71 | 72 | .side-bar-items { 73 | border-radius: 15px; 74 | overflow-y: scroll hidden; 75 | border: 0; 76 | margin: 0; 77 | padding: 0; 78 | } 79 | 80 | .search-item-pic img { 81 | height: 60px; 82 | width: 60px; 83 | padding: 4px; 84 | object-fit: cover; 85 | overflow: hidden; 86 | } 87 | 88 | .next-post-btn-text { 89 | text-align: center; 90 | cursor: pointer; 91 | padding-left: 15px; 92 | height: 36px; 93 | box-sizing: border-box; 94 | float: left; 95 | padding-right: 15px; 96 | background: #5c69ff; 97 | border: none; 98 | padding: 10px 25px; 99 | } 100 | -------------------------------------------------------------------------------- /app/controllers/api/votes_controller.rb: -------------------------------------------------------------------------------- 1 | class Api::VotesController < ApplicationController 2 | def create 3 | 4 | vote = Vote.new(vote_params) 5 | vote.user_id = current_user.id 6 | klass = vote_params[:voteable_type] == "Post" ? Post : Comment 7 | @voteable_item = klass.find(vote_params[:voteable_id]) 8 | 9 | if(vote.save) 10 | instance_variable_set("@#{klass}".downcase, @voteable_item) 11 | if vote_params[:vote_type] == 'Upvote' 12 | @voteable_item.user.increment!(:votes) 13 | else 14 | @voteable_item.user.decrement!(:votes) 15 | end 16 | render "/api/#{"#{klass}".downcase}s/show" 17 | else 18 | @errors = vote.errors.full_messages 19 | render json: @errors, status: 422 20 | end 21 | end 22 | 23 | def update 24 | vote = Vote.find(params[:id]) 25 | if vote.update(vote_params) 26 | klass = vote_params[:voteable_type] == "Post" ? Post : Comment 27 | @voteable_item = klass.find(vote_params[:voteable_id]) 28 | instance_variable_set("@#{klass}".downcase, @voteable_item) 29 | if vote_params[:vote_type] == 'Upvote' 30 | @voteable_item.user.increment!(:votes) 31 | else 32 | @voteable_item.user.decrement!(:votes) 33 | end 34 | render "/api/#{"#{klass}".downcase}s/show" 35 | else 36 | @errors = vote.errors.full_messages 37 | render json: @errors, status: 422 38 | end 39 | end 40 | 41 | def destroy 42 | vote = Vote.find(params[:id]) 43 | if vote.destroy 44 | klass = vote[:voteable_type] == "Post" ? Post : Comment 45 | @voteable_item = klass.find(vote[:voteable_id]) 46 | instance_variable_set("@#{klass}".downcase, @voteable_item) 47 | if vote[:vote_type] == 'Upvote' 48 | @voteable_item.user.decrement!(:votes) 49 | else 50 | @voteable_item.user.increment!(:votes) 51 | end 52 | render "/api/#{"#{klass}".downcase}s/show" 53 | else 54 | @errors = vote.errors.full_messages 55 | render json: @errors, status: 422 56 | end 57 | end 58 | 59 | private 60 | 61 | def vote_params 62 | params.require(:vote).permit(:id, :user_id, :vote_type, :voteable_id, :voteable_type) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | config.after_initialize do 13 | Bullet.enable = true 14 | Bullet.alert = true 15 | Bullet.bullet_logger = true 16 | Bullet.console = true 17 | Bullet.rails_logger = true 18 | Bullet.add_footer = true 19 | Bullet.slack = { webhook_url: 'http://some.slack.url', channel: '#default', username: 'notifier' } 20 | end 21 | # Bullet.stacktrace_includes = [ 'your_gem', 'your_middleware' ] 22 | # Bullet.stacktrace_excludes = [ 'their_gem', 'their_middleware' ] 23 | 24 | # Show full error reports and disable caching. 25 | config.consider_all_requests_local = true 26 | config.action_controller.perform_caching = false 27 | 28 | # Don't care if the mailer can't send. 29 | config.action_mailer.raise_delivery_errors = false 30 | 31 | # Print deprecation notices to the Rails logger. 32 | config.active_support.deprecation = :log 33 | 34 | # Raise an error on page load if there are pending migrations. 35 | config.active_record.migration_error = :page_load 36 | 37 | # Debug mode disables concatenation and preprocessing of assets. 38 | # This option may cause significant delays in view rendering with a large 39 | # number of complex assets. 40 | config.assets.debug = true 41 | 42 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 43 | # yet still be able to expire them through the digest params. 44 | config.assets.digest = true 45 | 46 | # Adds additional error checking when serving assets at runtime. 47 | # Checks for improperly declared sprockets dependencies. 48 | # Raises helpful error messages. 49 | config.assets.raise_runtime_errors = true 50 | 51 | # Raises error for missing translations 52 | # config.action_view.raise_on_missing_translations = true 53 | end 54 | -------------------------------------------------------------------------------- /frontend/components/posts/post_index_item.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Link, 4 | Route 5 | } from 'react-router-dom'; 6 | import { 7 | DeleteButton 8 | } from '../delete/delete_button'; 9 | 10 | import PostDetail from './post_hover'; 11 | 12 | class PostIndexItem extends React.Component { 13 | constructor( props ) { 14 | super( props ); 15 | this.state = { 16 | visibleDetails: false 17 | }; 18 | this.mouseEnter = this 19 | .mouseEnter 20 | .bind( this ); 21 | this.mouseLeave = this 22 | .mouseLeave 23 | .bind( this ); 24 | this.style = this.style.bind( this ); 25 | this.hoverRenders = this.hoverRenders.bind( this ); 26 | } 27 | 28 | mouseEnter() { 29 | this.setState( { 30 | visibleDetails: true 31 | } ); 32 | } 33 | 34 | mouseLeave() { 35 | this.setState( { 36 | visibleDetails: false 37 | } ); 38 | } 39 | 40 | style() { 41 | if ( this.props.post.vote ) { 42 | 43 | switch ( this.props.post.vote.vote_type ) { 44 | case 'Upvote': 45 | return ( { 46 | border: '#1BB76E 4px solid' 47 | } ); 48 | case 'Downvote': 49 | return ( { 50 | border: '#DB3535 4px solid' 51 | } ); 52 | default: 53 | return ( { 54 | border: '#121211 4px solid' 55 | } ); 56 | 57 | } 58 | } else { 59 | return ( { 60 | border: '#121211 4px solid' 61 | } ); 62 | 63 | } 64 | 65 | } 66 | 67 | hoverRenders( post ) { 68 | return ( 69 |
    70 | 71 | {post.user_id === this.props.currentUser ? 72 | this.props.deletePost(post.id)}/> : null} 73 |
    74 | ); 75 | } 76 | 77 | render() { 78 | return ( 79 | 80 |
    this.mouseEnter()} 84 | onMouseLeave={() => this.mouseLeave()}> 85 |
    86 | 87 | 89 | 90 |
    91 | {this.state.visibleDetails 92 | ? this.hoverRenders(this.props.post) 93 | : null} 94 |
    95 | ); 96 | } 97 | } 98 | 99 | export default PostIndexItem; 100 | -------------------------------------------------------------------------------- /docs/component-hierarchy.md: -------------------------------------------------------------------------------- 1 | ## Component Hierarchy 2 | 3 | 1) AuthForm 4 | Users can sign in/up. 5 | 6 | 2) HomePage 7 | Root Page, which will display the navBar, posts index, and optionally the comments index 8 | 9 | 3) NavBar 10 | Contains the logo, which will redirect to the homepage, upload button, search-bar, and depending on the current session, login/signup or button to user page 11 | 12 | 4) PostShow 13 | Has the post, sidebar with an post index, as well as all comments for that post 14 | 15 | 5) PostsIndex 16 | Collection of all posts 17 | 18 | 6) PostsIndexItem 19 | Component for a single post. 20 | 21 | 7) CommentForm 22 | Area where the user will comment. 23 | 24 | 8) CommentsIndex 25 | Displays all of a posts comments. 26 | 27 | 9) CommentsIndexItem 28 | A single comment for a post or comment. 29 | 30 | 10) CommentsDetail/CommentsDetailContainer 31 | Houses any voting data(pending), as well as replies id 32 | 33 | 11) ImagesIndex/ImagesIndexContainer 34 | Will contain all the images for a single post. 35 | 36 | 12) ImagesIndexItem 37 | A single image for the ImagesIndex. 38 | 39 | 13) PostIndexItemDetails/PostIndexItemDetailsContainer 40 | Component that contains the details for a specific post, such as upvotes/downvotes, and title. 41 | 42 | 14) NewPostModal 43 | A container modal that allows the user to upload a post via a form. 44 | 45 | 46 | 47 | 48 | **AuthFormContainer** 49 | - AuthForm 50 | * Sign In 51 | * Sign Up 52 | 53 | **NewPostModal** 54 | * NewPostForm 55 | 56 | **HomePageContainer** 57 | * Header 58 | * PostsIndex 59 | * PostIndexItem 60 | * ImagesIndex 61 | * ImagesIndexItem 62 | * PostIndexItemDetails 63 | 64 | **Header** 65 | - Sign In/ Sign Up 66 | - Logo for (home Button) 67 | - New Post 68 | - Search Bar 69 | 70 | **PostShowContainer** 71 | * PostIndexItem 72 | * PostIndexItemDetails 73 | * ImagesIndex 74 | * ImagesIndexItem 75 | * CommentForm 76 | * CommentsIndex 77 | * CommentsIndexItem 78 | * CommentsDetail 79 | 80 | 81 | ## Routes 82 | 83 | |Path | Component | 84 | |-------|-------------| 85 | | "/sign-up" | "AuthFormContainer" | 86 | | "/sign-in" | "AuthFormContainer" | 87 | | "/posts" | "PostsIndex" | 88 | | "/post/new/" | "NewPostForm" | 89 | | "/posts/id" | "PostShow" | 90 | | "/posts/id/images" | "ImagesIndex" | 91 | | "/posts/id/comments" | "CommentsIndex" | 92 | | "/posts/id/comments/commentId" | "CommentDetail" | 93 | -------------------------------------------------------------------------------- /frontend/components/comments/reply_form.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createComment } from '../../actions/comment_actions'; 3 | import SessionFormModal from '../session_form/session_form_modal'; 4 | 5 | class ReplyForm extends React.Component{ 6 | constructor(props){ 7 | super(props); 8 | this.state = { 9 | body: '', 10 | parent_id: this.props.parentId, 11 | parent_type: this.props.parentType, 12 | post_id: this.props.postId, 13 | charsLeft: 140 14 | }; 15 | this.handleSubmit = this.handleSubmit.bind(this); 16 | } 17 | 18 | handleSubmit(e) { 19 | e.preventDefault(); 20 | if (this.props.loggedIn) { 21 | let commentData = { 22 | comment: { 23 | body: this.state.body, 24 | parent_id: this.state.parent_id, 25 | parent_type: this.state.parent_type, 26 | post_id: this.state.post_id 27 | } 28 | }; 29 | this 30 | .props 31 | .createComment(commentData) 32 | .then(() => this.setState({body: ''})); 33 | this.props.toggle(e); 34 | this.props.toggleChild(e); 35 | if (this.props.open) { 36 | this.props.replies(e); 37 | } 38 | } else { 39 | this.props.displayModal(); 40 | } 41 | } 42 | 43 | update(field) { 44 | return e => this.setState({ 45 | [field]: e.currentTarget.value 46 | }); 47 | } 48 | 49 | render() { 50 | return ( 51 |
    52 |
    53 |
    54 | 55 |
    56 | 57 |
    {140 - this.state.body.length}
    58 |
    59 |
    60 |
    61 |
    62 | ); 63 | } 64 | 65 | } 66 | 67 | 68 | import { connect } from 'react-redux'; 69 | import { withRouter } from 'react-router-dom'; 70 | import { displayModal } from '../../actions/modal_actions'; 71 | 72 | const mapStateToProps = ({ session }, {parentId}) => { 73 | return { 74 | CommentId: parentId, 75 | loggedIn: Boolean(session.currentUser), 76 | }; 77 | }; 78 | 79 | const mapDispatchToProps = dispatch => ({ 80 | displayModal: (component) => dispatch(displayModal(component)), 81 | createComment: (comment) => dispatch(createComment(comment)) 82 | }); 83 | 84 | export default withRouter(connect( 85 | mapStateToProps, 86 | mapDispatchToProps 87 | )(ReplyForm)); 88 | -------------------------------------------------------------------------------- /frontend/components/posts/post_hover.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import SessionFormModal from '../session_form/session_form_modal'; 4 | import { DeleteButton } from '../delete/delete_button'; 5 | 6 | 7 | const PostDetail = ({post, createVote, editVote, deleteVote, loggedIn, displayModal}) => { 8 | const _voted = (obj) => { 9 | return Boolean(obj.vote); 10 | }; 11 | 12 | const _toggleVote = (type) => { 13 | if (_voted(post)) { 14 | if (post.vote.vote_type !== type) { 15 | 16 | return (editVote({ 17 | vote: { 18 | id: post.vote.id, 19 | voteable_type: 'Post', 20 | voteable_id: post.id, 21 | vote_type: type 22 | } 23 | })); 24 | } else { 25 | return (deleteVote({id: post.vote.id})); 26 | } 27 | } else { 28 | createVote({ 29 | vote: { 30 | voteable_type: "Post", 31 | voteable_id: post.id, 32 | vote_type: type 33 | } 34 | }); 35 | } 36 | }; 37 | 38 | const _actionTodo = (type) => { 39 | if (loggedIn) { 40 | _toggleVote(type); 41 | } else { 42 | displayModal(); 43 | } 44 | }; 45 | 46 | 47 | return ( 48 |
    49 |
    50 |
    _actionTodo('Upvote')}> 51 | 52 |
    53 |
    _actionTodo('Downvote')}> 54 | 55 |
    56 |

    {post.totalvotes} 57 | points

    58 |
    59 |
    60 |

    {post.title}

    61 |
    62 |
    63 |

    Tags: Album

    64 |

    {post.view_count + ' views'}

    65 |
    66 |
    67 | ); 68 | }; 69 | 70 | import {createVote , editVote, deleteVote} from '../../actions/vote_actions'; 71 | import {displayModal} from '../../actions/modal_actions'; 72 | 73 | const mapStateToProps = ({session}) => { 74 | let userId = 0; 75 | if (session.currentUser){ userId = session.currentUser.id} 76 | 77 | return ({ 78 | loggedIn: Boolean(session.currentUser), 79 | currentUser: userId 80 | })}; 81 | 82 | 83 | const mapDispatchToProps = (dispatch) => { 84 | return { 85 | createVote: (voteData) => dispatch(createVote(voteData)), 86 | editVote: (voteData) => dispatch(editVote(voteData)), 87 | deleteVote: (id) => dispatch(deleteVote(id)), 88 | displayModal: (comp) => dispatch(displayModal(comp)) 89 | }; 90 | }; 91 | 92 | 93 | export default connect( 94 | mapStateToProps, mapDispatchToProps 95 | )(PostDetail); 96 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/upload_button.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { displayDropdown } from '../../actions/dropdown_actions'; 4 | import { displayModal } from '../../actions/modal_actions'; 5 | import UploadButtonContent from './upload_button_contents'; 6 | import UploadModalContent from '../upload_button/upload_button_container'; 7 | import SessionFormModal from '../session_form/session_form_modal'; 8 | 9 | 10 | class UploadButton extends React.Component { 11 | constructor(props){ 12 | super(props); 13 | this.state = {open: false}; 14 | this.handleClick = this.handleClick.bind(this); 15 | // this.handleOpen = this.handleOpen.bind(this); 16 | // this.handleClose = this.handleClose.bind(this); 17 | this.handleModal = this.handleModal.bind(this); 18 | this.uploadButtonRender = this.uploadButtonRender.bind(this); 19 | } 20 | 21 | // handleOpen() { 22 | // this.setState({open: true}); 23 | // } 24 | // 25 | // handleClose() { 26 | // this.setState({open: false}); 27 | // } 28 | 29 | 30 | handleClick(e) { 31 | e.preventDefault(); 32 | e.stopPropagation(); 33 | this.props.displayDropdown(); 34 | } 35 | 36 | handleModal(e) { 37 | e.preventDefault(); 38 | e.stopPropagation(); 39 | this.props.displayModal(UploadModalContent()); 40 | } 41 | 42 | uploadButtonRender(){ 43 | if (this.props.loggedIn) { 44 | return ( this.props.displayModal()); 45 | } else { 46 | return this.props.displayModal(); 47 | } 48 | } 49 | 50 | render() { 51 | return ( 52 | 53 |
    54 |
    55 | 56 |
    57 |
    58 | New Post 59 |
    60 |
    61 | 63 | { this.props.visible ? : null } 64 |
    65 |
    66 |
    67 | ); 68 | 69 | 70 | 71 | 72 | } 73 | 74 | } 75 | const mapStateToProps = (state) => { 76 | return { 77 | loggedIn: Boolean(state.session.currentUser), 78 | visible: Boolean(state.dropdown.uploadDropdown), 79 | modal: Boolean(state.dropdown.uploadModal) 80 | }; 81 | }; 82 | 83 | const mapDispatchToProps = (dispatch) => { 84 | return { 85 | displayDropdown: () => dispatch(displayDropdown({ uploadDropdown: true })), 86 | displayModal: (component) => dispatch(displayModal(component)) 87 | }; 88 | }; 89 | 90 | export default connect( 91 | mapStateToProps, 92 | mapDispatchToProps 93 | )(UploadButton); 94 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar-container{ 2 | width: 100%; 3 | background-color: #34373c; 4 | height: 50px; 5 | z-index: 100; 6 | position: relative; 7 | top: 0; 8 | display: inline-flex; 9 | align-content: center; 10 | margin: 0; 11 | } 12 | 13 | .navbar-container *{ 14 | color: white; 15 | } 16 | 17 | .logo-menu-combo { 18 | display: flex; 19 | } 20 | #topbar { 21 | width: 58%; 22 | background-color: #34373c; 23 | font-size: 15px; 24 | height: 50px; 25 | z-index: 100; 26 | top: 0; 27 | margin: 0 auto; 28 | display: flex; 29 | justify-content: space-between; 30 | } 31 | 32 | .link-menu{ 33 | display: flex; 34 | align-self: center; 35 | } 36 | 37 | .right-side { 38 | width: 27%; 39 | display: flex; 40 | justify-content: space-around; 41 | align-content: center; 42 | } 43 | 44 | 45 | .logo-icon { 46 | background: url(https://s3.us-east-2.amazonaws.com/imgirbucket-dev/images/images/logo-2.png) center center no-repeat; 47 | width: 72px; 48 | height: 46px; 49 | background-size: contain; 50 | margin: auto 12px; 51 | } 52 | 53 | .hoverable:hover{ 54 | background: #50545c; 55 | border-radius: 12px; 56 | } 57 | 58 | 59 | .upload-button { 60 | display: flex; 61 | width: inherit; 62 | justify-content: space-around; 63 | align-self: center; 64 | font-size: 14px; 65 | } 66 | .link-menu i{ 67 | align-self: center; 68 | padding: auto 0; 69 | } 70 | 71 | .upload-button-container { 72 | position: relative; 73 | display: flex; 74 | width: 160px; 75 | margin: 9px 0px 9px 20px; 76 | border-radius: 4px; 77 | border: 1px solid #1bb76e; 78 | background: #1bb76e; 79 | cursor: pointer; 80 | padding: 0; 81 | vertical-align: baseline; 82 | } 83 | 84 | // 85 | .left-side { 86 | margin: auto 0; 87 | display: inline-flex; 88 | align-content: center; 89 | justify-content: space-between; 90 | } 91 | .user-nav { 92 | align-self: center; 93 | align-content: center; 94 | } 95 | 96 | .left-side ul li { 97 | margin: 0; 98 | list-style: none; 99 | display: inline-flex; 100 | user-select: none; 101 | padding: 0; 102 | // position: relative; 103 | } 104 | 105 | .navlink-btn { 106 | margin: auto 20px; 107 | text-decoration: none; 108 | } 109 | 110 | .user-nav, #global-search-container{ 111 | 112 | padding: 0 15px; 113 | color: #f2f2f2; 114 | } 115 | 116 | .signin-link{ 117 | align-content: center; 118 | margin: 5px 10px; 119 | padding: 5px 0; 120 | height: 25px; 121 | } 122 | .signin-link:hover{ 123 | background: #50545c; 124 | border-radius: 6px; 125 | } 126 | 127 | #global-search-container:hover{ 128 | background: #50545c; 129 | border-radius: 15px; 130 | height: 30px; 131 | padding-top: 5px; 132 | margin-bottom: 5px; 133 | } 134 | 135 | hoverable i { 136 | border-radius: 4px; 137 | } 138 | -------------------------------------------------------------------------------- /app/assets/stylesheets/components/user.scss: -------------------------------------------------------------------------------- 1 | .user-page { 2 | display : flex; 3 | position : relative; 4 | top : 10px; 5 | width : 57%; 6 | min-height : 150px; 7 | margin : 10px auto 0; 8 | border-radius : 15px; 9 | justify-content : space-around; 10 | } 11 | 12 | .user-comments { 13 | display : flex; 14 | width : 63%; 15 | height : 70vh; 16 | padding : 20px 5px; 17 | border-radius : 4px; 18 | background-color : #34373c; 19 | flex-direction : column; 20 | justify-content : center; 21 | } 22 | 23 | .notoriety-list { 24 | height : 0; 25 | overflow : hidden; 26 | font-size : 14px; 27 | line-height : 15px; 28 | letter-spacing : 1px; 29 | transition : height 0.3s ease; 30 | } 31 | 32 | .open { 33 | display : block; 34 | height : 245px; 35 | } 36 | 37 | .notoriety-list td { 38 | padding : 2px; 39 | border-bottom : 2px solid #222; 40 | } 41 | 42 | .stat { 43 | color : #AFD8F8; 44 | vertical-align : top; 45 | font-weight : 700; 46 | } 47 | 48 | .left-end { 49 | text-align : end; 50 | } 51 | 52 | .user-side-bar { 53 | top : 90px; 54 | width : 35%; 55 | height : 70vh; 56 | border-radius : 15px; 57 | } 58 | 59 | .user-info-item { 60 | display : flex; 61 | height : 80px; 62 | margin : 15px 0; 63 | padding : 8px; 64 | border-radius : 5px; 65 | } 66 | 67 | .user-info-item img { 68 | width : 80px; 69 | height : 80px; 70 | margin-right : 20px; 71 | overflow : hidden; 72 | object-fit : cover; 73 | } 74 | 75 | .user-info-item-info { 76 | color : white; 77 | overflow : hidden; 78 | } 79 | 80 | .panel-header-toolbox { 81 | display : flex; 82 | width : 90%; 83 | margin : 0 auto 5px; 84 | padding : 10px; 85 | background-color : #141518; 86 | justify-content : space-between; 87 | } 88 | 89 | .user-info { 90 | height : auto; 91 | } 92 | 93 | .display { 94 | width : 90%; 95 | height : 80%; 96 | margin : 0 auto; 97 | padding : 10px; 98 | overflow : auto; 99 | } 100 | 101 | .panel { 102 | min-height : 50px; 103 | margin : 0 0 10px; 104 | padding : 10px; 105 | border-radius : 5px; 106 | background-color : #34373c; 107 | } 108 | 109 | .textbox { 110 | margin : 4.5px; 111 | padding : 10px; 112 | color : white; 113 | background-color : #141518; 114 | } 115 | 116 | .close { 117 | padding : 0; 118 | } 119 | 120 | .blue { 121 | color : #4E76C9; 122 | } 123 | 124 | .selected { 125 | color : #1bb76e; 126 | } 127 | 128 | .user-info-picker { 129 | display : flex; 130 | padding : 25px; 131 | flex-direction : column; 132 | justify-content : space-around; 133 | } 134 | 135 | .notoriety { 136 | display : flex; 137 | } 138 | 139 | .flex { 140 | display: flex; 141 | } 142 | 143 | .green { 144 | color: #1BB76E; 145 | } 146 | 147 | .center { 148 | justify-content: center; 149 | } 150 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | # PostgreSQL. Versions 8.2 and up are supported. 2 | # 3 | # Install the pg driver: 4 | # gem install pg 5 | # On OS X with Homebrew: 6 | # gem install pg -- --with-pg-config=/usr/local/bin/pg_config 7 | # On OS X with MacPorts: 8 | # gem install pg -- --with-pg-config=/opt/local/lib/postgresql84/bin/pg_config 9 | # On Windows: 10 | # gem install pg 11 | # Choose the win32 build. 12 | # Install PostgreSQL and put its /bin directory on your path. 13 | # 14 | # Configure Using Gemfile 15 | # gem 'pg' 16 | # 17 | default: &default 18 | adapter: postgresql 19 | encoding: unicode 20 | # For details on connection pooling, see rails configuration guide 21 | # http://guides.rubyonrails.org/configuring.html#database-pooling 22 | pool: 5 23 | 24 | development: 25 | <<: *default 26 | database: imGir_development 27 | 28 | # The specified database role being used to connect to postgres. 29 | # To create additional roles in postgres see `$ createuser --help`. 30 | # When left blank, postgres will use the default role. This is 31 | # the same name as the operating system user that initialized the database. 32 | # username: postgres 33 | 34 | # The password associated with the postgres role (username). 35 | # password: <%= ENV["password"] %> 36 | 37 | # Connect on a TCP socket. Omitted by default since the client uses a 38 | # domain socket that doesn't need configuration. Windows does not have 39 | # domain sockets, so uncomment these lines. 40 | # host: localhost 41 | 42 | # The TCP port the server listens on. Defaults to 5432. 43 | # If your server runs on a different port number, change accordingly. 44 | # port: 5432 45 | 46 | # Schema search path. The server defaults to $user,public 47 | #schema_search_path: myapp,sharedapp,public 48 | 49 | # Minimum log levels, in increasing order: 50 | # debug5, debug4, debug3, debug2, debug1, 51 | # log, notice, warning, error, fatal, and panic 52 | # Defaults to warning. 53 | #min_messages: notice 54 | 55 | # Warning: The database defined as "test" will be erased and 56 | # re-generated from your development database when you run "rake". 57 | # Do not set this db to the same as development or production. 58 | test: 59 | <<: *default 60 | database: imGir_test 61 | 62 | # As with config/secrets.yml, you never want to store sensitive information, 63 | # like your database password, in your source code. If your source code is 64 | # ever seen by anyone, they now have access to your database. 65 | # 66 | # Instead, provide the password as a unix environment variable when you boot 67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database 68 | # for a full rundown on how to provide these environment variables in a 69 | # production deployment. 70 | # 71 | # On Heroku and other platform providers, you may have a full connection URL 72 | # available as an environment variable. For example: 73 | # 74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase" 75 | # 76 | # You can use this database configuration with: 77 | # 78 | # production: 79 | # url: <%= ENV['DATABASE_URL'] %> 80 | # 81 | production: 82 | <<: *default 83 | database: imGir_production 84 | username: imGir 85 | password: <%= ENV['imGir_DATABASE_PASSWORD'] %> 86 | -------------------------------------------------------------------------------- /frontend/components/user/user.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | NavLink, 4 | Link, Switch, Route 5 | } from 'react-router-dom'; 6 | import Moment from 'react-moment'; 7 | import UserSideBar from './user_side_bar'; 8 | import UserGallery from './user_gallery'; 9 | import UserComments from './user_comments'; 10 | 11 | 12 | class User extends React.Component { 13 | constructor( props ) { 14 | super( props ); 15 | this.state = { 16 | userId: this.props.match.params.id, 17 | headerType: 'Gallery Comments' 18 | }; 19 | this.reload = this.reload.bind(this); 20 | } 21 | 22 | componentDidMount() { 23 | this.props.requestOneUser( this.state.userId ); 24 | this.reload(this.props.location.pathname.split( '/' )[ 3 ]); 25 | } 26 | 27 | reload(options){ 28 | switch ( options ) { 29 | case 'comments': 30 | this.props.requestUserComments( parseInt( this.state.userId ), 'Post' ); 31 | this.setState({ headerType: "Gallery Comments"}); 32 | break; 33 | case 'submitted': 34 | this.props.requestUserPosts( this.state.userId, 'submitted' ); 35 | this.setState({ headerType: 'Submitted Images'}); 36 | break; 37 | case 'favorites': 38 | this.props.requestUserPosts( this.state.userId, 'favorites' ); 39 | this.setState({ headerType: "Gallery Favorites"}); 40 | case 'replies': 41 | this.props.requestUserComments( parseInt( this.state.userId ), 'Comment' ); 42 | this.setState({ headerType: 'Comment Replies'}); 43 | break; 44 | default: 45 | this.props.requestUserComments( this.state.userId, 'Post' ); 46 | this.setState({ headerType: "Gallery Comments"}); 47 | break; 48 | 49 | } 50 | } 51 | 52 | componentWillReceiveProps( nextProps ) { 53 | 54 | if ( this.props.match.params.id !== nextProps.match.params.id ) { 55 | this.setState({ 56 | userId: nextProps.match.params.id 57 | }); 58 | this.reload(this.props.location.pathname.split( '/' )[ 3 ]); 59 | } else if ( this.props.location.pathname.split( '/' )[ 3 ] !== nextProps.location 60 | .pathname.split( '/' )[ 3 ] ) { 61 | this.reload(nextProps.location.pathname.split( '/' )[ 3 ]); 62 | 63 | } 64 | } 65 | 66 | 67 | render() { 68 | 69 | this.props.user ? document.title = this.props.user.username : document.title = 'Zimgir'; 70 | return ( 71 |
    72 |
    73 |
    74 |
    75 | {this.state.headerType} 76 |
    77 |
    78 | Options Stuff 79 |
    80 |
    81 |
    82 | 83 | }/> 84 | }/> 85 | }/> 86 | }/> 87 | }/> 88 | 89 |
    90 | 91 |
    92 | 93 | 94 |
    95 | ); 96 | } 97 | } 98 | export default User; 99 | -------------------------------------------------------------------------------- /frontend/components/nav_bar/search_bar_container.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | connect 4 | } from 'react-redux'; 5 | import { 6 | displayDropdown 7 | } from '../../actions/dropdown_actions'; 8 | import { 9 | fetchSearch 10 | } from '../../actions/search_actions'; 11 | import SearchBarInput from './search_bar_input'; 12 | import { 13 | selectAllResults 14 | } from '../../reducers/selectors'; 15 | import { 16 | withRouter, 17 | Link 18 | } from 'react-router-dom'; 19 | 20 | 21 | 22 | class SearchBar extends React.Component { 23 | constructor( props ) { 24 | super( props ); 25 | this.state = { 26 | search: '' 27 | }; 28 | this.handleChange = this.handleChange.bind( this ); 29 | this.handleClick = this.handleClick.bind( this ); 30 | this.handleSearchClick = this.handleSearchClick.bind(this); 31 | } 32 | 33 | handleChange() { 34 | return e => { 35 | this.setState( { 36 | [ 'search' ]: e.currentTarget.value 37 | } ); 38 | this.props.fetchSearch( e.currentTarget.value ); 39 | }; 40 | } 41 | 42 | 43 | handleClick( e ) { 44 | e.preventDefault(); 45 | e.stopPropagation(); 46 | this.props.displayDropdown( { 47 | searchBar: !this.props.visible 48 | } ); 49 | } 50 | 51 | 52 | componentWillReceiveProps() { 53 | this.setState( { 54 | [ 'search' ]: '' 55 | } ); 56 | } 57 | 58 | handleSearchClick(){ 59 | this.props.displayDropdown( { 60 | searchBar: !this.props.visible 61 | } ); 62 | 63 | } 64 | 65 | 66 | searchResults() { 67 | if ( this.props.results.length ) { 68 | return ( 69 |
    70 | {this.props.results.map((post) => { 71 | return ( 72 |
    73 |
    74 | 75 |
    76 |
    77 |

    {post.title}

    78 |
    79 |
    80 | ) 81 | })} 82 |
    83 |
    84 | ) 85 | } else { 86 | return
    SEARCH SYNTAX: TITLE
    87 | } 88 | } 89 | 90 | 91 | render() { 92 | 93 | return ( 94 | 95 |
    96 | 97 | {this.props.visible ? 98 |
    99 | 103 | {this.searchResults()} 104 |
    105 | : null } 106 |
    107 | 108 | ); 109 | } 110 | } 111 | 112 | const mapStateToProps = ( state, ownProps ) => ( { 113 | results: selectAllResults( state.search.results ), 114 | visible: Boolean( state.dropdown.searchBar ), 115 | } ); 116 | 117 | const mapDispatchToProps = ( dispatch ) => ( { 118 | fetchSearch: query => dispatch( fetchSearch( query ) ), 119 | displayDropdown: ( obj ) => dispatch( displayDropdown( obj ) ) 120 | } ); 121 | 122 | 123 | 124 | export default withRouter( connect( 125 | mapStateToProps, 126 | mapDispatchToProps 127 | )( SearchBar ) ); 128 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | require 'faker' 2 | require 'json' 3 | 4 | file = File.read('db/message2.json') 5 | data_hash = JSON.parse(file) 6 | 7 | User.create!(username: 'Wadah', password: 'password') 8 | User.create!(username: 'Yaakov', password: 'password') 9 | 10 | 40.times do || 11 | User.create!(username: Faker::Internet.user_name, password: Faker::Internet.password(8)) 12 | end 13 | 14 | # User.create!(username: 'Musgrave', password: 'SpursSpurs123') 15 | # User.create!(username: 'Chris_Hakos', password: 'password') 16 | # User.create!(username: 'CalvinLEE', password: 'password') 17 | # User.create!(username: 'WP_Johnson', password: 'password') 18 | # User.create!(username: 'BAIKEN', password: 'password') 19 | # User.create!(username: 'Jesus Christ', password: 'password') 20 | # User.create!(username: 'MikeBoan', password: 'password') 21 | # User.create!(username: 'Oscar', password: 'password') 22 | # User.create!(username: 'WenBoooooook', password: 'password') 23 | # User.create!(username: 'Mos_Steph', password: 'password') 24 | # User.create!(username: 'McBennett', password: 'password') 25 | # User.create!(username: 'Nick_da_Greek', password: 'password') 26 | # User.create!(username: 'PapaMikeNoel', password: 'password') 27 | # User.create!(username: 'TheRealSeanSnyder', password: 'password') 28 | # User.create!(username: 'TheFakeSeanChowdhury', password: 'password') 29 | # User.create!(username: 'JohnBaek', password: 'password') 30 | # User.create!(username: 'Martinian', password: 'password') 31 | # User.create!(username: 'Kingsley_Shacklebolt', password: 'password') 32 | # User.create!(username: '3r1cVooo', password: 'password') 33 | # User.create!(username: 'AmmarWonderWall', password: 'password') 34 | # User.create!(username: 'Nathaniel_Thorn', password: 'password') 35 | # User.create!(username: 'NathanNathan', password: 'password') 36 | # User.create!(username: 'Nathan', password: 'password') 37 | # User.create!(username: 'Tommy_Pickles', password: 'password') 38 | 39 | def create_replies(comment, post_id, parent_id) 40 | user_id = User.all.sample.id 41 | Comment.create({ 42 | user_id: user_id, 43 | parent_id: parent_id, 44 | parent_type: 'Comment', 45 | post_id: post_id, 46 | body: comment["comment"] 47 | }) 48 | Vote.create({ 49 | user_id: user_id, 50 | voteable_id: parent_id, 51 | voteable_type: 'Comment', 52 | vote_type: "Upvote" 53 | 54 | }) 55 | if (comment["children"]) 56 | comment["children"].reverse[0..2].each do |reply | 57 | create_replies(reply, post_id, parent_id) 58 | end 59 | end 60 | 61 | end 62 | 63 | data_hash.each do |post | 64 | 65 | #puts post["images"].to_s 66 | bar = Post.create({ 67 | title: post["title"], 68 | description: post["description"], 69 | user_id: User.all.sample.id 70 | }) 71 | post["images"].each do |image | 72 | Image.create({ 73 | imageable_id: bar.id, 74 | imageable_type: 'Post', 75 | image: image, 76 | main_image: true, 77 | }) 78 | end 79 | post["comments"].reverse[0..20].each do |comment | 80 | user_id = User.all.sample.id 81 | c2 = Comment.create({ 82 | user_id: user_id, 83 | parent_id: bar.id, 84 | parent_type: 'Post', 85 | post_id: bar.id, 86 | body: comment["comment"] 87 | }) 88 | Vote.create({ 89 | user_id: user_id, 90 | voteable_id: bar.id, 91 | voteable_type: 'Post', 92 | vote_type: "Upvote" 93 | 94 | }) 95 | if comment["children"] 96 | comment["children"].reverse[0..5].each do |child | 97 | create_replies(child, bar.id, c2.id) 98 | end 99 | end 100 | end 101 | end -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # Code is not reloaded between requests. 5 | config.cache_classes = true 6 | 7 | # Eager load code on boot. This eager loads most of Rails and 8 | # your application in memory, allowing both threaded web servers 9 | # and those relying on copy on write to perform better. 10 | # Rake tasks automatically ignore this option for performance. 11 | config.eager_load = true 12 | 13 | # Full error reports are disabled and caching is turned on. 14 | config.consider_all_requests_local = false 15 | config.action_controller.perform_caching = true 16 | 17 | # Enable Rack::Cache to put a simple HTTP cache in front of your application 18 | # Add `rack-cache` to your Gemfile before enabling this. 19 | # For large-scale production use, consider using a caching reverse proxy like 20 | # NGINX, varnish or squid. 21 | # config.action_dispatch.rack_cache = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Compress JavaScripts and CSS. 28 | config.assets.js_compressor = :uglifier 29 | # config.assets.css_compressor = :sass 30 | 31 | # Do not fallback to assets pipeline if a precompiled asset is missed. 32 | config.assets.compile = false 33 | 34 | # Asset digests allow you to set far-future HTTP expiration dates on all assets, 35 | # yet still be able to expire them through the digest params. 36 | config.assets.digest = true 37 | 38 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb 39 | 40 | # Specifies the header that your server uses for sending files. 41 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 42 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 43 | 44 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 45 | # config.force_ssl = true 46 | 47 | # Use the lowest log level to ensure availability of diagnostic information 48 | # when problems arise. 49 | config.log_level = :debug 50 | 51 | # Prepend all log lines with the following tags. 52 | # config.log_tags = [ :subdomain, :uuid ] 53 | 54 | # Use a different logger for distributed setups. 55 | # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) 56 | 57 | # Use a different cache store in production. 58 | # config.cache_store = :mem_cache_store 59 | 60 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 61 | # config.action_controller.asset_host = 'http://assets.example.com' 62 | 63 | # Ignore bad email addresses and do not raise email delivery errors. 64 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 65 | # config.action_mailer.raise_delivery_errors = false 66 | 67 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 68 | # the I18n.default_locale when a translation cannot be found). 69 | config.i18n.fallbacks = true 70 | 71 | # Send deprecation notices to registered listeners. 72 | config.active_support.deprecation = :notify 73 | 74 | # Use default logging formatter so that PID and timestamp are not suppressed. 75 | config.log_formatter = ::Logger::Formatter.new 76 | 77 | # Do not dump schema after migrations. 78 | config.active_record.dump_schema_after_migration = false 79 | end 80 | --------------------------------------------------------------------------------