├── 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 |
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 |
6 | Login with Wenbook
7 |
8 |
9 |
10 | Login with Google
11 |
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 |
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 |
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 |
12 |
16 |
17 |
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 |
6 | Login
7 | or
8 | Sign up!
9 |
10 | );
11 |
12 | const personalGreeting = (currentUser, logout) => (
13 |
14 | Hi, {currentUser.username}!
15 | Log Out
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 |
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 |
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 | Head back to Top
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 |
56 |
57 |
64 |
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 |
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 |
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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------