Compare commits
71 commits
develop
...
frontendon
Author | SHA1 | Date | |
---|---|---|---|
|
4cab56d653 | ||
|
f941c1c362 | ||
|
d0df7214ab | ||
|
1e145c8d98 | ||
|
065c1fae03 | ||
|
732f9a663f | ||
|
73b0d02841 | ||
|
52430ef8d5 | ||
|
6fc7a09662 | ||
|
40293b7245 | ||
|
c102c8c7b9 | ||
|
ffc0deeaf5 | ||
|
48fb750560 | ||
|
788bce0286 | ||
|
697ea396da | ||
|
b4019135f8 | ||
|
e0b16f8fcf | ||
|
ad5d8be766 | ||
|
9fc02de1fa | ||
|
81bfd8eb28 | ||
|
f8506e9763 | ||
|
84e9a67332 | ||
|
8cd3fdbf32 | ||
|
f77154dc1c | ||
|
c37b7ae59f | ||
|
7cfb830870 | ||
|
7ee822cb80 | ||
|
3e27c267a6 | ||
|
ed486c5ae6 | ||
|
c7f2996397 | ||
|
3d985b6205 | ||
|
ceb2420b8d | ||
|
150e7a4dfb | ||
|
6c84205b38 | ||
|
c1dbfc9f72 | ||
|
2971b81463 | ||
|
037575409d | ||
|
d6c07c24cf | ||
|
a5ed563d8e | ||
|
f35cea5ba6 | ||
|
10359a1f53 | ||
|
cba83c0011 | ||
|
753fb20308 | ||
|
3d428d52fc | ||
|
1a3a81508d | ||
|
d765487635 | ||
|
0220874590 | ||
|
0063c9f5d5 | ||
|
2913770af4 | ||
|
a9f353a07f | ||
|
a72b4c6871 | ||
|
a468846bc8 | ||
|
0defd97ebc | ||
|
e6e0ca561a | ||
|
d910094d76 | ||
|
5f4a789435 | ||
|
53ddf60cfa | ||
|
a50679fa24 | ||
|
edd54b246e | ||
|
c15b54ee6a | ||
|
0e530626ed | ||
|
fe88cf27de | ||
|
aaa6875dcb | ||
|
da026a3003 | ||
|
562da81a0f | ||
|
db2ca5945c | ||
|
74ad3d33c8 | ||
|
6309d764f9 | ||
|
9cd5246411 | ||
|
3778eaab4a | ||
|
36a58e4bf3 |
943 changed files with 15842 additions and 23682 deletions
|
@ -1,2 +1 @@
|
|||
https://github.com/heroku/heroku-buildpack-nodejs.git
|
||||
https://github.com/heroku/heroku-buildpack-ruby.git
|
||||
|
|
|
@ -1,16 +1,10 @@
|
|||
---
|
||||
engines:
|
||||
brakeman:
|
||||
enabled: true
|
||||
bundler-audit:
|
||||
enabled: true
|
||||
duplication:
|
||||
enabled: true
|
||||
config:
|
||||
languages:
|
||||
count_threshold: 3 # rule of three
|
||||
ruby:
|
||||
mass_threshold: 36 # default: 18
|
||||
javascript:
|
||||
mass_threshold: 80 # default: 40
|
||||
eslint:
|
||||
|
@ -18,21 +12,11 @@ engines:
|
|||
channel: "eslint-3"
|
||||
fixme:
|
||||
enabled: true
|
||||
rubocop:
|
||||
enabled: true
|
||||
exclude_fingerprints:
|
||||
- 74f18007b920e8d81148d2f6a2756534
|
||||
ratings:
|
||||
paths:
|
||||
- 'Gemfile.lock'
|
||||
- '**.erb'
|
||||
- '**.rb'
|
||||
- '**.js'
|
||||
- '**.jsx'
|
||||
exclude_paths:
|
||||
- app/assets/images/
|
||||
- app/assets/javascripts/lib/
|
||||
- frontend/src/patched/
|
||||
- db/
|
||||
- script/
|
||||
- spec/
|
||||
- public/images
|
||||
- public/lib
|
||||
- src/patched/
|
||||
|
|
26
.eslintrc.js
26
.eslintrc.js
|
@ -1,26 +0,0 @@
|
|||
module.exports = {
|
||||
"sourceType": "module",
|
||||
"parser": "babel-eslint",
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"extends": "standard",
|
||||
"installedESLint": true,
|
||||
"env": {
|
||||
"es6": true,
|
||||
"node": true
|
||||
},
|
||||
"plugins": [
|
||||
"promise",
|
||||
"standard",
|
||||
"react"
|
||||
],
|
||||
"rules": {
|
||||
"react/jsx-uses-react": [2],
|
||||
"react/jsx-uses-vars": [2],
|
||||
"space-before-function-paren": [2, "never"],
|
||||
"yoda": [2, "never", { "exceptRange": true }]
|
||||
}
|
||||
}
|
61
.example-env
61
.example-env
|
@ -1,56 +1,5 @@
|
|||
# Node JS env
|
||||
export NODE_REALTIME_PORT='5000' # should match REALTIME_SERVER, below
|
||||
|
||||
# Rails env
|
||||
export DB_USERNAME='postgres'
|
||||
export DB_PASSWORD='3112'
|
||||
export DB_HOST='localhost'
|
||||
export DB_PORT='5432'
|
||||
export DB_NAME='metamaps'
|
||||
|
||||
export REALTIME_SERVER='http://localhost:5000'
|
||||
export MAILER_DEFAULT_URL='localhost:3000'
|
||||
export DEVISE_MAILER_SENDER='team@metamaps.cc'
|
||||
|
||||
export DEVISE_SECRET_KEY='f71c467e526f23d614b3b08866cad4788c502bed869c282f06e73ee6c94675b62fe1f6d52fa7ba8196b33031f0d2f3b67e27ea07693c52ecebccb01700cad614'
|
||||
export SECRET_KEY_BASE='267c8a84f63963282f45bc3010eaddf027abfab58fc759d6e239c8005f85ee99d6d01b1ab6394cdee9ca7f8c9213a0cf91d3d8d3350f096123e2caccbcc0924f'
|
||||
|
||||
# # you can safely leave these blank, unless you're deploying an instance, in
|
||||
# # which case you'll need to set them up
|
||||
#
|
||||
# export S3_REGION
|
||||
# export S3_BUCKET_NAME
|
||||
# export AWS_ACCESS_KEY_ID
|
||||
# export AWS_SECRET_ACCESS_KEY
|
||||
#
|
||||
# export SMTP_DOMAIN
|
||||
# export SMTP_PASSWORD
|
||||
# export SMTP_PORT
|
||||
# export SMTP_SERVER
|
||||
# export SMTP_USERNAME
|
||||
|
||||
# # send exception notifications to a slack incoming webhook
|
||||
# export SLACK_EN_WEBHOOK_URL
|
||||
|
||||
# ruby garbage collection stuff
|
||||
|
||||
export RUBY_GC_TUNE=0 #set to 1 to enable GC test
|
||||
export RUBY_GC_TOKEN=4f4380fc9a2857d1f008005a3eb86928
|
||||
export RUBY_GC_HEAP_INIT_SLOTS=186426
|
||||
export RUBY_GC_HEAP_FREE_SLOTS=559278
|
||||
export RUBY_GC_HEAP_GROWTH_FACTOR=1.03
|
||||
export RUBY_GC_HEAP_GROWTH_MAX_SLOTS=74570
|
||||
export RUBY_GC_HEAP_OLDOBJECT_LIMIT_FACTOR=1.4
|
||||
export RUBY_GC_MALLOC_LIMIT=32883406
|
||||
export RUBY_GC_MALLOC_LIMIT_MAX=69055153
|
||||
export RUBY_GC_MALLOC_LIMIT_GROWTH_FACTOR=1.68
|
||||
export RUBY_GC_OLDMALLOC_LIMIT=32509481
|
||||
export RUBY_GC_OLDMALLOC_LIMIT_MAX=68269910
|
||||
export RUBY_GC_OLDMALLOC_LIMIT_GROWTH_FACTOR=1.4
|
||||
|
||||
## find the ENV currently in use in the app using :
|
||||
## grep -rIso -P "(?<=ENV)(\.fetch\(|\[).[A-Z_]+.(\)|\])"
|
||||
|
||||
# for a uniq ordered list of env vars:
|
||||
## grep -rIsoh -P "(?<=ENV)(\.fetch\(|\[).[A-Z_]+.(\)|\])" | grep -oP "[A-Z_]+" | sort -u > temp
|
||||
|
||||
NODE_ENV=development
|
||||
PORT=3000
|
||||
API_PROTOCOL=http
|
||||
API_HOST=localhost:3001
|
||||
ACTIONCABLE=ws://localhost:3001/cable
|
10
.gitignore
vendored
10
.gitignore
vendored
|
@ -5,13 +5,10 @@
|
|||
# git config --global core.excludesfile ~/.gitignore_global
|
||||
|
||||
#assety stuff
|
||||
public/assets
|
||||
public/metamaps_mobile
|
||||
public/api/index.html
|
||||
vendor/
|
||||
public/css
|
||||
public/metamaps.bundle.js
|
||||
node_modules
|
||||
npm-debug.log
|
||||
app/assets/javascripts/webpacked
|
||||
|
||||
#secrets and config
|
||||
.env
|
||||
|
@ -30,6 +27,3 @@ coverage
|
|||
.DS_Store
|
||||
*/.DS_Store
|
||||
.DS_Store?
|
||||
.vagrant
|
||||
gentle/
|
||||
startserver.sh
|
||||
|
|
2
.nvmrc
2
.nvmrc
|
@ -1 +1 @@
|
|||
6.2.2
|
||||
8.9.4
|
||||
|
|
2
.rspec
2
.rspec
|
@ -1,2 +0,0 @@
|
|||
--color
|
||||
--require spec_helper
|
29
.rubocop.yml
29
.rubocop.yml
|
@ -1,29 +0,0 @@
|
|||
AllCops:
|
||||
TargetRubyVersion: 2.3
|
||||
Exclude:
|
||||
- 'db/**/*'
|
||||
- 'tmp/**/*'
|
||||
- 'bin/**/*'
|
||||
- 'vendor/**/*'
|
||||
- 'app/assets/javascripts/node_modules/**/*'
|
||||
- 'Vagrantfile'
|
||||
|
||||
Rails:
|
||||
Enabled: true
|
||||
|
||||
Metrics/LineLength:
|
||||
Max: 120
|
||||
|
||||
Metrics/AbcSize:
|
||||
Max: 16
|
||||
|
||||
Style/Documentation:
|
||||
Enabled: false
|
||||
|
||||
Style/EmptyMethod:
|
||||
EnforcedStyle: expanded
|
||||
|
||||
# I like this cop, but occasionally code is more readable without a guard clause,
|
||||
# and I don't want to write rubocop:disable comments every time that happens
|
||||
Style/GuardClause:
|
||||
Enabled: false
|
|
@ -1 +0,0 @@
|
|||
metamaps
|
|
@ -1 +0,0 @@
|
|||
2.3.0
|
|
@ -1,7 +0,0 @@
|
|||
if ENV['COVERAGE'] == 'on'
|
||||
SimpleCov.start 'rails' do
|
||||
add_group 'Policies', 'app/policies'
|
||||
add_group 'Services', 'app/services'
|
||||
add_group 'Serializers', 'app/serializers'
|
||||
end
|
||||
end
|
25
.travis.yml
25
.travis.yml
|
@ -1,25 +1,4 @@
|
|||
sudo: false
|
||||
language: ruby
|
||||
cache:
|
||||
bundler: true
|
||||
directories:
|
||||
- app/assets/javascripts/node_modules
|
||||
rvm:
|
||||
- 2.3.0
|
||||
before_script:
|
||||
- echo "Rspec setup"
|
||||
- export RAILS_ENV=test
|
||||
- cp .example-env .env
|
||||
- bundle exec rake db:create
|
||||
- bundle exec rake db:schema:load
|
||||
- echo "node setup"
|
||||
- . $HOME/.nvm/nvm.sh
|
||||
- nvm install stable
|
||||
- nvm use stable
|
||||
- npm install --no-optional
|
||||
script:
|
||||
- bundle exec rspec && bundle exec brakeman -q -z && npm test
|
||||
language: node_js
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token: 479d3bf56798fbc7fff3fc8151a5ed09e8ac368fd5af332c437b9e07dbebb44e
|
||||
postgresql: "9.4"
|
||||
repo_token: 479d3bf56798fbc7fff3fc8151a5ed09e8ac368fd5af332c437b9e07dbebb44e
|
57
Gemfile
57
Gemfile
|
@ -1,57 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
ruby '2.3.0'
|
||||
|
||||
gem 'rails', '~> 5.0.0'
|
||||
|
||||
gem 'active_model_serializers'
|
||||
gem 'aws-sdk', '~> 2.7.0'
|
||||
gem 'best_in_place'
|
||||
gem 'delayed_job'
|
||||
gem 'delayed_job_active_record'
|
||||
gem 'devise'
|
||||
gem 'doorkeeper'
|
||||
gem 'dotenv-rails'
|
||||
gem 'exception_notification'
|
||||
gem 'httparty'
|
||||
gem 'json'
|
||||
gem 'kaminari'
|
||||
gem 'mailboxer'
|
||||
gem 'paperclip'
|
||||
gem 'pg'
|
||||
gem 'puma'
|
||||
gem 'pundit'
|
||||
gem 'pundit_extra'
|
||||
gem 'rack-attack'
|
||||
gem 'rack-cors'
|
||||
gem 'redis', '~> 3.3.3'
|
||||
gem 'slack-notifier'
|
||||
gem 'snorlax'
|
||||
gem 'sucker_punch'
|
||||
|
||||
# asset stuff
|
||||
gem 'jquery-rails'
|
||||
gem 'jquery-ui-rails'
|
||||
gem 'sass-rails'
|
||||
gem 'uglifier'
|
||||
|
||||
group :test do
|
||||
gem 'brakeman', require: false
|
||||
gem 'factory_bot_rails'
|
||||
gem 'json-schema'
|
||||
gem 'rspec-rails'
|
||||
gem 'shoulda-matchers'
|
||||
gem 'simplecov', require: false
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
gem 'better_errors'
|
||||
gem 'binding_of_caller'
|
||||
gem 'faker'
|
||||
gem 'pry-byebug'
|
||||
gem 'pry-rails'
|
||||
gem 'rubocop', '~> 0.48.1' # match code climate https://github.com/tootsuite/mastodon/issues/1758
|
||||
gem 'timecop'
|
||||
gem 'tunemygc'
|
||||
end
|
350
Gemfile.lock
350
Gemfile.lock
|
@ -1,350 +0,0 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (5.0.5)
|
||||
actionpack (= 5.0.5)
|
||||
nio4r (>= 1.2, < 3.0)
|
||||
websocket-driver (~> 0.6.1)
|
||||
actionmailer (5.0.5)
|
||||
actionpack (= 5.0.5)
|
||||
actionview (= 5.0.5)
|
||||
activejob (= 5.0.5)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (5.0.5)
|
||||
actionview (= 5.0.5)
|
||||
activesupport (= 5.0.5)
|
||||
rack (~> 2.0)
|
||||
rack-test (~> 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
||||
actionview (5.0.5)
|
||||
activesupport (= 5.0.5)
|
||||
builder (~> 3.1)
|
||||
erubis (~> 2.7.0)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
||||
active_model_serializers (0.10.6)
|
||||
actionpack (>= 4.1, < 6)
|
||||
activemodel (>= 4.1, < 6)
|
||||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.2)
|
||||
activejob (5.0.5)
|
||||
activesupport (= 5.0.5)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (5.0.5)
|
||||
activesupport (= 5.0.5)
|
||||
activerecord (5.0.5)
|
||||
activemodel (= 5.0.5)
|
||||
activesupport (= 5.0.5)
|
||||
arel (~> 7.0)
|
||||
activesupport (5.0.5)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (~> 0.7)
|
||||
minitest (~> 5.1)
|
||||
tzinfo (~> 1.1)
|
||||
addressable (2.5.2)
|
||||
public_suffix (>= 2.0.2, < 4.0)
|
||||
arel (7.1.4)
|
||||
ast (2.3.0)
|
||||
aws-sdk (2.7.0)
|
||||
aws-sdk-resources (= 2.7.0)
|
||||
aws-sdk-core (2.7.0)
|
||||
aws-sigv4 (~> 1.0)
|
||||
jmespath (~> 1.0)
|
||||
aws-sdk-resources (2.7.0)
|
||||
aws-sdk-core (= 2.7.0)
|
||||
aws-sigv4 (1.0.2)
|
||||
bcrypt (3.1.11)
|
||||
best_in_place (3.1.1)
|
||||
actionpack (>= 3.2)
|
||||
railties (>= 3.2)
|
||||
better_errors (2.3.0)
|
||||
coderay (>= 1.0.0)
|
||||
erubi (>= 1.0.0)
|
||||
rack (>= 0.9.0)
|
||||
binding_of_caller (0.7.2)
|
||||
debug_inspector (>= 0.0.1)
|
||||
brakeman (3.7.2)
|
||||
builder (3.2.3)
|
||||
byebug (9.1.0)
|
||||
carrierwave (1.1.0)
|
||||
activemodel (>= 4.0.0)
|
||||
activesupport (>= 4.0.0)
|
||||
mime-types (>= 1.16)
|
||||
case_transform (0.2)
|
||||
activesupport
|
||||
climate_control (0.2.0)
|
||||
cocaine (0.5.8)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coderay (1.1.2)
|
||||
concurrent-ruby (1.0.5)
|
||||
debug_inspector (0.0.3)
|
||||
delayed_job (4.1.3)
|
||||
activesupport (>= 3.0, < 5.2)
|
||||
delayed_job_active_record (4.1.2)
|
||||
activerecord (>= 3.0, < 5.2)
|
||||
delayed_job (>= 3.0, < 5)
|
||||
devise (4.3.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0, < 5.2)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
diff-lcs (1.3)
|
||||
docile (1.1.5)
|
||||
doorkeeper (4.2.6)
|
||||
railties (>= 4.2)
|
||||
dotenv (2.2.1)
|
||||
dotenv-rails (2.2.1)
|
||||
dotenv (= 2.2.1)
|
||||
railties (>= 3.2, < 5.2)
|
||||
erubi (1.6.1)
|
||||
erubis (2.7.0)
|
||||
exception_notification (4.2.2)
|
||||
actionmailer (>= 4.0, < 6)
|
||||
activesupport (>= 4.0, < 6)
|
||||
execjs (2.7.0)
|
||||
factory_bot (4.8.2)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_bot_rails (4.8.2)
|
||||
factory_bot (~> 4.8.2)
|
||||
railties (>= 3.0.0)
|
||||
faker (1.8.4)
|
||||
i18n (~> 0.5)
|
||||
ffi (1.9.18)
|
||||
globalid (0.4.0)
|
||||
activesupport (>= 4.2.0)
|
||||
httparty (0.15.6)
|
||||
multi_xml (>= 0.5.2)
|
||||
i18n (0.9.3)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jmespath (1.3.1)
|
||||
jquery-rails (4.3.1)
|
||||
rails-dom-testing (>= 1, < 3)
|
||||
railties (>= 4.2.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
jquery-ui-rails (6.0.1)
|
||||
railties (>= 3.2.16)
|
||||
json (2.1.0)
|
||||
json-schema (2.8.0)
|
||||
addressable (>= 2.4)
|
||||
jsonapi-renderer (0.1.3)
|
||||
kaminari (1.0.1)
|
||||
activesupport (>= 4.1.0)
|
||||
kaminari-actionview (= 1.0.1)
|
||||
kaminari-activerecord (= 1.0.1)
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-actionview (1.0.1)
|
||||
actionview
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-activerecord (1.0.1)
|
||||
activerecord
|
||||
kaminari-core (= 1.0.1)
|
||||
kaminari-core (1.0.1)
|
||||
loofah (2.0.3)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.6.6)
|
||||
mime-types (>= 1.16, < 4)
|
||||
mailboxer (0.15.1)
|
||||
carrierwave (>= 0.5.8)
|
||||
rails (>= 5.0.0)
|
||||
method_source (0.8.2)
|
||||
mime-types (3.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2016.0521)
|
||||
mimemagic (0.3.2)
|
||||
mini_portile2 (2.3.0)
|
||||
minitest (5.11.1)
|
||||
multi_xml (0.6.0)
|
||||
nio4r (2.1.0)
|
||||
nokogiri (1.8.1)
|
||||
mini_portile2 (~> 2.3.0)
|
||||
orm_adapter (0.5.0)
|
||||
paperclip (5.2.0)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
cocaine (~> 0.5.5)
|
||||
mime-types
|
||||
mimemagic (~> 0.3.0)
|
||||
parser (2.4.0.2)
|
||||
ast (~> 2.3)
|
||||
pg (0.21.0)
|
||||
powerpack (0.1.1)
|
||||
pry (0.10.4)
|
||||
coderay (~> 1.1.0)
|
||||
method_source (~> 0.8.1)
|
||||
slop (~> 3.4)
|
||||
pry-byebug (3.5.0)
|
||||
byebug (~> 9.1)
|
||||
pry (~> 0.10)
|
||||
pry-rails (0.3.6)
|
||||
pry (>= 0.10.4)
|
||||
public_suffix (3.0.0)
|
||||
puma (3.10.0)
|
||||
pundit (1.1.0)
|
||||
activesupport (>= 3.0.0)
|
||||
pundit_extra (0.3.0)
|
||||
rack (2.0.3)
|
||||
rack-attack (5.0.1)
|
||||
rack
|
||||
rack-cors (1.0.1)
|
||||
rack-test (0.6.3)
|
||||
rack (>= 1.0)
|
||||
rails (5.0.5)
|
||||
actioncable (= 5.0.5)
|
||||
actionmailer (= 5.0.5)
|
||||
actionpack (= 5.0.5)
|
||||
actionview (= 5.0.5)
|
||||
activejob (= 5.0.5)
|
||||
activemodel (= 5.0.5)
|
||||
activerecord (= 5.0.5)
|
||||
activesupport (= 5.0.5)
|
||||
bundler (>= 1.3.0)
|
||||
railties (= 5.0.5)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.0.3)
|
||||
loofah (~> 2.0)
|
||||
railties (5.0.5)
|
||||
actionpack (= 5.0.5)
|
||||
activesupport (= 5.0.5)
|
||||
method_source
|
||||
rake (>= 0.8.7)
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rainbow (2.2.2)
|
||||
rake
|
||||
rake (12.3.0)
|
||||
rb-fsevent (0.10.2)
|
||||
rb-inotify (0.9.10)
|
||||
ffi (>= 0.5.0, < 2)
|
||||
redis (3.3.3)
|
||||
responders (2.4.0)
|
||||
actionpack (>= 4.2.0, < 5.3)
|
||||
railties (>= 4.2.0, < 5.3)
|
||||
rspec-core (3.6.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-expectations (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-mocks (3.6.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-rails (3.6.1)
|
||||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
railties (>= 3.0)
|
||||
rspec-core (~> 3.6.0)
|
||||
rspec-expectations (~> 3.6.0)
|
||||
rspec-mocks (~> 3.6.0)
|
||||
rspec-support (~> 3.6.0)
|
||||
rspec-support (3.6.0)
|
||||
rubocop (0.48.1)
|
||||
parser (>= 2.3.3.1, < 3.0)
|
||||
powerpack (~> 0.1)
|
||||
rainbow (>= 1.99.1, < 3.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (~> 1.0, >= 1.0.1)
|
||||
ruby-progressbar (1.9.0)
|
||||
sass (3.5.1)
|
||||
sass-listen (~> 4.0.0)
|
||||
sass-listen (4.0.0)
|
||||
rb-fsevent (~> 0.9, >= 0.9.4)
|
||||
rb-inotify (~> 0.9, >= 0.9.7)
|
||||
sass-rails (5.0.6)
|
||||
railties (>= 4.0.0, < 6)
|
||||
sass (~> 3.1)
|
||||
sprockets (>= 2.8, < 4.0)
|
||||
sprockets-rails (>= 2.0, < 4.0)
|
||||
tilt (>= 1.1, < 3)
|
||||
shoulda-matchers (3.1.2)
|
||||
activesupport (>= 4.0.0)
|
||||
simplecov (0.15.0)
|
||||
docile (~> 1.1.0)
|
||||
json (>= 1.8, < 3)
|
||||
simplecov-html (~> 0.10.0)
|
||||
simplecov-html (0.10.2)
|
||||
slack-notifier (2.3.1)
|
||||
slop (3.6.0)
|
||||
snorlax (0.1.6)
|
||||
rails (> 4.1)
|
||||
sprockets (3.7.1)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
sprockets-rails (3.2.1)
|
||||
actionpack (>= 4.0)
|
||||
activesupport (>= 4.0)
|
||||
sprockets (>= 3.0.0)
|
||||
sucker_punch (2.0.3)
|
||||
concurrent-ruby (~> 1.0.0)
|
||||
thor (0.20.0)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.8)
|
||||
timecop (0.9.1)
|
||||
tunemygc (1.0.69)
|
||||
tzinfo (1.2.4)
|
||||
thread_safe (~> 0.1)
|
||||
uglifier (3.2.0)
|
||||
execjs (>= 0.3.0, < 3)
|
||||
unicode-display_width (1.3.0)
|
||||
warden (1.2.7)
|
||||
rack (>= 1.0)
|
||||
websocket-driver (0.6.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
||||
DEPENDENCIES
|
||||
active_model_serializers
|
||||
aws-sdk (~> 2.7.0)
|
||||
best_in_place
|
||||
better_errors
|
||||
binding_of_caller
|
||||
brakeman
|
||||
delayed_job
|
||||
delayed_job_active_record
|
||||
devise
|
||||
doorkeeper
|
||||
dotenv-rails
|
||||
exception_notification
|
||||
factory_bot_rails
|
||||
faker
|
||||
httparty
|
||||
jquery-rails
|
||||
jquery-ui-rails
|
||||
json
|
||||
json-schema
|
||||
kaminari
|
||||
mailboxer
|
||||
paperclip
|
||||
pg
|
||||
pry-byebug
|
||||
pry-rails
|
||||
puma
|
||||
pundit
|
||||
pundit_extra
|
||||
rack-attack
|
||||
rack-cors
|
||||
rails (~> 5.0.0)
|
||||
redis (~> 3.3.3)
|
||||
rspec-rails
|
||||
rubocop (~> 0.48.1)
|
||||
sass-rails
|
||||
shoulda-matchers
|
||||
simplecov
|
||||
slack-notifier
|
||||
snorlax
|
||||
sucker_punch
|
||||
timecop
|
||||
tunemygc
|
||||
uglifier
|
||||
|
||||
RUBY VERSION
|
||||
ruby 2.3.0p0
|
||||
|
||||
BUNDLED WITH
|
||||
1.16.1
|
661
LICENSE
661
LICENSE
|
@ -1,661 +0,0 @@
|
|||
GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
Version 3, 19 November 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The GNU Affero General Public License is a free, copyleft license for
|
||||
software and other kinds of works, specifically designed to ensure
|
||||
cooperation with the community in the case of network server software.
|
||||
|
||||
The licenses for most software and other practical works are designed
|
||||
to take away your freedom to share and change the works. By contrast,
|
||||
our General Public Licenses are intended to guarantee your freedom to
|
||||
share and change all versions of a program--to make sure it remains free
|
||||
software for all its users.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
them if you wish), that you receive source code or can get it if you
|
||||
want it, that you can change the software or use pieces of it in new
|
||||
free programs, and that you know you can do these things.
|
||||
|
||||
Developers that use our General Public Licenses protect your rights
|
||||
with two steps: (1) assert copyright on the software, and (2) offer
|
||||
you this License which gives you legal permission to copy, distribute
|
||||
and/or modify the software.
|
||||
|
||||
A secondary benefit of defending all users' freedom is that
|
||||
improvements made in alternate versions of the program, if they
|
||||
receive widespread use, become available for other developers to
|
||||
incorporate. Many developers of free software are heartened and
|
||||
encouraged by the resulting cooperation. However, in the case of
|
||||
software used on network servers, this result may fail to come about.
|
||||
The GNU General Public License permits making a modified version and
|
||||
letting the public access it on a server without ever releasing its
|
||||
source code to the public.
|
||||
|
||||
The GNU Affero General Public License is designed specifically to
|
||||
ensure that, in such cases, the modified source code becomes available
|
||||
to the community. It requires the operator of a network server to
|
||||
provide the source code of the modified version running there to the
|
||||
users of that server. Therefore, public use of a modified version, on
|
||||
a publicly accessible server, gives the public access to the source
|
||||
code of the modified version.
|
||||
|
||||
An older license, called the Affero General Public License and
|
||||
published by Affero, was designed to accomplish similar goals. This is
|
||||
a different license, not a version of the Affero GPL, but Affero has
|
||||
released a new version of the Affero GPL which permits relicensing under
|
||||
this license.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
TERMS AND CONDITIONS
|
||||
|
||||
0. Definitions.
|
||||
|
||||
"This License" refers to version 3 of the GNU Affero General Public License.
|
||||
|
||||
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||
works, such as semiconductor masks.
|
||||
|
||||
"The Program" refers to any copyrightable work licensed under this
|
||||
License. Each licensee is addressed as "you". "Licensees" and
|
||||
"recipients" may be individuals or organizations.
|
||||
|
||||
To "modify" a work means to copy from or adapt all or part of the work
|
||||
in a fashion requiring copyright permission, other than the making of an
|
||||
exact copy. The resulting work is called a "modified version" of the
|
||||
earlier work or a work "based on" the earlier work.
|
||||
|
||||
A "covered work" means either the unmodified Program or a work based
|
||||
on the Program.
|
||||
|
||||
To "propagate" a work means to do anything with it that, without
|
||||
permission, would make you directly or secondarily liable for
|
||||
infringement under applicable copyright law, except executing it on a
|
||||
computer or modifying a private copy. Propagation includes copying,
|
||||
distribution (with or without modification), making available to the
|
||||
public, and in some countries other activities as well.
|
||||
|
||||
To "convey" a work means any kind of propagation that enables other
|
||||
parties to make or receive copies. Mere interaction with a user through
|
||||
a computer network, with no transfer of a copy, is not conveying.
|
||||
|
||||
An interactive user interface displays "Appropriate Legal Notices"
|
||||
to the extent that it includes a convenient and prominently visible
|
||||
feature that (1) displays an appropriate copyright notice, and (2)
|
||||
tells the user that there is no warranty for the work (except to the
|
||||
extent that warranties are provided), that licensees may convey the
|
||||
work under this License, and how to view a copy of this License. If
|
||||
the interface presents a list of user commands or options, such as a
|
||||
menu, a prominent item in the list meets this criterion.
|
||||
|
||||
1. Source Code.
|
||||
|
||||
The "source code" for a work means the preferred form of the work
|
||||
for making modifications to it. "Object code" means any non-source
|
||||
form of a work.
|
||||
|
||||
A "Standard Interface" means an interface that either is an official
|
||||
standard defined by a recognized standards body, or, in the case of
|
||||
interfaces specified for a particular programming language, one that
|
||||
is widely used among developers working in that language.
|
||||
|
||||
The "System Libraries" of an executable work include anything, other
|
||||
than the work as a whole, that (a) is included in the normal form of
|
||||
packaging a Major Component, but which is not part of that Major
|
||||
Component, and (b) serves only to enable use of the work with that
|
||||
Major Component, or to implement a Standard Interface for which an
|
||||
implementation is available to the public in source code form. A
|
||||
"Major Component", in this context, means a major essential component
|
||||
(kernel, window system, and so on) of the specific operating system
|
||||
(if any) on which the executable work runs, or a compiler used to
|
||||
produce the work, or an object code interpreter used to run it.
|
||||
|
||||
The "Corresponding Source" for a work in object code form means all
|
||||
the source code needed to generate, install, and (for an executable
|
||||
work) run the object code and to modify the work, including scripts to
|
||||
control those activities. However, it does not include the work's
|
||||
System Libraries, or general-purpose tools or generally available free
|
||||
programs which are used unmodified in performing those activities but
|
||||
which are not part of the work. For example, Corresponding Source
|
||||
includes interface definition files associated with source files for
|
||||
the work, and the source code for shared libraries and dynamically
|
||||
linked subprograms that the work is specifically designed to require,
|
||||
such as by intimate data communication or control flow between those
|
||||
subprograms and other parts of the work.
|
||||
|
||||
The Corresponding Source need not include anything that users
|
||||
can regenerate automatically from other parts of the Corresponding
|
||||
Source.
|
||||
|
||||
The Corresponding Source for a work in source code form is that
|
||||
same work.
|
||||
|
||||
2. Basic Permissions.
|
||||
|
||||
All rights granted under this License are granted for the term of
|
||||
copyright on the Program, and are irrevocable provided the stated
|
||||
conditions are met. This License explicitly affirms your unlimited
|
||||
permission to run the unmodified Program. The output from running a
|
||||
covered work is covered by this License only if the output, given its
|
||||
content, constitutes a covered work. This License acknowledges your
|
||||
rights of fair use or other equivalent, as provided by copyright law.
|
||||
|
||||
You may make, run and propagate covered works that you do not
|
||||
convey, without conditions so long as your license otherwise remains
|
||||
in force. You may convey covered works to others for the sole purpose
|
||||
of having them make modifications exclusively for you, or provide you
|
||||
with facilities for running those works, provided that you comply with
|
||||
the terms of this License in conveying all material for which you do
|
||||
not control copyright. Those thus making or running the covered works
|
||||
for you must do so exclusively on your behalf, under your direction
|
||||
and control, on terms that prohibit them from making any copies of
|
||||
your copyrighted material outside their relationship with you.
|
||||
|
||||
Conveying under any other circumstances is permitted solely under
|
||||
the conditions stated below. Sublicensing is not allowed; section 10
|
||||
makes it unnecessary.
|
||||
|
||||
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||
|
||||
No covered work shall be deemed part of an effective technological
|
||||
measure under any applicable law fulfilling obligations under article
|
||||
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||
similar laws prohibiting or restricting circumvention of such
|
||||
measures.
|
||||
|
||||
When you convey a covered work, you waive any legal power to forbid
|
||||
circumvention of technological measures to the extent such circumvention
|
||||
is effected by exercising rights under this License with respect to
|
||||
the covered work, and you disclaim any intention to limit operation or
|
||||
modification of the work as a means of enforcing, against the work's
|
||||
users, your or third parties' legal rights to forbid circumvention of
|
||||
technological measures.
|
||||
|
||||
4. Conveying Verbatim Copies.
|
||||
|
||||
You may convey verbatim copies of the Program's source code as you
|
||||
receive it, in any medium, provided that you conspicuously and
|
||||
appropriately publish on each copy an appropriate copyright notice;
|
||||
keep intact all notices stating that this License and any
|
||||
non-permissive terms added in accord with section 7 apply to the code;
|
||||
keep intact all notices of the absence of any warranty; and give all
|
||||
recipients a copy of this License along with the Program.
|
||||
|
||||
You may charge any price or no price for each copy that you convey,
|
||||
and you may offer support or warranty protection for a fee.
|
||||
|
||||
5. Conveying Modified Source Versions.
|
||||
|
||||
You may convey a work based on the Program, or the modifications to
|
||||
produce it from the Program, in the form of source code under the
|
||||
terms of section 4, provided that you also meet all of these conditions:
|
||||
|
||||
a) The work must carry prominent notices stating that you modified
|
||||
it, and giving a relevant date.
|
||||
|
||||
b) The work must carry prominent notices stating that it is
|
||||
released under this License and any conditions added under section
|
||||
7. This requirement modifies the requirement in section 4 to
|
||||
"keep intact all notices".
|
||||
|
||||
c) You must license the entire work, as a whole, under this
|
||||
License to anyone who comes into possession of a copy. This
|
||||
License will therefore apply, along with any applicable section 7
|
||||
additional terms, to the whole of the work, and all its parts,
|
||||
regardless of how they are packaged. This License gives no
|
||||
permission to license the work in any other way, but it does not
|
||||
invalidate such permission if you have separately received it.
|
||||
|
||||
d) If the work has interactive user interfaces, each must display
|
||||
Appropriate Legal Notices; however, if the Program has interactive
|
||||
interfaces that do not display Appropriate Legal Notices, your
|
||||
work need not make them do so.
|
||||
|
||||
A compilation of a covered work with other separate and independent
|
||||
works, which are not by their nature extensions of the covered work,
|
||||
and which are not combined with it such as to form a larger program,
|
||||
in or on a volume of a storage or distribution medium, is called an
|
||||
"aggregate" if the compilation and its resulting copyright are not
|
||||
used to limit the access or legal rights of the compilation's users
|
||||
beyond what the individual works permit. Inclusion of a covered work
|
||||
in an aggregate does not cause this License to apply to the other
|
||||
parts of the aggregate.
|
||||
|
||||
6. Conveying Non-Source Forms.
|
||||
|
||||
You may convey a covered work in object code form under the terms
|
||||
of sections 4 and 5, provided that you also convey the
|
||||
machine-readable Corresponding Source under the terms of this License,
|
||||
in one of these ways:
|
||||
|
||||
a) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by the
|
||||
Corresponding Source fixed on a durable physical medium
|
||||
customarily used for software interchange.
|
||||
|
||||
b) Convey the object code in, or embodied in, a physical product
|
||||
(including a physical distribution medium), accompanied by a
|
||||
written offer, valid for at least three years and valid for as
|
||||
long as you offer spare parts or customer support for that product
|
||||
model, to give anyone who possesses the object code either (1) a
|
||||
copy of the Corresponding Source for all the software in the
|
||||
product that is covered by this License, on a durable physical
|
||||
medium customarily used for software interchange, for a price no
|
||||
more than your reasonable cost of physically performing this
|
||||
conveying of source, or (2) access to copy the
|
||||
Corresponding Source from a network server at no charge.
|
||||
|
||||
c) Convey individual copies of the object code with a copy of the
|
||||
written offer to provide the Corresponding Source. This
|
||||
alternative is allowed only occasionally and noncommercially, and
|
||||
only if you received the object code with such an offer, in accord
|
||||
with subsection 6b.
|
||||
|
||||
d) Convey the object code by offering access from a designated
|
||||
place (gratis or for a charge), and offer equivalent access to the
|
||||
Corresponding Source in the same way through the same place at no
|
||||
further charge. You need not require recipients to copy the
|
||||
Corresponding Source along with the object code. If the place to
|
||||
copy the object code is a network server, the Corresponding Source
|
||||
may be on a different server (operated by you or a third party)
|
||||
that supports equivalent copying facilities, provided you maintain
|
||||
clear directions next to the object code saying where to find the
|
||||
Corresponding Source. Regardless of what server hosts the
|
||||
Corresponding Source, you remain obligated to ensure that it is
|
||||
available for as long as needed to satisfy these requirements.
|
||||
|
||||
e) Convey the object code using peer-to-peer transmission, provided
|
||||
you inform other peers where the object code and Corresponding
|
||||
Source of the work are being offered to the general public at no
|
||||
charge under subsection 6d.
|
||||
|
||||
A separable portion of the object code, whose source code is excluded
|
||||
from the Corresponding Source as a System Library, need not be
|
||||
included in conveying the object code work.
|
||||
|
||||
A "User Product" is either (1) a "consumer product", which means any
|
||||
tangible personal property which is normally used for personal, family,
|
||||
or household purposes, or (2) anything designed or sold for incorporation
|
||||
into a dwelling. In determining whether a product is a consumer product,
|
||||
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||
product received by a particular user, "normally used" refers to a
|
||||
typical or common use of that class of product, regardless of the status
|
||||
of the particular user or of the way in which the particular user
|
||||
actually uses, or expects or is expected to use, the product. A product
|
||||
is a consumer product regardless of whether the product has substantial
|
||||
commercial, industrial or non-consumer uses, unless such uses represent
|
||||
the only significant mode of use of the product.
|
||||
|
||||
"Installation Information" for a User Product means any methods,
|
||||
procedures, authorization keys, or other information required to install
|
||||
and execute modified versions of a covered work in that User Product from
|
||||
a modified version of its Corresponding Source. The information must
|
||||
suffice to ensure that the continued functioning of the modified object
|
||||
code is in no case prevented or interfered with solely because
|
||||
modification has been made.
|
||||
|
||||
If you convey an object code work under this section in, or with, or
|
||||
specifically for use in, a User Product, and the conveying occurs as
|
||||
part of a transaction in which the right of possession and use of the
|
||||
User Product is transferred to the recipient in perpetuity or for a
|
||||
fixed term (regardless of how the transaction is characterized), the
|
||||
Corresponding Source conveyed under this section must be accompanied
|
||||
by the Installation Information. But this requirement does not apply
|
||||
if neither you nor any third party retains the ability to install
|
||||
modified object code on the User Product (for example, the work has
|
||||
been installed in ROM).
|
||||
|
||||
The requirement to provide Installation Information does not include a
|
||||
requirement to continue to provide support service, warranty, or updates
|
||||
for a work that has been modified or installed by the recipient, or for
|
||||
the User Product in which it has been modified or installed. Access to a
|
||||
network may be denied when the modification itself materially and
|
||||
adversely affects the operation of the network or violates the rules and
|
||||
protocols for communication across the network.
|
||||
|
||||
Corresponding Source conveyed, and Installation Information provided,
|
||||
in accord with this section must be in a format that is publicly
|
||||
documented (and with an implementation available to the public in
|
||||
source code form), and must require no special password or key for
|
||||
unpacking, reading or copying.
|
||||
|
||||
7. Additional Terms.
|
||||
|
||||
"Additional permissions" are terms that supplement the terms of this
|
||||
License by making exceptions from one or more of its conditions.
|
||||
Additional permissions that are applicable to the entire Program shall
|
||||
be treated as though they were included in this License, to the extent
|
||||
that they are valid under applicable law. If additional permissions
|
||||
apply only to part of the Program, that part may be used separately
|
||||
under those permissions, but the entire Program remains governed by
|
||||
this License without regard to the additional permissions.
|
||||
|
||||
When you convey a copy of a covered work, you may at your option
|
||||
remove any additional permissions from that copy, or from any part of
|
||||
it. (Additional permissions may be written to require their own
|
||||
removal in certain cases when you modify the work.) You may place
|
||||
additional permissions on material, added by you to a covered work,
|
||||
for which you have or can give appropriate copyright permission.
|
||||
|
||||
Notwithstanding any other provision of this License, for material you
|
||||
add to a covered work, you may (if authorized by the copyright holders of
|
||||
that material) supplement the terms of this License with terms:
|
||||
|
||||
a) Disclaiming warranty or limiting liability differently from the
|
||||
terms of sections 15 and 16 of this License; or
|
||||
|
||||
b) Requiring preservation of specified reasonable legal notices or
|
||||
author attributions in that material or in the Appropriate Legal
|
||||
Notices displayed by works containing it; or
|
||||
|
||||
c) Prohibiting misrepresentation of the origin of that material, or
|
||||
requiring that modified versions of such material be marked in
|
||||
reasonable ways as different from the original version; or
|
||||
|
||||
d) Limiting the use for publicity purposes of names of licensors or
|
||||
authors of the material; or
|
||||
|
||||
e) Declining to grant rights under trademark law for use of some
|
||||
trade names, trademarks, or service marks; or
|
||||
|
||||
f) Requiring indemnification of licensors and authors of that
|
||||
material by anyone who conveys the material (or modified versions of
|
||||
it) with contractual assumptions of liability to the recipient, for
|
||||
any liability that these contractual assumptions directly impose on
|
||||
those licensors and authors.
|
||||
|
||||
All other non-permissive additional terms are considered "further
|
||||
restrictions" within the meaning of section 10. If the Program as you
|
||||
received it, or any part of it, contains a notice stating that it is
|
||||
governed by this License along with a term that is a further
|
||||
restriction, you may remove that term. If a license document contains
|
||||
a further restriction but permits relicensing or conveying under this
|
||||
License, you may add to a covered work material governed by the terms
|
||||
of that license document, provided that the further restriction does
|
||||
not survive such relicensing or conveying.
|
||||
|
||||
If you add terms to a covered work in accord with this section, you
|
||||
must place, in the relevant source files, a statement of the
|
||||
additional terms that apply to those files, or a notice indicating
|
||||
where to find the applicable terms.
|
||||
|
||||
Additional terms, permissive or non-permissive, may be stated in the
|
||||
form of a separately written license, or stated as exceptions;
|
||||
the above requirements apply either way.
|
||||
|
||||
8. Termination.
|
||||
|
||||
You may not propagate or modify a covered work except as expressly
|
||||
provided under this License. Any attempt otherwise to propagate or
|
||||
modify it is void, and will automatically terminate your rights under
|
||||
this License (including any patent licenses granted under the third
|
||||
paragraph of section 11).
|
||||
|
||||
However, if you cease all violation of this License, then your
|
||||
license from a particular copyright holder is reinstated (a)
|
||||
provisionally, unless and until the copyright holder explicitly and
|
||||
finally terminates your license, and (b) permanently, if the copyright
|
||||
holder fails to notify you of the violation by some reasonable means
|
||||
prior to 60 days after the cessation.
|
||||
|
||||
Moreover, your license from a particular copyright holder is
|
||||
reinstated permanently if the copyright holder notifies you of the
|
||||
violation by some reasonable means, this is the first time you have
|
||||
received notice of violation of this License (for any work) from that
|
||||
copyright holder, and you cure the violation prior to 30 days after
|
||||
your receipt of the notice.
|
||||
|
||||
Termination of your rights under this section does not terminate the
|
||||
licenses of parties who have received copies or rights from you under
|
||||
this License. If your rights have been terminated and not permanently
|
||||
reinstated, you do not qualify to receive new licenses for the same
|
||||
material under section 10.
|
||||
|
||||
9. Acceptance Not Required for Having Copies.
|
||||
|
||||
You are not required to accept this License in order to receive or
|
||||
run a copy of the Program. Ancillary propagation of a covered work
|
||||
occurring solely as a consequence of using peer-to-peer transmission
|
||||
to receive a copy likewise does not require acceptance. However,
|
||||
nothing other than this License grants you permission to propagate or
|
||||
modify any covered work. These actions infringe copyright if you do
|
||||
not accept this License. Therefore, by modifying or propagating a
|
||||
covered work, you indicate your acceptance of this License to do so.
|
||||
|
||||
10. Automatic Licensing of Downstream Recipients.
|
||||
|
||||
Each time you convey a covered work, the recipient automatically
|
||||
receives a license from the original licensors, to run, modify and
|
||||
propagate that work, subject to this License. You are not responsible
|
||||
for enforcing compliance by third parties with this License.
|
||||
|
||||
An "entity transaction" is a transaction transferring control of an
|
||||
organization, or substantially all assets of one, or subdividing an
|
||||
organization, or merging organizations. If propagation of a covered
|
||||
work results from an entity transaction, each party to that
|
||||
transaction who receives a copy of the work also receives whatever
|
||||
licenses to the work the party's predecessor in interest had or could
|
||||
give under the previous paragraph, plus a right to possession of the
|
||||
Corresponding Source of the work from the predecessor in interest, if
|
||||
the predecessor has it or can get it with reasonable efforts.
|
||||
|
||||
You may not impose any further restrictions on the exercise of the
|
||||
rights granted or affirmed under this License. For example, you may
|
||||
not impose a license fee, royalty, or other charge for exercise of
|
||||
rights granted under this License, and you may not initiate litigation
|
||||
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||
any patent claim is infringed by making, using, selling, offering for
|
||||
sale, or importing the Program or any portion of it.
|
||||
|
||||
11. Patents.
|
||||
|
||||
A "contributor" is a copyright holder who authorizes use under this
|
||||
License of the Program or a work on which the Program is based. The
|
||||
work thus licensed is called the contributor's "contributor version".
|
||||
|
||||
A contributor's "essential patent claims" are all patent claims
|
||||
owned or controlled by the contributor, whether already acquired or
|
||||
hereafter acquired, that would be infringed by some manner, permitted
|
||||
by this License, of making, using, or selling its contributor version,
|
||||
but do not include claims that would be infringed only as a
|
||||
consequence of further modification of the contributor version. For
|
||||
purposes of this definition, "control" includes the right to grant
|
||||
patent sublicenses in a manner consistent with the requirements of
|
||||
this License.
|
||||
|
||||
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||
patent license under the contributor's essential patent claims, to
|
||||
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||
propagate the contents of its contributor version.
|
||||
|
||||
In the following three paragraphs, a "patent license" is any express
|
||||
agreement or commitment, however denominated, not to enforce a patent
|
||||
(such as an express permission to practice a patent or covenant not to
|
||||
sue for patent infringement). To "grant" such a patent license to a
|
||||
party means to make such an agreement or commitment not to enforce a
|
||||
patent against the party.
|
||||
|
||||
If you convey a covered work, knowingly relying on a patent license,
|
||||
and the Corresponding Source of the work is not available for anyone
|
||||
to copy, free of charge and under the terms of this License, through a
|
||||
publicly available network server or other readily accessible means,
|
||||
then you must either (1) cause the Corresponding Source to be so
|
||||
available, or (2) arrange to deprive yourself of the benefit of the
|
||||
patent license for this particular work, or (3) arrange, in a manner
|
||||
consistent with the requirements of this License, to extend the patent
|
||||
license to downstream recipients. "Knowingly relying" means you have
|
||||
actual knowledge that, but for the patent license, your conveying the
|
||||
covered work in a country, or your recipient's use of the covered work
|
||||
in a country, would infringe one or more identifiable patents in that
|
||||
country that you have reason to believe are valid.
|
||||
|
||||
If, pursuant to or in connection with a single transaction or
|
||||
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||
covered work, and grant a patent license to some of the parties
|
||||
receiving the covered work authorizing them to use, propagate, modify
|
||||
or convey a specific copy of the covered work, then the patent license
|
||||
you grant is automatically extended to all recipients of the covered
|
||||
work and works based on it.
|
||||
|
||||
A patent license is "discriminatory" if it does not include within
|
||||
the scope of its coverage, prohibits the exercise of, or is
|
||||
conditioned on the non-exercise of one or more of the rights that are
|
||||
specifically granted under this License. You may not convey a covered
|
||||
work if you are a party to an arrangement with a third party that is
|
||||
in the business of distributing software, under which you make payment
|
||||
to the third party based on the extent of your activity of conveying
|
||||
the work, and under which the third party grants, to any of the
|
||||
parties who would receive the covered work from you, a discriminatory
|
||||
patent license (a) in connection with copies of the covered work
|
||||
conveyed by you (or copies made from those copies), or (b) primarily
|
||||
for and in connection with specific products or compilations that
|
||||
contain the covered work, unless you entered into that arrangement,
|
||||
or that patent license was granted, prior to 28 March 2007.
|
||||
|
||||
Nothing in this License shall be construed as excluding or limiting
|
||||
any implied license or other defenses to infringement that may
|
||||
otherwise be available to you under applicable patent law.
|
||||
|
||||
12. No Surrender of Others' Freedom.
|
||||
|
||||
If conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot convey a
|
||||
covered work so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you may
|
||||
not convey it at all. For example, if you agree to terms that obligate you
|
||||
to collect a royalty for further conveying from those to whom you convey
|
||||
the Program, the only way you could satisfy both those terms and this
|
||||
License would be to refrain entirely from conveying the Program.
|
||||
|
||||
13. Remote Network Interaction; Use with the GNU General Public License.
|
||||
|
||||
Notwithstanding any other provision of this License, if you modify the
|
||||
Program, your modified version must prominently offer all users
|
||||
interacting with it remotely through a computer network (if your version
|
||||
supports such interaction) an opportunity to receive the Corresponding
|
||||
Source of your version by providing access to the Corresponding Source
|
||||
from a network server at no charge, through some standard or customary
|
||||
means of facilitating copying of software. This Corresponding Source
|
||||
shall include the Corresponding Source for any work covered by version 3
|
||||
of the GNU General Public License that is incorporated pursuant to the
|
||||
following paragraph.
|
||||
|
||||
Notwithstanding any other provision of this License, you have
|
||||
permission to link or combine any covered work with a work licensed
|
||||
under version 3 of the GNU General Public License into a single
|
||||
combined work, and to convey the resulting work. The terms of this
|
||||
License will continue to apply to the part which is the covered work,
|
||||
but the work with which it is combined will remain governed by version
|
||||
3 of the GNU General Public License.
|
||||
|
||||
14. Revised Versions of this License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions of
|
||||
the GNU Affero General Public License from time to time. Such new versions
|
||||
will be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Program specifies that a certain numbered version of the GNU Affero General
|
||||
Public License "or any later version" applies to it, you have the
|
||||
option of following the terms and conditions either of that numbered
|
||||
version or of any later version published by the Free Software
|
||||
Foundation. If the Program does not specify a version number of the
|
||||
GNU Affero General Public License, you may choose any version ever published
|
||||
by the Free Software Foundation.
|
||||
|
||||
If the Program specifies that a proxy can decide which future
|
||||
versions of the GNU Affero General Public License can be used, that proxy's
|
||||
public statement of acceptance of a version permanently authorizes you
|
||||
to choose that version for the Program.
|
||||
|
||||
Later license versions may give you additional or different
|
||||
permissions. However, no additional obligations are imposed on any
|
||||
author or copyright holder as a result of your choosing to follow a
|
||||
later version.
|
||||
|
||||
15. Disclaimer of Warranty.
|
||||
|
||||
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||
|
||||
16. Limitation of Liability.
|
||||
|
||||
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||
SUCH DAMAGES.
|
||||
|
||||
17. Interpretation of Sections 15 and 16.
|
||||
|
||||
If the disclaimer of warranty and limitation of liability provided
|
||||
above cannot be given local legal effect according to their terms,
|
||||
reviewing courts shall apply local law that most closely approximates
|
||||
an absolute waiver of all civil liability in connection with the
|
||||
Program, unless a warranty or assumption of liability accompanies a
|
||||
copy of the Program in return for a fee.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
state the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If your software can interact with users remotely through a computer
|
||||
network, you should also make sure that it provides a way for users to
|
||||
get its source. For example, if your program is a web application, its
|
||||
interface could display a "Source" link that leads users to an archive
|
||||
of the code. There are many ways you could offer source, and different
|
||||
solutions will be better for different programs; see section 13 for the
|
||||
specific requirements.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or school,
|
||||
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||
For more information on this, and how to apply and follow the GNU AGPL, see
|
||||
<http://www.gnu.org/licenses/>.
|
3
Procfile
3
Procfile
|
@ -1,3 +0,0 @@
|
|||
web: bundle exec puma -p $PORT
|
||||
worker: bundle exec rake jobs:work
|
||||
|
133
README.md
133
README.md
|
@ -1,62 +1,95 @@
|
|||
Metamaps
|
||||
=======
|
||||
[![Build Status](https://travis-ci.org/metamaps/metamaps-ui.svg?branch=master)](https://travis-ci.org/metamaps/metamaps-ui)
|
||||
|
||||
[![Build Status](https://travis-ci.org/metamaps/metamaps.svg?branch=develop)](https://travis-ci.org/metamaps/metamaps)
|
||||
[![Code Climate](https://codeclimate.com/github/metamaps/metamaps/badges/gpa.svg)](https://codeclimate.com/github/metamaps/metamaps)
|
||||
Make sure you're running a good up to date LTS version of `node`
|
||||
|
||||
## What is Metamaps?
|
||||
Install all the dependencies
|
||||
`$ npm install`
|
||||
|
||||
Metamaps is a free and open-source technology for changemakers, innovators, educators and students. It enables individuals and communities to build and visualize their shared knowledge and unlock their collective intelligence.
|
||||
Make sure you have `node-sass` installed
|
||||
`$ npm install -g node-sass`
|
||||
|
||||
You can find a version of this software running at [metamaps.cc][site-beta], where the technology is being tested in an open beta.
|
||||
Make sure that you have a .env file setup. You can copy the .example-env
|
||||
```
|
||||
$ cp .example-env .env
|
||||
```
|
||||
Edit it however you need to.
|
||||
|
||||
Metamaps is developed and maintained by a distributed, nomadic community comprised of technologists, artists and storytellers. You can get in touch by using whichever of these channels you prefer:
|
||||
Start up the nodejs server which serves the UI files, the socketio realtime server, and proxies requests to the API.
|
||||
```
|
||||
$ npm start
|
||||
```
|
||||
|
||||
## How do I learn more?
|
||||
|
||||
- Contact: [team@metamaps.cc](mailto:team@metamaps.cc) or [@metamapps](https://twitter.com/metamapps) on Twitter
|
||||
- User Documentation: [docs.metamaps.cc](https://docs.metamaps.cc)
|
||||
- User Community: [hylo.com/c/metamaps](https://www.hylo.com/c/metamaps)
|
||||
- To see what we're developing, or to weigh in on what you'd like to see developed, see our [Metamaps Feedback and Features](https://trello.com/b/uFOA6a2x/metamaps-feedback-feature-ideas-requests) board on trello
|
||||
- To follow along with, or contribute,to our design process, see our [Metamaps Design](https://trello.com/b/8HlCikOX/metamaps-design) board on trello
|
||||
- To follow along with, or contribute to, our development process, see our [Github Issues and Pull Requests](https://github.com/metamaps/metamaps/issues)
|
||||
- Request an invite to the open beta [here](https://metamaps.cc/request)
|
||||
Build the css. If you're developing and writing css, make sure that it will rebuild the css when you make changes, by running the `npm sass:watch` process and leaving it running.
|
||||
```
|
||||
$ npm run sass # to build the css once
|
||||
$ npm run sass:watch # to watch it for more changes
|
||||
```
|
||||
|
||||
<!-- markdown hack to split two lists -->
|
||||
Run the metamaps api in another terminal, on whichever port you configured in your .env file.
|
||||
For now, make sure you are running on the `add-user-route` branch of Metamaps, and that it's up to date with the latest on that branch
|
||||
`$ rails s -p 3001`
|
||||
|
||||
- To send us a personal message get in touch with us via email, Twitter, or Hylo
|
||||
- If you would like to report a bug, please check the [issues][contributing-issues] section in our [contributing instructions][contributing].
|
||||
- If you would like to get set up as a developer, that's great! Read on for help getting your development environment set up.
|
||||
open up http://localhost:3000 and start coding!
|
||||
|
||||
## Installation for local use or development of Metamaps
|
||||
Checklist
|
||||
- [x] Get the Import lightbox working, and not conflicting on screen
|
||||
- [x] Handling CSRF
|
||||
- [x] Fix images referenced in the JS
|
||||
- [x] Figure out how authentication of requests from the frontend to the API works
|
||||
- [x] Figure out how to combine the nodejs realtime server into server.js
|
||||
- [x] Notifications: make sure loading states are working for popup and page
|
||||
- [x] Request unreadNotificationCount
|
||||
- [x] Request invite code
|
||||
- [x] Request user object itself
|
||||
- [x] Load the metacodes
|
||||
- [x] move ImportDialog lightbox into main app
|
||||
- [x] create topic form
|
||||
- [x] Fork map lightbox / component
|
||||
- [x] lightboxes
|
||||
- [x] About lightbox
|
||||
- [x] Notifications: Request unreadNotificationCount
|
||||
- [x] notifications list
|
||||
- [x] notification page
|
||||
- [x] list metacodes
|
||||
- [x] new metacode
|
||||
- [x] edit metacode
|
||||
- [x] list metacode_sets
|
||||
- [x] new metacode set
|
||||
- [x] edit metacode set
|
||||
|
||||
First off is getting the code downloaded to your computer. You can download a zip file from github, but if you've got `git` you can just run `git clone https://github.com/metamaps/metamaps` in your terminal.
|
||||
- [ ] fix other places where metacode sets are used
|
||||
- [ ] page titles
|
||||
- [ ] mobile titles
|
||||
- [ ] open graph meta tags
|
||||
- [ ] make newtopic form load metacodes from users selected ones
|
||||
- [ ] create synapse form
|
||||
- [ ] replace old loader with react loader
|
||||
- [ ] ensure exports of maps work
|
||||
- [ ] Notifications: make sure notifications either look nice, or redirect
|
||||
- [ ] Notifications: pagination
|
||||
- [ ] Notifications: CSS fixes related to 'controller-x' in body classes
|
||||
- [ ] Make sure loading state for explore maps pages work
|
||||
- [ ] Get actioncable working
|
||||
- [ ] Switch Metacodes lightbox / component
|
||||
- [ ] break up index.html into parts
|
||||
- [ ] Handle CSS metacode colors
|
||||
- [ ] Fix Request An Invite page
|
||||
- [ ] Make 'new map' action work
|
||||
- [ ] Modify the remaining rails templates into JSX templates
|
||||
- [ ] authorized apps
|
||||
- [ ] registered apps
|
||||
- [ ] authorize
|
||||
- [ ] user passwords
|
||||
- [ ] Modify the RubyOnRails app to only serve JSON responses, no HTML pages anymore
|
||||
- [ ] Modify the frontend to request that data from the API which is necessary at first to load the page
|
||||
- [x] Load the metacode sets
|
||||
|
||||
There are instructions for setup on various platforms, with particular support for Mac and Ubuntu, which can be found here:
|
||||
- [Mac Install Walkthrough][mac-installation]
|
||||
- [Ubuntu Install Walkthrough][ubuntu-installation]
|
||||
To run the server as a daemon that will be re-run if it crashes, you can
|
||||
use the forever node package.
|
||||
```
|
||||
$ npm install -g forever
|
||||
$ forever start server.js
|
||||
```
|
||||
|
||||
If you prefer to isolate your install in a virtual machine, you may find it simpler to setup using Vagrant:
|
||||
- [Vagrant installation][vagrant-installation]
|
||||
|
||||
We don't promise support for Windows, but at one point we had it running and we've kept those docs available for reference
|
||||
- [Outdated Windows Walkthrough][windows-installation]
|
||||
|
||||
## Licensing information
|
||||
|
||||
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
|
||||
|
||||
The license can be read [here][license].
|
||||
|
||||
Copyright (c) 2017 Connor Turland
|
||||
|
||||
[site-beta]: http://metamaps.cc
|
||||
[license]: https://github.com/metamaps/metamaps/blob/develop/LICENSE
|
||||
[contributing]: https://github.com/metamaps/metamaps/blob/develop/doc/CONTRIBUTING.md
|
||||
[contributing-issues]: https://github.com/metamaps/metamaps/blob/develop/doc/CONTRIBUTING.md#reporting-bugs-and-other-issues
|
||||
[mac-installation]: https://github.com/metamaps/metamaps/blob/develop/doc/MacInstallation.md
|
||||
[ubuntu-installation]: https://github.com/metamaps/metamaps/blob/develop/doc/UbuntuInstallation.md
|
||||
[vagrant-installation]: https://github.com/metamaps/metamaps/blob/develop/doc/VagrantInstallation.md
|
||||
[windows-installation]: https://github.com/metamaps/metamaps/blob/develop/doc/WindowsInstallation.md
|
||||
To build the javascript file
|
||||
`$ npm run build`
|
9
Rakefile
9
Rakefile
|
@ -1,9 +0,0 @@
|
|||
#!/usr/bin/env rake
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Add your own tasks in files placed in lib/tasks ending in .rake,
|
||||
# for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
|
||||
|
||||
require File.expand_path('../config/application', __FILE__)
|
||||
|
||||
Metamaps::Application.load_tasks
|
45
Vagrantfile
vendored
45
Vagrantfile
vendored
|
@ -1,45 +0,0 @@
|
|||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
$script = <<SCRIPT
|
||||
|
||||
# install base req's
|
||||
sudo apt-get update
|
||||
sudo apt-get install git curl -y
|
||||
|
||||
# rvm and ruby
|
||||
su - vagrant -c 'curl -sSL https://rvm.io/mpapis.asc | gpg --import -'
|
||||
su - vagrant -c 'curl -sSL https://get.rvm.io | bash -s stable --ruby=2.3.0'
|
||||
|
||||
# install some other deps
|
||||
sudo apt-get install nodejs -y
|
||||
sudo apt-get install npm -y
|
||||
sudo apt-get install postgresql -y
|
||||
sudo apt-get install libpq-dev -y
|
||||
|
||||
# get imagemagick
|
||||
sudo apt-get install imagemagick --fix-missing
|
||||
|
||||
# Install node
|
||||
ln -fs /usr/bin/nodejs /usr/bin/node
|
||||
|
||||
# install forever for running the node server
|
||||
sudo npm install forever -g
|
||||
|
||||
# set the postgres password
|
||||
sudo -u postgres psql -c "ALTER USER postgres WITH PASSWORD '3112';"
|
||||
|
||||
SCRIPT
|
||||
|
||||
VAGRANTFILE_API_VERSION = '2'
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
config.vm.box = 'trusty64'
|
||||
config.vm.box_url = 'http://cloud-images.ubuntu.com/vagrant/trusty/current/trusty-server-cloudimg-amd64-vagrant-disk1.box'
|
||||
config.vm.network :forwarded_port, guest: 3000, host: 3000
|
||||
config.vm.network :forwarded_port, guest: 5000, host: 5000
|
||||
config.vm.network 'private_network', ip: '10.0.1.11'
|
||||
config.vm.synced_folder '.', '/vagrant', nfs: true
|
||||
|
||||
config.vm.provision 'shell', inline: $script
|
||||
end
|
34
apiProxyMiddleware.js
Normal file
34
apiProxyMiddleware.js
Normal file
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
This file takes appropriate requests from our UI client
|
||||
and pipes them through to the API, proxying the response
|
||||
back to the client. To do this, it needs to pass
|
||||
the _Metamaps_session cookie in particular,
|
||||
in order to make authorized requests
|
||||
*/
|
||||
|
||||
const request = require('request')
|
||||
const { API_PROTOCOL, API_HOST } = process.env
|
||||
|
||||
const API_URL = `${API_PROTOCOL}://${API_HOST}`
|
||||
|
||||
function apiProxyMiddleware (req, res, next) {
|
||||
// TODO: tidy this up!
|
||||
if (!(req.xhr || req.headers['content-type'] === 'application/json' || req.originalUrl.indexOf('.json') > -1 || req.method !== 'GET')) {
|
||||
return next()
|
||||
}
|
||||
const method = req.method.toLowerCase()
|
||||
req.pipe(
|
||||
request[method](API_URL + req.originalUrl, {
|
||||
headers: {
|
||||
...req.headers,
|
||||
cookie: `_Metamaps_session=${req.cookies._Metamaps_session}`,
|
||||
host: API_HOST
|
||||
},
|
||||
followRedirect: false
|
||||
})
|
||||
)
|
||||
.on('error', (err) => console.log(err))
|
||||
.pipe(res)
|
||||
}
|
||||
|
||||
module.exports = apiProxyMiddleware
|
|
@ -1,7 +0,0 @@
|
|||
// eslint-disable spaced-comment
|
||||
// JS and CSS bundles
|
||||
//= link_directory ../javascripts .js
|
||||
//= link_directory ../stylesheets .css
|
||||
|
||||
// Other
|
||||
//= link_tree ../images
|
|
@ -1,21 +0,0 @@
|
|||
/* global Metamaps */
|
||||
|
||||
/* erb variables from rails */
|
||||
Metamaps.ServerData = Metamaps.ServerData || {}
|
||||
Metamaps.ServerData['junto_spinner_darkgrey.gif'] = '<%= asset_path('junto_spinner_darkgrey.gif') %>'
|
||||
Metamaps.ServerData['user.png'] = '<%= asset_path('user.png') %>'
|
||||
Metamaps.ServerData['icons/wildcard.png'] = '<%= asset_path('icons/wildcard.png') %>'
|
||||
Metamaps.ServerData['topic_description_signifier.png'] = '<%= asset_path('topic_description_signifier.png') %>'
|
||||
Metamaps.ServerData['topic_link_signifier.png'] = '<%= asset_path('topic_link_signifier.png') %>'
|
||||
Metamaps.ServerData['synapse16.png'] = '<%= asset_path('synapse16.png') %>'
|
||||
Metamaps.ServerData['sounds/MM_sounds.mp3'] = '<%= asset_path 'sounds/MM_sounds.mp3' %>'
|
||||
Metamaps.ServerData['sounds/MM_sounds.ogg'] = '<%= asset_path 'sounds/MM_sounds.ogg' %>'
|
||||
Metamaps.ServerData['exploremaps_sprite.png'] = '<%= asset_path 'exploremaps_sprite.png' %>'
|
||||
Metamaps.ServerData['map_control_sprite.png'] = '<%= asset_path 'map_control_sprite.png' %>'
|
||||
Metamaps.ServerData['user_sprite.png'] = '<%= asset_path 'user_sprite.png' %>'
|
||||
Metamaps.ServerData.Metacodes = <%= Metacode.all.to_json.gsub(%r[(icon.*?)(\"},)], '\1?purple=stupid\2').html_safe %>
|
||||
Metamaps.ServerData.REALTIME_SERVER = '<%= ENV['REALTIME_SERVER'] %>'
|
||||
Metamaps.ServerData.RAILS_ENV = '<%= ENV['RAILS_ENV'] %>'
|
||||
Metamaps.ServerData.VERSION = '<%= METAMAPS_VERSION %>'
|
||||
Metamaps.ServerData.BUILD = '<%= METAMAPS_BUILD %>'
|
||||
Metamaps.ServerData.LAST_UPDATED = '<%= METAMAPS_LAST_UPDATED %>'
|
|
@ -1,23 +0,0 @@
|
|||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||
// listed below.
|
||||
//
|
||||
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
||||
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
||||
//
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// the compiled file.
|
||||
//
|
||||
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
||||
// GO AFTER THE REQUIRES BELOW.
|
||||
//
|
||||
/* eslint-disable spaced-comment */
|
||||
//= require jquery
|
||||
//= require jquery-ui
|
||||
//= require jquery_ujs
|
||||
//= require action_cable
|
||||
//= require_directory ./lib
|
||||
//= require ./cloudcarousel-secret
|
||||
//= require ./metamaps.secret.bundle
|
||||
//= require ./Metamaps.ServerData
|
||||
//= require homepageVimeoFallback
|
||||
/* eslint-enable spaced-comment */
|
|
@ -1,22 +0,0 @@
|
|||
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
||||
// listed below.
|
||||
//
|
||||
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
||||
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
||||
//
|
||||
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
||||
// the compiled file.
|
||||
//
|
||||
// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
|
||||
// GO AFTER THE REQUIRES BELOW.
|
||||
//
|
||||
/* eslint-disable spaced-comment */
|
||||
//= require jquery
|
||||
//= require jquery-ui
|
||||
//= require jquery_ujs
|
||||
//= require action_cable
|
||||
//= require_directory ./lib
|
||||
//= require ./webpacked/metamaps.bundle
|
||||
//= require ./Metamaps.ServerData
|
||||
//= require homepageVimeoFallback
|
||||
/* eslint-enable spaced-comment */
|
|
@ -1,438 +0,0 @@
|
|||
//////////////////////////////////////////////////////////////////////////////////
|
||||
// CloudCarousel V1.0.5
|
||||
// (c) 2011 by R Cecco. <http://www.professorcloud.com>
|
||||
// MIT License
|
||||
//
|
||||
// Reflection code based on plugin by Christophe Beyls <http://www.digitalia.be>
|
||||
//
|
||||
// Please retain this copyright header in all versions of the software
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
var matched, browser;
|
||||
|
||||
jQuery.uaMatch = function( ua ) {
|
||||
ua = ua.toLowerCase();
|
||||
|
||||
var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
|
||||
/(msie) ([\w.]+)/.exec( ua ) ||
|
||||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
|
||||
[];
|
||||
|
||||
return {
|
||||
browser: match[ 1 ] || "",
|
||||
version: match[ 2 ] || "0"
|
||||
};
|
||||
};
|
||||
|
||||
matched = jQuery.uaMatch( navigator.userAgent );
|
||||
browser = {};
|
||||
|
||||
if ( matched.browser ) {
|
||||
browser[ matched.browser ] = true;
|
||||
browser.version = matched.version;
|
||||
}
|
||||
|
||||
// Chrome is Webkit, but Webkit is also Safari.
|
||||
if ( browser.chrome ) {
|
||||
browser.webkit = true;
|
||||
} else if ( browser.webkit ) {
|
||||
browser.safari = true;
|
||||
}
|
||||
|
||||
jQuery.browser = browser;
|
||||
|
||||
(function($) {
|
||||
|
||||
// START Reflection object.
|
||||
// Creates a reflection for underneath an image.
|
||||
// IE uses an image with IE specific filter properties, other browsers use the Canvas tag.
|
||||
// The position and size of the reflection gets updated by updateAll() in Controller.
|
||||
function Reflection(img, reflHeight, opacity) {
|
||||
|
||||
var reflection, cntx, imageWidth = img.width, imageHeight = img.width, gradient, parent;
|
||||
|
||||
parent = $(img.parentNode);
|
||||
this.element = reflection = parent.append("<canvas class='reflection' style='position:absolute'/>").find(':last')[0];
|
||||
if ( !reflection.getContext && $.browser.msie) {
|
||||
this.element = reflection = parent.append("<img class='reflection' style='position:absolute'/>").find(':last')[0];
|
||||
reflection.src = img.src;
|
||||
reflection.style.filter = "flipv progid:DXImageTransform.Microsoft.Alpha(opacity=" + (opacity * 100) + ", style=1, finishOpacity=0, startx=0, starty=0, finishx=0, finishy=" + (reflHeight / imageHeight * 100) + ")";
|
||||
|
||||
} else {
|
||||
cntx = reflection.getContext("2d");
|
||||
try {
|
||||
|
||||
|
||||
$(reflection).attr({width: imageWidth, height: reflHeight});
|
||||
cntx.save();
|
||||
cntx.translate(0, imageHeight-1);
|
||||
cntx.scale(1, -1);
|
||||
cntx.drawImage(img, 0, 0, imageWidth, imageHeight);
|
||||
cntx.restore();
|
||||
cntx.globalCompositeOperation = "destination-out";
|
||||
gradient = cntx.createLinearGradient(0, 0, 0, reflHeight);
|
||||
gradient.addColorStop(0, "rgba(255, 255, 255, " + (1 - opacity) + ")");
|
||||
gradient.addColorStop(1, "rgba(255, 255, 255, 1.0)");
|
||||
cntx.fillStyle = gradient;
|
||||
cntx.fillRect(0, 0, imageWidth, reflHeight);
|
||||
} catch(e) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Store a copy of the alt and title attrs into the reflection
|
||||
$(reflection).attr({ 'alt': $(img).attr('alt'), title: $(img).attr('title')} );
|
||||
|
||||
} //END Reflection object
|
||||
|
||||
// START Item object.
|
||||
// A wrapper object for items within the carousel.
|
||||
var Item = function(imgIn, options)
|
||||
{
|
||||
this.orgWidth = imgIn.width;
|
||||
this.orgHeight = imgIn.height;
|
||||
this.image = imgIn;
|
||||
this.reflection = null;
|
||||
this.alt = imgIn.alt;
|
||||
this.title = imgIn.title;
|
||||
this.imageOK = false;
|
||||
this.options = options;
|
||||
|
||||
this.imageOK = true;
|
||||
|
||||
if (this.options.reflHeight > 0)
|
||||
{
|
||||
this.reflection = new Reflection(this.image, this.options.reflHeight, this.options.reflOpacity);
|
||||
}
|
||||
$(this.image).css('position','absolute'); // Bizarre. This seems to reset image width to 0 on webkit!
|
||||
};// END Item object
|
||||
|
||||
|
||||
// Controller object.
|
||||
// This handles moving all the items, dealing with mouse clicks etc.
|
||||
var Controller = function(container, images, options)
|
||||
{
|
||||
var items = [], funcSin = Math.sin, funcCos = Math.cos, ctx=this;
|
||||
this.controlTimer = 0;
|
||||
this.stopped = false;
|
||||
//this.imagesLoaded = 0;
|
||||
this.container = container;
|
||||
this.xRadius = options.xRadius;
|
||||
this.yRadius = options.yRadius;
|
||||
this.showFrontTextTimer = 0;
|
||||
this.autoRotateTimer = 0;
|
||||
if (options.xRadius === 0)
|
||||
{
|
||||
this.xRadius = ($(container).width()/2.3);
|
||||
}
|
||||
if (options.yRadius === 0)
|
||||
{
|
||||
this.yRadius = ($(container).height()/6);
|
||||
}
|
||||
|
||||
this.xCentre = options.xPos;
|
||||
this.yCentre = options.yPos;
|
||||
this.frontIndex = 0; // Index of the item at the front
|
||||
|
||||
// Start with the first item at the front.
|
||||
this.rotation = this.destRotation = Math.PI/2;
|
||||
this.timeDelay = 1000/options.FPS;
|
||||
|
||||
// Turn on the infoBox
|
||||
if(options.altBox !== null)
|
||||
{
|
||||
$(options.altBox).css('display','block');
|
||||
$(options.titleBox).css('display','block');
|
||||
}
|
||||
// Turn on relative position for container to allow absolutely positioned elements
|
||||
// within it to work.
|
||||
$(container).css({ position:'relative', overflow:'hidden'} );
|
||||
|
||||
$(options.buttonLeft).css('display','inline');
|
||||
$(options.buttonRight).css('display','inline');
|
||||
|
||||
// Setup the buttons.
|
||||
$(options.buttonLeft).bind('mouseup',this,function(event){
|
||||
event.data.rotate(-1);
|
||||
return false;
|
||||
});
|
||||
$(options.buttonRight).bind('mouseup',this,function(event){
|
||||
event.data.rotate(1);
|
||||
return false;
|
||||
});
|
||||
|
||||
// START METAMAPS CODE
|
||||
// Add code that makes tab and shift+tab scroll through metacodes
|
||||
$('.new_topic').bind('keydown',this,function(event){
|
||||
if (event.keyCode == 9 && event.shiftKey) {
|
||||
$(container).show()
|
||||
event.data.rotate(-1);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
} else if (event.keyCode == 9) {
|
||||
$(container).show()
|
||||
event.data.rotate(1);
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
});
|
||||
// END METAMAPS CODE
|
||||
|
||||
// You will need this plugin for the mousewheel to work: http://plugins.jquery.com/project/mousewheel
|
||||
if (options.mouseWheel)
|
||||
{
|
||||
// START METAMAPS CODE
|
||||
/*$('body').bind('mousewheel',this,function(event, delta) {
|
||||
if (Metamaps.Create.newTopic.beingCreated &&
|
||||
!Metamaps.Create.isSwitchingSet &&
|
||||
!Metamaps.Create.newTopic.pinned) {
|
||||
event.data.rotate(delta);
|
||||
return false;
|
||||
}
|
||||
});*/
|
||||
// END METAMAPS CODE
|
||||
// ORIGINAL CODE
|
||||
// $(container).bind('mousewheel',this,function(event, delta) {
|
||||
// event.data.rotate(delta);
|
||||
// return false;
|
||||
// });
|
||||
//
|
||||
}
|
||||
$(container).unbind('mouseover click').bind('mouseover click',this,function(event){
|
||||
|
||||
clearInterval(event.data.autoRotateTimer); // Stop auto rotation if mouse over.
|
||||
var text = $(event.target).attr('alt');
|
||||
// If we have moved over a carousel item, then show the alt and title text.
|
||||
|
||||
if ( text !== undefined && text !== null )
|
||||
{
|
||||
|
||||
clearTimeout(event.data.showFrontTextTimer);
|
||||
$(options.altBox).html( ($(event.target).attr('alt') ));
|
||||
//$(options.titleBox).html( ($(event.target).attr('title') ));
|
||||
if ( options.bringToFront && event.type == 'click' )
|
||||
{
|
||||
$(options.titleBox).html( ($(event.target).attr('title') ));
|
||||
// START METAMAPS CODE
|
||||
Metamaps.Create.newTopic.metacode = $(event.target).attr('data-id');
|
||||
// END METAMAPS CODE
|
||||
var idx = $(event.target).data('itemIndex');
|
||||
var frontIndex = event.data.frontIndex;
|
||||
//var diff = idx - frontIndex;
|
||||
var diff = (idx - frontIndex) % images.length;
|
||||
if (Math.abs(diff) > images.length / 2) {
|
||||
diff += (diff > 0 ? -images.length : images.length);
|
||||
}
|
||||
|
||||
event.data.rotate(-diff);
|
||||
}
|
||||
}
|
||||
});
|
||||
// START METAMAPS CODE - initialize newTopic.metacode
|
||||
var first = $(this.container).find('img').get(0)
|
||||
Metamaps.Create.newTopic.metacode = $(first).data('id')
|
||||
// END METAMAPS CODE
|
||||
|
||||
// If we have moved out of a carousel item (or the container itself),
|
||||
// restore the text of the front item in 1 second.
|
||||
$(container).bind('mouseout',this,function(event){
|
||||
var context = event.data;
|
||||
clearTimeout(context.showFrontTextTimer);
|
||||
context.showFrontTextTimer = setTimeout( function(){context.showFrontText();},1000);
|
||||
context.autoRotate(); // Start auto rotation.
|
||||
});
|
||||
|
||||
// Prevent items from being selected as mouse is moved and clicked in the container.
|
||||
$(container).bind('mousedown',this,function(event){
|
||||
|
||||
event.data.container.focus();
|
||||
return false;
|
||||
});
|
||||
container.onselectstart = function () { return false; }; // For IE.
|
||||
|
||||
this.innerWrapper = $(container).wrapInner('<div style="position:absolute;width:100%;height:100%;"/>').children()[0];
|
||||
|
||||
// Shows the text from the front most item.
|
||||
this.showFrontText = function()
|
||||
{
|
||||
if ( items[this.frontIndex] === undefined ) { return; } // Images might not have loaded yet.
|
||||
// METAMAPS CODE
|
||||
Metamaps.Create.newTopic.setMetacode($(items[this.frontIndex].image).attr('data-id'))
|
||||
// NOT METAMAPS CODE
|
||||
//$(options.titleBox).html( $(items[this.frontIndex].image).attr('title'));
|
||||
//$(options.altBox).html( $(items[this.frontIndex].image).attr('alt'));
|
||||
};
|
||||
|
||||
this.go = function()
|
||||
{
|
||||
if(this.controlTimer !== 0) { return; }
|
||||
var context = this;
|
||||
this.controlTimer = setTimeout( function(){context.updateAll();},this.timeDelay);
|
||||
};
|
||||
|
||||
this.stop = function()
|
||||
{
|
||||
clearTimeout(this.controlTimer);
|
||||
this.controlTimer = 0;
|
||||
// METAMAPS CODE
|
||||
$(container).hide()
|
||||
// END METAMAPS CODE
|
||||
};
|
||||
|
||||
|
||||
// Starts the rotation of the carousel. Direction is the number (+-) of carousel items to rotate by.
|
||||
this.rotate = function(direction)
|
||||
{
|
||||
this.frontIndex -= direction;
|
||||
if (this.frontIndex == -1) this.frontIndex = items.length - 1;
|
||||
this.frontIndex %= items.length;
|
||||
this.destRotation += ( Math.PI / items.length ) * ( 2*direction );
|
||||
this.showFrontText();
|
||||
this.go();
|
||||
};
|
||||
|
||||
|
||||
this.autoRotate = function()
|
||||
{
|
||||
if ( options.autoRotate !== 'no' )
|
||||
{
|
||||
var dir = (options.autoRotate === 'right')? 1 : -1;
|
||||
this.autoRotateTimer = setInterval( function(){ctx.rotate(dir); }, options.autoRotateDelay );
|
||||
}
|
||||
};
|
||||
|
||||
// This is the main loop function that moves everything.
|
||||
this.updateAll = function()
|
||||
{
|
||||
var minScale = options.minScale; // This is the smallest scale applied to the furthest item.
|
||||
var smallRange = (1-minScale) * 0.5;
|
||||
var w,h,x,y,scale,item,sinVal;
|
||||
|
||||
var change = (this.destRotation - this.rotation);
|
||||
var absChange = Math.abs(change);
|
||||
|
||||
this.rotation += change * options.speed;
|
||||
if ( absChange < 0.001 ) { this.rotation = this.destRotation; }
|
||||
var itemsLen = items.length;
|
||||
var spacing = (Math.PI / itemsLen) * 2;
|
||||
//var wrapStyle = null;
|
||||
var radians = this.rotation;
|
||||
var isMSIE = $.browser.msie;
|
||||
|
||||
// Turn off display. This can reduce repaints/reflows when making style and position changes in the loop.
|
||||
// See http://dev.opera.com/articles/view/efficient-javascript/?page=3
|
||||
this.innerWrapper.style.display = 'none';
|
||||
|
||||
var style;
|
||||
var px = 'px', reflHeight;
|
||||
var context = this;
|
||||
for (var i = 0; i<itemsLen ;i++)
|
||||
{
|
||||
item = items[i];
|
||||
|
||||
sinVal = funcSin(radians);
|
||||
|
||||
scale = ((sinVal+1) * smallRange) + minScale;
|
||||
|
||||
x = this.xCentre + (( (funcCos(radians) * this.xRadius) - (item.orgWidth*0.5)) * scale);
|
||||
y = this.yCentre + (( (sinVal * this.yRadius) ) * scale);
|
||||
|
||||
if (item.imageOK)
|
||||
{
|
||||
var img = item.image;
|
||||
|
||||
img.style.zIndex = "" + (scale * 100)>>0; // >>0 = Math.foor(). Firefox doesn't like fractional decimals in z-index.
|
||||
w = img.width = item.orgWidth * scale;
|
||||
h = img.height = item.orgHeight * scale;
|
||||
img.style.left = x + px ;
|
||||
img.style.top = y + px;
|
||||
if (item.reflection !== null)
|
||||
{
|
||||
reflHeight = options.reflHeight * scale;
|
||||
style = item.reflection.element.style;
|
||||
style.left = x + px;
|
||||
style.top = y + h + options.reflGap * scale + px;
|
||||
style.width = w + px;
|
||||
if (isMSIE)
|
||||
{
|
||||
style.filter.finishy = (reflHeight / h * 100);
|
||||
}else
|
||||
{
|
||||
style.height = reflHeight + px;
|
||||
}
|
||||
}
|
||||
}
|
||||
radians += spacing;
|
||||
}
|
||||
// Turn display back on.
|
||||
this.innerWrapper.style.display = 'block';
|
||||
|
||||
// If we have a preceptable change in rotation then loop again next frame.
|
||||
if ( absChange >= 0.001 )
|
||||
{
|
||||
this.controlTimer = setTimeout( function(){context.updateAll();},this.timeDelay);
|
||||
}else
|
||||
{
|
||||
// Otherwise just stop completely.
|
||||
this.stop();
|
||||
}
|
||||
}; // END updateAll
|
||||
|
||||
// Create an Item object for each image
|
||||
// func = function(){return;ctx.updateAll();} ;
|
||||
|
||||
// Check if images have loaded. We need valid widths and heights for the reflections.
|
||||
this.checkImagesLoaded = function()
|
||||
{
|
||||
var i;
|
||||
for(i=0;i<images.length;i++) {
|
||||
if ( (images[i].width === undefined) || ( (images[i].complete !== undefined) && (!images[i].complete) ))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
for(i=0;i<images.length;i++) {
|
||||
items.push( new Item( images[i], options ) );
|
||||
$(images[i]).data('itemIndex',i);
|
||||
}
|
||||
// If all images have valid widths and heights, we can stop checking.
|
||||
clearInterval(this.tt);
|
||||
// METAMAPS COMMENT this.showFrontText();
|
||||
this.autoRotate();
|
||||
this.updateAll();
|
||||
|
||||
};
|
||||
|
||||
this.tt = setInterval( function(){ctx.checkImagesLoaded();},50);
|
||||
}; // END Controller object
|
||||
|
||||
// The jQuery plugin part. Iterates through items specified in selector and inits a Controller class for each one.
|
||||
$.fn.CloudCarousel = function(options) {
|
||||
|
||||
this.each( function() {
|
||||
|
||||
options = $.extend({}, {
|
||||
reflHeight:0,
|
||||
reflOpacity:0.5,
|
||||
reflGap:0,
|
||||
minScale:0.5,
|
||||
xPos:0,
|
||||
yPos:0,
|
||||
xRadius:0,
|
||||
yRadius:0,
|
||||
altBox:null,
|
||||
titleBox:null,
|
||||
FPS: 30,
|
||||
autoRotate: 'no',
|
||||
autoRotateDelay: 1500,
|
||||
speed:0.2,
|
||||
mouseWheel: false,
|
||||
bringToFront: false
|
||||
},options );
|
||||
// Create a Controller for each carousel.
|
||||
$(this).data('cloudcarousel', new Controller( this, $('.cloudcarousel',$(this)), options) );
|
||||
});
|
||||
return this;
|
||||
};
|
||||
|
||||
})(jQuery);
|
|
@ -1,29 +0,0 @@
|
|||
/* global $ */
|
||||
|
||||
$(document).ready(function() {
|
||||
if (window.location.pathname === '/') {
|
||||
$.ajax({
|
||||
type: 'GET',
|
||||
url: 'https://i.vimeocdn.com/video/',
|
||||
error: function(e) {
|
||||
$('.homeVideo').hide()
|
||||
$('.homeVideo').replaceWith($('<video/>', {
|
||||
poster: '<%= asset_path('metamaps-intro-poster.webp') %>',
|
||||
width: '560',
|
||||
height: '315',
|
||||
class: 'homeVideo',
|
||||
controls: ''
|
||||
}))
|
||||
$('.homeVideo').append($('<source/>', {
|
||||
src: 'https://metamaps.cc/videos/metamaps-intro.mp4',
|
||||
type: 'video/mp4'
|
||||
}))
|
||||
$('.homeVideo').append(
|
||||
'<p>You can watch our instruction video at ' +
|
||||
'<a href="https://metamaps.cc/videos/metamaps-intro.mp4">' +
|
||||
'https://metamaps.cc/videos/metamaps-intro.mp4</a>.'
|
||||
)
|
||||
}
|
||||
})
|
||||
}// if
|
||||
})
|
File diff suppressed because one or more lines are too long
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
||||
* listed below.
|
||||
*
|
||||
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
||||
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
||||
*
|
||||
* You're free to add application-wide styles to this file and they'll appear at the top of the
|
||||
* compiled file, but it's generally better to create a new file per style scope.
|
||||
*
|
||||
*= require ./special
|
||||
*/
|
|
@ -1,109 +0,0 @@
|
|||
#metacodeSelector {
|
||||
display: none;
|
||||
}
|
||||
.metacodeSelect {
|
||||
border-top: 1px solid #DDD;
|
||||
padding: 0;
|
||||
background: #FFF;
|
||||
|
||||
.metacodeFilterInput {
|
||||
width: 100px;
|
||||
outline: none;
|
||||
border: 0;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
color: #424242;
|
||||
font-family: 'din-medium', helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.metacodeList {
|
||||
list-style: none;
|
||||
background: #FFF;
|
||||
|
||||
li {
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover, &.keySelect {
|
||||
background: #4CAF50;
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
padding-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.selectedMetacode {
|
||||
float: left;
|
||||
background: #FFF;
|
||||
border-top-left-radius: 2px;
|
||||
border-bottom-left-radius: 2px;
|
||||
padding: 5px 10px 5px 6px;
|
||||
vertical-align: top;
|
||||
border-right: 1px solid #DDD;
|
||||
cursor: pointer;
|
||||
}
|
||||
.selectedMetacode:hover, .selectedMetacode.isBeingSelected {
|
||||
background: #EDEDED;
|
||||
}
|
||||
.selectedMetacode img {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.selectedMetacode span {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.selectedMetacode .downArrow {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-style: solid;
|
||||
border-width: 8px 6px 0 6px;
|
||||
border-color: #777 transparent transparent transparent;
|
||||
margin-left: 2px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
.new_topic {
|
||||
margin: 0;
|
||||
margin-top: -17px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
#new_topic .twitter-typeahead {
|
||||
position: relative !important;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
.new_topic #topic_name,
|
||||
.new_topic .tt-hint {
|
||||
border-radius: none;
|
||||
border-top-right-radius: 2px;
|
||||
border-bottom-right-radius: 2px;
|
||||
}
|
||||
.openMetacodeSwitcher {
|
||||
top: -16px;
|
||||
left: -16px;
|
||||
}
|
||||
#metacodeImg {
|
||||
height: 120px;
|
||||
width: 380px;
|
||||
display: none;
|
||||
position: absolute !important;
|
||||
top: -30px;
|
||||
z-index: -1;
|
||||
}
|
||||
#metacodeImgTitle {
|
||||
display: none;
|
||||
float: left;
|
||||
width: 120px;
|
||||
text-align: center;
|
||||
margin-left: 110px;
|
||||
}
|
|
@ -1,473 +0,0 @@
|
|||
/* basic scrollbar styling */
|
||||
/* vertical scrollbar */
|
||||
.mCSB_container{
|
||||
width:auto;
|
||||
margin-right:30px;
|
||||
overflow:hidden;
|
||||
}
|
||||
.mCSB_container.mCS_no_scrollbar{
|
||||
margin-right:0;
|
||||
}
|
||||
.mCS_disabled>.mCustomScrollBox>.mCSB_container.mCS_no_scrollbar,
|
||||
.mCS_destroyed>.mCustomScrollBox>.mCSB_container.mCS_no_scrollbar{
|
||||
margin-right:30px;
|
||||
}
|
||||
.mCustomScrollBox>.mCSB_scrollTools{
|
||||
width:16px;
|
||||
height:100%;
|
||||
top:0;
|
||||
right:0;
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_draggerContainer{
|
||||
position:absolute;
|
||||
top:0;
|
||||
left:0;
|
||||
bottom:0;
|
||||
right:0;
|
||||
height:auto;
|
||||
}
|
||||
.mCSB_scrollTools a+.mCSB_draggerContainer{
|
||||
margin:20px 0;
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:2px;
|
||||
height:100%;
|
||||
margin:0 auto;
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
border-radius:10px;
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_dragger{
|
||||
cursor:pointer;
|
||||
width:100%;
|
||||
height:30px;
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:4px;
|
||||
height:100%;
|
||||
margin:0 auto;
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
border-radius:10px;
|
||||
text-align:center;
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonUp,
|
||||
.mCSB_scrollTools .mCSB_buttonDown{
|
||||
display:block;
|
||||
position:relative;
|
||||
height:20px;
|
||||
overflow:hidden;
|
||||
margin:0 auto;
|
||||
cursor:pointer;
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonDown{
|
||||
top:100%;
|
||||
margin-top:-40px;
|
||||
}
|
||||
/* horizontal scrollbar */
|
||||
.mCSB_horizontal>.mCSB_container{
|
||||
height:auto;
|
||||
margin-right:0;
|
||||
margin-bottom:30px;
|
||||
overflow:hidden;
|
||||
}
|
||||
.mCSB_horizontal>.mCSB_container.mCS_no_scrollbar{
|
||||
margin-bottom:0;
|
||||
}
|
||||
.mCS_disabled>.mCSB_horizontal>.mCSB_container.mCS_no_scrollbar,
|
||||
.mCS_destroyed>.mCSB_horizontal>.mCSB_container.mCS_no_scrollbar{
|
||||
margin-right:0;
|
||||
margin-bottom:30px;
|
||||
}
|
||||
.mCSB_horizontal.mCustomScrollBox>.mCSB_scrollTools{
|
||||
width:100%;
|
||||
height:16px;
|
||||
top:auto;
|
||||
right:auto;
|
||||
bottom:0;
|
||||
left:0;
|
||||
overflow:hidden;
|
||||
}
|
||||
.mCSB_horizontal>.mCSB_scrollTools a+.mCSB_draggerContainer{
|
||||
margin:0 20px;
|
||||
}
|
||||
.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:100%;
|
||||
height:2px;
|
||||
margin:7px 0;
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
border-radius:10px;
|
||||
}
|
||||
.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger{
|
||||
width:30px;
|
||||
height:100%;
|
||||
}
|
||||
.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:100%;
|
||||
height:4px;
|
||||
margin:6px auto;
|
||||
-webkit-border-radius:10px;
|
||||
-moz-border-radius:10px;
|
||||
border-radius:10px;
|
||||
}
|
||||
.mCSB_horizontal>.mCSB_scrollTools .mCSB_buttonLeft,
|
||||
.mCSB_horizontal>.mCSB_scrollTools .mCSB_buttonRight{
|
||||
display:block;
|
||||
position:relative;
|
||||
width:20px;
|
||||
height:100%;
|
||||
overflow:hidden;
|
||||
margin:0 auto;
|
||||
cursor:pointer;
|
||||
float:left;
|
||||
}
|
||||
.mCSB_horizontal>.mCSB_scrollTools .mCSB_buttonRight{
|
||||
margin-left:-40px;
|
||||
float:right;
|
||||
}
|
||||
.mCustomScrollBox{
|
||||
-ms-touch-action:none; /*MSPointer events - direct all pointer events to js*/
|
||||
}
|
||||
|
||||
/* default scrollbar colors and backgrounds (default theme) */
|
||||
.mCustomScrollBox>.mCSB_scrollTools{
|
||||
opacity:0.75;
|
||||
filter:"alpha(opacity=75)"; -ms-filter:"alpha(opacity=75)"; /* old ie */
|
||||
}
|
||||
.mCustomScrollBox:hover>.mCSB_scrollTools{
|
||||
opacity:1;
|
||||
filter:"alpha(opacity=100)"; -ms-filter:"alpha(opacity=100)"; /* old ie */
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_draggerRail{
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.4);
|
||||
filter:"alpha(opacity=40)"; -ms-filter:"alpha(opacity=40)"; /* old ie */
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
background:#fff; /* rgba fallback */
|
||||
background:rgba(255,255,255,0.75);
|
||||
filter:"alpha(opacity=75)"; -ms-filter:"alpha(opacity=75)"; /* old ie */
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{
|
||||
background:rgba(255,255,255,0.85);
|
||||
filter:"alpha(opacity=85)"; -ms-filter:"alpha(opacity=85)"; /* old ie */
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,
|
||||
.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{
|
||||
background:rgba(255,255,255,0.9);
|
||||
filter:"alpha(opacity=90)"; -ms-filter:"alpha(opacity=90)"; /* old ie */
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonUp,
|
||||
.mCSB_scrollTools .mCSB_buttonDown,
|
||||
.mCSB_scrollTools .mCSB_buttonLeft,
|
||||
.mCSB_scrollTools .mCSB_buttonRight{
|
||||
background-repeat:no-repeat;
|
||||
opacity:0.4;
|
||||
filter:"alpha(opacity=40)"; -ms-filter:"alpha(opacity=40)"; /* old ie */
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonUp{
|
||||
background-position:0 0;
|
||||
/*
|
||||
sprites locations are 0 0/-16px 0/-32px 0/-48px 0 (light) and -80px 0/-96px 0/-112px 0/-128px 0 (dark)
|
||||
*/
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonDown{
|
||||
background-position:0 -20px;
|
||||
/*
|
||||
sprites locations are 0 -20px/-16px -20px/-32px -20px/-48px -20px (light) and -80px -20px/-96px -20px/-112px -20px/-128px -20px (dark)
|
||||
*/
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonLeft{
|
||||
background-position:0 -40px;
|
||||
/*
|
||||
sprites locations are 0 -40px/-20px -40px/-40px -40px/-60px -40px (light) and -80px -40px/-100px -40px/-120px -40px/-140px -40px (dark)
|
||||
*/
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonRight{
|
||||
background-position:0 -56px;
|
||||
/*
|
||||
sprites locations are 0 -56px/-20px -56px/-40px -56px/-60px -56px (light) and -80px -56px/-100px -56px/-120px -56px/-140px -56px (dark)
|
||||
*/
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonUp:hover,
|
||||
.mCSB_scrollTools .mCSB_buttonDown:hover,
|
||||
.mCSB_scrollTools .mCSB_buttonLeft:hover,
|
||||
.mCSB_scrollTools .mCSB_buttonRight:hover{
|
||||
opacity:0.75;
|
||||
filter:"alpha(opacity=75)"; -ms-filter:"alpha(opacity=75)"; /* old ie */
|
||||
}
|
||||
.mCSB_scrollTools .mCSB_buttonUp:active,
|
||||
.mCSB_scrollTools .mCSB_buttonDown:active,
|
||||
.mCSB_scrollTools .mCSB_buttonLeft:active,
|
||||
.mCSB_scrollTools .mCSB_buttonRight:active{
|
||||
opacity:0.9;
|
||||
filter:"alpha(opacity=90)"; -ms-filter:"alpha(opacity=90)"; /* old ie */
|
||||
}
|
||||
|
||||
/*scrollbar themes*/
|
||||
/*dark (dark colored scrollbar)*/
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.15);
|
||||
}
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.75);
|
||||
}
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{
|
||||
background:rgba(0,0,0,0.85);
|
||||
}
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{
|
||||
background:rgba(0,0,0,0.9);
|
||||
}
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_buttonUp{
|
||||
background-position:-80px 0;
|
||||
}
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_buttonDown{
|
||||
background-position:-80px -20px;
|
||||
}
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_buttonLeft{
|
||||
background-position:-80px -40px;
|
||||
}
|
||||
.mCS-dark>.mCSB_scrollTools .mCSB_buttonRight{
|
||||
background-position:-80px -56px;
|
||||
}
|
||||
/*light-2*/
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:4px;
|
||||
background:#fff; /* rgba fallback */
|
||||
background:rgba(255,255,255,0.1);
|
||||
-webkit-border-radius:1px;
|
||||
-moz-border-radius:1px;
|
||||
border-radius:1px;
|
||||
}
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:4px;
|
||||
background:#fff; /* rgba fallback */
|
||||
background:rgba(255,255,255,0.75);
|
||||
-webkit-border-radius:1px;
|
||||
-moz-border-radius:1px;
|
||||
border-radius:1px;
|
||||
}
|
||||
.mCS-light-2.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:100%;
|
||||
height:4px;
|
||||
margin:6px 0;
|
||||
}
|
||||
.mCS-light-2.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:100%;
|
||||
height:4px;
|
||||
margin:6px auto;
|
||||
}
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{
|
||||
background:rgba(255,255,255,0.85);
|
||||
}
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{
|
||||
background:rgba(255,255,255,0.9);
|
||||
}
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_buttonUp{
|
||||
background-position:-32px 0;
|
||||
}
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_buttonDown{
|
||||
background-position:-32px -20px;
|
||||
}
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_buttonLeft{
|
||||
background-position:-40px -40px;
|
||||
}
|
||||
.mCS-light-2>.mCSB_scrollTools .mCSB_buttonRight{
|
||||
background-position:-40px -56px;
|
||||
}
|
||||
/*dark-2*/
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:4px;
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.1);
|
||||
-webkit-border-radius:1px;
|
||||
-moz-border-radius:1px;
|
||||
border-radius:1px;
|
||||
}
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:4px;
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.75);
|
||||
-webkit-border-radius:1px;
|
||||
-moz-border-radius:1px;
|
||||
border-radius:1px;
|
||||
}
|
||||
.mCS-dark-2.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:100%;
|
||||
height:4px;
|
||||
margin:6px 0;
|
||||
}
|
||||
.mCS-dark-2.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:100%;
|
||||
height:4px;
|
||||
margin:6px auto;
|
||||
}
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{
|
||||
background:rgba(0,0,0,0.85);
|
||||
}
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{
|
||||
background:rgba(0,0,0,0.9);
|
||||
}
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_buttonUp{
|
||||
background-position:-112px 0;
|
||||
}
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_buttonDown{
|
||||
background-position:-112px -20px;
|
||||
}
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_buttonLeft{
|
||||
background-position:-120px -40px;
|
||||
}
|
||||
.mCS-dark-2>.mCSB_scrollTools .mCSB_buttonRight{
|
||||
background-position:-120px -56px;
|
||||
}
|
||||
/*light-thick*/
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:4px;
|
||||
background:#fff; /* rgba fallback */
|
||||
background:rgba(255,255,255,0.1);
|
||||
-webkit-border-radius:2px;
|
||||
-moz-border-radius:2px;
|
||||
border-radius:2px;
|
||||
}
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:6px;
|
||||
background:#fff; /* rgba fallback */
|
||||
background:rgba(255,255,255,0.75);
|
||||
-webkit-border-radius:2px;
|
||||
-moz-border-radius:2px;
|
||||
border-radius:2px;
|
||||
}
|
||||
.mCS-light-thick.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:100%;
|
||||
height:4px;
|
||||
margin:6px 0;
|
||||
}
|
||||
.mCS-light-thick.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:100%;
|
||||
height:6px;
|
||||
margin:5px auto;
|
||||
}
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{
|
||||
background:rgba(255,255,255,0.85);
|
||||
}
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{
|
||||
background:rgba(255,255,255,0.9);
|
||||
}
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_buttonUp{
|
||||
background-position:-16px 0;
|
||||
}
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_buttonDown{
|
||||
background-position:-16px -20px;
|
||||
}
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_buttonLeft{
|
||||
background-position:-20px -40px;
|
||||
}
|
||||
.mCS-light-thick>.mCSB_scrollTools .mCSB_buttonRight{
|
||||
background-position:-20px -56px;
|
||||
}
|
||||
/*dark-thick*/
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:4px;
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.1);
|
||||
-webkit-border-radius:2px;
|
||||
-moz-border-radius:2px;
|
||||
border-radius:2px;
|
||||
}
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:6px;
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.75);
|
||||
-webkit-border-radius:2px;
|
||||
-moz-border-radius:2px;
|
||||
border-radius:2px;
|
||||
}
|
||||
.mCS-dark-thick.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:100%;
|
||||
height:4px;
|
||||
margin:6px 0;
|
||||
}
|
||||
.mCS-dark-thick.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:100%;
|
||||
height:6px;
|
||||
margin:5px auto;
|
||||
}
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{
|
||||
background:rgba(0,0,0,0.85);
|
||||
}
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{
|
||||
background:rgba(0,0,0,0.9);
|
||||
}
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_buttonUp{
|
||||
background-position:-96px 0;
|
||||
}
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_buttonDown{
|
||||
background-position:-96px -20px;
|
||||
}
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_buttonLeft{
|
||||
background-position:-100px -40px;
|
||||
}
|
||||
.mCS-dark-thick>.mCSB_scrollTools .mCSB_buttonRight{
|
||||
background-position:-100px -56px;
|
||||
}
|
||||
/*light-thin*/
|
||||
.mCS-light-thin>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
background:#fff; /* rgba fallback */
|
||||
background:rgba(255,255,255,0.1);
|
||||
}
|
||||
.mCS-light-thin>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:2px;
|
||||
}
|
||||
.mCS-light-thin.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:100%;
|
||||
}
|
||||
.mCS-light-thin.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:100%;
|
||||
height:2px;
|
||||
margin:7px auto;
|
||||
}
|
||||
/*dark-thin*/
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.15);
|
||||
}
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:2px;
|
||||
background:#000; /* rgba fallback */
|
||||
background:rgba(0,0,0,0.75);
|
||||
}
|
||||
.mCS-dark-thin.mCSB_horizontal>.mCSB_scrollTools .mCSB_draggerRail{
|
||||
width:100%;
|
||||
}
|
||||
.mCS-dark-thin.mCSB_horizontal>.mCSB_scrollTools .mCSB_dragger .mCSB_dragger_bar{
|
||||
width:100%;
|
||||
height:2px;
|
||||
margin:7px auto;
|
||||
}
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_dragger:hover .mCSB_dragger_bar{
|
||||
background:rgba(0,0,0,0.85);
|
||||
}
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_dragger:active .mCSB_dragger_bar,
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_dragger.mCSB_dragger_onDrag .mCSB_dragger_bar{
|
||||
background:rgba(0,0,0,0.9);
|
||||
}
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonUp{
|
||||
background-position:-80px 0;
|
||||
}
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonDown{
|
||||
background-position:-80px -20px;
|
||||
}
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonLeft{
|
||||
background-position:-80px -40px;
|
||||
}
|
||||
.mCS-dark-thin>.mCSB_scrollTools .mCSB_buttonRight{
|
||||
background-position:-80px -56px;
|
||||
}
|
|
@ -1,6 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationCable
|
||||
class Channel < ActionCable::Channel::Base
|
||||
end
|
||||
end
|
|
@ -1,23 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationCable
|
||||
class Connection < ActionCable::Connection::Base
|
||||
identified_by :current_user
|
||||
|
||||
def connect
|
||||
self.current_user = find_verified_user
|
||||
logger.add_tags 'ActionCable', current_user.name
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def find_verified_user
|
||||
verified_user = User.find_by(id: cookies.signed['user.id'])
|
||||
if verified_user && cookies.signed['user.expires_at'] > Time.now.getlocal
|
||||
verified_user
|
||||
else
|
||||
reject_unauthorized_connection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MapChannel < ApplicationCable::Channel
|
||||
# Called when the consumer has successfully
|
||||
# become a subscriber of this channel.
|
||||
def subscribed
|
||||
map = Map.find(params[:id])
|
||||
return unless Pundit.policy(current_user, map).show?
|
||||
stream_from "map_#{params[:id]}"
|
||||
Events::UserPresentOnMap.publish!(map, current_user)
|
||||
end
|
||||
|
||||
def unsubscribed
|
||||
map = Map.find(params[:id])
|
||||
return unless Pundit.policy(current_user, map).show?
|
||||
Events::UserNotPresentOnMap.publish!(map, current_user)
|
||||
end
|
||||
end
|
|
@ -1,90 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccessController < ApplicationController
|
||||
before_action :require_user, only: %i[access access_request
|
||||
approve_access approve_access_post
|
||||
deny_access deny_access_post request_access]
|
||||
before_action :set_map, only: %i[access access_request
|
||||
approve_access approve_access_post
|
||||
deny_access deny_access_post request_access]
|
||||
after_action :verify_authorized
|
||||
|
||||
# GET maps/:id/request_access
|
||||
def request_access
|
||||
@map = nil
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
render 'maps/request_access'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/access_request
|
||||
def access_request
|
||||
AccessRequest.create(user: current_user, map: @map)
|
||||
respond_to do |format|
|
||||
format.json { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/access
|
||||
def access
|
||||
user_ids = params[:access].to_a.map(&:to_i) || []
|
||||
|
||||
@map.add_new_collaborators(user_ids)
|
||||
@map.remove_old_collaborators(user_ids)
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head :ok }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/approve_access/:request_id
|
||||
def approve_access
|
||||
request = AccessRequest.find(params[:request_id])
|
||||
request.approve # also marks mailboxer notification as read
|
||||
respond_to do |format|
|
||||
format.html { redirect_to map_path(@map), notice: 'Request was approved' }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/deny_access/:request_id
|
||||
def deny_access
|
||||
request = AccessRequest.find(params[:request_id])
|
||||
request.deny # also marks mailboxer notification as read
|
||||
respond_to do |format|
|
||||
format.html { redirect_to map_path(@map), notice: 'Request was turned down' }
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/approve_access/:request_id
|
||||
def approve_access_post
|
||||
request = AccessRequest.find(params[:request_id])
|
||||
request.approve
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.json do
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/deny_access/:request_id
|
||||
def deny_access_post
|
||||
request = AccessRequest.find(params[:request_id])
|
||||
request.deny
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.json do
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_map
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
end
|
||||
end
|
|
@ -1,13 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V1
|
||||
class DeprecatedController < ApplicationController
|
||||
def deprecated
|
||||
render json: {
|
||||
error: '/api/v1 has been deprecated! Please use /api/v2 instead.'
|
||||
}, status: :gone
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class MappingsController < WithUpdatesController
|
||||
def searchable_columns
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,16 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class MapsController < WithUpdatesController
|
||||
def searchable_columns
|
||||
%i[name desc]
|
||||
end
|
||||
|
||||
def apply_filters(collection)
|
||||
collection = collection.where(user_id: params[:user_id]) if params[:user_id]
|
||||
collection
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class MetacodesController < RestfulController
|
||||
def searchable_columns
|
||||
[:name]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,210 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class RestfulController < ActionController::Base
|
||||
include Pundit
|
||||
include PunditExtra
|
||||
|
||||
snorlax_used_rest!
|
||||
|
||||
before_action :load_resource, only: %i[show update destroy]
|
||||
after_action :verify_authorized
|
||||
|
||||
def index
|
||||
authorize resource_class
|
||||
instantiate_collection
|
||||
respond_with_collection
|
||||
end
|
||||
|
||||
def create
|
||||
instantiate_resource
|
||||
resource.user = current_user if current_user.present?
|
||||
authorize resource
|
||||
create_action
|
||||
respond_with_resource
|
||||
end
|
||||
|
||||
def destroy
|
||||
destroy_action
|
||||
head :no_content
|
||||
end
|
||||
|
||||
def catch_404
|
||||
skip_authorization
|
||||
render json: { error: '404 Not found' }, status: :not_found
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def accessible_records
|
||||
if current_user
|
||||
visible_records
|
||||
else
|
||||
public_records
|
||||
end
|
||||
end
|
||||
|
||||
def current_user
|
||||
token_user || doorkeeper_user
|
||||
end
|
||||
|
||||
def load_resource
|
||||
super
|
||||
authorize resource
|
||||
end
|
||||
|
||||
def resource_serializer
|
||||
"Api::V2::#{resource_name.camelize}Serializer".constantize
|
||||
end
|
||||
|
||||
def respond_with_resource(scope: default_scope, serializer: resource_serializer,
|
||||
root: serializer_root)
|
||||
if resource.errors.empty?
|
||||
render json: resource, scope: scope, serializer: serializer, root: root
|
||||
else
|
||||
respond_with_errors
|
||||
end
|
||||
end
|
||||
|
||||
def respond_with_collection(resources: collection, scope: default_scope,
|
||||
serializer: resource_serializer, root: serializer_root)
|
||||
pagination_link_headers!(pagination(resources))
|
||||
render json: resources, scope: scope, each_serializer: serializer, root: root,
|
||||
meta: pagination(resources), meta_key: :page
|
||||
end
|
||||
|
||||
def default_scope
|
||||
{
|
||||
embeds: embeds,
|
||||
current_user: current_user
|
||||
}
|
||||
end
|
||||
|
||||
def embeds
|
||||
(params[:embed] || '').split(',').map(&:to_sym)
|
||||
end
|
||||
|
||||
def token_user
|
||||
token = params[:access_token]
|
||||
access_token = Token.find_by(token: token)
|
||||
@token_user ||= access_token.user if access_token
|
||||
end
|
||||
|
||||
def doorkeeper_user
|
||||
return if doorkeeper_token.blank?
|
||||
doorkeeper_render_error unless valid_doorkeeper_token?
|
||||
@doorkeeper_user ||= User.find(doorkeeper_token.resource_owner_id)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
@permitted_params ||= PermittedParams.new(params)
|
||||
end
|
||||
|
||||
def serializer_root
|
||||
'data'
|
||||
end
|
||||
|
||||
def pagination(collection)
|
||||
return @pagination_data unless @pagination_data.nil?
|
||||
|
||||
current_page = (params[:page] || 1).to_i
|
||||
per = (params[:per] || 25).to_i
|
||||
total_pages = (collection.total_count.to_f / per).ceil
|
||||
@pagination_data = {
|
||||
current_page: current_page,
|
||||
next_page: current_page < total_pages ? current_page + 1 : 0,
|
||||
prev_page: current_page > 1 ? current_page - 1 : 0,
|
||||
total_pages: total_pages,
|
||||
total_count: collection.total_count,
|
||||
per: per
|
||||
}
|
||||
end
|
||||
|
||||
def pagination_link_headers!(data)
|
||||
base_url = request.base_url + request.path
|
||||
old_query = request.query_parameters
|
||||
nxt = old_query.merge(page: data[:next_page]).map { |x| x.join('=') }.join('&')
|
||||
prev = old_query.merge(page: data[:prev_page]).map { |x| x.join('=') }.join('&')
|
||||
last = old_query.merge(page: data[:total_pages]).map { |x| x.join('=') }.join('&')
|
||||
|
||||
response.headers['Link'] = [
|
||||
%(<#{base_url}?#{nxt}>; rel="next"),
|
||||
%(<#{base_url}?#{prev}>; rel="prev"),
|
||||
%(<#{base_url}?#{last}>; rel="last")
|
||||
].join(',')
|
||||
response.headers['X-Total-Pages'] = data[:total_pages].to_s
|
||||
response.headers['X-Total-Count'] = data[:total_count].to_s
|
||||
response.headers['X-Per-Page'] = data[:per].to_s
|
||||
end
|
||||
|
||||
def instantiate_collection
|
||||
collection = accessible_records
|
||||
collection = yield collection if block_given?
|
||||
collection = search_by_q(collection) if params[:q]
|
||||
collection = apply_filters(collection)
|
||||
collection = order_by_sort(collection) if params[:sort]
|
||||
collection = collection.page(params[:page]).per(params[:per])
|
||||
self.collection = collection
|
||||
end
|
||||
|
||||
# override this method to explicitly set searchable columns
|
||||
def searchable_columns
|
||||
return @searchable_columns unless @searchable_columns.nil?
|
||||
|
||||
columns = resource_class.columns.select do |column|
|
||||
column.type == :text || column.type == :string
|
||||
end
|
||||
@searchable_columns = columns.map(&:name)
|
||||
end
|
||||
|
||||
# e.g. ?q=test&searchfields=name,desc
|
||||
def searchfields
|
||||
return searchable_columns if params[:searchfields].blank?
|
||||
|
||||
searchfields = params[:searchfields].split(',')
|
||||
searchfields.select! { |f| searchable_columns.include?(f.to_sym) }
|
||||
searchfields.empty? ? searchable_columns : searchfields
|
||||
end
|
||||
|
||||
# thanks to http://stackoverflow.com/questions/4430578
|
||||
def search_by_q(collection)
|
||||
table = resource_class.arel_table
|
||||
safe_query = "%#{params[:q].gsub(/[%_]/, '\\\\\0')}%"
|
||||
search_column = ->(column) { table[column].matches(safe_query) }
|
||||
|
||||
condition = searchfields.reduce(nil) do |prev, column|
|
||||
next search_column.call(column) if prev.nil?
|
||||
search_column.call(column).or(prev)
|
||||
end
|
||||
collection.where(condition)
|
||||
end
|
||||
|
||||
def apply_filters(collection)
|
||||
# override this function for specific filters
|
||||
collection
|
||||
end
|
||||
|
||||
def order_by_sort(collection)
|
||||
builder = collection
|
||||
sorts = params[:sort].split(',')
|
||||
sorts.each do |sort|
|
||||
direction = sort.starts_with?('-') ? 'desc' : 'asc'
|
||||
sort = sort.sub(/^-/, '')
|
||||
if resource_class.columns.map(&:name).include?(sort)
|
||||
builder = builder.order(sort => direction)
|
||||
end
|
||||
end
|
||||
builder
|
||||
end
|
||||
|
||||
def visible_records
|
||||
policy_scope(resource_class)
|
||||
end
|
||||
|
||||
def public_records
|
||||
policy_scope(resource_class)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,22 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class SessionsController < ApplicationController
|
||||
def create
|
||||
@user = User.find_by(email: params[:email])
|
||||
if @user&.valid_password(params[:password])
|
||||
sign_in(@user)
|
||||
render json: @user
|
||||
else
|
||||
render json: { error: 'Error' }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
sign_out
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,30 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class StarsController < RestfulController
|
||||
skip_before_action :load_resource
|
||||
|
||||
def create
|
||||
@map = Map.find(params[:id])
|
||||
@star = Star.new(user: current_user, map: @map)
|
||||
authorize @map, :star?
|
||||
create_action
|
||||
|
||||
if @star.errors.empty?
|
||||
render json: @map, scope: default_scope, serializer: MapSerializer, root: serializer_root
|
||||
else
|
||||
respond_with_errors
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map, :unstar?
|
||||
@star = @map.stars.find_by(user: current_user)
|
||||
@star.destroy if @star.present?
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class SynapsesController < WithUpdatesController
|
||||
def searchable_columns
|
||||
[:desc]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,32 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class TokensController < RestfulController
|
||||
protect_from_forgery
|
||||
|
||||
def searchable_columns
|
||||
[:description]
|
||||
end
|
||||
|
||||
def create
|
||||
if params[:token].blank?
|
||||
self.resource = resource_class.new
|
||||
else
|
||||
instantiate_resource
|
||||
end
|
||||
|
||||
resource.user = current_user if current_user.present?
|
||||
authorize resource
|
||||
create_action
|
||||
respond_with_resource
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def current_user
|
||||
token_user || doorkeeper_user || method(:current_user).super_method.super_method.call
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class TopicsController < WithUpdatesController
|
||||
def searchable_columns
|
||||
%i[name desc link]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class UsersController < RestfulController
|
||||
def current
|
||||
raise Pundit::NotAuthorizedError if current_user.nil?
|
||||
@user = current_user
|
||||
authorize @user
|
||||
show # delegate to the normal show function
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def searchable_columns
|
||||
[:name]
|
||||
end
|
||||
|
||||
# only ask serializer to return is_admin field if we're on the
|
||||
# current_user action
|
||||
def default_scope
|
||||
super.merge(show_full_user: action_name == 'current')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,28 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Api
|
||||
module V2
|
||||
class WithUpdatesController < RestfulController
|
||||
def create
|
||||
instantiate_resource
|
||||
resource.user = current_user if current_user.present?
|
||||
resource.updated_by = current_user if current_user.present?
|
||||
authorize resource
|
||||
create_action
|
||||
respond_with_resource
|
||||
end
|
||||
|
||||
def update
|
||||
resource.updated_by = current_user if current_user.present?
|
||||
update_action
|
||||
respond_with_resource
|
||||
end
|
||||
|
||||
def destroy
|
||||
resource.updated_by = current_user if current_user.present?
|
||||
destroy_action
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,84 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationController < ActionController::Base
|
||||
include ApplicationHelper
|
||||
include Pundit
|
||||
include PunditExtra
|
||||
rescue_from Pundit::NotAuthorizedError, with: :handle_unauthorized
|
||||
protect_from_forgery(with: :exception)
|
||||
|
||||
before_action :invite_link
|
||||
before_action :prepare_exception_notifier
|
||||
after_action :allow_embedding
|
||||
|
||||
def default_serializer_options
|
||||
{ root: false }
|
||||
end
|
||||
|
||||
# this is for global login
|
||||
include ContentHelper
|
||||
|
||||
helper_method :user
|
||||
helper_method :authenticated?
|
||||
helper_method :admin?
|
||||
|
||||
def handle_unauthorized
|
||||
if authenticated? && (params[:controller] == 'maps') && (params[:action] == 'show')
|
||||
redirect_to request_access_map_path(params[:id])
|
||||
elsif authenticated?
|
||||
redirect_to root_path, notice: "You don't have permission to see that page."
|
||||
else
|
||||
store_location_for(resource, request.fullpath)
|
||||
redirect_to sign_in_path, notice: 'Try signing in to do that.'
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def invite_link
|
||||
@invite_link = "#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : '')
|
||||
end
|
||||
|
||||
def require_no_user
|
||||
return true unless authenticated?
|
||||
redirect_to edit_user_path(user), notice: 'You must be logged out.'
|
||||
false
|
||||
end
|
||||
|
||||
def require_user
|
||||
return true if authenticated?
|
||||
redirect_to sign_in_path, notice: 'You must be logged in.'
|
||||
false
|
||||
end
|
||||
|
||||
def require_admin
|
||||
return true if authenticated? && admin?
|
||||
redirect_to root_url, notice: 'You need to be an admin for that.'
|
||||
false
|
||||
end
|
||||
|
||||
def user
|
||||
current_user
|
||||
end
|
||||
|
||||
def authenticated?
|
||||
current_user
|
||||
end
|
||||
|
||||
def admin?
|
||||
authenticated? && current_user.admin
|
||||
end
|
||||
|
||||
def allow_embedding
|
||||
# allow all
|
||||
response.headers.except! 'X-Frame-Options'
|
||||
# or allow a whitelist
|
||||
# response.headers['X-Frame-Options'] = 'ALLOW-FROM http://blog.metamaps.cc'
|
||||
end
|
||||
|
||||
def prepare_exception_notifier
|
||||
request.env['exception_notifier.exception_data'] = {
|
||||
current_user: current_user
|
||||
}
|
||||
end
|
||||
end
|
|
@ -1,90 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ExploreController < ApplicationController
|
||||
before_action :require_authentication, only: %i[mine shared starred]
|
||||
before_action :authorize_explore
|
||||
after_action :verify_authorized
|
||||
after_action :verify_policy_scoped
|
||||
|
||||
respond_to :html, :json, :csv
|
||||
|
||||
# GET /explore/active
|
||||
def active
|
||||
@maps = map_scope(Map.where.not(name: 'Untitled Map').where.not(permission: 'private'))
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
# root url => main/home. main/home renders maps/activemaps view.
|
||||
redirect_to(root_url) && return if authenticated?
|
||||
respond_with(@maps, @user)
|
||||
end
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/featured
|
||||
def featured
|
||||
@maps = map_scope(Map.where(featured: true))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/mine
|
||||
def mine
|
||||
@maps = map_scope(Map.where(user_id: current_user.id))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/shared
|
||||
def shared
|
||||
@maps = map_scope(Map.where(id: current_user.shared_maps.map(&:id)))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/starred
|
||||
def starred
|
||||
@maps = map_scope(Map.where(id: current_user.stars.map(&:map_id)))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /explore/mapper/:id
|
||||
def mapper
|
||||
@user = User.find(params[:id])
|
||||
@maps = map_scope(Map.where(user: @user))
|
||||
|
||||
respond_to do |format|
|
||||
format.html { respond_with(@maps, @user) }
|
||||
format.json { render json: @maps.to_json }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def map_scope(scope)
|
||||
policy_scope(scope).order(updated_at: :desc).page(params[:page]).per(20)
|
||||
end
|
||||
|
||||
def authorize_explore
|
||||
authorize :Explore
|
||||
end
|
||||
|
||||
def require_authentication
|
||||
# skip_policy_scope
|
||||
redirect_to explore_active_path unless authenticated?
|
||||
end
|
||||
end
|
|
@ -1,44 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# bad code that should be checked over before entering one of the
|
||||
# nice files from the right side of this repo
|
||||
class HacksController < ApplicationController
|
||||
include ActionView::Helpers::TextHelper # string truncate method
|
||||
|
||||
# rate limited by rack-attack - currently 5r/s
|
||||
def load_url_title
|
||||
authorize :Hack
|
||||
url = params[:url]
|
||||
response, url = get_with_redirects(url)
|
||||
title = get_encoded_title(response)
|
||||
render json: { success: true, title: title, url: url }
|
||||
rescue StandardError
|
||||
render json: { success: false }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def get_with_redirects(url)
|
||||
uri = URI.parse(url)
|
||||
response = Net::HTTP.get_response(uri)
|
||||
while response.code == '301'
|
||||
uri = URI.parse(response['location'])
|
||||
response = Net::HTTP.get_response(uri)
|
||||
end
|
||||
[response, uri.to_s]
|
||||
end
|
||||
|
||||
def get_encoded_title(http_response)
|
||||
# ensure there's actually an html title tag
|
||||
title = http_response.body.sub(%r{.*(<title>.*</title>).*}m, '\1')
|
||||
return '' unless title.starts_with?('<title>')
|
||||
return '' unless title.ends_with?('</title>')
|
||||
title = title.sub('<title>', '').sub(%r{</title>$}, '')
|
||||
|
||||
# encode and trim the title to 140 usable characters
|
||||
charset = http_response['content-type'].sub(/.*charset=(.*);?.*/, '\1')
|
||||
charset = nil if charset == 'text/html'
|
||||
title = title.force_encoding(charset) if charset
|
||||
truncate(title, length: 140)
|
||||
end
|
||||
end
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MainController < ApplicationController
|
||||
before_action :authorize_main
|
||||
after_action :verify_authorized
|
||||
|
||||
# GET /
|
||||
def home
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
if authenticated?
|
||||
@maps = policy_scope(Map).where.not(name: 'Untitled Map').where.not(permission: 'private')
|
||||
.order(updated_at: :desc).page(1).per(20)
|
||||
render 'explore/active'
|
||||
else
|
||||
render 'main/home'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET /request
|
||||
def requestinvite
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_main
|
||||
authorize :Main
|
||||
end
|
||||
end
|
|
@ -1,64 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MappingsController < ApplicationController
|
||||
before_action :require_user, only: %i[create update destroy]
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
respond_to :json
|
||||
|
||||
# GET /mappings/1.json
|
||||
def show
|
||||
@mapping = Mapping.find(params[:id])
|
||||
authorize @mapping
|
||||
|
||||
render json: @mapping
|
||||
end
|
||||
|
||||
# POST /mappings.json
|
||||
def create
|
||||
@mapping = Mapping.new(mapping_params)
|
||||
authorize @mapping
|
||||
@mapping.user = current_user
|
||||
@mapping.updated_by = current_user
|
||||
|
||||
if @mapping.save
|
||||
render json: @mapping, status: :created
|
||||
else
|
||||
render json: @mapping.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /mappings/1.json
|
||||
def update
|
||||
@mapping = Mapping.find(params[:id])
|
||||
authorize @mapping
|
||||
@mapping.updated_by = current_user
|
||||
@mapping.map.updated_by = current_user
|
||||
@mapping.assign_attributes(mapping_params)
|
||||
|
||||
if @mapping.save
|
||||
head :no_content
|
||||
else
|
||||
render json: @mapping.errors, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /mappings/1.json
|
||||
def destroy
|
||||
@mapping = Mapping.find(params[:id])
|
||||
authorize @mapping
|
||||
@mapping.updated_by = current_user
|
||||
@mapping.map.updated_by = current_user
|
||||
@mapping.destroy
|
||||
|
||||
head :no_content
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def mapping_params
|
||||
params.require(:mapping).permit(:id, :xloc, :yloc, :mappable_id, :mappable_type, :map_id)
|
||||
end
|
||||
end
|
|
@ -1,215 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MapsController < ApplicationController
|
||||
before_action :require_user, only: %i[create update destroy events follow unfollow]
|
||||
before_action :set_map, only: %i[show conversation update destroy
|
||||
contains events export
|
||||
follow unfollow unfollow_from_email]
|
||||
after_action :verify_authorized
|
||||
|
||||
# GET maps/:id
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
UserMap.where(map: @map, user: current_user).map(&:mark_invite_notifications_as_read)
|
||||
@allmappers = @map.contributors
|
||||
@allcollaborators = @map.editors
|
||||
@alltopics = policy_scope(@map.topics)
|
||||
@allsynapses = policy_scope(@map.synapses)
|
||||
@allmappings = policy_scope(@map.mappings)
|
||||
@allmessages = @map.messages.sort_by(&:created_at)
|
||||
@allstars = @map.stars
|
||||
@allrequests = @map.access_requests
|
||||
end
|
||||
format.json { render json: @map }
|
||||
format.csv { redirect_to action: :export, format: :csv }
|
||||
format.ttl { redirect_to action: :export, format: :ttl }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/conversation
|
||||
def conversation
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
UserMap.where(map: @map, user: current_user).map(&:mark_invite_notifications_as_read)
|
||||
@allmappers = @map.contributors
|
||||
@allcollaborators = @map.editors
|
||||
@alltopics = policy_scope(@map.topics)
|
||||
@allsynapses = policy_scope(@map.synapses)
|
||||
@allmappings = policy_scope(@map.mappings)
|
||||
@allmessages = @map.messages.sort_by(&:created_at)
|
||||
@allstars = @map.stars
|
||||
@allrequests = @map.access_requests
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/new
|
||||
def new
|
||||
@map = Map.new(name: 'Untitled Map', permission: 'public', arranged: true)
|
||||
authorize @map
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@map.user = current_user
|
||||
@map.save
|
||||
redirect_to(map_path(@map) + '?new')
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps
|
||||
def create
|
||||
@map = Map.new(create_map_params)
|
||||
@map.user = current_user
|
||||
@map.updated_by = current_user
|
||||
@map.arranged = false
|
||||
authorize @map
|
||||
|
||||
if params[:topicsToMap].present?
|
||||
create_topics!
|
||||
create_synapses! if params[:synapsesToMap].present?
|
||||
@map.arranged = true
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
if @map.save
|
||||
format.json { render json: @map }
|
||||
else
|
||||
format.json { render json: 'invalid params' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT maps/:id
|
||||
def update
|
||||
@map.updated_by = current_user
|
||||
@map.assign_attributes(update_map_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @map.save
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.json { render json: @map.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE maps/:id
|
||||
def destroy
|
||||
@map.updated_by = current_user
|
||||
@map.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
head :no_content
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/contains
|
||||
def contains
|
||||
respond_to do |format|
|
||||
format.json { render json: @map.contains(current_user).as_json(user: current_user) }
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/export
|
||||
def export
|
||||
exporter = MapExportService.new(current_user, @map, base_url: request.base_url)
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: exporter.json }
|
||||
format.csv { send_data exporter.csv }
|
||||
format.ttl { render text: exporter.rdf }
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/events/:event
|
||||
def events
|
||||
valid_event = false
|
||||
if params[:event] == 'conversation'
|
||||
Events::ConversationStartedOnMap.publish!(@map, current_user)
|
||||
valid_event = true
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
head :bad_request unless valid_event
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/follow
|
||||
def follow
|
||||
follow = FollowService.follow(@map, current_user, 'followed')
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if follow
|
||||
head :ok
|
||||
else
|
||||
head :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/unfollow
|
||||
def unfollow
|
||||
FollowService.unfollow(@map, current_user)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET maps/:id/unfollow_from_email
|
||||
def unfollow_from_email
|
||||
FollowService.unfollow(@map, current_user)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
redirect_to map_path(@map), notice: 'You are no longer following this map'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_map
|
||||
@map = Map.find(params[:id])
|
||||
authorize @map
|
||||
end
|
||||
|
||||
def create_map_params
|
||||
params.permit(:name, :desc, :permission, :source_id)
|
||||
end
|
||||
|
||||
def update_map_params
|
||||
params.require(:map).permit(:id, :name, :arranged, :desc, :permission, :screenshot)
|
||||
end
|
||||
|
||||
def create_topics!
|
||||
params[:topicsToMap].split(',').each do |topic|
|
||||
topic = topic.split('/')
|
||||
mapping = Mapping.new(map: @map, user: current_user,
|
||||
mappable: Topic.find(topic[0]),
|
||||
xloc: topic[1], yloc: topic[2])
|
||||
authorize mapping, :create?
|
||||
mapping.save
|
||||
end
|
||||
end
|
||||
|
||||
def create_synapses!
|
||||
params[:synapsesToMap].split(',').each do |synapse_id|
|
||||
mapping = Mapping.new(map: @map, user: current_user,
|
||||
mappable: Synapse.find(synapse_id))
|
||||
authorize mapping, :create?
|
||||
mapping.save
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,68 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MessagesController < ApplicationController
|
||||
before_action :require_user, except: [:show]
|
||||
after_action :verify_authorized
|
||||
|
||||
# GET /messages/1.json
|
||||
def show
|
||||
@message = Message.find(params[:id])
|
||||
authorize @message
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @message }
|
||||
end
|
||||
end
|
||||
|
||||
# POST /messages
|
||||
# POST /messages.json
|
||||
def create
|
||||
@message = Message.new(message_params)
|
||||
@message.user = current_user
|
||||
authorize @message
|
||||
|
||||
respond_to do |format|
|
||||
if @message.save
|
||||
format.json { render json: @message, status: :created, location: messages_url }
|
||||
else
|
||||
format.json { render json: @message.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /messages/1
|
||||
# PUT /messages/1.json
|
||||
def update
|
||||
@message = Message.find(params[:id])
|
||||
authorize @message
|
||||
|
||||
respond_to do |format|
|
||||
if @message.update_attributes(message_params)
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.json { render json: @message.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /messages/1
|
||||
# DELETE /messages/1.json
|
||||
def destroy
|
||||
@message = Message.find(params[:id])
|
||||
authorize @message
|
||||
|
||||
@message.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def message_params
|
||||
# params.require(:message).permit(:id, :resource_id, :message)
|
||||
params.permit(:id, :resource_id, :resource_type, :message)
|
||||
end
|
||||
end
|
|
@ -1,129 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MetacodeSetsController < ApplicationController
|
||||
before_action :require_admin
|
||||
|
||||
# GET /metacode_sets
|
||||
# GET /metacode_sets.json
|
||||
def index
|
||||
@metacode_sets = MetacodeSet.order('name').all
|
||||
|
||||
respond_to do |format|
|
||||
format.html # index.html.erb
|
||||
format.json { render json: @metacode_sets }
|
||||
end
|
||||
end
|
||||
|
||||
### SHOW IS NOT CURRENTLY IN USE
|
||||
# GET /metacode_sets/1
|
||||
# GET /metacode_sets/1.json
|
||||
# def show
|
||||
# @metacode_set = MetacodeSet.find(params[:id])
|
||||
#
|
||||
# respond_to do |format|
|
||||
# format.html # show.html.erb
|
||||
# format.json { render json: @metacode_set }
|
||||
# end
|
||||
# end
|
||||
|
||||
# GET /metacode_sets/new
|
||||
# GET /metacode_sets/new.json
|
||||
def new
|
||||
@metacode_set = MetacodeSet.new
|
||||
|
||||
respond_to do |format|
|
||||
format.html # new.html.erb
|
||||
format.json { render json: @metacode_set }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /metacode_sets/1/edit
|
||||
def edit
|
||||
@metacode_set = MetacodeSet.find(params[:id])
|
||||
end
|
||||
|
||||
# POST /metacode_sets
|
||||
# POST /metacode_sets.json
|
||||
def create
|
||||
@user = current_user
|
||||
@metacode_set = MetacodeSet.new(metacode_set_params)
|
||||
@metacode_set.user_id = @user.id
|
||||
|
||||
respond_to do |format|
|
||||
if @metacode_set.save
|
||||
# create the InMetacodeSet for all the metacodes that were selected for the set
|
||||
@metacodes = params[:metacodes][:value].split(',')
|
||||
@metacodes.each do |m|
|
||||
InMetacodeSet.create(metacode_id: m, metacode_set_id: @metacode_set.id)
|
||||
end
|
||||
format.html do
|
||||
redirect_to metacode_sets_url,
|
||||
notice: 'Metacode set was successfully created.'
|
||||
end
|
||||
format.json do
|
||||
render json: @metacode_set, status: :created, location: metacode_sets_url
|
||||
end
|
||||
else
|
||||
format.html { render action: 'new' }
|
||||
format.json { render json: @metacode_set.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /metacode_sets/1
|
||||
# PUT /metacode_sets/1.json
|
||||
def update
|
||||
@metacode_set = MetacodeSet.find(params[:id])
|
||||
|
||||
respond_to do |format|
|
||||
if @metacode_set.update_attributes(metacode_set_params)
|
||||
|
||||
# build an array of the IDs of the metacodes currently in the set
|
||||
current_metacodes = @metacode_set.metacodes.map { |m| m.id.to_s }
|
||||
# get the list of desired metacodes for the set from the user input and build an array out of it
|
||||
new_metacodes = params[:metacodes][:value].split(',')
|
||||
|
||||
# remove the metacodes that were in it, but now aren't
|
||||
removed_metacodes = current_metacodes - new_metacodes
|
||||
removed_metacodes.each do |m|
|
||||
inmetacodeset = InMetacodeSet.find_by(metacode_id: m, metacode_set_id: @metacode_set.id)
|
||||
inmetacodeset.destroy
|
||||
end
|
||||
|
||||
# add the new metacodes
|
||||
added_metacodes = new_metacodes - current_metacodes
|
||||
added_metacodes.each do |m|
|
||||
InMetacodeSet.create(metacode_id: m, metacode_set_id: @metacode_set.id)
|
||||
end
|
||||
|
||||
format.html { redirect_to metacode_sets_url, notice: 'Metacode set was successfully updated.' }
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.html { render action: 'edit' }
|
||||
format.json { render json: @metacode_set.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /metacode_sets/1
|
||||
# DELETE /metacode_sets/1.json
|
||||
def destroy
|
||||
@metacode_set = MetacodeSet.find(params[:id])
|
||||
|
||||
# delete everything that tracks what's in the set
|
||||
@metacode_set.in_metacode_sets.each(&:destroy)
|
||||
|
||||
@metacode_set.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.html { redirect_to metacode_sets_url }
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def metacode_set_params
|
||||
params.require(:metacode_set).permit(:desc, :mapperContributed, :name)
|
||||
end
|
||||
end
|
|
@ -1,88 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MetacodesController < ApplicationController
|
||||
before_action :require_admin, except: %i[index show]
|
||||
before_action :set_metacode, only: %i[edit update]
|
||||
|
||||
# GET /metacodes
|
||||
# GET /metacodes.json
|
||||
def index
|
||||
@metacodes = Metacode.order('name').all
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
return unless require_admin
|
||||
render :index
|
||||
end
|
||||
format.json { render json: @metacodes }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /metacodes/1.json
|
||||
# GET /metacodes/Action.json
|
||||
# GET /metacodes/action.json
|
||||
def show
|
||||
@metacode = Metacode.where('DOWNCASE(name) = ?', downcase(params[:name])).first if params[:name]
|
||||
set_metacode unless @metacode
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @metacode }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /metacodes/new
|
||||
# GET /metacodes/new.json
|
||||
def new
|
||||
@metacode = Metacode.new
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { render json: @metacode }
|
||||
end
|
||||
end
|
||||
|
||||
# GET /metacodes/1/edit
|
||||
def edit
|
||||
end
|
||||
|
||||
# POST /metacodes
|
||||
# POST /metacodes.json
|
||||
def create
|
||||
@metacode = Metacode.new(metacode_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @metacode.save
|
||||
format.html { redirect_to metacodes_url, notice: 'Metacode was successfully created.' }
|
||||
format.json { render json: @metacode, status: :created, location: metacodes_url }
|
||||
else
|
||||
format.html { render :new }
|
||||
format.json { render json: @metacode.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /metacodes/1
|
||||
# PUT /metacodes/1.json
|
||||
def update
|
||||
respond_to do |format|
|
||||
if @metacode.update(metacode_params)
|
||||
format.html { redirect_to metacodes_url, notice: 'Metacode was successfully updated.' }
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.html { render :edit }
|
||||
format.json { render json: @metacode.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Never trust parameters from the scary internet, only allow the white list through.
|
||||
def metacode_params
|
||||
params.require(:metacode).permit(:id, :name, :aws_icon, :manual_icon, :color)
|
||||
end
|
||||
|
||||
def set_metacode
|
||||
@metacode = Metacode.find(params[:id])
|
||||
end
|
||||
end
|
|
@ -1,107 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class NotificationsController < ApplicationController
|
||||
before_action :set_receipts, only: %i[index show mark_read mark_unread]
|
||||
before_action :set_notification, only: %i[show mark_read mark_unread]
|
||||
before_action :set_receipt, only: %i[show mark_read mark_unread]
|
||||
|
||||
def index
|
||||
@notifications = current_user.mailbox.notifications.page(params[:page]).per(25)
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
notifications = @notifications.map do |notification|
|
||||
receipt = @receipts.find_by(notification_id: notification.id)
|
||||
NotificationDecorator.decorate(notification, receipt)
|
||||
end
|
||||
if !notifications.empty?
|
||||
render json: notifications
|
||||
else
|
||||
render json: [].to_json
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def show
|
||||
@receipt.update(is_read: true)
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
case @notification.notification_code
|
||||
when MAP_ACCESS_APPROVED, MAP_INVITE_TO_EDIT
|
||||
redirect_to map_path(@notification.notified_object.map)
|
||||
when TOPIC_ADDED_TO_MAP
|
||||
redirect_to map_path(@notification.notified_object.map)
|
||||
when TOPIC_CONNECTED_1
|
||||
redirect_to topic_path(@notification.notified_object.topic1)
|
||||
when TOPIC_CONNECTED_2
|
||||
redirect_to topic_path(@notification.notified_object.topic2)
|
||||
end
|
||||
end
|
||||
format.json do
|
||||
render json: NotificationDecorator.decorate(@notification, @receipt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def mark_read
|
||||
@receipt.update(is_read: true)
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.json do
|
||||
render json: NotificationDecorator.decorate(@notification, @receipt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def mark_unread
|
||||
@receipt.update(is_read: false)
|
||||
respond_to do |format|
|
||||
format.js
|
||||
format.json do
|
||||
render json: NotificationDecorator.decorate(@notification, @receipt)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def unsubscribe
|
||||
unsubscribe_redirect_if_logged_out!
|
||||
check_if_already_unsubscribed!
|
||||
return if performed? # if one of these checks already redirected, we're done
|
||||
|
||||
if current_user.update(emails_allowed: false)
|
||||
redirect_to edit_user_path(current_user),
|
||||
notice: 'You will no longer receive emails from Metamaps.'
|
||||
else
|
||||
flash[:alert] = 'Sorry, something went wrong. You have not been unsubscribed from emails.'
|
||||
redirect_to edit_user_path(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def unsubscribe_redirect_if_logged_out!
|
||||
return if current_user.present?
|
||||
|
||||
flash[:notice] = 'Continue to unsubscribe from emails by logging in.'
|
||||
redirect_to "#{sign_in_path}?redirect_to=#{unsubscribe_notifications_path}"
|
||||
end
|
||||
|
||||
def check_if_already_unsubscribed!
|
||||
return if current_user.emails_allowed
|
||||
|
||||
redirect_to edit_user_path(current_user), notice: 'You were already unsubscribed from emails.'
|
||||
end
|
||||
|
||||
def set_receipts
|
||||
@receipts = current_user.mailboxer_notification_receipts
|
||||
end
|
||||
|
||||
def set_notification
|
||||
@notification = current_user.mailbox.notifications.find_by(id: params[:id])
|
||||
end
|
||||
|
||||
def set_receipt
|
||||
@receipt = @receipts.find_by(notification_id: params[:id])
|
||||
end
|
||||
end
|
|
@ -1,171 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SearchController < ApplicationController
|
||||
include TopicsHelper
|
||||
include MapsHelper
|
||||
include UsersHelper
|
||||
include SynapsesHelper
|
||||
|
||||
before_action :authorize_search
|
||||
after_action :verify_authorized
|
||||
after_action :verify_policy_scoped, only: %i[maps mappers synapses topics]
|
||||
|
||||
# get /search/topics?term=SOMETERM
|
||||
def topics
|
||||
term = params[:term]
|
||||
user = params[:user] ? params[:user] : false
|
||||
|
||||
if term.present? && term.downcase[0..3] != 'map:' &&
|
||||
term.downcase[0..6] != 'mapper:' && !term.casecmp('topic:').zero?
|
||||
|
||||
# remove "topic:" if appended at beginning
|
||||
term = term[6..-1] if term.downcase[0..5] == 'topic:'
|
||||
|
||||
# if desc: search desc instead
|
||||
desc = false
|
||||
if term.downcase[0..4] == 'desc:'
|
||||
term = term[5..-1]
|
||||
desc = true
|
||||
end
|
||||
|
||||
# if link: search link instead
|
||||
link = false
|
||||
if term.downcase[0..4] == 'link:'
|
||||
term = term[5..-1]
|
||||
link = true
|
||||
end
|
||||
|
||||
# check whether there's a filter by metacode as part of the query
|
||||
filter_by_metacode = false
|
||||
Metacode.all.each do |m|
|
||||
length_one = m.name.length + 1
|
||||
length_two = m.name.length
|
||||
|
||||
if term.downcase[0..length_two] == m.name.downcase + ':'
|
||||
term = term[length_one..-1]
|
||||
filter_by_metacode = m
|
||||
end
|
||||
end
|
||||
|
||||
search = '%' + term.downcase.strip + '%'
|
||||
builder = policy_scope(Topic)
|
||||
|
||||
if filter_by_metacode
|
||||
if term == ''
|
||||
builder = builder.none
|
||||
else
|
||||
builder = builder.where('LOWER("name") like ? OR
|
||||
LOWER("desc") like ? OR
|
||||
LOWER("link") like ?', search, search, search)
|
||||
builder = builder.where(metacode_id: filter_by_metacode.id)
|
||||
end
|
||||
elsif desc
|
||||
builder = builder.where('LOWER("desc") like ?', search)
|
||||
elsif link
|
||||
builder = builder.where('LOWER("link") like ?', search)
|
||||
else # regular case, just search the name
|
||||
builder = builder.where('LOWER("name") like ? OR
|
||||
LOWER("desc") like ? OR
|
||||
LOWER("link") like ?', search, search, search)
|
||||
end
|
||||
|
||||
builder = builder.where(user: user) if user
|
||||
@topics = builder.order(:name)
|
||||
else
|
||||
skip_policy_scope
|
||||
@topics = []
|
||||
end
|
||||
|
||||
render json: autocomplete_array_json(@topics).to_json
|
||||
end
|
||||
|
||||
# get /search/maps?term=SOMETERM
|
||||
def maps
|
||||
term = params[:term]
|
||||
user = params[:user] ? params[:user] : nil
|
||||
|
||||
if term.present? && term.downcase[0..5] != 'topic:' &&
|
||||
term.downcase[0..6] != 'mapper:' && !term.casecmp('map:').zero?
|
||||
|
||||
# remove "map:" if appended at beginning
|
||||
term = term[4..-1] if term.downcase[0..3] == 'map:'
|
||||
|
||||
# if desc: search desc instead
|
||||
desc = false
|
||||
if term.downcase[0..4] == 'desc:'
|
||||
term = term[5..-1]
|
||||
desc = true
|
||||
end
|
||||
|
||||
search = '%' + term.downcase.strip + '%'
|
||||
builder = policy_scope(Map)
|
||||
|
||||
builder = if desc
|
||||
builder.where('LOWER("desc") like ?', search)
|
||||
else
|
||||
builder.where('LOWER("name") like ?', search)
|
||||
end
|
||||
builder = builder.where(user: user) if user
|
||||
@maps = builder.order(:name)
|
||||
else
|
||||
skip_policy_scope
|
||||
@maps = []
|
||||
end
|
||||
|
||||
render json: autocomplete_map_array_json(@maps).to_json
|
||||
end
|
||||
|
||||
# get /search/mappers?term=SOMETERM
|
||||
def mappers
|
||||
term = params[:term]
|
||||
if term.present? && term.downcase[0..3] != 'map:' &&
|
||||
term.downcase[0..5] != 'topic:' && !term.casecmp('mapper:').zero?
|
||||
|
||||
# remove "mapper:" if appended at beginning
|
||||
term = term[7..-1] if term.downcase[0..6] == 'mapper:'
|
||||
search = term.downcase.strip + '%'
|
||||
|
||||
builder = policy_scope(User).where('LOWER("name") like ?', search)
|
||||
@mappers = builder.order(:name)
|
||||
else
|
||||
skip_policy_scope
|
||||
@mappers = []
|
||||
end
|
||||
render json: autocomplete_user_array_json(@mappers).to_json
|
||||
end
|
||||
|
||||
# get /search/synapses?term=SOMETERM OR
|
||||
# get /search/synapses?topic1id=SOMEID&topic2id=SOMEID
|
||||
def synapses
|
||||
term = params[:term]
|
||||
topic1id = params[:topic1id]
|
||||
topic2id = params[:topic2id]
|
||||
|
||||
if term.present?
|
||||
@synapses = policy_scope(Synapse)
|
||||
.where('LOWER("desc") like ?', '%' + term.downcase.strip + '%')
|
||||
.order('"desc"')
|
||||
|
||||
@synapses = @synapses.uniq(&:desc)
|
||||
elsif topic1id.present?
|
||||
one = policy_scope(Synapse).where(topic1_id: topic1id, topic2_id: topic2id)
|
||||
two = policy_scope(Synapse).where(topic2_id: topic1id, topic1_id: topic2id)
|
||||
@synapses = one + two
|
||||
@synapses.sort! { |s1, s2| s1.desc <=> s2.desc }.to_a
|
||||
else
|
||||
skip_policy_scope
|
||||
@synapses = []
|
||||
end
|
||||
|
||||
# limit to 5 results
|
||||
@synapses = @synapses.to_a.slice(0, 5)
|
||||
|
||||
render json: autocomplete_synapse_array_json(@synapses).to_json
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_search
|
||||
authorize :Search
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class StarsController < ApplicationController
|
||||
before_action :require_user
|
||||
before_action :set_map
|
||||
after_action :verify_authorized
|
||||
|
||||
# POST maps/:id/star
|
||||
def create
|
||||
authorize @map, :star?
|
||||
Star.find_or_create_by(map_id: @map.id, user_id: current_user.id)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: 'Successfully starred map' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST maps/:id/unstar
|
||||
def destroy
|
||||
authorize @map, :unstar?
|
||||
star = Star.find_by(map_id: @map.id, user_id: current_user.id)
|
||||
star&.delete
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
render json: { message: 'Successfully unstarred map' }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_map
|
||||
@map = Map.find(params[:id])
|
||||
end
|
||||
end
|
|
@ -1,84 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class SynapsesController < ApplicationController
|
||||
include TopicsHelper
|
||||
|
||||
before_action :require_user, only: %i[create update destroy]
|
||||
after_action :verify_authorized, except: :index
|
||||
after_action :verify_policy_scoped, only: :index
|
||||
|
||||
respond_to :json
|
||||
|
||||
# GET /synapses/1.json
|
||||
def show
|
||||
@synapse = Synapse.find(params[:id])
|
||||
authorize @synapse
|
||||
|
||||
render json: @synapse
|
||||
end
|
||||
|
||||
# POST /synapses
|
||||
# POST /synapses.json
|
||||
def create
|
||||
@synapse = Synapse.new(synapse_params)
|
||||
@synapse.desc = '' if @synapse.desc.nil?
|
||||
@synapse.desc.strip! # no trailing/leading whitespace
|
||||
@synapse.user = current_user
|
||||
@synapse.updated_by = current_user
|
||||
|
||||
# we want invalid params to return :unprocessable_entity
|
||||
# so we have to authorize AFTER saving. But if authorize
|
||||
# fails, we need to rollback the SQL transaction
|
||||
success = nil
|
||||
ActiveRecord::Base.transaction do
|
||||
success = @synapse.save
|
||||
success ? authorize(@synapse) : skip_authorization
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
if success
|
||||
format.json { render json: @synapse, status: :created }
|
||||
else
|
||||
format.json { render json: @synapse.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /synapses/1
|
||||
# PUT /synapses/1.json
|
||||
def update
|
||||
@synapse = Synapse.find(params[:id])
|
||||
@synapse.desc = '' if @synapse.desc.nil?
|
||||
authorize @synapse
|
||||
@synapse.updated_by = current_user
|
||||
@synapse.assign_attributes(synapse_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @synapse.save
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.json { render json: @synapse.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE synapses/:id
|
||||
def destroy
|
||||
@synapse = Synapse.find(params[:id])
|
||||
authorize @synapse
|
||||
@synapse.updated_by = current_user
|
||||
@synapse.destroy
|
||||
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def synapse_params
|
||||
params.require(:synapse).permit(
|
||||
:id, :desc, :category, :weight, :permission, :topic1_id, :topic2_id
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,10 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TokensController < ApplicationController
|
||||
before_action :require_user, only: [:new]
|
||||
|
||||
def new
|
||||
@token = Token.new(user: current_user)
|
||||
render :new, layout: false
|
||||
end
|
||||
end
|
|
@ -1,212 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TopicsController < ApplicationController
|
||||
include TopicsHelper
|
||||
|
||||
before_action :require_user, only: %i[create update destroy follow unfollow]
|
||||
before_action :set_topic, only: %i[show update relative_numbers
|
||||
relatives network destroy
|
||||
follow unfollow unfollow_from_email]
|
||||
after_action :verify_authorized, except: :autocomplete_topic
|
||||
|
||||
respond_to :html, :js, :json
|
||||
|
||||
# GET /topics/autocomplete_topic
|
||||
def autocomplete_topic
|
||||
term = params[:term]
|
||||
if term.present?
|
||||
topics = policy_scope(Topic)
|
||||
.where('LOWER("name") like ?', term.downcase + '%')
|
||||
.order('"name"')
|
||||
map_topics = topics.select { |t| t&.metacode&.name == 'Metamap' }
|
||||
# prioritize topics which point to maps, over maps
|
||||
exclude = map_topics.length.positive? ? map_topics.map(&:name) : ['']
|
||||
maps = policy_scope(Map)
|
||||
.where('LOWER("name") like ? AND name NOT IN (?)', term.downcase + '%', exclude)
|
||||
.order('"name"')
|
||||
else
|
||||
topics = []
|
||||
maps = []
|
||||
end
|
||||
@all = topics.to_a.concat(maps.to_a).sort_by(&:name)
|
||||
|
||||
render json: autocomplete_array_json(@all).to_json
|
||||
end
|
||||
|
||||
# GET topics/:id
|
||||
def show
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
@alltopics = [@topic].concat(policy_scope(Topic.relatives(@topic.id, current_user)).to_a)
|
||||
@allsynapses = policy_scope(Synapse.for_topic(@topic.id)).to_a
|
||||
@allcreators = @alltopics.map(&:user).uniq
|
||||
@allcreators += @allsynapses.map(&:user).uniq
|
||||
|
||||
respond_with(@allsynapses, @alltopics, @allcreators, @topic)
|
||||
end
|
||||
format.json { render json: @topic.as_json(user: current_user).to_json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET topics/:id/network
|
||||
def network
|
||||
@alltopics = [@topic].concat(policy_scope(Topic.relatives(@topic.id, current_user)).to_a)
|
||||
@allsynapses = policy_scope(Synapse.for_topic(@topic.id))
|
||||
|
||||
@allcreators = @alltopics.map(&:user).uniq
|
||||
@allcreators += @allsynapses.map(&:user).uniq
|
||||
|
||||
@json = {}
|
||||
@json['topic'] = @topic.as_json(user: current_user)
|
||||
@json['creators'] = @allcreators
|
||||
@json['relatives'] = @alltopics.as_json(user: current_user)
|
||||
@json['synapses'] = @allsynapses
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET topics/:id/relative_numbers
|
||||
def relative_numbers
|
||||
topics_already_has = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
||||
|
||||
alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).to_a
|
||||
if params[:metacode].present?
|
||||
alltopics.delete_if { |topic| topic.metacode_id != params[:metacode].to_i }
|
||||
end
|
||||
alltopics.delete_if { |topic| !topics_already_has.index(topic.id).nil? }
|
||||
|
||||
@json = Hash.new(0)
|
||||
alltopics.each do |t|
|
||||
@json[t.metacode.id] += 1
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @json }
|
||||
end
|
||||
end
|
||||
|
||||
# GET topics/:id/relatives
|
||||
def relatives
|
||||
topics_already_has = params[:network] ? params[:network].split(',').map(&:to_i) : []
|
||||
|
||||
alltopics = policy_scope(Topic.relatives(@topic.id, current_user)).to_a
|
||||
if params[:metacode].present?
|
||||
alltopics.delete_if { |topic| topic.metacode_id != params[:metacode].to_i }
|
||||
end
|
||||
alltopics.delete_if do |topic|
|
||||
!topics_already_has.index(topic.id.to_s).nil?
|
||||
end
|
||||
|
||||
# find synapses between topics in alltopics array
|
||||
allsynapses = policy_scope(Synapse.for_topic(@topic.id)).to_a
|
||||
synapse_ids = (allsynapses.map(&:topic1_id) + allsynapses.map(&:topic2_id)).uniq
|
||||
allsynapses.delete_if do |synapse|
|
||||
!synapse_ids.index(synapse.id).nil?
|
||||
end
|
||||
|
||||
creators_already_has = params[:creators] ? params[:creators].split(',').map(&:to_i) : []
|
||||
allcreators = (alltopics.map(&:user) + allsynapses.map(&:user)).uniq.delete_if do |user|
|
||||
!creators_already_has.index(user.id).nil?
|
||||
end
|
||||
|
||||
@json = {}
|
||||
@json['topics'] = alltopics.as_json(user: current_user)
|
||||
@json['synapses'] = allsynapses
|
||||
@json['creators'] = allcreators
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @json }
|
||||
end
|
||||
end
|
||||
|
||||
# POST /topics
|
||||
# POST /topics.json
|
||||
def create
|
||||
@topic = Topic.new(topic_params)
|
||||
authorize @topic
|
||||
@topic.user = current_user
|
||||
@topic.updated_by = current_user
|
||||
|
||||
respond_to do |format|
|
||||
if @topic.save
|
||||
format.json { render json: @topic.as_json(user: current_user), status: :created }
|
||||
else
|
||||
format.json { render json: @topic.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /topics/1
|
||||
# PUT /topics/1.json
|
||||
def update
|
||||
@topic.updated_by = current_user
|
||||
@topic.assign_attributes(topic_params)
|
||||
|
||||
respond_to do |format|
|
||||
if @topic.save
|
||||
format.json { head :no_content }
|
||||
else
|
||||
format.json { render json: @topic.errors, status: :unprocessable_entity }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE topics/:id
|
||||
def destroy
|
||||
@topic.updated_by = current_user
|
||||
@topic.destroy
|
||||
respond_to do |format|
|
||||
format.json { head :no_content }
|
||||
end
|
||||
end
|
||||
|
||||
# POST topics/:id/follow
|
||||
def follow
|
||||
follow = FollowService.follow(@topic, current_user, 'followed')
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
if follow
|
||||
head :ok
|
||||
else
|
||||
head :bad_request
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# POST topics/:id/unfollow
|
||||
def unfollow
|
||||
FollowService.unfollow(@topic, current_user)
|
||||
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
head :ok
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET topics/:id/unfollow_from_email
|
||||
def unfollow_from_email
|
||||
FollowService.unfollow(@topic, current_user)
|
||||
|
||||
respond_to do |format|
|
||||
format.html do
|
||||
redirect_to topic_path(@topic), notice: 'You are no longer following this topic'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_topic
|
||||
@topic = Topic.find(params[:id])
|
||||
authorize @topic
|
||||
end
|
||||
|
||||
def topic_params
|
||||
params.require(:topic).permit(:id, :name, :desc, :link, :permission, :metacode_id, :defer_to_map_id)
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class PasswordsController < Devise::PasswordsController
|
||||
protected
|
||||
|
||||
def after_resetting_password_path_for(resource)
|
||||
signed_in_root_path(resource)
|
||||
end
|
||||
|
||||
def after_sending_reset_password_instructions_path_for(_resource_name)
|
||||
sign_in_path if is_navigational_format?
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,40 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class RegistrationsController < Devise::RegistrationsController
|
||||
before_action :configure_sign_up_params, only: [:create]
|
||||
before_action :configure_account_update_params, only: [:update]
|
||||
after_action :store_location, only: [:new]
|
||||
|
||||
protected
|
||||
|
||||
def after_update_path_for(resource)
|
||||
signed_in_root_path(resource)
|
||||
end
|
||||
|
||||
def after_sign_in_path_for(resource)
|
||||
stored = stored_location_for(User)
|
||||
return stored if stored
|
||||
|
||||
if request.referer&.match(sign_in_url) || request.referer&.match(sign_up_url)
|
||||
super
|
||||
else
|
||||
request.referer || root_path
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def store_location
|
||||
store_location_for(User, params[:redirect_to]) if params[:redirect_to]
|
||||
end
|
||||
|
||||
def configure_sign_up_params
|
||||
devise_parameter_sanitizer.permit(:sign_up, keys: %i[name joinedwithcode])
|
||||
end
|
||||
|
||||
def configure_account_update_params
|
||||
devise_parameter_sanitizer.permit(:account_update, keys: [:image])
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Users
|
||||
class SessionsController < Devise::SessionsController
|
||||
after_action :store_location, only: [:new]
|
||||
|
||||
protected
|
||||
|
||||
def after_sign_in_path_for(resource)
|
||||
stored = stored_location_for(User)
|
||||
return stored if stored
|
||||
|
||||
if request.referer&.match(sign_in_url) || request.referer&.match(sign_up_url)
|
||||
super
|
||||
else
|
||||
request.referer || root_path
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def store_location
|
||||
store_location_for(User, params[:redirect_to]) if params[:redirect_to]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,122 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class UsersController < ApplicationController
|
||||
before_action :require_user, only: %i[edit update updatemetacodes update_metacode_focus]
|
||||
|
||||
respond_to :html, :json
|
||||
|
||||
# GET /users/1.json
|
||||
def show
|
||||
@user = User.find(params[:id])
|
||||
|
||||
render json: @user
|
||||
end
|
||||
|
||||
# GET /users/:id/edit
|
||||
def edit
|
||||
@user = User.find(current_user.id)
|
||||
end
|
||||
|
||||
# PUT /users/:id
|
||||
def update
|
||||
@user = User.find(current_user.id)
|
||||
|
||||
if user_params[:password] == '' && user_params[:password_confirmation] == ''
|
||||
# not trying to change the password
|
||||
if @user.update_attributes(user_params.except(:password, :password_confirmation))
|
||||
update_follow_settings(@user, params[:settings])
|
||||
@user.image = nil if params[:remove_image] == '1'
|
||||
@user.save
|
||||
bypass_sign_in(@user)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to root_url, notice: 'Settings updated' }
|
||||
end
|
||||
else
|
||||
bypass_sign_in(@user)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to edit_user_path(@user), notice: @user.errors.to_a[0] }
|
||||
end
|
||||
end
|
||||
else
|
||||
# trying to change the password
|
||||
correct_pass = @user.valid_password?(params[:current_password])
|
||||
|
||||
if correct_pass && @user.update_attributes(user_params)
|
||||
update_follow_settings(@user, params[:settings])
|
||||
@user.image = nil if params[:remove_image] == '1'
|
||||
@user.save
|
||||
sign_in(@user, bypass: true)
|
||||
respond_to do |format|
|
||||
format.html { redirect_to root_url, notice: 'Settings updated' }
|
||||
end
|
||||
else
|
||||
respond_to do |format|
|
||||
if correct_pass
|
||||
u = User.find(@user.id)
|
||||
sign_in(u, bypass: true)
|
||||
format.html { redirect_to edit_user_path(@user), notice: @user.errors.to_a[0] }
|
||||
else
|
||||
sign_in(@user, bypass: true)
|
||||
format.html { redirect_to edit_user_path(@user), notice: 'Incorrect current password' }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# GET /users/:id/details [.json]
|
||||
def details
|
||||
@user = User.find(params[:id])
|
||||
|
||||
@details = {}
|
||||
|
||||
@details['name'] = @user.name
|
||||
@details['created_at'] = @user.created_at.strftime('%m/%d/%Y')
|
||||
@details['image'] = @user.image.url(:ninetysix)
|
||||
@details['generation'] = @user.generation
|
||||
@details['numSynapses'] = @user.synapses.count
|
||||
@details['numTopics'] = @user.topics.count
|
||||
@details['numMaps'] = @user.maps.count
|
||||
|
||||
render json: @details
|
||||
end
|
||||
|
||||
# PUT /user/updatemetacodes
|
||||
def updatemetacodes
|
||||
@user = current_user
|
||||
|
||||
@m = params[:metacodes][:value]
|
||||
@user.settings.metacodes = @m.split(',')
|
||||
|
||||
@user.save
|
||||
|
||||
respond_to do |format|
|
||||
format.json { render json: @user }
|
||||
end
|
||||
end
|
||||
|
||||
# PUT /user/update_metacode_focus
|
||||
def update_metacode_focus
|
||||
@user = current_user
|
||||
@user.settings.metacode_focus = params[:value]
|
||||
@user.save
|
||||
respond_to do |format|
|
||||
format.json { render json: { success: 'success' } }
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def update_follow_settings(user, settings)
|
||||
user.settings.follow_topic_on_created = settings[:follow_topic_on_created]
|
||||
user.settings.follow_topic_on_contributed = settings[:follow_topic_on_contributed]
|
||||
user.settings.follow_map_on_created = settings[:follow_map_on_created]
|
||||
user.settings.follow_map_on_contributed = settings[:follow_map_on_contributed]
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.require(:user).permit(
|
||||
:name, :email, :image, :password, :password_confirmation, :emails_allowed, :settings
|
||||
)
|
||||
end
|
||||
end
|
|
@ -1,51 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class NotificationDecorator
|
||||
class << self
|
||||
def decorate(notification, receipt)
|
||||
result = {
|
||||
id: notification.id,
|
||||
type: notification.notification_code,
|
||||
subject: notification.subject,
|
||||
is_read: receipt.is_read,
|
||||
created_at: notification.created_at,
|
||||
actor: notification.sender,
|
||||
data: {
|
||||
object: notification.notified_object
|
||||
}
|
||||
}
|
||||
|
||||
case notification.notification_code
|
||||
when MAP_ACCESS_APPROVED, MAP_ACCESS_REQUEST, MAP_INVITE_TO_EDIT
|
||||
map = notification.notified_object&.map
|
||||
result[:data][:map] = {
|
||||
id: map&.id,
|
||||
name: map&.name
|
||||
}
|
||||
when TOPIC_ADDED_TO_MAP
|
||||
topic = notification.notified_object&.eventable
|
||||
map = notification.notified_object&.map
|
||||
result[:data][:topic] = {
|
||||
id: topic&.id,
|
||||
name: topic&.name
|
||||
}
|
||||
result[:data][:map] = {
|
||||
id: map&.id,
|
||||
name: map&.name
|
||||
}
|
||||
when TOPIC_CONNECTED_1, TOPIC_CONNECTED_2
|
||||
topic1 = notification.notified_object&.topic1
|
||||
topic2 = notification.notified_object&.topic2
|
||||
result[:data][:topic1] = {
|
||||
id: topic1&.id,
|
||||
name: topic1&.name
|
||||
}
|
||||
result[:data][:topic2] = {
|
||||
id: topic2&.id,
|
||||
name: topic2&.name
|
||||
}
|
||||
end
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ApplicationHelper
|
||||
def invite_link
|
||||
"#{request.base_url}/join" + (current_user ? "?code=#{current_user.code}" : '')
|
||||
end
|
||||
|
||||
def user_unread_notification_count
|
||||
return 0 if current_user.nil?
|
||||
@uunc ||= current_user.mailboxer_notification_receipts.reduce(0) do |total, receipt|
|
||||
receipt.is_read ? total : total + 1
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module ContentHelper
|
||||
def resource_name
|
||||
:user
|
||||
end
|
||||
|
||||
def resource
|
||||
@resource ||= User.new
|
||||
end
|
||||
|
||||
def devise_mapping
|
||||
@devise_mapping ||= Devise.mappings[:user]
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module DeviseHelper
|
||||
def devise_error_messages!
|
||||
resource.errors.to_a[0]
|
||||
end
|
||||
|
||||
def devise_error_messages?
|
||||
resource.errors.empty? ? false : true
|
||||
end
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module InMetacodeSetsHelper
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MainHelper
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MapMailerHelper
|
||||
def access_approved_subject(map)
|
||||
map.name + ' - access approved'
|
||||
end
|
||||
|
||||
def access_request_subject(map)
|
||||
map.name + ' - request to edit'
|
||||
end
|
||||
|
||||
def invite_to_edit_subject(map)
|
||||
map.name + ' - invited to edit'
|
||||
end
|
||||
end
|
|
@ -1,4 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MappingHelper
|
||||
end
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MapsHelper
|
||||
# JSON autocomplete format for typeahead
|
||||
def autocomplete_map_array_json(maps)
|
||||
maps.map do |m|
|
||||
{
|
||||
id: m.id,
|
||||
label: m.name,
|
||||
value: m.name,
|
||||
description: m.desc.try(:truncate, 30),
|
||||
permission: m.permission,
|
||||
topicCount: m.topics.count,
|
||||
synapseCount: m.synapses.count,
|
||||
contributorCount: m.contributors.count,
|
||||
rtype: 'map',
|
||||
contributorTip: contributor_tip(m),
|
||||
mapContributorImage: first_contributor_image(m)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def first_contributor_image(map)
|
||||
if map.contributors.count.positive?
|
||||
return map.contributors[0].image.url(:thirtytwo)
|
||||
end
|
||||
'https://s3.amazonaws.com/metamaps-assets/site/user.png'
|
||||
end
|
||||
|
||||
def contributor_tip(map)
|
||||
output = ''
|
||||
if map.contributors.count.positive?
|
||||
map.contributors.each_with_index do |contributor, _index|
|
||||
user_image = contributor.image.url(:thirtytwo)
|
||||
output += '<li>'
|
||||
output += %(<img class="tipUserImage" width="25" height="25" src="#{user_image}" />)
|
||||
output += "<span>#{contributor.name}</span>"
|
||||
output += '</li>'
|
||||
end
|
||||
end
|
||||
output
|
||||
end
|
||||
end
|
|
@ -1,79 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module MetacodesHelper
|
||||
def metacodeset
|
||||
metacodes = current_user.settings.metacodes
|
||||
|
||||
return false unless metacodes[0].include?('metacodeset')
|
||||
return 'Most' if metacodes[0].sub('metacodeset-', '') == 'Most'
|
||||
return 'Recent' if metacodes[0].sub('metacodeset-', '') == 'Recent'
|
||||
|
||||
MetacodeSet.find(metacodes[0].sub('metacodeset-', '').to_i)
|
||||
end
|
||||
|
||||
def user_metacodes
|
||||
@m = current_user.settings.metacodes
|
||||
set = metacodeset
|
||||
@metacodes = if set && set == 'Most'
|
||||
Metacode.where(id: current_user.most_used_metacodes).to_a
|
||||
elsif set && set == 'Recent'
|
||||
Metacode.where(id: current_user.recent_metacodes).to_a
|
||||
elsif set
|
||||
set.metacodes.to_a
|
||||
else
|
||||
Metacode.where(id: @m).to_a
|
||||
end
|
||||
|
||||
focus_code = user_metacode
|
||||
if !focus_code.nil? && @metacodes.index { |m| m.id == focus_code.id }.nil?
|
||||
@metacodes.push(focus_code)
|
||||
end
|
||||
|
||||
@metacodes.sort! { |m1, m2| m2.name.downcase <=> m1.name.downcase }
|
||||
|
||||
if !focus_code.nil?
|
||||
@metacodes.rotate!(@metacodes.index { |m| m.id == focus_code.id })
|
||||
else
|
||||
@metacodes.rotate!(-1)
|
||||
end
|
||||
end
|
||||
|
||||
def user_metacode
|
||||
current_user.settings.metacode_focus ? Metacode.find(current_user.settings.metacode_focus.to_i) : nil
|
||||
end
|
||||
|
||||
def user_most_used_metacodes
|
||||
@metacodes = current_user.most_used_metacodes.map { |id| Metacode.find(id) }
|
||||
end
|
||||
|
||||
def user_recent_metacodes
|
||||
@metacodes = current_user.recent_metacodes.map { |id| Metacode.find(id) }
|
||||
end
|
||||
|
||||
def metacode_sets_json
|
||||
metacode_sets = []
|
||||
metacode_sets << {
|
||||
name: 'Recently Used',
|
||||
metacodes: user_recent_metacodes
|
||||
.map { |m| { id: m.id, icon_path: asset_path(m.icon), name: m.name } }
|
||||
}
|
||||
metacode_sets << {
|
||||
name: 'Most Used',
|
||||
metacodes: user_most_used_metacodes
|
||||
.map { |m| { id: m.id, icon_path: asset_path(m.icon), name: m.name } }
|
||||
}
|
||||
metacode_sets += MetacodeSet.order('name').all.map do |set|
|
||||
{
|
||||
name: set.name,
|
||||
metacodes: set.metacodes.order('name')
|
||||
.map { |m| { id: m.id, icon_path: asset_path(m.icon), name: m.name } }
|
||||
}
|
||||
end
|
||||
metacode_sets << {
|
||||
name: 'All',
|
||||
metacodes: Metacode.order('name').all
|
||||
.map { |m| { id: m.id, icon_path: asset_path(m.icon), name: m.name } }
|
||||
}
|
||||
metacode_sets.to_json
|
||||
end
|
||||
end
|
|
@ -1,26 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module SynapsesHelper
|
||||
## this one is for building our custom JSON autocomplete format for typeahead
|
||||
def autocomplete_synapse_generic_json(unique)
|
||||
unique.map do |s|
|
||||
{ label: s.desc, value: s.desc }
|
||||
end
|
||||
end
|
||||
|
||||
## this one is for building our custom JSON autocomplete format for typeahead
|
||||
def autocomplete_synapse_array_json(synapses)
|
||||
synapses.map do |s|
|
||||
{
|
||||
id: s.id,
|
||||
label: s.desc.blank? ? '(no description)' : s.desc,
|
||||
value: s.desc,
|
||||
permission: s.permission,
|
||||
mapCount: s.maps.count,
|
||||
originator: s.user.name,
|
||||
originatorImage: s.user.image.url(:thirtytwo),
|
||||
rtype: 'synapse'
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,11 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module TopicMailerHelper
|
||||
def added_to_map_subject(topic, map)
|
||||
topic.name + ' was added to map ' + map.name
|
||||
end
|
||||
|
||||
def connected_subject(topic)
|
||||
'new synapse to topic ' + topic.name
|
||||
end
|
||||
end
|
|
@ -1,43 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module TopicsHelper
|
||||
## this one is for building our custom JSON autocomplete format for typeahead
|
||||
def autocomplete_array_json(topics)
|
||||
topics.map do |t|
|
||||
is_map = t.is_a?(Map)
|
||||
metamap_metacode = Metacode.find_by(name: 'Metamap')
|
||||
{
|
||||
id: t.id,
|
||||
label: t.name,
|
||||
value: t.name,
|
||||
description: t.desc ? t.desc&.truncate(70) : '', # make this return matched results
|
||||
originator: t.user.name,
|
||||
originatorImage: t.user.image.url(:thirtytwo),
|
||||
permission: t.permission,
|
||||
|
||||
rtype: is_map ? 'map' : 'topic',
|
||||
inmaps: is_map ? [] : t.inmaps(current_user),
|
||||
inmapsLinks: is_map ? [] : t.inmaps_links(current_user),
|
||||
type: is_map ? metamap_metacode.name : t.metacode.name,
|
||||
typeImageURL: is_map ? metamap_metacode.icon : t.metacode.icon,
|
||||
mapCount: is_map ? 0 : t.maps.count,
|
||||
synapseCount: is_map ? 0 : t.synapses.count
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# recursively find all nodes in any given nodes network
|
||||
def network(node, array, count)
|
||||
array = [] if array.nil?
|
||||
array.push(node)
|
||||
return array if count.zero?
|
||||
|
||||
# check if each relative is already in the array and if not, call the network function again
|
||||
remaining_relatives = node.relatives.to_a - array
|
||||
remaining_relatives.each do |relative|
|
||||
array = (array | network(relative, array, count - 1))
|
||||
end
|
||||
|
||||
array
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module UsersHelper
|
||||
# build custom json autocomplete for typeahead
|
||||
def autocomplete_user_array_json(users)
|
||||
json_users = []
|
||||
users.each do |user|
|
||||
json_users.push user.as_json_for_autocomplete
|
||||
end
|
||||
json_users
|
||||
end
|
||||
end
|
|
@ -1,31 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationMailer < ActionMailer::Base
|
||||
default from: 'team@metamaps.cc'
|
||||
layout 'mailer'
|
||||
|
||||
class << self
|
||||
def mail_for_notification(notification)
|
||||
case notification.notification_code
|
||||
when MAP_ACCESS_REQUEST
|
||||
request = notification.notified_object
|
||||
MapMailer.access_request(request)
|
||||
when MAP_ACCESS_APPROVED
|
||||
request = notification.notified_object
|
||||
MapMailer.access_approved(request)
|
||||
when MAP_INVITE_TO_EDIT
|
||||
user_map = notification.notified_object
|
||||
MapMailer.invite_to_edit(user_map)
|
||||
when TOPIC_ADDED_TO_MAP
|
||||
event = notification.notified_object
|
||||
TopicMailer.added_to_map(event, notification.recipients[0])
|
||||
when TOPIC_CONNECTED_1
|
||||
synapse = notification.notified_object
|
||||
TopicMailer.connected(synapse, synapse.topic1, notification.recipients[0])
|
||||
when TOPIC_CONNECTED_2
|
||||
synapse = notification.notified_object
|
||||
TopicMailer.connected(synapse, synapse.topic2, notification.recipients[0])
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MapActivityMailer < ApplicationMailer
|
||||
default from: 'team@metamaps.cc'
|
||||
|
||||
def daily_summary(user, map, summary_data)
|
||||
@user = user
|
||||
@map = map
|
||||
@summary_data = summary_data
|
||||
mail(to: user.email, subject: MapActivityService.subject_line(map))
|
||||
end
|
||||
end
|
|
@ -1,24 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class MapMailer < ApplicationMailer
|
||||
include MapMailerHelper
|
||||
default from: 'team@metamaps.cc'
|
||||
|
||||
def access_approved(request)
|
||||
@request = request
|
||||
@map = request.map
|
||||
mail(to: request.user.email, subject: access_approved_subject(@map))
|
||||
end
|
||||
|
||||
def access_request(request)
|
||||
@request = request
|
||||
@map = request.map
|
||||
mail(to: @map.user.email, subject: access_request_subject(@map))
|
||||
end
|
||||
|
||||
def invite_to_edit(user_map)
|
||||
@inviter = user_map.map.user
|
||||
@map = user_map.map
|
||||
mail(to: user_map.user.email, subject: invite_to_edit_subject(@map))
|
||||
end
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class TopicMailer < ApplicationMailer
|
||||
include TopicMailerHelper
|
||||
default from: 'team@metamaps.cc'
|
||||
|
||||
def added_to_map(event, user)
|
||||
@entity = event.eventable
|
||||
@event = event
|
||||
mail(to: user.email, subject: added_to_map_subject(@entity, event.map))
|
||||
end
|
||||
|
||||
def connected(synapse, topic, user)
|
||||
@entity = topic
|
||||
@event = synapse
|
||||
mail(to: user.email, subject: connected_subject(topic))
|
||||
end
|
||||
end
|
|
@ -1,38 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccessRequest < ApplicationRecord
|
||||
belongs_to :user
|
||||
belongs_to :map
|
||||
has_one :user_map
|
||||
|
||||
after_create :after_created_async
|
||||
|
||||
def approve
|
||||
self.approved = true
|
||||
self.answered = true
|
||||
save
|
||||
|
||||
Mailboxer::Notification.where(notified_object: self).find_each do |notification|
|
||||
Mailboxer::Receipt.where(notification: notification).update_all(is_read: true)
|
||||
end
|
||||
|
||||
UserMap.create(user: user, map: map, access_request: self)
|
||||
end
|
||||
|
||||
def deny
|
||||
self.approved = false
|
||||
self.answered = true
|
||||
save
|
||||
|
||||
Mailboxer::Notification.where(notified_object: self).find_each do |notification|
|
||||
Mailboxer::Receipt.where(notification: notification).update_all(is_read: true)
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def after_created_async
|
||||
NotificationService.access_request(self)
|
||||
end
|
||||
handle_asynchronously :after_created_async
|
||||
end
|
|
@ -1,5 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class ApplicationRecord < ActiveRecord::Base
|
||||
self.abstract_class = true
|
||||
end
|
|
@ -1,39 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Attachment < ApplicationRecord
|
||||
belongs_to :attachable, polymorphic: true
|
||||
|
||||
has_attached_file :file,
|
||||
styles: lambda { |a|
|
||||
if a.instance.image?
|
||||
{
|
||||
thumb: 'x128#',
|
||||
medium: 'x320>'
|
||||
}
|
||||
else
|
||||
{}
|
||||
end
|
||||
}
|
||||
|
||||
validates_attachment_content_type :file, content_type: Attachable.allowed_types
|
||||
|
||||
def image?
|
||||
Attachable.image_types.include?(file.instance.file_content_type)
|
||||
end
|
||||
|
||||
def audio?
|
||||
Attachable.audio_types.include?(file.instance.file_content_type)
|
||||
end
|
||||
|
||||
def text?
|
||||
Attachable.text_types.include?(file.instance.file_content_type)
|
||||
end
|
||||
|
||||
def pdf?
|
||||
Attachable.pdf_types.include?(file.instance.file_content_type)
|
||||
end
|
||||
|
||||
def document?
|
||||
text? || pdf?
|
||||
end
|
||||
end
|
|
@ -1,51 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Attachable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_many :attachments, as: :attachable, dependent: :destroy
|
||||
end
|
||||
|
||||
def images
|
||||
attachments.where(file_content_type: image_types)
|
||||
end
|
||||
|
||||
def audios
|
||||
attachments.where(file_content_type: audio_types)
|
||||
end
|
||||
|
||||
def texts
|
||||
attachments.where(file_content_type: text_types)
|
||||
end
|
||||
|
||||
def pdfs
|
||||
attachments.where(file_content_type: pdf_types)
|
||||
end
|
||||
|
||||
def documents
|
||||
attachments.where(file_content_type: text_types + pdf_types)
|
||||
end
|
||||
|
||||
class << self
|
||||
def image_types
|
||||
['image/png', 'image/gif', 'image/jpeg']
|
||||
end
|
||||
|
||||
def audio_types
|
||||
['audio/ogg', 'audio/mp3']
|
||||
end
|
||||
|
||||
def text_types
|
||||
['text/plain']
|
||||
end
|
||||
|
||||
def pdf_types
|
||||
['application/pdf']
|
||||
end
|
||||
|
||||
def allowed_types
|
||||
image_types + audio_types + text_types + pdf_types
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,12 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Routing
|
||||
extend ActiveSupport::Concern
|
||||
include Rails.application.routes.url_helpers
|
||||
|
||||
included do
|
||||
def default_url_options
|
||||
ActionMailer::Base.default_url_options
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,29 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Event < ApplicationRecord
|
||||
KINDS = %w[user_present_on_map user_not_present_on_map
|
||||
conversation_started_on_map
|
||||
topic_added_to_map topic_moved_on_map topic_removed_from_map
|
||||
synapse_added_to_map synapse_removed_from_map
|
||||
topic_updated synapse_updated].freeze
|
||||
|
||||
belongs_to :eventable, polymorphic: true
|
||||
belongs_to :map
|
||||
belongs_to :user
|
||||
|
||||
scope :chronologically, (-> { order('created_at asc') })
|
||||
|
||||
after_create :notify_webhooks!, if: :map
|
||||
|
||||
validates :kind, inclusion: { in: KINDS }
|
||||
validates :eventable, presence: true
|
||||
|
||||
def belongs_to?(this_user)
|
||||
user_id == this_user.id
|
||||
end
|
||||
|
||||
def notify_webhooks!
|
||||
map.webhooks.each { |webhook| WebhookService.publish! webhook: webhook, event: self }
|
||||
end
|
||||
handle_asynchronously :notify_webhooks!
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class ConversationStartedOnMap < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(map, user)
|
||||
create!(kind: 'conversation_started_on_map',
|
||||
eventable: map,
|
||||
map: map,
|
||||
user: user)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class SynapseAddedToMap < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(synapse, map, user, meta)
|
||||
create!(kind: 'synapse_added_to_map',
|
||||
eventable: synapse,
|
||||
map: map,
|
||||
user: user,
|
||||
meta: meta)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class SynapseRemovedFromMap < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(synapse, map, user, meta)
|
||||
create!(kind: 'synapse_removed_from_map',
|
||||
eventable: synapse,
|
||||
map: map,
|
||||
user: user,
|
||||
meta: meta)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class SynapseUpdated < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(synapse, user, meta)
|
||||
create!(kind: 'synapse_updated',
|
||||
eventable: synapse,
|
||||
user: user,
|
||||
meta: meta)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,22 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class TopicAddedToMap < Event
|
||||
after_create :notify_users!
|
||||
|
||||
def self.publish!(topic, map, user, meta)
|
||||
create!(kind: 'topic_added_to_map',
|
||||
eventable: topic,
|
||||
map: map,
|
||||
user: user,
|
||||
meta: meta)
|
||||
end
|
||||
|
||||
def notify_users!
|
||||
# in the future, notify followers of both the topic, and the map
|
||||
NotificationService.notify_followers(eventable, TOPIC_ADDED_TO_MAP, self)
|
||||
# NotificationService.notify_followers(map, MAP_RECEIVED_TOPIC, self)
|
||||
end
|
||||
handle_asynchronously :notify_users!
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class TopicMovedOnMap < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(topic, map, user, meta)
|
||||
create!(kind: 'topic_moved_on_map',
|
||||
eventable: topic,
|
||||
map: map,
|
||||
user: user,
|
||||
meta: meta)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,15 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class TopicRemovedFromMap < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(topic, map, user, meta)
|
||||
create!(kind: 'topic_removed_from_map',
|
||||
eventable: topic,
|
||||
map: map,
|
||||
user: user,
|
||||
meta: meta)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,18 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class TopicUpdated < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(topic, user, meta)
|
||||
create!(kind: 'topic_updated',
|
||||
eventable: topic,
|
||||
user: user,
|
||||
meta: meta)
|
||||
end
|
||||
|
||||
def notify_users!
|
||||
NotificationService.notify_followers(eventable, 'topic_updated', self)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class UserNotPresentOnMap < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(map, user)
|
||||
create!(kind: 'user_not_present_on_map',
|
||||
eventable: map,
|
||||
map: map,
|
||||
user: user)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,14 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Events
|
||||
class UserPresentOnMap < Event
|
||||
# after_create :notify_users!
|
||||
|
||||
def self.publish!(map, user)
|
||||
create!(kind: 'user_present_on_map',
|
||||
eventable: map,
|
||||
map: map,
|
||||
user: user)
|
||||
end
|
||||
end
|
||||
end
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue