├── .gitignore
├── .ruby-version
├── Gemfile
├── Gemfile.lock
├── README.md
├── Rakefile
├── app
├── assets
│ ├── config
│ │ └── manifest.js
│ ├── images
│ │ ├── .keep
│ │ ├── login
│ │ │ ├── login1.woff
│ │ │ ├── login2.woff
│ │ │ ├── login3.woff
│ │ │ └── login4.woff
│ │ ├── main
│ │ │ ├── add-file.svg
│ │ │ ├── blue.png
│ │ │ ├── check.svg
│ │ │ ├── clear.png
│ │ │ ├── create-server.png
│ │ │ ├── empty-left.png
│ │ │ ├── empty-right.png
│ │ │ ├── exclamation.svg
│ │ │ ├── font
│ │ │ │ ├── fat.woff
│ │ │ │ ├── fatter.woff
│ │ │ │ ├── middle.woff
│ │ │ │ ├── thin.woff
│ │ │ │ └── thinner.woff
│ │ │ ├── friend-x.svg
│ │ │ ├── friendless.svg
│ │ │ ├── hangup.svg
│ │ │ ├── headphones.svg
│ │ │ ├── join-server.png
│ │ │ ├── long logo.svg
│ │ │ ├── online-empty.svg
│ │ │ ├── or.svg
│ │ │ ├── pending-empty.svg
│ │ │ ├── personal.svg
│ │ │ ├── search.svg
│ │ │ ├── server-modal-left.png
│ │ │ ├── server-modal-right.png
│ │ │ ├── welcome-comp.svg
│ │ │ ├── welcome-phone.svg
│ │ │ ├── white_logo.png
│ │ │ ├── x-button.svg
│ │ │ └── x-user.svg
│ │ └── splash
│ │ │ ├── discor-font.woff
│ │ │ ├── discors-font-bold.woff
│ │ │ └── p-font.woff
│ ├── javascripts
│ │ ├── application.js
│ │ ├── cable.js
│ │ └── channels
│ │ │ ├── .keep
│ │ │ ├── chat.coffee
│ │ │ └── voice.coffee
│ └── stylesheets
│ │ ├── application.css
│ │ ├── channel_modal.scss
│ │ ├── chrome.scss
│ │ ├── css_reset.css
│ │ ├── edge.scss
│ │ ├── edit_user.scss
│ │ ├── firefox.scss
│ │ ├── friends.scss
│ │ ├── loading.scss
│ │ ├── login.scss
│ │ ├── main.scss
│ │ ├── messages.scss
│ │ ├── server_modal.scss
│ │ ├── spash.scss
│ │ └── user_popup.scss
├── channels
│ ├── application_cable
│ │ ├── channel.rb
│ │ └── connection.rb
│ ├── chat_channel.rb
│ ├── notifications_channel.rb
│ └── voice_channel.rb
├── controllers
│ ├── api
│ │ ├── audio_channels_controller.rb
│ │ ├── channels_controller.rb
│ │ ├── dm_channel_memberships_controller.rb
│ │ ├── friend_requests_controller.rb
│ │ ├── friends_controller.rb
│ │ ├── messages_controller.rb
│ │ ├── servers_controller.rb
│ │ ├── sessions_controller.rb
│ │ └── users_controller.rb
│ ├── application_controller.rb
│ ├── concerns
│ │ └── .keep
│ └── static_pages_controller.rb
├── jobs
│ ├── application_job.rb
│ ├── friend_request_broadcast_job.rb
│ ├── friendship_broadcast_job.rb
│ └── notification_broadcast_job.rb
├── mailers
│ └── application_mailer.rb
├── models
│ ├── application_record.rb
│ ├── audio_channel.rb
│ ├── channel.rb
│ ├── concerns
│ │ └── .keep
│ ├── dm_channel_membership.rb
│ ├── friend_request.rb
│ ├── friendship.rb
│ ├── message.rb
│ ├── server.rb
│ ├── server_membership.rb
│ ├── session.rb
│ └── user.rb
└── views
│ ├── api
│ ├── audio_channels
│ │ ├── _audio_channel.json.jbuilder
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── channels
│ │ ├── _channel.json.jbuilder
│ │ ├── dm_index.json.jbuilder
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── friend_requests
│ │ ├── _friend_request.json.jbuilder
│ │ └── show.json.jbuilder
│ ├── messages
│ │ └── _message.json.jbuilder
│ ├── servers
│ │ ├── _server.json.jbuilder
│ │ ├── index.json.jbuilder
│ │ └── show.json.jbuilder
│ └── users
│ │ ├── _user.json.jbuilder
│ │ ├── index.json.jbuilder
│ │ ├── show.json.jbuilder
│ │ └── user_data.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
├── credentials.yml.enc
├── database.yml
├── environment.rb
├── environments
│ ├── development.rb
│ ├── production.rb
│ └── test.rb
├── initializers
│ ├── application_controller_renderer.rb
│ ├── assets.rb
│ ├── backtrace_silencers.rb
│ ├── content_security_policy.rb
│ ├── cookies_serializer.rb
│ ├── filter_parameter_logging.rb
│ ├── inflections.rb
│ ├── mime_types.rb
│ └── wrap_parameters.rb
├── locales
│ └── en.yml
├── puma.rb
├── routes.rb
├── spring.rb
└── storage.yml
├── db
├── migrate
│ ├── 20190121174348_create_users.rb
│ ├── 20190122185229_create_servers.rb
│ ├── 20190122190214_create_server_memberships.rb
│ ├── 20190123145743_create_channels.rb
│ ├── 20190124162319_create_messages.rb
│ ├── 20190125020020_create_dm_channel_memberships.rb
│ ├── 20190126030608_create_friend_requests.rb
│ ├── 20190126032316_create_friendships.rb
│ ├── 20190127215251_create_sessions.rb
│ ├── 20190129004713_create_voice_channels.rb
│ ├── 20190129010218_change_voice_channels_to_audio_channels.rb
│ ├── 20190129201658_create_active_storage_tables.active_storage.rb
│ ├── 20190203031645_add_column_to_sessions.rb
│ └── 20190218200301_remove_session_token_from_users.rb
├── schema.rb
└── seeds.rb
├── frontend
├── actions
│ ├── channel_actions.js
│ ├── friends_actions.js
│ ├── notification_actions.js
│ ├── server_actions.js
│ ├── session_actions.js
│ ├── ui_actions.js
│ └── voice_channel_actions.js
├── components
│ ├── app.jsx
│ ├── app
│ │ ├── app_root.jsx
│ │ ├── app_routes
│ │ │ ├── friends_main.jsx
│ │ │ ├── me_main.jsx
│ │ │ ├── me_route.jsx
│ │ │ ├── server_main.jsx
│ │ │ └── server_route.jsx
│ │ ├── channels
│ │ │ ├── channels
│ │ │ │ ├── channel.jsx
│ │ │ │ ├── channels.jsx
│ │ │ │ └── channels_container.jsx
│ │ │ ├── create_channel_form.jsx
│ │ │ ├── dm_channels
│ │ │ │ ├── dm_channel.jsx
│ │ │ │ ├── dm_channels.jsx
│ │ │ │ └── dm_channels_container.jsx
│ │ │ ├── edit_user_form.jsx
│ │ │ ├── user_bar
│ │ │ │ ├── user_bar.jsx
│ │ │ │ └── user_bar_container.jsx
│ │ │ └── voice_channels
│ │ │ │ ├── voice_channel.jsx
│ │ │ │ ├── voice_channels.jsx
│ │ │ │ └── voice_channels_container.jsx
│ │ ├── chat
│ │ │ ├── chat
│ │ │ │ ├── chat.jsx
│ │ │ │ └── chat_container.jsx
│ │ │ ├── message.jsx
│ │ │ └── message_form.jsx
│ │ ├── friends
│ │ │ ├── friend.jsx
│ │ │ ├── friends_header.jsx
│ │ │ ├── friends_list
│ │ │ │ ├── friends.jsx
│ │ │ │ ├── friends_container.jsx
│ │ │ │ └── online_friends_container.jsx
│ │ │ ├── mutual_server.jsx
│ │ │ └── pending_friends
│ │ │ │ ├── pending_friends.jsx
│ │ │ │ └── pending_friends_container.jsx
│ │ ├── header
│ │ │ ├── header
│ │ │ │ ├── header.jsx
│ │ │ │ └── header_container.jsx
│ │ │ └── me_header
│ │ │ │ ├── me_header.jsx
│ │ │ │ └── me_header_container.jsx
│ │ ├── loading_screen.jsx
│ │ ├── modal
│ │ │ ├── tooltip.jsx
│ │ │ ├── user_popup.jsx
│ │ │ └── user_popup_container.jsx
│ │ ├── server_members
│ │ │ ├── server_member.jsx
│ │ │ └── server_members
│ │ │ │ ├── server_members.jsx
│ │ │ │ └── server_members_container.jsx
│ │ └── servers
│ │ │ ├── create_server_form.jsx
│ │ │ ├── dm_notification.jsx
│ │ │ ├── join_server_form.jsx
│ │ │ ├── search_server_item.jsx
│ │ │ ├── search_servers_form.jsx
│ │ │ ├── server.jsx
│ │ │ └── servers
│ │ │ ├── servers.jsx
│ │ │ └── servers_container.jsx
│ ├── root.jsx
│ ├── session_form
│ │ ├── login
│ │ │ ├── login_form.jsx
│ │ │ └── login_form_container.jsx
│ │ └── signup
│ │ │ ├── signup_form.jsx
│ │ │ └── signup_form_container.jsx
│ └── splash
│ │ ├── splash.jsx
│ │ └── splash_container.jsx
├── discors.jsx
├── reducers
│ ├── entities
│ │ ├── channels_reducer.js
│ │ ├── entities_reducer.js
│ │ ├── friend_requests_reducer.js
│ │ ├── friends_reducer.js
│ │ ├── servers_reducer.js
│ │ ├── users_reducer.js
│ │ └── voice_channels_reducer.js
│ ├── errors
│ │ ├── channel_errors_reducer.js
│ │ ├── errors_reducer.js
│ │ ├── server_errors_reducer.js
│ │ └── session_errors_reducer.js
│ ├── loading_reducer.js
│ ├── notifications
│ │ ├── dm_notifications_reducer.js
│ │ └── notifications_reducer.js
│ ├── root_reducer.js
│ ├── session_reducer.js
│ └── ui_reducer.js
├── store
│ └── store.js
└── util
│ ├── channel_api_util.js
│ ├── friends_api_util.js
│ ├── route_util.jsx
│ ├── server_api_utl.js
│ ├── session_api_util.js
│ └── voice_channel_api_util.js
├── helpers
└── application_helper.rb
├── lib
├── assets
│ └── .keep
└── tasks
│ ├── .keep
│ └── sessions.rake
├── log
└── .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
├── storage
└── .keep
├── test
├── application_system_test_case.rb
├── controllers
│ └── .keep
├── fixtures
│ ├── .keep
│ └── files
│ │ └── .keep
├── helpers
│ └── .keep
├── integration
│ └── .keep
├── mailers
│ └── .keep
├── models
│ └── .keep
├── system
│ └── .keep
└── test_helper.rb
├── tmp
└── .keep
├── vendor
└── .keep
└── webpack.config.js
/.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 | # Ignore uploaded files in development
17 | /storage/*
18 | !/storage/.keep
19 |
20 | /node_modules
21 | /yarn-error.log
22 |
23 | /public/assets
24 | .byebug_history
25 |
26 | # Ignore master key for decrypting credentials and more.
27 | /config/master.key
28 |
29 | node_modules/
30 | bundle.js
31 | bundle.js*
32 | bundle.js.map
33 | .byebug_history
34 | .DS_Store
35 | npm-debug.log
--------------------------------------------------------------------------------
/.ruby-version:
--------------------------------------------------------------------------------
1 | 2.5.1
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 | git_source(:github) { |repo| "https://github.com/#{repo}.git" }
3 |
4 | ruby '2.5.1'
5 |
6 | # Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
7 | gem 'rails', '~> 5.2.2'
8 | # Use postgresql as the database for Active Record
9 | gem 'pg', '>= 0.18', '< 2.0'
10 | # Use Puma as the app server
11 | gem 'puma', '~> 3.11'
12 | # Use SCSS for stylesheets
13 | gem 'sass-rails', '~> 5.0'
14 | # Use Uglifier as compressor for JavaScript assets
15 | gem 'uglifier', '>= 1.3.0'
16 | # See https://github.com/rails/execjs#readme for more supported runtimes
17 | # gem 'mini_racer', platforms: :ruby
18 |
19 | # Use CoffeeScript for .coffee assets and views
20 | gem 'coffee-rails', '~> 4.2'
21 | # Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder
22 | gem 'jbuilder', '~> 2.5'
23 | # Use Redis adapter to run Action Cable in production
24 | gem 'redis', '~> 4.0'
25 | # Use ActiveModel has_secure_password
26 | gem 'bcrypt', '~> 3.1.7'
27 | gem 'ez_download'
28 | # Use ActiveStorage variant
29 | # gem 'mini_magick', '~> 4.8'
30 |
31 | # Use Capistrano for deployment
32 | # gem 'capistrano-rails', group: :development
33 |
34 | # Reduces boot times through caching; required in config/boot.rb
35 | gem 'bootsnap', '>= 1.1.0', require: false
36 | gem "aws-sdk-s3"
37 | gem 'jquery-rails'
38 |
39 | group :development, :test do
40 | # Call 'byebug' anywhere in the code to stop execution and get a debugger console
41 | gem 'byebug', platforms: [:mri, :mingw, :x64_mingw]
42 | end
43 |
44 | group :development do
45 | # Access an interactive console on exception pages or by calling 'console' anywhere in the code.
46 | gem 'web-console', '>= 3.3.0'
47 | gem 'listen', '>= 3.0.5', '< 3.2'
48 | # Spring speeds up development by keeping your application running in the background. Read more: https://github.com/rails/spring
49 | gem 'spring'
50 | gem 'spring-watcher-listen', '~> 2.0.0'
51 | gem 'better_errors'
52 | gem 'binding_of_caller'
53 | gem 'pry-rails'
54 | gem 'annotate'
55 | end
56 |
57 | group :test do
58 | # Adds support for Capybara system testing and selenium driver
59 | gem 'capybara', '>= 2.15'
60 | gem 'selenium-webdriver'
61 | # Easy installation and use of chromedriver to run system tests with Chrome
62 | gem 'chromedriver-helper'
63 | end
64 |
65 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem
66 | gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]
67 |
--------------------------------------------------------------------------------
/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 |
5 |
6 |
--------------------------------------------------------------------------------
/app/assets/images/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/.keep
--------------------------------------------------------------------------------
/app/assets/images/login/login1.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/login/login1.woff
--------------------------------------------------------------------------------
/app/assets/images/login/login2.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/login/login2.woff
--------------------------------------------------------------------------------
/app/assets/images/login/login3.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/login/login3.woff
--------------------------------------------------------------------------------
/app/assets/images/login/login4.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/login/login4.woff
--------------------------------------------------------------------------------
/app/assets/images/main/add-file.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/assets/images/main/blue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/blue.png
--------------------------------------------------------------------------------
/app/assets/images/main/check.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/main/clear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/clear.png
--------------------------------------------------------------------------------
/app/assets/images/main/create-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/create-server.png
--------------------------------------------------------------------------------
/app/assets/images/main/empty-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/empty-left.png
--------------------------------------------------------------------------------
/app/assets/images/main/empty-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/empty-right.png
--------------------------------------------------------------------------------
/app/assets/images/main/exclamation.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
--------------------------------------------------------------------------------
/app/assets/images/main/font/fat.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/font/fat.woff
--------------------------------------------------------------------------------
/app/assets/images/main/font/fatter.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/font/fatter.woff
--------------------------------------------------------------------------------
/app/assets/images/main/font/middle.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/font/middle.woff
--------------------------------------------------------------------------------
/app/assets/images/main/font/thin.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/font/thin.woff
--------------------------------------------------------------------------------
/app/assets/images/main/font/thinner.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/font/thinner.woff
--------------------------------------------------------------------------------
/app/assets/images/main/friend-x.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/main/hangup.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/assets/images/main/headphones.svg:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/app/assets/images/main/join-server.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/join-server.png
--------------------------------------------------------------------------------
/app/assets/images/main/long logo.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
31 |
--------------------------------------------------------------------------------
/app/assets/images/main/or.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
22 |
--------------------------------------------------------------------------------
/app/assets/images/main/personal.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/app/assets/images/main/server-modal-left.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/server-modal-left.png
--------------------------------------------------------------------------------
/app/assets/images/main/server-modal-right.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/server-modal-right.png
--------------------------------------------------------------------------------
/app/assets/images/main/welcome-comp.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
14 |
--------------------------------------------------------------------------------
/app/assets/images/main/welcome-phone.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/assets/images/main/white_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/main/white_logo.png
--------------------------------------------------------------------------------
/app/assets/images/main/x-button.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/main/x-user.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/app/assets/images/splash/discor-font.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/splash/discor-font.woff
--------------------------------------------------------------------------------
/app/assets/images/splash/discors-font-bold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/splash/discors-font-bold.woff
--------------------------------------------------------------------------------
/app/assets/images/splash/p-font.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/images/splash/p-font.woff
--------------------------------------------------------------------------------
/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 rails-ujs
14 | //= require activestorage
15 | //= require jquery
16 | //= require jquery_ujs
17 | //= require_tree .
18 |
--------------------------------------------------------------------------------
/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/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/assets/javascripts/channels/.keep
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/chat.coffee:
--------------------------------------------------------------------------------
1 | # App.chat = App.cable.subscriptions.create "ChatChannel",
2 | # connected: ->
3 | # # Called when the subscription is ready for use on the server
4 |
5 | # disconnected: ->
6 | # # Called when the subscription has been terminated by the server
7 |
8 | # received: (data) ->
9 | # # Called when there's incoming data on the websocket for this channel
10 |
--------------------------------------------------------------------------------
/app/assets/javascripts/channels/voice.coffee:
--------------------------------------------------------------------------------
1 | # App.voice = App.cable.subscriptions.create "VoiceChannel",
2 | # connected: ->
3 | # # Called when the subscription is ready for use on the server
4 |
5 | # disconnected: ->
6 | # # Called when the subscription has been terminated by the server
7 |
8 | # received: (data) ->
9 | # # Called when there's incoming data on the websocket for this channel
10 |
--------------------------------------------------------------------------------
/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/channel_modal.scss:
--------------------------------------------------------------------------------
1 | .create-channel-form {
2 | user-select: none;
3 | box-sizing: border-box;
4 | }
5 |
6 | .create-channel-form header {
7 | padding: 20px;
8 | }
9 | .create-channel-form main {
10 | margin-top: 18px;
11 | margin-bottom: 66px;
12 | padding: 20px;
13 | }
14 |
15 | .create-channel-form h4 {
16 | color: #f6f6f7;
17 | font-weight: 600;
18 | line-height: 20px;
19 | letter-spacing: .3px;
20 | font-family: main4;
21 | }
22 |
23 | .create-channel-form p {
24 | color: #b9bbbe;
25 | line-height: 16px;
26 | font-size: 12px;
27 | font-family: main2;
28 | margin-bottom: 20px;
29 | }
30 |
31 | .create-channel-form-bottom {
32 | background-color: #2f3136;
33 | box-shadow: inset 0 1px 0 rgba(47,49,54,.6);
34 | padding: 20px;
35 | height: 78px;
36 | display: flex;
37 | justify-content: flex-end;
38 | align-items: center;
39 | box-sizing: border-box;
40 | }
41 |
42 | .create-channel-form #session-submit {
43 | width: 123px;
44 | margin-bottom: 0;
45 | }
46 |
47 | .create-channel-cancel {
48 | font-size: 14px;
49 | font-weight: 500;
50 | justify-content: center;
51 | line-height: 16px;
52 | padding: 2px 16px;
53 | height: 38px;
54 | color: #fff;
55 | font-family: main2;
56 | }
57 |
58 | .create-channel-cancel:hover {
59 | text-decoration: underline;
60 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/chrome.scss:
--------------------------------------------------------------------------------
1 | @media screen and (-webkit-min-device-pixel-ratio:0) and (min-resolution:.001dpcm) {
2 | .chat-main {
3 |
4 | }
5 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/css_reset.css:
--------------------------------------------------------------------------------
1 | html, body, div, span, applet, object, iframe,
2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre,
3 | a, abbr, acronym, address, big, cite, code,
4 | del, dfn, em, img, ins, kbd, q, s, samp,
5 | small, strike, strong, sub, sup, tt, var,
6 | b, u, i, center,
7 | dl, dt, dd, ol, ul, li,
8 | fieldset, form, label, legend,
9 | table, caption, tbody, tfoot, thead, tr, th, td,
10 | article, aside, canvas, details, embed,
11 | figure, figcaption, footer, header, hgroup,
12 | menu, nav, output, ruby, section, summary,
13 | time, mark, audio, video {
14 | margin: 0;
15 | padding: 0;
16 | border: 0;
17 | font-size: 100%;
18 | font: inherit;
19 | vertical-align: baseline;
20 | }
21 | /* HTML5 display-role reset for older browsers */
22 | article, aside, details, figcaption, figure,
23 | footer, header, hgroup, menu, nav, section {
24 | display: block;
25 | }
26 | body {
27 | line-height: 1;
28 | }
29 | ol, ul {
30 | list-style: none;
31 | }
32 | blockquote, q {
33 | quotes: none;
34 | }
35 | blockquote:before, blockquote:after,
36 | q:before, q:after {
37 | content: '';
38 | content: none;
39 | }
40 | table {
41 | border-collapse: collapse;
42 | border-spacing: 0;
43 | }
44 | input, textarea, select, button {
45 | text-rendering: auto;
46 | color: initial;
47 | letter-spacing: normal;
48 | word-spacing: normal;
49 | text-transform: none;
50 | text-indent: 0px;
51 | text-shadow: none;
52 | display: inline-block;
53 | text-align: start;
54 | margin: 0em;
55 | font: 400 11px system-ui;
56 | }
57 | a { text-decoration: none; }
58 | textarea, select, input, button { outline: none; }
59 |
60 | button, input[type="submit"], input[type="reset"] {
61 | background: none;
62 | color: inherit;
63 | border: none;
64 | padding: 0;
65 | font: inherit;
66 | cursor: pointer;
67 | outline: inherit;
68 | }
69 |
70 | body {
71 | background-color: #26262b;
72 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/edge.scss:
--------------------------------------------------------------------------------
1 | @supports (-ms-ime-align: auto) {
2 | .side-bar {
3 | overflow-x: hidden;
4 | }
5 |
6 | .side-scroll-container {
7 | width: 86px;
8 | }
9 |
10 | .channel-scroll-wrapper {
11 | width: 256px;
12 | }
13 |
14 | .channels {
15 | overflow: hidden
16 | }
17 |
18 | .server-members-scroll {
19 | width: 256px;
20 | }
21 |
22 | .message-scroll-wrapper {
23 | height: 100%;
24 | width: 100%;
25 | overflow-x: hidden;
26 | padding-right: 16px;
27 | box-sizing: content-box;
28 | }
29 |
30 | .chat-main {
31 | overflow: hidden;
32 | height: calc(100vh - 38px);
33 | }
34 |
35 | .message-wrapper {
36 | display: block !important;
37 | overflow: hidden;
38 | }
39 |
40 | .right-main {
41 | width: 240px;
42 | }
43 |
44 | .friends-scroll-wrapper {
45 | width: calc(100vw - 294px);
46 | }
47 |
48 | .ReactModal__Content {
49 | overflow: hidden !important;
50 | }
51 |
52 | .create-server-form-bottom button {
53 | align-items: center;
54 | }
55 |
56 | .message-list {
57 | overflow-x: visible !important;
58 | }
59 | }
60 |
61 | @supports (-ms-accelerator:true) {
62 | .side-bar {
63 | overflow-x: hidden;
64 | }
65 |
66 | .side-scroll-container {
67 | width: 86px;
68 | }
69 |
70 | .channel-scroll-wrapper {
71 | width: 256px;
72 | }
73 |
74 | .channels {
75 | overflow: hidden
76 | }
77 |
78 | .server-members-scroll {
79 | width: 256px;
80 | }
81 |
82 | .message-scroll-wrapper {
83 | height: 100%;
84 | width: 100%;
85 | overflow-x: hidden;
86 | padding-right: 16px;
87 | box-sizing: content-box;
88 | }
89 |
90 | .chat-main {
91 | overflow: hidden;
92 | height: calc(100vh - 38px);
93 | }
94 |
95 | .message-wrapper {
96 | display: block !important;
97 | overflow: hidden;
98 | }
99 |
100 | .right-main {
101 | width: 240px;
102 | }
103 |
104 | .friends-scroll-wrapper {
105 | width: calc(100vw - 294px);
106 | }
107 |
108 | .ReactModal__Content {
109 | overflow: hidden !important;
110 | background-color: black;
111 | opacity: 0.85;
112 | }
113 |
114 | .create-server-form-bottom button {
115 | align-items: center;
116 | }
117 |
118 | .search-results ul {
119 | width: 435px;
120 | padding-right: 20px;
121 | box-sizing: border-box;
122 | }
123 |
124 | .message-list {
125 | overflow-x: visible !important;
126 | }
127 | }
128 |
129 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/edit_user.scss:
--------------------------------------------------------------------------------
1 | .edit-user-form {
2 | padding: 20px;
3 | user-select: none;
4 | }
5 |
6 | .edit-user-form-top {
7 | flex-wrap: nowrap;
8 | justify-content: flex-start;
9 | align-items: stretch;
10 | flex: 1 1 auto;
11 | box-sizing: border-box;
12 | display: flex;
13 | }
14 |
15 | .avatar-container {
16 | margin-right: 10px;
17 | flex: 0 1 auto;
18 | width: 100px;
19 | }
20 |
21 | .edit-user-username-email {
22 | flex: 1 1 auto;
23 | margin-left: 10px;
24 | }
25 |
26 | .edit-user-form-bottom {
27 | margin-top: 10px;
28 | padding-top: 20px;
29 | box-sizing: border-box;
30 | border-top: 1px solid rgba(114,118,125,.3);
31 | }
32 |
33 | .edit-user-button-container {
34 | display: flex;
35 | justify-content: flex-end;
36 | align-items: center;
37 | }
38 |
39 | .edit-user-button {
40 | background-color: #43b581;
41 | color: #fff;
42 | cursor: pointer;
43 | height: 32px;
44 | width: auto;
45 | transition: background-color .17s ease,color .17s ease;
46 | align-items: center;
47 | border-radius: 3px;
48 | box-sizing: border-box;
49 | display: flex;
50 | font-size: 14px;
51 | font-weight: 500;
52 | justify-content: center;
53 | line-height: 16px;
54 | padding: 2px 16px;
55 | font-family: main3;
56 | }
57 |
58 | .edit-user-button:hover {
59 | background-color: #3ca374;
60 | }
61 |
62 | .avatar-container input {
63 | position: absolute;
64 | top: 0px;
65 | left: 0px;
66 | width: 100%;
67 | height: 100%;
68 | opacity: 0;
69 | cursor: pointer;
70 | }
71 |
72 | .avatar-wrapper:hover {
73 | box-shadow: inset 0 0 120px rgba(0,0,0,.75);
74 | }
75 |
76 | .avatar-wrapper {
77 | background-position: 50%;
78 | background-repeat: no-repeat;
79 | background-size: cover;
80 | border-radius: 50%;
81 | box-sizing: border-box;
82 | display: inline-block;
83 | flex-shrink: 0;
84 | height: 100px;
85 | width: 100px;
86 | position: relative;
87 | margin-bottom: 10px;
88 | }
89 |
90 | .add-file-icon {
91 | align-items: center;
92 | background-position: 50%;
93 | background-repeat: no-repeat;
94 | border-radius: 50%;
95 | box-shadow: 0 2px 4px 0 rgba(0,0,0,.2);
96 | display: flex;
97 | height: 28px;
98 | justify-content: center;
99 | left: auto;
100 | position: absolute;
101 | right: 0;
102 | top: 0;
103 | width: 28px;
104 | background-color: #dcddde;
105 | background-image: image_url('main/add-file.svg');
106 | }
107 |
108 | .avatar-wrapper p {
109 | align-items: center;
110 | bottom: 0;
111 | color: #fff;
112 | display: flex;
113 | justify-content: center;
114 | left: 0;
115 | pointer-events: none;
116 | position: absolute;
117 | right: 0;
118 | top: 0;
119 | text-align: center;
120 | font-family: main3;
121 | font-size: 10px;
122 | font-weight: 700;
123 | line-height: 12px;
124 | white-space: pre;
125 | visibility: hidden;
126 | }
127 |
128 | .avatar-wrapper:hover p {
129 | visibility: visible;
130 | }
131 |
132 | .remove-avatar-button {
133 | color: #b9bbbe;
134 | cursor: pointer;
135 | display: block;
136 | font-size: 12px;
137 | text-decoration: none;
138 | font-family: main3;
139 | transition: opacity .5s,color .5s;
140 | margin: auto;
141 | }
142 |
143 | .remove-avatar-button:hover {
144 | color: #72767d;
145 | text-decoration: underline;
146 | }
147 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/firefox.scss:
--------------------------------------------------------------------------------
1 | @-moz-document url-prefix() {
2 |
3 | .side-bar {
4 | overflow-x: hidden;
5 | }
6 |
7 | .side-scroll-container {
8 | width: 86px;
9 | }
10 |
11 | .channel-scroll-wrapper {
12 | width: 255px;
13 | }
14 |
15 | .channels {
16 | overflow: hidden
17 | }
18 |
19 | .server-members-scroll {
20 | width: 255px;
21 | }
22 |
23 | .message-scroll-wrapper {
24 | height: 100%;
25 | width: 100%;
26 | overflow-x: hidden;
27 | padding-right: 15px;
28 | box-sizing: content-box;
29 | }
30 |
31 | .chat-main {
32 | overflow: hidden;
33 | height: calc(100vh - 48px) !important;
34 | }
35 |
36 | .message-wrapper {
37 | display: block !important;
38 | overflow: hidden;
39 | }
40 |
41 | .right-main {
42 | width: 240px;
43 | }
44 |
45 | .friends-scroll-wrapper {
46 | width: calc(100vw - 294px);
47 | }
48 |
49 | .ReactModal__Content {
50 | overflow: hidden !important;
51 | }
52 |
53 | .create-server-form-bottom button {
54 | align-items: center;
55 | }
56 |
57 | .search-results ul {
58 | width: 435px;
59 | padding-right: 20px;
60 | box-sizing: border-box;
61 | }
62 |
63 | .message-list {
64 | overflow-x: visible !important;
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/loading.scss:
--------------------------------------------------------------------------------
1 | .loading-screen {
2 | align-items: center;
3 | background-color: #2f3136;
4 | bottom: 0;
5 | display: flex;
6 | flex-direction: column;
7 | justify-content: center;
8 | left: 0;
9 | overflow: hidden;
10 | position: absolute;
11 | right: 0;
12 | text-rendering: optimizeLegibility;
13 | top: 0;
14 | z-index: 3000;
15 | transition: opacity 200ms ease-in-out;
16 | opacity: 1;
17 | }
18 |
19 | .loading-screen h5 {
20 | color: #72767d;
21 | font-size: 12px;
22 | line-height: 16px;
23 | max-width: 600px;
24 | text-transform: uppercase;
25 | font-family: main3;
26 | }
27 |
28 | .hidden {
29 | opacity: 0;
30 | // transition: opacity 200ms ease-in-out;
31 | }
--------------------------------------------------------------------------------
/app/assets/stylesheets/user_popup.scss:
--------------------------------------------------------------------------------
1 | .user-popup-top {
2 | padding: 20px 10px;
3 | background-color: #2f3136;
4 | display: flex;
5 | justify-content: center;
6 | align-content: center;
7 | align-items: center;
8 | flex-direction: column;
9 | }
10 |
11 | .user-popup{
12 | color: #fff;
13 | line-height: 20px;
14 | font-size: 16px;
15 | user-select: none;
16 | }
17 |
18 | .user-popup-img {
19 | height: 90px;
20 | width: 90px;
21 | border-radius: 50%;
22 | background-clip: padding-box;
23 | background-position: 50%;
24 | background-size: 100%;
25 | margin-bottom: 10px;
26 | position: relative;
27 | }
28 |
29 | .user-popup-top h5 {
30 | font-weight: 600;
31 | color: #fff;
32 | font-family: main3;
33 | font-size: 16px;
34 | overflow: hidden;
35 | text-overflow: ellipsis;
36 | max-width: 80%;
37 | }
38 |
39 | .user-popup-bottom {
40 | background: #18191c;
41 | box-shadow: 0 0 1px rgba(0,0,0,.82), 0 1px 4px rgba(0,0,0,.1);
42 | border-radius: 0 0 5px 5px;
43 | display: flex;
44 | flex-direction: column;
45 | }
46 |
47 | .user-popup-button:hover {
48 | background: #040405;
49 | color: #fff;
50 | }
51 |
52 | .user-popup-button {
53 | color: hsla(0,0%,100%,.6);
54 | border-radius: 5px;
55 | box-sizing: border-box;
56 | font-size: 13px;
57 | font-weight: 500;
58 | line-height: 16px;
59 | margin: 2px 0;
60 | overflow: hidden;
61 | padding: 6px 9px;
62 | text-overflow: ellipsis;
63 | white-space: nowrap;
64 | width: 100%;
65 | }
66 |
67 | .popup-online-indicator {
68 | background-size: 22px;
69 | bottom: 3px;
70 | height: 18px;
71 | position: absolute;
72 | right: 3px;
73 | width: 18px;
74 | background-position: 50% 50%;
75 | background-repeat: no-repeat;
76 | border: 3px solid #2f3136;
77 | border-radius: 50%;
78 | }
--------------------------------------------------------------------------------
/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/channels/notifications_channel.rb:
--------------------------------------------------------------------------------
1 | class NotificationsChannel < ApplicationCable::Channel
2 | def subscribed
3 | current_user = User.find(params['userId'])
4 | stream_for current_user
5 | end
6 |
7 | def unsubscribed
8 | end
9 | end
--------------------------------------------------------------------------------
/app/channels/voice_channel.rb:
--------------------------------------------------------------------------------
1 | class VoiceChannel < ApplicationCable::Channel
2 | def subscribed
3 | stream_from "voice_channel_#{params['channelId']}"
4 | end
5 |
6 | def unsubscribed
7 | end
8 |
9 | def broadcast(options)
10 | channel_id = options["channelId"]
11 | type = options["data"]["type"]
12 | from = options["data"]["from"] || nil
13 | to = options["data"]["to"] || nil
14 | sdp = options["data"]["sdp"] || nil
15 | candidate = options["data"]["candidate"] || nil
16 |
17 | ActionCable.server.broadcast("voice_channel_#{channel_id}",
18 | type: type,
19 | from: from,
20 | to: to,
21 | sdp: sdp,
22 | candidate: candidate
23 | )
24 | end
25 | end
26 |
--------------------------------------------------------------------------------
/app/controllers/api/audio_channels_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::AudioChannelsController < ApplicationController
2 | def create
3 | @audio_channel = AudioChannel.new(audio_channel_params)
4 |
5 | if @audio_channel.save
6 | render "api/audio_channels/show"
7 | else
8 | render json: @audio_channel.errors.full_messages, status: 422
9 | end
10 | end
11 |
12 | def index
13 | server = Server.find_by(id: audio_channel_params[:server_id])
14 |
15 | if server
16 | @audio_channels = server.audio_channels
17 | render :index
18 | else
19 | render json: ["Server does not exist"], status: 422
20 | end
21 | end
22 |
23 | def destroy
24 | current_audio_channel.destroy
25 | render "api/audio_channels/show"
26 | end
27 |
28 | private
29 |
30 | def current_audio_channel
31 | @audio_channel ||= AudioChannel.find(params[:id])
32 | end
33 |
34 | def audio_channel_params
35 | params.require(:audio_channel).permit(:name, :server_id)
36 | end
37 | end
--------------------------------------------------------------------------------
/app/controllers/api/channels_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::ChannelsController < ApplicationController
2 | def create
3 | @channel = Channel.new(channel_params)
4 |
5 | if @channel.save
6 | render "api/channels/show"
7 | else
8 | render json: @channel.errors.full_messages, status: 422
9 | end
10 | end
11 |
12 | def index
13 | server = Server.find_by(id: channel_params[:server_id])
14 |
15 | if server
16 | @channels = server.channels
17 | render :index
18 | else
19 | render json: ["Server does not exist"], status: 422
20 | end
21 | end
22 |
23 | def destroy
24 | current_channel.destroy
25 | render "api/channels/show"
26 | end
27 |
28 | private
29 |
30 | def current_channel
31 | @channel ||= Channel.find(params[:id])
32 | end
33 |
34 | def channel_params
35 | params.require(:channel).permit(:name, :server_id)
36 | end
37 | end
--------------------------------------------------------------------------------
/app/controllers/api/dm_channel_memberships_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::DmChannelMembershipsController < ApplicationController
2 |
3 | def destroy
4 | channel_id = params[:id]
5 | @dm_channel_membership = DmChannelMembership.find_by(user_id: current_user.id, channel_id: channel_id)
6 | @dm_channel_membership.destroy
7 | render json: @dm_channel_membership
8 | end
9 |
10 | def create
11 | user_id = dm_channel_params[:user_id].to_i
12 | current_user_id = current_user.id
13 | unless current_user_id == user_id
14 | name = current_user_id > user_id ? "#{user_id}-#{current_user_id}" : "#{current_user_id}-#{user_id}"
15 | @channel = Channel.find_or_create_by(name: name)
16 | @channel.dm_memberships.create(user_id: current_user_id)
17 | @channel.dm_memberships.create(user_id: user_id)
18 |
19 | render "api/channels/show"
20 | else
21 | render json: ['Cannot DM yourself'], status: 401
22 | end
23 | end
24 |
25 | private
26 |
27 | def dm_channel_params
28 | params.require(:dm_channel).permit(:user_id)
29 | end
30 | end
--------------------------------------------------------------------------------
/app/controllers/api/friend_requests_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::FriendRequestsController < ApplicationController
2 |
3 | def create
4 | friend = User.find(friend_request_params[:friend_id])
5 | @friend_request = current_user.friend_requests.new(friend: friend)
6 |
7 | if @friend_request.save
8 | @user = current_user
9 | render 'api/friend_requests/show'
10 | else
11 | render json: @friend_request.errors.full_messages, status: :unprocessable_entity
12 | end
13 | end
14 |
15 | def destroy
16 | current_friend_request.destroy
17 | render :show
18 | end
19 |
20 | def update
21 | current_friend_request.accept
22 | render :show
23 | end
24 |
25 | private
26 |
27 | def friend_request_params
28 | params.require(:friend_request).permit(:friend_id)
29 | end
30 |
31 | def current_friend_request
32 | @friend_request ||= FriendRequest.find(params[:id])
33 | end
34 | end
--------------------------------------------------------------------------------
/app/controllers/api/friends_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::FriendsController < ApplicationController
2 |
3 | def destroy
4 | current_user.remove_friend(current_friend)
5 | head :no_content
6 | end
7 |
8 | private
9 |
10 | def current_friend
11 | @friend ||= current_user.friends.find(params[:id])
12 | end
13 | end
--------------------------------------------------------------------------------
/app/controllers/api/messages_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::MessagesController < ApplicationController
2 | def create
3 | @message = Message.new(message_params)
4 | @message.author_id = current_user.id
5 |
6 | if @message.save
7 | render :show
8 | else
9 | render json: @message.errors.full_messages, status: 422
10 | end
11 | end
12 |
13 | def index
14 | server = Channel.find(message_params[:channel_id])
15 | @messages = channel.messges
16 | render :index
17 | end
18 |
19 | private
20 |
21 | def current_message
22 | @message ||= Message.find(params[:id])
23 | end
24 |
25 | def message_params
26 | params.require(:message).permit(:body, :channel_id, :author_id)
27 | end
28 | end
--------------------------------------------------------------------------------
/app/controllers/api/servers_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::ServersController < ApplicationController
2 | def create
3 | @server = current_user.admin_servers.new(server_params)
4 |
5 | if @server.save
6 | current_user.server_memberships.create!(server_id: @server.id)
7 | @server.channels.create!(name: 'general')
8 | @server.audio_channels.create!(name: 'General')
9 |
10 | render "api/servers/show"
11 | else
12 | render json: @server.errors.full_messages, status: 422
13 | end
14 | end
15 |
16 | def index
17 | @servers = Server.all.includes(:channels)
18 | end
19 |
20 | def join
21 | @server = Server.find_by(name: server_params[:name])
22 | if @server
23 | current_user.server_memberships.create(server_id: @server.id)
24 | render :show
25 | else
26 | render json: ["Server does not exist"], status: 401
27 | end
28 | end
29 |
30 | def members
31 | if current_server
32 | @users = User.distinct.select('users.*').left_outer_joins(:message_channels).left_outer_joins(:server_memberships)
33 | .where("server_memberships.server_id = :current_server_id OR channels.server_id = :current_server_id", current_server_id: current_server.id)
34 | .includes(:sessions, :server_memberships)
35 | # @users = current_server.members.includes(:sessions, :server_memberships)
36 | render "api/users/index"
37 | else
38 | render json: ["Server does not exist"], status: 401
39 | end
40 | end
41 |
42 | def destroy
43 | if current_user.id == current_server.admin_id
44 | current_server.destroy
45 | else
46 | current_user.server_memberships.find_by(server_id: current_server.id).destroy
47 | end
48 |
49 | render :show
50 | end
51 |
52 | private
53 |
54 | def current_server
55 | @server ||= Server.find_by(id: params[:id])
56 | end
57 |
58 | def server_params
59 | params.require(:server).permit(:name, :icon)
60 | end
61 | end
--------------------------------------------------------------------------------
/app/controllers/api/sessions_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::SessionsController < ApplicationController
2 | def create
3 | @user = User.find_by_credentials(
4 | user_params[:username],
5 | user_params[:password]
6 | )
7 |
8 | if @user
9 | login(@user, request.user_agent)
10 | render "api/users/show"
11 | else
12 | render json: ["Invalid username/password combination"], status: 401
13 | end
14 | end
15 |
16 | def destroy
17 | @user = current_user
18 | if @user
19 | logout(request.user_agent)
20 | render "api/users/show"
21 | else
22 | render json: ["Nobody signed in"], status: 404
23 | end
24 | end
25 |
26 | private
27 |
28 | def user_params
29 | params.require(:user).permit(:username, :password)
30 | end
31 | end
--------------------------------------------------------------------------------
/app/controllers/api/users_controller.rb:
--------------------------------------------------------------------------------
1 | class Api::UsersController < ApplicationController
2 |
3 | def create
4 | @user = User.new(user_params)
5 |
6 | if @user.save
7 | login(@user, request.user_agent)
8 | render :show
9 | else
10 | render json: @user.errors.full_messages, status: 422
11 | end
12 | end
13 |
14 | def update
15 | @user = current_user
16 |
17 | if @user.id == 71
18 | render json: ['Can not edit the demo user'], status: 422
19 | else
20 | if current_user.update_attributes(user_params)
21 | render :show
22 | else
23 | render json: current_user.errors.full_messages, status: 422
24 | end
25 | end
26 | end
27 |
28 | def data
29 | current_user_id = current_user.id
30 |
31 | @servers = current_user.servers.includes(:channels, :audio_channels)
32 | @channels = @servers.map(&:channels).flatten.uniq
33 | @audio_channels = @servers.map(&:audio_channels).flatten.uniq
34 | @requests = FriendRequest.where(friend_id: current_user_id).or(FriendRequest.where(user_id: current_user_id))
35 | @friendships = current_user.friendships.pluck(:friend_id)
36 | @dm_channels = current_user.dm_channels
37 |
38 | # This is the code I would actually use; however, to decrease subsequent load times for non-tech people viewing my
39 | # site, I have temporarily replaced it with a load of all the users.
40 | # dm_user_ids = [current_user_id]
41 |
42 | # @dm_channels.each do |channel|
43 | # dm_arr = channel.name.split('-')
44 | # if dm_arr[0].to_i == current_user_id
45 | # dm_user_ids << dm_arr[1].to_i
46 | # else
47 | # dm_user_ids << dm_arr[0].to_i
48 | # end
49 | # end
50 |
51 | # @users = User.distinct.select('users.*').left_outer_joins(:friend_requests)
52 | # .left_outer_joins(:incoming_friend_requests).left_outer_joins(:friendships)
53 | # .where("incoming_friend_requests_users.user_id = :current_user_id OR friend_requests.friend_id = :current_user_id OR friendships.friend_id = :current_user_id OR users.id IN (:dm_user_ids)", current_user_id: current_user_id, dm_user_ids: dm_user_ids)
54 | # .includes(:sessions, :server_memberships)
55 |
56 | @users = User.all.includes(:sessions, :server_memberships)
57 | render "api/users/user_data"
58 | end
59 |
60 | private
61 |
62 | def user_params
63 | params.require(:user).permit(:username, :password, :email, :avatar)
64 | end
65 | end
--------------------------------------------------------------------------------
/app/controllers/application_controller.rb:
--------------------------------------------------------------------------------
1 | class ApplicationController < ActionController::Base
2 | helper_method :current_user, :logged_in?
3 |
4 | private
5 |
6 | def current_user
7 | unless @current_user
8 | current_session = Session.find_by(session_token: session[:session_token])
9 | @current_user = User.find(current_session.user_id) if current_session
10 | end
11 |
12 | @current_user
13 | end
14 |
15 | def logged_in?
16 | !!current_user
17 | end
18 |
19 | def login(user, user_agent)
20 | session[:session_token] = user.reset_session_token!(user_agent)
21 | @current_user = user
22 | end
23 |
24 | def logout(user_agent)
25 | current_user.destroy_session!(user_agent)
26 | session[:session_token] = nil
27 | @current_user = nil
28 | end
29 |
30 | def require_logged_in
31 | unless current_user
32 | render json: { base: ['invalid credentials'] }, status: 401
33 | end
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/app/controllers/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/controllers/concerns/.keep
--------------------------------------------------------------------------------
/app/controllers/static_pages_controller.rb:
--------------------------------------------------------------------------------
1 | class StaticPagesController < ApplicationController
2 | def root
3 | end
4 | end
--------------------------------------------------------------------------------
/app/jobs/application_job.rb:
--------------------------------------------------------------------------------
1 | class ApplicationJob < ActiveJob::Base
2 | end
3 |
--------------------------------------------------------------------------------
/app/jobs/friend_request_broadcast_job.rb:
--------------------------------------------------------------------------------
1 | class FriendRequestBroadcastJob < ApplicationJob
2 | queue_as :default
3 |
4 | def perform(friend_request, create)
5 | friend = User.find(friend_request.friend_id)
6 |
7 | if create
8 | NotificationsChannel.broadcast_to(
9 | friend, {type: 'friend_request',
10 | friend_request: render_friend_request(friend_request),
11 | user: render_user(friend_request.user),
12 | }
13 | )
14 | else
15 | user = User.find(friend_request.user_id)
16 |
17 | NotificationsChannel.broadcast_to(
18 | user, {type: 'friend_request_destroy',
19 | friend_request: render_friend_request(friend_request)
20 | }
21 | )
22 |
23 | NotificationsChannel.broadcast_to(
24 | friend, {type: 'friend_request_destroy',
25 | friend_request: render_friend_request(friend_request)
26 | }
27 | )
28 | end
29 | end
30 |
31 | private
32 |
33 | def render_friend_request(friend_request)
34 | ApplicationController.renderer.render(
35 | partial: 'api/friend_requests/friend_request',
36 | locals: { friend_request: friend_request }
37 | )
38 | end
39 |
40 | def render_user(user)
41 | ApplicationController.renderer.render(
42 | partial: 'api/users/user',
43 | locals: { user: user }
44 | )
45 | end
46 | end
--------------------------------------------------------------------------------
/app/jobs/friendship_broadcast_job.rb:
--------------------------------------------------------------------------------
1 | class FriendshipBroadcastJob < ApplicationJob
2 | queue_as :default
3 |
4 | def perform(user, friend, create)
5 | if create
6 | NotificationsChannel.broadcast_to(
7 | user, {type: 'friend',
8 | user: render_user(friend)
9 | }
10 | )
11 | else
12 | NotificationsChannel.broadcast_to(
13 | user, {type: 'friend_destroy',
14 | user_id: friend.id
15 | }
16 | )
17 | end
18 | end
19 |
20 | private
21 |
22 | def render_user(user)
23 | ApplicationController.renderer.render(
24 | partial: 'api/users/user',
25 | locals: { user: user }
26 | )
27 | end
28 | end
--------------------------------------------------------------------------------
/app/jobs/notification_broadcast_job.rb:
--------------------------------------------------------------------------------
1 | class NotificationBroadcastJob < ApplicationJob
2 | queue_as :default
3 |
4 | def perform(message, user, channel)
5 | NotificationsChannel.broadcast_to(
6 | user, {type: 'message',
7 | user: render_user(message.author),
8 | channel: render_channel(channel)
9 | }
10 | )
11 | end
12 |
13 | private
14 |
15 | def render_user(user)
16 | ApplicationController.renderer.render(
17 | partial: 'api/users/user',
18 | locals: { user: user }
19 | )
20 | end
21 |
22 | def render_channel(channel)
23 | ApplicationController.renderer.render(
24 | partial: 'api/channels/channel',
25 | locals: { channel: channel }
26 | )
27 | end
28 | end
--------------------------------------------------------------------------------
/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/audio_channel.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: audio_channels
4 | #
5 | # id :bigint(8) not null, primary key
6 | # name :string not null
7 | # server_id :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class AudioChannel < ApplicationRecord
13 | validates :name, presence: true
14 | validates :name, uniqueness: {scope: :server_id}
15 |
16 | belongs_to :server,
17 | optional: true
18 | end
19 |
--------------------------------------------------------------------------------
/app/models/channel.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: channels
4 | #
5 | # id :bigint(8) not null, primary key
6 | # name :string not null
7 | # server_id :integer
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class Channel < ApplicationRecord
13 | validates :name, presence: true
14 | validates :name, uniqueness: {scope: :server_id}
15 |
16 | belongs_to :server,
17 | optional: true
18 |
19 | has_many :messages,
20 | dependent: :destroy
21 |
22 | has_many :dm_memberships,
23 | class_name: :DmChannelMembership,
24 | dependent: :destroy
25 |
26 | has_many :members,
27 | through: :dm_memberships,
28 | source: :user
29 |
30 | end
31 |
--------------------------------------------------------------------------------
/app/models/concerns/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/app/models/concerns/.keep
--------------------------------------------------------------------------------
/app/models/dm_channel_membership.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: dm_channel_memberships
4 | #
5 | # id :bigint(8) not null, primary key
6 | # user_id :integer not null
7 | # channel_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class DmChannelMembership < ApplicationRecord
13 | validates :user_id, uniqueness: {scope: :channel_id}
14 |
15 | belongs_to :channel
16 |
17 | belongs_to :user
18 | end
19 |
--------------------------------------------------------------------------------
/app/models/friend_request.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: friend_requests
4 | #
5 | # id :bigint(8) not null, primary key
6 | # user_id :integer not null
7 | # friend_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class FriendRequest < ApplicationRecord
13 | validates :user, presence: true
14 | validates :friend, presence: true, uniqueness: { scope: :user }
15 | validate :not_self
16 | validate :not_friends
17 | validate :not_pending
18 |
19 | belongs_to :user
20 |
21 | belongs_to :friend,
22 | class_name: :User
23 |
24 | def accept
25 | user.friends << friend
26 | destroy
27 | end
28 |
29 | def send_request
30 | FriendRequestBroadcastJob.perform_later(self, true)
31 | end
32 |
33 | def send_request_destroy
34 | FriendRequestBroadcastJob.perform_later(self, false)
35 | end
36 |
37 | after_create_commit :send_request
38 | after_destroy :send_request_destroy
39 |
40 | private
41 |
42 | def not_self
43 | errors.add(:friend, "can't be equal to user") if user == friend
44 | end
45 |
46 | def not_friends
47 | errors.add(:friend, 'is already added') if user.friends.include?(friend)
48 | end
49 |
50 | def not_pending
51 | errors.add(:friend, 'already requested friendship') if friend.pending_friends.include?(user)
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/app/models/friendship.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: friendships
4 | #
5 | # id :bigint(8) not null, primary key
6 | # user_id :integer not null
7 | # friend_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class Friendship < ApplicationRecord
13 | validates :user, presence: true
14 | validates :friend, presence: true, uniqueness: { scope: :user }
15 | validate :not_self
16 |
17 | after_create :create_inverse_relationship
18 | after_destroy :destroy_inverse_relationship
19 |
20 | belongs_to :user
21 |
22 | belongs_to :friend,
23 | class_name: :User
24 |
25 | def send_friend
26 | FriendshipBroadcastJob.perform_later(self.user, self.friend, true)
27 | end
28 |
29 | def send_friend_destroy
30 | FriendshipBroadcastJob.perform_later(self.user, self.friend, false)
31 | end
32 |
33 | after_create_commit :send_friend
34 | after_destroy :send_friend_destroy
35 |
36 | private
37 |
38 | def create_inverse_relationship
39 | friend.friendships.create(friend: user)
40 | end
41 |
42 | def destroy_inverse_relationship
43 | friendship = friend.friendships.find_by(friend: user)
44 | friendship.destroy if friendship
45 | end
46 |
47 | def not_self
48 | errors.add(:friend, "can't be equal to user") if user == friend
49 | end
50 | end
51 |
--------------------------------------------------------------------------------
/app/models/message.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: messages
4 | #
5 | # id :bigint(8) not null, primary key
6 | # body :string not null
7 | # author_id :integer not null
8 | # channel_id :integer not null
9 | # created_at :datetime not null
10 | # updated_at :datetime not null
11 | #
12 |
13 | class Message < ApplicationRecord
14 | validates :body, presence: true
15 |
16 | belongs_to :channel
17 |
18 | belongs_to :author,
19 | class_name: :User
20 |
21 | def send_notification
22 | message_channel = self.channel
23 | unless message_channel.server_id
24 | user_id = message_channel.name.split('-').find { |el| el != self.author_id.to_s }.to_i
25 | DmChannelMembership.create(user_id: user_id, channel_id: message_channel.id)
26 | user = User.find(user_id)
27 | NotificationBroadcastJob.perform_later(self, user, message_channel)
28 | end
29 | end
30 |
31 | after_create_commit :send_notification
32 | end
33 |
--------------------------------------------------------------------------------
/app/models/server.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: servers
4 | #
5 | # id :bigint(8) not null, primary key
6 | # name :string not null
7 | # admin_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class Server < ApplicationRecord
13 | validates :name, presence: true, uniqueness: true
14 |
15 | belongs_to :admin,
16 | class_name: :User
17 |
18 | has_many :memberships,
19 | class_name: :ServerMembership,
20 | dependent: :destroy
21 |
22 | has_many :members,
23 | through: :memberships,
24 | source: :user
25 |
26 | has_many :channels,
27 | dependent: :destroy
28 |
29 | has_many :audio_channels,
30 | dependent: :destroy
31 |
32 | has_many :server_messages,
33 | through: :channels,
34 | source: :messages
35 |
36 | has_many :message_users,
37 | through: :server_messages,
38 | source: :author
39 |
40 | has_one_attached :icon
41 | end
42 |
--------------------------------------------------------------------------------
/app/models/server_membership.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: server_memberships
4 | #
5 | # id :bigint(8) not null, primary key
6 | # user_id :integer not null
7 | # server_id :integer not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | #
11 |
12 | class ServerMembership < ApplicationRecord
13 | validates :user_id, uniqueness: {scope: :server_id}
14 |
15 | belongs_to :server
16 |
17 | belongs_to :user
18 |
19 | end
20 |
--------------------------------------------------------------------------------
/app/models/session.rb:
--------------------------------------------------------------------------------
1 | # == Schema Information
2 | #
3 | # Table name: sessions
4 | #
5 | # id :bigint(8) not null, primary key
6 | # user_id :integer not null
7 | # session_token :string not null
8 | # created_at :datetime not null
9 | # updated_at :datetime not null
10 | # user_agent :string not null
11 | #
12 |
13 | class Session < ApplicationRecord
14 | validates :session_token, :user_agent, presence: true
15 | validates :session_token, uniqueness: true
16 |
17 | belongs_to :user
18 | end
19 |
--------------------------------------------------------------------------------
/app/views/api/audio_channels/_audio_channel.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! audio_channel, :id, :name, :server_id
--------------------------------------------------------------------------------
/app/views/api/audio_channels/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @audio_channels.each do |audio_channel|
2 | json.set! audio_channel.id do
3 | json.partial! 'api/audio_channels/audio_channel', audio_channel: audio_channel
4 | end
5 | end
--------------------------------------------------------------------------------
/app/views/api/audio_channels/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "api/audio_channels/audio_channel", audio_channel: @audio_channel
--------------------------------------------------------------------------------
/app/views/api/channels/_channel.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! channel, :id, :name, :server_id
--------------------------------------------------------------------------------
/app/views/api/channels/dm_index.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.channels do
2 | @channels.each do |channel|
3 | json.set! channel.id do
4 | json.partial! 'api/channels/channel', channel: channel
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/api/channels/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @channels.each do |channel|
2 | json.set! channel.id do
3 | json.partial! 'api/channels/channel', channel: channel
4 | end
5 | end
--------------------------------------------------------------------------------
/app/views/api/channels/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "api/channels/channel", channel: @channel
--------------------------------------------------------------------------------
/app/views/api/friend_requests/_friend_request.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! friend_request, :id, :user_id, :friend_id
--------------------------------------------------------------------------------
/app/views/api/friend_requests/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! 'api/friend_requests/friend_request', friend_request: @friend_request
--------------------------------------------------------------------------------
/app/views/api/messages/_message.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.extract! message, :id, :author_id, :channel_id, :body
--------------------------------------------------------------------------------
/app/views/api/servers/_server.json.jbuilder:
--------------------------------------------------------------------------------
1 | icon_url = (server.icon.attached? ? url_for(server.icon) : '')
2 | channels = server.channels
3 | first_channel = channels.select { |channel| channel.name == "general" }.first
4 | root_channel = (first_channel ? first_channel.id : '')
5 |
6 | json.extract! server, :id, :name, :admin_id
7 | json.icon_url icon_url
8 | json.root_channel root_channel
--------------------------------------------------------------------------------
/app/views/api/servers/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | @servers.with_attached_icon.each do |server|
2 | json.set! server.id do
3 | json.partial! 'api/servers/server', server: server
4 | end
5 | end
--------------------------------------------------------------------------------
/app/views/api/servers/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "api/servers/server", server: @server
--------------------------------------------------------------------------------
/app/views/api/users/_user.json.jbuilder:
--------------------------------------------------------------------------------
1 | server_array = user.server_memberships.sort.map { |membership| membership.server_id }
2 | online = !user.sessions.empty?
3 | image_url = user.avatar.attached? ? url_for(user.avatar) : user.image_url
4 |
5 | json.extract! user, :id, :username, :email
6 | json.image_url image_url
7 | json.servers server_array
8 | json.online online
--------------------------------------------------------------------------------
/app/views/api/users/index.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.users do
2 | @users.with_attached_avatar.each do |user|
3 | json.set! user.id do
4 | json.partial! 'api/users/user', user: user
5 | end
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/views/api/users/show.json.jbuilder:
--------------------------------------------------------------------------------
1 | json.partial! "api/users/user", user: @user
--------------------------------------------------------------------------------
/app/views/api/users/user_data.json.jbuilder:
--------------------------------------------------------------------------------
1 | outgoing = []
2 | incoming = []
3 |
4 | @requests.each do |request|
5 | if request.user_id == current_user.id
6 | outgoing << request
7 | else
8 | incoming << request
9 | end
10 | end
11 |
12 | json.channels do
13 |
14 | @channels.each do |channel|
15 | json.set! channel.id do
16 | json.partial! 'api/channels/channel', channel: channel
17 | end
18 | end
19 |
20 | @dm_channels.each do |channel|
21 | json.set! channel.id do
22 | json.partial! 'api/channels/channel', channel: channel
23 | end
24 | end
25 | end
26 |
27 | json.voice_channels do
28 | @audio_channels.each do |audio_channel|
29 | json.set! audio_channel.id do
30 | json.partial! 'api/audio_channels/audio_channel', audio_channel: audio_channel
31 | end
32 | end
33 |
34 | end
35 |
36 | json.users do
37 | @users.with_attached_avatar.each do |user|
38 | json.set! user.id do
39 | json.partial! 'api/users/user', user: user
40 | end
41 | end
42 | end
43 |
44 | json.servers do
45 | @servers.with_attached_icon.each do |server|
46 | json.set! server.id do
47 | json.partial! 'api/servers/server', server: server
48 | end
49 | end
50 | end
51 |
52 | json.friend_requests do
53 |
54 | outgoing.each do |request|
55 | json.set! request.id do
56 | json.partial! 'api/friend_requests/friend_request', friend_request: request
57 | end
58 | end
59 |
60 | incoming.each do |request|
61 | json.set! request.id do
62 | json.partial! 'api/friend_requests/friend_request', friend_request: request
63 | end
64 | end
65 |
66 | end
67 |
68 | json.friends do
69 | json.array! @friendships
70 | end
71 |
72 | json.currentUserId current_user.id
--------------------------------------------------------------------------------
/app/views/layouts/application.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
6 | Discors
7 | <%= csrf_meta_tags %>
8 | <%= csp_meta_tag %>
9 |
10 | <%= stylesheet_link_tag 'application', media: 'all' %>
11 | <%= javascript_include_tag 'application' %>
12 |
13 |
14 |
15 | <%= yield %>
16 |
17 |
18 |
--------------------------------------------------------------------------------
/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 |
6 | <% end %>
7 |
8 |
--------------------------------------------------------------------------------
/bin/bundle:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__)
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 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a starting point to setup your application.
14 | # Add necessary setup steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | # puts "\n== Copying sample files =="
24 | # unless File.exist?('config/database.yml')
25 | # cp 'config/database.yml.sample', 'config/database.yml'
26 | # end
27 |
28 | puts "\n== Preparing database =="
29 | system! 'bin/rails db:setup'
30 |
31 | puts "\n== Removing old logs and tempfiles =="
32 | system! 'bin/rails log:clear tmp:clear'
33 |
34 | puts "\n== Restarting application server =="
35 | system! 'bin/rails restart'
36 | end
37 |
--------------------------------------------------------------------------------
/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 'fileutils'
3 | include FileUtils
4 |
5 | # path to your application root.
6 | APP_ROOT = File.expand_path('..', __dir__)
7 |
8 | def system!(*args)
9 | system(*args) || abort("\n== Command #{args} failed ==")
10 | end
11 |
12 | chdir APP_ROOT do
13 | # This script is a way to update your development environment automatically.
14 | # Add necessary update steps to this file.
15 |
16 | puts '== Installing dependencies =='
17 | system! 'gem install bundler --conservative'
18 | system('bundle check') || system!('bundle install')
19 |
20 | # Install JavaScript dependencies if using Yarn
21 | # system('bin/yarn')
22 |
23 | puts "\n== Updating database =="
24 | system! 'bin/rails db:migrate'
25 |
26 | puts "\n== Removing old logs and tempfiles =="
27 | system! 'bin/rails log:clear tmp:clear'
28 |
29 | puts "\n== Restarting application server =="
30 | system! 'bin/rails restart'
31 | end
32 |
--------------------------------------------------------------------------------
/bin/yarn:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env ruby
2 | APP_ROOT = File.expand_path('..', __dir__)
3 | Dir.chdir(APP_ROOT) do
4 | begin
5 | exec "yarnpkg", *ARGV
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 | module Discors
10 | class Application < Rails::Application
11 | # Initialize configuration defaults for originally generated Rails version.
12 | config.load_defaults 5.2
13 |
14 | # Settings in config/environments/* take precedence over those specified here.
15 | # Application configuration can go into files in config/initializers
16 | # -- all .rb files in that directory are automatically loaded after loading
17 | # the framework and any gems in your application.
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 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations.
5 |
--------------------------------------------------------------------------------
/config/cable.yml:
--------------------------------------------------------------------------------
1 | development:
2 | adapter: async
3 |
4 | test:
5 | adapter: async
6 |
7 | production:
8 | adapter: redis
9 | url: <%= ENV['REDIS_URL'] %>
10 | channel_prefix: discors_production
--------------------------------------------------------------------------------
/config/credentials.yml.enc:
--------------------------------------------------------------------------------
1 | Jt9ijb9pcSCrOUEGJ+os/Re03t/IXT34HE7jMI/0FlaniZaBCtdjbXmCxluscTumRaLRAocJ3mcisS/Feu8biltms7zq7X5LxRl484YgVRBp97RKeLvsKRiOoj2X3P4JSAjMLab4l+fekOEN/Y5t7J6zWWQZbA8wQfpCq8NbOYKVP46/G024k5AArUmY5mKblmF7jZ6PjrHy4ZJSaXgCjXBe4MlTJ7AzOtwNAN3aEe0OrHuTBY5xtvFoE8j9Qp5P2IsYIatbMrIb4oeNGgoyIn3i34gMxfHzgrBkvVkXlVQlYdafOqKUeqKoE0Q0YV1MO6gC3l7Ie+yDTg/p3/wUV7gXzW/nF9WmVC//9uZk3+Jky70CTh6AYcy5jJw2tEdxaQsXDG08B9b6TEKaMXPVACbkpo1GBzP6IYPU1sMKQyNOZnmQznLl/8NQEfHwfSLgT8T3E9vse/kaSxcoceipxSnIzGfvwYCsSpacVwj2Tu9a9pENxelBlEkOJ8KWvVH04oOXuY3w31eaNlEl2rLHRy/OiiRwoZ2/eIrF/6qGguHL4yHGecKU7sX+rBxDGYVWJxk7fy2GDCk4O71MjOXAAdr4X/i9--4HuINgUYfZJpO7Em--eG5T4nEF1bughzTmprpy4w==
--------------------------------------------------------------------------------
/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: discors_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: discors
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: discors_test
61 |
62 | # As with config/secrets.yml, you never want to store sensitive information,
63 | # like your database password, in your source code. If your source code is
64 | # ever seen by anyone, they now have access to your database.
65 | #
66 | # Instead, provide the password as a unix environment variable when you boot
67 | # the app. Read http://guides.rubyonrails.org/configuring.html#configuring-a-database
68 | # for a full rundown on how to provide these environment variables in a
69 | # production deployment.
70 | #
71 | # On Heroku and other platform providers, you may have a full connection URL
72 | # available as an environment variable. For example:
73 | #
74 | # DATABASE_URL="postgres://myuser:mypass@localhost/somedatabase"
75 | #
76 | # You can use this database configuration with:
77 | #
78 | # production:
79 | # url: <%= ENV['DATABASE_URL'] %>
80 | #
81 | production:
82 | <<: *default
83 | database: discors_production
84 | username: discors
85 | password: <%= ENV['DISCORS_DATABASE_PASSWORD'] %>
86 |
--------------------------------------------------------------------------------
/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 | # Run rails dev:cache to toggle caching.
17 | if Rails.root.join('tmp', 'caching-dev.txt').exist?
18 | config.action_controller.perform_caching = true
19 |
20 | config.cache_store = :memory_store
21 | config.public_file_server.headers = {
22 | 'Cache-Control' => "public, max-age=#{2.days.to_i}"
23 | }
24 | else
25 | config.action_controller.perform_caching = false
26 |
27 | config.cache_store = :null_store
28 | end
29 |
30 | # Store uploaded files on the local file system (see config/storage.yml for options)
31 | config.active_storage.service = :local
32 |
33 | # Don't care if the mailer can't send.
34 | config.action_mailer.raise_delivery_errors = false
35 |
36 | config.action_mailer.perform_caching = false
37 |
38 | # Print deprecation notices to the Rails logger.
39 | config.active_support.deprecation = :log
40 |
41 | # Raise an error on page load if there are pending migrations.
42 | config.active_record.migration_error = :page_load
43 |
44 | # Highlight code that triggered database queries in logs.
45 | config.active_record.verbose_query_logs = true
46 |
47 | # Debug mode disables concatenation and preprocessing of assets.
48 | # This option may cause significant delays in view rendering with a large
49 | # number of complex assets.
50 | config.assets.debug = true
51 |
52 | # Suppress logger output for asset requests.
53 | config.assets.quiet = true
54 |
55 | # Raises error for missing translations
56 | # config.action_view.raise_on_missing_translations = true
57 |
58 | # Use an evented file watcher to asynchronously detect changes in source code,
59 | # routes, locales, etc. This feature depends on the listen gem.
60 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker
61 |
62 | config.active_storage.service = :amazon_dev
63 | end
64 |
--------------------------------------------------------------------------------
/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.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 |
31 | # Store uploaded files on the local file system in a temporary directory
32 | config.active_storage.service = :test
33 |
34 | config.action_mailer.perform_caching = false
35 |
36 | # Tell Action Mailer not to deliver emails to the real world.
37 | # The :test delivery method accumulates sent emails in the
38 | # ActionMailer::Base.deliveries array.
39 | config.action_mailer.delivery_method = :test
40 |
41 | # Print deprecation notices to the stderr.
42 | config.active_support.deprecation = :stderr
43 |
44 | # Raises error for missing translations
45 | # config.action_view.raise_on_missing_translations = true
46 | end
47 |
--------------------------------------------------------------------------------
/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/content_security_policy.rb:
--------------------------------------------------------------------------------
1 | # Be sure to restart your server when you modify this file.
2 |
3 | # Define an application-wide content security policy
4 | # For further information see the following documentation
5 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy
6 |
7 | # Rails.application.config.content_security_policy do |policy|
8 | # policy.default_src :self, :https
9 | # policy.font_src :self, :https, :data
10 | # policy.img_src :self, :https, :data
11 | # policy.object_src :none
12 | # policy.script_src :self, :https
13 | # policy.style_src :self, :https
14 |
15 | # # Specify URI for violation reports
16 | # # policy.report_uri "/csp-violation-report-endpoint"
17 | # end
18 |
19 | # If you are using UJS then enable automatic nonce generation
20 | # Rails.application.config.content_security_policy_nonce_generator = -> request { SecureRandom.base64(16) }
21 |
22 | # Report CSP violations to a specified URI
23 | # For further information see the following documentation:
24 | # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy-Report-Only
25 | # Rails.application.config.content_security_policy_report_only = true
26 |
--------------------------------------------------------------------------------
/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.
30 | #
31 | # preload_app!
32 |
33 | # Allow puma to be restarted by `rails restart` command.
34 | plugin :tmp_restart
35 |
--------------------------------------------------------------------------------
/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 | namespace :api, defaults: {format: :json} do
4 | resources :users, only: [:create, :update] do
5 | collection do
6 | get 'data'
7 | end
8 | end
9 | resource :session, only: [:create, :destroy]
10 | resources :channels, only: [:create, :index, :destroy]
11 | resources :audio_channels, only: [:create, :index, :destroy]
12 | resources :friend_requests, only: [:create, :destroy, :update]
13 | resources :friends, only: [:destroy]
14 | resources :dm_channel_memberships, only: [:destroy, :create]
15 | resources :servers, only: [:create, :index, :destroy] do
16 | collection do
17 | post 'join'
18 | end
19 | member do
20 | get 'members'
21 | end
22 | end
23 | end
24 |
25 | mount ActionCable.server, at: '/cable'
26 | root "static_pages#root"
27 |
28 | end
29 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/config/storage.yml:
--------------------------------------------------------------------------------
1 | test:
2 | service: Disk
3 | root: <%= Rails.root.join("tmp/storage") %>
4 |
5 | local:
6 | service: Disk
7 | root: <%= Rails.root.join("storage") %>
8 |
9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key)
10 | amazon_dev:
11 | service: S3
12 | access_key_id: <%= Rails.application.credentials.aws[:access_key_id] %>
13 | secret_access_key: <%= Rails.application.credentials.aws[:secret_access_key] %>
14 | region: <%= Rails.application.credentials.aws[:region] %>
15 | bucket: <%= Rails.application.credentials.aws[:dev][:bucket] %>
16 |
17 | amazon_prod:
18 | service: S3
19 | access_key_id: <%= Rails.application.credentials.aws[:access_key_id] %>
20 | secret_access_key: <%= Rails.application.credentials.aws[:secret_access_key] %>
21 | region: <%= Rails.application.credentials.aws[:region] %>
22 | bucket: <%= Rails.application.credentials.aws[:prod][:bucket] %>
23 | # Remember not to checkin your GCS keyfile to a repository
24 | # google:
25 | # service: GCS
26 | # project: your_project
27 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %>
28 | # bucket: your_own_bucket
29 |
30 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key)
31 | # microsoft:
32 | # service: AzureStorage
33 | # storage_account_name: your_account_name
34 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %>
35 | # container: your_container_name
36 |
37 | # mirror:
38 | # service: Mirror
39 | # primary: local
40 | # mirrors: [ amazon, google, microsoft ]
41 |
--------------------------------------------------------------------------------
/db/migrate/20190121174348_create_users.rb:
--------------------------------------------------------------------------------
1 | class CreateUsers < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :users do |t|
4 | t.string :username, null: false
5 | t.string :email, null: false
6 | t.string :image_url, null: false
7 | t.string :password_digest, null: false
8 | t.string :session_token, null: false
9 |
10 | t.timestamps
11 | end
12 | add_index :users, :username, unique: true
13 | add_index :users, :email, unique: true
14 | add_index :users, :session_token, unique: true
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/db/migrate/20190122185229_create_servers.rb:
--------------------------------------------------------------------------------
1 | class CreateServers < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :servers do |t|
4 | t.string :name, null: false
5 | t.integer :admin_id, null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :servers, :name, unique: true
10 | add_index :servers, :admin_id
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20190122190214_create_server_memberships.rb:
--------------------------------------------------------------------------------
1 | class CreateServerMemberships < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :server_memberships do |t|
4 | t.integer :user_id, null: false
5 | t.integer :server_id, null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :server_memberships, :user_id
10 | add_index :server_memberships, :server_id
11 | add_index :server_memberships, [:user_id, :server_id], unique: true
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20190123145743_create_channels.rb:
--------------------------------------------------------------------------------
1 | class CreateChannels < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :channels do |t|
4 | t.string :name, null: false
5 | t.integer :server_id
6 |
7 | t.timestamps
8 | end
9 | add_index :channels, :server_id
10 | add_index :channels, [:name, :server_id], unique: true
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20190124162319_create_messages.rb:
--------------------------------------------------------------------------------
1 | class CreateMessages < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :messages do |t|
4 | t.string :body, null: false
5 | t.integer :author_id, null: false
6 | t.integer :channel_id, null: false
7 |
8 | t.timestamps
9 | end
10 | add_index :messages, :author_id
11 | add_index :messages, :channel_id
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20190125020020_create_dm_channel_memberships.rb:
--------------------------------------------------------------------------------
1 | class CreateDmChannelMemberships < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :dm_channel_memberships do |t|
4 | t.integer :user_id, null: false
5 | t.integer :channel_id, null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :dm_channel_memberships, :user_id
10 | add_index :dm_channel_memberships, :channel_id
11 | add_index :dm_channel_memberships, [:user_id, :channel_id], unique: true
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20190126030608_create_friend_requests.rb:
--------------------------------------------------------------------------------
1 | class CreateFriendRequests < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :friend_requests do |t|
4 | t.integer :user_id, null: false
5 | t.integer :friend_id, null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :friend_requests, :user_id
10 | add_index :friend_requests, :friend_id
11 | add_index :friend_requests, [:user_id, :friend_id], unique: true
12 | add_index :channels, :name
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/db/migrate/20190126032316_create_friendships.rb:
--------------------------------------------------------------------------------
1 | class CreateFriendships < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :friendships do |t|
4 | t.integer :user_id, null: false
5 | t.integer :friend_id, null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :friendships, :user_id
10 | add_index :friendships, :friend_id
11 | add_index :friendships, [:user_id, :friend_id], unique: true
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/db/migrate/20190127215251_create_sessions.rb:
--------------------------------------------------------------------------------
1 | class CreateSessions < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :sessions do |t|
4 | t.integer "user_id", null: false
5 | t.string "session_token", null: false
6 |
7 | t.timestamps
8 | end
9 | add_index :sessions, :user_id
10 | add_index :sessions, :session_token
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20190129004713_create_voice_channels.rb:
--------------------------------------------------------------------------------
1 | class CreateVoiceChannels < ActiveRecord::Migration[5.2]
2 | def change
3 | create_table :voice_channels do |t|
4 | t.string :name, null: false
5 | t.integer :server_id
6 |
7 | t.timestamps
8 | end
9 | add_index :voice_channels, :server_id
10 | add_index :voice_channels, [:name, :server_id], unique: true
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/db/migrate/20190129010218_change_voice_channels_to_audio_channels.rb:
--------------------------------------------------------------------------------
1 | class ChangeVoiceChannelsToAudioChannels < ActiveRecord::Migration[5.2]
2 | def change
3 | rename_table :voice_channels, :audio_channels
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/db/migrate/20190129201658_create_active_storage_tables.active_storage.rb:
--------------------------------------------------------------------------------
1 | # This migration comes from active_storage (originally 20170806125915)
2 | class CreateActiveStorageTables < ActiveRecord::Migration[5.2]
3 | def change
4 | create_table :active_storage_blobs do |t|
5 | t.string :key, null: false
6 | t.string :filename, null: false
7 | t.string :content_type
8 | t.text :metadata
9 | t.bigint :byte_size, null: false
10 | t.string :checksum, null: false
11 | t.datetime :created_at, null: false
12 |
13 | t.index [ :key ], unique: true
14 | end
15 |
16 | create_table :active_storage_attachments do |t|
17 | t.string :name, null: false
18 | t.references :record, null: false, polymorphic: true, index: false
19 | t.references :blob, null: false
20 |
21 | t.datetime :created_at, null: false
22 |
23 | t.index [ :record_type, :record_id, :name, :blob_id ], name: "index_active_storage_attachments_uniqueness", unique: true
24 | t.foreign_key :active_storage_blobs, column: :blob_id
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/db/migrate/20190203031645_add_column_to_sessions.rb:
--------------------------------------------------------------------------------
1 | class AddColumnToSessions < ActiveRecord::Migration[5.2]
2 | def change
3 | add_column :sessions, :user_agent, :string, null: false
4 | add_index :sessions, :user_agent
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20190218200301_remove_session_token_from_users.rb:
--------------------------------------------------------------------------------
1 | class RemoveSessionTokenFromUsers < ActiveRecord::Migration[5.2]
2 | def change
3 | remove_column :users, :session_token
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/frontend/actions/channel_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIUtil from '../util/channel_api_util';
2 | import { receiveUsers, removeServer } from './server_actions';
3 |
4 | export const RECEIVE_CHANNELS = 'RECEIVE_CHANNELS';
5 | export const RECEIVE_CHANNEL = 'RECEIVE_CHANNEL';
6 | export const REMOVE_CHANNEL = 'REMOVE_CHANNEL';
7 | export const RECEIVE_CHANNEL_ERRORS = 'RECEIVE_CHANNEL_ERRORS';
8 | export const REMOVE_CHANNEL_ERRORS = 'REMOVE_CHANNEL_ERRORS';
9 |
10 |
11 | export const receiveChannels = channels => ({
12 | type: RECEIVE_CHANNELS,
13 | channels
14 | });
15 |
16 | export const receiveChannel = channel => ({
17 | type: RECEIVE_CHANNEL,
18 | channel
19 | });
20 |
21 | export const receiveDmChannel = channel => ({
22 | type: RECEIVE_CHANNEL,
23 | channel
24 | });
25 |
26 | export const removeChannel = channelId => ({
27 | type: REMOVE_CHANNEL,
28 | channelId
29 | });
30 |
31 | export const receiveErrors = errors => ({
32 | type: RECEIVE_CHANNEL_ERRORS,
33 | errors
34 | });
35 |
36 | export const removeChannelErrors = () => ({
37 | type: REMOVE_CHANNEL_ERRORS,
38 | });
39 |
40 |
41 | export const fetchChannels = (serverId, userId) => dispatch => (
42 | APIUtil.fetchChannels(serverId).then(channels => (
43 | dispatch(receiveChannels(channels))
44 | ), () => (
45 | dispatch(removeServer(serverId, userId))
46 | ))
47 | );
48 |
49 | export const deleteChannel = (id) => dispatch => (
50 | APIUtil.deleteChannel(id).then(() => (
51 | dispatch(removeChannel(id))
52 | ))
53 | );
54 |
55 | export const deleteDmChannel = (id) => dispatch => (
56 | APIUtil.deleteDmChannel(id).then((membership) => (
57 | dispatch(removeChannel(membership.channel_id))
58 | ))
59 | );
60 |
61 | export const fetchDmChannels = () => dispatch => (
62 | APIUtil.fetchDmChannels().then(({channels, users}) => {
63 | dispatch(receiveUsers(users));
64 | return dispatch(receiveChannels(channels));
65 | })
66 | );
67 |
68 | export const createChannel = (channel) => dispatch => (
69 | APIUtil.createChannel(channel).then(channel => (
70 | dispatch(receiveChannel(channel))
71 | ), err => (
72 | dispatch(receiveErrors(err.responseJSON))
73 | ))
74 | );
75 |
76 | export const createDmChannel = (userId) => dispatch => (
77 | APIUtil.createDmChannel(userId).then(channel => (
78 | dispatch(receiveChannel(channel))
79 | ))
80 | );
81 |
82 |
--------------------------------------------------------------------------------
/frontend/actions/friends_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIUtil from '../util/friends_api_util';
2 | import { receiveUsers } from './server_actions';
3 |
4 | export const RECEIVE_REQUESTS = 'RECEIVE_REQUESTS';
5 | export const RECEIVE_REQUEST = 'RECEIVE_REQUEST';
6 | export const REMOVE_REQUEST = 'REMOVE_REQUEST';
7 | export const REMOVE_FRIEND = 'REMOVE_FRIEND';
8 | export const RECEIVE_FRIENDS = 'RECEIVE_FRIENDS';
9 | export const RECEIVE_FRIEND = 'RECEIVE_FRIEND';
10 |
11 |
12 | export const receiveFriendRequests = friendRequests => ({
13 | type: RECEIVE_REQUESTS,
14 | friendRequests
15 | });
16 |
17 | export const receiveFriends = friendData => ({
18 | type: RECEIVE_FRIENDS,
19 | friendData
20 | });
21 |
22 | export const receiveFriend = friendId => ({
23 | type: RECEIVE_FRIEND,
24 | friend: [friendId]
25 | });
26 |
27 | export const receiveFriendRequest = friendRequest => ({
28 | type: RECEIVE_REQUEST,
29 | friendRequest
30 | });
31 |
32 | export const removeFriendRequest = requestId => ({
33 | type: REMOVE_REQUEST,
34 | requestId
35 | });
36 |
37 | export const removeFriend = friendId => ({
38 | type: REMOVE_FRIEND,
39 | friendId
40 | });
41 |
42 |
43 | export const fetchFriendRequests = () => dispatch => (
44 | APIUtil.fetchFriendRequests().then(({ requests, users }) => {
45 | dispatch(receiveUsers(users));
46 | return dispatch(receiveFriendRequests(requests));
47 | })
48 | );
49 |
50 | export const fetchFriends = () => dispatch => (
51 | APIUtil.fetchFriends().then(friends => (
52 | dispatch(receiveFriends(friends))
53 | ))
54 | );
55 |
56 | export const createFriendRequest = (friendRequest) => dispatch => (
57 | APIUtil.createFriendRequest(friendRequest).then(request => (
58 | dispatch(receiveFriendRequest(request))
59 | ))
60 | );
61 |
62 | export const acceptFriendRequest = (friendRequest) => dispatch => (
63 | APIUtil.acceptFriendRequest(friendRequest).then(() => {
64 | dispatch(removeFriendRequest(friendRequest.id));
65 | return dispatch(receiveFriend(friendRequest.user_id));
66 | })
67 | );
68 |
69 | export const deleteFriendRequest = (friendRequest) => dispatch => (
70 | APIUtil.deleteFriendRequest(friendRequest).then(() => (
71 | dispatch(removeFriendRequest(friendRequest.id))
72 | ))
73 | );
74 |
75 | export const deleteFriend = (id) => dispatch => (
76 | APIUtil.deleteFriend(id).then(() => (
77 | dispatch(removeFriend(id))
78 | ))
79 | );
--------------------------------------------------------------------------------
/frontend/actions/notification_actions.js:
--------------------------------------------------------------------------------
1 | export const RECEIVE_DM_NOTIFICATION = 'RECEIVE_DM_NOTIFICATION';
2 | export const REMOVE_DM_NOTIFICATION = 'REMOVE_DM_NOTIFICATION';
3 |
4 |
5 | export const receiveDmNotification = (notification) => ({
6 | type: RECEIVE_DM_NOTIFICATION,
7 | notification
8 | });
9 |
10 | export const removeDmNotification = (channelId) => ({
11 | type: REMOVE_DM_NOTIFICATION,
12 | channelId,
13 | });
--------------------------------------------------------------------------------
/frontend/actions/server_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIUtil from '../util/server_api_utl';
2 |
3 | export const RECEIVE_SERVERS = 'RECEIVE_SERVERS';
4 | export const RECEIVE_USERS = 'RECEIVE_USERS';
5 | export const RECEIVE_SERVER = 'RECEIVE_SERVER';
6 | export const RECEIVE_SERVER_ERRORS = 'RECEIVE_SERVER_ERRORS';
7 | export const REMOVE_SERVER_ERRORS = 'REMOVE_SERVER_ERRORS';
8 | export const REMOVE_SERVER = 'REMOVE_SERVER';
9 |
10 |
11 | export const receiveServers = servers => ({
12 | type: RECEIVE_SERVERS,
13 | servers
14 | });
15 |
16 | export const receiveUsers = users => ({
17 | type: RECEIVE_USERS,
18 | users
19 | });
20 |
21 | export const receiveServer = (server, userId) => ({
22 | type: RECEIVE_SERVER,
23 | server,
24 | userId
25 | });
26 |
27 | export const removeServer = (serverId, userId) => ({
28 | type: REMOVE_SERVER,
29 | serverId,
30 | userId
31 | });
32 |
33 | export const receiveErrors = errors => ({
34 | type: RECEIVE_SERVER_ERRORS,
35 | errors
36 | });
37 |
38 | export const removeServerErrors = () => ({
39 | type: REMOVE_SERVER_ERRORS,
40 | });
41 |
42 | export const fetchServers = () => dispatch => (
43 | APIUtil.fetchServers().then(servers => (
44 | dispatch(receiveServers(servers))
45 | ))
46 | );
47 |
48 | export const fetchMembers = (id) => dispatch => (
49 | APIUtil.fetchMembers(id).then(({users, requests}) => {
50 | return dispatch(receiveUsers(users));
51 | })
52 | );
53 |
54 | export const createServer = (formData) => dispatch => (
55 | APIUtil.createServer(formData).then(server => (
56 | dispatch(receiveServer(server, server.admin_id))
57 | ), err => (
58 | dispatch(receiveErrors(err.responseJSON))
59 | ))
60 | );
61 |
62 | export const deleteServer = (serverId, userId) => dispatch => (
63 | APIUtil.deleteServer(serverId).then(() => (
64 | dispatch(removeServer(serverId, userId))
65 | )
66 | )
67 | );
68 |
69 | export const joinServer = (server, userId) => dispatch => (
70 | APIUtil.joinServer(server).then(server => (
71 | dispatch(receiveServer(server, userId))
72 | ), err => (
73 | dispatch(receiveErrors(err.responseJSON))
74 | ))
75 | );
76 |
--------------------------------------------------------------------------------
/frontend/actions/session_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIUtil from '../util/session_api_util';
2 |
3 | export const RECEIVE_CURRENT_USER = 'RECEIVE_CURRENT_USER';
4 | export const RECEIVE_USER = 'RECEIVE_USER';
5 | export const RECEIVE_CURRENT_USER_DATA = 'RECEIVE_CURRENT_USER_DATA';
6 | export const LOGOUT_CURRENT_USER = 'LOGOUT_CURRENT_USER';
7 | export const RECEIVE_SESSION_ERRORS = 'RECEIVE_SESSION_ERRORS';
8 | export const REMOVE_SESSION_ERRORS = 'REMOVE_SESSION_ERRORS';
9 |
10 |
11 | export const receiveCurrentUser = currentUser => ({
12 | type: RECEIVE_CURRENT_USER,
13 | currentUser
14 | });
15 |
16 | export const receiveUser = user => ({
17 | type: RECEIVE_USER,
18 | user
19 | });
20 |
21 | export const receiveCurrentUserData = currentUserData => ({
22 | type: RECEIVE_CURRENT_USER_DATA,
23 | currentUserData
24 | });
25 |
26 | export const logoutCurrentUser = () => ({
27 | type: LOGOUT_CURRENT_USER,
28 | });
29 |
30 | export const receiveErrors = errors => ({
31 | type: RECEIVE_SESSION_ERRORS,
32 | errors
33 | });
34 |
35 | export const removeErrors = errors => ({
36 | type: REMOVE_SESSION_ERRORS,
37 | });
38 |
39 |
40 | export const signup = user => dispatch => (
41 | APIUtil.signup(user).then(user => (
42 | dispatch(receiveCurrentUser(user))
43 | ), err => (
44 | dispatch(receiveErrors(err.responseJSON))
45 | ))
46 | );
47 |
48 | export const editUser = formData => dispatch => (
49 | APIUtil.editUser(formData).then(user => (
50 | dispatch(receiveCurrentUser(user))
51 | ), err => (
52 | dispatch(receiveErrors(err.responseJSON))
53 | ))
54 | );
55 |
56 | export const login = user => dispatch => (
57 | APIUtil.login(user).then(user => (
58 | dispatch(receiveCurrentUser(user))
59 | ), err => (
60 | dispatch(receiveErrors(err.responseJSON))
61 | ))
62 | );
63 |
64 | export const fetchCurrentUserData = () => dispatch => (
65 | APIUtil.fetchCurrentUserData().then(userData => (
66 | dispatch(receiveCurrentUserData(userData))
67 | ))
68 | );
69 |
70 | export const logout = () => dispatch => (
71 | APIUtil.logout().then(user => (
72 | dispatch(logoutCurrentUser())
73 | ))
74 | );
--------------------------------------------------------------------------------
/frontend/actions/ui_actions.js:
--------------------------------------------------------------------------------
1 | export const BEGIN_LOADING = 'BEGIN_LOADING';
2 | export const FINISH_LOADING = 'FINISH_LOADING';
3 |
4 |
5 | export const beginLoading = () => ({
6 | type: BEGIN_LOADING,
7 | });
8 |
9 | export const finishLoading = () => ({
10 | type: FINISH_LOADING,
11 | });
--------------------------------------------------------------------------------
/frontend/actions/voice_channel_actions.js:
--------------------------------------------------------------------------------
1 | import * as APIUtil from '../util/voice_channel_api_util';
2 | import { receiveErrors } from './channel_actions';
3 |
4 | export const RECEIVE_VOICE_CHANNELS = 'RECEIVE_VOICE_CHANNELS';
5 | export const RECEIVE_VOICE_CHANNEL = 'RECEIVE_VOICE_CHANNEL';
6 | export const REMOVE_VOICE_CHANNEL = 'REMOVE_VOICE_CHANNEL';
7 | export const RECEIVE_VOICE_CHANNEL_ERRORS = 'RECEIVE_VOICE_CHANNEL_ERRORS';
8 | export const REMOVE_VOICE_CHANNEL_ERRORS = 'REMOVE_VOICE_CHANNEL_ERRORS';
9 |
10 |
11 | export const receiveVoiceChannels = voiceChannels => ({
12 | type: RECEIVE_VOICE_CHANNELS,
13 | voiceChannels
14 | });
15 |
16 | export const receiveVoiceChannel = voiceChannel => ({
17 | type: RECEIVE_VOICE_CHANNEL,
18 | voiceChannel
19 | });
20 |
21 | export const removeVoiceChannel = voiceChannelId => ({
22 | type: REMOVE_VOICE_CHANNEL,
23 | voiceChannelId
24 | });
25 |
26 |
27 | export const fetchVoiceChannels = (serverId) => dispatch => (
28 | APIUtil.fetchVoiceChannels(serverId).then(voiceChannels => (
29 | dispatch(receiveVoiceChannels(voiceChannels))
30 | ))
31 | );
32 |
33 | export const deleteVoiceChannel = (id) => dispatch => (
34 | APIUtil.deleteVoiceChannel(id).then(() => (
35 | dispatch(removeVoiceChannel(id))
36 | ))
37 | );
38 |
39 | export const createVoiceChannel = (voiceChannel) => dispatch => (
40 | APIUtil.createVoiceChannel(voiceChannel).then(voiceChannel => (
41 | dispatch(receiveVoiceChannel(voiceChannel))
42 | ), err => (
43 | dispatch(receiveErrors(err.responseJSON))
44 | ))
45 | );
--------------------------------------------------------------------------------
/frontend/components/app.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Route, Switch } from 'react-router-dom';
3 | import LoginForm from './session_form/login/login_form_container';
4 | import SignupForm from './session_form/signup/signup_form_container';
5 | import Splash from './splash/splash_container';
6 | import { AuthRoute, ProtectedRoute } from '../util/route_util';
7 | import AppRoot from './app/app_root';
8 |
9 | const App = () => (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 |
18 | export default App;
--------------------------------------------------------------------------------
/frontend/components/app/app_routes/friends_main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import FriendsHeader from '../friends/friends_header';
3 | import Friends from '../friends/friends_list/friends_container';
4 | import PendingFriends from '../friends/pending_friends/pending_friends_container';
5 | import OnlineFriends from '../friends/friends_list/online_friends_container';
6 |
7 | class FriendsMain extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.state = { tab: 'all' };
11 | this.changeTab = this.changeTab.bind(this);
12 | }
13 |
14 | changeTab(tabName) {
15 | this.setState({ tab: tabName });
16 | }
17 |
18 | render() {
19 | let component;
20 |
21 | if (this.state.tab === 'all') {
22 | component =
23 | } else if (this.state.tab === 'online') {
24 | component =
25 | } else {
26 | component =
27 | }
28 |
29 | return (
30 | <>
31 |
32 |
33 |
34 |
35 |
36 |
Name
37 |
38 |
Status
39 |
40 |
Mutual Servers
41 |
42 |
43 |
44 | {component}
45 |
46 |
47 |
48 | >
49 | )
50 | }
51 | }
52 |
53 | export default FriendsMain;
54 |
--------------------------------------------------------------------------------
/frontend/components/app/app_routes/me_main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Chat from '../chat/chat/chat_container';
3 | import MeHeader from '../header/me_header/me_header_container';
4 |
5 | class MeMain extends React.Component {
6 | render() {
7 | return (
8 | <>
9 |
15 | >
16 | )
17 | }
18 | }
19 |
20 | export default MeMain;
--------------------------------------------------------------------------------
/frontend/components/app/app_routes/me_route.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DMChannels from '../channels/dm_channels/dm_channels_container';
3 | import MeMain from './me_main';
4 | import { Route, Switch } from 'react-router-dom';
5 | import FriendsMain from './friends_main';
6 |
7 | class MeRoute extends React.Component {
8 | render() {
9 | return (
10 | <>
11 |
12 |
13 |
14 |
15 |
16 | >
17 | )
18 | }
19 | }
20 |
21 | export default MeRoute;
22 |
--------------------------------------------------------------------------------
/frontend/components/app/app_routes/server_main.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Header from '../header/header/header_container';
3 | import Chat from '../chat/chat/chat_container';
4 | import ServerMembers from '../server_members/server_members/server_members_container';
5 |
6 | class ServerMain extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = { showMembers: true, classText: 'members-transition-left' };
10 | this.toggleShowMembers = this.toggleShowMembers.bind(this);
11 | }
12 |
13 | toggleShowMembers() {
14 | const { showMembers } = this.state;
15 |
16 | if (showMembers) {
17 | this.setState({ classText: "members-transition-right" });
18 | setTimeout(() => this.setState({ showMembers: !showMembers }), 200);
19 | } else {
20 | this.setState({ showMembers: !showMembers });
21 | setTimeout(() => this.setState({ classText: 'members-transition-left' }), 1);
22 | }
23 | }
24 |
25 | render() {
26 | return (
27 |
28 |
29 |
30 |
31 | {this.state.showMembers ? : null}
32 |
33 |
34 | )
35 | }
36 | }
37 |
38 | export default ServerMain;
--------------------------------------------------------------------------------
/frontend/components/app/app_routes/server_route.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Channels from '../channels/channels/channels_container';
3 | import ServerMain from './server_main';
4 |
5 | class ServerRoute extends React.Component {
6 | render() {
7 | return (
8 | <>
9 |
10 |
11 | >
12 | )
13 | }
14 | }
15 |
16 | export default ServerRoute;
17 |
--------------------------------------------------------------------------------
/frontend/components/app/channels/channels/channel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink, withRouter } from 'react-router-dom';
3 | import Tooltip from '../../modal/tooltip';
4 |
5 | class Channel extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = { active: false };
9 |
10 | this.handleMouseEnter = this.handleMouseEnter.bind(this);
11 | this.handleMouseLeave = this.handleMouseLeave.bind(this);
12 | this.handleDelete = this.handleDelete.bind(this);
13 | }
14 |
15 | handleMouseEnter() {
16 | if (this.props.channel.name !== 'general' && this.props.currentUser.id === this.props.server.admin_id) {
17 | this.setState({ active: true });
18 | }
19 | }
20 |
21 | handleMouseLeave() {
22 | if (this.props.channel.name !== 'general' && this.props.currentUser.id === this.props.server.admin_id) {
23 | this.setState({ active: false });
24 | }
25 | }
26 |
27 | handleDelete(e) {
28 | e.preventDefault();
29 | const that = this;
30 | this.props.deleteChannel().then((action) => {
31 | if (action.channelId == that.props.match.params.channelId) {
32 | that.props.history.push(`/channels/${that.props.server.id}/${that.props.server.root_channel}`);
33 | }
34 | });
35 | }
36 |
37 | render() {
38 | return (
39 |
45 |
49 | {this.props.channel.name}
50 | {this.state.active ?
51 | (
53 | }
54 | position="top right"
55 | text="Delete Channel"
56 | />) : null}
57 |
58 | )
59 | };
60 | };
61 |
62 | export default withRouter(Channel);
--------------------------------------------------------------------------------
/frontend/components/app/channels/channels/channels_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import Channels from './channels';
3 | import { fetchChannels, createChannel, removeChannelErrors, deleteChannel } from '../../../../actions/channel_actions';
4 | import { withRouter } from 'react-router-dom';
5 | import { deleteServer } from '../../../../actions/server_actions';
6 |
7 | const mapStateToProps = (state, ownProps) => {
8 | const serverId = ownProps.match.params.serverId;
9 | const channels = Object.values(state.entities.channels).filter(channel => channel.server_id == serverId);
10 |
11 | return {
12 | currentUser: state.entities.users[state.session.id],
13 | server: state.entities.servers[serverId] || {},
14 | channels,
15 | errors: state.errors.channel,
16 | };
17 | };
18 |
19 | const mapDispatchToProps = dispatch => {
20 | return {
21 | fetchChannels: (serverId, userId) => dispatch(fetchChannels(serverId, userId)),
22 | createChannel: channel => dispatch(createChannel(channel)),
23 | removeChannelErrors: () => dispatch(removeChannelErrors()),
24 | deleteServer: (serverId, userId) => dispatch(deleteServer(serverId, userId)),
25 | deleteChannel: (id) => dispatch(deleteChannel(id)),
26 | };
27 | };
28 |
29 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Channels));
30 |
--------------------------------------------------------------------------------
/frontend/components/app/channels/create_channel_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import merge from 'lodash/merge';
3 |
4 | class CreateChannelForm extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = { name: '' };
8 | this.handleSubmit = this.handleSubmit.bind(this);
9 | this.update = this.update.bind(this);
10 | }
11 |
12 | componentDidMount() {
13 | this.nameInput.focus();
14 | }
15 |
16 | update(e) {
17 | this.setState({
18 | name: e.currentTarget.value
19 | });
20 | }
21 |
22 | handleSubmit(e) {
23 | e.preventDefault();
24 | this.props.createChannel(merge(this.state, {server_id: this.props.server.id})).then(this.props.closeModal);
25 | }
26 |
27 | render() {
28 | return (
29 |
51 | )
52 | }
53 | }
54 |
55 | export default CreateChannelForm;
56 |
--------------------------------------------------------------------------------
/frontend/components/app/channels/dm_channels/dm_channel.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink, withRouter } from 'react-router-dom';
3 |
4 | class DmChannel extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = { active: false };
8 |
9 | this.handleMouseEnter = this.handleMouseEnter.bind(this);
10 | this.handleMouseLeave = this.handleMouseLeave.bind(this);
11 | this.handleDelete = this.handleDelete.bind(this);
12 | }
13 |
14 | handleMouseEnter() {
15 | this.setState({ active: true });
16 | }
17 |
18 | handleMouseLeave() {
19 | this.setState({ active: false });
20 | }
21 |
22 | handleDelete(e) {
23 | e.preventDefault();
24 | const channelId = this.props.channel.id;
25 | const notification = document.getElementById(`dm-notification-${channelId}`);
26 |
27 | if (notification) {
28 | notification.className = 'dm-notification';
29 | setTimeout(() => this.props.removeDmNotification(channelId), 200);
30 | }
31 |
32 | const that = this;
33 | this.props.deleteDmChannel().then((action) => {
34 | if (action.channelId == that.props.match.params.channelId) {
35 | that.props.history.push(`/channels/@me`);
36 | }
37 | });
38 | }
39 |
40 | render() {
41 | return (
42 |
49 |
55 | {this.props.user.username}
56 | {this.state.active ? : null}
57 |
58 | )
59 | };
60 | };
61 |
62 | export default withRouter(DmChannel);
--------------------------------------------------------------------------------
/frontend/components/app/channels/dm_channels/dm_channels.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import DmChannel from './dm_channel';
3 | import UserBar from '../user_bar/user_bar_container';
4 | import { NavLink } from 'react-router-dom';
5 |
6 | class DMChannels extends React.Component {
7 | render() {
8 | const that = this;
9 | const channels = this.props.channels.map((channel, idx) => {
10 | const nameArr = channel.name.split('-');
11 | let userId;
12 |
13 | if (nameArr[0] == this.props.currentUser.id) {
14 | userId = nameArr[1];
15 | } else {
16 | userId = nameArr[0];
17 | }
18 |
19 | return this.props.deleteDmChannel(channel.id)}
24 | removeDmNotification={this.props.removeDmNotification}
25 | />;
26 | });
27 |
28 | return (
29 |
30 |
31 |
36 |
37 | Friends
38 | {this.props.requestCount ? {this.props.requestCount}
: null}
39 |
40 |
41 |
42 |
43 |
44 |
45 |
DIRECT MESSAGES
46 |
47 |
48 | {channels}
49 |
50 |
51 |
52 |
53 |
54 |
55 | )
56 | }
57 | }
58 |
59 | export default DMChannels;
--------------------------------------------------------------------------------
/frontend/components/app/channels/dm_channels/dm_channels_container.jsx:
--------------------------------------------------------------------------------
1 | import { withRouter } from 'react-router-dom';
2 | import { logout } from '../../../../actions/session_actions';
3 | import DMChannels from './dm_channels';
4 | import { connect } from 'react-redux';
5 | import { fetchDmChannels, deleteDmChannel } from '../../../../actions/channel_actions';
6 | import { removeDmNotification } from '../../../../actions/notification_actions';
7 |
8 | const mapStateToProps = (state) => {
9 | const incomingRequests = Object.values(state.entities.friendRequests).filter((request) => {
10 | return request.friend_id === state.session.id;
11 | }).length;
12 |
13 | return {
14 | currentUser: state.entities.users[state.session.id],
15 | channels: Object.values(state.entities.channels).filter(channel => !channel.server_id),
16 | users: state.entities.users,
17 | requestCount: incomingRequests,
18 | };
19 | };
20 |
21 | const mapDispatchToProps = dispatch => {
22 | return {
23 | fetchDmChannels: () => dispatch(fetchDmChannels()),
24 | logout: () => dispatch(logout()),
25 | deleteDmChannel: id => dispatch(deleteDmChannel(id)),
26 | removeDmNotification: channelId => dispatch(removeDmNotification(channelId)),
27 | };
28 | };
29 |
30 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(DMChannels));
31 |
--------------------------------------------------------------------------------
/frontend/components/app/channels/user_bar/user_bar.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Modal from 'react-modal';
3 | import Tooltip from '../../modal/tooltip';
4 | import EditUserForm from '../edit_user_form';
5 |
6 | class UserBar extends React.Component {
7 | constructor(props) {
8 | super(props);
9 | this.state = { showUserModal: false };
10 | this.handleOpenUserModal = this.handleOpenUserModal.bind(this);
11 | this.handleCloseUserModal = this.handleCloseUserModal.bind(this);
12 | }
13 |
14 | handleOpenUserModal() {
15 | this.setState({ showUserModal: true });
16 | }
17 |
18 | handleCloseUserModal() {
19 | this.setState({ showUserModal: false });
20 | this.props.removeErrors();
21 | }
22 |
23 | render() {
24 | return (
25 | <>
26 |
27 |
35 | }
36 | position="top center"
37 | text="Update Profile"
38 | />
39 |
{this.props.currentUser.username}
40 |
41 | this.props.logout()}>
43 |
44 |
45 | }
46 | position="top center"
47 | text="Logout"
48 | />
49 |
50 |
75 |
81 |
82 | >
83 | )
84 | }
85 | }
86 |
87 | export default UserBar;
88 |
--------------------------------------------------------------------------------
/frontend/components/app/channels/user_bar/user_bar_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { withRouter } from 'react-router-dom';
3 | import { logout, editUser, removeErrors } from '../../../../actions/session_actions';
4 | import UserBar from './user_bar';
5 |
6 | const mapStateToProps = (state) => {
7 | return {
8 | currentUser: state.entities.users[state.session.id],
9 | errors: state.errors.session,
10 | };
11 | };
12 |
13 | const mapDispatchToProps = dispatch => {
14 | return {
15 | logout: () => dispatch(logout()),
16 | editUser: formData => dispatch(editUser(formData)),
17 | removeErrors: () => dispatch(removeErrors()),
18 | };
19 | };
20 |
21 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(UserBar));
--------------------------------------------------------------------------------
/frontend/components/app/channels/voice_channels/voice_channels_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import VoiceChannels from './voice_channels';
3 | import { withRouter } from 'react-router-dom';
4 | import { fetchVoiceChannels, createVoiceChannel, deleteVoiceChannel } from '../../../../actions/voice_channel_actions';
5 | import { removeChannelErrors } from '../../../../actions/channel_actions';
6 |
7 | const mapStateToProps = (state, ownProps) => {
8 | const serverId = ownProps.match.params.serverId;
9 | const voiceChannels = Object.values(state.entities.voiceChannels).filter(channel => channel.server_id == serverId);
10 |
11 | return {
12 | currentUser: state.entities.users[state.session.id],
13 | server: state.entities.servers[serverId] || {},
14 | voiceChannels,
15 | errors: state.errors.channel,
16 | };
17 | };
18 |
19 | const mapDispatchToProps = dispatch => {
20 | return {
21 | fetchVoiceChannels: serverId => dispatch(fetchVoiceChannels(serverId)),
22 | createVoiceChannel: voiceChannel => dispatch(createVoiceChannel(voiceChannel)),
23 | removeChannelErrors: () => dispatch(removeChannelErrors()),
24 | deleteVoiceChannel: (id) => dispatch(deleteVoiceChannel(id)),
25 | };
26 | };
27 |
28 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(VoiceChannels));
--------------------------------------------------------------------------------
/frontend/components/app/chat/chat/chat_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { withRouter } from 'react-router-dom';
3 | import Chat from './chat';
4 | import { removeDmNotification } from '../../../../actions/notification_actions';
5 | import { removeChannel } from '../../../../actions/channel_actions';
6 |
7 | const mapStateToProps = (state, ownProps) => {
8 | const channelId = ownProps.match.params.channelId;
9 | const channel = state.entities.channels[channelId] || {};
10 | const server = state.entities.servers[channel.server_id] || {};
11 |
12 | return {
13 | currentUser: state.entities.users[state.session.id],
14 | channelId,
15 | users: state.entities.users,
16 | channel,
17 | server,
18 | loading: state.ui.loading,
19 | };
20 | };
21 |
22 | const mapDispatchToProps = dispatch => {
23 | return {
24 | removeDmNotification: channelId => dispatch(removeDmNotification(channelId)),
25 | removeChannel: channelId => dispatch(removeChannel(channelId)),
26 | };
27 | };
28 |
29 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Chat));
30 |
--------------------------------------------------------------------------------
/frontend/components/app/chat/message_form.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { merge } from 'lodash';
3 | import { withRouter } from 'react-router-dom';
4 |
5 | class MessageForm extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = {
9 | body: "",
10 | author_id: props.user.id,
11 | };
12 |
13 | this.messageInput = React.createRef();
14 | this.handleSubmit = this.handleSubmit.bind(this);
15 | this.update = this.update.bind(this);
16 | }
17 |
18 | update(e) {
19 | this.setState({ body: e.currentTarget.value });
20 | }
21 |
22 | componentDidMount() {
23 | this.messageInput.current.focus();
24 | }
25 |
26 | componentDidUpdate(prevProps) {
27 | const channelId = this.props.match.params.channelId;
28 |
29 | if (prevProps.channel && prevProps.channel.id !== channelId) {
30 | this.messageInput.current.focus();
31 | }
32 | }
33 |
34 | handleSubmit(e) {
35 | e.preventDefault();
36 | this.props.subscription.speak({ message: merge({ channel_id: this.props.channelId }, this.state) });
37 | this.setState({ body: "" });
38 | }
39 |
40 | render() {
41 | let placeholder = '';
42 |
43 | if (this.props.channel.name && this.props.match.params.serverId) {
44 | placeholder = `Message #${this.props.channel.name}`;
45 | } else if (this.props.channel.name) {
46 | const nameArr = this.props.channel.name.split('-');
47 | let userId;
48 |
49 | if (nameArr[0] == this.props.user.id) {
50 | userId = nameArr[1];
51 | } else {
52 | userId = nameArr[0];
53 | }
54 |
55 | placeholder = `Message @${this.props.users[userId] ? this.props.users[userId].username : null}`;
56 | }
57 |
58 | return (
59 |
71 | );
72 | }
73 | }
74 |
75 | export default withRouter(MessageForm);
--------------------------------------------------------------------------------
/frontend/components/app/friends/friend.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router-dom';
3 | import { intersection } from 'lodash';
4 | import Tooltip from '../modal/tooltip';
5 | import MutualServer from './mutual_server';
6 |
7 | class Friend extends React.Component {
8 | constructor(props) {
9 | super(props);
10 | this.handleClickParent = this.handleClickParent.bind(this);
11 | this.handleReject = this.handleReject.bind(this);
12 | this.handleAccept = this.handleAccept.bind(this);
13 | }
14 |
15 | handleClickParent() {
16 | this.props.createDmChannel(this.props.user.id).then((action) => this.props.history.push(`/channels/@me/${action.channel.id}`));
17 | }
18 |
19 | handleReject(e) {
20 | e.stopPropagation();
21 | this.props.reject();
22 | }
23 |
24 | handleAccept(e) {
25 | e.stopPropagation();
26 | this.props.accept();
27 | }
28 |
29 | render() {
30 | const mutualServerIds = intersection(this.props.currentUser.servers, this.props.user.servers);
31 |
32 | let mutualServers;
33 | if (mutualServerIds.length <= 6) {
34 | mutualServers = mutualServerIds.map((serverId, idx) => {
35 | const server = this.props.servers[serverId] || {};
36 | return ;
37 | });
38 | } else {
39 | mutualServers = [];
40 |
41 | for (let i = 0; i < 5; i++) {
42 | const serverId = mutualServerIds[i];
43 | const server = this.props.servers[serverId] || {};
44 | mutualServers.push();
45 | }
46 |
47 | mutualServers.push({`+${mutualServerIds.length - 5}`}
);
48 | }
49 |
50 | return (
51 |
55 |
56 |
57 |
{this.props.user.username}
58 |
59 |
60 |
64 |
{this.props.status}
65 |
66 |
67 | {mutualServers}
68 |
69 |
70 | {this.props.status === 'Incoming friend request' ?
71 |
73 | }
74 | position="top center"
75 | text='Accept'
76 | />
77 | : null}
78 |
83 | }
84 | position="top center"
85 | text={this.props.status.includes('request') ? (this.props.status.includes('Outgoing') ? "Cancel" : "Ignore") : "Remove Friend"}
86 | />
87 |
88 |
89 | )
90 | }
91 | }
92 |
93 | export default withRouter(Friend);
--------------------------------------------------------------------------------
/frontend/components/app/friends/friends_header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | class FriendsHeader extends React.Component {
5 | render() {
6 | return (
7 |
22 | )
23 | }
24 | }
25 |
26 | const mapStateToProps = (state, ownProps) => {
27 | const incomingRequests = Object.values(state.entities.friendRequests).filter((request) => {
28 | return request.friend_id === state.session.id;
29 | }).length;
30 |
31 | return {
32 | requestCount: incomingRequests,
33 | };
34 | };
35 |
36 | export default connect(mapStateToProps)(FriendsHeader);
--------------------------------------------------------------------------------
/frontend/components/app/friends/friends_list/friends.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Friend from '../friend';
3 |
4 | class Friends extends React.Component {
5 | render() {
6 | const friends = this.props.friends.map((friend, idx) => {
7 | return this.props.deleteFriend(friend.id)}
11 | createDmChannel={this.props.createDmChannel}
12 | status={friend.online ? 'Online' : 'Offline'}
13 | first={idx === 0}
14 | currentUser={this.props.currentUser}
15 | servers={this.props.servers}
16 | />;
17 | });
18 |
19 | return (
20 |
21 |
22 | {friends}
23 | {friends.length === 0 ? (this.props.allFriends ? (
24 |
25 |
26 |
Wumpus has no friends. You could though!
27 |
28 | ) : (
29 |
30 |
31 |
No one's around to play with Wumpus.
32 |
33 | )) : null}
34 |
35 |
36 | )
37 | }
38 | }
39 |
40 | export default Friends;
--------------------------------------------------------------------------------
/frontend/components/app/friends/friends_list/friends_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import Friends from './friends';
3 | import { deleteFriend } from '../../../../actions/friends_actions';
4 | import { createDmChannel } from '../../../../actions/channel_actions';
5 |
6 | const mapStateToProps = (state) => {
7 | const friends = state.entities.friends.map((id) => {
8 | return state.entities.users[id] || {};
9 | }).sort((a, b) => {
10 | if (a.username < b.username) { return -1; }
11 | if (a.username > b.username) { return 1; }
12 | return 0;
13 | });
14 |
15 | return {
16 | friends,
17 | currentUser: state.entities.users[state.session.id],
18 | servers: state.entities.servers,
19 | allFriends: true
20 | };
21 | };
22 |
23 | const mapDispatchToProps = dispatch => {
24 | return {
25 | deleteFriend: (id) => dispatch(deleteFriend(id)),
26 | createDmChannel: userId => dispatch(createDmChannel(userId)),
27 | };
28 | };
29 |
30 | export default connect(mapStateToProps, mapDispatchToProps)(Friends);
--------------------------------------------------------------------------------
/frontend/components/app/friends/friends_list/online_friends_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import Friends from './friends';
3 | import { fetchFriends, deleteFriend } from '../../../../actions/friends_actions';
4 | import { createDmChannel } from '../../../../actions/channel_actions';
5 |
6 |
7 | const mapStateToProps = (state, ownProps) => {
8 | const friends = state.entities.friends.map((id) => {
9 | return state.entities.users[id] || {};
10 | }).filter((friend) => {
11 | return friend.online;
12 | });
13 |
14 | return {
15 | friends,
16 | currentUser: state.entities.users[state.session.id],
17 | servers: state.entities.servers,
18 | };
19 | };
20 |
21 | const mapDispatchToProps = dispatch => {
22 | return {
23 | fetchFriends: () => dispatch(fetchFriends()),
24 | deleteFriend: (id) => dispatch(deleteFriend(id)),
25 | createDmChannel: userId => dispatch(createDmChannel(userId)),
26 | };
27 | };
28 |
29 | export default connect(mapStateToProps, mapDispatchToProps)(Friends);
--------------------------------------------------------------------------------
/frontend/components/app/friends/mutual_server.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Tooltip from '../modal/tooltip';
3 | import { Link } from 'react-router-dom';
4 |
5 | class MutualServer extends React.Component {
6 | render() {
7 | return (
8 | e.stopPropagation()}
11 | className="mutual-server"
12 | style={this.props.server.icon_url ?
13 | { backgroundImage: `url(${this.props.server.icon_url})`, backgroundSize: '100%' } :
14 | { backgroundSize: '100%' }}
15 | onKeyDown={(e) => e.preventDefault()}
16 | >
17 | }
18 | position="top center"
19 | text={this.props.server.name}
20 | />
21 | )
22 | }
23 | }
24 |
25 | export default MutualServer;
--------------------------------------------------------------------------------
/frontend/components/app/friends/pending_friends/pending_friends.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Friend from '../friend';
3 |
4 | class PendingFriends extends React.Component {
5 | render() {
6 | const incomingFriends = this.props.incomingRequests.map((request, idx) => {
7 | const user = this.props.users[request.user_id];
8 | return this.props.acceptFriendRequest(request)}
13 | reject={() => this.props.deleteFriendRequest(request)}
14 | createDmChannel={this.props.createDmChannel}
15 | first={idx === 0}
16 | currentUser={this.props.currentUser}
17 | servers={this.props.servers}
18 | />;
19 | });
20 |
21 | const outgoingFriends = this.props.outgoingRequests.map((request, idx) => {
22 | const user = this.props.users[request.friend_id];
23 | return this.props.acceptFriendRequest(request)}
28 | reject={() => this.props.deleteFriendRequest(request)}
29 | createDmChannel={this.props.createDmChannel}
30 | first={idx === 0 && incomingFriends.empty}
31 | currentUser={this.props.currentUser}
32 | servers={this.props.servers}
33 | />;
34 | });
35 |
36 | return (
37 |
38 |
39 | {incomingFriends}
40 | {outgoingFriends}
41 | {incomingFriends.length === 0 && outgoingFriends.length === 0 ?
42 |
43 |
44 |
There are no pending friend requests. Here's a Wumpus for now.
45 |
46 | : null}
47 |
48 |
49 | )
50 | }
51 | }
52 |
53 | export default PendingFriends;
--------------------------------------------------------------------------------
/frontend/components/app/friends/pending_friends/pending_friends_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import PendingFriends from './pending_friends';
3 | import { fetchFriendRequests, acceptFriendRequest, deleteFriendRequest } from '../../../../actions/friends_actions';
4 | import { createDmChannel } from '../../../../actions/channel_actions';
5 |
6 | const mapStateToProps = (state) => {
7 | const outgoingRequests = Object.values(state.entities.friendRequests).filter((request) => {
8 | return request.user_id === state.session.id;
9 | });
10 |
11 | const incomingRequests = Object.values(state.entities.friendRequests).filter((request) => {
12 | return request.friend_id === state.session.id;
13 | });
14 |
15 | return {
16 | users: state.entities.users,
17 | incomingRequests,
18 | outgoingRequests,
19 | currentUser: state.entities.users[state.session.id],
20 | servers: state.entities.servers
21 | };
22 | };
23 |
24 | const mapDispatchToProps = dispatch => {
25 | return {
26 | fetchFriendRequests: () => dispatch(fetchFriendRequests()),
27 | acceptFriendRequest: (request) => dispatch(acceptFriendRequest(request)),
28 | deleteFriendRequest: (request) => dispatch(deleteFriendRequest(request)),
29 | createDmChannel: userId => dispatch(createDmChannel(userId)),
30 | };
31 | };
32 |
33 | export default connect(mapStateToProps, mapDispatchToProps)(PendingFriends);
--------------------------------------------------------------------------------
/frontend/components/app/header/header/header.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Tooltip from '../../modal/tooltip';
3 |
4 | class Header extends React.Component {
5 | render() {
6 | return (
7 |
8 |
9 |
13 |
{this.props.channel.name}
14 |
15 |
23 | }
24 | position="bottom right"
25 | text="Member List"
26 | />
27 |
28 | )
29 | }
30 | }
31 |
32 | export default Header;
33 |
--------------------------------------------------------------------------------
/frontend/components/app/header/header/header_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import Header from './header';
3 | import { withRouter } from 'react-router-dom';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | channel: state.entities.channels[ownProps.match.params.channelId] || {},
8 | };
9 | };
10 |
11 | export default withRouter(connect(mapStateToProps)(Header));
12 |
--------------------------------------------------------------------------------
/frontend/components/app/header/me_header/me_header_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { withRouter } from 'react-router-dom';
3 | import MeHeader from './me_header';
4 |
5 | const mapStateToProps = (state, ownProps) => {
6 | return {
7 | currentUser: state.entities.users[state.session.id],
8 | users: state.entities.users,
9 | channel: state.entities.channels[ownProps.match.params.channelId] || {},
10 | };
11 | };
12 |
13 | export default withRouter(connect(mapStateToProps)(MeHeader));
14 |
--------------------------------------------------------------------------------
/frontend/components/app/loading_screen.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const LoadingScreen = (props) => {
4 | return (
5 |
6 |
11 |
{props.text}
12 |
13 | )
14 | }
15 |
16 | export default LoadingScreen;
17 |
18 |
--------------------------------------------------------------------------------
/frontend/components/app/modal/tooltip.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import Popup from 'reactjs-popup';
3 |
4 | class Tooltip extends React.Component {
5 | render() {
6 | return (
7 |
33 | {this.props.text}
34 |
35 | );
36 | }
37 | }
38 |
39 | export default Tooltip;
--------------------------------------------------------------------------------
/frontend/components/app/modal/user_popup_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { createDmChannel } from '../../../actions/channel_actions';
3 | import { createFriendRequest } from '../../../actions/friends_actions';
4 | import UserPopup from './user_popup';
5 | import { withRouter } from 'react-router-dom';
6 |
7 | const mapStateToProps = (state) => {
8 | return {
9 | currentUserId: state.session.id,
10 | friendRequests: Object.values(state.entities.friendRequests),
11 | friends: state.entities.friends,
12 | };
13 | };
14 |
15 | const mapDispatchToProps = dispatch => {
16 | return {
17 | createDmChannel: userId => dispatch(createDmChannel(userId)),
18 | createFriendRequest: request => dispatch(createFriendRequest(request))
19 | };
20 | };
21 |
22 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(UserPopup));
23 |
--------------------------------------------------------------------------------
/frontend/components/app/server_members/server_member.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router-dom';
3 | import UserPopup from '../modal/user_popup_container';
4 |
5 | class ServerMember extends React.Component {
6 |
7 | transition() {
8 | const popUp = document.getElementsByClassName('popup-content');
9 | popUp[0].style.transform = 'translate(10px, 0)';
10 | }
11 |
12 | render() {
13 | return (
14 |
16 |
22 | {this.props.user.username}
23 |
24 | }
25 | onOpen={this.transition}
26 | user={this.props.user}
27 | position={"left center"}
28 | offsetX={-5}
29 | offsetY={this.props.admin ? 10 : -90}
30 | showMessageButton={true}
31 | />
32 | )
33 | }
34 | }
35 |
36 | export default withRouter(ServerMember);
--------------------------------------------------------------------------------
/frontend/components/app/server_members/server_members/server_members.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ServerMember from '../server_member';
3 |
4 | class ServerMembers extends React.Component {
5 |
6 | componentWillMount() {
7 | this.props.beginLoading();
8 | }
9 |
10 | componentDidMount() {
11 | this.props.fetchMembers(this.props.match.params.serverId).then(this.props.finishLoading);
12 | }
13 |
14 | componentWillUpdate(prevProps) {
15 | if (prevProps.server.id && prevProps.server.id != this.props.match.params.serverId) {
16 | this.props.beginLoading();
17 | }
18 | }
19 |
20 | componentDidUpdate(prevProps) {
21 | if (prevProps.server.id && prevProps.server.id != this.props.match.params.serverId) {
22 | this.props.fetchMembers(this.props.match.params.serverId).then(this.props.finishLoading);
23 | }
24 | }
25 |
26 | render() {
27 | const admin = this.props.members.find((member) => {
28 | return member.id === this.props.server.admin_id;
29 | });
30 |
31 | let adminEl;
32 | let adminOnline;
33 | let adminOffline;
34 |
35 | if (admin) {
36 | adminEl = ;
37 | if (admin.online) {
38 | adminOnline = 1;
39 | adminOffline = 0;
40 | } else {
41 | adminOnline = 0;
42 | adminOffline = 1;
43 | }
44 | } else {
45 | adminEl = null;
46 | }
47 |
48 | const online = this.props.members.filter(member => member.online);
49 | const offline = this.props.members.filter(member => !member.online);
50 |
51 | const onlineMembers = online.map((member, idx) => {
52 | if (member.id === this.props.server.admin_id) {
53 | return null;
54 | } else {
55 | return ;
56 | }
57 | });
58 |
59 | const offlineMembers = offline.map((member, idx) => {
60 | if (member.id === this.props.server.admin_id) {
61 | return null
62 | } else {
63 | return ;
64 | }
65 | });
66 |
67 | return (
68 |
69 |
70 |
71 |
ADMIN
72 | {adminEl}
73 | {online.length - adminOnline > 0 ? {`ONLINE—${online.length - adminOnline}`}
: null}
74 | {onlineMembers}
75 | {offline.length - adminOffline > 0 ? {`OFFLINE—${offline.length - adminOffline}`}
: null}
76 | {offlineMembers}
77 |
78 |
79 |
80 | )
81 | }
82 | }
83 |
84 | export default ServerMembers;
--------------------------------------------------------------------------------
/frontend/components/app/server_members/server_members/server_members_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { withRouter } from 'react-router-dom';
3 | import ServerMembers from './server_members';
4 | import { fetchMembers } from '../../../../actions/server_actions';
5 | import { beginLoading, finishLoading } from '../../../../actions/ui_actions';
6 |
7 | const mapStateToProps = (state, ownProps) => {
8 | const server = state.entities.servers[ownProps.match.params.serverId] || {};
9 | return {
10 | server,
11 | members: Object.values(state.entities.users).filter(user => user.servers.includes(server.id)).sort((a, b) => {
12 | if (a.username < b.username) { return -1; }
13 | if (a.username > b.username) { return 1; }
14 | return 0;
15 | })
16 | };
17 | };
18 |
19 | const mapDispatchToProps = dispatch => {
20 | return {
21 | fetchMembers: id => dispatch(fetchMembers(id)),
22 | beginLoading: () => dispatch(beginLoading()),
23 | finishLoading: () => dispatch(finishLoading()),
24 | };
25 | };
26 |
27 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(ServerMembers));
28 |
29 |
--------------------------------------------------------------------------------
/frontend/components/app/servers/dm_notification.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import Tooltip from '../modal/tooltip';
4 |
5 | class DmNotification extends React.Component {
6 | constructor(props) {
7 | super(props);
8 | this.state = { classText: 'dm-notification'};
9 | }
10 |
11 | componentDidMount() {
12 | setTimeout(() => this.setState({classText: 'dm-notification right'}), 200);
13 | }
14 |
15 | render() {
16 | return (
17 | e.preventDefault()}
23 | >
24 | {this.props.notification.count}
25 |
26 | }
27 | position="right center"
28 | text={this.props.user.username}
29 | />
30 | )
31 | };
32 | };
33 |
34 | export default DmNotification;
--------------------------------------------------------------------------------
/frontend/components/app/servers/join_server_form.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router-dom';
3 |
4 | class JoinServerForm extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.state = { name: '' };
8 | this.handleSubmit = this.handleSubmit.bind(this);
9 | this.handleRedirect = this.handleRedirect.bind(this);
10 | this.update = this.update.bind(this);
11 | }
12 |
13 | componentDidMount() {
14 | this.nameInput.focus();
15 | }
16 |
17 | update(e) {
18 | this.setState({
19 | name: e.currentTarget.value
20 | });
21 | }
22 |
23 | handleSubmit(e) {
24 | e.preventDefault();
25 | this.props.joinServer(this.state, this.props.currentUserId).then(this.handleRedirect);
26 | }
27 |
28 | handleRedirect(action) {
29 | this.props.closeModal();
30 | this.props.history.push(`/channels/${action.server.id}/${action.server.root_channel}`);
31 | }
32 |
33 | render() {
34 | return (
35 |
51 | )
52 | }
53 | }
54 |
55 | export default withRouter(JoinServerForm);
56 |
--------------------------------------------------------------------------------
/frontend/components/app/servers/search_server_item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { withRouter } from 'react-router-dom';
3 |
4 | class SearchServerItem extends React.Component {
5 | constructor(props) {
6 | super(props);
7 | this.handleSubmit = this.handleSubmit.bind(this);
8 | this.handleRedirect = this.handleRedirect.bind(this);
9 | }
10 |
11 | handleSubmit(e) {
12 | e.preventDefault();
13 | this.props.joinServer({name: this.props.server.name}, this.props.currentUserId).then(this.handleRedirect);
14 | }
15 |
16 | handleRedirect(action) {
17 | this.props.closeModal();
18 | this.props.history.push(`/channels/${action.server.id}/${action.server.root_channel}`);
19 | }
20 |
21 |
22 | render() {
23 | return (
24 |
30 | )
31 | };
32 | };
33 |
34 | export default withRouter(SearchServerItem);
--------------------------------------------------------------------------------
/frontend/components/app/servers/server.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { NavLink } from 'react-router-dom';
3 | import Tooltip from '../modal/tooltip';
4 |
5 | class Server extends React.Component {
6 |
7 | render() {
8 | return (
9 | e.preventDefault()}
17 | >
18 | }
19 | position="right center"
20 | text={this.props.server.name}
21 | />
22 | )
23 | };
24 | };
25 |
26 | export default Server;
--------------------------------------------------------------------------------
/frontend/components/app/servers/servers/servers_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import Servers from './servers';
3 | import { fetchServers, createServer, joinServer } from '../../../../actions/server_actions';
4 | import { removeServerErrors } from '../../../../actions/server_actions';
5 | import { withRouter } from 'react-router-dom';
6 |
7 | const mapStateToProps = state => {
8 | let onlineCount = 0;
9 | state.entities.friends.forEach(friendId => {
10 | if (state.entities.users[friendId] && state.entities.users[friendId].online) {
11 | onlineCount++;
12 | }
13 | });
14 |
15 | const dmNotifications = Object.values(state.notifications.dm);
16 | const users = dmNotifications.map(notification => {
17 | return state.entities.users[notification.authorId];
18 | });
19 | const currentUser = state.entities.users[state.session.id];
20 |
21 | const servers = currentUser.servers.map(serverId => {
22 | return state.entities.servers[serverId] || {};
23 | });
24 |
25 | return {
26 | servers,
27 | errors: state.errors.server,
28 | onlineCount,
29 | dmNotifications,
30 | users,
31 | currentUser
32 | };
33 | };
34 |
35 | const mapDispatchToProps = dispatch => {
36 | return {
37 | fetchServers: () => dispatch(fetchServers()),
38 | createServer: (formData) => dispatch(createServer(formData)),
39 | joinServer: (server, userId) => dispatch(joinServer(server, userId)),
40 | removeServerErrors: () => dispatch(removeServerErrors()),
41 | };
42 | };
43 |
44 | export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Servers));
45 |
--------------------------------------------------------------------------------
/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;
--------------------------------------------------------------------------------
/frontend/components/session_form/login/login_form_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { login, removeErrors } from '../../../actions/session_actions';
3 | import LoginForm from './login_form';
4 |
5 | const mapStateToProps = ({ errors }) => {
6 | return {
7 | errors: errors.session,
8 | };
9 | };
10 |
11 | const mapDispatchToProps = dispatch => {
12 | return {
13 | removeErrors: () => dispatch(removeErrors()),
14 | login: (user) => dispatch(login(user)),
15 | };
16 | };
17 |
18 | export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
--------------------------------------------------------------------------------
/frontend/components/session_form/signup/signup_form_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import { signup, removeErrors } from '../../../actions/session_actions';
3 | import SignupForm from './signup_form';
4 |
5 | const mapStateToProps = ({ errors }) => {
6 | return {
7 | errors: errors.session,
8 | };
9 | };
10 |
11 | const mapDispatchToProps = dispatch => {
12 | return {
13 | signup: (user) => dispatch(signup(user)),
14 | removeErrors: () => dispatch(removeErrors()),
15 | };
16 | };
17 |
18 | export default connect(mapStateToProps, mapDispatchToProps)(SignupForm);
19 |
--------------------------------------------------------------------------------
/frontend/components/splash/splash_container.jsx:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux';
2 | import Splash from './splash';
3 | import { login } from '../../actions/session_actions';
4 |
5 | const mapStateToProps = ({ session }) => {
6 | return {
7 | currentUserId: session.id,
8 | };
9 | };
10 |
11 | const mapDispatchToProps = dispatch => {
12 | return {
13 | login: () => dispatch(login({ username: 'hodor', password: 'hodorhodor' })),
14 | };
15 | }
16 |
17 | export default connect(mapStateToProps, mapDispatchToProps)(Splash);
18 |
--------------------------------------------------------------------------------
/frontend/discors.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import Root from './components/root';
4 | import configureStore from './store/store';
5 |
6 | document.addEventListener('DOMContentLoaded', () => {
7 | let store;
8 | if (window.currentUser) {
9 | const preloadedState = {
10 | session: { id: window.currentUser.id },
11 | entities: {
12 | users: { [window.currentUser.id]: window.currentUser }
13 | }
14 | };
15 | store = configureStore(preloadedState);
16 | delete window.currentUser;
17 | } else {
18 | store = configureStore();
19 | }
20 |
21 | const root = document.getElementById('root');
22 | ReactDOM.render(, root);
23 | });
--------------------------------------------------------------------------------
/frontend/reducers/entities/channels_reducer.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash/merge';
2 | import { RECEIVE_CHANNELS, RECEIVE_CHANNEL, REMOVE_CHANNEL } from '../../actions/channel_actions';
3 | import { RECEIVE_CURRENT_USER_DATA } from '../../actions/session_actions';
4 |
5 | const channelsReducer = (state = {}, action) => {
6 | Object.freeze(state);
7 | switch (action.type) {
8 | case RECEIVE_CURRENT_USER_DATA:
9 | return action.currentUserData.channels || {};
10 | case RECEIVE_CHANNELS:
11 | return merge({}, state, action.channels);
12 | case RECEIVE_CHANNEL:
13 | return merge({}, state, { [action.channel.id]: action.channel });
14 | case REMOVE_CHANNEL:
15 | const newState = merge({}, state);
16 | delete newState[action.channelId];
17 | return newState;
18 | default:
19 | return state;
20 | }
21 | };
22 |
23 | export default channelsReducer;
24 |
--------------------------------------------------------------------------------
/frontend/reducers/entities/entities_reducer.js:
--------------------------------------------------------------------------------
1 |
2 | import { combineReducers } from 'redux';
3 | import users from './users_reducer';
4 | import servers from './servers_reducer';
5 | import channels from './channels_reducer';
6 | import friendRequests from './friend_requests_reducer';
7 | import friends from './friends_reducer';
8 | import voiceChannels from './voice_channels_reducer';
9 |
10 | export default combineReducers({
11 | users,
12 | servers,
13 | channels,
14 | friendRequests,
15 | friends,
16 | voiceChannels,
17 | });
18 |
--------------------------------------------------------------------------------
/frontend/reducers/entities/friend_requests_reducer.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash/merge';
2 | import { RECEIVE_REQUESTS, REMOVE_REQUEST, RECEIVE_REQUEST } from '../../actions/friends_actions';
3 | import { RECEIVE_CURRENT_USER_DATA } from '../../actions/session_actions';
4 |
5 | const friendRequestsReducer = (state = {}, action) => {
6 | Object.freeze(state);
7 | switch (action.type) {
8 | case RECEIVE_CURRENT_USER_DATA:
9 | return action.currentUserData.friend_requests || {};
10 | case RECEIVE_REQUESTS:
11 | return action.friendRequests || {};
12 | case RECEIVE_REQUEST:
13 | return merge({}, state, { [action.friendRequest.id]: action.friendRequest });
14 | case REMOVE_REQUEST:
15 | const newState = merge({}, state);
16 | delete newState[action.requestId];
17 | return newState;
18 | default:
19 | return state;
20 | }
21 | };
22 |
23 | export default friendRequestsReducer;
24 |
--------------------------------------------------------------------------------
/frontend/reducers/entities/friends_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_FRIENDS, RECEIVE_FRIEND, REMOVE_FRIEND } from "../../actions/friends_actions";
2 | import { union } from 'lodash';
3 | import { RECEIVE_CURRENT_USER_DATA } from "../../actions/session_actions";
4 |
5 | const friendsReducer = (state = [], action) => {
6 | Object.freeze(state);
7 | switch (action.type) {
8 | case RECEIVE_CURRENT_USER_DATA:
9 | return action.currentUserData.friends;
10 | case RECEIVE_FRIENDS:
11 | return action.friendData.friends;
12 | case RECEIVE_FRIEND:
13 | return union([], state, action.friend);
14 | case REMOVE_FRIEND:
15 | const newState = union([], state);
16 | return newState.filter((friendId) => (
17 | friendId !== action.friendId
18 | ));
19 | default:
20 | return state;
21 | }
22 | };
23 |
24 | export default friendsReducer;
25 |
--------------------------------------------------------------------------------
/frontend/reducers/entities/servers_reducer.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash/merge';
2 | import { RECEIVE_SERVERS, RECEIVE_SERVER, REMOVE_SERVER } from '../../actions/server_actions';
3 | import { RECEIVE_CURRENT_USER_DATA } from '../../actions/session_actions';
4 |
5 | const serversReducer = (state = {}, action) => {
6 | Object.freeze(state);
7 | switch (action.type) {
8 | case RECEIVE_CURRENT_USER_DATA:
9 | return action.currentUserData.servers || {};
10 | case RECEIVE_SERVERS:
11 | return merge({}, state, action.servers);
12 | case RECEIVE_SERVER:
13 | return merge({}, state, { [action.server.id]: action.server });
14 | case REMOVE_SERVER:
15 | const newState = merge({}, state);
16 | delete newState[action.serverId];
17 | return newState;
18 | default:
19 | return state;
20 | }
21 | };
22 |
23 | export default serversReducer;
24 |
--------------------------------------------------------------------------------
/frontend/reducers/entities/users_reducer.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash/merge';
2 | import { RECEIVE_CURRENT_USER, RECEIVE_CURRENT_USER_DATA, RECEIVE_USER } from '../../actions/session_actions';
3 | import { RECEIVE_USERS, REMOVE_SERVER, RECEIVE_SERVER } from '../../actions/server_actions';
4 | import { RECEIVE_FRIENDS } from '../../actions/friends_actions';
5 |
6 | const usersReducer = (state = {}, action) => {
7 | Object.freeze(state);
8 | let newState;
9 | switch (action.type) {
10 | case RECEIVE_CURRENT_USER_DATA:
11 | return merge({}, state, action.currentUserData.users);
12 | case RECEIVE_CURRENT_USER:
13 | return merge({}, state, { [action.currentUser.id]: action.currentUser });
14 | case RECEIVE_USER:
15 | return merge({}, state, { [action.user.id]: action.user });
16 | case RECEIVE_USERS:
17 | return merge({}, state, action.users);
18 | case RECEIVE_FRIENDS:
19 | return merge({}, state, action.friendData.users);
20 | case REMOVE_SERVER:
21 | newState = merge({}, state);
22 | newState[action.userId].servers = newState[action.userId].servers.filter(serverId => {
23 | return serverId !== action.serverId;
24 | });
25 |
26 | return newState;
27 | case RECEIVE_SERVER:
28 | newState = merge({}, state);
29 | if (!newState[action.userId].servers.includes(action.server.id)) {
30 | newState[action.userId].servers.push(action.server.id);
31 | }
32 |
33 | return newState;
34 | default:
35 | return state;
36 | }
37 | };
38 |
39 | export default usersReducer;
--------------------------------------------------------------------------------
/frontend/reducers/entities/voice_channels_reducer.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash/merge';
2 | import { RECEIVE_VOICE_CHANNEL, RECEIVE_VOICE_CHANNELS, REMOVE_VOICE_CHANNEL } from '../../actions/voice_channel_actions';
3 | import { RECEIVE_CURRENT_USER_DATA } from '../../actions/session_actions';
4 |
5 | const voiceChannelsReducer = (state = {}, action) => {
6 | Object.freeze(state);
7 | switch (action.type) {
8 | case RECEIVE_CURRENT_USER_DATA:
9 | return action.currentUserData.voice_channels || {};
10 | case RECEIVE_VOICE_CHANNELS:
11 | return merge({}, state, action.voiceChannels);
12 | case RECEIVE_VOICE_CHANNEL:
13 | return merge({}, state, { [action.voiceChannel.id]: action.voiceChannel });
14 | case REMOVE_VOICE_CHANNEL:
15 | const newState = merge({}, state);
16 | delete newState[action.voiceChannelId];
17 | return newState;
18 | default:
19 | return state;
20 | }
21 | };
22 |
23 | export default voiceChannelsReducer;
24 |
25 |
--------------------------------------------------------------------------------
/frontend/reducers/errors/channel_errors_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_CHANNEL_ERRORS, RECEIVE_CHANNEL, REMOVE_CHANNEL_ERRORS } from '../../actions/channel_actions';
2 | import { RECEIVE_VOICE_CHANNEL } from '../../actions/voice_channel_actions';
3 |
4 | export default (state = [], action) => {
5 | Object.freeze(state);
6 | switch (action.type) {
7 | case RECEIVE_CHANNEL_ERRORS:
8 | return action.errors;
9 | case RECEIVE_CHANNEL:
10 | return [];
11 | case RECEIVE_VOICE_CHANNEL:
12 | return [];
13 | case REMOVE_CHANNEL_ERRORS:
14 | return [];
15 | default:
16 | return state;
17 | }
18 | };
19 |
--------------------------------------------------------------------------------
/frontend/reducers/errors/errors_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import session from './session_errors_reducer';
3 | import server from './server_errors_reducer';
4 | import channel from './channel_errors_reducer';
5 |
6 | export default combineReducers({
7 | session,
8 | server,
9 | channel,
10 | });
--------------------------------------------------------------------------------
/frontend/reducers/errors/server_errors_reducer.js:
--------------------------------------------------------------------------------
1 | import { RECEIVE_SERVER_ERRORS, RECEIVE_SERVER, REMOVE_SERVER_ERRORS } from '../../actions/server_actions';
2 |
3 | export default (state = [], action) => {
4 | Object.freeze(state);
5 | switch (action.type) {
6 | case RECEIVE_SERVER_ERRORS:
7 | return action.errors;
8 | case RECEIVE_SERVER:
9 | return [];
10 | case REMOVE_SERVER_ERRORS:
11 | return [];
12 | default:
13 | return state;
14 | }
15 | };
--------------------------------------------------------------------------------
/frontend/reducers/errors/session_errors_reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_SESSION_ERRORS,
3 | RECEIVE_CURRENT_USER,
4 | REMOVE_SESSION_ERRORS,
5 | } from '../../actions/session_actions';
6 |
7 | export default (state = [], action) => {
8 | Object.freeze(state);
9 | switch (action.type) {
10 | case RECEIVE_SESSION_ERRORS:
11 | return action.errors;
12 | case RECEIVE_CURRENT_USER:
13 | return [];
14 | case REMOVE_SESSION_ERRORS:
15 | return [];
16 | default:
17 | return state;
18 | }
19 | };
--------------------------------------------------------------------------------
/frontend/reducers/loading_reducer.js:
--------------------------------------------------------------------------------
1 | import { BEGIN_LOADING, FINISH_LOADING } from "../actions/ui_actions";
2 |
3 | export default (state = false, action) => {
4 | Object.freeze(state);
5 | switch (action.type) {
6 | case BEGIN_LOADING:
7 | return true;
8 | case FINISH_LOADING:
9 | return false;
10 | default:
11 | return state;
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/frontend/reducers/notifications/dm_notifications_reducer.js:
--------------------------------------------------------------------------------
1 | import merge from 'lodash/merge';
2 | import { RECEIVE_DM_NOTIFICATION, REMOVE_DM_NOTIFICATION } from '../../actions/notification_actions';
3 |
4 | const dmNotificationsReducer = (state = {}, action) => {
5 | let newState;
6 | Object.freeze(state);
7 | switch (action.type) {
8 | case RECEIVE_DM_NOTIFICATION:
9 | if (state[action.notification.channelId]) {
10 | newState = merge({}, state);
11 | newState[action.notification.channelId].count++;
12 | return newState;
13 | } else {
14 | return merge({}, state, { [action.notification.channelId]: merge(action.notification, { count: 1 }) });
15 | }
16 | case REMOVE_DM_NOTIFICATION:
17 | newState = merge({}, state);
18 | delete newState[action.channelId];
19 | return newState;
20 | default:
21 | return state;
22 | }
23 | };
24 |
25 | export default dmNotificationsReducer;
26 |
--------------------------------------------------------------------------------
/frontend/reducers/notifications/notifications_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import dm from './dm_notifications_reducer';
3 |
4 | export default combineReducers({
5 | dm,
6 | });
--------------------------------------------------------------------------------
/frontend/reducers/root_reducer.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import entities from './entities/entities_reducer';
3 | import ui from './ui_reducer';
4 | import session from './session_reducer';
5 | import errors from './errors/errors_reducer';
6 | import notifications from './notifications/notifications_reducer';
7 |
8 | const rootReducer = combineReducers({
9 | entities,
10 | session,
11 | errors,
12 | ui,
13 | notifications,
14 | });
15 |
16 | export default rootReducer;
--------------------------------------------------------------------------------
/frontend/reducers/session_reducer.js:
--------------------------------------------------------------------------------
1 | import {
2 | RECEIVE_CURRENT_USER,
3 | LOGOUT_CURRENT_USER,
4 | RECEIVE_CURRENT_USER_DATA,
5 | } from '../actions/session_actions';
6 |
7 | const _nullUser = Object.freeze({
8 | id: null
9 | });
10 |
11 | const sessionReducer = (state = _nullUser, action) => {
12 | Object.freeze(state);
13 | switch (action.type) {
14 | case RECEIVE_CURRENT_USER_DATA:
15 | return { id: action.currentUserData.currentUserId };
16 | case RECEIVE_CURRENT_USER:
17 | return { id: action.currentUser.id };
18 | case LOGOUT_CURRENT_USER:
19 | return _nullUser;
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 loading from './loading_reducer';
3 |
4 | export default combineReducers({
5 | loading,
6 | });
--------------------------------------------------------------------------------
/frontend/store/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, applyMiddleware } from 'redux';
2 | // import logger from 'redux-logger';
3 | import thunk from 'redux-thunk';
4 | import rootReducer from '../reducers/root_reducer';
5 |
6 | const configureStore = (preloadedState = {}) => (
7 | createStore(
8 | rootReducer,
9 | preloadedState,
10 | // applyMiddleware(thunk, logger)
11 | applyMiddleware(thunk)
12 | )
13 | );
14 |
15 | export default configureStore;
--------------------------------------------------------------------------------
/frontend/util/channel_api_util.js:
--------------------------------------------------------------------------------
1 | export const createChannel = channel => (
2 | $.ajax({
3 | method: 'POST',
4 | url: '/api/channels',
5 | data: { channel }
6 | })
7 | );
8 |
9 | export const createDmChannel = user_id => (
10 | $.ajax({
11 | method: 'POST',
12 | url: '/api/dm_channel_memberships',
13 | data: {dm_channel: { user_id }}
14 | })
15 | );
16 |
17 | export const fetchChannel = id => (
18 | $.ajax({
19 | method: 'GET',
20 | url: `/api/channels/${id}`,
21 | })
22 | );
23 |
24 | export const deleteChannel = id => (
25 | $.ajax({
26 | method: 'DELETE',
27 | url: `/api/channels/${id}`,
28 | })
29 | );
30 |
31 | export const deleteDmChannel = id => (
32 | $.ajax({
33 | method: 'DELETE',
34 | url: `/api/dm_channel_memberships/${id}`,
35 | })
36 | );
37 |
38 | export const fetchChannels = (server_id) => (
39 | $.ajax({
40 | method: 'GET',
41 | url: '/api/channels',
42 | data: { channel: {server_id} }
43 | })
44 | );
45 |
46 | export const fetchDmChannels = () => (
47 | $.ajax({
48 | method: 'GET',
49 | url: '/api/dm_channel_memberships',
50 | })
51 | );
52 |
53 |
--------------------------------------------------------------------------------
/frontend/util/friends_api_util.js:
--------------------------------------------------------------------------------
1 | export const createFriendRequest = friend_request => (
2 | $.ajax({
3 | method: 'POST',
4 | url: '/api/friend_requests',
5 | data: { friend_request }
6 | })
7 | );
8 |
9 | export const acceptFriendRequest = friend_request => (
10 | $.ajax({
11 | method: 'PATCH',
12 | url: `/api/friend_requests/${friend_request.id}`,
13 | })
14 | );
15 |
16 | export const deleteFriendRequest = friend_request => (
17 | $.ajax({
18 | method: 'DELETE',
19 | url: `/api/friend_requests/${friend_request.id}`,
20 | })
21 | );
22 |
23 | export const fetchFriendRequests = () => (
24 | $.ajax({
25 | method: 'GET',
26 | url: '/api/friend_requests',
27 | })
28 | );
29 |
30 | export const deleteFriend = id => (
31 | $.ajax({
32 | method: 'DELETE',
33 | url: `/api/friends/${id}`,
34 | })
35 | );
36 |
37 | export const fetchFriends = () => (
38 | $.ajax({
39 | method: 'GET',
40 | url: '/api/friends',
41 | })
42 | );
--------------------------------------------------------------------------------
/frontend/util/route_util.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { connect } from 'react-redux';
3 | import { Route, Redirect, withRouter } from 'react-router-dom';
4 |
5 | const Auth = ({ component: Component, path, loggedIn, exact }) => (
6 | (
7 | !loggedIn ? (
8 |
9 | ) : (
10 |
11 | )
12 | )} />
13 | );
14 |
15 | const Protected = ({ component: Component, path, loggedIn, exact }) => (
16 | (
17 | loggedIn ? (
18 |
19 | ) : (
20 |
21 | )
22 | )} />
23 | );
24 |
25 | const mapStateToProps = state => (
26 | { loggedIn: Boolean(state.session.id) }
27 | );
28 |
29 | export const AuthRoute = withRouter(connect(mapStateToProps)(Auth));
30 | export const ProtectedRoute = withRouter(connect(mapStateToProps)(Protected));
--------------------------------------------------------------------------------
/frontend/util/server_api_utl.js:
--------------------------------------------------------------------------------
1 | export const createServer = formData => (
2 | $.ajax({
3 | method: 'POST',
4 | url: '/api/servers',
5 | data: formData,
6 | contentType: false,
7 | processData: false
8 | })
9 | );
10 |
11 | export const fetchServer = id => (
12 | $.ajax({
13 | method: 'GET',
14 | url: `/api/servers/${id}`,
15 | })
16 | );
17 |
18 | export const fetchMembers = id => (
19 | $.ajax({
20 | method: 'GET',
21 | url: `/api/servers/${id}/members`,
22 | })
23 | );
24 |
25 | export const fetchServers = () => (
26 | $.ajax({
27 | method: 'GET',
28 | url: '/api/servers',
29 | })
30 | );
31 |
32 | export const joinServer = server => (
33 | $.ajax({
34 | method: 'POST',
35 | url: `/api/servers/join`,
36 | data: { server }
37 | })
38 | );
39 |
40 | export const deleteServer = id => (
41 | $.ajax({
42 | method: 'DELETE',
43 | url: `/api/servers/${id}`,
44 | })
45 | );
--------------------------------------------------------------------------------
/frontend/util/session_api_util.js:
--------------------------------------------------------------------------------
1 | export const login = user => (
2 | $.ajax({
3 | method: 'POST',
4 | url: '/api/session',
5 | data: { user }
6 | })
7 | );
8 |
9 | export const fetchCurrentUserData = () => (
10 | $.ajax({
11 | method: 'GET',
12 | url: '/api/users/data',
13 | })
14 | );
15 |
16 | export const signup = user => (
17 | $.ajax({
18 | method: 'POST',
19 | url: '/api/users',
20 | data: { user }
21 | })
22 | );
23 |
24 | export const logout = () => (
25 | $.ajax({
26 | method: 'DELETE',
27 | url: '/api/session'
28 | })
29 | );
30 |
31 | export const editUser = (formData) => (
32 | $.ajax({
33 | url: `/api/users/${formData.get('user[id]')}`,
34 | method: 'PATCH',
35 | data: formData,
36 | contentType: false,
37 | processData: false
38 | })
39 | );
--------------------------------------------------------------------------------
/frontend/util/voice_channel_api_util.js:
--------------------------------------------------------------------------------
1 | export const createVoiceChannel = audio_channel => (
2 | $.ajax({
3 | method: 'POST',
4 | url: '/api/audio_channels',
5 | data: { audio_channel }
6 | })
7 | );
8 |
9 | export const fetchVoiceChannel = id => (
10 | $.ajax({
11 | method: 'GET',
12 | url: `/api/audio_channels/${id}`,
13 | })
14 | );
15 |
16 | export const deleteVoiceChannel = id => (
17 | $.ajax({
18 | method: 'DELETE',
19 | url: `/api/audio_channels/${id}`,
20 | })
21 | );
22 |
23 | export const fetchVoiceChannels = (server_id) => (
24 | $.ajax({
25 | method: 'GET',
26 | url: '/api/audio_channels',
27 | data: { audio_channel: { server_id } }
28 | })
29 | );
30 |
--------------------------------------------------------------------------------
/helpers/application_helper.rb:
--------------------------------------------------------------------------------
1 | module ApplicationHelper
2 | end
3 |
--------------------------------------------------------------------------------
/lib/assets/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/lib/assets/.keep
--------------------------------------------------------------------------------
/lib/tasks/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/lib/tasks/.keep
--------------------------------------------------------------------------------
/lib/tasks/sessions.rake:
--------------------------------------------------------------------------------
1 | namespace :sessions do
2 | desc "Clear expired sessions"
3 | task :clear_expired_sessions => :environment do
4 | sql = "DELETE FROM sessions WHERE updated_at < (CURRENT_TIMESTAMP - INTERVAL '1 days');"
5 | ActiveRecord::Base.connection.execute(sql)
6 | end
7 | end
--------------------------------------------------------------------------------
/log/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/log/.keep
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "discors",
3 | "private": true,
4 | "dependencies": {
5 | "@babel/core": "^7.2.2",
6 | "@babel/preset-env": "^7.2.3",
7 | "@babel/preset-react": "^7.0.0",
8 | "@microlink/react": "^3.0.5",
9 | "babel-loader": "^8.0.5",
10 | "babel-polyfill": "^6.26.0",
11 | "lodash": "^4.17.11",
12 | "moment-timezone": "^0.5.23",
13 | "react": "^16.7.0",
14 | "react-dom": "^16.7.0",
15 | "react-modal": "^3.8.1",
16 | "react-redux": "^6.0.0",
17 | "react-router-dom": "^4.3.1",
18 | "reactjs-popup": "^1.3.2",
19 | "redux": "^4.0.1",
20 | "redux-logger": "^3.0.6",
21 | "redux-thunk": "^2.3.0",
22 | "styled-components": "^4.1.3",
23 | "webpack": "^4.29.0",
24 | "webpack-cli": "^3.2.1"
25 | },
26 | "description": "This README would normally document whatever steps are necessary to get the application up and running.",
27 | "version": "1.0.0",
28 | "main": "index.js",
29 | "directories": {
30 | "lib": "lib",
31 | "test": "test"
32 | },
33 | "scripts": {
34 | "test": "echo \"Error: no test specified\" && exit 1",
35 | "postinstall": "./node_modules/.bin/webpack --mode=production",
36 | "start": "./node_modules/.bin/webpack --mode=production",
37 | "webpack": "webpack --mode=development --watch"
38 | },
39 | "engines": {
40 | "node": "10.13.0",
41 | "npm": "6.5.0"
42 | },
43 | "repository": {
44 | "type": "git",
45 | "url": "git+https://github.com/jeffdeliso/discors.git"
46 | },
47 | "keywords": [],
48 | "author": "",
49 | "license": "ISC",
50 | "bugs": {
51 | "url": "https://github.com/jeffdeliso/discors/issues"
52 | },
53 | "homepage": "https://github.com/jeffdeliso/discors#readme"
54 | }
55 |
--------------------------------------------------------------------------------
/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/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/public/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/public/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/public/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/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 |
--------------------------------------------------------------------------------
/storage/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/storage/.keep
--------------------------------------------------------------------------------
/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/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/test/controllers/.keep
--------------------------------------------------------------------------------
/test/fixtures/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/test/fixtures/.keep
--------------------------------------------------------------------------------
/test/fixtures/files/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/test/fixtures/files/.keep
--------------------------------------------------------------------------------
/test/helpers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/test/helpers/.keep
--------------------------------------------------------------------------------
/test/integration/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/test/integration/.keep
--------------------------------------------------------------------------------
/test/mailers/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/test/mailers/.keep
--------------------------------------------------------------------------------
/test/models/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/test/models/.keep
--------------------------------------------------------------------------------
/test/system/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/test/system/.keep
--------------------------------------------------------------------------------
/test/test_helper.rb:
--------------------------------------------------------------------------------
1 | ENV['RAILS_ENV'] ||= 'test'
2 | require_relative '../config/environment'
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 |
--------------------------------------------------------------------------------
/tmp/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/tmp/.keep
--------------------------------------------------------------------------------
/vendor/.keep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jeffdeliso/discors/f78504485ad26efcc087a51e2dcceff84e689ca5/vendor/.keep
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: [
6 | 'babel-polyfill',
7 | './frontend/discors.jsx'
8 | ],
9 | output: {
10 | path: path.resolve(__dirname, 'app', 'assets', 'javascripts'),
11 | filename: 'bundle.js'
12 | },
13 | module: {
14 | rules: [
15 | {
16 | test: /\.jsx?$/,
17 | exclude: /(node_modules)/,
18 | use: {
19 | loader: 'babel-loader',
20 | query: {
21 | presets: ['@babel/env', '@babel/react']
22 | }
23 | },
24 | }
25 | ]
26 | },
27 | devtool: 'source-map',
28 | resolve: {
29 | extensions: [".js", ".jsx", "*"]
30 | }
31 | };
32 |
--------------------------------------------------------------------------------