added 260907 version of worker toolkit

This commit is contained in:
2026-09-08 20:30:51 -04:00
parent cbd3f0f8ca
commit 97aca37663
1283 changed files with 142951 additions and 0 deletions

View File

@@ -0,0 +1,30 @@
# See https://help.github.com/articles/ignoring-files for more about ignoring files.
#
# If you find yourself ignoring temporary files generated by your text editor
# or operating system, you probably want to add a global ignore instead:
# git config --global core.excludesfile '~/.gitignore_global'
# Ignore bundler config.
/.bundle
# Ignore the default SQLite database.
/db/*.sqlite3
/db/*.sqlite3-journal
# Ignore all logfiles and tempfiles.
/log/*
!/log/.keep
/tmp
# Ignore env
/.env*
# Ignore idea's files
/.idea
/coverage
/public/uploads/tmp
# Ignore Claude Code files
.claude/

View File

@@ -0,0 +1,7 @@
Pry.config.pager = false
Pry.config.color = true
if defined?(Rails)
Pry.config.prompt_name = "#{Rails.application.class.module_parent_name.downcase.green}/#{Rails.env.red}"
end

View File

@@ -0,0 +1,2 @@
--color
--tag ~type:system

View File

@@ -0,0 +1 @@
../.ruby-version

View File

@@ -0,0 +1,53 @@
# Auto generated files with errors to ignore.
# Remove from this list as you refactor files.
---
ignore:
- app/controllers/api/v1/aws_ses_controller.rb:
- Security/Open
- app/controllers/api/v1/profiles_controller.rb:
- Style/SafeNavigation
- app/controllers/api/v1/sessions_controller.rb:
- Style/SafeNavigation
- app/jobs/group_top_posts_job.rb:
- Style/SafeNavigation
- app/jobs/merge_trackables/checkin_trackables.rb:
- Performance/StringIdentifierArgument
- Lint/SymbolConversion
- app/jobs/merge_trackables/dispatcher.rb:
- Lint/SymbolConversion
- app/jobs/merge_trackables/user_trackable_association.rb:
- Lint/SymbolConversion
- app/models/ability.rb:
- Lint/SymbolConversion
- app/models/concerns/topicable.rb:
- Performance/StringIdentifierArgument
- app/models/profile.rb:
- Performance/StringIdentifierArgument
- app/models/registration.rb:
- Layout/MultilineMethodCallIndentation
- app/services/charts_pattern.rb:
- Lint/DuplicateMethods
- Performance/StringIdentifierArgument
- app/services/checkin/updater.rb:
- Lint/SymbolConversion
- Style/RedundantParentheses
- app/services/trackable_creator.rb:
- Lint/SymbolConversion
- lib/tasks/app.rake:
- Lint/ConstantDefinitionInBlock
- Style/GlobalStdStream
- Lint/Loop
- lib/tasks/hbi_completeness.rake:
- Lint/ConstantDefinitionInBlock
- lib/tasks/oneoff.rake:
- Performance/StringIdentifierArgument
- Layout/MultilineMethodCallIndentation
- lib/tasks/trackables.rake:
- Lint/ConstantDefinitionInBlock
- Lint/UselessAssignment
- lib/tasks/usda.rake:
- Lint/ConstantDefinitionInBlock
- lib/tasks/utils.rake:
- Performance/StringIdentifierArgument
- spec/models/food_spec.rb:
- Lint/ConstantDefinitionInBlock

View File

@@ -0,0 +1 @@
../.tool-versions

View File

@@ -0,0 +1,23 @@
FROM ruby:3.2.3
# set working directory
WORKDIR /app
# install dependencies
RUN apt-get update -qq && \
apt-get install -y nodejs postgresql-client
# install bundler
RUN gem install bundler:2.5.6
# copy the Gemfile and Gemfile.lock to the container
COPY Gemfile Gemfile.lock ./
# install the gems
RUN bundle install --full-index
# copy the rest of the application files to the container
COPY . .
# start the server
CMD ["bundle", "exec", "puma", "-C", "config/puma.rb"]

View File

@@ -0,0 +1,109 @@
source "https://rubygems.org"
ruby "3.2.3"
# Configuration management. keep on top of Gemfile
gem "dotenv-rails", groups: %i[development test]
# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem "rails", "~> 7.1.0"
gem "rake"
gem "sprockets-rails"
# JSON serializer
gem "active_model_serializers", "~> 0.9"
# Use postgresql and mongo as the database for Active Record
gem "mongoid", "8.1.3" # https://www.mongodb.com/docs/mongoid/current/reference/compatibility/#rails-compatibility
gem "pg"
# Use Puma as the app server
gem "puma", "5.6.8"
# Authentication libraries
gem "cancancan", "~> 3.6.1"
gem "cancancan-mongoid", "~> 2.0"
gem "devise", "~> 4.8"
gem "devise_invitable", "~> 2.0"
gem "omniauth", "~> 1.8"
gem "omniauth-facebook", "~> 3.0"
# Colored output to console
gem "colored"
# Background jobs
gem "sidekiq", "~> 7.3"
# Structured seed data
gem "seedbank"
# ISO 3166 standard countries
gem "countries", require: "countries/global"
# Pusher Client
gem "pusher"
# ActiveRecord data translations
gem "globalize"
# Abort requests that are taking too long
gem "rack-timeout"
# wrapper for tomorrow.io API
gem "tomorrowio_rb", "~>0.0.3"
gem "geocoder"
gem "nearest_time_zone"
gem "symmetric-encryption"
gem "ruby-progressbar", require: false
gem "kaminari-actionview"
gem "kaminari-mongoid"
gem "rack-cors", "2.0.1", require: "rack/cors" # freezing to gemfile.lock version because heroku is not respecting lockfile
gem "simplecov", require: false, group: :test
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem "bullet"
gem "byebug"
gem "database_cleaner"
gem "database_cleaner-mongoid"
gem "erb_lint", require: false
gem "factory_bot_rails"
# Generate Fake data
gem "ffaker"
gem "pry-byebug"
gem "pry-doc"
gem "pry-rails"
gem "rspec-rails"
gem "standardrb"
end
group :development do
gem "annotate"
gem "awesome_print"
gem "better_errors"
gem "brakeman"
gem "foreman", require: false
gem "letter_opener"
end
group :test do
gem "capybara"
gem "cuprite"
gem "mongoid-rspec"
gem "shoulda-matchers"
gem "vcr"
gem "webmock"
end
group :production do
gem "rails_12factor"
end
# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem "tzinfo-data", platforms: %i[mingw mswin x64_mingw jruby]
gem "bugsnag"

View File

@@ -0,0 +1,581 @@
GEM
remote: https://rubygems.org/
specs:
actioncable (7.1.5.2)
actionpack (= 7.1.5.2)
activesupport (= 7.1.5.2)
nio4r (~> 2.0)
websocket-driver (>= 0.6.1)
zeitwerk (~> 2.6)
actionmailbox (7.1.5.2)
actionpack (= 7.1.5.2)
activejob (= 7.1.5.2)
activerecord (= 7.1.5.2)
activestorage (= 7.1.5.2)
activesupport (= 7.1.5.2)
mail (>= 2.7.1)
net-imap
net-pop
net-smtp
actionmailer (7.1.5.2)
actionpack (= 7.1.5.2)
actionview (= 7.1.5.2)
activejob (= 7.1.5.2)
activesupport (= 7.1.5.2)
mail (~> 2.5, >= 2.5.4)
net-imap
net-pop
net-smtp
rails-dom-testing (~> 2.2)
actionpack (7.1.5.2)
actionview (= 7.1.5.2)
activesupport (= 7.1.5.2)
nokogiri (>= 1.8.5)
racc
rack (>= 2.2.4)
rack-session (>= 1.0.1)
rack-test (>= 0.6.3)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
actiontext (7.1.5.2)
actionpack (= 7.1.5.2)
activerecord (= 7.1.5.2)
activestorage (= 7.1.5.2)
activesupport (= 7.1.5.2)
globalid (>= 0.6.0)
nokogiri (>= 1.8.5)
actionview (7.1.5.2)
activesupport (= 7.1.5.2)
builder (~> 3.1)
erubi (~> 1.11)
rails-dom-testing (~> 2.2)
rails-html-sanitizer (~> 1.6)
active_model_serializers (0.9.8)
activemodel (>= 3.2)
concurrent-ruby (~> 1.0)
activejob (7.1.5.2)
activesupport (= 7.1.5.2)
globalid (>= 0.3.6)
activemodel (7.1.5.2)
activesupport (= 7.1.5.2)
activerecord (7.1.5.2)
activemodel (= 7.1.5.2)
activesupport (= 7.1.5.2)
timeout (>= 0.4.0)
activestorage (7.1.5.2)
actionpack (= 7.1.5.2)
activejob (= 7.1.5.2)
activerecord (= 7.1.5.2)
activesupport (= 7.1.5.2)
marcel (~> 1.0)
activesupport (7.1.5.2)
base64
benchmark (>= 0.3)
bigdecimal
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
logger (>= 1.4.2)
minitest (>= 5.1)
mutex_m
securerandom (>= 0.3)
tzinfo (~> 2.0)
addressable (2.8.7)
public_suffix (>= 2.0.2, < 7.0)
andand (1.3.3)
annotate (3.2.0)
activerecord (>= 3.2, < 8.0)
rake (>= 10.4, < 14.0)
ast (2.4.2)
awesome_print (1.9.2)
base64 (0.3.0)
bcrypt (3.1.20)
benchmark (0.5.0)
better_errors (2.10.1)
erubi (>= 1.0.0)
rack (>= 0.9.0)
rouge (>= 1.0.0)
better_html (2.1.1)
actionview (>= 6.0)
activesupport (>= 6.0)
ast (~> 2.0)
erubi (~> 1.4)
parser (>= 2.4)
smart_properties
bigdecimal (3.3.1)
brakeman (6.1.2)
racc
bson (4.15.0)
bugsnag (6.27.1)
concurrent-ruby (~> 1.0)
builder (3.3.0)
bullet (7.2.0)
activesupport (>= 3.0.0)
uniform_notifier (~> 1.11)
byebug (11.1.3)
cancancan (3.6.1)
cancancan-mongoid (2.0.0)
cancancan (>= 2.0, < 4)
capybara (3.40.0)
addressable
matrix
mini_mime (>= 0.1.3)
nokogiri (~> 1.11)
rack (>= 1.6.0)
rack-test (>= 0.6.3)
regexp_parser (>= 1.5, < 3.0)
xpath (~> 3.2)
coderay (1.1.3)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
colored (1.2)
concurrent-ruby (1.3.5)
connection_pool (2.5.5)
countries (4.0.1)
i18n_data (~> 0.13.0)
sixarm_ruby_unaccent (~> 1.1)
crack (1.0.1)
bigdecimal
rexml
crass (1.0.6)
csv (3.3.0)
cuprite (0.15)
capybara (~> 3.0)
ferrum (~> 0.14.0)
database_cleaner (2.1.0)
database_cleaner-active_record (>= 2, < 3)
database_cleaner-active_record (2.2.2)
activerecord (>= 5.a)
database_cleaner-core (~> 2.0)
database_cleaner-core (2.0.1)
database_cleaner-mongoid (2.0.1)
database_cleaner-core (~> 2.0.0)
mongoid
date (3.5.0)
descendants_tracker (0.0.4)
thread_safe (~> 0.3, >= 0.3.1)
devise (4.9.4)
bcrypt (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 4.1.0)
responders
warden (~> 1.2.3)
devise_invitable (2.0.11)
actionmailer (>= 5.0)
devise (>= 4.6)
diff-lcs (1.6.2)
docile (1.4.0)
dotenv (3.1.0)
dotenv-rails (3.1.0)
dotenv (= 3.1.0)
railties (>= 6.1)
drb (2.2.3)
erb (6.0.0)
erb_lint (0.5.0)
activesupport
better_html (>= 2.0.1)
parser (>= 2.7.1.4)
rainbow
rubocop
smart_properties
erubi (1.13.1)
factory_bot (6.4.6)
activesupport (>= 5.0.0)
factory_bot_rails (6.4.3)
factory_bot (~> 6.4)
railties (>= 5.0.0)
faraday (1.8.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0.1)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.1)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
multipart-post (>= 1.2, < 3)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-httpclient (1.0.1)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
ferrum (0.14)
addressable (~> 2.5)
concurrent-ruby (~> 1.1)
webrick (~> 1.7)
websocket-driver (>= 0.6, < 0.8)
ffaker (2.23.0)
foreman (0.88.1)
geocoder (1.8.3)
base64 (>= 0.1.0)
csv (>= 3.0.0)
globalid (1.3.0)
activesupport (>= 6.1)
globalize (6.3.0)
activemodel (>= 4.2, < 7.2)
activerecord (>= 4.2, < 7.2)
request_store (~> 1.0)
hashdiff (1.2.1)
hashie (3.5.7)
httpclient (2.8.3)
i18n (1.14.7)
concurrent-ruby (~> 1.0)
i18n_data (0.13.0)
io-console (0.8.1)
irb (1.15.3)
pp (>= 0.6.0)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
json (2.7.1)
jwt (2.3.0)
kaminari-actionview (1.2.1)
actionview
kaminari-core (= 1.2.1)
kaminari-core (1.2.1)
kaminari-mongoid (1.0.2)
kaminari-core (~> 1.0)
mongoid
kdtree (0.4)
language_server-protocol (3.17.0.3)
launchy (2.5.2)
addressable (~> 2.8)
letter_opener (1.10.0)
launchy (>= 2.2, < 4)
lint_roller (1.1.0)
logger (1.7.0)
loofah (2.24.1)
crass (~> 1.0.2)
nokogiri (>= 1.12.0)
mail (2.9.0)
logger
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
marcel (1.0.4)
matrix (0.4.2)
method_source (1.1.0)
mini_mime (1.1.5)
mini_portile2 (2.8.9)
minitest (5.26.2)
mongo (2.20.1)
bson (>= 4.14.1, < 6.0.0)
mongoid (8.1.3)
activemodel (>= 5.1, < 7.2, != 7.0.0)
concurrent-ruby (>= 1.0.5, < 2.0)
mongo (>= 2.18.0, < 3.0.0)
ruby2_keywords (~> 0.0.5)
mongoid-compatibility (0.6.0)
activesupport
mongoid (>= 2.0)
mongoid-rspec (4.2.0)
mongoid (>= 3.0, < 10.0)
mongoid-compatibility (>= 0.5.1)
multi_json (1.15.0)
multi_xml (0.6.0)
multipart-post (2.1.1)
mutex_m (0.3.0)
nearest_time_zone (0.0.4)
andand
kdtree
require_all
net-imap (0.5.12)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.1)
net-protocol
nio4r (2.7.3)
nokogiri (1.18.10)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
oauth2 (1.4.7)
faraday (>= 0.8, < 2.0)
jwt (>= 1.0, < 3.0)
multi_json (~> 1.3)
multi_xml (~> 0.5)
rack (>= 1.2, < 3)
omniauth (1.8.1)
hashie (>= 3.4.6, < 3.6.0)
rack (>= 1.6.2, < 3)
omniauth-facebook (3.0.0)
omniauth-oauth2 (~> 1.2)
omniauth-oauth2 (1.5.0)
oauth2 (~> 1.1)
omniauth (~> 1.2)
orm_adapter (0.5.0)
parallel (1.24.0)
parser (3.3.0.5)
ast (~> 2.4.1)
racc
pg (1.5.6)
pp (0.6.3)
prettyprint
prettyprint (0.2.0)
pry (0.14.2)
coderay (~> 1.1)
method_source (~> 1.0)
pry-byebug (3.10.1)
byebug (~> 11.0)
pry (>= 0.13, < 0.15)
pry-doc (1.5.0)
pry (~> 0.11)
yard (~> 0.9.11)
pry-rails (0.3.11)
pry (>= 0.13.0)
psych (5.2.6)
date
stringio
public_suffix (6.0.2)
puma (5.6.8)
nio4r (~> 2.0)
pusher (2.0.3)
httpclient (~> 2.8)
multi_json (~> 1.15)
pusher-signature (~> 0.1.8)
pusher-signature (0.1.8)
racc (1.8.1)
rack (2.2.21)
rack-cors (2.0.1)
rack (>= 2.0.0)
rack-session (1.0.2)
rack (< 3)
rack-test (2.2.0)
rack (>= 1.3)
rack-timeout (0.7.0)
rackup (1.0.1)
rack (< 3)
webrick
rails (7.1.5.2)
actioncable (= 7.1.5.2)
actionmailbox (= 7.1.5.2)
actionmailer (= 7.1.5.2)
actionpack (= 7.1.5.2)
actiontext (= 7.1.5.2)
actionview (= 7.1.5.2)
activejob (= 7.1.5.2)
activemodel (= 7.1.5.2)
activerecord (= 7.1.5.2)
activestorage (= 7.1.5.2)
activesupport (= 7.1.5.2)
bundler (>= 1.15.0)
railties (= 7.1.5.2)
rails-dom-testing (2.3.0)
activesupport (>= 5.0.0)
minitest
nokogiri (>= 1.6)
rails-html-sanitizer (1.6.2)
loofah (~> 2.21)
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
rails_12factor (0.0.3)
rails_serve_static_assets
rails_stdout_logging
rails_serve_static_assets (0.0.5)
rails_stdout_logging (0.0.5)
railties (7.1.5.2)
actionpack (= 7.1.5.2)
activesupport (= 7.1.5.2)
irb
rackup (>= 1.0.0)
rake (>= 12.2)
thor (~> 1.0, >= 1.2.2)
zeitwerk (~> 2.6)
rainbow (3.1.1)
rake (13.2.1)
rdoc (6.16.0)
erb
psych (>= 4.0.0)
tsort
redis-client (0.26.1)
connection_pool
regexp_parser (2.9.0)
reline (0.6.3)
io-console (~> 0.5)
request_store (1.5.0)
rack (>= 1.4)
require_all (3.0.0)
responders (3.2.0)
actionpack (>= 7.0)
railties (>= 7.0)
rexml (3.4.4)
rouge (4.2.1)
rspec-core (3.13.6)
rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-mocks (3.13.7)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-rails (7.1.1)
actionpack (>= 7.0)
activesupport (>= 7.0)
railties (>= 7.0)
rspec-core (~> 3.13)
rspec-expectations (~> 3.13)
rspec-mocks (~> 3.13)
rspec-support (~> 3.13)
rspec-support (3.13.6)
rubocop (1.62.1)
json (~> 2.3)
language_server-protocol (>= 3.17.0)
parallel (~> 1.10)
parser (>= 3.3.0.2)
rainbow (>= 2.2.2, < 4.0)
regexp_parser (>= 1.8, < 3.0)
rexml (>= 3.2.5, < 4.0)
rubocop-ast (>= 1.31.1, < 2.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 2.4.0, < 3.0)
rubocop-ast (1.31.2)
parser (>= 3.3.0.4)
rubocop-performance (1.20.2)
rubocop (>= 1.48.1, < 2.0)
rubocop-ast (>= 1.30.0, < 2.0)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
securerandom (0.4.1)
seedbank (0.5.0)
rake (>= 10.0)
shoulda-matchers (6.2.0)
activesupport (>= 5.2.0)
sidekiq (7.3.9)
base64
connection_pool (>= 2.3.0)
logger
rack (>= 2.2.4)
redis-client (>= 0.22.2)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
simplecov_json_formatter (~> 0.1)
simplecov-html (0.12.3)
simplecov_json_formatter (0.1.4)
sixarm_ruby_unaccent (1.2.0)
smart_properties (1.17.0)
sprockets (4.2.2)
concurrent-ruby (~> 1.0)
logger
rack (>= 2.2.4, < 4)
sprockets-rails (3.5.2)
actionpack (>= 6.1)
activesupport (>= 6.1)
sprockets (>= 3.0.0)
standard (1.35.1)
language_server-protocol (~> 3.17.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.62.0)
standard-custom (~> 1.0.0)
standard-performance (~> 1.3)
standard-custom (1.0.2)
lint_roller (~> 1.0)
rubocop (~> 1.50)
standard-performance (1.3.1)
lint_roller (~> 1.1)
rubocop-performance (~> 1.20.2)
standardrb (1.0.1)
standard
stringio (3.1.8)
symmetric-encryption (4.6.0)
coercible (~> 1.0)
thor (1.4.0)
thread_safe (0.3.6)
timeout (0.4.4)
tomorrowio_rb (0.0.3)
tsort (0.2.0)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.5.0)
uniform_notifier (1.16.0)
vcr (6.3.1)
base64
warden (1.2.9)
rack (>= 2.0.9)
webmock (3.26.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
webrick (1.9.2)
websocket-driver (0.7.6)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
xpath (3.2.0)
nokogiri (~> 1.8)
yard (0.9.36)
zeitwerk (2.7.3)
PLATFORMS
ruby
DEPENDENCIES
active_model_serializers (~> 0.9)
annotate
awesome_print
better_errors
brakeman
bugsnag
bullet
byebug
cancancan (~> 3.6.1)
cancancan-mongoid (~> 2.0)
capybara
colored
countries
cuprite
database_cleaner
database_cleaner-mongoid
devise (~> 4.8)
devise_invitable (~> 2.0)
dotenv-rails
erb_lint
factory_bot_rails
ffaker
foreman
geocoder
globalize
kaminari-actionview
kaminari-mongoid
letter_opener
mongoid (= 8.1.3)
mongoid-rspec
nearest_time_zone
omniauth (~> 1.8)
omniauth-facebook (~> 3.0)
pg
pry-byebug
pry-doc
pry-rails
puma (= 5.6.8)
pusher
rack-cors (= 2.0.1)
rack-timeout
rails (~> 7.1.0)
rails_12factor
rake
rspec-rails
ruby-progressbar
seedbank
shoulda-matchers
sidekiq (~> 7.3)
simplecov
sprockets-rails
standardrb
symmetric-encryption
tomorrowio_rb (~> 0.0.3)
tzinfo-data
vcr
webmock
RUBY VERSION
ruby 3.2.3p157
BUNDLED WITH
2.5.6

View File

@@ -0,0 +1,2 @@
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -C config/sidekiq.yml

View File

@@ -0,0 +1,2 @@
web: bundle exec puma -C config/puma.rb
worker: bundle exec sidekiq -C config/sidekiq.yml

View File

@@ -0,0 +1,5 @@
# 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__)
Rails.application.load_tasks

View File

@@ -0,0 +1,3 @@
//= link_tree ../images
//= link_directory ../javascripts .js
//= link_directory ../stylesheets .css

View File

@@ -0,0 +1,28 @@
module Api
module V1
class AwsSesController < ApplicationController
skip_authorize_resource only: [:mail_it, :notification]
skip_before_action :authenticate_user!, only: [:mail_it, :notification]
def notification
message_type = request.headers["x-amz-sns-message-type"]
# sns_topic = request.headers['x-amz-sns-topic-arn']
raw_post = request.raw_post
if message_type.include? "Confirmation"
send_subscription_confirmation(raw_post)
elsif message_type.include? "Notification"
EmailRejectDispatcher.perform_async(raw_post)
end
render nothing: true, status: 200
end
def send_subscription_confirmation(raw_post)
json = JSON.parse(raw_post)
open(json["SubscribeURL"])
end
end
end
end

View File

@@ -0,0 +1,9 @@
module Api
module V1
class ChartListsController < ApplicationController
def show
render json: ChartListService.new(current_user: current_user).as_json
end
end
end
end

View File

@@ -0,0 +1,32 @@
module Api
module V1
class ChartsController < ApplicationController
def show
chart = Chart.new(chart_params)
# FIXME
# rubocop:disable Style/SignalException
fail(ActiveRecord::RecordInvalid, chart) if chart.invalid?
# rubocop:enable Style/SignalException
render json: chart
end
def chart_params
includes_params = {
tags: [],
foods: [],
symptoms: [],
conditions: [],
treatments: [],
weathersMeasures: [],
harveyBradshawIndices: []
}
params.permit(:id, :start_at, :end_at, includes: includes_params).tap do |whitelist|
whitelist[:user] = current_user
end
end
end
end
end

View File

@@ -0,0 +1,31 @@
module Api
module V1
class ChartsPatternController < ApplicationController
skip_before_action :authenticate_user!, only: [:index]
def index
offset = charts_pattern_params[:offset].to_i
start_at = (charts_pattern_params[:start_at].to_date - offset.days).to_s
end_date = charts_pattern_params[:end_at].to_date
end_at = ((Time.current.to_date == end_date) ? end_date : (end_date + offset.days)).to_s
@patterns = Pattern.where(id: {"$in": charts_pattern_params[:pattern_ids] || []})
@extended_patterns = @patterns.map do |pattern|
pattern.extend(PatternExtender).form_chart_data(start_at: start_at,
end_at: end_at,
pattern: pattern)
end
render json: @extended_patterns, meta: {color_ids: Flaredown::Colorable::IDS}
end
private
def charts_pattern_params
params.permit(:start_at, :end_at, :offset, pattern_ids: [])
end
end
end
end

View File

@@ -0,0 +1,40 @@
module Api
module V1
class CheckinsController < ApplicationController
def index
date = params[:date]
if date.blank? && params.require(:page)
render json: current_user.checkins.where(:note.nin => [nil, ""]).order_by(date: :desc).page(params[:page]).per(10)
else
render json: current_user.checkins.includes([:harvey_bradshaw_index, :promotion_rate, :conditions, :symptoms, :treatments]).select { |x|
x.date.to_date == Date.parse(date)
}
end
end
def show
render json: Checkin.find(id)
end
def create
date = params.require(:checkin).require(:date)
parsed = DateTime.parse(date)
now = DateTime.current
save_date = DateTime.new(parsed.year, parsed.month, parsed.day, now.hour, now.minute, now.second)
checkin = Checkin::Creator.new(current_user.id, save_date).create!
render json: checkin
end
def update
render json: Checkin::Updater.new(current_user, params).update!
end
private
def id
params.require(:id)
end
end
end
end

View File

@@ -0,0 +1,45 @@
module Api
module V1
class CommentsController < ApplicationController
load_and_authorize_resource
skip_before_action :authenticate_user!, only: [:index]
def index
render json: @comments.where(:id.in => params[:ids]).order_by(created_at: :asc)
end
def show
render json: @comment
end
def create
@comment.encrypted_user_id = current_user.encrypted_id
if @comment.save
UpdatePostCountersJob.perform_async(parent_id: create_params[:post_id], parent_type: "Post")
unless @comment.encrypted_user_id == @comment.post.encrypted_user_id
Notification.create(
kind: :comment,
notificateable: @comment,
encrypted_user_id: @comment.encrypted_user_id,
encrypted_notify_user_id: @comment.post.encrypted_user_id
)
end
DiscussionMention.perform_async(current_user.encrypted_id, @comment.id.to_s)
render json: @comment, status: :created
else
render json: {errors: @comment.errors}, status: :unprocessable_entity
end
end
private
def create_params
params.require(:comment).permit(:body, :post_id)
end
end
end
end

View File

@@ -0,0 +1,33 @@
module Api
module V1
class ConditionsController < ApplicationController
load_and_authorize_resource
skip_before_action :authenticate_user!, only: [:show]
def index
@conditions = @conditions.includes(:translations)
@conditions = ids.present? ? @conditions.where(id: ids) : @conditions.order(:name).limit(50)
render json: @conditions
end
def show
render json: @condition
end
def create
render json: TrackableCreator.new(@condition, current_user).create!
end
private
def create_params
params.require(:condition).permit(:name)
end
def ids
@ids ||= params[:ids] if params[:ids].is_a?(Array)
end
end
end
end

View File

@@ -0,0 +1,32 @@
module Api
module V1
class CountriesController < ApplicationController
skip_before_action :authenticate_user!
def index
render json: Country.all, each_serializer: CountrySerializer
end
def show
country = Country.find_country_by_alpha2(alpha2)
# FIXME
# rubocop:disable Style/SignalException
fail ActiveRecord::RecordNotFound if country.nil?
# rubocop:enable Style/SignalException
render json: country, serializer: CountrySerializer
end
private
def alpha2
id = params.require(:id)
match_data = /^[[:alpha:]]{2}$/.match(id)
# FIXME
# rubocop:disable Style/SignalException
fail(ActionController::BadRequest, "id param must be a 2 alphabetic characters string") if match_data.nil?
# rubocop:enable Style/SignalException
match_data[0]
end
end
end
end

View File

@@ -0,0 +1,11 @@
module Api
module V1
class DataExportSchedulesController < ApplicationController
def create
DataExportJob.perform_later(current_user.id)
head :created
end
end
end
end

View File

@@ -0,0 +1,27 @@
module Api
module V1
class DayHabitsController < ApplicationController
skip_before_action :authenticate_user!
def index
render json: DayHabit.all
end
def show
day_habit = DayHabit.find(day_habit_id)
render json: day_habit
end
private
def day_habit_id
id = params.require(:id)
# FIXME
# rubocop:disable Style/SignalException
fail(ActionController::BadRequest, "id param is not a valid day_habit id") unless DayHabit.all_ids.include?(id)
# rubocop:enable Style/SignalException
id
end
end
end
end

View File

@@ -0,0 +1,9 @@
module Api
module V1
class DiscoursesController < ApplicationController
def create
render json: {url: DiscourseClient.new(current_user, params).generate_url}
end
end
end
end

View File

@@ -0,0 +1,29 @@
module Api
module V1
class EducationLevelsController < ApplicationController
skip_before_action :authenticate_user!
def index
render json: EducationLevel.all
end
def show
education_level = EducationLevel.find(education_level_id)
render json: education_level
end
private
def education_level_id
id = params.require(:id)
# FIXME
# rubocop:disable Style/SignalException
unless EducationLevel.all_ids.include?(id)
fail(ActionController::BadRequest, "id param is not a valid education_level id")
end
# rubocop:enable Style/SignalException
id
end
end
end
end

View File

@@ -0,0 +1,27 @@
module Api
module V1
class EthnicitiesController < ApplicationController
skip_before_action :authenticate_user!
def index
render json: Ethnicity.all
end
def show
ethnicity = Ethnicity.find(ethnicity_id)
render json: ethnicity
end
private
def ethnicity_id
id = params.require(:id)
# FIXME
# rubocop:disable Style/SignalException
fail(ActionController::BadRequest, "id param is not a valid ethnicity id") unless Ethnicity.all_ids.include?(id)
# rubocop:enable Style/SignalException
id
end
end
end
end

View File

@@ -0,0 +1,42 @@
module Api
module V1
class FoodsController < ApplicationController
load_and_authorize_resource
def index
@foods = @foods.includes(:translations)
foods =
if ids.present?
@foods.where(id: ids)
elsif scope.present?
CollectionRetriever.new(Food, scope, current_user).retrieve
end
render json: foods
end
def show
render json: @food
end
def create
render json: TrackableCreator.new(@food, current_user).create!
end
private
def create_params
{long_desc: params.require(:food).require(:name)}
end
def ids
@ids ||= params[:ids]
end
def scope
@scope ||= params[:scope]&.to_sym
end
end
end
end

View File

@@ -0,0 +1,28 @@
module Api
module V1
class HarveyBradshawIndicesController < ApplicationController
load_and_authorize_resource
def show
render json: @harvey_bradshaw_index
end
def create
@harvey_bradshaw_index.save
render json: @harvey_bradshaw_index
end
private
def create_params
params.require(:harvey_bradshaw_index).permit(
:abdominal_mass, :abdominal_pain, :abscess,
:anal_fissure, :aphthous_ulcers, :arthralgia,
:checkin_id, :erythema_nodosum, :new_fistula,
:pyoderma_gangrenosum, :stools, :uveitis, :well_being
)
end
end
end
end

View File

@@ -0,0 +1,19 @@
module Api
module V1
class InvitationsController < ApplicationController
skip_before_action :authenticate_user!
def show
render json: Invitation.find(params[:id])
end
def update
invitation = Invitation.find(params[:id])
invitation.accept!(
params.require(:invitation).permit(:email, :password, :password_confirmation)
)
render json: invitation
end
end
end
end

View File

@@ -0,0 +1,52 @@
module Api
module V1
class NotificationsController < ApplicationController
def index
notifications = Notification.where(encrypted_notify_user_id: current_user.encrypted_id)
authorize_collection :index, notifications
render json: {notifications: notifications.aggregated_by_kind_and_subject}
end
def update
notifications = Notification.where(notification_params)
authorize_collection :update, notifications
if notifications.update_all(unread: false)
render json: {notifications: notifications.aggregated_by_kind_and_subject}
else
render json: {errors: notifications.map(&:errors).compact}, status: :unprocessable_entity
end
end
def destroy
notifications = Notification.where(notification_params)
authorize_collection :destroy, notifications
if notifications.destroy
head :no_content
else
render json: {errors: notifications.map(&:errors).compact}, status: :unprocessable_entity
end
end
private
def notification_params
parameters = params.permit(:notificateable_id, :notificateable_type)
parameters[:notificateable_type] = parameters[:notificateable_type].titleize
parameters[:encrypted_notify_user_id] = current_user.encrypted_id
parameters
end
def authorize_collection(name, collection)
collection.each { |element| authorize! name, element }
end
end
end
end

View File

@@ -0,0 +1,35 @@
module Api
module V1
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
Devise.omniauth_providers.each do |provider|
define_method provider do
handle_omniauth
end
end
def failure
Rails.logger.warn("Api::V1::OmniauthCallbacksController#failure: #{failure_message}".yellow)
render json: {errors: failure_message}, status: 401
end
private
def handle_omniauth
user = User.find_for_database_authentication(email: email_param)
if user && user.invitation_token.nil?
render json: user, root: false, serializer: SessionSerializer
else
render json: {errors: "User not found"}, status: 401
end
end
def oauth_params
@oauth_params ||= ActionController::Parameters.new(request.env["omniauth.auth"])
end
def email_param
oauth_params.fetch(:info).fetch(:email)
end
end
end
end

View File

@@ -0,0 +1,60 @@
module Api
module V1
class OracleRequestsController < ApplicationController
skip_before_action :authenticate_user!
serialization_scope :oracle_token
load_resource
def show
render json: @oracle_request
end
def create
if oracle_token.present?
@oracle_request.token = oracle_token
else
loop do
@oracle_request.token = SecureRandom.uuid
break unless OracleRequest.where(token: @oracle_request.token).exists?
end
end
@oracle_request.save
render json: @oracle_request, serializer: OracleRequestWithTokenSerializer
end
def update
if @oracle_request.can_edit?(oracle_token)
@oracle_request.update!(create_params)
render json: @oracle_request
else
render json: {errors: "Unauthorized"}, status: :unauthorised
end
end
private
def create_params
params.require(:oracle_request).permit(
:age,
:sex_id,
responce: [
:name,
:confidence,
:correction
],
symptom_ids: []
)
end
def oracle_token
request.headers["X-Oracle-Token"]
end
end
end
end

View File

@@ -0,0 +1,56 @@
module Api
module V1
class PasswordsController < ApplicationController
skip_before_action :authenticate_user!
def show
user = user_signed_in? ? current_user : User.with_reset_password_token(params[:id])
if user.blank?
raise ActiveRecord::RecordNotFound, "User not found"
else
render json: user, token: params[:id], serializer: PasswordSerializer
end
end
def create
user = User.find_by!(email: email_param.downcase)
return unless user.send_reset_password_instructions
render json: user, serializer: PasswordSerializer
end
def update
if user_signed_in?
if current_user.update_with_password(update_password_params)
render json: current_user, token: params[:id], serializer: PasswordSerializer
else
render json: {errors: current_user.errors}, status: :unprocessable_entity
end
else
user = User.reset_password_by_token(update_password_by_token_params)
if user.errors.empty?
render json: user, token: params[:id], serializer: PasswordSerializer
else
render json: {errors: user.errors}, status: :unprocessable_entity
end
end
end
private
def email_param
params.require(:password).fetch(:email)
end
def update_password_params
params.require(:password).permit(:current_password, :password, :password_confirmation)
end
def update_password_by_token_params
params.require(:password).permit(:reset_password_token, :password, :password_confirmation)
end
end
end
end

View File

@@ -0,0 +1,68 @@
module Api
module V1
class PatternsController < ApplicationController
load_and_authorize_resource
skip_before_action :authenticate_user!, only: [:index]
def index
page = params[:page] || 1
pattern_ids = params[:pattern_ids]
@patterns =
if pattern_ids.present?
Pattern.where(id: {"$in" => pattern_ids})
else
Pattern.accessible_by(current_ability).where(encrypted_user_id: encrypted_user_id)
end
render json: @patterns.page(page).per(10)
end
def show
pattern = Pattern.find_by(id: pattern_params[:id])
render json: pattern
end
def create
@pattern = PatternCreator.new(pattern_params.to_h).create
render json: @pattern
end
def update
@pattern.update(pattern_params)
render json: @pattern
end
def destroy
pattern = Pattern.find_by(id: params[:id])
authorize! :destroy, pattern
if pattern.destroy
head :no_content
else
render json: {errors: pattern.errors}, status: :unprocessable_entity
end
end
private
def pattern_params
params.require(:pattern)
.permit(:name, :start_at, :end_at, includes: [:id, :category, :label])
.merge(user_id: current_user.id)
end
def current_ability
@current_ability ||= Ability.new(current_user)
end
def encrypted_user_id
@encrypted_user_id ||= SymmetricEncryption.encrypt(current_user.id)
end
end
end
end

View File

@@ -0,0 +1,18 @@
module Api
module V1
class PostablesController < ApplicationController
load_and_authorize_resource
def index
render json: PostableSerializer.new(
@postables
.where(encrypted_user_id: current_user.encrypted_id)
.order_by(created_at: :desc)
.page(params[:page])
.per(20),
current_user
)
end
end
end
end

View File

@@ -0,0 +1,46 @@
module Api
module V1
class PostsController < ApplicationController
load_and_authorize_resource
skip_before_action :authenticate_user!, only: [:index, :show]
def index
if params[:summary]
render json: SummaryPosts.new(current_user).show_list
else
@posts = DiscussionPosts.new(params, current_user).show_list
results = @posts
.includes([:comments, :notifications, :reactions])
.order(last_commented: :desc, created_at: :desc)
.page(params[:page])
.per(10)
render json: results
end
end
def show
render json: @post
end
def create
@post.encrypted_user_id = current_user.encrypted_id
if @post.save
render json: @post, status: :created
else
render json: {errors: @post.errors}, status: :unprocessable_entity
end
end
private
def create_params
params.require(:post).permit(
:title, :body,
tag_ids: [], symptom_ids: [], condition_ids: [], treatment_ids: []
)
end
end
end
end

View File

@@ -0,0 +1,75 @@
module Api
module V1
class ProfilesController < ApplicationController
require "sidekiq/api"
load_and_authorize_resource
skip_before_action :authenticate_user!, only: [:index]
def index
post = Post.find(params[:post_id])
encrypted_user_ids =
(post.comments.distinct(:encrypted_user_id) << post.encrypted_user_id).uniq.map do |encrypted_id|
SymmetricEncryption.decrypt(encrypted_id)
end
@profiles = Profile.where(user_id: encrypted_user_ids).where.not(slug_name: nil)
render json: @profiles.map { |profile| profile.attributes.slice("screen_name", "slug_name") }
end
def show
render json: @profile
end
def update
initial_onboarding_reminder = params.dig(:profile, :onboarding_reminder)
@profile.assign_attributes(update_params.merge(transform_hash_time))
time_changed = @profile.checkin_reminder_at_changed? || @profile.time_zone_name_changed?
@profile.save!
if time_changed || initial_onboarding_reminder
delete_old_job(@profile.reminder_job_id)
job_id = CheckinReminderJob.perform_in(get_reminder_time.minutes, @profile.id, @profile.checkin_reminder_at)
@profile.update_column(:reminder_job_id, job_id)
end
current_user.profile.reload
set_locale
render json: @profile
end
private
def update_params
params.require(:profile).permit(
:country_id, :sex_id, :onboarding_step_id, :birth_date,
:day_habit_id, :education_level_id, :day_walking_hours,
:pressure_units, :temperature_units, :screen_name, :notify,
:checkin_reminder, :time_zone_name, :notify_top_posts, ethnicity_ids: []
)
end
def transform_hash_time
checkin_reminder_at = params.require(:profile)[:checkin_reminder_at]
user_time = checkin_reminder_at && checkin_reminder_at.values.join(":")
{checkin_reminder_at: user_time.try(:to_time, :utc)}
end
def get_reminder_time
time_zone_name = @profile.time_zone_name
checkin_at_timezone = @profile.checkin_reminder_at.strftime("%H:%M").in_time_zone(time_zone_name)
# Select minutes
(checkin_at_timezone - Time.current.in_time_zone(time_zone_name)).divmod(1.day)[1].divmod(1.minute)[0]
end
def delete_old_job(enqueued_job_id)
Sidekiq::ScheduledSet.new.find_job(enqueued_job_id)&.delete
end
end
end
end

View File

@@ -0,0 +1,35 @@
module Api
module V1
class PromotionRatesController < ApplicationController
load_and_authorize_resource
def show
render json: @promotion_rate
end
def create
@promotion_rate.save
render json: @promotion_rate
end
def update
@promotion_rate.update(resource_params.merge(additional_params))
render json: @promotion_rate
end
private
def resource_params
params.require(:promotion_rate).permit(:checkin_id, :score, :feedback)
end
def additional_params
user = @promotion_rate.checkin.user
{user_created_at: user.created_at}
end
end
end
end

View File

@@ -0,0 +1,17 @@
module Api
module V1
class PushersController < ApplicationController
def create
render json: Flaredown.pusher.authenticate!(current_user, socket_id)
rescue
render json: {errors: "Bad authentication"}, status: "403"
end
private
def socket_id
params.require(:socket_id)
end
end
end
end

View File

@@ -0,0 +1,77 @@
module Api
module V1
class ReactionsController < ApplicationController
def create
react(__method__)
end
def update
react(__method__)
end
def destroy
reaction = Reaction.where(reaction_params).first
authorize! :destroy, reaction
if reaction.destroy
UpdatePostCountersJob.perform_async(parent_id: reaction_params[:reactable_id],
parent_type: reaction_params[:reactable_type])
head :no_content
else
render json: {errors: reaction.errors}, status: :unprocessable_entity
end
end
private
def react(method_name)
reaction = Reaction.find_or_initialize_by(reaction_params)
authorize! method_name, reaction
if reaction.save
UpdatePostCountersJob.perform_async(parent_id: reaction_params[:reactable_id],
parent_type: reaction_params[:reactable_type])
unless reaction.encrypted_user_id == reaction.reactable.encrypted_user_id
Notification.create(
kind: :reaction,
notificateable: reaction.reactable,
encrypted_user_id: reaction.encrypted_user_id,
encrypted_notify_user_id: reaction.reactable.encrypted_user_id
)
end
reaction.id = params[:id] if params[:id].present?
render json: serialized_reaction(reaction), status: :created
else
render json: {errors: reaction.errors}, status: :unprocessable_entity
end
end
def reaction_params
reaction = params.require(:reaction)
{
value: reaction[:value],
reactable_id: reaction[:reactable_id],
reactable_type: reaction[:reactable_type].titleize,
encrypted_user_id: current_user.encrypted_id
}
end
def serialized_reaction(reaction)
ReactionSerializer
.new(
Reaction.similar_to(reaction).values_count_with_participated(current_user.encrypted_id),
reaction.reactable_id.to_s,
reaction.reactable_type
)
.serialize_one
end
end
end
end

View File

@@ -0,0 +1,15 @@
module Api
module V1
class RegistrationsController < ApplicationController
skip_before_action :authenticate_user!
def create
render json: Registration.create!(params)
end
def destroy
render json: Registration.delete!(params)
end
end
end
end

View File

@@ -0,0 +1,34 @@
module Api
module V1
class SearchesController < ApplicationController
SEARCH_MAPPER = {
"dose" => Search::ForDose,
"food" => Search::ForFood,
"topic" => Search::ForTopic
}.freeze
skip_before_action :authenticate_user!, only: :show
def show
search = (SEARCH_MAPPER[resource_param] || Search).new(search_params)
# FIXME
# rubocop:disable Style/SignalException
fail(ActiveRecord::RecordInvalid, search) if search.invalid?
# rubocop:enable Style/SignalException
render json: search, serializer: SearchSerializer
end
def search_params
params.permit(:resource, :scope, query: [:name, :treatment_id]).tap do |params|
params[:user] = current_user
end
end
def resource_param
params[:resource]
end
end
end
end

View File

@@ -0,0 +1,29 @@
module Api
module V1
class SessionsController < ApplicationController
skip_before_action :authenticate_user!
def create
# FIXME
# rubocop:disable Style/SignalException
fail "missing information" if params[:user].nil?
fail "invalid email or password" if user.nil?
# rubocop:enable Style/SignalException
render json: user, root: false, serializer: SessionSerializer
rescue => e
render json: {errors: Array(e.message)}, status: 401
end
private
def user
@user ||=
begin
user = User.find_for_database_authentication(email: params[:user][:email])
user if user && user.valid_password?(params[:user][:password])
end
end
end
end
end

View File

@@ -0,0 +1,27 @@
module Api
module V1
class SexesController < ApplicationController
skip_before_action :authenticate_user!
def index
render json: Sex.all
end
def show
sex = Sex.find(sex_id)
render json: sex
end
private
def sex_id
id = params.require(:id)
# FIXME
# rubocop:disable Style/SignalException
fail(ActionController::BadRequest, "id param is not a valid sex id") unless Sex.all_ids.include?(id)
# rubocop:enable Style/SignalException
id
end
end
end
end

View File

@@ -0,0 +1,33 @@
module Api
module V1
class SymptomsController < ApplicationController
load_and_authorize_resource
skip_before_action :authenticate_user!, only: [:show]
def index
@symptoms = @symptoms.includes(:translations)
@symptoms = ids.present? ? @symptoms.where(id: ids) : @symptoms.order(:name).limit(50)
render json: @symptoms
end
def show
render json: @symptom
end
def create
render json: TrackableCreator.new(@symptom, current_user).create!
end
private
def create_params
params.require(:symptom).permit(:name)
end
def ids
@ids ||= params[:ids] if params[:ids].is_a?(Array)
end
end
end
end

View File

@@ -0,0 +1,40 @@
module Api
module V1
class TagsController < ApplicationController
load_and_authorize_resource
skip_before_action :authenticate_user!, only: [:show]
def index
@tags = @tags.includes(:translations)
if ids.present?
@tags = @tags.where(id: ids)
elsif scope.present?
@tags = CollectionRetriever.new(Tag, scope, current_user).retrieve
end
render json: @tags
end
def show
render json: @tag
end
def create
render json: TrackableCreator.new(@tag, current_user).create!
end
private
def create_params
params.require(:tag).permit(:name)
end
def ids
@ids ||= params[:ids]
end
def scope
@scope ||= params[:scope].try(:to_sym)
end
end
end
end

View File

@@ -0,0 +1,32 @@
module Api
module V1
class TopicFollowingsController < ApplicationController
load_and_authorize_resource
def show
render json: @topic_following
end
def update
if @topic_following.update(update_params)
render json: @topic_following, status: :ok
else
render json: {errors: @topic_following.errors}, status: :unprocessable_entity
end
end
private
def update_params
empty_params = {
"tag_ids" => [],
"symptom_ids" => [],
"condition_ids" => [],
"treatment_ids" => []
}
empty_params.merge(params.require(:topic_following).permit(empty_params))
end
end
end
end

View File

@@ -0,0 +1,46 @@
module Api
module V1
class TrackingsController < ApplicationController
load_and_authorize_resource except: :create
def index
render json: @trackings.by_trackable_type(trackable_type).active_at(at)
end
def show
render json: @tracking
end
def create
tracking = Tracking.new(create_params.merge(start_at: Time.zone.today, user: current_user))
authorize! :create, tracking
tracking.save!
current_user.topic_following.add_topic("#{tracking.trackable_type.downcase}_ids", tracking.trackable_id)
render json: tracking
end
def destroy
TrackingDestroyer.new(current_user, @tracking, Time.zone.today).destroy
head :no_content
end
private
def at
Time.zone.parse(params.require(:at))
end
def trackable_type
params.require(:trackable_type)
end
def create_params
params.require(:tracking).permit(:trackable_id, :trackable_type, :color_id)
end
end
end
end

View File

@@ -0,0 +1,33 @@
module Api
module V1
class TreatmentsController < ApplicationController
load_and_authorize_resource
skip_before_action :authenticate_user!, only: [:show]
def index
@treatments = @treatments.includes(:translations)
@treatments = ids.present? ? @treatments.where(id: ids) : @treatments.order(:name).limit(50)
render json: @treatments
end
def show
render json: @treatment
end
def create
render json: TrackableCreator.new(@treatment, current_user).create!
end
private
def create_params
params.require(:treatment).permit(:name)
end
def ids
@ids ||= params[:ids] if params[:ids].is_a?(Array)
end
end
end
end

View File

@@ -0,0 +1,30 @@
module Api
module V1
class UnsubscribesController < ApplicationController
skip_before_action :authenticate_user!
def update
@profile = Profile.find_by(notify_token: activation_params[:notify_token])
@profile&.update_column(attribute_dispatcher_key, false)
render json: @profile
end
private
def activation_params
params.permit(:notify_token, :notify_top_posts, :stop_remind)
end
def attribute_dispatcher_key
if activation_params[:stop_remind]
:checkin_reminder
elsif activation_params[:notify_top_posts]
:notify_top_posts
else
:notify
end
end
end
end
end

View File

@@ -0,0 +1,23 @@
module Api
module V1
class UsersController < ApplicationController
load_and_authorize_resource
def show
render json: @user
end
def update
@user.update!(user_params)
render json: @user
end
private
def user_params
params.require(:user).permit(:email)
end
end
end
end

View File

@@ -0,0 +1,19 @@
module Api
module V1
class WeathersController < ApplicationController
def index
unless params[:postal_code].blank?
weather = WeatherRetriever.get(Date.parse(params.require(:date)), params.require(:postal_code))
end
authorize! :read, weather
if weather.present?
render json: weather
else
render json: {weathers: []}
end
end
end
end
end

View File

@@ -0,0 +1,50 @@
class ApplicationController < ActionController::API
include CanCan::ControllerAdditions
include ActionController::Serialization
include ExceptionLogger
before_action :authenticate_user_from_token!, if: :presence_of_authentication_token?
before_action :authenticate_user!, except: [:root]
before_action :set_locale
def root
render json: {
ios: {
major: 0,
minor: 0
},
android: {
major: 0,
minor: 0
}
}
end
protected
def set_locale
I18n.locale = user_signed_in? ? current_user.locale : I18n.default_locale
rescue I18n::InvalidLocale
# FIXME
Rails.logger.warn("'#{current_user.profile.locale}' locale for user '#{current_user.email}' not available or invalid, using default")
I18n.locale = I18n.default_locale
end
private
def authenticate_user_from_token!
user = User.find_by(authorization)
sign_in user, store: false if user
end
def authorization
@authorization ||=
/^Token token="(?<token>.*)", email="(?<email>.*)"$/.match(request.headers["Authorization"]) || {}
{authentication_token: @authorization[:token], email: @authorization[:email]}
end
def presence_of_authentication_token?
authorization[:authentication_token].present?
end
end

View File

@@ -0,0 +1,56 @@
module ExceptionLogger
extend ActiveSupport::Concern
included do
# keep this on top
if Rails.env.production?
rescue_from "Exception" do |exception|
render json: {errors: exception.message}, status: :unprocessable_entity
end
end
rescue_from "ActiveRecord::RecordNotFound", "Mongoid::Errors::DocumentNotFound" do
render json: {errors: ["Resource Not Found"]}, status: 404
end
rescue_from "ActionController::UnknownFormat" do
render json: {errors: ["Format not supported"]}, status: 406
end
rescue_from "ActiveRecord::RecordInvalid", "Mongoid::Errors::Validations" do |exception|
log_exception(exception)
render json: {errors: exception.record.errors}, status: :unprocessable_entity
end
rescue_from "ActionController::ParameterMissing" do |exception|
render json: {errors: ["Required parameter missing: #{exception.param}"]}, status: :unprocessable_entity
end
rescue_from "ActionController::BadRequest" do |exception|
render json: {errors: ["Bad request: #{exception.message}"]}, status: :bad_request
end
rescue_from "CanCan::AccessDenied" do |exception|
log_exception(exception)
render json: {errors: ["Unauthorized"]}, status: :unauthorized
end
end
protected
# --- FOR RAILS 4: ---
## @exception = env["action_dispatch.exception"]
## exception_wrapper = ActionDispatch::ExceptionWrapper.new(env, @exception)
# --- FOR RAILS 5: ---
def log_exception(exception)
application_trace = ActionDispatch::ExceptionWrapper.new(request.env["action_dispatch.backtrace_cleaner"], exception)
trace = application_trace.application_trace
trace = trace.map! { |t| " #{t}\n" }
Rails.logger.error "\n#{exception.class.name} (#{exception.message}):\n#{trace.join}"
end
end

View File

@@ -0,0 +1,18 @@
module PatternExtender
attr_accessor :start_at, :end_at, :pattern, :user, :chart_data
def form_chart_data(options)
@start_at = options[:start_at]
@end_at = options[:end_at]
@pattern = options[:pattern]
@user = options[:user]
@chart_data = ChartsPattern.new(start_at: start_at, end_at: end_at, pattern: pattern, user: user).chart_data
chart_data.merge(used_color_ids: used_color_ids)
end
def used_color_ids
chart_data[:series].map { |item| item[:color_id] }.compact
end
end

View File

@@ -0,0 +1,7 @@
module PostExtender
attr_reader :priority
def frequency_topic_priority(priority)
@priority = priority
end
end

View File

@@ -0,0 +1,2 @@
class ApplicationJob < ActiveJob::Base
end

View File

@@ -0,0 +1,18 @@
class CheckinReminderJob
require "sidekiq/api"
include Sidekiq::Worker
def perform(profile_id, checkin_reminder_at)
profile = Profile.find_by(id: profile_id)
return unless profile
return unless profile.checkin_reminder
return if profile.rejected_type.present?
return unless jid == profile.reminder_job_id
CheckinReminderMailer.remind(email: profile.email).deliver_later
profile.update_column(:reminder_job_id, self.class.perform_in(24.hours, profile_id, checkin_reminder_at))
end
end

View File

@@ -0,0 +1,138 @@
class DataExportJob < ActiveJob::Base
SUBFIELD_SEPARATOR = "; ".freeze
queue_as :default
attr_accessor :user_locale, :symptom_ids, :condition_ids, :treatment_ids, :tag_ids, :food_ids
def perform(user_id)
user = User.find(user_id)
data = csv_data(user)
UserDataMailer.trackings_csv(user.email, data).deliver_later
end
def csv_data(user)
checkins = user.checkins.includes(:conditions, :symptoms, :treatments).order_by(date: :asc)
set_attributes(user.locale, checkins)
CSV.generate do |csv|
csv << headers
checkins.each { |checkin| csv << checkin_row(checkin) }
end
end
private
def checkin_row(checkin)
row = [checkin.date]
symptoms_map = trackables_map(checkin.symptoms, "symptom")
conditions_map = trackables_map(checkin.conditions, "condition")
treatments_map = treatments_map(checkin.treatments)
condition_names.keys.each { |id| row << conditions_map[id] }
symptom_names.keys.each { |id| row << symptoms_map[id] }
treatment_names.keys.each { |id| row << treatments_map[id] }
row << tag_names.slice(*checkin.tag_ids).values.join(SUBFIELD_SEPARATOR)
row << food_names.slice(*checkin.food_ids).values.join(SUBFIELD_SEPARATOR)
row << checkin.weather&.summary
row << checkin.weather&.temperature_max
row << checkin.weather&.temperature_min
row << checkin.weather&.pressure
row << checkin.weather&.precip_intensity
row << checkin.weather&.humidity
end
def set_attributes(locale, checkins)
checkin_ids = checkins.pluck(:id)
self.user_locale = I18n.locale_available?(locale) ? locale : I18n.default_locale
self.symptom_ids = Checkin::Symptom.in(checkin_id: checkin_ids).distinct(:symptom_id)
self.condition_ids = Checkin::Condition.in(checkin_id: checkin_ids).distinct(:condition_id)
self.treatment_ids = Checkin::Treatment.in(checkin_id: checkin_ids).distinct(:treatment_id)
self.tag_ids = checkins.distinct(:tag_ids)
self.food_ids = checkins.distinct(:food_ids)
end
def symptom_names
@symptom_names ||= object_names(Symptom, :name, symptom_ids)
end
def condition_names
@condition_names ||= object_names(Condition, :name, condition_ids)
end
def treatment_names
@treatment_names ||= object_names(Treatment, :name, treatment_ids)
end
def tag_names
@tag_names ||= object_names(Tag, :name, tag_ids)
end
def food_names
@food_names ||= object_names(Food, :long_desc, food_ids)
end
def object_names(klass, field, ids)
name_slug = klass.name.underscore
relation = :"#{name_slug}_translations"
# .pluck(:"#{name_slug}_id", field)
klass
.joins(relation)
.where(:id => ids, relation => {locale: user_locale})
.pluck(:id, field)
.to_h
end
def headers
weather_headers = [
"Weather summary",
"Weather max temperature",
"Weather min temperature",
"Weather pressure",
"Weather precipitation intensity",
"Weather humidity"
]
["Date"]
.concat(condition_names.values)
.concat(symptom_names.values)
.concat(treatment_names.values)
.concat(%w[Tags Foods])
.concat(weather_headers)
end
def trackables_map(trackables, type)
id_field = "#{type}_id"
trackables.map { |c| [c[id_field], c.value] }.to_h
end
# special handling to allow distinguishing between treatments with no dosage, and treatments not taken
def treatments_map(treatments)
treatments.map do |treatment|
[
treatment["treatment_id"],
treatment_value(treatment.is_taken, treatment.value)
]
end.to_h
end
def treatment_value(is_taken, value)
if is_taken
value || "Taken"
else
"Not Taken"
end
end
end

View File

@@ -0,0 +1,40 @@
class DiscussionMention
include Sidekiq::Worker
NAME_REGEXP = /@\w+/
def perform(encrypted_user_id, comment_id)
comment = Comment.find(comment_id)
profiles = found_profiles(comment.body)
return if profiles.empty?
create_notifications(encrypted_user_id, profiles.map(&:user_id), comment)
end
private
def found_profiles(mentioned_string)
selected_names = mentioned_string.scan(NAME_REGEXP).uniq
selected_names.each_with_object([]) do |matched_string, result_array|
matched_string.slice!(0) # remove '@ tag'
profile = Profile.find_by(slug_name: matched_string)
result_array << profile if profile
end
end
def create_notifications(encrypted_user_id, user_ids, comment_id)
encrypted_notify_user_ids = user_ids.map { |user_id| SymmetricEncryption.encrypt(user_id) }
comment = Comment.find(comment_id)
encrypted_notify_user_ids.map do |encrypted_notify_user_id|
Notification.create(
kind: :mention,
notificateable: comment,
encrypted_user_id: encrypted_user_id,
encrypted_notify_user_id: encrypted_notify_user_id
)
end
end
end

View File

@@ -0,0 +1,31 @@
class EmailRejectDispatcher
include Sidekiq::Worker
def perform(raw_post)
body = JSON.parse(raw_post)
test_case_type = body["notificationType"]
message_raw = body["Message"]
recipients =
if test_case_type == "Bounce"
emails = body.dig("mail", "destination") || []
{bounce: emails}
elsif message_raw
generate_recipients(message_raw)
end
return unless recipients.present?
EmailRejectJob.perform_async(recipients)
end
def generate_recipients(message_raw)
message = JSON.parse message_raw
emails = message.dig("mail", "destination") || []
rejected_type = message["notificationType"].downcase
{rejected_type.to_sym => emails}
end
end

View File

@@ -0,0 +1,9 @@
class EmailRejectJob
include Sidekiq::Worker
def perform(recipients_hash)
User.where(email: recipients_hash.values.first).includes(:profile).map do |user|
user.profile.update_column(:rejected_type, recipients_hash.keys.first.to_s)
end
end
end

View File

@@ -0,0 +1,30 @@
class GroupNotifiersPerUser
include Sidekiq::Worker
def perform(encrypted_user_id)
begin
user_id = SymmetricEncryption.decrypt(encrypted_user_id)
rescue OpenSSL::Cipher::CipherError
return
end
user = User.find_by(id: user_id)
return unless user&.notify
return if user&.rejected_type.present? # Rejected via AWS SES
active_notifications = Notification.where(encrypted_notify_user_id: encrypted_user_id, delivered: false)
return if active_notifications.none?
aggregate_data = active_notifications.groupped_by_post_and_kind(encrypted_user_id)
NotificationsMailer.notify(email: user.email, data: aggregate_data).deliver_later
clean_notifications(active_notifications)
end
private
def clean_notifications(active_notifications)
active_notifications.batch_size(500).each { |notification| notification.update(delivered: true) }
end
end

View File

@@ -0,0 +1,14 @@
class GroupTopPostsJob
include Sidekiq::Worker
def perform(notify_token)
profile = Profile.find_by(notify_token: notify_token)
user = profile&.user
return unless profile && profile.notify_top_posts && user
return if user&.rejected_type.present? # Rejected via AWS SES
top_posts_ids = DiscussionPosts.new(nil, user).refined_top_list.map(&:id).map(&:to_s)
TopPostsMailer.notify(email: user.email, notify_token: notify_token, top_posts_ids: top_posts_ids).deliver_later
end
end

View File

@@ -0,0 +1,7 @@
class HelloWorldJob < ActiveJob::Base
queue_as :default
def perform(*_args)
Rails.logger.debug("Hello World".green)
end
end

View File

@@ -0,0 +1,32 @@
class MergeTrackables::CheckinTrackables
include Sidekiq::Worker
def perform(trackable_type, parent_id, rest_ids)
if Checkin::FIELD_TYPE.include? trackable_type
update_health_factors(trackable_type, parent_id, rest_ids)
else
update_regular_trackables(trackable_type, parent_id, rest_ids)
end
p "PERFORM MergeTrackables::PostTrackables"
MergeTrackables::PatternIncludes.perform_async(trackable_type, parent_id, rest_ids)
end
def update_health_factors(trackable_type, parent_id, rest_ids)
Checkin.where("#{trackable_type}_ids" => {"$in" => rest_ids}).map do |checkin|
updated_ids = checkin.send("#{trackable_type}_ids") - rest_ids + [parent_id]
checkin.update("#{trackable_type}_ids".to_sym => updated_ids.uniq)
end
end
def update_regular_trackables(trackable_type, parent_id, rest_ids)
klass = "Checkin::#{trackable_type.capitalize}".constantize
return unless klass
klass.where("#{trackable_type}_id".to_sym.in => rest_ids).map do |checkin|
checkin.update("#{trackable_type}_id".to_sym => parent_id)
end
end
end

View File

@@ -0,0 +1,50 @@
class MergeTrackables::Dispatcher
include Sidekiq::Worker
def perform(trackable_type, translation = nil)
trackable_class = trackable_type.capitalize.constantize
searchable_attr = (trackable_class.name == "Food") ? "long_desc" : "name"
return find_duplicates(trackable_type, trackable_class, translation, searchable_attr) if translation.present?
trackable_class::Translation.find_each do |trackable_translation|
translation = trackable_translation.send(searchable_attr.to_s)
next unless translation
find_duplicates(trackable_type, trackable_class, translation, searchable_attr)
end
end
def find_duplicates(trackable_type, trackable_class, translation, searchable_attr = "name")
begin
escaped_translation = Regexp.escape(translation.squish).split(" ").join("s+")
regex = "^\\s*#{escaped_translation}\\s*$"
same_translations = trackable_class::Translation.where("#{searchable_attr} ~* ?", regex)
return if same_translations.length <= 1 # Next step if origin found only
same_trackables = trackable_class
.where(id: same_translations.where(locale: "en").select("#{trackable_type}_id".to_sym))
.order(trackable_usages_count: :desc, id: :asc)
parent, *rest = same_trackables
return if parent.nil? || rest.length.zero?
p "DUPLICATES FOUND..."
p "#{trackable_type.capitalize} ids: #{same_trackables.pluck(:id)}"
p "#{trackable_type.capitalize} translations: }"
p same_trackables.map(&:"#{searchable_attr}")
rest_ids = rest.map(&:id)
# Remove duplicates from translations
same_translations.where("#{trackable_type}_id": rest_ids).map(&:destroy)
rescue ActiveRecord::StatementInvalid
return
end
p "PERFORM MergeTrackables::UserTrackableAssociation"
MergeTrackables::UserTrackableAssociation.perform_async(trackable_type, parent.id, rest_ids)
end
end

View File

@@ -0,0 +1,22 @@
class MergeTrackables::PatternIncludes
include Sidekiq::Worker
def perform(trackable_type, parent_id, rest_ids)
parent = trackable_type.capitalize.constantize.find_by(id: parent_id)
return unless parent
Pattern.where("includes.id" => {"$in" => rest_ids}).map do |item|
item.includes
.select { |hash| hash[:category] == trackable_type.tableize && rest_ids.include?(hash[:id]) }
.map do |selected_hash|
selected_hash[:id] = parent_id
selected_hash[:label] = parent.name
end
item.includes = item.includes.uniq
item.save
end
MergeTrackables::PostTrackables.perform_async(trackable_type, parent_id, rest_ids)
end
end

View File

@@ -0,0 +1,17 @@
class MergeTrackables::PostTrackables
include Sidekiq::Worker
def perform(trackable_type, parent_id, rest_ids)
trackable_key = "#{trackable_type}_ids"
Post.where(trackable_key.to_sym.in => rest_ids).map do |post|
updated_ids = post.send(trackable_key) - rest_ids + [parent_id]
post.update(trackable_key.to_sym => updated_ids.uniq)
end
p "PERFORM MergeTrackables::RemoveDuplicates"
MergeTrackables::RemoveDuplicates.perform_async(trackable_type, rest_ids)
end
end

View File

@@ -0,0 +1,11 @@
class MergeTrackables::RemoveDuplicates
include Sidekiq::Worker
def perform(trackable_type, rest_ids)
trackable_class = trackable_type.capitalize.constantize
return unless trackable_class
trackable_class.where(id: rest_ids).map(&:destroy)
p "FINISH!!!!"
end
end

View File

@@ -0,0 +1,17 @@
class MergeTrackables::TopicFollowing
include Sidekiq::Worker
def perform(trackable_type, parent_id, rest_ids)
trackable_key = "#{trackable_type}_ids"
TopicFollowing.all(trackable_key.to_sym.in => rest_ids).map do |tf|
updated_ids = tf.send(trackable_key) - rest_ids + [parent_id]
tf.update(trackable_key.to_sym => updated_ids.uniq)
end
p "PERFORM MergeTrackables::CheckinTrackables"
MergeTrackables::CheckinTrackables.perform_async(trackable_type, parent_id, rest_ids)
end
end

View File

@@ -0,0 +1,31 @@
class MergeTrackables::TrackableUsages
include Sidekiq::Worker
def perform(trackable_type, parent_id, rest_ids)
trackable_class = trackable_type.capitalize.constantize
return unless trackable_class
parent = trackable_class.find_by(id: parent_id)
rest = trackable_class.where(id: rest_ids)
return if parent.nil? && rest.length.zero?
TrackableUsage.where(trackable: rest).map do |tr_usage|
parent_usage = TrackableUsage.find_by(trackable: parent, user_id: tr_usage.user_id)
if parent_usage
parent_usage.count += tr_usage.count
parent_usage.save
tr_usage.destroy
else
tr_usage.update(trackable_id: parent.id)
parent.increment!(:trackable_usages_count)
end
end
p "PERFORM MergeTrackables::Trackings"
MergeTrackables::Trackings.perform_async(trackable_type, parent_id, rest_ids)
end
end

View File

@@ -0,0 +1,13 @@
class MergeTrackables::Trackings
include Sidekiq::Worker
def perform(trackable_type, parent_id, rest_ids)
Tracking.where(trackable_id: rest_ids, trackable_type: trackable_type.camelcase).map do |tracking|
tracking.update(trackable_id: parent_id)
end
p "PERFORM MergeTrackables::TopicFollowing"
MergeTrackables::TopicFollowing.perform_async(trackable_type, parent_id, rest_ids)
end
end

View File

@@ -0,0 +1,27 @@
class MergeTrackables::UserTrackableAssociation
include Sidekiq::Worker
def perform(trackable_type, parent_id, rest_ids)
trackable_class = trackable_type.capitalize.constantize
klass = "User#{trackable_type.capitalize}".constantize
return unless trackable_class && klass
parent = trackable_class.find_by(id: parent_id)
rest = trackable_class.where(id: rest_ids)
return if parent.nil? && rest.length.zero?
klass.where(trackable_type.to_sym => rest).map do |user_trackable_type|
parent_user_trackable = klass.find_by(trackable_type.to_sym => parent,
:user_id => user_trackable_type.user_id)
if parent_user_trackable.present?
user_trackable_type.destroy
else
user_trackable_type.update_columns("#{trackable_type}_id".to_sym => parent.id)
end
end
p "PERFORM MergeTrackables::TrackableUsages"
MergeTrackables::TrackableUsages.perform_async(trackable_type, parent_id, rest_ids)
end
end

View File

@@ -0,0 +1,20 @@
class NotesExportJob < ActiveJob::Base
SUBFIELD_SEPARATOR = "; ".freeze
queue_as :default
attr_accessor :user_locale, :symptom_ids, :condition_ids, :treatment_ids, :tag_ids, :food_ids
def perform(user_id)
user = User.find(user_id)
note_details = user.checkins.where.not(note: ["", nil]).order(date: :desc).pluck(:date, :note)
body = note_details.map { |detail| detail.join(",") }
ActionMailer::Base.mail(
from: Rails.application.secrets.smtp_email_from,
to: user.email,
subject: "Flaredown data export",
body: body
).deliver
end
end

View File

@@ -0,0 +1,9 @@
class NotificationDispatcher
include Sidekiq::Worker
def perform
Notification.distinct(:encrypted_notify_user_id).each do |encrypted_id|
GroupNotifiersPerUser.perform_async(encrypted_id)
end
end
end

View File

@@ -0,0 +1,10 @@
class PositionReferenceJob
include Sidekiq::Worker
def perform(object_class, objec_id, postal_code)
object = object_class.constantize.find_by(id: objec_id)
position = Position.find_or_create_by(postal_code: postal_code)
object.update(position_id: position.id) if position.persisted?
end
end

View File

@@ -0,0 +1,37 @@
class SameTrackablesJob
include Sidekiq::Worker
def perform(options)
trackable_type = options["trackable_type"]
translation = options["translation"]
trackable_class = trackable_type.capitalize.constantize
searchable_attr = (trackable_class.name == "Food") ? "long_desc" : "name"
[].tap do |array|
if translation.present?
array << find_duplicates(trackable_class, translation, searchable_attr)
else
trackable_class::Translation.find_each do |trackable_translation|
translation_value = trackable_translation.send(searchable_attr.to_s)
duplicates = find_duplicates(trackable_class, translation_value, searchable_attr)
array << duplicates if duplicates.present?
end
end
p "Same #{trackable_type.pluralize.camelize}: "
p array.uniq
end
end
def find_duplicates(trackable_class, translation, searchable_attr = "name")
escaped_translation = Regexp.escape(translation.squish).split(" ").join("s+")
regex = "^\\s*#{escaped_translation}\\s*$"
same_translations = trackable_class::Translation.where("#{searchable_attr} ~* ?", regex)
same_translations.map(&:"#{searchable_attr}") if same_translations.length > 1
rescue ActiveRecord::StatementInvalid
nil
end
end

View File

@@ -0,0 +1,16 @@
class SwitchTrackableVisibility < ApplicationJob
queue_as :default
def perform(trackable_usage_id)
trackable_usage = TrackableUsage.find_by(id: trackable_usage_id)
return unless trackable_usage
trackable = trackable_usage.trackable.reload
if trackable.trackable_usages_count >= Flaredown.config.trackables_min_popularity
trackable.update(global: true)
else
trackable.update(global: false) if trackable.global? # rubocop:disable Style/IfInsideElse
end
end
end

View File

@@ -0,0 +1,20 @@
class TopPostsMailerDispatcher
include Sidekiq::Worker
TOP_POST_WEEKLY_TIME = ENV["TOP_POST_WEEKLY_TIME"]
MAX_DIFF = 5.minutes
def perform
return unless Time.current.tuesday? # Send on tuesdays
Profile.where(notify_top_posts: true, time_zone_name: find_time_zone_names).find_each(batch_size: 500) do |profile|
GroupTopPostsJob.perform_async(profile.notify_token)
end
end
def find_time_zone_names
Profile.pluck(:time_zone_name).compact.uniq.select do |time_zone_name|
(Time.current.in_time_zone(time_zone_name) - TOP_POST_WEEKLY_TIME.in_time_zone(time_zone_name)).abs < MAX_DIFF
end
end
end

View File

@@ -0,0 +1,25 @@
class UpdateCheckinReminders
require "sidekiq/api"
include Sidekiq::Worker
def perform(profile_id)
profile = Profile.find_by(id: profile_id)
return unless profile
return unless profile.checkin_reminder && profile.checkin_reminder_at
Sidekiq::ScheduledSet.new.find_job(profile.reminder_job_id)&.delete
job_id = CheckinReminderJob.perform_in(get_reminder_time(profile).minutes, profile_id, profile.checkin_reminder_at)
profile.update_column(:reminder_job_id, job_id)
end
def get_reminder_time(profile)
time_zone_name = profile.time_zone_name
checkin_at_timezone = profile.checkin_reminder_at.strftime("%H:%M").in_time_zone(time_zone_name)
# Select minutes
(checkin_at_timezone - Time.current.in_time_zone(time_zone_name)).divmod(1.day)[1].divmod(1.minute)[0]
end
end

View File

@@ -0,0 +1,13 @@
class UpdatePostCountersJob
include Sidekiq::Worker
def perform(params)
parent_type = params["parent_type"]
return unless parent_type == "Post"
post = Post.find_by(_id: params["parent_id"], _type: parent_type)
return unless post
post&.update_counters
end
end

View File

@@ -0,0 +1,9 @@
class ApplicationMailer < ActionMailer::Base
default from: Rails.application.secrets.smtp_email_from
REGEXP = /\A\s*([-\p{L}\d+._]{1,64})@((?:[-\p{L}\d]+\.)+\p{L}{2,})\s*\z/i
def valid_email?(email)
email =~ REGEXP
end
end

View File

@@ -0,0 +1,21 @@
class CheckinReminderMailer < ApplicationMailer
layout "mailer_layout"
def remind(notification_hash)
@email = notification_hash[:email]
return unless valid_email?(@email)
user = User.find_by(email: @email)
notify_token = user&.notify_token
return unless notify_token
return if user&.rejected_type.present? # Rejected via AWS SES
@click_here_link = Rails.application.secrets.base_url
@unsubscribe_link =
Rails.application.secrets.base_url + "/unsubscribe/#{User.find_by(email: @email).notify_token}?stop_remind"
attachments.inline["optional_email_img.png"] = File.read("public/images/optional_email_img.png")
mail(to: @email, subject: I18n.t("checkin_reminder_mailer.subject"))
end
end

View File

@@ -0,0 +1,23 @@
class NotificationsMailer < ApplicationMailer
layout "mailer_layout"
helper_method :resource_title, :resource_url
def notify(notification_hash)
@email = notification_hash[:email]
return unless valid_email?(@email)
@unsubscribe_link = Rails.application.secrets.base_url + "/unsubscribe/#{User.find_by(email: @email).notify_token}"
@data = notification_hash[:data]
mail(to: @email, subject: "New response to your Flaredown message")
end
def resource_title(object_id, class_name = "Post")
class_name.constantize.find(object_id).title.to_s
end
def resource_url(object_id, class_name = "Post")
[ENV["BASE_URL"], class_name.tableize, object_id].map(&:to_s).join("/")
end
end

View File

@@ -0,0 +1,12 @@
class PromotionRate::LowRateMailer < ApplicationMailer
layout "mailer_layout"
def show(email, objects, start_date, end_date)
@email = email
@objects = objects
@start_date = start_date
@end_date = end_date
mail(to: @email, subject: I18n.t("promotion_rate.low_rate.subject"))
end
end

View File

@@ -0,0 +1,12 @@
class PromotionRate::StatisticMailer < ApplicationMailer
layout "mailer_layout"
def show(email, objects, start_date, end_date)
@email = email
@objects = objects
@start_date = start_date
@end_date = end_date
mail(to: @email, subject: I18n.t("promotion_rate.statistic.subject"))
end
end

View File

@@ -0,0 +1,14 @@
class TopPostsMailer < ApplicationMailer
layout "mailer_layout"
def notify(mailer_hash)
@email = mailer_hash[:email]
return unless valid_email?(@email)
@unsubscribe_link =
Rails.application.secrets.base_url + "/unsubscribe/#{mailer_hash[:notify_token]}?notify_top_posts=false"
@top_posts = Post.where(:_type => "Post", :_id.in => mailer_hash[:top_posts_ids])
mail(to: @email, subject: 'Weekly "top posts"')
end
end

View File

@@ -0,0 +1,7 @@
class UserDataMailer < ApplicationMailer
def trackings_csv(email, csv_data)
attachments["data.csv"] = csv_data
mail to: email
end
end

View File

@@ -0,0 +1,100 @@
class Ability
include CanCan::Ability
# rubocop:disable Metrics/AbcSize, Metrics/MethodLength
def initialize(user)
user ||= User.new # guest user (not logged in)
can :manage, User, id: user.id
can :manage, Profile, user_id: user.id
can :show, Condition
can :index, Condition, global: true
cannot :index, Condition, global: false
can :index, Condition, global: false, id: user.trackings.where(trackable_type: "Condition").pluck(:trackable_id)
can :read, Condition, global: false, id: popular_trackable_ids("Condition")
can :create, Condition, global: false
can :manage, Condition, id: user.condition_ids
can :show, Food
can :index, Food, global: true
cannot :index, Food, global: false
can :index, Food, global: false, id: user.food_ids
can :create, Food, global: false
can :manage, Food, id: user.food_ids
can :read, HarveyBradshawIndex, encrypted_user_id: user.encrypted_id
can :create, HarveyBradshawIndex do |hbi|
checkin = hbi.checkin
checkin.encrypted_user_id == user.encrypted_id && checkin.available_for_hbi?
end
can :read, PromotionRate, encrypted_user_id: user.encrypted_id
can [:create, :update], PromotionRate do |rate|
checkin = rate.checkin
checkin.encrypted_user_id == user.encrypted_id && checkin.available_for_promotion?
end
can [:create], Pattern
can [:read, :update, :destroy], Pattern, encrypted_user_id: user.encrypted_id
can [:read], ChartsPattern
can :read, [Comment, Post]
can :create, [Comment, Post], encrypted_user_id: user.encrypted_id
can :read, Notification
can :destroy, Notification, encrypted_notify_user_id: user.encrypted_id
can :update, Notification, encrypted_notify_user_id: user.encrypted_id
can :read, Postable, encrypted_user_id: user.encrypted_id
can :read, Reaction
can [:create, :update, :destroy], Reaction, encrypted_user_id: user.encrypted_id
can :show, Symptom
can :index, Symptom, global: true
cannot :index, Symptom, global: false
can :index, Symptom, global: false, id: user.trackings.where(trackable_type: "Symptom").pluck(:trackable_id)
can :read, Symptom, global: false, id: popular_trackable_ids("Symptom")
can :create, Symptom, global: false
can :manage, Symptom, id: user.symptom_ids
can [:read, :update], TopicFollowing, encrypted_user_id: user.encrypted_id
can :show, Treatment
can :index, Treatment, global: true
cannot :index, Treatment, global: false
can :index, Treatment, global: false, id: user.trackings.where(trackable_type: "Treatment").pluck(:trackable_id)
can :read, Symptom, global: false, id: popular_trackable_ids("Treatment")
can :create, Treatment, global: false
can :manage, Treatment, id: user.treatment_ids
can :manage, Tracking, user_id: user.id
can :show, Tag
can :index, Tag, global: true
cannot :index, Tag, global: false
can :index, Tag, global: false, id: user.tag_ids
can :create, Tag, global: false
can :manage, Tag, id: user.tag_ids
can :read, Weather if user.persisted?
can :read, NilClass if user.persisted?
end
# rubocop:enable Metrics/AbcSize, Metrics/MethodLength
private
def popular_trackable_ids(trackable_class_name)
user_trackable_class = "User#{trackable_class_name}".constantize
trackable_id_attr = "#{trackable_class_name.underscore}_id".to_sym
counts = user_trackable_class.group(trackable_id_attr).count
min_popularity = Flaredown.config.trackables_min_popularity
counts.select { |_id, count| count >= min_popularity }.keys
end
end

View File

@@ -0,0 +1,3 @@
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end

View File

@@ -0,0 +1,38 @@
class Chart
include ActiveModel::Serialization
include ActiveModel::Model
attr_accessor :id, :user, :start_at, :end_at, :includes
#
# Validations
#
validates :user, :start_at, :end_at, presence: true
def checkins
@checkins ||= user.checkins.by_date(start_at.to_date, end_at.to_date).map do |checkin|
checkin.includes = includes
checkin
end
end
def trackables
@_trackables ||= trackings.map(&:trackable)
end
private
def trackings
return Tracking.none if includes.blank?
relation = Tracking.includes(:trackable)
relation
.where(trackable_type: :Condition, trackable_id: includes[:conditions] || [])
.or(relation.where(trackable_type: :Symptom, trackable_id: includes[:symptoms] || []))
.or(relation.where(trackable_type: :Treatment, trackable_id: includes[:treatments] || []))
.where(user_id: user.id)
.active_in_range(start_at.to_date, end_at.to_date)
end
end

View File

@@ -0,0 +1,165 @@
class Checkin
include Mongoid::Document
HBI_PERIODICITY = 7
PR_PERIODICITY = 7
PR_START_FROM = 7
attr_accessor :includes
FIELD_TYPE = %w[tag food].freeze
#
# Fields
#
field :date, type: DateTime
field :food_ids, type: Array, default: []
field :note, type: String
field :postal_code, type: String
field :tag_ids, type: Array
field :weather_id, type: Integer
field :encrypted_user_id, type: String, encrypted: {type: :integer}
field :position_id, type: Integer
field :promotion_skipped_at, type: Date
#
# Relations
#
has_one :harvey_bradshaw_index
has_one :promotion_rate, dependent: :destroy
has_many :treatments, class_name: "Checkin::Treatment"
has_many :conditions, class_name: "Checkin::Condition"
has_many :symptoms, class_name: "Checkin::Symptom"
accepts_nested_attributes_for :conditions, :symptoms, :treatments, allow_destroy: true
#
# Indexes
#
index(encrypted_user_id: 1)
index(date: 1, encrypted_user_id: 1)
#
# Validations
#
validates :encrypted_user_id, presence: true
validates :date, presence: true
#
# Scopes
#
scope :by_date, ->(startkey, endkey) { where(:date.gte => startkey, :date.lte => endkey) }
def user
@user ||= User.find(user_id)
end
def weather
@weather ||= Weather.find_by(id: weather_id)
end
def position
@position ||= Position.find_by(id: position_id)
end
def tags
if includes
@_tags_included ||= Tag.where(id: tag_ids - (includes[:tags] || []))
else
@_tags ||= Tag.where(id: tag_ids)
end
end
def foods
if includes
@_foods_included ||= Food.where(id: food_ids - (includes[:foods] || []))
else
@_foods ||= Food.where(id: food_ids)
end
end
def available_for_hbi?
return true if harvey_bradshaw_index
return true unless latest_hbi
HBI_PERIODICITY - ((latest_hbi.date)...date).count < 1
end
def available_for_promotion?
return true if promotion_rate
return false if user_has_already_rated?
return false unless date.today?
return ready_for_promotion? if latest_skipped_pr_at.blank?
PR_PERIODICITY - ((latest_skipped_pr_at)...date).count < 1
end
class Condition
include Mongoid::Document
include Checkin::Trackable
include Checkin::Fiveable
field :condition_id, type: Integer
validates :condition_id, uniqueness: {scope: :checkin_id}
end
class Symptom
include Mongoid::Document
include Checkin::Trackable
include Checkin::Fiveable
field :symptom_id, type: Integer
validates :symptom_id, uniqueness: {scope: :checkin_id}
end
class Treatment
include Mongoid::Document
include Checkin::Trackable
#
# Fields
#
field :treatment_id, type: Integer
field :value, type: String
field :is_taken, type: Boolean
#
# Indexes
#
index(treatment_id: 1)
index(treatment_id: 1, is_taken: 1, value: 1)
#
# Validations
#
validates :treatment_id, uniqueness: {scope: :checkin_id}
end
def self.ids_by_category_attrs(category_name, trackable_id)
where(id: {"$in" => "Checkin::#{category_name.camelize}".constantize
.where(:"#{category_name}_id" => trackable_id, :value.ne => nil).pluck(:checkin_id)})
end
private
def latest_hbi
@_latest_hbi ||= HarveyBradshawIndex.where(encrypted_user_id: encrypted_user_id).order(date: :desc).first
end
def latest_skipped_pr_at
@_lates_skipped_pr ||=
Checkin
.where(encrypted_user_id: encrypted_user_id)
.order_by(promotion_skipped_at: :desc)
.first&.promotion_skipped_at
end
def ready_for_promotion?
user.created_at <= PR_START_FROM.day.ago
end
def user_has_already_rated?
@_rated ||= PromotionRate.where(encrypted_user_id: encrypted_user_id).any?
end
end

View File

@@ -0,0 +1,8 @@
module Checkin::Fiveable
extend ActiveSupport::Concern
included do
field :value, type: Integer
validates :value, inclusion: {in: (0..4), allow_blank: true}
end
end

View File

@@ -0,0 +1,16 @@
module Checkin::Trackable
extend ActiveSupport::Concern
included do
#
# Fields
#
field :position, type: Integer
field :color_id, type: String
#
# Relations
#
belongs_to :checkin, index: true
end
end

View File

@@ -0,0 +1,20 @@
class Comment
include Mongoid::Document
include Mongoid::Timestamps
include Usernameable
store_in collection: "postables"
field :body, type: String
field :_type, type: String, default: -> { self.class.name }
field :encrypted_user_id, type: String, encrypted: {type: :integer}
validates :body, :post, :encrypted_user_id, presence: true
belongs_to :post, index: true, counter_cache: true
has_many :reactions, as: :reactable, dependent: :destroy
has_many :notifications, as: :notificateable, dependent: :destroy
after_create { post.update(last_commented: Time.current) }
end

Some files were not shown because too many files have changed in this diff Show More