├── [0m,
├── [0m]
├── .gitignore
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
├── assets
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ ├── .keep
│ │ ├── home.png
│ │ └── nyc.png
│ ├── javascripts
│ │ ├── application.js
│ │ ├── cable.js
│ │ └── channels
│ │ │ └── .keep
│ └── stylesheets
│ │ ├── application.css
│ │ ├── discover.css
│ │ ├── edit_profile.css
│ │ ├── feed_page.css
│ │ ├── homepage.css
│ │ ├── modal.css
│ │ ├── nav.css
│ │ ├── notify_modal.css
│ │ ├── photo_form.css
│ │ ├── profile_page.scss
│ │ ├── reset.css
│ │ ├── session_form.css
│ │ ├── show_image.css
│ │ └── user_photo_form.css
├── channels
│ └── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
├── controllers
│ ├── api
│ │ ├── feeds_controller.rb
│ │ ├── follows_controller.rb
│ │ ├── notifications_controller.rb
│ │ ├── pictures_controller.rb
│ │ ├── sessions_controller.rb
│ │ └── users_controller.rb
│ ├── application_controller.rb
│ ├── concerns
│ │ └── .keep
│ └── static_pages_controller.rb
├── helpers
│ └── application_helper.rb
├── jobs
│ └── application_job.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── application_record.rb
│ ├── concerns
│ │ └── .keep
│ ├── follow.rb
│ ├── like.rb
│ ├── notification.rb
│ ├── picture.rb
│ └── user.rb
└── views
│ ├── api
│ ├── feeds
│ │ ├── fresh.json.jbuilder
│ │ └── home.json.jbuilder
│ ├── follows
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── notifications
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── pictures
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ └── users
│ │ ├── _user.json.jbuilder
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── layouts
│ ├── application.html.erb
│ ├── mailer.html.erb
│ └── mailer.text.erb
│ └── static_pages
│ └── root.html.erb
├── bin
├── bundle
├── rails
├── rake
├── setup
├── spring
├── update
└── yarn
├── config.ru
├── config
├── application.rb
├── boot.rb
├── cable.yml
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── application_controller_renderer.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── cookies_serializer.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ └── wrap_parameters.rb
├── locales
│ └── en.yml
├── puma.rb
├── routes.rb
├── secrets.yml
└── spring.rb
├── db
├── migrate
│ ├── 20180605195708_create_users.rb
│ ├── 20180605211823_change_users.rb
│ ├── 20180607212713_create_pictures.rb
│ ├── 20180610022153_create_follows.rb
│ ├── 20180613202647_create_notifications.rb
│ ├── 20180613203140_update_users.rb
│ ├── 20180614170848_update_notification.rb
│ ├── 20180614172931_update_users2.rb
│ └── 20180729043814_create_likes.rb
├── schema.rb
└── seeds.rb
├── frontend
├── actions
│ ├── follow_actions.js
│ ├── modal_actions.js
│ ├── notification_actions.js
│ ├── picture_actions.js
│ └── session_actions.js
├── components
│ ├── App.jsx
│ ├── discover
│ │ ├── discover_component.jsx
│ │ ├── discover_container.js
│ │ └── discover_feed_item.jsx
│ ├── feed
│ │ ├── feed.jsx
│ │ ├── feed_container.js
│ │ └── feed_list_item.jsx
│ ├── greeting
│ │ ├── greeting.jsx
│ │ └── greeting_container.js
│ ├── home.jsx
│ ├── modal
│ │ └── modal.jsx
│ ├── nav
│ │ ├── nav_bar.jsx
│ │ └── nav_container.js
│ ├── notification
│ │ ├── notification_modal.jsx
│ │ ├── notification_modal_container.js
│ │ └── notify_modal_list_item.jsx
│ ├── profile
│ │ ├── edit_user_container.js
│ │ ├── edit_user_form.jsx
│ │ ├── photo_list_item.jsx
│ │ ├── profile_container.js
│ │ ├── profile_page.jsx
│ │ ├── user_photo_container.js
│ │ └── user_photo_form.jsx
│ ├── root.jsx
│ ├── session
│ │ ├── login_form_container.js
│ │ ├── session_form.jsx
│ │ └── signup_form_container.js
│ ├── show
│ │ ├── edit_picture_container.js
│ │ ├── show_picture.jsx
│ │ └── show_picture_container.js
│ └── upload
│ │ ├── picture_form.jsx
│ │ └── upload_picture_container.js
├── index.jsx
├── reducers
│ ├── entities_reducer.js
│ ├── errors_reducer.js
│ ├── follows_reducer.js
│ ├── modal_reducer.js
│ ├── notifications_reducer.js
│ ├── pictures_reducer.js
│ ├── root_reducer.js
│ ├── session_errors_reducer.js
│ ├── session_reducer.js
│ ├── ui_reducer.js
│ └── users_reducer.js
├── store
│ └── store.js
└── util
│ ├── feed_util.js
│ ├── follow_util.js
│ ├── notification_util.js
│ ├── picture_api_util.js
│ ├── route_util.jsx
│ ├── selectors.js
│ └── session_api_util.js
├── lib
├── assets
│ └── .keep
└── tasks
│ └── .keep
├── package-lock.json
├── package.json
├── public
├── 404.html
├── 422.html
├── 500.html
├── apple-touch-icon-precomposed.png
├── apple-touch-icon.png
├── favicon.ico
└── robots.txt
├── test
├── application_system_test_case.rb
├── controllers
│ └── .keep
├── fixtures
│ ├── .keep
│ ├── files
│ │ └── .keep
│ ├── follows.yml
│ ├── notifications.yml
│ ├── pictures.yml
│ └── users.yml
├── helpers
│ └── .keep
├── integration
│ └── .keep
├── mailers
│ └── .keep
├── models
│ ├── .keep
│ ├── follow_test.rb
│ ├── notification_test.rb
│ ├── picture_test.rb
│ └── user_test.rb
├── system
│ └── .keep
└── test_helper.rb
├── vendor
└── .keep
└── webpack.config.js
/[0m,:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/[0m,
--------------------------------------------------------------------------------
/[0m]:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/[0m]
--------------------------------------------------------------------------------
/.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 all logfiles and tempfiles.
11 | /log/*
12 | /tmp/*
13 | !/log/.keep
14 | !/tmp/.keep
15 |
16 | /node_modules
17 | /yarn-error.log
18 |
19 | .byebug_history
20 |
21 | node_modules/
22 | bundle.js
23 | bundle.js.map
24 | .byebug_history
25 | .DS_Store
26 | npm-debug.log
27 |
28 | # Ignore application configuration
29 | /config/application.yml
30 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | git_source(:github) do |repo_name|
4 | repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
5 | "https://github.com/#{repo_name}.git"
6 | end
7 |
8 | gem 'faker', :git => 'https://github.com/stympy/faker.git', :branch => 'master'
9 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
10 | gem 'rails', '~> 5.1.6'
11 | # Use postgresql as the database for Active Record
12 | gem 'pg', '>= 0.18', '< 2.0'
13 | # Use Puma as the app server
14 | gem 'puma', '~> 3.7'
15 | # Use SCSS for stylesheets
16 | gem 'sass-rails', '~> 5.0'
17 | # Use Uglifier as compressor for JavaScript assets
18 | gem 'uglifier', '>= 1.3.0'
19 | # See https://github.com/rails/execjs#readme for more supported runtimes
20 | # gem 'therubyracer', platforms: :ruby
21 | gem 'figaro'
22 | # Use CoffeeScript for .coffee assets and views
23 | gem 'coffee-rails', '~> 4.2'
24 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
25 | gem 'jbuilder', '~> 2.5'
26 | # Use Redis adapter to run Action Cable in production
27 | # gem 'redis', '~> 4.0'
28 | # Use ActiveModel has_secure_password
29 | # gem 'bcrypt', '~> 3.1.7'
30 | gem 'dotenv-rails', groups: [:development, :test]
31 | # Use Capistrano for deployment
32 | # gem 'capistrano-rails', group: :development
33 | gem 'annotate'
34 | gem 'bcrypt'
35 | gem 'jquery-rails'
36 | group :development, :test do
37 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
38 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
39 | # Adds support for Capybara system testing and selenium driver
40 | gem 'capybara', '~> 2.13'
41 | gem 'selenium-webdriver'
42 | end
43 |
44 |
45 | group :development do
46 | # Access an IRB console on exception pages or by using <%= console %> anywhere in the code.
47 | gem 'web-console', '>= 3.3.0'
48 | gem 'listen', '>= 3.0.5', '< 3.2'
49 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
50 | gem 'spring'
51 | gem 'spring-watcher-listen', '~> 2.0.0'
52 | gem 'better_errors'
53 | gem 'binding_of_caller'
54 | gem 'pry-rails'
55 |
56 | end
57 |
58 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
59 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
60 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GIT
2 | remote: https://github.com/stympy/faker.git
3 | revision: a7c03e67c18356b32a487795332c01c3d8d9add7
4 | branch: master
5 | specs:
6 | faker (1.8.7)
7 | i18n (>= 0.7)
8 |
9 | GEM
10 | remote: https://rubygems.org/
11 | specs:
12 | actioncable (5.1.6)
13 | actionpack (= 5.1.6)
14 | nio4r (~> 2.0)
15 | websocket-driver (~> 0.6.1)
16 | actionmailer (5.1.6)
17 | actionpack (= 5.1.6)
18 | actionview (= 5.1.6)
19 | activejob (= 5.1.6)
20 | mail (~> 2.5, >= 2.5.4)
21 | rails-dom-testing (~> 2.0)
22 | actionpack (5.1.6)
23 | actionview (= 5.1.6)
24 | activesupport (= 5.1.6)
25 | rack (~> 2.0)
26 | rack-test (>= 0.6.3)
27 | rails-dom-testing (~> 2.0)
28 | rails-html-sanitizer (~> 1.0, >= 1.0.2)
29 | actionview (5.1.6)
30 | activesupport (= 5.1.6)
31 | builder (~> 3.1)
32 | erubi (~> 1.4)
33 | rails-dom-testing (~> 2.0)
34 | rails-html-sanitizer (~> 1.0, >= 1.0.3)
35 | activejob (5.1.6)
36 | activesupport (= 5.1.6)
37 | globalid (>= 0.3.6)
38 | activemodel (5.1.6)
39 | activesupport (= 5.1.6)
40 | activerecord (5.1.6)
41 | activemodel (= 5.1.6)
42 | activesupport (= 5.1.6)
43 | arel (~> 8.0)
44 | activesupport (5.1.6)
45 | concurrent-ruby (~> 1.0, >= 1.0.2)
46 | i18n (>= 0.7, < 2)
47 | minitest (~> 5.1)
48 | tzinfo (~> 1.1)
49 | addressable (2.5.2)
50 | public_suffix (>= 2.0.2, < 4.0)
51 | annotate (2.7.4)
52 | activerecord (>= 3.2, < 6.0)
53 | rake (>= 10.4, < 13.0)
54 | arel (8.0.0)
55 | bcrypt (3.1.12)
56 | better_errors (2.4.0)
57 | coderay (>= 1.0.0)
58 | erubi (>= 1.0.0)
59 | rack (>= 0.9.0)
60 | bindex (0.5.0)
61 | binding_of_caller (0.8.0)
62 | debug_inspector (>= 0.0.1)
63 | builder (3.2.3)
64 | byebug (10.0.2)
65 | capybara (2.18.0)
66 | addressable
67 | mini_mime (>= 0.1.3)
68 | nokogiri (>= 1.3.3)
69 | rack (>= 1.0.0)
70 | rack-test (>= 0.5.4)
71 | xpath (>= 2.0, < 4.0)
72 | childprocess (0.9.0)
73 | ffi (~> 1.0, >= 1.0.11)
74 | coderay (1.1.2)
75 | coffee-rails (4.2.2)
76 | coffee-script (>= 2.2.0)
77 | railties (>= 4.0.0)
78 | coffee-script (2.4.1)
79 | coffee-script-source
80 | execjs
81 | coffee-script-source (1.12.2)
82 | concurrent-ruby (1.0.5)
83 | crass (1.0.4)
84 | debug_inspector (0.0.3)
85 | dotenv (2.4.0)
86 | dotenv-rails (2.4.0)
87 | dotenv (= 2.4.0)
88 | railties (>= 3.2, < 6.0)
89 | erubi (1.7.1)
90 | execjs (2.7.0)
91 | ffi (1.9.23)
92 | figaro (1.1.1)
93 | thor (~> 0.14)
94 | globalid (0.4.1)
95 | activesupport (>= 4.2.0)
96 | i18n (1.0.1)
97 | concurrent-ruby (~> 1.0)
98 | jbuilder (2.7.0)
99 | activesupport (>= 4.2.0)
100 | multi_json (>= 1.2)
101 | jquery-rails (4.3.3)
102 | rails-dom-testing (>= 1, < 3)
103 | railties (>= 4.2.0)
104 | thor (>= 0.14, < 2.0)
105 | listen (3.1.5)
106 | rb-fsevent (~> 0.9, >= 0.9.4)
107 | rb-inotify (~> 0.9, >= 0.9.7)
108 | ruby_dep (~> 1.2)
109 | loofah (2.2.2)
110 | crass (~> 1.0.2)
111 | nokogiri (>= 1.5.9)
112 | mail (2.7.0)
113 | mini_mime (>= 0.1.1)
114 | method_source (0.9.0)
115 | mini_mime (1.0.0)
116 | mini_portile2 (2.3.0)
117 | minitest (5.11.3)
118 | multi_json (1.13.1)
119 | nio4r (2.3.1)
120 | nokogiri (1.8.2)
121 | mini_portile2 (~> 2.3.0)
122 | pg (1.0.0)
123 | pry (0.11.3)
124 | coderay (~> 1.1.0)
125 | method_source (~> 0.9.0)
126 | pry-rails (0.3.6)
127 | pry (>= 0.10.4)
128 | public_suffix (3.0.2)
129 | puma (3.11.4)
130 | rack (2.0.5)
131 | rack-test (1.0.0)
132 | rack (>= 1.0, < 3)
133 | rails (5.1.6)
134 | actioncable (= 5.1.6)
135 | actionmailer (= 5.1.6)
136 | actionpack (= 5.1.6)
137 | actionview (= 5.1.6)
138 | activejob (= 5.1.6)
139 | activemodel (= 5.1.6)
140 | activerecord (= 5.1.6)
141 | activesupport (= 5.1.6)
142 | bundler (>= 1.3.0)
143 | railties (= 5.1.6)
144 | sprockets-rails (>= 2.0.0)
145 | rails-dom-testing (2.0.3)
146 | activesupport (>= 4.2.0)
147 | nokogiri (>= 1.6)
148 | rails-html-sanitizer (1.0.4)
149 | loofah (~> 2.2, >= 2.2.2)
150 | railties (5.1.6)
151 | actionpack (= 5.1.6)
152 | activesupport (= 5.1.6)
153 | method_source
154 | rake (>= 0.8.7)
155 | thor (>= 0.18.1, < 2.0)
156 | rake (12.3.1)
157 | rb-fsevent (0.10.3)
158 | rb-inotify (0.9.10)
159 | ffi (>= 0.5.0, < 2)
160 | ruby_dep (1.5.0)
161 | rubyzip (1.2.1)
162 | sass (3.5.6)
163 | sass-listen (~> 4.0.0)
164 | sass-listen (4.0.0)
165 | rb-fsevent (~> 0.9, >= 0.9.4)
166 | rb-inotify (~> 0.9, >= 0.9.7)
167 | sass-rails (5.0.7)
168 | railties (>= 4.0.0, < 6)
169 | sass (~> 3.1)
170 | sprockets (>= 2.8, < 4.0)
171 | sprockets-rails (>= 2.0, < 4.0)
172 | tilt (>= 1.1, < 3)
173 | selenium-webdriver (3.12.0)
174 | childprocess (~> 0.5)
175 | rubyzip (~> 1.2)
176 | spring (2.0.2)
177 | activesupport (>= 4.2)
178 | spring-watcher-listen (2.0.1)
179 | listen (>= 2.7, < 4.0)
180 | spring (>= 1.2, < 3.0)
181 | sprockets (3.7.1)
182 | concurrent-ruby (~> 1.0)
183 | rack (> 1, < 3)
184 | sprockets-rails (3.2.1)
185 | actionpack (>= 4.0)
186 | activesupport (>= 4.0)
187 | sprockets (>= 3.0.0)
188 | thor (0.20.0)
189 | thread_safe (0.3.6)
190 | tilt (2.0.8)
191 | tzinfo (1.2.5)
192 | thread_safe (~> 0.1)
193 | uglifier (4.1.10)
194 | execjs (>= 0.3.0, < 3)
195 | web-console (3.6.2)
196 | actionview (>= 5.0)
197 | activemodel (>= 5.0)
198 | bindex (>= 0.4.0)
199 | railties (>= 5.0)
200 | websocket-driver (0.6.5)
201 | websocket-extensions (>= 0.1.0)
202 | websocket-extensions (0.1.3)
203 | xpath (3.1.0)
204 | nokogiri (~> 1.8)
205 |
206 | PLATFORMS
207 | ruby
208 |
209 | DEPENDENCIES
210 | annotate
211 | bcrypt
212 | better_errors
213 | binding_of_caller
214 | byebug
215 | capybara (~> 2.13)
216 | coffee-rails (~> 4.2)
217 | dotenv-rails
218 | faker!
219 | figaro
220 | jbuilder (~> 2.5)
221 | jquery-rails
222 | listen (>= 3.0.5, < 3.2)
223 | pg (>= 0.18, < 2.0)
224 | pry-rails
225 | puma (~> 3.7)
226 | rails (~> 5.1.6)
227 | sass-rails (~> 5.0)
228 | selenium-webdriver
229 | spring
230 | spring-watcher-listen (~> 2.0.0)
231 | tzinfo-data
232 | uglifier (>= 1.3.0)
233 | web-console (>= 3.3.0)
234 |
235 | BUNDLED WITH
236 | 1.16.1
237 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # README
2 |
3 | This site is a clone of 500px.com. While logged in, and one can view other people's photos on their feed, follow other users, and upload pictures of their own. Ruby on Rails was used for the back end, and React was used for the front end.
4 |
5 | [360px](https://threesixtypixels.herokuapp.com/#/)
6 |
7 | # Technologies
8 |
9 | * Ruby on Rails with postgresql
10 | * React-Redux
11 | * Cloudinary
12 | * AJAX
13 | * Vanilla DOM
14 |
15 | # Key Features
16 |
17 | ### Home and Discover Feeds
18 |
19 | 
20 |
21 | * At first, when tackling how to fetch the correct pictures for both feeds, I was not sure how to differenctiate between the two. Up until that point, I was only taught about the standard routes. After researching this topic, I came across custom routes , which proved to be a viable solution. My routes for my two feeds are as follows:
22 |
23 | ```ruby
24 | get '/feed', to: 'feeds#home', controller: 'feeds'
25 | get '/fresh', to: 'feeds#fresh', controller: 'feeds'
26 | ```
27 | * In the feeds controller, I used a predefined User method on the current user in order to get the two feeds accordingly. Right now, my app fetches pictures based on who the user is following, as well as whatever pictures are most recent. My logic also adds pictures from users the current user isn't following, so their feed won't be empty upon signing up for an account:
28 |
29 | ```ruby
30 | def get_home_feed
31 | followings = self.followings
32 | pictures = []
33 | followings.each do |follow|
34 | user = User.find(follow.leader_id)
35 | pictures += user.pictures
36 | end
37 | if pictures.length < 30
38 | other_pictures = Picture.all.sort {|a,b| b.created_at <=> a.created_at}
39 | count = 0
40 | other_pictures.each do |picture|
41 | unless count == 3 || picture.uploader_id === self.id || pictures.include?(picture)
42 | pictures.push(picture)
43 | count += 1
44 | end
45 |
46 | end
47 | end
48 | pictures.sort {|a,b| b.created_at <=> a.created_at}.take(30)
49 | end
50 | ```
51 | On the front end, html elements are rendered based on if the current user is following the user who posted the picture:
52 |
53 | 
54 |
55 | ### Followings
56 |
57 | 
58 |
59 | I really enjoyed implementing this in my website. It was fun planning out and and going through the steps these step. While the backend for implementing follows was faily simple, more logic was required in the front end.
60 |
61 | * One of the key mechanices for rendering the follow button correctly is knowing whether the current user is following the user whose profile they are on. The follow for both users is retreived in the following selector:
62 |
63 | ```javascript
64 | export const findFollow = (follows, currentUserId, profileUserId) => {
65 | const followArr = Object.values(follows);
66 | for (var i = 0; i < followArr.length; i++) {
67 | const follow = followArr[i];
68 | if (follow.follower_id === currentUserId && follow.leader_id === profileUserId) {
69 | return follow;
70 | }
71 | }
72 | return null;
73 | };
74 | ```
75 |
76 | * In order for the follow button is stay updated, I determined that I would have to dispatch the proper action based on whether the current user is following the user who the button corrensponds to:
77 |
78 | ```javascript
79 | updateFollow(){
80 | const {deleteFollow, createFollow, follow, user, currentUser, createNotification} = this.props;
81 | if (follow){
82 | deleteFollow(follow.id);
83 | } else {
84 | createFollow({leader_id: user.id, follower_id: currentUser.id});
85 | createNotification({initiator_id: currentUser.id, user_id: user.id});
86 | }
87 | }
88 | ```
89 |
90 | A notification is also created so the new followee will receive a notification letting them know that they received a new follower.
91 |
92 | # Things to Note
93 |
94 | * Logging in through demo login provides the user with a new notification so they can see the feature without having to make multiple accounts.
95 |
96 | In the near future, I plan on coming back to this project and adding new features. These include:
97 |
98 | * Taggings for pictures
99 | * A search feature for users and tags
100 | * Likes and comments
101 | * A notification page
102 |
--------------------------------------------------------------------------------
/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_relative 'config/application'
5 |
6 | Rails.application.load_tasks
7 |
--------------------------------------------------------------------------------
/app/assets/config/manifest.js:
--------------------------------------------------------------------------------
1 | //= link_tree ../images
2 | //= link_directory ../javascripts .js
3 | //= link_directory ../stylesheets .css
4 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/app/assets/images/.keep
--------------------------------------------------------------------------------
/app/assets/images/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/app/assets/images/home.png
--------------------------------------------------------------------------------
/app/assets/images/nyc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/app/assets/images/nyc.png
--------------------------------------------------------------------------------
/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, or any plugin's
5 | // 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. JavaScript code in this file should be added after the last require_* statement.
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 rails-ujs
16 | //= require_tree .
17 |
--------------------------------------------------------------------------------
/app/assets/javascripts/cable.js:
--------------------------------------------------------------------------------
1 | // Action Cable provides the framework to deal with WebSockets in Rails.
2 | // You can generate new channels where WebSocket features live using the `rails generate channel` command.
3 | //
4 | //= require action_cable
5 | //= require_self
6 | //= require_tree ./channels
7 |
8 | (function() {
9 | this.App || (this.App = {});
10 |
11 | App.cable = ActionCable.createConsumer();
12 |
13 | }).call(this);
14 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/app/assets/javascripts/channels/.keep
--------------------------------------------------------------------------------
/app/assets/stylesheets/application.css:
--------------------------------------------------------------------------------
1 | /*
2 | * This is a manifest file that'll be compiled into application.css, which will include all the files
3 | * listed below.
4 | *
5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
6 | * 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 other CSS/SCSS
10 | * files in this directory. Styles in this file should be added after the last require_* statement.
11 | * It is generally better to create a new file per style scope.
12 | *
13 | *= require_tree .
14 | *= require_self
15 | */
16 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/discover.css:
--------------------------------------------------------------------------------
1 |
2 | .h1 {
3 | top: 100px;
4 | position: absolute;
5 | }
6 |
7 | .discover-holder {
8 | background-color: #f0f0f5;
9 | width: 100vw;
10 | height: 100%;
11 | }
12 |
13 | .discover-top-div {
14 | position: absolute;
15 | top: 54px;
16 | width: 100vw;
17 | height: 125px;
18 | background-color: white;
19 | border-bottom: 1px lightgrey solid;
20 | padding-left: 60px;
21 | }
22 |
23 | .discover-top-div h1 {
24 | font-family: sans-serif;
25 | margin-bottom: 10px;
26 | color: #2D2D2D;
27 | font-size: 36px;
28 | }
29 |
30 | .discover-top-div p {
31 | margin-top: 0px;
32 | font-family: sans-serif;
33 | color: #5B5B5B;
34 | font-size: 18px;
35 | }
36 |
37 | .discover-picture-list {
38 | margin: auto;
39 | padding: 0;
40 | }
41 |
42 | .discover-picture-list li{
43 | list-style: none;
44 | margin: 0px .60vw .0px 0px;
45 | border-bottom: solid #f0f0f5 .60vw;
46 | position: relative;
47 | height: 22.3vw;
48 | }
49 |
50 | .discover-list-holder {
51 | margin: 0 auto;
52 | width: 86vw;
53 | display: flex;
54 | flex-wrap: wrap;
55 | align-items: center;
56 | margin-top: 180px;
57 | border-top: #f0f0f5 solid 20px
58 | }
59 |
60 | .discover-photo {
61 | /* height: 300px;
62 | width: 400px; */
63 | width: 27.4vw;
64 | height: 22.3vw;
65 | /* transition-property: filter;
66 | transition-duration: .5s; */
67 | }
68 |
69 | .discover-photo:hover {
70 | /* filter: drop-shadow(8px 8px 8px black); */
71 | cursor: pointer;
72 | /* transition-property: filter;
73 | transition-duration: .5s; */
74 | }
75 |
76 | /* .user-info {
77 | width: 27.4vw;
78 | height: 22.3vw;
79 | background-color: red;
80 | z-index: 50;
81 | position: absolute;
82 | top: 0%;
83 | left: 0%;
84 | } */
85 |
86 | .user-info {
87 | z-index: 1;
88 | width: 27.4vw;
89 | height: 70px;
90 | /* background-color:grey; */
91 | position: relative;
92 | top: -75px;
93 | opacity: 0;
94 | background: linear-gradient(rgba(0,0,0,0),black);
95 | transition: .5s ease
96 | }
97 |
98 |
99 |
100 | .discover-username {
101 | font-family: Rajdhani;
102 | color: white;
103 | position: relative;
104 | bottom: 23px;
105 | margin-left: 10px;
106 | }
107 |
108 | .discover-profile-icon {
109 | border-radius: 50%;
110 | height: 50px;
111 | width: 50px;
112 | position: relative;
113 | margin-top: 10px;
114 | margin-bottom: 7px;
115 | margin-left: 15px;
116 | box-shadow: 0px 2px 4px rgba(0,0,0,0.2);
117 |
118 | }
119 |
120 | .discover-picture-list li:hover .user-info {
121 | display: block;
122 | opacity: 1;
123 | }
124 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/edit_profile.css:
--------------------------------------------------------------------------------
1 | .upload-cover-button {
2 | width: 300px;
3 | background-color: #00aaff;
4 | color: white;
5 | font-size: 16px;
6 | border-width: 0px;
7 | height: 40px;
8 | margin-left: 50px;
9 | border-radius: 3px;
10 | margin-top: 40px
11 | }
12 |
13 | .upload-cover-button:hover{
14 | background-color: #6FB7FE;
15 | cursor: pointer;
16 | }
17 |
18 | .upload-profile-button {
19 | width: 300px;
20 | background-color: #ce54ff;
21 | color: white;
22 | font-size: 16px;
23 | border-width: 0px;
24 | height: 40px;
25 | margin-left: 50px;
26 | border-radius: 3px;
27 | margin-top: 40px;
28 | }
29 |
30 | .upload-profile-button:hover{
31 | background-color: #d670ff;
32 | cursor: pointer;
33 | }
34 |
35 | .description-submit {
36 | width: 300px;
37 | background-color: #34bf49;
38 | color: white;
39 | font-size: 16px;
40 | border-width: 0px;
41 | height: 40px;
42 | margin-left: 50px;
43 | border-radius: 3px;
44 | }
45 |
46 | .description-submit:hover {
47 | background-color: #41cb56;
48 | cursor: pointer;
49 | }
50 |
51 | .description-input {
52 | height: 35px;
53 | width: 300px;
54 | margin-left: 50px;
55 | margin-top: 0px;
56 | margin-bottom: 35px;
57 |
58 | }
59 |
60 | .description {
61 | color: grey;
62 | font-family: sans-serif;
63 | font-size: 14px;
64 | text-align: center;
65 | margin-top: 40px
66 | }
67 |
68 | .edit-close {
69 | position: absolute
70 | }
71 |
72 | .edit-close:hover {
73 | cursor: pointer;
74 | color: red;
75 | }
76 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/feed_page.css:
--------------------------------------------------------------------------------
1 | .feed-container {
2 | background-color: #f0f0f5;
3 | width: 100vw;
4 | height: 100%;
5 | }
6 |
7 | .feed-photo {
8 | /* height: 600px;
9 | width: 800px; */
10 | height: 32.6vw;
11 | width: 48.5vw;
12 | }
13 |
14 | .feed-photo:hover {
15 | cursor: zoom-in;
16 | }
17 |
18 |
19 |
20 |
21 |
22 |
23 | .photo-holder li {
24 | list-style-type: none;
25 | /* top: 70px; */
26 | position: relative;
27 | }
28 |
29 |
30 | .feed-list {
31 | padding: 0;
32 | margin-top: 50px;
33 | border-top: #f0f0f5 solid 20px
34 | }
35 |
36 | .feed-profile-icon {
37 | border-radius: 50%;
38 | height: 50px;
39 | width: 50px;
40 | position: relative;
41 | margin-top: 10px;
42 | margin-bottom: 7px;
43 | margin-left: 15px;
44 | }
45 |
46 | .feed-username {
47 | font-weight: 400;
48 | font-size: 17px;
49 | font-weight: bolder;
50 | color: #5f5754;
51 | position: relative;
52 | bottom: 23px;
53 | margin-left: 10px;
54 | }
55 |
56 | .photo-holder {
57 | /* height: 740px;
58 | width: 800px; */
59 | /* height: 39.8vw; */
60 | /* height: 42.8vw; */
61 | width: 48.5vw;
62 | background-color: white;
63 | margin: 0 auto;
64 | margin-bottom: 20px;
65 | border: 1px solid lightgrey;
66 |
67 | }
68 |
69 | .feed-title {
70 | font-size: 22px;
71 | font-family: Rajdhani;
72 | color: #5f5754;
73 | margin-top: 15px;
74 | margin-left: 15px;
75 | margin-right: 15px;
76 | width: 47vw;
77 | overflow: wrap break-word;
78 | padding-bottom: 15px;
79 | }
80 | .feed-title2 {
81 | font-size: 22px;
82 | font-family: Rajdhani;
83 | color: white;
84 | margin-top: 15px;
85 | margin-left: 15px;
86 | margin-right: 15px;
87 | width: 48.5vw;
88 | overflow: wrap break-word;
89 | padding-bottom: 15px;
90 | }
91 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/homepage.css:
--------------------------------------------------------------------------------
1 | .homeBackground{
2 | background: url(https://res.cloudinary.com/dbm56y2y/image/upload/v1528870698/benjamin-patin-419152-unsplash.jpg);
3 | width: 100vw;
4 | height: 100vh;
5 | background-repeat: no-repeat;
6 | background-size:cover
7 | }
8 |
9 | .home_content{
10 | top: 400px;
11 | align-content: center;
12 | position: absolute;
13 | top:40%;
14 | left: 0;
15 | right: 0;
16 | margin: auto;
17 | }
18 | /* got help from https://stackoverflow.com/questions/2570972/css-font-border from Pablo Bianchi for this text shadow */
19 | .home_header {
20 | color: white;
21 | text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
22 | text-align: center;
23 | font-family: Rajdhani;
24 | font-size: 44px;
25 | font-weight: 900;
26 | }
27 |
28 | .home_p {
29 | color: white;
30 | text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
31 | text-align: center;
32 | margin: 40px 0px;
33 | font-family: Rajdhani;
34 | font-size: 38px;
35 | }
36 |
37 | .home_link {
38 | text-align: center;
39 |
40 | }
41 |
42 | .home_button {
43 | background-color: #00aaff;
44 | border-radius: 30px;
45 | color: white;
46 | height: 55px;
47 | width: 160px;
48 | font-size: 18px;
49 | border-width: 0px;
50 |
51 | }
52 |
53 | .home_button:hover {
54 | /* background-color: #3BB0FE; */
55 | background-color: #6FB7FE;
56 | cursor: pointer;
57 |
58 | }
59 |
60 |
61 | .home_link {
62 | align-content: center;
63 |
64 | }
65 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/modal.css:
--------------------------------------------------------------------------------
1 | .modal-background {
2 | position: fixed;
3 | top: 0;
4 | bottom: 0;
5 | right: 0;
6 | left: 0;
7 | background: rgba(0, 0, 0, 0.7);
8 | z-index: 30;
9 | }
10 |
11 | .modal-background2 {
12 | position: fixed;
13 | top: 0;
14 | bottom: 0;
15 | right: 0;
16 | left: 0;
17 | z-index: 200;
18 | }
19 |
20 | .modal-child {
21 | position: absolute;
22 | background-color: white;
23 | top: 50%;
24 | left: 50%;
25 | transform: translate(-50%, -50%);
26 | width: 400px;
27 | height: 550px;
28 | z-index: 50;
29 | }
30 |
31 | .modal-child-edit {
32 | position: absolute;
33 | background-color: white;
34 | top: 50%;
35 | left: 50%;
36 | transform: translate(-50%, -50%);
37 | width: 400px;
38 | height: 387px;
39 | z-index: 50;
40 | }
41 |
42 | .modal-child-show {
43 | position: absolute;
44 | background-color: white;
45 | top: 50%;
46 | left: 50%;
47 | transform: translate(-50%, -50%);
48 | /* width: 800px;
49 | height: 600px; */
50 | width: 72.9vw;
51 | height: 45.1vw;
52 | }
53 |
54 | .modal-child-user {
55 | position: absolute;
56 | background-color: white;
57 | top: 50%;
58 | left: 50%;
59 | transform: translate(-50%, -50%);
60 | width: 400px;
61 | height: 470px;
62 | z-index: 50;
63 | }
64 |
65 | .modal-child-notify {
66 | z-index: 50;
67 | position: fixed;
68 | background-color: white;
69 | right: 0%;
70 | top: 0%;
71 | margin-top: 57px;
72 | margin-right: 10px;
73 | border: 1px solid rgba(34,34,34,0.1);
74 | /* transform: translate(-50%, -50%); */
75 | width: 356px;
76 | }
77 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/nav.css:
--------------------------------------------------------------------------------
1 | .nav_bar {
2 | width: 100%;
3 | justify-content: space-between;
4 | display: flex;
5 | }
6 |
7 | .nav_bar li {
8 | list-style: none;
9 | }
10 |
11 | .left_nav {
12 | padding: 0px;
13 | margin: 15px 0px 0px 50px;
14 | }
15 |
16 |
17 | .right_nav {
18 | width: 160px;
19 | display: flex;
20 | position: relative;
21 | justify-content: space-between;
22 | margin: 15px 50px 0px 0px;
23 | }
24 |
25 | .right_nav li{
26 | margin: auto 0px
27 | }
28 |
29 | .logo{
30 | color: white;
31 | text-decoration: none;
32 | font-size: 30px;
33 | font-family: Rajdhani;
34 | text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
35 | }
36 |
37 | .logo:hover {
38 | cursor: default;
39 | }
40 |
41 | .nav_button{
42 | background-color: #00aaff;
43 | border-radius: 30px;
44 | color: white;
45 | height: 30px;
46 | width: 90px;
47 | font-size: 14px;
48 | border-width: 0px
49 | }
50 |
51 | .nav_login {
52 | color: white;
53 | text-decoration: none;
54 | vertical-align: center;
55 | font-size: 16px;
56 | text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
57 | }
58 |
59 | .nav_login:hover {
60 | color: #00aaff;
61 | }
62 |
63 | .nav_button:hover {
64 | background-color: #6FB7FE;
65 | cursor: pointer;
66 | }
67 |
68 | /* this is for the login nav bar */
69 |
70 | .session_bar {
71 | width: 100%;
72 | justify-content: space-between;
73 | display: flex;
74 | background-color: white;
75 | border-bottom: 1px lightgrey solid;
76 | height: 53px;
77 | position: fixed;
78 | top: 0px;
79 | z-index: 99;
80 | /* overflow: hidden; */
81 | }
82 |
83 |
84 |
85 | .session_bar ul{
86 | margin-top: 0px;
87 |
88 | }
89 |
90 | .session_bar li {
91 | list-style: none
92 | }
93 |
94 | .session_container {
95 | background-color: #f0f0f5;
96 | width: 100vw;
97 | height: 100vh;
98 | }
99 |
100 | .session_logo {
101 | color: black;
102 | text-decoration: none;
103 | font-size: 30px;
104 | font-family: Rajdhani;
105 | z-index: 20;
106 | }
107 |
108 | .session_logo:hover {
109 | cursor: pointer;
110 | }
111 |
112 | .login_signup {
113 | background-color: #34bf49;
114 | color: white;
115 | height: 53px;
116 | width: 90px;
117 | font-size: 14px;
118 | border-width: 0px;
119 | z-index: 20;
120 | }
121 |
122 | .login_signup:hover {
123 | background-color: #41cb56;
124 | cursor: pointer;
125 | }
126 |
127 | .login_left {
128 | padding: 10px;
129 | margin: 0px 0px 0px 45px;
130 | display: flex;
131 | position: relative;
132 | justify-content: space-between;
133 | width: 200px;
134 | }
135 |
136 | .login_left li {
137 | position: absolute;
138 | }
139 |
140 |
141 | /* nav bar css for feed page */
142 |
143 | .feed_logo {
144 | color: black;
145 | text-decoration: none;
146 | font-size: 30px;
147 | font-family: Rajdhani;
148 | z-index: 20;
149 | }
150 |
151 | .feed_logo:hover {
152 | cursor: default;
153 | }
154 |
155 | .upload {
156 | background-color: #34bf49;
157 | color: white;
158 | height: 53px;
159 | width: 110px;
160 | /* font-size: 14px; */
161 | border-width: 0px;
162 | z-index: 99;
163 | justify-content: space-between;
164 | padding: 0;
165 | font-size: 20px;
166 | }
167 |
168 | .icon_upload {
169 | margin-right: 7px;
170 | }
171 |
172 | .font_overide {
173 | font-size: 14px;
174 | }
175 |
176 | .upload:hover {
177 | background-color: #41cb56;
178 | cursor: pointer;
179 | }
180 |
181 | .profile-icon {
182 | border-radius: 50%;
183 | /* background-color: grey; */
184 | height: 25px;
185 | width: 25px;
186 | position: relative;
187 | }
188 | /* had to user css friends phase2 for help with the hamburger menu */
189 | .side-holder:hover .profile-list {
190 | display: block
191 | }
192 |
193 | .profile-list {
194 | display: none;
195 | position: absolute;
196 | left: 10px;
197 | font-size: 14px;
198 | white-space: nowrap;
199 | background: #fff;
200 | overflow: hidden;
201 | padding: 0px;
202 | top: 40px;
203 | }
204 |
205 | .profile-list > li {
206 | width: 80px;
207 | text-align: center;
208 | padding-bottom: 10px;
209 |
210 | }
211 |
212 | .first-item-list {
213 | padding-top: 10px;
214 | }
215 | .profile-link {
216 | text-decoration: none;
217 | color: black;
218 | }
219 |
220 | .profile-link:hover {
221 | color: blue;
222 | cursor: pointer;
223 | }
224 |
225 | .profile-list > li:hover {
226 | color: blue;
227 | cursor: pointer;
228 | }
229 |
230 | /* nav bar styling for profile page */
231 | .profile_logo {
232 | color: black;
233 | text-decoration: none;
234 | font-size: 30px;
235 | font-family: Rajdhani;
236 | z-index: 20;
237 | }
238 |
239 | .profile_logo:hover {
240 | cursor: pointer;
241 | }
242 |
243 | /* nav bar for discover page */
244 |
245 | .discover {
246 | left: 105px;
247 | top: 21px;
248 | }
249 |
250 | .discover-text{
251 | text-decoration: none;
252 | color: black;
253 | }
254 |
255 | .discover-no-link{
256 | left: 105px;
257 | top: 21px;
258 | }
259 | .discover-text:hover {
260 | cursor: pointer;
261 | color: blue;
262 | }
263 |
264 | .discover-hidden {
265 | display: none;
266 | }
267 |
268 | .login-right-nav {
269 | width: 190px;
270 | display: flex;
271 | position: relative;
272 | justify-content: space-between;
273 | margin: 15px 50px 0px 0px;
274 | }
275 |
276 | .login-right-nav li{
277 | margin: auto 0px
278 | }
279 |
280 | .bell {
281 | font-size: 19px;
282 | position: relative;
283 | top: -1px;
284 | }
285 |
286 | .bell:hover {
287 | cursor: pointer;
288 | color: #68a2ff;
289 | }
290 |
291 | .notify {
292 | background-color: red;
293 | height: 25px;
294 | width: 25px;
295 | border-radius: 50%;
296 | text-align: center;
297 | color: white;
298 | top: -1px;
299 | position: relative;
300 | }
301 |
302 | .notiNumber {
303 | top: 4px;
304 | position: relative;
305 | font-weight: bold;
306 | }
307 |
308 | .notify:hover {
309 | cursor: pointer;
310 | opacity: .8;
311 | }
312 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/notify_modal.css:
--------------------------------------------------------------------------------
1 | .notify-header {
2 | height: 45px;
3 | width: 100%;
4 | background-color: #f7f8fa;
5 | border-bottom: 1px solid rgba(34,34,34,0.1);
6 | }
7 |
8 | .notify-title {
9 | color: #71767a;
10 | font-size: 20px;
11 | font-weight: bold;
12 | font-family: Rajdhani;
13 | font-weight: bolder;
14 | position: absolute;
15 | top: 9px;
16 | left: 10px;
17 | }
18 |
19 | .no-notify {
20 | background-color: white;
21 | height: 55px;
22 | }
23 |
24 | .no-notify span {
25 | color: #71767a;
26 | font-family: sans-serif;
27 | font-size: 14px;
28 | top: 62.5px;
29 | left: 100.8px;
30 | position: absolute;
31 | }
32 |
33 |
34 | .notify-link {
35 | text-decoration: none;
36 | }
37 |
38 | .notify-list {
39 | margin-top: 0px;
40 | padding-left: 0px;
41 | overflow: scroll;
42 | margin-bottom: 0px;
43 | max-height: 496px;
44 | }
45 |
46 | .notify-list li {
47 | height: 70px;
48 | width: 356px;
49 | list-style: none;
50 | border-bottom: 1px solid rgba(34,34,34,0.1);
51 | }
52 |
53 | .notify-text {
54 | position: relative;
55 | bottom: 31px;
56 | margin-left: 10px;
57 | overflow: wrap break-word;
58 | color: #71767a;
59 | }
60 |
61 | .notify-link-text {
62 | text-decoration: none;
63 | font-weight: bold;
64 | color: #71767a;
65 | }
66 |
67 | .notify-link-text:hover {
68 | color: #4eaae8
69 | }
70 |
71 |
72 | .notify-list li img {
73 | height: 50px;
74 | width: 50px;
75 | margin: 10px 0px 10px 10px;
76 | }
77 |
78 | .notify-list li img:hover {
79 | opacity: .8;
80 | }
81 |
82 | .notify-close {
83 | height: 30px;
84 | }
85 |
86 | .notify-close:hover {
87 | cursor: pointer
88 | }
89 |
90 | .notify-close span {
91 | color: #71767a;
92 | font-size: 14px;
93 | position: relative;
94 | top: -9px;
95 | left: 160px;
96 | }
97 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/photo_form.css:
--------------------------------------------------------------------------------
1 | .form-image {
2 | width:300px;
3 | height: 300px;
4 | margin: 17px 50px 19px 50px
5 | }
6 |
7 | .modal-upload-button {
8 | width: 300px;
9 | background-color: #34bf49;
10 | color: white;
11 | font-size: 16px;
12 | border-width: 0px;
13 | height: 40px;
14 | margin-left: 50px;
15 | border-radius: 3px;
16 |
17 | }
18 |
19 | .modal-upload-button:hover {
20 | background-color: #41cb56;
21 | cursor: pointer;
22 | }
23 |
24 | .upload-input {
25 | height: 35px;
26 | width: 300px;
27 | margin-left: 50px;
28 | margin-top: 0px;
29 | margin-bottom: 35px;
30 |
31 | }
32 |
33 | .upload-label {
34 | color: grey;
35 | font-family: sans-serif;
36 | font-size: 14px;
37 | margin-left: 182px;
38 | }
39 |
40 | .upload-label2 {
41 | color: grey;
42 | font-family: sans-serif;
43 | font-size: 14px;
44 | margin-left: 187px;
45 | }
46 |
47 | .close-font-upload {
48 | font-size: 24px
49 | }
50 | .upload-close:hover {
51 | cursor: pointer;
52 | color: red;
53 | }
54 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/profile_page.scss:
--------------------------------------------------------------------------------
1 | .profile-holder {
2 | background-color: #f0f0f5;
3 | width: 100vw;
4 | height: 100%;
5 | }
6 |
7 | .profile-holder2 {
8 | background-color: #f0f0f5;
9 | width: 100vw;
10 | height: 100vh;
11 | }
12 |
13 |
14 |
15 | .profile-photo {
16 | /* height: 300px;
17 | width: 400px; */
18 | width: 20.8vw;
19 | height: 16.9vw;
20 | transition-property: filter;
21 | transition-duration: .5s;
22 | /* transition: .35s ease; */
23 | }
24 |
25 | .profile-photo:hover {
26 | filter: drop-shadow(4px 4px 4px black);
27 | cursor: pointer;
28 | transition-property: filter;
29 | transition-duration: .5s;
30 | }
31 |
32 | .picture-list {
33 |
34 | margin: auto;
35 | padding: 0;
36 | }
37 |
38 | .picture-list li {
39 | list-style: none;
40 | margin: 0px .60vw .35vw 0px;
41 | position: relative;
42 | }
43 |
44 | .list-holder {
45 | margin: 0 auto;
46 | width: 86vw;
47 | display: flex;
48 | flex-wrap: wrap;
49 | align-items: center;
50 | }
51 |
52 |
53 |
54 |
55 |
56 | .following {
57 | background-color: #34bf49;
58 | color: white;
59 | border-radius: 10%;
60 | font-size: 14px;
61 | width: 78px;
62 | height: 28px;
63 | /* left: 100%;
64 | top: 54%; */
65 | top: 81vh;
66 | right: 25px;
67 | position: absolute;
68 |
69 | }
70 |
71 | .following2 {
72 | background-color: #34bf49;
73 | color: white;
74 | border-radius: 10%;
75 | font-size: 14px;
76 | width: 78px;
77 | height: 28px;
78 | /* left: 100%;
79 | top: 54%; */
80 | left: 93%;
81 | top: 170px;
82 | position: relative;
83 | }
84 |
85 | .following:hover span {
86 | display: none;
87 | }
88 |
89 | .following2:hover span {
90 | display: none;
91 | }
92 |
93 | .following:hover:before {
94 | content: "Unfollow"
95 | }
96 |
97 | .following2:hover:before {
98 | content: "Unfollow"
99 | }
100 |
101 |
102 |
103 | .following:hover {
104 | background-color: #ff3333;
105 | cursor: pointer;
106 | }
107 |
108 | .following2:hover {
109 | background-color: #ff3333;
110 | cursor: pointer;
111 | }
112 |
113 | .follow {
114 | background-color: #00aaff;
115 | content: "Follow";
116 | color: white;
117 | font-size: 14px;
118 | width: 78px;
119 | height: 28px;
120 | top: 81vh;
121 | right: 25px;
122 | position: absolute;
123 |
124 | }
125 | .follow2 {
126 | background-color: #00aaff;
127 | content: "Follow";
128 | color: white;
129 | font-size: 14px;
130 | width: 78px;
131 | height: 28px;
132 | left: 93%;
133 | top: 170px;
134 | position: relative;
135 | }
136 |
137 | .follow:hover {
138 | background-color: #6FB7FE;
139 | content: "Follow";
140 | cursor: pointer;
141 | }
142 |
143 | .follow2:hover {
144 | background-color: #6FB7FE;
145 | content: "Follow";
146 | cursor: pointer;
147 | }
148 |
149 | .cover-photo {
150 |
151 | /*background-size: cover;
152 | background-repeat: no-repeat;
153 | background-position: 50% 50%; */
154 | overflow: hidden;
155 | justify-content: center;
156 | align-items: center;
157 | width: 100vw;
158 | height: 80vh;
159 | display: flex;
160 | .inside-cover {
161 | /* flex-shrink: 0; */
162 | max-width: 100%;
163 | }
164 | }
165 |
166 | .cover-div {
167 | width: 100vw;
168 | height: 130px;
169 | background-color: #f0f0f5;
170 | border: 1px solid white;
171 | }
172 |
173 | .profile-pic {
174 | border-radius: 50%;
175 | height: 100px;
176 | width: 7%;
177 | border: 2px solid white;
178 | position: absolute;
179 | left: 46.2%;
180 | }
181 |
182 | .profile-image-holder {
183 | position: relative;
184 | width: 90vw;
185 | height: 100px;
186 | top: -50px;
187 | margin: auto;
188 | }
189 | /* .button-holder {
190 | width: 100vw;
191 | height: 185px;
192 | position: relative;
193 | } */
194 |
195 | .profile-title {
196 | text-align: center;
197 | font-size: 30px;
198 | color: #5f5754;
199 | font-family: Rajdhani;
200 | margin: 0px 0px 15px 0px;
201 | }
202 | .profile-description {
203 | text-align: center;
204 | font-size: 14px;
205 | color: #5f5754;
206 | margin: 0px 0px 15px 0px;
207 |
208 | }
209 | .profile-follow {
210 | text-align: center;
211 | font-size: 14px;
212 | color: #b9c1c7;
213 | margin: 20px auto;
214 | }
215 |
216 | .number {
217 | font-weight: bolder;
218 | margin-left: 10px;
219 | }
220 |
221 | .profile-footer {
222 | height: 54px;
223 | width: 100vw;
224 | bottom: 0;
225 | left: 0;
226 | position: relative;
227 | background-color: #f0f0f5;
228 | }
229 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/reset.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0px
3 | }
4 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/session_form.css:
--------------------------------------------------------------------------------
1 | .session_form {
2 | background-color: white;
3 | width: 400px;
4 | height: 500px;
5 | border: 1px lightgrey solid;
6 | position: absolute;
7 | margin: auto;
8 | top: 0;
9 | right: 0;
10 | bottom: 0;
11 | left: 0;
12 | }
13 |
14 | .internal-session-form > label {
15 | color: grey;
16 | font-family: sans-serif;
17 | font-size: 14px;
18 | margin-left: 42px;
19 | }
20 |
21 | .form-input {
22 | height: 35px;
23 | width: 316px;
24 | margin-left: 42px;
25 | margin-top: 5px;
26 | margin-bottom: 35px;
27 |
28 | }
29 |
30 | .session_header {
31 | color: grey;
32 | font-family: Rajdhani;
33 | text-align: center;
34 | }
35 |
36 | .login_login {
37 | width: 316px;
38 | background-color: #34bf49;
39 | color: white;
40 | font-size: 16px;
41 | border-width: 0px;
42 | height: 40px;
43 | margin-bottom: 20px;
44 | margin-left: 42px;
45 | border-radius: 3px;
46 |
47 | }
48 |
49 | .login_login:hover {
50 | background-color: #41cb56;
51 | cursor: pointer;
52 | }
53 |
54 | .demo_login {
55 | width: 316px;
56 | background-color: #00aaff;
57 | color: white;
58 | font-size: 16px;
59 | border-width: 0px;
60 | height: 40px;
61 | margin-bottom: 20px;
62 | margin-left: 42px;
63 | border-radius: 3px;
64 |
65 | }
66 |
67 | .demo_login:hover{
68 | background-color:#6FB7FE;
69 | cursor: pointer;
70 | }
71 |
72 | .signup_link {
73 | color: grey;
74 | font-family: sans-serif;
75 | font-size: 12px;
76 | text-align: center;
77 | }
78 |
79 | /* .session_errors {
80 | background-color: #f34f50;
81 | z-index: 10;
82 | position: absolute;
83 | margin: 0 auto;
84 | padding-left: 0px;
85 | margin: auto
86 | } */
87 |
88 | .session_errors li {
89 | color: white;
90 | list-style: none;
91 | text-align: center;
92 | z-index: 999;
93 | }
94 |
95 | .session_errors:nth-child(odd) {
96 | padding: 5px 5px 5px 5px
97 | }
98 |
99 | .session_errors:nth-child(even) {
100 | padding: 0px 0px 5px 0px
101 | }
102 |
103 |
104 | .error_holder ul {
105 | background-color: #f34f50;
106 | z-index: 999;
107 | position: relative;
108 | padding-left: 0px;
109 | margin-top: 0px;
110 | width: 310px;
111 | border-radius: 3px;
112 | margin: 0 auto
113 | }
114 |
115 | .error_holder {
116 | width: 100vw;
117 | position: absolute;
118 | z-index:1;
119 | top: 50px;
120 | z-index: 500;
121 | }
122 |
123 | .error_holder_transition {
124 |
125 |
126 | }
127 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/show_image.css:
--------------------------------------------------------------------------------
1 | .show-image{
2 | /* width: 700px;
3 | height: 600px; */
4 | width: 59vw;
5 | height: 45.1vw;
6 | }
7 |
8 | .default-cursor:hover {
9 | cursor: default;
10 | }
11 |
12 | .show-title {
13 | font-family: Rajdhani;
14 | text-align: center;
15 | top: 10px;
16 | position: relative;
17 | }
18 |
19 | .show-link {
20 | text-decoration: none;
21 | color: black;
22 | }
23 |
24 | .show-username {
25 | margin-left: 5px;
26 | }
27 | .show-div {
28 | /* width: 900px; */
29 | width: 72.9vw;
30 | /* height: 600px; */
31 | height: 45.1vw;
32 | display: flex;
33 | flex-wrap: wrap;
34 | background-color: white;
35 | }
36 |
37 | .show-div div {
38 |
39 | }
40 |
41 | .image-placeholder{
42 | width: 35px;
43 | height: 35px;
44 | border-radius: 50%;
45 | background-color: grey;
46 | float: left;
47 | margin-left: 10px
48 |
49 | }
50 |
51 | .show-child-1 {
52 |
53 | }
54 |
55 | .show-child-2 {
56 | position: relative;
57 | /* width: 200px; */
58 | width: 13.9vw;
59 | /* height: 600px; */
60 | height: 45.1vw;
61 | /* text-align: center; */
62 | }
63 | .button-holder{
64 | align-items: center;
65 | position: absolute;
66 | bottom: 0px;
67 | left: 0px;
68 | }
69 | .button1 {
70 | background-color: #1a1aff;
71 | color: white;
72 | height: 40px;
73 | /* width: 100px; */
74 | width: 6.95vw;
75 | font-size: 16px;
76 | border: none;
77 | }
78 |
79 | .button1:hover {
80 | /* background-color:#4d4dff; */
81 | background-color: white;
82 | color: #1a1aff;
83 | cursor: pointer;
84 | border: 2px solid #1a1aff;
85 | }
86 | .button2{
87 | background-color: #e60000;
88 | color: white;
89 | height: 40px;
90 | /* width: 100px; */
91 | width: 6.95vw;
92 | font-size: 16px;
93 | border: none;
94 | }
95 |
96 | .button2:hover {
97 | /* background-color: #ff4d4d; */
98 | background-color: white;
99 | color: #e60000;
100 | cursor: pointer;
101 | border: 2px solid #e60000;
102 | }
103 |
104 | .show-following {
105 | margin: 5px 0px 0px 50px;
106 | position: absolute;
107 | background-color: #34bf49;
108 | color: white;
109 | border-radius: 10%;
110 | font-size: 11px;
111 | width: 69px;
112 | height: 22px;
113 | display: block;
114 | }
115 |
116 | .show-following:hover span {
117 | display: none;
118 | }
119 |
120 | .show-following:hover:before {
121 | content: "Unfollow"
122 | }
123 |
124 |
125 |
126 | .show-following:hover {
127 | background-color: #ff3333;
128 | cursor: pointer;
129 | }
130 |
131 | .show-follow {
132 | margin: 5px 0px 0px 50px;
133 | position: absolute;
134 | background-color: #00aaff;
135 | content: "Follow";
136 | color: white;
137 | font-size: 11px;
138 | width: 69px;
139 | height: 22px;
140 | display: block;
141 |
142 | }
143 |
144 | .show-follow:hover {
145 | background-color: #6FB7FE;
146 | content: "Follow";
147 | cursor: pointer;
148 | }
149 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/user_photo_form.css:
--------------------------------------------------------------------------------
1 | .profile-title {
2 | text-align: center;
3 | color: grey;
4 | font-family: sans-serif;
5 | font-size: 14px;
6 | margin-bottom: 20px;
7 | }
8 |
9 |
10 | .user-image {
11 | width:300px;
12 | height: 300px;
13 | margin: 0px 50px 30px 50px
14 | }
15 |
--------------------------------------------------------------------------------
/app/channels/application_cable/channel.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Channel < ActionCable::Channel::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/channels/application_cable/connection.rb:
--------------------------------------------------------------------------------
1 | module ApplicationCable
2 | class Connection < ActionCable::Connection::Base
3 | end
4 | end
5 |
--------------------------------------------------------------------------------
/app/controllers/api/feeds_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::FeedsController < ApplicationController
2 |
3 | def home
4 | @pictures = current_user.get_home_feed
5 | render :home
6 | end
7 |
8 | def fresh
9 | @pictures = current_user.get_fresh_feed
10 | render :fresh
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/controllers/api/follows_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::FollowsController < ApplicationController
2 | def create
3 | @follow = Follow.new(follow_params)
4 | if @follow.save
5 | render :show
6 | else
7 | render json: @follow.errors.full_messages, status: 422
8 | end
9 | end
10 |
11 | def index
12 | @follows = Follow.all
13 | render :index
14 | end
15 |
16 | def destroy
17 | @follow = Follow.find(params[:id])
18 | @follow.destroy
19 | render :show
20 | end
21 |
22 | private
23 |
24 | def follow_params
25 | params.require(:follow).permit(:leader_id,:follower_id)
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/app/controllers/api/notifications_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::NotificationsController < ApplicationController
2 | def create
3 | @notification = Notification.new(notification_params)
4 | @notification.viewed = false
5 | if @notification.save!
6 | render :show
7 | else
8 | render json: @notification.errors.full_messages, status: 422
9 | end
10 | end
11 |
12 | def update
13 | @notification = Notification.find_by(id: params[:id].to_i)
14 | if @notification.update(notification_params)
15 | render :show
16 | else
17 | render json: @notification.errors.full_messages, status: 422
18 | end
19 | end
20 |
21 | def index
22 | @notifications = current_user.get_notifications
23 | render :index
24 | end
25 |
26 | def destroy
27 | @notification = Notification.find(params[:id])
28 | @notification.destroy
29 | render :show
30 | end
31 |
32 | private
33 |
34 | def notification_params
35 | params.require(:notification).permit(:initiator_id,:user_id, :viewed)
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/app/controllers/api/pictures_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::PicturesController < ApplicationController
2 |
3 | def create
4 | @picture = Picture.new(picture_params)
5 | @picture.uploader_id = params[:user_id]
6 | if @picture.save
7 | render :show
8 | else
9 | render json: @picture.errors.full_messages, status: 422
10 | end
11 | end
12 |
13 | def update
14 | @picture = Picture.find_by(id: params[:id].to_i)
15 | if @picture.update(picture_params)
16 | render :show
17 | else
18 | render json: @picture.errors.full_messages, status: 422
19 | end
20 | end
21 |
22 | def destroy
23 | @picture = Picture.find_by(id: params[:id])
24 | @picture.destroy
25 | render :show
26 | end
27 |
28 | def index
29 | # the params thing may be wrong. going to have to check this out when I test
30 | puts params
31 | user = User.find_by(id: params[:user_id])
32 | @pictures = user.pictures
33 | end
34 |
35 | def show
36 | @picture = Picture.find_by(id: params[:id])
37 | render :show
38 | end
39 |
40 | private
41 |
42 | def picture_params
43 | params.require(:picture).permit(:title, :image_url)
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/app/controllers/api/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SessionsController < ApplicationController
2 |
3 | def create
4 | @user = User.find_by_credentials(params[:user][:username], params[:user][:password])
5 | if @user
6 | login(@user)
7 | if @user.username == 'Felix'
8 | Notification.create(user_id: @user.id, initiator_id: User.all[3].id, viewed: false)
9 | Follow.create(leader_id: @user.id,follower_id: User.all[3].id)
10 | end
11 | render 'api/users/show'
12 | else
13 | render json: ["Must have valid username and password"], status: 401
14 | end
15 | end
16 |
17 | def destroy
18 | @user = current_user
19 | if @user
20 | if @user.username == 'Felix'
21 | notify = Notification.find_by(initiator_id: User.all[3].id)
22 | notify.destroy
23 | follow = Follow.find_by(follower_id: User.all[3].id)
24 | follow.destroy
25 | end
26 | logout
27 | render 'api/users/show'
28 | else
29 | render json: ["Must be logged in to log out"], status: 404
30 | end
31 | end
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/app/controllers/api/users_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::UsersController < ApplicationController
2 |
3 | def create
4 |
5 | @user = User.new(user_params)
6 | if @user.save
7 | login(@user)
8 | render :show
9 | else
10 | render json: @user.errors.full_messages, status: 422
11 | end
12 | end
13 |
14 | def index
15 | @users = User.all
16 | render :index
17 | end
18 |
19 | def show
20 | @user = User.find_by(id: params['id'])
21 | render :show
22 | end
23 |
24 |
25 | def update
26 | @user = User.find(params['id'])
27 | if @user.update(user_params)
28 | login(@user)
29 | render :show
30 | else
31 | render json: @user.errors.full_messages, status: 422
32 | end
33 | end
34 |
35 | private
36 |
37 | def user_params
38 | params.require(:user).permit(:username,:password,:description,:cover_url,:profile_url,:new_notification)
39 | end
40 |
41 | end
42 |
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | protect_from_forgery with: :exception
3 |
4 | helper_method :logged_in?, :current_user
5 |
6 | def current_user
7 | @current_user ||= User.find_by(session_token: session[:session_token])
8 | end
9 |
10 | def login(user)
11 | @current_user = user
12 | session[:session_token] = user.reset_session_token!
13 | end
14 |
15 | def logout
16 | current_user.reset_session_token!
17 | session[:session_token] = nil
18 | end
19 |
20 | def logged_in?
21 | !!current_user
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/controllers/static_pages_controller.rb:
--------------------------------------------------------------------------------
1 | class StaticPagesController < ApplicationController
2 |
3 | def root
4 | render :root
5 | end
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/app/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/mailers/application_mailer.rb:
--------------------------------------------------------------------------------
1 | class ApplicationMailer < ActionMailer::Base
2 | default from: 'from@example.com'
3 | layout 'mailer'
4 | end
5 |
--------------------------------------------------------------------------------
/app/models/application_record.rb:
--------------------------------------------------------------------------------
1 | class ApplicationRecord < ActiveRecord::Base
2 | self.abstract_class = true
3 | end
4 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/follow.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: follows
4 | #
5 | # id :bigint(8) not null, primary key
6 | # leader_id :integer not null
7 | # follower_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class Follow < ApplicationRecord
13 |
14 | validates :leader_id, :follower_id, presence: true
15 | validates_uniqueness_of :leader_id, :scope => [:follower_id]
16 | validates_uniqueness_of :follower_id, :scope => [:leader_id]
17 |
18 | belongs_to :leader,
19 | primary_key: :id,
20 | foreign_key: :leader_id,
21 | class_name: 'User'
22 |
23 | belongs_to :follower,
24 | primary_key: :id,
25 | foreign_key: :follower_id,
26 | class_name: 'User'
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/app/models/like.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: likes
4 | #
5 | # id :bigint(8) not null, primary key
6 | # liker_id :integer not null
7 | # picture_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class Like < ApplicationRecord
13 |
14 | validates :liker_id, :picture_id, presence: true
15 | validates_uniqueness_of :liker_id, :scope => [:picture_id]
16 | validates_uniqueness_of :picture, :scope => [:liker_id]
17 |
18 |
19 |
20 | # belongs_to :picture,
21 | # primary_key: :id,
22 | # foreign_key: :picture_id,
23 | # class_name: 'Picture'
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/app/models/notification.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notifications
4 | #
5 | # id :bigint(8) not null, primary key
6 | # user_id :integer not null
7 | # initiator_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # viewed :boolean
11 | #
12 |
13 | class Notification < ApplicationRecord
14 |
15 | validates :initiator_id, :user_id, presence: true
16 |
17 | belongs_to :user,
18 | primary_key: :id,
19 | foreign_key: :user_id,
20 | class_name: 'User'
21 |
22 | belongs_to :initiator,
23 | primary_key: :id,
24 | foreign_key: :initiator_id,
25 | class_name: 'User'
26 |
27 | end
28 |
--------------------------------------------------------------------------------
/app/models/picture.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: pictures
4 | #
5 | # id :bigint(8) not null, primary key
6 | # title :string
7 | # image_url :string not null
8 | # uploader_id :integer not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 |
13 | class Picture < ApplicationRecord
14 |
15 | validates :image_url, :uploader_id, presence: true
16 |
17 | belongs_to :user,
18 | primary_key: :id,
19 | foreign_key: :uploader_id,
20 | class_name: 'User'
21 | end
22 |
--------------------------------------------------------------------------------
/app/models/user.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :bigint(8) not null, primary key
6 | # username :string not null
7 | # password_digest :string not null
8 | # session_token :string not null
9 | # description :string
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | # profile_url :string
13 | # cover_url :string
14 | #
15 |
16 | class User < ApplicationRecord
17 | attr_reader :password
18 | validates :username, :password_digest, :session_token, presence: true
19 | validates :username, uniqueness: true
20 | validates :password, length: {minimum: 6}, allow_nil: true
21 | before_validation :ensure_session_token
22 |
23 | has_many :pictures,
24 | primary_key: :id,
25 | foreign_key: :uploader_id,
26 | class_name: 'Picture'
27 |
28 | has_many :followers,
29 | primary_key: :id,
30 | foreign_key: :leader_id,
31 | class_name: 'Follow'
32 |
33 | has_many :followings,
34 | primary_key: :id,
35 | foreign_key: :follower_id,
36 | class_name: 'Follow'
37 |
38 | def get_notifications
39 | Notification.all.select{|notification| self.id == notification.user_id}
40 | end
41 |
42 | def get_fresh_feed
43 | followings = self.followings
44 | leader_ids = []
45 | followings.each do |follow|
46 | leader_ids.push(follow.leader_id)
47 | end
48 | pictures = Picture.all.sort{|a,b| b.created_at <=> a.created_at}
49 | pictures.reject! {|picture| leader_ids.include?(picture.uploader_id) || picture.uploader_id == self.id}
50 | pictures.take(45)
51 | end
52 |
53 | def get_home_feed
54 | followings = self.followings
55 | pictures = []
56 | followings.each do |follow|
57 | user = User.find(follow.leader_id)
58 | pictures += user.pictures
59 | end
60 | if pictures.length < 30
61 | other_pictures = Picture.all.sort {|a,b| b.created_at <=> a.created_at}
62 | count = 0
63 | other_pictures.each do |picture|
64 | unless count == 10 || picture.uploader_id === self.id || pictures.include?(picture)
65 | pictures.push(picture)
66 | count += 1
67 | end
68 |
69 | end
70 | end
71 | pictures.sort {|a,b| b.created_at <=> a.created_at}.take(30)
72 | end
73 |
74 | def self.find_by_credentials(username,password)
75 | user = User.find_by(username: username)
76 | return nil unless user
77 | user.is_password?(password) ? user : nil
78 | end
79 |
80 | def password=(password)
81 | @password = password
82 | self.password_digest = BCrypt::Password.create(password)
83 | end
84 |
85 | def is_password?(password)
86 | BCrypt::Password.new(self.password_digest).is_password?(password)
87 | end
88 |
89 | def ensure_session_token
90 | self.session_token ||= SecureRandom.urlsafe_base64
91 | end
92 |
93 | def reset_session_token!
94 | self.session_token = SecureRandom.urlsafe_base64
95 | self.save!
96 | self.session_token
97 | end
98 | end
99 |
--------------------------------------------------------------------------------
/app/views/api/feeds/fresh.json.jbuilder:
--------------------------------------------------------------------------------
1 | @pictures.each do |picture|
2 | json.set! picture.id do
3 | json.extract! picture, :id, :title, :image_url, :uploader_id
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/feeds/home.json.jbuilder:
--------------------------------------------------------------------------------
1 | @pictures.each do |picture|
2 | json.set! picture.id do
3 | json.extract! picture, :id, :title, :image_url, :uploader_id
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/follows/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @follows.each do |follow|
2 | json.set! follow.id do
3 | json.extract! follow, :id, :leader_id, :follower_id
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/follows/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! @follow, :id, :leader_id, :follower_id
2 |
--------------------------------------------------------------------------------
/app/views/api/notifications/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @notifications.each do |notification|
2 | json.set! notification.id do
3 | json.extract! notification, :id, :user_id, :initiator_id, :viewed
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/notifications/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! @notification, :id, :user_id, :initiator_id, :viewed
2 |
--------------------------------------------------------------------------------
/app/views/api/pictures/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @pictures.each do |picture|
2 | json.set! picture.id do
3 | json.extract! picture, :id, :title, :image_url, :uploader_id
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/pictures/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! @picture, :id, :title, :image_url, :uploader_id
2 |
--------------------------------------------------------------------------------
/app/views/api/users/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! user, :id, :username, :description, :cover_url, :profile_url
2 |
--------------------------------------------------------------------------------
/app/views/api/users/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @users.each do |user|
2 | json.set! user.id do
3 | json.extract! user,:id, :username, :cover_url, :profile_url, :description
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/views/api/users/show.json.jbuilder:
--------------------------------------------------------------------------------
1 |
2 | json.partial! "api/users/user", user: @user
3 |
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | ThreeSixtyPx
5 | <%= csrf_meta_tags %>
6 |
7 | <%= stylesheet_link_tag 'application', media: 'all' %>
8 | <%= javascript_include_tag 'application' %>
9 |
10 |
11 |
12 |
13 |
14 |
15 |
24 |
25 |
26 |
27 | <%= yield %>
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
11 | <%= yield %>
12 |
13 |
14 |
--------------------------------------------------------------------------------
/app/views/layouts/mailer.text.erb:
--------------------------------------------------------------------------------
1 | <%= yield %>
2 |
--------------------------------------------------------------------------------
/app/views/static_pages/root.html.erb:
--------------------------------------------------------------------------------
1 | <% if logged_in? %>
2 |
8 | <% end %>
9 |
10 |
11 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3 | load Gem.bin_path('bundler', 'bundle')
4 |
--------------------------------------------------------------------------------
/bin/rails:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | begin
3 | load File.expand_path('../spring', __FILE__)
4 | rescue LoadError => e
5 | raise unless e.message.include?('spring')
6 | end
7 | APP_PATH = File.expand_path('../config/application', __dir__)
8 | require_relative '../config/boot'
9 | require 'rails/commands'
10 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/bin/setup:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a starting point to setup your application.
15 | # Add necessary setup steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | # Install JavaScript dependencies if using Yarn
22 | # system('bin/yarn')
23 |
24 |
25 | # puts "\n== Copying sample files =="
26 | # unless File.exist?('config/database.yml')
27 | # cp 'config/database.yml.sample', 'config/database.yml'
28 | # end
29 |
30 | puts "\n== Preparing database =="
31 | system! 'bin/rails db:setup'
32 |
33 | puts "\n== Removing old logs and tempfiles =="
34 | system! 'bin/rails log:clear tmp:clear'
35 |
36 | puts "\n== Restarting application server =="
37 | system! 'bin/rails restart'
38 | end
39 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/bin/update:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | require 'pathname'
3 | require 'fileutils'
4 | include FileUtils
5 |
6 | # path to your application root.
7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__)
8 |
9 | def system!(*args)
10 | system(*args) || abort("\n== Command #{args} failed ==")
11 | end
12 |
13 | chdir APP_ROOT do
14 | # This script is a way to update your development environment automatically.
15 | # Add necessary update steps to this file.
16 |
17 | puts '== Installing dependencies =='
18 | system! 'gem install bundler --conservative'
19 | system('bundle check') || system!('bundle install')
20 |
21 | puts "\n== Updating database =="
22 | system! 'bin/rails db:migrate'
23 |
24 | puts "\n== Removing old logs and tempfiles =="
25 | system! 'bin/rails log:clear tmp:clear'
26 |
27 | puts "\n== Restarting application server =="
28 | system! 'bin/rails restart'
29 | end
30 |
--------------------------------------------------------------------------------
/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | VENDOR_PATH = File.expand_path('..', __dir__)
3 | Dir.chdir(VENDOR_PATH) do
4 | begin
5 | exec "yarnpkg #{ARGV.join(" ")}"
6 | rescue Errno::ENOENT
7 | $stderr.puts "Yarn executable was not detected in the system."
8 | $stderr.puts "Download Yarn at https://yarnpkg.com/en/docs/install"
9 | exit 1
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/config.ru:
--------------------------------------------------------------------------------
1 | # This file is used by Rack-based servers to start the application.
2 |
3 | require_relative 'config/environment'
4 |
5 | run Rails.application
6 |
--------------------------------------------------------------------------------
/config/application.rb:
--------------------------------------------------------------------------------
1 | require_relative 'boot'
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 |
10 | module ThreeSixtyPx
11 | class Application < Rails::Application
12 | # Initialize configuration defaults for originally generated Rails version.
13 | config.load_defaults 5.1
14 |
15 | # Settings in config/environments/* take precedence over those specified here.
16 | # Application configuration should go into files in config/initializers
17 | # -- all .rb files in that directory are automatically loaded.
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/config/boot.rb:
--------------------------------------------------------------------------------
1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
2 |
3 | require 'bundler/setup' # Set up gems listed in the Gemfile.
4 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: redis://localhost:6379/1
10 | channel_prefix: ThreeSixtyPx_production
11 |
--------------------------------------------------------------------------------
/config/database.yml:
--------------------------------------------------------------------------------
1 | # PostgreSQL. Versions 9.1 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: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
23 |
24 | development:
25 | <<: *default
26 | database: ThreeSixtyPx_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: ThreeSixtyPx
33 |
34 | # The password associated with the postgres role (username).
35 | #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: ThreeSixtyPx_test
61 |
62 |
63 | # As with config/secrets.yml, you never want to store sensitive information,
64 | # like your database password, in your source code. If your source code is
65 | # ever seen by anyone, they now have access to your database.
66 | #
67 | # Instead, provide the password as a unix environment variable when you boot
68 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
69 | # for a full rundown on how to provide these environment variables in a
70 | # production deployment.
71 | #
72 | # On Heroku and other platform providers, you may have a full connection URL
73 | # available as an environment variable. For example:
74 | #
75 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
76 | #
77 | # You can use this database configuration with:
78 | #
79 | # production:
80 | # url: <%= ENV['DATABASE_URL'] %>
81 | #
82 | production:
83 | <<: *default
84 | database: ThreeSixtyPx_production
85 | username: ThreeSixtyPx
86 | password: <%= ENV['THREESIXTYPX_DATABASE_PASSWORD'] %>
87 |
--------------------------------------------------------------------------------
/config/environment.rb:
--------------------------------------------------------------------------------
1 | # Load the Rails application.
2 | require_relative 'application'
3 |
4 | # Initialize the Rails application.
5 | Rails.application.initialize!
6 |
--------------------------------------------------------------------------------
/config/environments/development.rb:
--------------------------------------------------------------------------------
1 | Rails.application.configure do
2 | # Settings specified here will take precedence over those in config/application.rb.
3 |
4 | # In the development environment your application's code is reloaded on
5 | # every request. This slows down response time but is perfect for development
6 | # since you don't have to restart the web server when you make code changes.
7 | config.cache_classes = false
8 |
9 | # Do not eager load code on boot.
10 | config.eager_load = false
11 |
12 | # Show full error reports.
13 | config.consider_all_requests_local = true
14 |
15 | # Enable/disable caching. By default caching is disabled.
16 | if Rails.root.join('tmp/caching-dev.txt').exist?
17 | config.action_controller.perform_caching = true
18 |
19 | config.cache_store = :memory_store
20 | config.public_file_server.headers = {
21 | 'Cache-Control' => "public, max-age=#{2.days.seconds.to_i}"
22 | }
23 | else
24 | config.action_controller.perform_caching = false
25 |
26 | config.cache_store = :null_store
27 | end
28 |
29 | # Don't care if the mailer can't send.
30 | config.action_mailer.raise_delivery_errors = false
31 |
32 | config.action_mailer.perform_caching = false
33 |
34 | # Print deprecation notices to the Rails logger.
35 | config.active_support.deprecation = :log
36 |
37 | # Raise an error on page load if there are pending migrations.
38 | config.active_record.migration_error = :page_load
39 |
40 | # Debug mode disables concatenation and preprocessing of assets.
41 | # This option may cause significant delays in view rendering with a large
42 | # number of complex assets.
43 | config.assets.debug = true
44 |
45 | # Suppress logger output for asset requests.
46 | config.assets.quiet = true
47 |
48 | # Raises error for missing translations
49 | # config.action_view.raise_on_missing_translations = true
50 |
51 | # Use an evented file watcher to asynchronously detect changes in source code,
52 | # routes, locales, etc. This feature depends on the listen gem.
53 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
54 | end
55 |
--------------------------------------------------------------------------------
/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 | # Attempt to read encrypted secrets from `config/secrets.yml.enc`.
18 | # Requires an encryption key in `ENV["RAILS_MASTER_KEY"]` or
19 | # `config/secrets.yml.key`.
20 | config.read_encrypted_secrets = true
21 |
22 | # Disable serving static files from the `/public` folder by default since
23 | # Apache or NGINX already handles this.
24 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present?
25 |
26 | # Compress JavaScripts and CSS.
27 | config.assets.js_compressor = :uglifier
28 | # config.assets.css_compressor = :sass
29 |
30 | # Do not fallback to assets pipeline if a precompiled asset is missed.
31 | config.assets.compile = false
32 |
33 | # `config.assets.precompile` and `config.assets.version` have moved to config/initializers/assets.rb
34 |
35 | # Enable serving of images, stylesheets, and JavaScripts from an asset server.
36 | # config.action_controller.asset_host = 'http://assets.example.com'
37 |
38 | # Specifies the header that your server uses for sending files.
39 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache
40 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX
41 |
42 | # Mount Action Cable outside main process or domain
43 | # config.action_cable.mount_path = nil
44 | # config.action_cable.url = 'wss://example.com/cable'
45 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ]
46 |
47 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies.
48 | # config.force_ssl = true
49 |
50 | # Use the lowest log level to ensure availability of diagnostic information
51 | # when problems arise.
52 | config.log_level = :debug
53 |
54 | # Prepend all log lines with the following tags.
55 | config.log_tags = [ :request_id ]
56 |
57 | # Use a different cache store in production.
58 | # config.cache_store = :mem_cache_store
59 |
60 | # Use a real queuing backend for Active Job (and separate queues per environment)
61 | # config.active_job.queue_adapter = :resque
62 | # config.active_job.queue_name_prefix = "ThreeSixtyPx_#{Rails.env}"
63 | config.action_mailer.perform_caching = false
64 |
65 | # Ignore bad email addresses and do not raise email delivery errors.
66 | # Set this to true and configure the email server for immediate delivery to raise delivery errors.
67 | # config.action_mailer.raise_delivery_errors = false
68 |
69 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to
70 | # the I18n.default_locale when a translation cannot be found).
71 | config.i18n.fallbacks = true
72 |
73 | # Send deprecation notices to registered listeners.
74 | config.active_support.deprecation = :notify
75 |
76 | # Use default logging formatter so that PID and timestamp are not suppressed.
77 | config.log_formatter = ::Logger::Formatter.new
78 |
79 | # Use a different logger for distributed setups.
80 | # require 'syslog/logger'
81 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name')
82 |
83 | if ENV["RAILS_LOG_TO_STDOUT"].present?
84 | logger = ActiveSupport::Logger.new(STDOUT)
85 | logger.formatter = config.log_formatter
86 | config.logger = ActiveSupport::TaggedLogging.new(logger)
87 | end
88 |
89 | # Do not dump schema after migrations.
90 | config.active_record.dump_schema_after_migration = false
91 | end
92 |
--------------------------------------------------------------------------------
/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 public file server for tests with Cache-Control for performance.
16 | config.public_file_server.enabled = true
17 | config.public_file_server.headers = {
18 | 'Cache-Control' => "public, max-age=#{1.hour.seconds.to_i}"
19 | }
20 |
21 | # Show full error reports and disable caching.
22 | config.consider_all_requests_local = true
23 | config.action_controller.perform_caching = false
24 |
25 | # Raise exceptions instead of rendering exception templates.
26 | config.action_dispatch.show_exceptions = false
27 |
28 | # Disable request forgery protection in test environment.
29 | config.action_controller.allow_forgery_protection = false
30 | config.action_mailer.perform_caching = false
31 |
32 | # Tell Action Mailer not to deliver emails to the real world.
33 | # The :test delivery method accumulates sent emails in the
34 | # ActionMailer::Base.deliveries array.
35 | config.action_mailer.delivery_method = :test
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 |
--------------------------------------------------------------------------------
/config/initializers/application_controller_renderer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # ActiveSupport::Reloader.to_prepare do
4 | # ApplicationController.renderer.defaults.merge!(
5 | # http_host: 'example.org',
6 | # https: false
7 | # )
8 | # end
9 |
--------------------------------------------------------------------------------
/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 | # Add Yarn node_modules folder to the asset load path.
9 | Rails.application.config.assets.paths << Rails.root.join('node_modules')
10 |
11 | # Precompile additional assets.
12 | # application.js, application.css, and all non-JS/CSS in the app/assets
13 | # folder are already added.
14 | # Rails.application.config.assets.precompile += %w( admin.js admin.css )
15 |
--------------------------------------------------------------------------------
/config/initializers/backtrace_silencers.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces.
4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ }
5 |
6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code.
7 | # Rails.backtrace_cleaner.remove_silencers!
8 |
--------------------------------------------------------------------------------
/config/initializers/cookies_serializer.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Specify a serializer for the signed and encrypted cookie jars.
4 | # Valid options are :json, :marshal, and :hybrid.
5 | Rails.application.config.action_dispatch.cookies_serializer = :json
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 |
--------------------------------------------------------------------------------
/config/initializers/inflections.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new inflection rules using the following format. Inflections
4 | # are locale specific, and you may define rules for as many different
5 | # locales as you wish. All of these examples are active by default:
6 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
7 | # inflect.plural /^(ox)$/i, '\1en'
8 | # inflect.singular /^(ox)en/i, '\1'
9 | # inflect.irregular 'person', 'people'
10 | # inflect.uncountable %w( fish sheep )
11 | # end
12 |
13 | # These inflection rules are supported but not enabled by default:
14 | # ActiveSupport::Inflector.inflections(:en) do |inflect|
15 | # inflect.acronym 'RESTful'
16 | # end
17 |
--------------------------------------------------------------------------------
/config/initializers/mime_types.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Add new mime types for use in respond_to blocks:
4 | # Mime::Type.register "text/richtext", :rtf
5 |
--------------------------------------------------------------------------------
/config/initializers/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]
9 | end
10 |
11 | # To enable root element in JSON for ActiveRecord objects.
12 | # ActiveSupport.on_load(:active_record) do
13 | # self.include_root_in_json = true
14 | # end
15 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | # Files in the config/locales directory are used for internationalization
2 | # and are automatically loaded by Rails. If you want to use locales other
3 | # than English, add the necessary files in this directory.
4 | #
5 | # To use the locales, use `I18n.t`:
6 | #
7 | # I18n.t 'hello'
8 | #
9 | # In views, this is aliased to just `t`:
10 | #
11 | # <%= t('hello') %>
12 | #
13 | # To use a different locale, set it with `I18n.locale`:
14 | #
15 | # I18n.locale = :es
16 | #
17 | # This would use the information in config/locales/es.yml.
18 | #
19 | # The following keys must be escaped otherwise they will not be retrieved by
20 | # the default I18n backend:
21 | #
22 | # true, false, on, off, yes, no
23 | #
24 | # Instead, surround them with single quotes.
25 | #
26 | # en:
27 | # 'true': 'foo'
28 | #
29 | # To learn more, please read the Rails Internationalization guide
30 | # available at http://guides.rubyonrails.org/i18n.html.
31 |
32 | en:
33 | hello: "Hello world"
34 |
--------------------------------------------------------------------------------
/config/puma.rb:
--------------------------------------------------------------------------------
1 | # Puma can serve each request in a thread from an internal thread pool.
2 | # The `threads` method setting takes two numbers: a minimum and maximum.
3 | # Any libraries that use thread pools should be configured to match
4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum
5 | # and maximum; this matches the default thread size of Active Record.
6 | #
7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
8 | threads threads_count, threads_count
9 |
10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000.
11 | #
12 | port ENV.fetch("PORT") { 3000 }
13 |
14 | # Specifies the `environment` that Puma will run in.
15 | #
16 | environment ENV.fetch("RAILS_ENV") { "development" }
17 |
18 | # Specifies the number of `workers` to boot in clustered mode.
19 | # Workers are forked webserver processes. If using threads and workers together
20 | # the concurrency of the application would be max `threads` * `workers`.
21 | # Workers do not work on JRuby or Windows (both of which do not support
22 | # processes).
23 | #
24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 }
25 |
26 | # Use the `preload_app!` method when specifying a `workers` number.
27 | # This directive tells Puma to first boot the application and load code
28 | # before forking the application. This takes advantage of Copy On Write
29 | # process behavior so workers use less memory. If you use this option
30 | # you need to make sure to reconnect any threads in the `on_worker_boot`
31 | # block.
32 | #
33 | # preload_app!
34 |
35 | # If you are preloading your application and using Active Record, it's
36 | # recommended that you close any connections to the database before workers
37 | # are forked to prevent connection leakage.
38 | #
39 | # before_fork do
40 | # ActiveRecord::Base.connection_pool.disconnect! if defined?(ActiveRecord)
41 | # end
42 |
43 | # The code in the `on_worker_boot` will be called if you are using
44 | # clustered mode by specifying a number of `workers`. After each worker
45 | # process is booted, this block will be run. If you are using the `preload_app!`
46 | # option, you will want to use this block to reconnect to any threads
47 | # or connections that may have been created at application boot, as Ruby
48 | # cannot share connections between processes.
49 | #
50 | # on_worker_boot do
51 | # ActiveRecord::Base.establish_connection if defined?(ActiveRecord)
52 | # end
53 | #
54 |
55 | # Allow puma to be restarted by `rails restart` command.
56 | plugin :tmp_restart
57 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Rails.application.routes.draw do
2 | # For details on the DSL available within this file, see http://guides.rubyonrails.org/routing.html
3 |
4 | namespace :api, defaults: {format: :json} do
5 | get '/feed', to: 'feeds#home', controller: 'feeds'
6 | get '/fresh', to: 'feeds#fresh', controller: 'feeds'
7 | resources :follows, only: [:index, :create, :destroy]
8 | resources :notifications, only: [:index, :create, :destroy, :update]
9 | resources :users, only: [:create,:update, :show, :index] do
10 | resources :pictures, only: [ :index, :create]
11 | end
12 | resources :pictures, only: [:show, :update, :destroy]
13 | resource :session, only: [:create, :destroy]
14 | end
15 |
16 | root 'static_pages#root'
17 | end
18 |
--------------------------------------------------------------------------------
/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 `rails 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 | # Shared secrets are available across all environments.
14 |
15 | # shared:
16 | # api_key: a1B2c3D4e5F6
17 |
18 | # Environmental secrets are only available for that specific environment.
19 |
20 | development:
21 | secret_key_base: 8ed3602384faacfdf1eb9efaf65643899d9e2d26eda005d4d2b928a0e90d00c4afc8dc0ff2cf61b1b88c7a34a644e30cb0f9650e1e32999f0c271ec52eec5fe0
22 |
23 | test:
24 | secret_key_base: da72116f264d0c04b790341b7d17e8c721b85375dd09c75edb443596e8cda1459d2fdbb0cad7f7e3dfd8dc17e3d0620505c8c0a9a54e5f416c7d6ca85d3bd055
25 |
26 | # Do not keep production secrets in the unencrypted secrets file.
27 | # Instead, either read values from the environment.
28 | # Or, use `bin/rails secrets:setup` to configure encrypted secrets
29 | # and move the `production:` environment over there.
30 |
31 | production:
32 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
33 |
--------------------------------------------------------------------------------
/config/spring.rb:
--------------------------------------------------------------------------------
1 | %w(
2 | .ruby-version
3 | .rbenv-vars
4 | tmp/restart.txt
5 | tmp/caching-dev.txt
6 | ).each { |path| Spring.watch(path) }
7 |
--------------------------------------------------------------------------------
/db/migrate/20180605195708_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration[5.1]
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.string :profile_url, null:false
8 | t.string :cover_url, null:false
9 | t.string :description
10 |
11 | t.timestamps
12 | end
13 | add_index :users, :username, unique: true
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/migrate/20180605211823_change_users.rb:
--------------------------------------------------------------------------------
1 | class ChangeUsers < ActiveRecord::Migration[5.1]
2 | def change
3 | remove_column :users, :profile_url
4 | remove_column :users, :cover_url
5 | add_column :users, :profile_url, :string
6 | add_column :users, :cover_url, :string
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20180607212713_create_pictures.rb:
--------------------------------------------------------------------------------
1 | class CreatePictures < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :pictures do |t|
4 | t.string :title
5 | t.string :image_url, null: false
6 | t.integer :uploader_id, null: false
7 | t.timestamps
8 | end
9 | add_index :pictures, :uploader_id
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/db/migrate/20180610022153_create_follows.rb:
--------------------------------------------------------------------------------
1 | class CreateFollows < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :follows do |t|
4 | t.integer :leader_id, null: false
5 | t.integer :follower_id, null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :follows, :leader_id
10 | add_index :follows, :follower_id
11 | add_index :follows, [:leader_id, :follower_id], :unique => true
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20180613202647_create_notifications.rb:
--------------------------------------------------------------------------------
1 | class CreateNotifications < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :notifications do |t|
4 | t.integer :user_id, null: false
5 | t.integer :initiator_id, null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :notifications, :user_id
10 | add_index :notifications, :initiator_id
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20180613203140_update_users.rb:
--------------------------------------------------------------------------------
1 | class UpdateUsers < ActiveRecord::Migration[5.1]
2 | def change
3 | add_column :users, :new_notification, :boolean
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20180614170848_update_notification.rb:
--------------------------------------------------------------------------------
1 | class UpdateNotification < ActiveRecord::Migration[5.1]
2 | def change
3 | add_column :notifications, :viewed, :boolean
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20180614172931_update_users2.rb:
--------------------------------------------------------------------------------
1 | class UpdateUsers2 < ActiveRecord::Migration[5.1]
2 | def change
3 | remove_column :users, :new_notification
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20180729043814_create_likes.rb:
--------------------------------------------------------------------------------
1 | class CreateLikes < ActiveRecord::Migration[5.1]
2 | def change
3 | create_table :likes do |t|
4 | t.integer :liker_id, null: false
5 | t.integer :picture_id, null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :likes, :liker_id
10 | add_index :likes, :picture_id
11 | add_index :likes, [:liker_id, :picture_id], :unique => true
12 |
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/schema.rb:
--------------------------------------------------------------------------------
1 | # This file is auto-generated from the current state of the database. Instead
2 | # of editing this file, please use the migrations feature of Active Record to
3 | # incrementally modify your database, and then regenerate this schema definition.
4 | #
5 | # Note that this schema.rb definition is the authoritative source for your
6 | # database schema. If you need to create the application database on another
7 | # system, you should be using db:schema:load, not running all the migrations
8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations
9 | # you'll amass, the slower it'll run and the greater likelihood for issues).
10 | #
11 | # It's strongly recommended that you check this file into your version control system.
12 |
13 | ActiveRecord::Schema.define(version: 20180729043814) do
14 |
15 | # These are extensions that must be enabled in order to support this database
16 | enable_extension "plpgsql"
17 |
18 | create_table "follows", force: :cascade do |t|
19 | t.integer "leader_id", null: false
20 | t.integer "follower_id", null: false
21 | t.datetime "created_at", null: false
22 | t.datetime "updated_at", null: false
23 | t.index ["follower_id"], name: "index_follows_on_follower_id"
24 | t.index ["leader_id", "follower_id"], name: "index_follows_on_leader_id_and_follower_id", unique: true
25 | t.index ["leader_id"], name: "index_follows_on_leader_id"
26 | end
27 |
28 | create_table "likes", force: :cascade do |t|
29 | t.integer "liker_id", null: false
30 | t.integer "picture_id", null: false
31 | t.datetime "created_at", null: false
32 | t.datetime "updated_at", null: false
33 | t.index ["liker_id", "picture_id"], name: "index_likes_on_liker_id_and_picture_id", unique: true
34 | t.index ["liker_id"], name: "index_likes_on_liker_id"
35 | t.index ["picture_id"], name: "index_likes_on_picture_id"
36 | end
37 |
38 | create_table "notifications", force: :cascade do |t|
39 | t.integer "user_id", null: false
40 | t.integer "initiator_id", null: false
41 | t.datetime "created_at", null: false
42 | t.datetime "updated_at", null: false
43 | t.boolean "viewed"
44 | t.index ["initiator_id"], name: "index_notifications_on_initiator_id"
45 | t.index ["user_id"], name: "index_notifications_on_user_id"
46 | end
47 |
48 | create_table "pictures", force: :cascade do |t|
49 | t.string "title"
50 | t.string "image_url", null: false
51 | t.integer "uploader_id", null: false
52 | t.datetime "created_at", null: false
53 | t.datetime "updated_at", null: false
54 | t.index ["uploader_id"], name: "index_pictures_on_uploader_id"
55 | end
56 |
57 | create_table "users", force: :cascade do |t|
58 | t.string "username", null: false
59 | t.string "password_digest", null: false
60 | t.string "session_token", null: false
61 | t.string "description"
62 | t.datetime "created_at", null: false
63 | t.datetime "updated_at", null: false
64 | t.string "profile_url"
65 | t.string "cover_url"
66 | t.index ["username"], name: "index_users_on_username", unique: true
67 | end
68 |
69 | end
70 |
--------------------------------------------------------------------------------
/db/seeds.rb:
--------------------------------------------------------------------------------
1 | # This file should contain all the record creation needed to seed the database with its default values.
2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup).
3 | #
4 | # Examples:
5 | #
6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }])
7 | # Character.create(name: 'Luke', movie: movies.first)
8 | #
9 |
10 | User.destroy_all
11 | user1 = User.create({username: "Felix", password: "password", cover_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528916078/dabtje61xxqksoljk470.jpg", profile_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528916116/pxdpqlokeeyhonybowbv.jpg", description: Faker::Movie.quote })
12 | user2 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", description: Faker::Movie.quote})
13 | user3 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", description: Faker::Movie.quote})
14 | user4 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", description: Faker::Movie.quote})
15 | user5 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528776849/coex5jgohodc0mqgty5r.jpg", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528776350/boy4qqret5rpranjk18j.jpg", description: Faker::Movie.quote})
16 | user6 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528776541/nren1oul9wgoqmsyeiux.jpg", description: Faker::Movie.quote})
17 | user7 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528776272/ygty6yjd99z9n32bi0kn.jpg", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528776894/aljdnek7yzyjcaamtyje.jpg", description: Faker::Movie.quote})
18 | user8 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528776588/bemxylyfht8kh8xhi7lh.jpg", description: Faker::Movie.quote})
19 | user9 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528822826/freestocks-org-570360-unsplash.jpg", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528777304/dw2qed71upmhlolo769c.jpg", description: Faker::Movie.quote})
20 | user10 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528494930/kulrpduwnhic8fbh9prf.png", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528490121/azwif34ctvpdbsbwtshp.jpg", description: Faker::Movie.quote})
21 | user11 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528867401/asaf-r-467443-unsplash.jpg", cover_url:"", description: Faker::Movie.quote})
22 | user12 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528867416/touzeey-302645-unsplash.jpg", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528867310/valentino-funghi-275990-unsplash.jpg", description: Faker::Movie.quote})
23 | user13 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528914135/jason-leung-667985-unsplash.jpg", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528867310/valentino-funghi-275990-unsplash.jpg", description: Faker::Movie.quote})
24 | user14 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1528914169/jordan-andrews-396838-unsplash.jpg", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528914153/jon-tyson-520952-unsplash.jpg", description: Faker::Movie.quote})
25 | user15 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1529038851/ezra-comeau-jeffrey-661583-unsplash.jpg", cover_url:"", description: Faker::Movie.quote})
26 | user16 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1529038852/brad-halcrow-590097-unsplash.jpg", cover_url:"", description: Faker::Movie.quote})
27 | user17 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1529038882/demetrius-washington-670055-unsplash.jpg", cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529038861/murray-campbell-29990-unsplash.jpg", description: Faker::Movie.quote})
28 | user18 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1529079265/kyle-loftus-592128-unsplash.jpg", cover_url:"", description: Faker::Movie.quote})
29 | user19 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1529079285/roberto-nickson-g-366181-unsplash.jpg", description: Faker::Movie.quote})
30 |
31 | user20 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1530853920/blake-cheek-725235-unsplash.jpg",cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853927/bence-boros-725201-unsplash.jpg", description: Faker::Movie.quote})
32 | user21 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1530853927/afa-ah-loo-724939-unsplash.jpg", description: Faker::Movie.quote})
33 | user22 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1530853941/roberto-delgado-webb-724962-unsplash.jpg",cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853929/qi-bin-725231-unsplash.jpg", description: Faker::Movie.quote})
34 | user23 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1530853946/stephany-lorena-726155-unsplash.jpg", description: Faker::Movie.quote})
35 | user24 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1530853952/yiran-ding-726220-unsplash.jpg", description: Faker::Movie.quote})
36 | user25 = User.create({username: Faker::Twitter.unique.screen_name, password: "password", profile_url: "https://res.cloudinary.com/dbm56y2y/image/upload/v1530853943/liliia-beda-724286-unsplash.jpg",cover_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853932/j-basiga-724297-unsplash.jpg", description: Faker::Movie.quote})
37 |
38 |
39 | Picture.destroy_all
40 | Picture.create({title: "Made it to sf",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528915569/james-donovan-150590-unsplash.jpg", uploader_id: user1.id})
41 | Picture.create({title: "Outdoor adventures",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528914897/luo-lei-625155-unsplash_2.jpg", uploader_id: user2.id })
42 | Picture.create({title: "Ready for new years",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528914995/chuttersnap-336571-unsplash.jpg", uploader_id: user3.id })
43 | Picture.create({title: "Finally Here!",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528915078/thomas-ribaud-665075-unsplash.jpg", uploader_id: user4.id })
44 | Picture.create({title: "Love this city",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528915184/sasha-stories-267948-unsplash.jpg", uploader_id: user5.id })
45 | Picture.create({title: "Pure relaxation",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528915244/seth-doyle-133176-unsplash.jpg", uploader_id: user6.id })
46 |
47 |
48 | Picture.create({title: "Morning Essentials",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528776389/zjc06v3xthn94njm8vnl.jpg", uploader_id: user7.id })
49 | Picture.create({title: "Beauty in the night",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528777129/s9imhavzxntyo3y7gmcc.jpg", uploader_id: user8.id })
50 | Picture.create({title: "never look back",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528776631/qfeakv2w9rdfbhkvbpff.jpg", uploader_id: user9.id })
51 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528915367/wenni-zhou-463395-unsplash.jpg", uploader_id: user10.id })
52 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528777262/qevtrfepysdcivhcn41b.jpg", uploader_id: user11.id })
53 | Picture.create({title: "beauty is art",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528822802/scott-webb-271478-unsplash.jpg", uploader_id: user12.id })
54 | Picture.create({title: "The joys of brunch",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528776915/zu1koeinx1axioxafjih.jpg", uploader_id: user13.id })
55 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528825624/ya74xwyn1gxspcpdrhqw.jpg", uploader_id: user14.id })
56 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528827269/erik-witsoe-366860-unsplash.jpg", uploader_id: user15.id })
57 | Picture.create({title: "last night was awesome",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528825718/nouv1lfig99eth0iatrl.jpg", uploader_id: user16.id })
58 | Picture.create({title: "nature speaks",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528867316/alex-koch-513845-unsplash.jpg", uploader_id: user17.id })
59 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528867285/dulcey-lima-456862-unsplash.jpg", uploader_id: user18.id })
60 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528867266/braden-jarvis-625463-unsplash.jpg", uploader_id: user19.id })
61 | Picture.create({title: "Wedding Bells",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528867264/callie-morgan-285271-unsplash.jpg", uploader_id: user20.id })
62 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528867278/eric-ward-304333-unsplash.jpg", uploader_id: user21.id })
63 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528914160/florin-kozma-678292-unsplash.jpg", uploader_id: user22.id })
64 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528914176/joshua-fuller-204247-unsplash.jpg", uploader_id: user23.id })
65 | Picture.create({title: "No adventure is too far off",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1528914153/jon-tyson-520952-unsplash.jpg", uploader_id: user24.id })
66 | Picture.create({title: "My little happy place",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529038894/allef-vinicius-253985-unsplash.jpg", uploader_id: user25.id })
67 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529038873/vladimir-kudinov-61639-unsplash.jpg", uploader_id: user1.id })
68 | Picture.create({title: "Pure Solice",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529038913/jorge-flores-223750-unsplash.jpg", uploader_id: user2.id })
69 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529038926/fancycrave-225507-unsplash.jpg", uploader_id: user3.id })
70 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529038906/pietro-de-grandi-329892-unsplash.jpg", uploader_id: user4.id })
71 | Picture.create({title: "This trip was well worth it",image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529038920/liam-pozz-247954-unsplash.jpg", uploader_id: user5.id })
72 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529079276/annie-spratt-460959-unsplash.jpg", uploader_id: user6.id })
73 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529079292/amber-teasley-369443-unsplash.jpg", uploader_id: user7.id })
74 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853931/ricardo-l-tamayo-725857-unsplash.jpg", uploader_id: user8.id })
75 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853933/jakub-dziubak-724845-unsplash.jpg", uploader_id: user9.id })
76 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853933/iswanto-arif-726203-unsplash.jpg", uploader_id: user10.id })
77 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853938/karl-fredrickson-724710-unsplash.jpg", uploader_id: user11.id })
78 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853938/derek-story-724325-unsplash.jpg", uploader_id: user12.id })
79 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853938/ben-konfrst-724459-unsplash.jpg", uploader_id: user13.id })
80 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853941/julie-johnson-723618-unsplash.jpg", uploader_id: user14.id })
81 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853942/rosie-kerr-726224-unsplash.jpg", uploader_id: user15.id })
82 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853942/karl-fredrickson-724719-unsplash.jpg", uploader_id: user16.id })
83 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853944/mads-schmidt-723416-unsplash.jpg", uploader_id: user17.id })
84 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853947/marko-blazevic-725854-unsplash.jpg", uploader_id: user18.id })
85 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853946/shridhar-gupta-724669-unsplash.jpg", uploader_id: user19.id })
86 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853949/victor-rodriguez-726159-unsplash.jpg", uploader_id: user20.id })
87 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853952/mesut-kaya-724174-unsplash.jpg", uploader_id: user21.id })
88 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853956/qi-bin-725216-unsplash.jpg", uploader_id: user22.id })
89 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530853965/wes-hicks-725738-unsplash.jpg", uploader_id: user23.id })
90 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530901252/da-kraplak-727110-unsplash.jpg", uploader_id: user24.id })
91 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530901254/grillot-edouard-726756-unsplash.jpg", uploader_id: user25.id })
92 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530901254/usukhbayar-gankhuyag-726907-unsplash.jpg", uploader_id: user1.id })
93 | Picture.create({title: Faker::Movie.quote,image_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1530901255/peter-oslanec-726455-unsplash.jpg", uploader_id: user2.id })
94 |
95 | Follow.create({leader_id:user3.id, follower_id:user1.id})
96 | Follow.create({leader_id:user22.id, follower_id:user1.id})
--------------------------------------------------------------------------------
/frontend/actions/follow_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIFollows from '../util/follow_util';
2 |
3 |
4 | export const RECEIVE_FOLLOW = 'RECEIVE_FOLLOW';
5 | export const REMOVE_FOLLOW = 'REMOVE_FOLLOW';
6 | export const RECEIVE_FOLLOWS = 'RECEIVE_FOLLOWS';
7 |
8 | export const receiveFollow = (follow) => ({
9 | type: RECEIVE_FOLLOW,
10 | follow
11 | });
12 |
13 | export const receiveFollows = (follows) => ({
14 | type: RECEIVE_FOLLOWS,
15 | follows
16 | });
17 |
18 | export const removeFollow = (follow) => ({
19 | type: REMOVE_FOLLOW,
20 | follow
21 | });
22 |
23 | export const fetchFollows = () => dispatch => (
24 | APIFollows.fetchFollows().then(follows => dispatch(receiveFollows(follows)))
25 | );
26 |
27 | export const createFollow = (follow) => dispatch => (
28 | APIFollows.createFollow(follow).then(follow => {
29 | return dispatch(receiveFollow(follow));
30 | })
31 | );
32 |
33 | export const deleteFollow = (id) => dispatch => (
34 | APIFollows.deleteFollow(id).then(follow => dispatch(removeFollow(follow)))
35 | );
36 |
--------------------------------------------------------------------------------
/frontend/actions/modal_actions.js:
--------------------------------------------------------------------------------
1 | export const OPEN_MODAL = 'OPEN_MODAL';
2 | export const CLOSE_MODAL = 'CLOSE_MODAL';
3 |
4 | export const openModal = modal => {
5 | return {
6 | type: OPEN_MODAL,
7 | modal
8 | };
9 | };
10 |
11 | export const closeModal = () => {
12 | return {
13 | type: CLOSE_MODAL
14 | };
15 | };
16 |
--------------------------------------------------------------------------------
/frontend/actions/notification_actions.js:
--------------------------------------------------------------------------------
1 | import * as APINotifications from '../util/notification_util';
2 |
3 |
4 | export const RECEIVE_NOTIFICATION = 'RECEIVE_NOTIFICATION';
5 | export const RECEIVE_NOTIFICATIONS = 'RECEIVE_NOTIFICATIONS';
6 |
7 | export const receiveNotification = (notification) => (
8 | {
9 | type: RECEIVE_NOTIFICATION,
10 | notification
11 | }
12 | );
13 | // {
14 | // return {
15 | // type: RECEIVE_NOTIFICATION,
16 | // notification
17 | // };
18 | // };
19 |
20 | export const receiveNotifications = (notifications) => ({
21 | type: RECEIVE_NOTIFICATIONS,
22 | notifications
23 | });
24 |
25 | export const fetchNotifications = () => dispatch => (
26 | APINotifications.fetchNotifications().then(notifications => dispatch(receiveNotifications(notifications)))
27 | );
28 |
29 | export const createNotification = (notification) => dispatch => {
30 | return APINotifications.createNotification(notification).then(notification => {
31 | return dispatch(receiveNotification(notification));
32 | });
33 | };
34 |
35 | export const updateNotification = (notification) => dispatch => {
36 | return APINotifications.updateNotification(notification).then(notification => {
37 | return dispatch(receiveNotification(notification));
38 | });
39 | };
40 |
--------------------------------------------------------------------------------
/frontend/actions/picture_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIPictures from '../util/picture_api_util';
2 | import * as APIFeed from '../util/feed_util';
3 | import RECEIVE_USERS from './session_actions';
4 | export const RECEIVE_PICTURE = 'RECEIVE_PICTURE';
5 | export const RECEIVE_PICTURES = 'RECEIVE_PICTURES';
6 | export const CLEAR_PICTURES = 'CLEAR_PICTURES';
7 | export const REMOVE_PICTURE = 'REMOVE_PICTURE';
8 |
9 |
10 | export const receivePictures = (pictures) => ({
11 | type: RECEIVE_PICTURES,
12 | pictures
13 | });
14 |
15 | export const receivePicture = (picture) => {
16 |
17 | return {type: RECEIVE_PICTURE,
18 | picture};
19 | };
20 |
21 | export const removePicture = (picture) => {
22 |
23 | return {type: REMOVE_PICTURE,
24 | picture};
25 | };
26 |
27 | export const clearPictures = () => {
28 | return {type: CLEAR_PICTURES};
29 | };
30 |
31 | export const getUserPictures = (userId) => dispatch => {
32 | return APIPictures.fetchUserPictures(userId).then(pictures => dispatch(receivePictures(pictures)));
33 | };
34 |
35 | export const getUserPicture = (pictureId) => dispatch => {
36 | return APIPictures.fetchUserPicture(pictureId).then(picture => dispatch(receivePicture(picture)));
37 | };
38 |
39 | export const uploadPicture = (picture, userId) => dispatch => {
40 | return APIPictures.uploadPicture(picture, userId).then(picture => {
41 | return dispatch(receivePicture(picture));
42 | });
43 | };
44 |
45 | export const updatePicture = (picture) => dispatch => {
46 | return APIPictures.updatePicture(picture).then(picture => {
47 | return dispatch(receivePicture(picture));
48 | });
49 | };
50 |
51 | export const deletePicture = (pictureId) => dispatch => {
52 | return APIPictures.deletePicture(pictureId).then(picture => dispatch(removePicture(picture)));
53 | };
54 |
55 | export const homeFeed = () => dispatch => {
56 | return APIFeed.homeFeed().then(pictures => {
57 | dispatch(receivePictures(pictures));
58 | }
59 | );
60 | };
61 |
62 | export const discoverFeed = () => dispatch => {
63 | return APIFeed.discoverFeed().then(pictures => {
64 | dispatch(receivePictures(pictures));
65 | }
66 | );
67 | };
68 |
--------------------------------------------------------------------------------
/frontend/actions/session_actions.js:
--------------------------------------------------------------------------------
1 | export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER';
2 | export const LOGOUT_CURRENT_USER = 'LOGOUT_CURRENT_USER';
3 | export const RECEIVE_ERRORS = 'RECEIVE_ERRORS';
4 | export const CLEAR_ERRORS = 'CLEAR_ERRORS';
5 | export const RECEIVE_USER = 'RECEIVE_USER';
6 | export const REMOVE_USER = 'CLEAR_USER';
7 | export const RECEIVE_USERS = 'RECEIVE_USERS';
8 | import * as APIUtil from '../util/session_api_util';
9 | import {fetchFollows} from './follow_actions';
10 | import {fetchNotifications} from './notification_actions';
11 |
12 | export const receiveCurrentUser = (currentUser) => {
13 | return {
14 | type: RECEIVE_CURRENT_USER,
15 | currentUser
16 | };
17 | };
18 |
19 | export const logoutCurrentUser = () => {
20 | return {
21 | type: LOGOUT_CURRENT_USER,
22 | };
23 | };
24 |
25 | export const receiveErrors = (errors) => {
26 | return {
27 | type: RECEIVE_ERRORS,
28 | errors
29 | };
30 | };
31 |
32 | export const clearErrors = () => {
33 | return {
34 | type: CLEAR_ERRORS
35 |
36 | };
37 | };
38 |
39 | export const receiveUser = (user) => {
40 | return {
41 | type: RECEIVE_USER,
42 | user
43 | };
44 | };
45 |
46 | export const removeUser = (user) => {
47 | return {type: REMOVE_USER, user};
48 | };
49 |
50 | export const receiveUsers = (users) => {
51 | return {type: RECEIVE_USERS, users};
52 | };
53 |
54 | export const getUser = (user) => dispatch => {
55 | return APIUtil.fetchUser(user)
56 | .then((user) =>
57 | dispatch(receiveUser(user)));
58 | };
59 |
60 |
61 | export const login = (user) => dispatch => {
62 | return APIUtil.login(user)
63 | .then((currentUser) => {
64 | dispatch(fetchUsers());
65 | dispatch(fetchNotifications());
66 | return dispatch(receiveCurrentUser(currentUser));},
67 | (errors) => dispatch(receiveErrors(errors.responseJSON))
68 | );
69 | };
70 |
71 | export const logout = () => dispatch => {
72 | return APIUtil.logout()
73 | .then(() => dispatch(logoutCurrentUser()));
74 | };
75 |
76 | export const signup = (user) => dispatch => (
77 | APIUtil.signup(user).then((currentUser) => {
78 | dispatch(fetchUsers());
79 | return dispatch(receiveCurrentUser(currentUser));},(errors) => dispatch(receiveErrors(errors.responseJSON))));
80 |
81 | export const fetchUsers = () => dispatch => {
82 | return APIUtil.fetchUsers().then((users) => dispatch(receiveUsers(users)));
83 | };
84 |
85 | export const updateUser = (user) => dispatch => (
86 | APIUtil.updateUser(user).then((currentUser) => dispatch(receiveCurrentUser(currentUser)),(errors) => dispatch(receiveErrors(errors.responseJSON))));
87 |
88 | export const updateUserNotification = (user) => dispatch => (
89 | APIUtil.updateUser(user).then((user) => dispatch(receiveUser(user)),(errors) => dispatch(receiveErrors(errors.responseJSON))));
90 |
91 | // APIUtil.signup(user)
92 | // .then((currentUser) =>
93 | // dispatch(receiveCurrentUser(currentUser)),
94 | // (errors) => dispatch(receiveErrors(errors.responseJSON)))
95 |
--------------------------------------------------------------------------------
/frontend/components/App.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import GreetingContainer from './greeting/greeting_container';
3 | import LoginFormContainer from './session/login_form_container';
4 | import SignupFormContainer from './session/signup_form_container';
5 | import Home from './home';
6 | import {Route, Switch} from 'react-router';
7 | import {AuthRoute, LogRoute} from '../util/route_util';
8 | import FeedContainer from './feed/feed_container';
9 | import ProfileContainer from './profile/profile_container';
10 | import Modal from './modal/modal';
11 | import DiscoverContainer from './discover/discover_container';
12 |
13 |
14 | const App = () => (
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | );
28 |
29 | export default App;
30 |
--------------------------------------------------------------------------------
/frontend/components/discover/discover_component.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavContainer from '../nav/nav_container';
3 | import DiscoverFeedItem from './discover_feed_item';
4 |
5 | class DiscoverComponent extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | componentDidMount () {
11 | const {fetchFollows, discoverFeed, fetchUsers} = this.props;
12 |
13 | fetchFollows();
14 | discoverFeed();
15 | fetchUsers();
16 | }
17 |
18 | render() {
19 | const {pictures, follows, currentUser, users, clearPictures, openModal, parseUrl} = this.props;
20 | const pictureList = pictures.map((picture =>{
21 | return( );
22 | }));
23 | return (
24 |
25 |
26 |
27 |
The Newest Pictures
28 |
Discover the the freshest photos just added to 360px
29 |
30 |
31 |
32 | {pictureList}
33 |
34 |
35 |
36 |
37 |
38 | );
39 | }
40 | }
41 |
42 | export default DiscoverComponent;
43 |
--------------------------------------------------------------------------------
/frontend/components/discover/discover_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { logout } from '../../actions/session_actions';
3 | import DiscoverComponent from './discover_component';
4 | import {getUserPictures, discoverFeed} from '../../actions/picture_actions';
5 | import {fetchUsers} from '../../actions/session_actions';
6 | import {fetchFollows} from '../../actions/follow_actions';
7 | import {pictureList, parseUrl} from '../../util/selectors';
8 | import {clearPictures} from '../../actions/picture_actions';
9 | import { openModal} from '../../actions/modal_actions';
10 |
11 |
12 | const mapStateToProps = ({follows, entities, session}) => (
13 | {
14 | currentUser: entities.users[session.id],
15 | pictures: pictureList(entities.pictures),
16 | users: entities.users,
17 | follows: follows,
18 | parseUrl: parseUrl
19 | });
20 |
21 | const mapDispatchToProps = () => dispatch => ({
22 | getUserPictures: (userId) => dispatch(getUserPictures(userId)),
23 | fetchFollows: (follows) => dispatch(fetchFollows(follows)),
24 | discoverFeed: () => dispatch(discoverFeed()),
25 | fetchUsers: () => dispatch(fetchUsers()),
26 | clearPictures: () => dispatch(clearPictures()),
27 | openModal: (modal) => dispatch(openModal(modal))
28 | });
29 |
30 | export default connect(mapStateToProps, mapDispatchToProps)(DiscoverComponent);
31 |
--------------------------------------------------------------------------------
/frontend/components/discover/discover_feed_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router-dom';
3 |
4 | class DiscoverFeedItem extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | profileDirect(e) {
10 | e.stopPropagation();
11 | }
12 |
13 | render() {
14 | const {picture, openModal, user, clearPictures, parseUrl} = this.props;
15 | const defaultUser = user || {id:"", username:"", profile_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529040240/default_profile.jpg"};
16 | const profile = defaultUser.profile_url || "https://res.cloudinary.com/dbm56y2y/image/upload/v1529040240/default_profile.jpg";
17 | return (
18 |
19 |
openModal({string:'show',picture: picture, user: user})}>
20 |
21 |
clearPictures()} to={`/profile/${picture.uploader_id}`}>
22 |
this.profileDirect(e)} className="discover-profile-icon" src={profile}/>
23 |
24 |
this.profileDirect(e)} className={`show-link`} to={`/profile/${picture.uploader_id}`}>
25 |
{defaultUser.username}
26 |
27 |
28 |
29 |
30 |
31 | );
32 | }
33 | }
34 |
35 | export default DiscoverFeedItem;
36 |
--------------------------------------------------------------------------------
/frontend/components/feed/feed.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavContainer from '../nav/nav_container';
3 | import {Link} from 'react-router-dom';
4 | import FeedListItem from './feed_list_item';
5 |
6 | class FeedComponent extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | }
10 |
11 | componentDidMount () {
12 | const {fetchFollows, homeFeed, fetchUsers} = this.props;
13 | fetchFollows();
14 | homeFeed();
15 | fetchUsers();
16 | }
17 |
18 | render(){
19 | const {users,clearPictures, openModal, currentUser, follows, parseUrlBig} = this.props;
20 | const pictures = this.props.pictures.map((picture) => {
21 | if (currentUser.id !== picture.uploader_id){
22 | return ;
23 | }
24 | });
25 | return(
26 |
34 | );
35 | }
36 | }
37 |
38 | export default FeedComponent;
39 |
--------------------------------------------------------------------------------
/frontend/components/feed/feed_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { logout } from '../../actions/session_actions';
3 | import Feed from './feed';
4 | import {getUserPictures, homeFeed} from '../../actions/picture_actions';
5 | import {fetchUsers} from '../../actions/session_actions';
6 | import {fetchFollows} from '../../actions/follow_actions';
7 | import {pictureList, parseUrlBig} from '../../util/selectors';
8 | import {clearPictures} from '../../actions/picture_actions';
9 | import { openModal} from '../../actions/modal_actions';
10 |
11 |
12 | const mapStateToProps = ({follows, entities, session}) => ({
13 | currentUser: entities.users[session.id],
14 | pictures: pictureList(entities.pictures),
15 | users: entities.users,
16 | follows: follows,
17 | parseUrlBig: parseUrlBig
18 | });
19 |
20 | const mapDispatchToProps = () => dispatch => ({
21 | getUserPictures: (userId) => dispatch(getUserPictures(userId)),
22 | fetchFollows: (follows) => dispatch(fetchFollows(follows)),
23 | homeFeed: () => dispatch(homeFeed()),
24 | fetchUsers: () => dispatch(fetchUsers()),
25 | clearPictures: () => dispatch(clearPictures()),
26 | openModal: (modal) => dispatch(openModal(modal))
27 | });
28 |
29 | export default connect(mapStateToProps, mapDispatchToProps)(Feed);
30 |
--------------------------------------------------------------------------------
/frontend/components/feed/feed_list_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router-dom';
3 | import {findFollow} from '../../util/selectors';
4 |
5 | class FeedListItem extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | suggested() {
11 | const {follows, currentUser, user} = this.props;
12 | if (user){
13 | if (findFollow(follows, currentUser.id, user.id)){
14 | return;
15 | } else {
16 | return ( " (suggested)" );
17 | }
18 | }
19 | }
20 | feedTitle() {
21 | const {picture} = this.props;
22 | if (picture.title === "") {
23 | return(title
);
24 | }else {
25 | return ({picture.title}
);
26 | }
27 | }
28 |
29 | render() {
30 | const {picture, user, clearPictures, openModal, currentUser, follows, parseUrlBig} = this.props;
31 | const defaultUser = user || {id:"", username: "", profile_url:"https://res.cloudinary.com/dbm56y2y/image/upload/v1529040240/default_profile.jpg"};
32 | const profile = defaultUser.profile_url || "https://res.cloudinary.com/dbm56y2y/image/upload/v1529040240/default_profile.jpg";
33 | return (
34 |
35 |
clearPictures()} to={`/profile/${picture.uploader_id}`}>
36 |
{defaultUser.username}{this.suggested()}
37 |
openModal({string:'show',picture: picture, user: user})}>
38 |
39 |
40 | {this.feedTitle()}
41 |
42 |
43 | );
44 | }
45 | }
46 |
47 | export default FeedListItem;
48 |
--------------------------------------------------------------------------------
/frontend/components/greeting/greeting.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 |
4 | const greeting = (props) => {
5 | if (props.currentUser){
6 | return (
7 |
8 |
Welcome {props.currentUser.username}!!!
9 | props.logout()}>Logout
10 |
11 | );
12 | } else {
13 | return (
14 |
15 |
Welcome!! Your can log in or sign up here :
16 | Signup
17 | Login
18 |
19 | );
20 | }
21 | };
22 |
23 | export default greeting;
24 |
--------------------------------------------------------------------------------
/frontend/components/greeting/greeting_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { logout } from '../../actions/session_actions';
3 | import Greeting from './greeting';
4 |
5 | const mapStateToProps = ({entities, session}) => ({
6 | currentUser: entities.users[session.id]
7 | });
8 |
9 | const mapDispatchToProps = () => dispatch => ({
10 | logout: () => dispatch(logout())
11 | });
12 |
13 | export default connect(mapStateToProps, mapDispatchToProps)(Greeting);
14 |
--------------------------------------------------------------------------------
/frontend/components/home.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavContainer from './nav/nav_container';
3 | import {Link} from 'react-router-dom';
4 | import {Redirect} from 'react-router';
5 |
6 | const Home = () => {
7 |
8 | return (
9 |
10 |
11 |
12 |
13 |
14 |
Find inspiration and discover the world through gorgeous photos
15 |
Share your vision with the world
16 |
17 | Join 360px
18 |
19 |
20 |
21 | );
22 | };
23 |
24 | export default Home;
25 |
--------------------------------------------------------------------------------
/frontend/components/modal/modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { closeModal } from '../../actions/modal_actions';
3 | import { connect } from 'react-redux';
4 | import UploadPictureContainer from '../upload/upload_picture_container';
5 | import EditPictureContainer from '../show/edit_picture_container';
6 | import ShowPictureContainer from '../show/show_picture_container';
7 | import {Animated} from "react-animated-css";
8 | import EditUserContainer from '../profile/edit_user_container';
9 | import UserPhotoContainer from '../profile/user_photo_container';
10 | import NotificationModalContainer from '../notification/notification_modal_container';
11 |
12 |
13 | const Modal = ({modal, closeModal}) => {
14 | if (!modal) {
15 | return null;
16 | }
17 | let component;
18 | switch (modal.string) {
19 | case 'upload':
20 | component = ;
21 | break;
22 | case 'edit':
23 | component = ;
24 | break;
25 | case 'show':
26 | component = ;
27 | break;
28 | case 'editUser':
29 | component = ;
30 | break;
31 | case 'uploadProfile':
32 | component = ;
33 | break;
34 | case 'uploadCover':
35 | component = ;
36 | break;
37 | case 'notify':
38 | component = ;
39 | break;
40 | default:
41 | return null;
42 | }
43 | if (modal.string === 'upload' || modal.string === 'edit' ){
44 | return (
45 |
46 |
e.stopPropagation()}>
47 | { component }
48 |
49 |
50 | );
51 | } else if (modal.string === 'show') {
52 | return (
53 |
54 |
e.stopPropagation()}>
55 | { component }
56 |
57 |
58 | );
59 | } else if ( modal.string === 'editUser') {
60 | return (
61 |
62 |
e.stopPropagation()}>
63 | { component }
64 |
65 |
66 | );
67 | } else if ( modal.string ==='uploadProfile' || modal.string === 'uploadCover' ) {
68 | return (
69 |
70 |
e.stopPropagation()}>
71 | { component }
72 |
73 |
74 | );
75 | } else if ( modal.string ==='notify' ) {
76 | return (
77 | //
78 |
e.stopPropagation()}>
79 | { component }
80 |
81 | //
82 | );
83 | }
84 | };
85 |
86 | const mapStateToProps = state => {
87 | return {
88 | modal: state.ui.modal
89 | };
90 | };
91 |
92 | const mapDispatchToProps = dispatch => {
93 | return {
94 | closeModal: () => dispatch(closeModal())
95 | };
96 | };
97 |
98 | export default connect(mapStateToProps, mapDispatchToProps)(Modal);
99 |
--------------------------------------------------------------------------------
/frontend/components/nav/nav_bar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, withRouter } from 'react-router-dom';
3 |
4 | class NavBar extends React.Component {
5 |
6 | componentDidMount() {
7 | if(this.props.currentUser){
8 | this.props.fetchNotifications();
9 | this.props.fetchUsers();
10 | }
11 | }
12 |
13 |
14 | nav(){
15 | const path = this.props.match.path;
16 | if (path === "/"){
17 | return "nav_bar";
18 | }else {
19 | return "session_bar";
20 | }
21 | }
22 | logo(){
23 |
24 | const path = this.props.match.path;
25 | if (path === "/"){
26 | return (
27 | 360px
28 | );
29 | }else if (path === "/login" || path === "/signup") {
30 | return (
31 | 360px
32 | );
33 | }
34 | else if (path === "/feed") {
35 | return (
36 | 360px
37 | );
38 | }
39 | else if (path.includes("profile") || path === "/discover" || path === "/notifications") {
40 | return (
41 | this.props.clearPictures()} className="profile_logo">360px
42 | );
43 | }
44 | }
45 |
46 | prepareFeed() {
47 | // const {clearPictures, homePage} = this.props;
48 | // clearPictures();
49 | // homePage();
50 | }
51 |
52 | // updateState(num){
53 | // this.props.clearPictures();
54 | // this.props.getUserPictures(num);
55 | // this.props.getUser(num);
56 | // }
57 |
58 |
59 | sideLink(){
60 | const path = this.props.match.path;
61 | const{getUserPictures, currentUser, clearPictures} = this.props;
62 | if (path === "/"){
63 | return (
64 | Log in
65 | );
66 | } else if (path === "/feed" || path.includes("profile") || path === "/discover" || path === "/notifications") {
67 | const defaultProfile = this.props.currentUser.profile_url || "https://res.cloudinary.com/dbm56y2y/image/upload/v1529040240/default_profile.jpg";
68 | return (
69 |
70 |
clearPictures()} to={`/profile/${this.props.currentUser.id}`} className = "profile-link">
71 |
72 | clearPictures()} to={`/profile/${currentUser.id}`} className = "profile-link">Profile
73 | this.handleLogout()}>Logout
74 |
75 |
76 | );
77 | }
78 | }
79 |
80 | handleLogout() {
81 | this.props.closeModal();
82 | this.props.logout();
83 |
84 | }
85 |
86 | sideButton() {
87 | const path = this.props.match.path;
88 | if (path === "/"){
89 | return (
90 | Sign up
91 | );
92 | }else if (path === "/login") {
93 | return (
94 | Sign up
95 | );
96 | } else if (path === "/signup") {
97 | return (
98 | Login
99 | );
100 | }else if (path === "/feed" || path.includes("profile") || path === "/discover" || path === "/notifications") {
101 | return (
102 | this.uploadPicture(e)} className="upload"> Upload
103 | );
104 | }
105 | }
106 |
107 | leftNav(){
108 | const path = this.props.match.path;
109 | if (path === "/"){
110 | return "left_nav";
111 | }else {
112 | return "login_left";
113 | }
114 | }
115 |
116 | uploadPicture(e) {
117 | e.preventDefault();
118 | const currentUserId = this.props.currentUser.id;
119 | cloudinary.openUploadWidget(window.cloudinary_options, (error, picture) => {
120 | if (error === null){
121 | this.props.openModal({string:'upload', picture: picture[0]});
122 | }
123 | });
124 | }
125 | discover() {
126 | const path = this.props.match.path;
127 | if (path === "/" || path === "/login" || path === "/signup") {
128 | return (Discover );
129 | } else if (path === "/feed" || path.includes("profile") || path === "/notifications"){
130 | return ( this.props.clearPictures()} className="discover-text" to="/discover">Discover );
131 | } else if (path === "/discover") {
132 | return (Discover );
133 | }
134 | }
135 |
136 | bell() {
137 | const {openModal, newNotifications} = this.props;
138 | const path = this.props.match.path;
139 | const length = this.props.newNotifications.length;
140 | if (length === 0 && (path === "/feed" || path.includes("profile") || path === "/discover" || path === "/notifications") ) {
141 | return ( openModal({string: 'notify', newNotifications: newNotifications})} className="bell"> );
142 | } else if (length > 0 && (path === "/feed" || path.includes("profile") || path === "/discover" || path === "/notifications") ) {
143 | return ( this.notifyClick()} className="notify">{length}
);
144 |
145 | }
146 | }
147 |
148 | notifyClick() {
149 | const {newNotifications,updateNotification, openModal, notifications} = this.props;
150 | newNotifications.forEach((n) => {
151 | notifications.forEach((n2) => {
152 | if (n.user_id === n2.user_id && n2.initiator_id === n.initiator_id && n.id !== n2.id){
153 | updateNotification({id:n2.id, viewed: true});
154 | }
155 | });
156 | updateNotification({id:n.id, viewed: true});
157 | });
158 | openModal({string: 'notify', newNotifications: newNotifications});
159 | }
160 |
161 | rightNav() {
162 | const path = this.props.match.path;
163 | if (path === "/" || path === "/login" || path === "/signup"){
164 | return "right_nav";
165 | } else {
166 | return "login-right-nav";
167 | }
168 | }
169 |
170 |
171 | render() {
172 | return (
173 |
174 |
175 | {this.logo()}
176 | {this.discover()}
177 |
178 |
179 | {this.sideLink()}
180 | {this.bell()}
181 | {this.sideButton()}
182 |
183 |
184 | );
185 | }
186 | }
187 |
188 | export default withRouter(NavBar);
189 |
--------------------------------------------------------------------------------
/frontend/components/nav/nav_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { signup, login, logout } from '../../actions/session_actions';
3 | import { Link } from 'react-router-dom';
4 | import React from 'react';
5 | import NavBar from './nav_bar';
6 | import {uploadPicture} from '../../actions/picture_actions';
7 | import { openModal, closeModal} from '../../actions/modal_actions';
8 | import {getUserPictures, clearPictures, homePage} from '../../actions/picture_actions';
9 | import {getUser,fetchUsers} from '../../actions/session_actions';
10 | import {fetchNotifications, updateNotification} from '../../actions/notification_actions';
11 | import {newNotifications, notifyArray} from '../../util/selectors';
12 |
13 | const mapStateToProps = ({entities, session, notifications}, ownProps) => {
14 | return {currentUser: entities.users[session.id],
15 | profileUser: ownProps.user,
16 | newNotifications: newNotifications(notifications),
17 | notifications: notifyArray(notifications)
18 | };
19 | };
20 | const mapDispatchToProps = () => dispatch => ({
21 | logout: () => dispatch(logout()),
22 | uploadPicture: (picture, userId) => dispatch(uploadPicture(picture, userId)),
23 | openModal: (modal) => dispatch(openModal(modal)),
24 | getUserPictures: (userId) => dispatch(getUserPictures(userId)),
25 | getUser: (userId) => dispatch(getUser(userId)),
26 | clearPictures: () => dispatch(clearPictures()),
27 | homePage: () => dispatch(homePage()),
28 | fetchNotifications: () => dispatch(fetchNotifications()),
29 | closeModal: () => dispatch(closeModal()),
30 | updateNotification: (notification) => dispatch(updateNotification(notification)),
31 | fetchUsers: () => dispatch(fetchUsers())
32 | });
33 |
34 | export default connect(mapStateToProps, mapDispatchToProps)(NavBar);
35 |
--------------------------------------------------------------------------------
/frontend/components/notification/notification_modal.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router-dom';
3 | import NotifyModalListItem from './notify_modal_list_item';
4 |
5 | class NotifyModal extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | }
9 |
10 | componentDidMount(){
11 |
12 | document.addEventListener('keydown', (e) => {
13 | if (e.keyCode === 27) this.props.closeModal();
14 | });
15 |
16 | let notifyModal = document.getElementsByClassName('modal-child-notify');
17 |
18 | document.addEventListener('click', (e) => {
19 | if (notifyModal.length > 0){
20 | var clickOnModal = notifyModal[0].contains(e.target);
21 |
22 | if (!clickOnModal) {
23 | this.props.closeModal();
24 | notifyModal = [];
25 | }
26 | }
27 | });
28 |
29 |
30 |
31 | }
32 |
33 | notifyBody(notificationList) {
34 | if (this.props.notifications.length === 0){
35 | return(You have no notifications
);
36 | }else {
37 | return (
38 |
39 | {notificationList}
40 |
41 | );
42 | }
43 | }
44 | render() {
45 | const {users, closeModal} = this.props;
46 | let ids = [];
47 | const notificationList = this.props.notifications.map((n) => {
48 | if (!ids.includes(n.initiator_id)){
49 | ids.push(n.initiator_id);
50 | return ;
51 | }
52 | });
53 | return (
54 |
55 |
56 | Notifications
57 |
58 | {this.notifyBody(notificationList)}
59 |
60 |
61 | );
62 | }
63 | }
64 |
65 | export default NotifyModal;
66 |
--------------------------------------------------------------------------------
/frontend/components/notification/notification_modal_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import React from 'react';
3 | import {findFollow, notifyArray} from '../../util/selectors';
4 | import NotifyModal from './notification_modal';
5 | import { closeModal, openModal } from '../../actions/modal_actions';
6 | import {deletePicture, updatePicture} from '../../actions/picture_actions';
7 | import {fetchFollows, createFollow, deleteFollow} from '../../actions/follow_actions';
8 | import {createNotification} from '../../util/notification_util';
9 |
10 |
11 | const mapStateToProps = ({follows,notifications, session, entities:{users},ui:{modal}}, ownProps) => {
12 | return {
13 | currentUser: users[session.id],
14 | users: users,
15 | newNotifications: modal.newNotifications,
16 | notifications: notifyArray(notifications)
17 | };
18 | };
19 | const mapDispatchToProps = () => dispatch => ({
20 | closeModal: () => dispatch(closeModal())
21 |
22 | });
23 |
24 | export default connect(mapStateToProps, mapDispatchToProps)(NotifyModal);
25 |
--------------------------------------------------------------------------------
/frontend/components/notification/notify_modal_list_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router-dom';
3 |
4 | class NotifyModalListItem extends React.Component {
5 | constructor(props){
6 | super(props);
7 | }
8 |
9 | render() {
10 | const {initiator, closeModal} = this.props;
11 | let url = initiator.profile_url || "https://res.cloudinary.com/dbm56y2y/image/upload/v1529040240/default_profile.jpg";
12 | if (url === ""){ url = "https://res.cloudinary.com/dbm56y2y/image/upload/v1529040240/default_profile.jpg";}
13 | return (
14 |
15 | closeModal()} to={`/profile/${initiator.id}`}> closeModal()} className="notify-link-text" to={`/profile/${initiator.id}`}>{initiator.username} started following you
16 |
17 | );
18 | }
19 | }
20 |
21 | export default NotifyModalListItem;
22 |
--------------------------------------------------------------------------------
/frontend/components/profile/edit_user_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import React from 'react';
3 | import EditUserForm from './edit_user_form';
4 | import { closeModal, openModal } from '../../actions/modal_actions';
5 | import { updateUser } from '../../actions/session_actions';
6 | import {fetchFollows, createFollow, deleteFollow} from '../../actions/follow_actions';
7 |
8 | const mapStateToProps = ({follows, session, entities:{users},ui:{modal}}, ownProps) => {
9 | return {
10 | user: modal.user
11 | };
12 | };
13 | const mapDispatchToProps = () => dispatch => ({
14 | closeModal: () => dispatch(closeModal()),
15 | openModal: (object) => dispatch(openModal(object)),
16 | updateUser: (user) => dispatch(updateUser(user))
17 | });
18 |
19 | export default connect(mapStateToProps, mapDispatchToProps)(EditUserForm);
20 |
--------------------------------------------------------------------------------
/frontend/components/profile/edit_user_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class EditUserForm extends React.Component {
4 | constructor(props) {
5 | super(props);
6 | this.state = {description: this.props.user.description};
7 | this.handleSubmit = this.handleSubmit.bind(this);
8 | this.handleChange = this.handleChange.bind(this);
9 | }
10 |
11 | handleChange() {
12 | return (e) => {
13 | return this.setState({description: e.target.value});
14 | };
15 | }
16 |
17 | handleSubmit() {
18 | const {updateUser, user,closeModal} = this.props;
19 | updateUser({id: user.id, username: user.username, description: this.state.description, cover_url: user.cover_url, profile_url: user.profile_url});
20 | closeModal();
21 | }
22 |
23 | handleUpload(e, string) {
24 | this.props.closeModal();
25 | e.preventDefault();
26 | cloudinary.openUploadWidget(window.cloudinary_options, (error, picture) => {
27 | if (error === null){
28 | this.props.openModal({string:string, picture: picture[0]});
29 |
30 | }
31 | });
32 | }
33 |
34 |
35 |
36 | render() {
37 | return(
38 |
39 |
this.props.closeModal()} className="fa fa-close edit-close">
40 |
this.handleUpload(e,'uploadCover')}> Upload Cover Picture
41 |
this.handleUpload(e, 'uploadProfile')}> Upload Profile Picture
42 |
Add/Change Description
43 |
47 |
48 | );
49 | }
50 | }
51 |
52 | export default EditUserForm;
53 |
--------------------------------------------------------------------------------
/frontend/components/profile/photo_list_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const PhotoListItem = ({picture, openModal, user, parseUrl}) => {
4 | return (
5 | openModal({string:'show',picture: picture, user: user})}>
6 | );
7 | };
8 |
9 | export default PhotoListItem;
10 |
--------------------------------------------------------------------------------
/frontend/components/profile/profile_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import ProfilePage from './profile_page';
3 | import {pictureList, findFollow, getFollowers, getFollowings, parseUrl} from '../../util/selectors';
4 | import {getUserPictures} from '../../actions/picture_actions';
5 | import {getUser} from '../../actions/session_actions';
6 | import { openModal} from '../../actions/modal_actions';
7 | import {fetchFollows, createFollow, deleteFollow} from '../../actions/follow_actions';
8 | import {createNotification} from '../../util/notification_util';
9 |
10 | const mapStateToProps = ({entities, session, follows}, ownProps) =>{
11 | const defaultUser = {id: "",username:""};
12 | return {
13 | currentUser: entities.users[session.id],
14 | userId: parseInt(ownProps.match.params.id),
15 | user: entities.users[parseInt(ownProps.match.params.id)] || defaultUser,
16 | pictures: pictureList(entities.pictures),
17 | follow: findFollow(follows,session.id, parseInt(ownProps.match.params.id)),
18 | followers: getFollowers(follows,parseInt(ownProps.match.params.id)),
19 | following: getFollowings(follows,parseInt(ownProps.match.params.id)),
20 | parseUrl: parseUrl,
21 | createNotification: createNotification
22 | };
23 | };
24 |
25 | const mapDispatchToProps = () => dispatch => ({
26 | getUserPictures: (userId) => dispatch(getUserPictures(userId)),
27 | getUser: (userId) => dispatch(getUser(userId)),
28 | openModal: (modal) => dispatch(openModal(modal)),
29 | fetchFollows: (follows) => dispatch(fetchFollows(follows)),
30 | createFollow: (follow) => dispatch(createFollow(follow)),
31 | deleteFollow: (id) => dispatch(deleteFollow(id))
32 | });
33 |
34 | export default connect(mapStateToProps, mapDispatchToProps)(ProfilePage);
35 |
--------------------------------------------------------------------------------
/frontend/components/profile/profile_page.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import NavContainer from '../nav/nav_container';
3 | import PhotoListItem from './photo_list_item';
4 | class ProfilePage extends React.Component {
5 | constructor(props){
6 | super(props);
7 | this.state = {user: this.props.user};
8 | this.updateFollow = this.updateFollow.bind(this);
9 | }
10 |
11 | componentDidMount () {
12 | this.props.getUserPictures(this.props.userId);
13 | this.props.getUser(this.props.userId);
14 | this.props.fetchFollows();
15 | }
16 |
17 | componentWillReceiveProps(nextProps) {
18 | if (this.props.userId !== nextProps.userId){
19 | this.props.getUserPictures(nextProps.userId);
20 | }
21 | }
22 |
23 | holderClass() {
24 | if (this.props.pictures.length < 5 && !this.props.user.cover_url) {
25 | return "profile-holder2";
26 | } else {
27 | return "profile-holder";
28 | }
29 |
30 | }
31 |
32 | updateFollow(){
33 | const {deleteFollow, createFollow, follow, user, currentUser, createNotification} = this.props;
34 | if (follow){
35 | deleteFollow(follow.id);
36 | } else {
37 | createFollow({leader_id: user.id, follower_id: currentUser.id});
38 | createNotification({initiator_id: currentUser.id, user_id: user.id});
39 | }
40 | }
41 |
42 | profileButton() {
43 | const {follow,currentUser,user, openModal} = this.props;
44 | const followButtonClass = this.determineClass();
45 | const followingButtonClass = this.determineFollowingClass();
46 | if (follow && currentUser.id !== user.id){
47 | return (
48 | this.updateFollow()} className={followingButtonClass}>Following
49 | );
50 | } else if (currentUser.id !== user.id) {
51 | return (
52 | this.updateFollow()} className={followButtonClass}>Follow
53 | );
54 | } else if (currentUser.id === user.id) {
55 | return (
56 | openModal({string:'editUser', user: currentUser})} className={followButtonClass}>Edit
57 | );
58 | }
59 | }
60 |
61 | determineFollowingClass() {
62 | const {user} = this.props;
63 | if (user.cover_url) {
64 | return "following";
65 | } else {
66 | return "following2";
67 | }
68 | }
69 |
70 | determineClass() {
71 | const {user} = this.props;
72 | if (user.cover_url) {
73 | return "follow";
74 | } else {
75 | return "follow2";
76 | }
77 | }
78 |
79 | userDescription() {
80 | const {user} = this.props;
81 | if (user.description){
82 | return (
83 | {user.description}
84 | );
85 | }
86 | }
87 |
88 | cover() {
89 | const {user} = this.props;
90 | if (user.cover_url){
91 | return(
92 |
93 |
94 |
95 | );
96 | } else {
97 | return (
98 |
99 | );
100 | }
101 | }
102 |
103 | render() {
104 | const {openModal, user, followers, following, parseUrl} = this.props;
105 | const profileUrl = user.profile_url || "https://s33.postimg.cc/nk3sgiaa7/default_profile.jpg";
106 | const pictures = this.props.pictures.map((picture) => {
107 | if (user.id === picture.uploader_id){
108 | return ;
109 | }
110 | });
111 | return (
112 |
113 | {this.profileButton()}
114 |
115 | {this.cover()}
116 |
117 |
118 |
119 | {user.username}
120 | {user.description}
121 | {followers} Followers {following} Following
122 |
123 |
124 | {pictures}
125 |
126 |
127 |
128 |
129 |
130 | );
131 |
132 |
133 | }
134 | }
135 |
136 | export default ProfilePage;
137 |
--------------------------------------------------------------------------------
/frontend/components/profile/user_photo_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import React from 'react';
3 | import UserPhotoForm from './user_photo_form';
4 | import { closeModal, openModal } from '../../actions/modal_actions';
5 | import { updateUser } from '../../actions/session_actions';
6 | import {fetchFollows, createFollow, deleteFollow} from '../../actions/follow_actions';
7 |
8 | const mapStateToProps = ({follows, session, entities:{users},ui:{modal}}, ownProps) => {
9 | return {
10 | string: modal.string,
11 | url: modal.picture.secure_url,
12 | currentUser: users[session.id]
13 | };
14 | };
15 | const mapDispatchToProps = () => dispatch => ({
16 | closeModal: () => dispatch(closeModal()),
17 | openModal: (object) => dispatch(openModal(object)),
18 | updateUser: (user) => dispatch(updateUser(user))
19 | });
20 |
21 | export default connect(mapStateToProps, mapDispatchToProps)(UserPhotoForm);
22 |
--------------------------------------------------------------------------------
/frontend/components/profile/user_photo_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class UserPhotoForm extends React.Component {
4 |
5 | constructor(props) {
6 | super(props);
7 | }
8 |
9 | title() {
10 | const {string} = this.props;
11 | if (string === 'uploadProfile'){
12 | return "Profile Picture";
13 | } else if (string === 'uploadCover'){
14 | return "Cover Photo";
15 | }
16 | }
17 |
18 | updateUser() {
19 | const {currentUser, string,url, updateUser, closeModal} = this.props;
20 | if (string === 'uploadCover') {
21 | updateUser({id: currentUser.id, username: currentUser.username, cover_url: url, profile_url: currentUser.profile_url});
22 | } else if (string === 'uploadProfile') {
23 | updateUser({id: currentUser.id, username: currentUser.username, cover_url: currentUser.cover_url, profile_url: url});
24 | }
25 | closeModal();
26 | }
27 |
28 | render() {
29 | const {url} = this.props;
30 | return (
31 |
32 |
this.props.closeModal()} className="fa fa-close upload-close">
33 |
{this.title()}
34 |
35 |
36 |
this.updateUser()} className= "modal-upload-button">Submit
37 |
38 | );
39 | }
40 |
41 | }
42 |
43 | export default UserPhotoForm;
44 |
--------------------------------------------------------------------------------
/frontend/components/root.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Provider } from 'react-redux';
3 | import { HashRouter } from 'react-router-dom';
4 | import App from './App';
5 |
6 | const Root = ({ store }) => (
7 |
8 |
9 |
10 |
11 |
12 | );
13 |
14 | export default Root;
15 |
--------------------------------------------------------------------------------
/frontend/components/session/login_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { login, clearErrors } from '../../actions/session_actions';
3 | import SessionForm from './session_form';
4 | import { Link } from 'react-router-dom';
5 | import React from 'react';
6 |
7 |
8 | const mapStateToProps = (state,ownProps) => ({
9 | errors: state.errors.session,
10 | formType: 'Log In',
11 | link: Sign up ,
12 | user: state.entities.users[state.session.id]
13 | });
14 | const mapDispatchToProps = () => dispatch => ({
15 | processForm: (user) => dispatch(login(user)),
16 | clear: () => dispatch(clearErrors())
17 | });
18 |
19 | export default connect(mapStateToProps, mapDispatchToProps)(SessionForm);
20 |
--------------------------------------------------------------------------------
/frontend/components/session/session_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter, Link } from 'react-router-dom';
3 | import NavContainer from '../nav/nav_container';
4 | import {Redirect} from 'react-router';
5 | import {Animated} from "react-animated-css";
6 |
7 | class SessionForm extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = {
11 | username: "",
12 | password: ""
13 | };
14 | this.handleSubmit = this.handleSubmit.bind(this);
15 | this.handleChange = this.handleChange.bind(this);
16 | }
17 |
18 | componentWillMount () {
19 | this.props.clear();
20 | }
21 |
22 |
23 |
24 | handleSubmit(e) {
25 | e.preventDefault();
26 | const username = this.state.username;
27 | const password = this.state.password;
28 | this.props.processForm(this.state);
29 | return (
30 |
31 | );
32 | }
33 |
34 | handleChange(field) {
35 | return (e) => {
36 | this.setState({[field]: e.target.value});
37 | };
38 | }
39 |
40 | listErrors() {
41 | if (this.props.errors.length > 0){
42 | return(
43 |
44 |
45 | {this.props.errors.map((error,i) => {
46 | return( {error} );
47 | }
48 | )}
49 |
50 |
51 | );
52 | }
53 | }
54 |
55 | demo(){
56 | const {formType} = this.props;
57 | if (formType === 'Log In'){
58 | return (
59 | this.props.processForm({username:"Felix",password:"password"})}>Demo Login
60 | );
61 | } else {
62 | return (
63 | this.props.login({username:"Felix",password:"password"})}>Demo Login
64 | )
65 | }
66 | }
67 |
68 | bottomLink() {
69 | const {formType} = this.props;
70 | if (formType === 'Log In'){
71 | return (
72 | Don't have an account? {this.props.link}
73 | );
74 | } else {
75 | return (
76 | Already have an account? {this.props.link}
77 | );
78 | }
79 | }
80 |
81 | render(){
82 |
83 | const {formType} = this.props;
84 | return (
85 |
86 |
87 |
88 | {this.listErrors()}
89 |
90 |
91 |
{formType}
92 |
93 |
103 |
104 | {this.demo()}
105 |
106 | {this.bottomLink()}
107 |
108 |
109 | );
110 | }
111 | }
112 |
113 | export default withRouter(SessionForm);
114 |
--------------------------------------------------------------------------------
/frontend/components/session/signup_form_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { signup, clearErrors, login } from '../../actions/session_actions';
3 | import SessionForm from './session_form';
4 | import { Link } from 'react-router-dom';
5 | import React from 'react';
6 |
7 | const mapStateToProps = (state,ownProps) => (
8 | {
9 | errors: state.errors.session,
10 | formType: 'Sign Up',
11 | link: Log In ,
12 | user: state.entities.users[state.session.id]
13 | }
14 | );
15 | const mapDispatchToProps = () => dispatch => ({
16 | processForm: (user) => dispatch(signup(user)),
17 | clear: () => dispatch(clearErrors()),
18 | login: (user) => dispatch(login(user))
19 | });
20 |
21 | export default connect(mapStateToProps, mapDispatchToProps)(SessionForm);
22 |
--------------------------------------------------------------------------------
/frontend/components/show/edit_picture_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import React from 'react';
3 | import PictureForm from '../upload/picture_form';
4 | import { closeModal } from '../../actions/modal_actions';
5 | import {updatePicture} from '../../actions/picture_actions';
6 |
7 | const mapStateToProps = ({session, entities:{users},ui:{modal}}) => (
8 | {
9 | currentUser: users[session.id],
10 | picture: modal.picture
11 | }
12 | );
13 | const mapDispatchToProps = () => dispatch => ({
14 | action: (picture) => dispatch(updatePicture(picture)),
15 | closeModal: () => dispatch(closeModal())
16 | });
17 |
18 | export default connect(mapStateToProps, mapDispatchToProps)(PictureForm);
19 |
--------------------------------------------------------------------------------
/frontend/components/show/show_picture.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import {Link} from 'react-router-dom';
3 | class ShowPictureComponent extends React.Component {
4 | constructor(props){
5 | super(props);
6 | this.handleDelete.bind(this);
7 | }
8 |
9 | componentDidMount(){
10 | document.addEventListener('keydown', (e) => {
11 | if (e.keyCode === 27) this.props.closeModal();
12 | });
13 | }
14 |
15 | nameLink() {
16 | if (this.props.picture.uploader_id === this.props.currentUser.id) {
17 | return "default-cursor";
18 | }
19 | }
20 |
21 | buttonDisplay() {
22 | if (this.props.currentUser.id === this.props.picture.uploader_id){
23 | return (
24 |
25 | this.handleEdit()} className="button1">Edit
26 | this.handleDelete()} className="button2">Delete
27 |
28 | );
29 | }
30 | }
31 |
32 | handleDelete() {
33 | this.props.deletePicture(this.props.picture.id);
34 | this.props.closeModal();
35 | }
36 |
37 | handleEdit() {
38 | this.props.closeModal();
39 | this.props.openModal({string:'edit', picture:this.props.picture});
40 | }
41 |
42 | updateFollow(){
43 | const {deleteFollow, createFollow, follow, user, currentUser, createNotification} = this.props;
44 | if (follow){
45 | deleteFollow(follow.id);
46 | } else {
47 | createFollow({leader_id: user.id, follower_id: currentUser.id});
48 | createNotification({initiator_id: currentUser.id, user_id: user.id});
49 | }
50 | }
51 |
52 | followButton() {
53 | const {follow,currentUser,user} = this.props;
54 | if (follow && currentUser.id !== user.id){
55 | return (
56 | this.updateFollow()} className="show-following">Following
57 | );
58 | } else if (currentUser.id !== user.id) {
59 | return (
60 | this.updateFollow()} className="show-follow">Follow
61 | );
62 | }
63 | }
64 |
65 | handleModalClose () {
66 |
67 | }
68 |
69 | render() {
70 | const {closeModal, picture, user} = this.props;
71 | const defaultProfile = this.props.user.profile_url || "https://res.cloudinary.com/dbm56y2y/image/upload/v1529040240/default_profile.jpg";
72 | return (
73 |
74 |
75 |
76 |
77 |
closeModal()} className={`show-link ${this.nameLink()}`} to={`/profile/${user.id}`} >
78 | closeModal()} className={`show-link ${this.nameLink()}`} to={`/profile/${user.id}`} >{user.username} {this.followButton()}
79 |
80 |
{picture.title}
81 |
82 | {this.buttonDisplay()}
83 |
84 |
85 |
86 |
87 | );
88 | }
89 | }
90 |
91 | export default ShowPictureComponent;
92 |
--------------------------------------------------------------------------------
/frontend/components/show/show_picture_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import React from 'react';
3 | import {findFollow} from '../../util/selectors';
4 | import ShowPictureComponent from './show_picture';
5 | import { closeModal, openModal } from '../../actions/modal_actions';
6 | import {deletePicture, updatePicture} from '../../actions/picture_actions';
7 | import {fetchFollows, createFollow, deleteFollow} from '../../actions/follow_actions';
8 | import {createNotification} from '../../util/notification_util';
9 |
10 | const mapStateToProps = ({follows, session, entities:{users},ui:{modal}}, ownProps) => {
11 | return {
12 | currentUser: users[session.id],
13 | picture: modal.picture,
14 | user: modal.user,
15 | follow: findFollow(follows,session.id, parseInt(modal.picture.uploader_id)),
16 | createNotification: createNotification
17 | };
18 | };
19 | const mapDispatchToProps = () => dispatch => ({
20 | closeModal: () => dispatch(closeModal()),
21 | deletePicture: (photoId) => dispatch(deletePicture(photoId)),
22 | updatePicture: (photo) => dispatch(updatePicture(photo)),
23 | openModal: (object) => dispatch(openModal(object)),
24 | fetchFollows: (follows) => dispatch(fetchFollows(follows)),
25 | createFollow: (follow) => dispatch(createFollow(follow)),
26 | deleteFollow: (id) => dispatch(deleteFollow(id))
27 |
28 | });
29 |
30 | export default connect(mapStateToProps, mapDispatchToProps)(ShowPictureComponent);
31 |
--------------------------------------------------------------------------------
/frontend/components/upload/picture_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | class PictureForm extends React.Component {
4 | constructor(props){
5 | super(props);
6 | const title = this.props.picture.title || "";
7 | this.state = {title:title};
8 | this.handleChange = this.handleChange.bind(this);
9 | this.handleSubmit = this.handleSubmit.bind(this);
10 | }
11 |
12 | handleChange() {
13 | return (e) => {
14 | this.setState({title:e.target.value});
15 | };
16 | }
17 |
18 | handleSubmit() {
19 |
20 | const {picture, currentUser, closeModal, action} = this.props;
21 | if (picture.url){
22 | action({title: this.state.title, image_url:picture.url}, currentUser.id);
23 | } else if (picture.image_url) {
24 | action({id: picture.id, title: this.state.title, image_url:picture.secure_url, uploader_id: picture.uploader_id});
25 | }
26 | closeModal();
27 | }
28 |
29 | render() {
30 | const url = this.props.picture.image_url || this.props.picture.url;
31 | return (
32 |
33 |
this.props.closeModal()} className="fa fa-close upload-close">
34 |
35 |
42 |
43 | );
44 | }
45 | }
46 | export default PictureForm;
47 |
--------------------------------------------------------------------------------
/frontend/components/upload/upload_picture_container.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import React from 'react';
3 | import PictureForm from './picture_form';
4 | import { closeModal } from '../../actions/modal_actions';
5 | import {uploadPicture} from '../../actions/picture_actions';
6 |
7 | const mapStateToProps = ({session, entities:{users},ui:{modal}}) => (
8 | {
9 | currentUser: users[session.id],
10 | picture: modal.picture
11 | }
12 | );
13 | const mapDispatchToProps = () => dispatch => ({
14 | action: (picture, userId) => dispatch(uploadPicture(picture, userId)),
15 | closeModal: () => dispatch(closeModal())
16 | });
17 |
18 | export default connect(mapStateToProps, mapDispatchToProps)(PictureForm);
19 |
--------------------------------------------------------------------------------
/frontend/index.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import configureStore from './store/store';
4 | import Root from './components/root';
5 | import * as APIUtil from './util/session_api_util';
6 | import * as thunkAction from './actions/session_actions';
7 | import * as APIPicture from './util/picture_api_util';
8 | import * as pictureAction from './actions/picture_actions';
9 | import * as followAction from './actions/follow_actions';
10 | import * as followUtil from './util/follow_util';
11 | import * as feedUtil from './util/feed_util';
12 | import * as NotificationUtil from './util/notification_util';
13 | import * as NotificationAction from './actions/notification_actions';
14 | document.addEventListener('DOMContentLoaded', () => {
15 | window.deleteFollow = followUtil.deleteFollow;
16 | window.makeFollow = followAction.createFollow;
17 | window.getFollows = followAction.fetchFollows;
18 | window.removeFollow = followAction.deleteFollow;
19 | window.homeFeed = feedUtil.homeFeed;
20 | window.home = pictureAction.homeFeed;
21 | window.fetchUsers = APIUtil.fetchUsers;
22 | window.receiveUsers = thunkAction.fetchUsers;
23 | window.updateUser = APIUtil.updateUser;
24 | window.updateUser = APIUtil.updateUser;
25 | window.updateCurrentUser = thunkAction.updateUser;
26 | window.fetchNotifications = NotificationUtil.fetchNotifications;
27 | window.createNotification = NotificationUtil.createNotification;
28 | window.deleteNotification = NotificationUtil.deleteNotification;
29 | window.getNotifications = NotificationAction.fetchNotifications;
30 | window.makeNotification = NotificationAction.createNotification;
31 | window.updateNotification = NotificationAction.updateNotification;
32 | let store;
33 | if (window.currentUser) {
34 | const preloadedState = {
35 | entities: {
36 | users: { [window.currentUser.id]: window.currentUser }
37 | },
38 | session: { id: window.currentUser.id }
39 | };
40 | store = configureStore(preloadedState);
41 | delete window.currentUser;
42 | } else {
43 | store = configureStore();
44 | }
45 | window.getState = store.getState;
46 | window.dispatch = store.dispatch;
47 |
48 | const root = document.getElementById('root');
49 | ReactDOM.render(, root);
50 | });
51 |
--------------------------------------------------------------------------------
/frontend/reducers/entities_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import usersReducer from './users_reducer';
3 | import picturesReducer from './pictures_reducer';
4 |
5 | const entitiesReducer = combineReducers({
6 | users: usersReducer,
7 | pictures: picturesReducer,
8 |
9 | });
10 |
11 | export default entitiesReducer;
12 |
--------------------------------------------------------------------------------
/frontend/reducers/errors_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 |
3 | import sessionErrorsReducer from './session_errors_reducer';
4 |
5 | const errorsReducer = combineReducers({
6 | session: sessionErrorsReducer
7 | });
8 |
9 | export default errorsReducer;
10 |
--------------------------------------------------------------------------------
/frontend/reducers/follows_reducer.js:
--------------------------------------------------------------------------------
1 | import {RECEIVE_FOLLOW, RECEIVE_FOLLOWS, REMOVE_FOLLOW} from '../actions/follow_actions';
2 | import merge from 'lodash/merge';
3 |
4 | const followsReducer = (initialState = {}, action) => {
5 | Object.freeze(initialState);
6 | switch (action.type){
7 | case RECEIVE_FOLLOW:
8 | return merge({}, initialState, {[action.follow.id]:action.follow});
9 | case RECEIVE_FOLLOWS:
10 | return merge({}, action.follows);
11 | case REMOVE_FOLLOW:
12 | const newState = merge({}, initialState);
13 | delete newState[action.follow.id];
14 | return newState;
15 | default:
16 | return initialState;
17 | }
18 | };
19 |
20 | export default followsReducer;
21 |
--------------------------------------------------------------------------------
/frontend/reducers/modal_reducer.js:
--------------------------------------------------------------------------------
1 | import { OPEN_MODAL, CLOSE_MODAL } from '../actions/modal_actions';
2 |
3 | export default function modalReducer(initialState = null, action) {
4 | Object.freeze(initialState);
5 | switch (action.type) {
6 | case OPEN_MODAL:
7 | return action.modal;
8 | case CLOSE_MODAL:
9 | return null;
10 | default:
11 | return initialState;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/frontend/reducers/notifications_reducer.js:
--------------------------------------------------------------------------------
1 | import {RECEIVE_NOTIFICATION, RECEIVE_NOTIFICATIONS} from '../actions/notification_actions';
2 | import merge from 'lodash/merge';
3 |
4 | const notificationsReducer = (initialState = {}, action) => {
5 | Object.freeze(initialState);
6 | switch (action.type){
7 | case RECEIVE_NOTIFICATION:
8 | return merge({}, initialState, {[action.notification.id]:action.notification});
9 | case RECEIVE_NOTIFICATIONS:
10 | return merge({}, action.notifications);
11 | default:
12 | return initialState;
13 | }
14 | };
15 |
16 | export default notificationsReducer;
17 |
--------------------------------------------------------------------------------
/frontend/reducers/pictures_reducer.js:
--------------------------------------------------------------------------------
1 | import {RECEIVE_PICTURE, RECEIVE_PICTURES, CLEAR_PICTURES, REMOVE_PICTURE} from '../actions/picture_actions';
2 | import merge from 'lodash/merge';
3 |
4 | const picturesReducer = (initialState = {}, action) => {
5 | Object.freeze(initialState);
6 | switch (action.type){
7 | case RECEIVE_PICTURE:
8 | return merge({}, initialState, {[action.picture.id]:action.picture});
9 | case RECEIVE_PICTURES:
10 | return merge({}, action.pictures);
11 | case CLEAR_PICTURES:
12 | return {};
13 | case REMOVE_PICTURE:
14 | const newState = merge({}, initialState);
15 | delete newState[action.picture.id];
16 | return newState;
17 | default:
18 | return initialState;
19 | }
20 | };
21 |
22 | export default picturesReducer;
23 |
--------------------------------------------------------------------------------
/frontend/reducers/root_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import errorsReducer from './errors_reducer';
3 | import sessionReducer from './session_reducer';
4 | import entitiesReducer from './entities_reducer';
5 | import followsReducer from './follows_reducer';
6 | import ui from './ui_reducer';
7 | import notificationsReducer from './notifications_reducer';
8 |
9 | const rootReducer = combineReducers({
10 | entities: entitiesReducer,
11 | follows: followsReducer,
12 | session: sessionReducer,
13 | ui: ui,
14 | errors: errorsReducer,
15 | notifications: notificationsReducer
16 | });
17 |
18 | export default rootReducer;
19 |
--------------------------------------------------------------------------------
/frontend/reducers/session_errors_reducer.js:
--------------------------------------------------------------------------------
1 | import {RECEIVE_CURRENT_USER,
2 | RECEIVE_ERRORS, CLEAR_ERRORS} from '../actions/session_actions.js';
3 |
4 | const sessionErrorsReducer = (initialState = [], action) => {
5 | Object.freeze(initialState);
6 | switch(action.type){
7 | case RECEIVE_CURRENT_USER:
8 | return [];
9 | case CLEAR_ERRORS:
10 | return [];
11 | case RECEIVE_ERRORS:
12 | return action.errors;
13 | default:
14 | return initialState;
15 | }
16 | };
17 |
18 | export default sessionErrorsReducer;
19 |
--------------------------------------------------------------------------------
/frontend/reducers/session_reducer.js:
--------------------------------------------------------------------------------
1 | import {RECEIVE_CURRENT_USER,
2 | LOGOUT_CURRENT_USER, REMOVE_USER} from '../actions/session_actions.js';
3 | import merge from 'lodash/merge';
4 |
5 | const defaultUser = Object.freeze({
6 | id: null
7 | });
8 |
9 | const sessionReducer = (state = defaultUser, action) => {
10 | Object.freeze(state);
11 | switch(action.type) {
12 | case RECEIVE_CURRENT_USER:
13 | return { id: action.currentUser.id };
14 | case LOGOUT_CURRENT_USER:
15 | return defaultUser;
16 | case REMOVE_USER:
17 | const newState = merge({}, state);
18 | delete newState[action.user.id];
19 | return newState;
20 | default:
21 | return state;
22 | }
23 | };
24 |
25 | export default sessionReducer;
26 |
--------------------------------------------------------------------------------
/frontend/reducers/ui_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import modal from './modal_reducer';
3 |
4 | export default combineReducers({
5 | modal
6 | });
7 |
--------------------------------------------------------------------------------
/frontend/reducers/users_reducer.js:
--------------------------------------------------------------------------------
1 | import {RECEIVE_CURRENT_USER, RECEIVE_USER, RECEIVE_USERS} from '../actions/session_actions.js';
2 | import merge from 'lodash/merge';
3 |
4 | const usersReducer = (initialState = {}, action) => {
5 | Object.freeze(initialState);
6 | switch(action.type){
7 | case RECEIVE_CURRENT_USER:
8 | return merge({}, initialState,
9 | {[action.currentUser.id]: action.currentUser});
10 | case RECEIVE_USER:
11 | return merge({}, initialState, {[action.user.id]:action.user});
12 | case RECEIVE_USERS:
13 | return merge({}, action.users);
14 | default:
15 | return initialState;
16 | }
17 | };
18 |
19 | export default usersReducer;
20 |
--------------------------------------------------------------------------------
/frontend/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | import thunk from 'redux-thunk';
3 | import rootReducer from '../reducers/root_reducer';
4 | // import logger from 'redux-logger'
5 |
6 | const configureStore = (preloadedState = {}) => (
7 | createStore(
8 | rootReducer,
9 | preloadedState,
10 | applyMiddleware(thunk)
11 | )
12 | );
13 |
14 | export default configureStore;
15 |
--------------------------------------------------------------------------------
/frontend/util/feed_util.js:
--------------------------------------------------------------------------------
1 | export const homeFeed = () => {
2 | return $.ajax({
3 | method: 'GET',
4 | url: '/api/feed'
5 | });
6 | };
7 |
8 | export const discoverFeed = () => {
9 | return $.ajax({
10 | method: 'GET',
11 | url: '/api/fresh'
12 | });
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/util/follow_util.js:
--------------------------------------------------------------------------------
1 | export const fetchFollows = () => {
2 | return $.ajax({
3 | method: 'GET',
4 | url: '/api/follows'
5 | });
6 | };
7 |
8 | export const createFollow = (follow) => {
9 | return $.ajax({
10 | method: 'POST',
11 | url: '/api/follows',
12 | data: {follow}
13 | });
14 | };
15 |
16 | export const deleteFollow = (id) => {
17 | return $.ajax({
18 | method: 'DELETE',
19 | url: `/api/follows/${id}`
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/frontend/util/notification_util.js:
--------------------------------------------------------------------------------
1 | export const fetchNotifications = () => {
2 | return $.ajax({
3 | method: 'GET',
4 | url: '/api/notifications'
5 | });
6 | };
7 |
8 | export const createNotification = (notification) => {
9 | return $.ajax({
10 | method: 'POST',
11 | url: '/api/notifications',
12 | data: {notification}
13 | });
14 | };
15 |
16 | export const deleteNotification = (id) => {
17 | return $.ajax({
18 | method: 'DELETE',
19 | url: `/api/notifications/${id}`
20 | });
21 | };
22 |
23 | export const updateNotification = (notification) => {
24 | return $.ajax({
25 | method: 'PATCH',
26 | url: `/api/notifications/${notification.id}`,
27 | data: {notification}
28 | });
29 | };
30 |
--------------------------------------------------------------------------------
/frontend/util/picture_api_util.js:
--------------------------------------------------------------------------------
1 | export const uploadPicture = (picture, userId) => {
2 | return $.ajax({
3 | method: 'POST',
4 | url: `/api/users/${userId}/pictures`,
5 | data: {picture}
6 | });
7 | };
8 |
9 | export const fetchUserPictures = (userId) => {
10 | return $.ajax({
11 | method: 'GET',
12 | url: `/api/users/${userId}/pictures`
13 | });
14 | };
15 |
16 | export const fetchPicture = (photoId) => {
17 | return $.ajax({
18 | method: 'GET',
19 | url: `/api/pictures/${photoId}`
20 | });
21 | };
22 |
23 | export const updatePicture = (picture) => {
24 | return $.ajax({
25 | method: 'PATCH',
26 | url: `/api/pictures/${picture.id}`,
27 | data: {picture}
28 | });
29 | };
30 |
31 | export const deletePicture = (photoId) => {
32 | return $.ajax({
33 | method: 'DELETE',
34 | url: `/api/pictures/${photoId}`
35 | });
36 | };
37 |
--------------------------------------------------------------------------------
/frontend/util/route_util.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { withRouter, Route, Redirect } from 'react-router-dom';
3 | import React from 'react';
4 |
5 | const Auth = ({component: Component, path, loggedIn, exact}) => (
6 | (
7 | !loggedIn ? (
8 |
9 | ) : (
10 |
11 | )
12 | )}/>
13 | );
14 |
15 | const mapStateToProps = state => {
16 | return {loggedIn: Boolean(state.session.id)};
17 | };
18 |
19 | const LogAuth = ({component: Component, path, loggedIn, exact}) => (
20 | (
21 | loggedIn ? (
22 |
23 | ) : (
24 |
25 | )
26 | )}/>
27 | );
28 |
29 | export const AuthRoute = withRouter(connect(mapStateToProps, null)(Auth));
30 | export const LogRoute = withRouter(connect(mapStateToProps, null)(LogAuth));
31 |
--------------------------------------------------------------------------------
/frontend/util/selectors.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | export const pictureList = (pictures) => {
4 | return Object.values(pictures).reverse();
5 | };
6 |
7 | export const pictureListFeed = (pictures) => {
8 | return Object.values(pictures).reverse();
9 | };
10 |
11 | export const findFollow = (follows, currentUserId, profileUserId) => {
12 | const followArr = Object.values(follows);
13 | for (var i = 0; i < followArr.length; i++) {
14 | const follow = followArr[i];
15 | if (follow.follower_id === currentUserId && follow.leader_id === profileUserId) {
16 | return follow;
17 | }
18 | }
19 | return null;
20 | };
21 |
22 | export const getFollowers = (follows, id) => {
23 | const followArr = Object.values(follows);
24 | let followers = 0;
25 | for (var i = 0; i < followArr.length; i++) {
26 | if(followArr[i].leader_id === id){
27 | followers += 1;
28 | }
29 | }
30 | return followers;
31 | };
32 |
33 | export const getFollowings = (follows, id) => {
34 | const followArr = Object.values(follows);
35 | let following = 0;
36 | for (var i = 0; i < followArr.length; i++) {
37 | if(followArr[i].follower_id === id){
38 | following += 1;
39 | }
40 | }
41 | return following;
42 | };
43 |
44 | export const parseUrl = (url) => {
45 | const newUrl = 'https://res.cloudinary.com/dbm56y2y/image/upload/c_scale,h_500/';
46 | return newUrl.concat(url.slice(48));
47 | };
48 |
49 | export const parseUrlBig = (url) => {
50 | const newUrl = 'https://res.cloudinary.com/dbm56y2y/image/upload/c_scale,w_1000/';
51 | return newUrl.concat(url.slice(48));
52 | };
53 |
54 | export const newNotifications = (notifications) => {
55 | const array = Object.values(notifications);
56 | if (array.length === 0) return [];
57 | let ids = [];
58 | let newArray = [];
59 | array.forEach((n) => {
60 | if (n.viewed === false && !ids.includes(n.initiator_id)){
61 | ids.push(n.initiator_id);
62 | newArray.push(n);
63 | }
64 | });
65 | return newArray.reverse();
66 | };
67 |
68 | export const notifyArray = (notifications) => {
69 | return Object.values(notifications).reverse();
70 |
71 | };
72 |
--------------------------------------------------------------------------------
/frontend/util/session_api_util.js:
--------------------------------------------------------------------------------
1 | export const signup = (user) => {
2 | return $.ajax({
3 | method: 'POST',
4 | url: '/api/users',
5 | data: {user}
6 | });
7 | };
8 |
9 | export const login = (user) => {
10 | return $.ajax({
11 | method: 'POST',
12 | url: '/api/session',
13 | data: {user}
14 | });
15 | };
16 |
17 | export const logout = () => {
18 | return $.ajax({
19 | method: 'DELETE',
20 | url: '/api/session'
21 | });
22 | };
23 |
24 | export const fetchUser = (id) => {
25 | return $.ajax({
26 | method: 'GET',
27 | url: `api/users/${id}`
28 | });
29 | };
30 |
31 | export const fetchUsers = () => {
32 | return $.ajax({
33 | method: 'GET',
34 | url: 'api/users'
35 | });
36 | };
37 |
38 | export const updateUser = (user) => {
39 | return $.ajax({
40 | method: 'PATCH',
41 | url: `/api/users/${user.id}`,
42 | data: {user}
43 | });
44 | };
45 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/lib/tasks/.keep
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "engines": {
3 | "node": "8.9.4",
4 | "npm": "6.0.1"
5 | },
6 | "scripts": {
7 | "webpack": "webpack --watch -d",
8 | "postinstall": "webpack --optimize-minimize"
9 | },
10 | "name": "ThreeSixtyPx",
11 | "private": true,
12 | "dependencies": {
13 | "babel-core": "^6.26.3",
14 | "babel-loader": "^7.1.4",
15 | "babel-preset-env": "^1.7.0",
16 | "babel-preset-react": "^6.24.1",
17 | "link-react": "^3.0.0",
18 | "lodash": "^4.17.10",
19 | "react": "^16.4.0",
20 | "react-addons-css-transition-group": "^15.6.2",
21 | "react-animated-css": "^1.0.4",
22 | "react-dom": "^16.4.0",
23 | "react-redux": "^5.0.7",
24 | "react-router-dom": "^4.2.2",
25 | "react-slick": "^0.23.1",
26 | "redux": "^4.0.0",
27 | "redux-logger": "^3.0.6",
28 | "redux-thunk": "^2.3.0",
29 | "slick-carousel": "^1.8.1",
30 | "webpack": "^4.11.0",
31 | "webpack-cli": "^3.0.2"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The page you were looking for doesn't exist (404)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The page you were looking for doesn't exist.
62 |
You may have mistyped the address or the page may have moved.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/422.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | The change you wanted was rejected (422)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
The change you wanted was rejected.
62 |
Maybe you tried to change something you didn't have access to.
63 |
64 |
If you are the application owner check the logs for more information.
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/public/500.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | We're sorry, but something went wrong (500)
5 |
6 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
We're sorry, but something went wrong.
62 |
63 |
If you are the application owner check the logs for more information.
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/public/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/public/favicon.ico
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 |
--------------------------------------------------------------------------------
/test/application_system_test_case.rb:
--------------------------------------------------------------------------------
1 | require "test_helper"
2 |
3 | class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
4 | driven_by :selenium, using: :chrome, screen_size: [1400, 1400]
5 | end
6 |
--------------------------------------------------------------------------------
/test/controllers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/test/controllers/.keep
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/test/fixtures/.keep
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/fixtures/follows.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: follows
4 | #
5 | # id :bigint(8) not null, primary key
6 | # leader_id :integer not null
7 | # follower_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
13 |
14 | one:
15 | leader_id: 1
16 | follower_id: 1
17 |
18 | two:
19 | leader_id: 1
20 | follower_id: 1
21 |
--------------------------------------------------------------------------------
/test/fixtures/notifications.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notifications
4 | #
5 | # id :bigint(8) not null, primary key
6 | # user_id :integer not null
7 | # initiator_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # viewed :boolean
11 | #
12 |
13 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
14 |
15 | one:
16 | user_id: 1
17 | initiator_id: 1
18 |
19 | two:
20 | user_id: 1
21 | initiator_id: 1
22 |
--------------------------------------------------------------------------------
/test/fixtures/pictures.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: pictures
4 | #
5 | # id :bigint(8) not null, primary key
6 | # title :string
7 | # image_url :string not null
8 | # uploader_id :integer not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 |
13 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
14 |
15 | one:
16 | id: 1
17 | title: MyString
18 | image_url: MyString
19 | uploader_id: 1
20 |
21 | two:
22 | id: 1
23 | title: MyString
24 | image_url: MyString
25 | uploader_id: 1
26 |
--------------------------------------------------------------------------------
/test/fixtures/users.yml:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :bigint(8) not null, primary key
6 | # username :string not null
7 | # password_digest :string not null
8 | # session_token :string not null
9 | # description :string
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | # profile_url :string
13 | # cover_url :string
14 | #
15 |
16 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html
17 |
18 | one:
19 | username: MyString
20 | password_digest: MyString
21 | session_token: MyString
22 | profile_url: MyString
23 | cover_url: MyString
24 | description: MyString
25 |
26 | two:
27 | username: MyString
28 | password_digest: MyString
29 | session_token: MyString
30 | profile_url: MyString
31 | cover_url: MyString
32 | description: MyString
33 |
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/test/integration/.keep
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/test/mailers/.keep
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/test/models/.keep
--------------------------------------------------------------------------------
/test/models/follow_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: follows
4 | #
5 | # id :bigint(8) not null, primary key
6 | # leader_id :integer not null
7 | # follower_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | require 'test_helper'
13 |
14 | class FollowTest < ActiveSupport::TestCase
15 | # test "the truth" do
16 | # assert true
17 | # end
18 | end
19 |
--------------------------------------------------------------------------------
/test/models/notification_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: notifications
4 | #
5 | # id :bigint(8) not null, primary key
6 | # user_id :integer not null
7 | # initiator_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # viewed :boolean
11 | #
12 |
13 | require 'test_helper'
14 |
15 | class NotificationTest < ActiveSupport::TestCase
16 | # test "the truth" do
17 | # assert true
18 | # end
19 | end
20 |
--------------------------------------------------------------------------------
/test/models/picture_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: pictures
4 | #
5 | # id :bigint(8) not null, primary key
6 | # title :string
7 | # image_url :string not null
8 | # uploader_id :integer not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 |
13 | require 'test_helper'
14 |
15 | class PictureTest < ActiveSupport::TestCase
16 | # test "the truth" do
17 | # assert true
18 | # end
19 | end
20 |
--------------------------------------------------------------------------------
/test/models/user_test.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: users
4 | #
5 | # id :bigint(8) not null, primary key
6 | # username :string not null
7 | # password_digest :string not null
8 | # session_token :string not null
9 | # description :string
10 | # created_at :datetime not null
11 | # updated_at :datetime not null
12 | # profile_url :string
13 | # cover_url :string
14 | #
15 |
16 | require 'test_helper'
17 |
18 | class UserTest < ActiveSupport::TestCase
19 | # test "the truth" do
20 | # assert true
21 | # end
22 | end
23 |
--------------------------------------------------------------------------------
/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/test/system/.keep
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/FelixAlvarado/360px/5d6879e71e00c2a32ef7e151f0c72809f3e192bb/vendor/.keep
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require("path");
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: "./frontend/index.jsx",
6 | output: {
7 | path: path.resolve(__dirname, 'app', 'assets', 'javascripts'),
8 | filename: "bundle.js"
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: [/\.jsx?$/, /\.js?$/],
14 | exclude: /node_modules/,
15 | loader: 'babel-loader',
16 | query: {
17 | presets: ['env', 'react']
18 | }
19 | }
20 | ]
21 | },
22 | devtool: 'source-map',
23 | resolve: {
24 | extensions: [".js", ".jsx", "*"]
25 | }
26 | };
27 |
--------------------------------------------------------------------------------