diff --git a/.docker/install-railsexpress b/.docker/install-railsexpress new file mode 100755 index 00000000..1c66b564 --- /dev/null +++ b/.docker/install-railsexpress @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby + +require 'open-uri' +patchset = ARGV[0] +version = ARGV[1] +arguments = ARGV[2..-1].join(' ') + +raise 'Please specify a patchset' if patchset.nil? +raise 'Please specify a ruby version' if version.nil? + +patches = open("https://raw.githubusercontent.com/skaes/rvm-patchsets/master/patchsets/ruby/#{version}/#{patchset}").read.split("\n").map do |patch| + "-p https://raw.githubusercontent.com/skaes/rvm-patchsets/master/patches/ruby/#{version}/#{patch}" +end.join(" ") + +puts "Installing ruby with #{`ruby-install --version`}" +puts "Available Rubies: #{`ruby-install`}" +`ruby-install #{patches} #{arguments} ruby #{version}` diff --git a/.docker/ssl.conf b/.docker/ssl.conf new file mode 100644 index 00000000..34d8c510 --- /dev/null +++ b/.docker/ssl.conf @@ -0,0 +1,20 @@ +server { + listen 443 ssl http2; + http2_push_preload on; + server_name localhost.dev; + + ssl_certificate /etc/nginx/certs/localhost+3.pem; + ssl_certificate_key /etc/nginx/certs/localhost+3-key.pem; + ssl_session_timeout 5m; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:!RC4:!aNULL:!eNULL:!MD5:!EXPORT:!EXP:!LOW:!SEED:!CAMELLIA:!IDEA:!PSK:!SRP:!SSLv:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA'; + ssl_prefer_server_ciphers on; + + location / { + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + proxy_redirect off; + proxy_pass http://app:3000; + } +} \ No newline at end of file diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..3a78c9d4 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,13 @@ +node_modules +npm-debug.log +Dockerfile* +docker-compose* +.dockerignore +.git +.gitignore +.env +*/bin +*/obj +README.md +LICENSE +.vscode \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..062fb675 --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +# 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 all logfiles and tempfiles. +/log/* +/tmp/* +!/log/.keep +!/tmp/.keep + +# Ignore uploaded files in development +/storage/* +!/storage/.keep + +/node_modules +/yarn-error.log + +/public/assets +.byebug_history + +# Ignore master key for decrypting credentials and more. +/config/master.key +/.docker/*.tgz +/.docker/certs + +/dev.to/* +!/dev.to/Procfile.local +!/dev.to/Procfile.falcon +!/dev.to/Gemfile +!/dev.to/Gemfile.lock + +!/dev.to/bin +/dev.to/bin/* +!/dev.to/bin/startup + +!/dev.to/config +/dev.to/config/* + +!/dev.to/config/environments/ +/dev.to/config/environments/* +!/dev.to/config/environments/local_production.rb + +!/dev.to/config/initializers +/dev.to/config/initializers/* +!/dev.to/config/initializers/carrierwave.rb +!/dev.to/config/initializers/airbrake.rb + +!/dev.to/app +/dev.to/app/* + +!/dev.to/app/helpers +/dev.to/app/helpers/* +!/dev.to/app/helpers/application_helper.rb + +!/dev.to/app/controllers +/dev.to/app/controllers/* +!/dev.to/app/controllers/stories_controller.rb + +!/dev.to/app/views +/dev.to/app/views/* +!/dev.to/app/views/layouts +/dev.to/app/views/layouts/* +!/dev.to/app/views/layouts/_top_bar.html.erb + + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..8166b55a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,48 @@ +FROM alpine:latest + +LABEL Alec Pervushin + +ENV RUBY_INSTALL_VERSION "0.7.0" +ENV RUBY_INSTALL_URL "https://github.com/postmodern/ruby-install/archive/v${RUBY_INSTALL_VERSION}.tar.gz" +ENV CHRUBY_VERSION '0.3.9' +ENV CHRUBY_URL "https://github.com/postmodern/chruby/archive/v${CHRUBY_VERSION}.tar.gz" + +RUN apk update && \ + apk upgrade && \ + apk add --no-cache gnupg curl bash procps musl zlib openssl \ + patch make gcc g++ gnupg musl-dev linux-headers zlib-dev openssl-dev \ + postgresql-dev tzdata ruby readline-dev git nodejs yarn + +SHELL ["/bin/bash", "-lc"] + +WORKDIR /tmp + +RUN curl -L -o ruby-install.tar.gz ${RUBY_INSTALL_URL} && \ + tar -xzvf ruby-install.tar.gz && \ + cd ruby-install-${RUBY_INSTALL_VERSION}/ && make install + +ADD .docker/current.tgz* /opt/ + +COPY .docker/install-railsexpress* /tmp/ +COPY dev.to/.ruby-version* /root/ +COPY dev.to/Gemfile* /tmp/ + +RUN echo 'export RUBY_VERSION=$([ -z "$RUBY_VERSION" ] && cat ~/.ruby-version || "$RUBY_VERSION")' >> /etc/profile + +ENV THREAD_PATCH "https://bugs.ruby-lang.org/attachments/download/7081/0001-thread_pthread.c-make-get_main_stack-portable-on-lin.patch" + +RUN ./install-railsexpress railsexpress $RUBY_VERSION \ + -p ${THREAD_PATCH} --jobs=2 --cleanup --no-reinstall + +RUN curl -L -o chruby.tar.gz ${CHRUBY_URL} && \ + tar -xzvf chruby.tar.gz && \ + cd chruby-${CHRUBY_VERSION}/ && make install && \ + echo "source /usr/local/share/chruby/chruby.sh" >> /etc/profile && \ + echo 'chruby $RUBY_VERSION' >> /etc/profile + +RUN gem install bundler rb-readline && bundle check || bundle install + +RUN apk del gnupg musl-dev linux-headers ruby && \ + rm -rf /tmp/* /var/cache/apk/* + +COPY docker-entrypoint.sh* / \ No newline at end of file diff --git a/apache-benchmark.p b/apache-benchmark.p new file mode 100644 index 00000000..e3754e12 --- /dev/null +++ b/apache-benchmark.p @@ -0,0 +1,22 @@ +# Output to a jpeg file +set terminal png size 1280,720 + +# Set the aspect ratio of the graph +set size 1, 1 + +# The file to write to +set output "graphs/ab_out.png" + +# The graph title +set title "Benchmark testing" + +set xlabel "Requests" + +set ylabel "Response (ms)" +set grid y + +# Tell gnuplot to use tabs as the delimiter instead of spaces (default) +set datafile separator '\t' + +# Plot the data +plot "tmp/ab_out.tsv" every ::2 using 5 title 'response time' with boxes \ No newline at end of file diff --git a/bin/apache_benchmark b/bin/apache_benchmark new file mode 100755 index 00000000..b049fcc9 --- /dev/null +++ b/bin/apache_benchmark @@ -0,0 +1,17 @@ +#!/usr/bin/env ruby +require "pathname" +require "fileutils" +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path("..", __dir__) +server_url = ARGV[0] || "http://localhost:3000/" + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +chdir APP_ROOT do + system! "ab -n 400 -c 10 -g tmp/ab_out.tsv #{server_url}" + system "gnuplot apache-benchmark.p" +end diff --git a/dev.to/Gemfile b/dev.to/Gemfile new file mode 100644 index 00000000..9c5cdd51 --- /dev/null +++ b/dev.to/Gemfile @@ -0,0 +1,157 @@ +source "https://rubygems.org" +ruby "2.6.1" + +# Enforce git to transmitted via https. +# workaround until bundler 2.0 is released. +git_source(:github) do |repo_name| + repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/") + "https://github.com/#{repo_name}.git" +end + +group :production do + gem 'nakayoshi_fork', '~> 0.0.4' # solves CoW friendly problem on MRI 2.2 and later +end + +gem "actionpack-action_caching", "~> 1.2" # Action caching for Action Pack (removed from core in Rails 4.0) +gem "active_record_union", "~> 1.3" # Adds proper union and union_all methods to ActiveRecord::Relation +gem "acts-as-taggable-on", "~> 6.0" # A tagging plugin for Rails applications that allows for custom tagging along dynamic contexts +gem "acts_as_follower", github: "thepracticaldev/acts_as_follower", branch: "master" # Allow any model to follow any other model +gem "addressable", "~> 2.5", ">= 2.5.2" # A replacement for the URI implementation that is part of Ruby's standard library +gem "administrate", "~> 0.11" # A Rails engine that helps you put together a super-flexible admin dashboard +gem "ahoy_email", "~> 0.5" # Email analytics for Rails +gem "airbrake", "~> 8.1" # Airbrake is an online tool that provides robust exception tracking in any of your Ruby applications +gem "algoliasearch-rails", "~> 1.21" # Algolia Search is a hosted search engine capable of delivering real-time results from the first keystroke +gem "algorithmia", "~> 1.0" # Ruby Client for Algorithmia Algorithms and Data API +gem "ancestry", "~> 3.0" # Ancestry allows the records of a ActiveRecord model to be organized in a tree structure +gem "autoprefixer-rails", "~> 9.5" # Parse CSS and add vendor prefixes to CSS rules using values from the Can I Use website +gem "aws-sdk-lambda", "~> 1.16" # Official AWS Ruby gem for AWS Lambda +gem "bootsnap", ">= 1.1.0", require: false # Boot large ruby/rails apps faster +gem "buffer", "~> 0.1" # Buffer is a Ruby Wrapper for the Buffer API +gem "carrierwave", "~> 1.3" # Upload files in your Ruby applications, map them to a range of ORMs, store them on different backends +gem "carrierwave-bombshelter", "~> 0.2" # Protect your carrierwave from image bombs +gem "cld", "~> 0.8" # Compact Language Detection for Ruby +gem "cloudinary", "~> 1.11" # Client library for easily using the Cloudinary service +gem "counter_culture", "~> 2.1" # counter_culture provides turbo-charged counter caches that are kept up-to-date +gem "dalli", "~> 2.7" # High performance memcached client for Ruby +gem "delayed_job_active_record", "~> 4.1" # ActiveRecord backend for Delayed::Job +gem "delayed_job_web", "~> 1.4" # Web interface for delayed_job +gem "devise", "~> 4.6" # Flexible authentication solution for Rails +gem "draper", "~> 3.0" # Draper adds an object-oriented layer of presentation logic to your Rails apps +gem "dry-struct", "~> 0.6" # Typed structs and value objects +gem "email_validator", "~> 2.0" # Email validator for Rails and ActiveModel +gem "emoji_regex", "~> 1.0" # A pair of Ruby regular expressions for matching Unicode Emoji symbols +gem "envied", "~> 0.9" # Ensure presence and type of your app's ENV-variables +gem "fastly", "~> 1.15" # Client library for the Fastly acceleration system +gem "fastly-rails", "~> 0.8" # Fastly dynamic caching integration for Rails +gem "feedjira", "~> 2.2" # A feed fetching and parsing library +gem "figaro", "~> 1.1" # Simple, Heroku-friendly Rails app configuration using ENV and a single YAML file +gem "fog", "~> 1.41" # The Ruby cloud services library +gem "front_matter_parser", "~> 0.2" # Parse a front matter from syntactically correct strings or files +gem "gemoji", "~> 3.0.0" # Character information and metadata for standard and custom emoji +gem "gibbon", "~> 3.2" # API wrapper for MailChimp's API +gem "google-api-client", "~> 0.27" # Client for accessing Google APIs +gem "html_truncator", "~> 0.4" # Truncate an HTML string properly +gem "httparty", "~> 0.16" # Makes http fun! Also, makes consuming restful web services dead easy +gem "inline_svg", "~> 1.3" # Embed SVG documents in your Rails views and style them with CSS +gem "jbuilder", "~> 2.8" # Create JSON structures via a Builder-style DSL +gem "jquery-rails", "~> 4.3" # A gem to automate using jQuery with Rails +gem "kaminari", "~> 1.1" # A Scope & Engine based, clean, powerful, customizable and sophisticated paginator +gem "liquid", "~> 4.0" # A secure, non-evaling end user template engine with aesthetic markup +gem "nokogiri", "~> 1.10" # HTML, XML, SAX, and Reader parser +gem "octokit", "~> 4.14" # Simple wrapper for the GitHub API +gem "omniauth", "~> 1.9" # A generalized Rack framework for multiple-provider authentication +gem "omniauth-github", "~> 1.3" # OmniAuth strategy for GitHub +gem "omniauth-twitter", "~> 1.4" # OmniAuth strategy for Twitter +gem "pg", "~> 1.1" # Pg is the Ruby interface to the PostgreSQL RDBMS +gem "pry", "~> 0.12" # An IRB alternative and runtime developer console +gem "pry-rails", "~> 0.3" # Use Pry as your rails console +gem "puma", "~> 3.12" # Puma is a simple, fast, threaded, and highly concurrent HTTP 1.1 server +gem "pundit", "~> 2.0" # Object oriented authorization for Rails applications +gem "pusher", "~> 1.3" # Ruby library for Pusher Channels HTTP API +gem "pusher-push-notifications", "~> 1.0" # Pusher Push Notifications Ruby server SDK +gem "rack-host-redirect", "~> 1.3" # Lean and simple host redirection via Rack middleware +gem "rack-timeout", "~> 0.5" # Rack middleware which aborts requests that have been running for longer than a specified timeout +gem "rails", "~> 5.2", ">= 5.2.3" # Ruby on Rails +gem "rails-assets-airbrake-js-client", "~> 1.6", source: "https://rails-assets.org" # Airbrake JavaScript Notifier +gem "rails-observers", "~> 0.1" # Rails observer (removed from core in Rails 4.0) +gem "recaptcha", "~> 4.13", require: "recaptcha/rails" # Helpers for the reCAPTCHA API +gem "redcarpet", "~> 3.4" # A fast, safe and extensible Markdown to (X)HTML parser +gem "reverse_markdown", "~> 1.1" # Map simple html back into markdown +gem "rolify", "~> 5.2" # Very simple Roles library +gem "rouge", "~> 3.3" # A pure-ruby code highlighter +gem "rubyzip", "~> 1.2", ">= 1.2.2" # Rubyzip is a ruby library for reading and writing zip files +gem "s3_direct_upload", "~> 0.1" # Direct Upload to Amazon S3 +gem "sail", "~> 1.5" # Sail is a lightweight Rails engine that brings an admin panel for managing configuration settings on a live Rails app +gem "sass-rails", "~> 5.0" # Sass adapter for the Rails asset pipeline +gem "scout_apm", "~> 2.4" # Monitors Ruby apps and reports detailed metrics on performance to Scout +gem "serviceworker-rails", "~> 0.5" # Integrates ServiceWorker into the Rails asset pipeline +gem "sitemap_generator", "~> 6.0" # SitemapGenerator is a framework-agnostic XML Sitemap generator +gem "skylight", "~> 3.1" # Skylight is a smart profiler for Rails, Sinatra, and other Ruby apps +gem "slack-notifier", "~> 2.3" # A slim ruby wrapper for posting to slack webhooks +gem "sprockets", "~> 3.7" # Sprockets is a Rack-based asset packaging system +gem "staccato", "~> 0.5" # Ruby Google Analytics Measurement +gem "storext", "~> 2.2" # Add type-casting and other features on top of ActiveRecord::Store.store_accessor +gem "stripe", "~> 4.8" # Ruby library for the Stripe API +gem "timber", "~> 2.6" # Great Ruby logging made easy +gem "twilio-ruby", "~> 5.22" # The official library for communicating with the Twilio REST API +gem "twitter", "~> 6.2" # A Ruby interface to the Twitter API +gem "uglifier", "~> 4.1" # Uglifier minifies JavaScript files +gem "validate_url", "~> 1.0" # Library for validating urls in Rails +gem "webpacker", "~> 3.5" # Use webpack to manage app-like JavaScript modules in Rails +gem "webpush", "~> 0.3" # Encryption Utilities for Web Push payload +gem "falcon", "~> 0.27" + +group :development do + gem "better_errors", "~> 2.5" # Provides a better error page for Rails and other Rack apps + gem "binding_of_caller", "~> 0.8" # Retrieve the binding of a method's caller + gem "brakeman", "~> 4.4", require: false # Brakeman detects security vulnerabilities in Ruby on Rails applications via static analysis + gem "bullet", "~> 5.9" # help to kill N+1 queries and unused eager loading + gem "bundler-audit", "~> 0.6" # bundler-audit provides patch-level verification for Bundled apps + gem "derailed_benchmarks", "~> 1.3", require: false # A series of things you can use to benchmark a Rails or Ruby app + gem "erb_lint", "~> 0.0", require: false # ERB Linter tool + gem "fix-db-schema-conflicts", "~> 3.0" # Ensures consistent output of db/schema.rb despite local differences in the database + # switch foreman to stable release when thor dependency is updated to 0.20+ + gem "foreman", github: "thepracticaldev/foreman", ref: "b64e401", require: false # Process manager for applications with multiple components + gem "guard", "~> 2.15", require: false # Guard is a command line tool to easily handle events on file system modifications + gem "guard-livereload", "~> 2.5", require: false # Guard::LiveReload automatically reloads your browser when 'view' files are modified + gem "guard-rspec", "~> 4.7", require: false # Guard::RSpec automatically run your specs + gem "memory_profiler", "~> 0.9", require: false # Memory profiling routines for Ruby 2.3+ + gem "web-console", "~> 3.7" # Rails Console on the Browser +end + +group :development, :test do + gem "capybara", "~> 3.13" # Capybara is an integration testing tool for rack based web applications + gem "faker", "~> 1.9" # A library for generating fake data such as names, addresses, and phone numbers + gem "parallel_tests", "~> 2.28" # Run Test::Unit / RSpec / Cucumber / Spinach in parallel + gem "pry-byebug", "~> 3.7" # Combine 'pry' with 'byebug'. Adds 'step', 'next', 'finish', 'continue' and 'break' commands to control execution + gem "rspec-rails", "~> 3.8" # rspec-rails is a testing framework for Rails 3+ + gem "rubocop", "~> 0.67", require: false # Automatic Ruby code style checking tool + gem "rubocop-performance", "~> 1.0", require: false # A collection of RuboCop cops to check for performance optimizations in Ruby code + gem "rubocop-rspec", "~> 1.32", require: false # Code style checking for RSpec files + gem "spring", "~> 2.0" # Preloads your application so things like console, rake and tests run faster + gem "spring-commands-rspec", "~> 1.0" # rspec command for spring +end + +group :test do + gem "approvals", "~> 0.0" # A library to make it easier to do golden-master style testing in Ruby + gem "factory_bot_rails", "~> 4.11" # factory_bot is a fixtures replacement with a straightforward definition syntax, support for multiple build strategies + gem "fake_stripe", "~> 0.2" # An implementation of the Stripe credit card processing service to run during your integration tests + gem "launchy", "~> 2.4" # Launchy is helper class for launching cross-platform applications in a fire and forget manner. + gem "pundit-matchers", "~> 1.6" # A set of RSpec matchers for testing Pundit authorisation policies + gem "rspec-retry", "~> 0.6" # retry intermittently failing rspec examples + gem "ruby-prof", "~> 0.17", require: false # ruby-prof is a fast code profiler for Ruby + gem "shoulda-matchers", "4.0.1", require: false # Simple one-liner tests for common Rails functionality + gem "simplecov", "~> 0.16", require: false # Code coverage with a powerful configuration library and automatic merging of coverage across test suites + gem "stackprof", "~> 0.2", require: false, platforms: :ruby # stackprof is a fast sampling profiler for ruby code, with cpu, wallclock and object allocation samplers + gem "stripe-ruby-mock", "~> 2.5", require: "stripe_mock" # A drop-in library to test stripe without hitting their servers + gem "test-prof", "~> 0.7" # Ruby Tests Profiling Toolbox + gem "timecop", "~> 0.9" # A gem providing "time travel" and "time freezing" capabilities, making it dead simple to test time-dependent code + gem "vcr", "~> 4.0" # Record your test suite's HTTP interactions and replay them during future test runs for fast, deterministic, accurate tests + gem "webdrivers", "~> 3.7" # Run Selenium tests more easily with install and updates for all supported webdrivers + gem "webmock", "~> 3.5" # WebMock allows stubbing HTTP requests and setting expectations on HTTP requests + gem "zonebie", "~> 0.6.1" # Runs your tests in a random timezone +end + +group :doc do + gem "sdoc", "~> 1.0" # rdoc generator html with javascript search index +end diff --git a/dev.to/Gemfile.lock b/dev.to/Gemfile.lock new file mode 100644 index 00000000..6d7f9463 --- /dev/null +++ b/dev.to/Gemfile.lock @@ -0,0 +1,1138 @@ +GIT + remote: https://github.com/thepracticaldev/acts_as_follower.git + revision: 288690cd99bc470eaee493fce5bfa9fe23157692 + branch: master + specs: + acts_as_follower (0.2.1) + activerecord (>= 4.0) + +GIT + remote: https://github.com/thepracticaldev/foreman.git + revision: b64e4019a8ed3cfae7216c3219dc91833845d9c7 + ref: b64e401 + specs: + foreman (0.85.0) + thor (>= 0.19, < 0.21) + +GEM + remote: https://rubygems.org/ + remote: https://rails-assets.org/ + specs: + CFPropertyList (2.3.6) + actioncable (5.2.3) + actionpack (= 5.2.3) + nio4r (~> 2.0) + websocket-driver (>= 0.6.1) + actionmailer (5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + mail (~> 2.5, >= 2.5.4) + rails-dom-testing (~> 2.0) + actionpack (5.2.3) + actionview (= 5.2.3) + activesupport (= 5.2.3) + rack (~> 2.0) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.2) + actionpack-action_caching (1.2.0) + actionpack (>= 4.0.0, < 6) + actionview (5.2.3) + activesupport (= 5.2.3) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.0.3) + active_record_union (1.3.0) + activerecord (>= 4.0) + activejob (5.2.3) + activesupport (= 5.2.3) + globalid (>= 0.3.6) + activemodel (5.2.3) + activesupport (= 5.2.3) + activemodel-serializers-xml (1.0.2) + activemodel (> 5.x) + activesupport (> 5.x) + builder (~> 3.1) + activerecord (5.2.3) + activemodel (= 5.2.3) + activesupport (= 5.2.3) + arel (>= 9.0) + activestorage (5.2.3) + actionpack (= 5.2.3) + activerecord (= 5.2.3) + marcel (~> 0.3.1) + activesupport (5.2.3) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 0.7, < 2) + minitest (~> 5.1) + tzinfo (~> 1.1) + acts-as-taggable-on (6.0.0) + activerecord (~> 5.0) + addressable (2.5.2) + public_suffix (>= 2.0.2, < 4.0) + administrate (0.11.0) + actionpack (>= 4.2, < 6.0) + actionview (>= 4.2, < 6.0) + activerecord (>= 4.2, < 6.0) + autoprefixer-rails (>= 6.0) + datetime_picker_rails (~> 0.0.7) + jquery-rails (>= 4.0) + kaminari (>= 1.0) + momentjs-rails (~> 2.8) + sass-rails (~> 5.0) + selectize-rails (~> 0.6) + after_commit_action (1.1.0) + activerecord (>= 3.0.0) + activesupport (>= 3.0.0) + ahoy_email (0.5.2) + actionmailer + activerecord + addressable (>= 2.3.2) + nokogiri + railties + safely_block (>= 0.1.1) + airbrake (8.1.4) + airbrake-ruby (~> 3.2) + airbrake-ruby (3.2.5) + rbtree3 (~> 0.5) + algoliasearch (1.26.0) + httpclient (~> 2.8, >= 2.8.3) + json (>= 1.5.1) + algoliasearch-rails (1.22.0) + algoliasearch (>= 1.17.0, < 2.0.0) + json (>= 1.5.1) + algorithmia (1.0.1) + httparty (~> 0.13) + json (~> 1.8) + ancestry (3.0.5) + activerecord (>= 3.2.0) + approvals (0.0.24) + nokogiri (~> 1.6) + thor (~> 0.18) + arel (9.0.0) + ast (2.4.0) + async (1.17.1) + console (~> 1.0) + nio4r (~> 2.3) + timers (~> 4.1) + async-container (0.10.2) + async (~> 1.0) + async-io (~> 1.4) + process-group + async-http (0.38.2) + async (~> 1.14) + async-io (~> 1.18) + http-protocol (~> 0.15) + async-io (1.21.0) + async (~> 1.14) + autoprefixer-rails (9.5.1) + execjs + aws-eventstream (1.0.1) + aws-partitions (1.125.0) + aws-sdk-core (3.44.0) + aws-eventstream (~> 1.0) + aws-partitions (~> 1.0) + aws-sigv4 (~> 1.0) + jmespath (~> 1.0) + aws-sdk-lambda (1.16.0) + aws-sdk-core (~> 3, >= 3.39.0) + aws-sigv4 (~> 1.0) + aws-sigv4 (1.0.3) + aws_cf_signer (0.1.3) + axiom-types (0.1.1) + descendants_tracker (~> 0.0.4) + ice_nine (~> 0.11.0) + thread_safe (~> 0.3, >= 0.3.1) + bcrypt (3.1.12) + benchmark-ips (2.7.2) + better_errors (2.5.1) + coderay (>= 1.0.0) + erubi (>= 1.0.0) + rack (>= 0.9.0) + better_html (1.0.11) + actionview (>= 4.0) + activesupport (>= 4.0) + ast (~> 2.0) + erubi (~> 1.4) + html_tokenizer (~> 0.0.6) + parser (>= 2.4) + smart_properties + bindex (0.5.0) + binding_of_caller (0.8.0) + debug_inspector (>= 0.0.1) + bootsnap (1.4.2) + msgpack (~> 1.0) + brakeman (4.4.0) + buffer (0.1.3) + addressable + environs + faraday + faraday_middleware + hashie + multi_json + rake + yajl-ruby + buftok (0.2.0) + build-environment (1.10.1) + builder (3.2.3) + bullet (5.9.0) + activesupport (>= 3.0.0) + uniform_notifier (~> 1.11) + bundler-audit (0.6.0) + bundler (~> 1.2) + thor (~> 0.18) + byebug (11.0.0) + capybara (3.13.2) + addressable + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (~> 1.2) + xpath (~> 3.2) + carrierwave (1.3.1) + activemodel (>= 4.0.0) + activesupport (>= 4.0.0) + mime-types (>= 1.16) + carrierwave-bombshelter (0.2.2) + activesupport (>= 3.2.0) + carrierwave + fastimage + caze (0.2.2) + activesupport (>= 3) + childprocess (0.9.0) + ffi (~> 1.0, >= 1.0.11) + cld (0.8.0) + ffi + cloudinary (1.11.1) + aws_cf_signer + rest-client + coderay (1.1.2) + coercible (1.0.0) + descendants_tracker (~> 0.0.1) + coffee-rails (4.2.2) + coffee-script (>= 2.2.0) + railties (>= 4.0.0) + coffee-script (2.4.1) + coffee-script-source + execjs + coffee-script-source (1.12.2) + concurrent-ruby (1.1.5) + connection_pool (2.2.2) + console (1.2.3) + counter_culture (2.1.4) + activerecord (>= 3.0.0) + activesupport (>= 3.0.0) + after_commit_action (~> 1.0) + crack (0.4.3) + safe_yaml (~> 1.0.0) + crass (1.0.4) + dalli (2.7.9) + dante (0.2.0) + datetime_picker_rails (0.0.7) + momentjs-rails (>= 2.8.1) + debug_inspector (0.0.3) + declarative (0.0.10) + declarative-option (0.1.0) + delayed_job (4.1.5) + activesupport (>= 3.0, < 5.3) + delayed_job_active_record (4.1.3) + activerecord (>= 3.0, < 5.3) + delayed_job (>= 3.0, < 5) + delayed_job_web (1.4.3) + activerecord (> 3.0.0) + delayed_job (> 2.0.3) + rack-protection (>= 1.5.5) + sinatra (>= 1.4.4) + derailed_benchmarks (1.3.5) + benchmark-ips (~> 2) + get_process_mem (~> 0) + heapy (~> 0) + memory_profiler (~> 0) + rack (>= 1) + rake (> 10, < 13) + thor (~> 0.19) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) + devise (4.6.2) + bcrypt (~> 3.0) + orm_adapter (~> 0.1) + railties (>= 4.1.0, < 6.0) + responders + warden (~> 1.2.3) + diff-lcs (1.3) + docile (1.3.1) + domain_name (0.5.20180417) + unf (>= 0.0.5, < 1.0.0) + draper (3.0.1) + actionpack (~> 5.0) + activemodel (~> 5.0) + activemodel-serializers-xml (~> 1.0) + activesupport (~> 5.0) + request_store (~> 1.0) + dry-configurable (0.8.2) + concurrent-ruby (~> 1.0) + dry-core (~> 0.4, >= 0.4.7) + dry-container (0.7.0) + concurrent-ruby (~> 1.0) + dry-configurable (~> 0.1, >= 0.1.3) + dry-core (0.4.7) + concurrent-ruby (~> 1.0) + dry-equalizer (0.2.2) + dry-inflector (0.1.2) + dry-logic (0.5.0) + dry-container (~> 0.2, >= 0.2.6) + dry-core (~> 0.2) + dry-equalizer (~> 0.2) + dry-struct (0.6.0) + dry-core (~> 0.4, >= 0.4.3) + dry-equalizer (~> 0.2) + dry-types (~> 0.13) + ice_nine (~> 0.11) + dry-types (0.14.0) + concurrent-ruby (~> 1.0) + dry-container (~> 0.3) + dry-core (~> 0.4, >= 0.4.4) + dry-equalizer (~> 0.2) + dry-inflector (~> 0.1, >= 0.1.2) + dry-logic (~> 0.5, >= 0.5) + em-websocket (0.5.1) + eventmachine (>= 0.12.9) + http_parser.rb (~> 0.6.0) + email_validator (2.0.1) + activemodel + emoji_regex (1.0.1) + envied (0.9.1) + coercible (~> 1.0) + thor (~> 0.15) + environs (1.1.0) + equalizer (0.0.11) + erb_lint (0.0.28) + activesupport + better_html (~> 1.0.7) + html_tokenizer + rainbow + rubocop (~> 0.51) + smart_properties + errbase (0.1.0) + erubi (1.8.0) + et-orbi (1.1.6) + tzinfo + eventmachine (1.2.5) + excon (0.60.0) + execjs (2.7.0) + factory_bot (4.11.1) + activesupport (>= 3.0.0) + factory_bot_rails (4.11.1) + factory_bot (~> 4.11.1) + railties (>= 3.0.0) + fake_stripe (0.2.0) + capybara + sinatra + webmock + faker (1.9.3) + i18n (>= 0.7) + falcon (0.27.0) + async (~> 1.13) + async-container (~> 0.10.0) + async-http (~> 0.38.0) + async-io (~> 1.20) + build-environment (~> 1.6) + http-protocol (~> 0.15) + localhost (~> 1.1) + rack (>= 1.0) + samovar (~> 2.1) + faraday (0.15.4) + multipart-post (>= 1.2, < 3) + faraday_middleware (0.13.1) + faraday (>= 0.7.4, < 1.0) + fastimage (2.1.1) + fastly (1.15.0) + fastly-rails (0.8.0) + fastly (~> 1.6) + railties (> 2, < 6) + feedjira (2.2.0) + faraday (>= 0.9) + faraday_middleware (>= 0.9) + loofah (>= 2.0) + sax-machine (>= 1.0) + ffi (1.9.25) + figaro (1.1.1) + thor (~> 0.14) + fission (0.5.0) + CFPropertyList (~> 2.2) + fix-db-schema-conflicts (3.0.3) + rubocop (>= 0.38.0) + fog (1.41.0) + fog-aliyun (>= 0.1.0) + fog-atmos + fog-aws (>= 0.6.0) + fog-brightbox (~> 0.4) + fog-cloudatcost (~> 0.1.0) + fog-core (~> 1.45) + fog-digitalocean (>= 0.3.0) + fog-dnsimple (~> 1.0) + fog-dynect (~> 0.0.2) + fog-ecloud (~> 0.1) + fog-google (<= 0.1.0) + fog-internet-archive + fog-joyent + fog-json + fog-local + fog-openstack + fog-powerdns (>= 0.1.1) + fog-profitbricks + fog-rackspace + fog-radosgw (>= 0.0.2) + fog-riakcs + fog-sakuracloud (>= 0.0.4) + fog-serverlove + fog-softlayer + fog-storm_on_demand + fog-terremark + fog-vmfusion + fog-voxel + fog-vsphere (>= 0.4.0) + fog-xenserver + fog-xml (~> 0.1.1) + ipaddress (~> 0.5) + json (>= 1.8, < 2.0) + fog-aliyun (0.2.0) + fog-core (~> 1.27) + fog-json (~> 1.0) + ipaddress (~> 0.8) + xml-simple (~> 1.1) + fog-atmos (0.1.0) + fog-core + fog-xml + fog-aws (2.0.0) + fog-core (~> 1.38) + fog-json (~> 1.0) + fog-xml (~> 0.1) + ipaddress (~> 0.8) + fog-brightbox (0.14.0) + fog-core (~> 1.22) + fog-json + inflecto (~> 0.0.2) + fog-cloudatcost (0.1.2) + fog-core (~> 1.36) + fog-json (~> 1.0) + fog-xml (~> 0.1) + ipaddress (~> 0.8) + fog-core (1.45.0) + builder + excon (~> 0.58) + formatador (~> 0.2) + fog-digitalocean (0.3.0) + fog-core (~> 1.42) + fog-json (>= 1.0) + fog-xml (>= 0.1) + ipaddress (>= 0.5) + fog-dnsimple (1.0.0) + fog-core (~> 1.38) + fog-json (~> 1.0) + fog-dynect (0.0.3) + fog-core + fog-json + fog-xml + fog-ecloud (0.3.0) + fog-core + fog-xml + fog-google (0.1.0) + fog-core + fog-json + fog-xml + fog-internet-archive (0.0.1) + fog-core + fog-json + fog-xml + fog-joyent (0.0.1) + fog-core (~> 1.42) + fog-json (>= 1.0) + fog-json (1.0.2) + fog-core (~> 1.0) + multi_json (~> 1.10) + fog-local (0.4.0) + fog-core (~> 1.27) + fog-openstack (0.1.24) + fog-core (~> 1.40) + fog-json (>= 1.0) + ipaddress (>= 0.8) + fog-powerdns (0.1.1) + fog-core (~> 1.27) + fog-json (~> 1.0) + fog-xml (~> 0.1) + fog-profitbricks (4.1.1) + fog-core (~> 1.42) + fog-json (~> 1.0) + fog-rackspace (0.1.5) + fog-core (>= 1.35) + fog-json (>= 1.0) + fog-xml (>= 0.1) + ipaddress (>= 0.8) + fog-radosgw (0.0.5) + fog-core (>= 1.21.0) + fog-json + fog-xml (>= 0.0.1) + fog-riakcs (0.1.0) + fog-core + fog-json + fog-xml + fog-sakuracloud (1.7.5) + fog-core + fog-json + fog-serverlove (0.1.2) + fog-core + fog-json + fog-softlayer (1.1.4) + fog-core + fog-json + fog-storm_on_demand (0.1.1) + fog-core + fog-json + fog-terremark (0.1.0) + fog-core + fog-xml + fog-vmfusion (0.1.0) + fission + fog-core + fog-voxel (0.1.0) + fog-core + fog-xml + fog-vsphere (1.13.1) + fog-core + rbvmomi (~> 1.9) + fog-xenserver (0.3.0) + fog-core + fog-xml + fog-xml (0.1.3) + fog-core + nokogiri (>= 1.5.11, < 2.0.0) + formatador (0.2.5) + front_matter_parser (0.2.0) + fugit (1.1.6) + et-orbi (~> 1.1, >= 1.1.6) + raabro (~> 1.1) + gemoji (3.0.0) + get_process_mem (0.2.3) + gibbon (3.2.0) + faraday (>= 0.9.1) + multi_json (>= 1.11.0) + globalid (0.4.2) + activesupport (>= 4.2.0) + google-api-client (0.27.3) + addressable (~> 2.5, >= 2.5.1) + googleauth (>= 0.5, < 0.10.0) + httpclient (>= 2.8.1, < 3.0) + mime-types (~> 3.0) + representable (~> 3.0) + retriable (>= 2.0, < 4.0) + signet (~> 0.10) + googleauth (0.8.0) + faraday (~> 0.12) + jwt (>= 1.4, < 3.0) + memoist (~> 0.16) + multi_json (~> 1.11) + os (>= 0.9, < 2.0) + signet (~> 0.7) + guard (2.15.0) + formatador (>= 0.2.4) + listen (>= 2.7, < 4.0) + lumberjack (>= 1.0.12, < 2.0) + nenv (~> 0.1) + notiffany (~> 0.0) + pry (>= 0.9.12) + shellany (~> 0.0) + thor (>= 0.18.1) + guard-compat (1.2.1) + guard-livereload (2.5.2) + em-websocket (~> 0.5) + guard (~> 2.8) + guard-compat (~> 1.0) + multi_json (~> 1.8) + guard-rspec (4.7.3) + guard (~> 2.1) + guard-compat (~> 1.1) + rspec (>= 2.99.0, < 4.0) + hashdiff (0.3.8) + hashie (3.6.0) + heapy (0.1.4) + hkdf (0.3.0) + html_tokenizer (0.0.7) + html_truncator (0.4.2) + nokogiri (~> 1.5) + http (3.3.0) + addressable (~> 2.3) + http-cookie (~> 1.0) + http-form_data (~> 2.0) + http_parser.rb (~> 0.6.0) + http-cookie (1.0.3) + domain_name (~> 0.5) + http-form_data (2.1.1) + http-hpack (0.1.1) + http-protocol (0.15.0) + http-hpack (~> 0.1.0) + http_parser.rb (0.6.0) + httparty (0.16.3) + mime-types (~> 3.0) + multi_xml (>= 0.5.2) + httpclient (2.8.3) + i18n (1.6.0) + concurrent-ruby (~> 1.0) + ice_nine (0.11.2) + inflecto (0.0.2) + inline_svg (1.3.1) + activesupport (>= 3.0) + nokogiri (>= 1.6) + ipaddress (0.8.3) + jaro_winkler (1.5.2) + jbuilder (2.8.0) + activesupport (>= 4.2.0) + multi_json (>= 1.2) + jmespath (1.4.0) + jquery-fileupload-rails (0.4.7) + actionpack (>= 3.1) + railties (>= 3.1) + sass (>= 3.2) + jquery-rails (4.3.3) + rails-dom-testing (>= 1, < 3) + railties (>= 4.2.0) + thor (>= 0.14, < 2.0) + json (1.8.6) + jwt (2.1.0) + kaminari (1.1.1) + activesupport (>= 4.1.0) + kaminari-actionview (= 1.1.1) + kaminari-activerecord (= 1.1.1) + kaminari-core (= 1.1.1) + kaminari-actionview (1.1.1) + actionview + kaminari-core (= 1.1.1) + kaminari-activerecord (1.1.1) + activerecord + kaminari-core (= 1.1.1) + kaminari-core (1.1.1) + launchy (2.4.3) + addressable (~> 2.3) + liquid (4.0.3) + listen (3.1.5) + rb-fsevent (~> 0.9, >= 0.9.4) + rb-inotify (~> 0.9, >= 0.9.7) + ruby_dep (~> 1.2) + localhost (1.1.5) + loofah (2.2.3) + crass (~> 1.0.2) + nokogiri (>= 1.5.9) + lumberjack (1.0.13) + mail (2.7.1) + mini_mime (>= 0.1.1) + mapping (1.1.1) + marcel (0.3.3) + mimemagic (~> 0.3.2) + memoist (0.16.0) + memoizable (0.4.2) + thread_safe (~> 0.3, >= 0.3.1) + memory_profiler (0.9.12) + method_source (0.9.2) + mime-types (3.2.2) + mime-types-data (~> 3.2015) + mime-types-data (3.2018.0812) + mimemagic (0.3.3) + mini_mime (1.0.1) + mini_portile2 (2.4.0) + minitest (5.11.3) + momentjs-rails (2.20.1) + railties (>= 3.1) + msgpack (1.2.4) + multi_json (1.13.1) + multi_xml (0.6.0) + multipart-post (2.0.0) + mustermann (1.0.3) + nakayoshi_fork (0.0.4) + naught (1.1.0) + nenv (0.3.0) + net-http-persistent (3.0.0) + connection_pool (~> 2.2) + net_http_ssl_fix (0.0.10) + netrc (0.11.0) + nio4r (2.3.1) + nokogiri (1.10.2) + mini_portile2 (~> 2.4.0) + notiffany (0.1.1) + nenv (~> 0.1) + shellany (~> 0.0) + oauth (0.5.4) + oauth2 (1.4.1) + faraday (>= 0.8, < 0.16.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) + octokit (4.14.0) + sawyer (~> 0.8.0, >= 0.5.3) + omniauth (1.9.0) + hashie (>= 3.4.6, < 3.7.0) + rack (>= 1.6.2, < 3) + omniauth-github (1.3.0) + omniauth (~> 1.5) + omniauth-oauth2 (>= 1.4.0, < 2.0) + omniauth-oauth (1.1.0) + oauth + omniauth (~> 1.0) + omniauth-oauth2 (1.6.0) + oauth2 (~> 1.1) + omniauth (~> 1.9) + omniauth-twitter (1.4.0) + omniauth-oauth (~> 1.1) + rack + orm_adapter (0.5.0) + os (1.0.0) + parallel (1.17.0) + parallel_tests (2.28.0) + parallel + parser (2.6.2.0) + ast (~> 2.4.0) + pg (1.1.4) + process-group (1.1.0) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-byebug (3.7.0) + byebug (~> 11.0) + pry (~> 0.10) + pry-rails (0.3.9) + pry (>= 0.10.4) + psych (3.1.0) + public_suffix (3.0.3) + puma (3.12.1) + pundit (2.0.1) + activesupport (>= 3.0.0) + pundit-matchers (1.6.0) + rspec-rails (>= 3.0.0) + pusher (1.3.2) + httpclient (~> 2.7) + multi_json (~> 1.0) + pusher-signature (~> 0.1.8) + pusher-push-notifications (1.0.0) + caze + rest-client + pusher-signature (0.1.8) + raabro (1.1.6) + rack (2.0.7) + rack-host-redirect (1.3.0) + rack + rack-protection (2.0.5) + rack + rack-proxy (0.6.5) + rack + rack-test (1.1.0) + rack (>= 1.0, < 3) + rack-timeout (0.5.1) + rails (5.2.3) + actioncable (= 5.2.3) + actionmailer (= 5.2.3) + actionpack (= 5.2.3) + actionview (= 5.2.3) + activejob (= 5.2.3) + activemodel (= 5.2.3) + activerecord (= 5.2.3) + activestorage (= 5.2.3) + activesupport (= 5.2.3) + bundler (>= 1.3.0) + railties (= 5.2.3) + sprockets-rails (>= 2.0.0) + rails-assets-airbrake-js-client (1.6.6) + rails-dom-testing (2.0.3) + activesupport (>= 4.2.0) + nokogiri (>= 1.6) + rails-html-sanitizer (1.0.4) + loofah (~> 2.2, >= 2.2.2) + rails-observers (0.1.5) + activemodel (>= 4.0) + railties (5.2.3) + actionpack (= 5.2.3) + activesupport (= 5.2.3) + method_source + rake (>= 0.8.7) + thor (>= 0.19.0, < 2.0) + rainbow (3.0.0) + rake (12.3.2) + rb-fsevent (0.10.3) + rb-inotify (0.9.10) + ffi (>= 0.5.0, < 2) + rbtree3 (0.5.0) + rbvmomi (1.11.6) + builder (~> 3.0) + json (>= 1.8) + nokogiri (~> 1.5) + trollop (~> 2.1) + rdoc (6.1.1) + recaptcha (4.13.2) + json + redcarpet (3.4.0) + regexp_parser (1.3.0) + representable (3.0.4) + declarative (< 0.1.0) + declarative-option (< 0.2.0) + uber (< 0.2.0) + request_store (1.4.0) + rack (>= 1.4) + responders (2.4.1) + actionpack (>= 4.2.0, < 6.0) + railties (>= 4.2.0, < 6.0) + rest-client (2.0.2) + http-cookie (>= 1.0.2, < 2.0) + mime-types (>= 1.16, < 4.0) + netrc (~> 0.8) + retriable (3.1.2) + reverse_markdown (1.1.0) + nokogiri + rolify (5.2.0) + rouge (3.3.0) + rspec (3.8.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-core (3.8.0) + rspec-support (~> 3.8.0) + rspec-expectations (3.8.2) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-mocks (3.8.0) + diff-lcs (>= 1.2.0, < 2.0) + rspec-support (~> 3.8.0) + rspec-rails (3.8.2) + actionpack (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-core (~> 3.8.0) + rspec-expectations (~> 3.8.0) + rspec-mocks (~> 3.8.0) + rspec-support (~> 3.8.0) + rspec-retry (0.6.1) + rspec-core (> 3.3) + rspec-support (3.8.0) + rubocop (0.67.2) + jaro_winkler (~> 1.5.1) + parallel (~> 1.10) + parser (>= 2.5, != 2.5.1.1) + psych (>= 3.1.0) + rainbow (>= 2.2.2, < 4.0) + ruby-progressbar (~> 1.7) + unicode-display_width (>= 1.4.0, < 1.6) + rubocop-performance (1.0.0) + rubocop (>= 0.58.0) + rubocop-rspec (1.32.0) + rubocop (>= 0.60.0) + ruby-prof (0.17.0) + ruby-progressbar (1.10.0) + ruby_dep (1.5.0) + rubyzip (1.2.2) + s3_direct_upload (0.1.7) + coffee-rails (>= 3.1) + jquery-fileupload-rails (~> 0.4.1) + rails (>= 3.1) + sass-rails (>= 3.1) + safe_yaml (1.0.4) + safely_block (0.2.1) + errbase + sail (1.5.1) + fugit + jquery-rails + rails + sass-rails + samovar (2.1.1) + console (~> 1.0) + mapping (~> 1.0) + sass (3.7.2) + 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.7) + railties (>= 4.0.0, < 6) + sass (~> 3.1) + sprockets (>= 2.8, < 4.0) + sprockets-rails (>= 2.0, < 4.0) + tilt (>= 1.1, < 3) + sawyer (0.8.1) + addressable (>= 2.3.5, < 2.6) + faraday (~> 0.8, < 1.0) + sax-machine (1.3.2) + scout_apm (2.4.24) + sdoc (1.0.0) + rdoc (>= 5.0) + selectize-rails (0.12.6) + selenium-webdriver (3.141.0) + childprocess (~> 0.5) + rubyzip (~> 1.2, >= 1.2.2) + serviceworker-rails (0.5.5) + railties (>= 3.1) + shellany (0.0.1) + shoulda-matchers (4.0.1) + activesupport (>= 4.2.0) + signet (0.11.0) + addressable (~> 2.3) + faraday (~> 0.9) + jwt (>= 1.5, < 3.0) + multi_json (~> 1.10) + simple_oauth (0.3.1) + simplecov (0.16.1) + docile (~> 1.1) + json (>= 1.8, < 3) + simplecov-html (~> 0.10.0) + simplecov-html (0.10.2) + sinatra (2.0.5) + mustermann (~> 1.0) + rack (~> 2.0) + rack-protection (= 2.0.5) + tilt (~> 2.0) + sitemap_generator (6.0.2) + builder (~> 3.0) + skylight (3.1.4) + skylight-core (= 3.1.4) + skylight-core (3.1.4) + activesupport (>= 4.2.0) + slack-notifier (2.3.2) + smart_properties (1.13.1) + spring (2.0.2) + activesupport (>= 4.2) + spring-commands-rspec (1.0.4) + spring (>= 0.9.1) + sprockets (3.7.2) + concurrent-ruby (~> 1.0) + rack (> 1, < 3) + sprockets-rails (3.2.1) + actionpack (>= 4.0) + activesupport (>= 4.0) + sprockets (>= 3.0.0) + staccato (0.5.1) + stackprof (0.2.12) + storext (2.2.2) + rails (>= 4.0, < 6.0) + virtus + stripe (4.8.0) + faraday (~> 0.13) + net-http-persistent (~> 3.0) + stripe-ruby-mock (2.5.4) + dante (>= 0.2.0) + multi_json (~> 1.0) + stripe (>= 2.0.3) + test-prof (0.7.3) + thor (0.20.3) + thread_safe (0.3.6) + tilt (2.0.9) + timber (2.6.2) + msgpack (~> 1.0) + timecop (0.9.1) + timers (4.3.0) + trollop (2.1.2) + twilio-ruby (5.22.0) + faraday (~> 0.9) + jwt (>= 1.5, <= 2.5) + nokogiri (>= 1.6, < 2.0) + twitter (6.2.0) + addressable (~> 2.3) + buftok (~> 0.2.0) + equalizer (~> 0.0.11) + http (~> 3.0) + http-form_data (~> 2.0) + http_parser.rb (~> 0.6.0) + memoizable (~> 0.4.0) + multipart-post (~> 2.0) + naught (~> 1.0) + simple_oauth (~> 0.3.0) + tzinfo (1.2.5) + thread_safe (~> 0.1) + uber (0.1.0) + uglifier (4.1.20) + execjs (>= 0.3.0, < 3) + unf (0.1.4) + unf_ext + unf_ext (0.0.7.5) + unicode-display_width (1.5.0) + uniform_notifier (1.12.1) + validate_url (1.0.8) + activemodel (>= 3.0.0) + public_suffix + vcr (4.0.0) + virtus (1.0.5) + axiom-types (~> 0.1) + coercible (~> 1.0) + descendants_tracker (~> 0.0, >= 0.0.3) + equalizer (~> 0.0, >= 0.0.9) + warden (1.2.8) + rack (>= 2.0.6) + web-console (3.7.0) + actionview (>= 5.0) + activemodel (>= 5.0) + bindex (>= 0.4.0) + railties (>= 5.0) + webdrivers (3.7.2) + net_http_ssl_fix + nokogiri (~> 1.6) + rubyzip (~> 1.0) + selenium-webdriver (~> 3.0) + webmock (3.5.1) + addressable (>= 2.3.6) + crack (>= 0.3.2) + hashdiff + webpacker (3.5.5) + activesupport (>= 4.2) + rack-proxy (>= 0.6.1) + railties (>= 4.2) + webpush (0.3.7) + hkdf (~> 0.2) + jwt (~> 2.0) + websocket-driver (0.7.0) + websocket-extensions (>= 0.1.0) + websocket-extensions (0.1.3) + xml-simple (1.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + yajl-ruby (1.4.1) + zonebie (0.6.1) + +PLATFORMS + ruby + +DEPENDENCIES + actionpack-action_caching (~> 1.2) + active_record_union (~> 1.3) + acts-as-taggable-on (~> 6.0) + acts_as_follower! + addressable (~> 2.5, >= 2.5.2) + administrate (~> 0.11) + ahoy_email (~> 0.5) + airbrake (~> 8.1) + algoliasearch-rails (~> 1.21) + algorithmia (~> 1.0) + ancestry (~> 3.0) + approvals (~> 0.0) + autoprefixer-rails (~> 9.5) + aws-sdk-lambda (~> 1.16) + better_errors (~> 2.5) + binding_of_caller (~> 0.8) + bootsnap (>= 1.1.0) + brakeman (~> 4.4) + buffer (~> 0.1) + bullet (~> 5.9) + bundler-audit (~> 0.6) + capybara (~> 3.13) + carrierwave (~> 1.3) + carrierwave-bombshelter (~> 0.2) + cld (~> 0.8) + cloudinary (~> 1.11) + counter_culture (~> 2.1) + dalli (~> 2.7) + delayed_job_active_record (~> 4.1) + delayed_job_web (~> 1.4) + derailed_benchmarks (~> 1.3) + devise (~> 4.6) + draper (~> 3.0) + dry-struct (~> 0.6) + email_validator (~> 2.0) + emoji_regex (~> 1.0) + envied (~> 0.9) + erb_lint (~> 0.0) + factory_bot_rails (~> 4.11) + fake_stripe (~> 0.2) + faker (~> 1.9) + falcon (~> 0.27) + fastly (~> 1.15) + fastly-rails (~> 0.8) + feedjira (~> 2.2) + figaro (~> 1.1) + fix-db-schema-conflicts (~> 3.0) + fog (~> 1.41) + foreman! + front_matter_parser (~> 0.2) + gemoji (~> 3.0.0) + gibbon (~> 3.2) + google-api-client (~> 0.27) + guard (~> 2.15) + guard-livereload (~> 2.5) + guard-rspec (~> 4.7) + html_truncator (~> 0.4) + httparty (~> 0.16) + inline_svg (~> 1.3) + jbuilder (~> 2.8) + jquery-rails (~> 4.3) + kaminari (~> 1.1) + launchy (~> 2.4) + liquid (~> 4.0) + memory_profiler (~> 0.9) + nakayoshi_fork (~> 0.0.4) + nokogiri (~> 1.10) + octokit (~> 4.14) + omniauth (~> 1.9) + omniauth-github (~> 1.3) + omniauth-twitter (~> 1.4) + parallel_tests (~> 2.28) + pg (~> 1.1) + pry (~> 0.12) + pry-byebug (~> 3.7) + pry-rails (~> 0.3) + puma (~> 3.12) + pundit (~> 2.0) + pundit-matchers (~> 1.6) + pusher (~> 1.3) + pusher-push-notifications (~> 1.0) + rack-host-redirect (~> 1.3) + rack-timeout (~> 0.5) + rails (~> 5.2, >= 5.2.3) + rails-assets-airbrake-js-client (~> 1.6)! + rails-observers (~> 0.1) + recaptcha (~> 4.13) + redcarpet (~> 3.4) + reverse_markdown (~> 1.1) + rolify (~> 5.2) + rouge (~> 3.3) + rspec-rails (~> 3.8) + rspec-retry (~> 0.6) + rubocop (~> 0.67) + rubocop-performance (~> 1.0) + rubocop-rspec (~> 1.32) + ruby-prof (~> 0.17) + rubyzip (~> 1.2, >= 1.2.2) + s3_direct_upload (~> 0.1) + sail (~> 1.5) + sass-rails (~> 5.0) + scout_apm (~> 2.4) + sdoc (~> 1.0) + serviceworker-rails (~> 0.5) + shoulda-matchers (= 4.0.1) + simplecov (~> 0.16) + sitemap_generator (~> 6.0) + skylight (~> 3.1) + slack-notifier (~> 2.3) + spring (~> 2.0) + spring-commands-rspec (~> 1.0) + sprockets (~> 3.7) + staccato (~> 0.5) + stackprof (~> 0.2) + storext (~> 2.2) + stripe (~> 4.8) + stripe-ruby-mock (~> 2.5) + test-prof (~> 0.7) + timber (~> 2.6) + timecop (~> 0.9) + twilio-ruby (~> 5.22) + twitter (~> 6.2) + uglifier (~> 4.1) + validate_url (~> 1.0) + vcr (~> 4.0) + web-console (~> 3.7) + webdrivers (~> 3.7) + webmock (~> 3.5) + webpacker (~> 3.5) + webpush (~> 0.3) + zonebie (~> 0.6.1) + +RUBY VERSION + ruby 2.6.1p33 + +BUNDLED WITH + 1.17.3 diff --git a/dev.to/Procfile.falcon b/dev.to/Procfile.falcon new file mode 100644 index 00000000..2b6aeffb --- /dev/null +++ b/dev.to/Procfile.falcon @@ -0,0 +1,2 @@ +web: bundle exec falcon serve --port 3000 --bind https://0.0.0.0 --hostname localhost.dev +worker: bundle exec rake jobs:work diff --git a/dev.to/Procfile.local b/dev.to/Procfile.local new file mode 100644 index 00000000..bffa2bf4 --- /dev/null +++ b/dev.to/Procfile.local @@ -0,0 +1,2 @@ +web: bundle exec puma -C config/puma.rb -p 3000 +worker: bundle exec rake jobs:work diff --git a/dev.to/app/controllers/stories_controller.rb b/dev.to/app/controllers/stories_controller.rb new file mode 100644 index 00000000..3ddfb672 --- /dev/null +++ b/dev.to/app/controllers/stories_controller.rb @@ -0,0 +1,272 @@ +class StoriesController < ApplicationController + before_action :authenticate_user!, except: %i[index search show] + before_action :set_cache_control_headers, only: %i[index search show] + before_action :define_push_headers, only: :index + + def index + return handle_user_or_organization_or_podcast_index if params[:username] + return handle_tag_index if params[:tag] + + handle_base_index + end + + def search + @query = "...searching" + @article_index = true + set_surrogate_key_header "articles-page-with-query" + render template: "articles/search" + end + + def show + @story_show = true + if (@article = Article.find_by(path: "/#{params[:username].downcase}/#{params[:slug]}")&.decorate) + handle_article_show + elsif (@article = Article.find_by(slug: params[:slug])&.decorate) + handle_possible_redirect + else + @podcast = Podcast.find_by(slug: params[:username]) || not_found + @episode = PodcastEpisode.find_by(slug: params[:slug]) || not_found + handle_podcast_show + end + end + + def warm_comments + @article = Article.find_by(path: "/#{params[:username].downcase}/#{params[:slug]}")&.decorate || not_found + @warm_only = true + assign_article_show_variables + render partial: "articles/full_comment_area" + end + + private + + def define_push_headers + push_headers = [ + "<#{view_context.asset_path('bell.svg')}>; rel=preload; as=image", + "<#{view_context.asset_path('menu.svg')}>; rel=preload; as=image", + "<#{view_context.asset_path('connect.svg')}>; rel=preload; as=image", + "<#{view_context.asset_path('stack.svg')}>; rel=preload; as=image", + "<#{view_context.asset_path('lightning.svg')}>; rel=preload; as=image", + ] + + response.headers['Link'] = push_headers.join(', ') + end + + def redirect_to_changed_username_profile + potential_username = params[:username].tr("@", "").downcase + user_or_org = User.find_by("old_username = ? OR old_old_username = ?", potential_username, potential_username) || + Organization.find_by("old_slug = ? OR old_old_slug = ?", potential_username, potential_username) + if user_or_org.present? + redirect_to user_or_org.path + return + else + not_found + end + end + + def handle_possible_redirect + potential_username = params[:username].tr("@", "").downcase + @user = User.find_by("old_username = ? OR old_old_username = ?", potential_username, potential_username) + if @user&.articles&.find_by(slug: params[:slug]) + redirect_to "/#{@user.username}/#{params[:slug]}" + return + elsif (@organization = @article.organization) + redirect_to "/#{@organization.slug}/#{params[:slug]}" + return + end + not_found + end + + def handle_user_or_organization_or_podcast_index + @podcast = Podcast.find_by(slug: params[:username].downcase) + @organization = Organization.find_by(slug: params[:username].downcase) + if @podcast + handle_podcast_index + elsif @organization + handle_organization_index + else + handle_user_index + end + end + + def handle_tag_index + @tag = params[:tag].downcase + @page = (params[:page] || 1).to_i + @tag_model = Tag.find_by(name: @tag) || not_found + @moderators = User.with_role(:tag_moderator, @tag_model) + if @tag_model.alias_for.present? + redirect_to "/t/#{@tag_model.alias_for}" + return + end + + @stories = article_finder(8) + + @stories = @stories.where(approved: true) if @tag_model&.requires_approval + + @stories = stories_by_timeframe + @stories = @stories.decorate + + @article_index = true + set_surrogate_key_header "articles-#{@tag}" + response.headers["Surrogate-Control"] = "max-age=600, stale-while-revalidate=30, stale-if-error=86400" + render template: "articles/tag_index" + end + + def handle_base_index + @home_page = true + @page = (params[:page] || 1).to_i + num_articles = 35 + @stories = article_finder(num_articles) + if %w[week month year infinity].include?(params[:timeframe]) + @stories = @stories.where("published_at > ?", Timeframer.new(params[:timeframe]).datetime). + order("score DESC") + elsif params[:timeframe] == "latest" + @stories = @stories.order("published_at DESC"). + where("featured_number > ? AND score > ?", 1_449_999_999, -40) + @featured_story = Article.new + else + @default_home_feed = true + @stories = @stories. + where("score > ? OR featured = ?", 9, true). + order("hotness_score DESC") + if user_signed_in? + offset = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 3, 3, 4, 5, 6, 7, 8, 9].sample # random offset, weighted more towards zero + @stories = @stories.offset(offset) + end + end + assign_podcasts + @article_index = true + set_surrogate_key_header "main_app_home_page" + response.headers["Surrogate-Control"] = "max-age=600, stale-while-revalidate=30, stale-if-error=86400" + render template: "articles/index" + end + + def handle_podcast_index + @podcast_index = true + @article_index = true + @list_of = "podcast-episodes" + @podcast_episodes = @podcast.podcast_episodes.order("published_at DESC").limit(30) + set_surrogate_key_header "podcast_episodes" + render template: "podcast_episodes/index" + end + + def handle_organization_index + @user = @organization + @stories = ArticleDecorator.decorate_collection(@organization.articles.published. + limited_column_select. + includes(:user). + order("published_at DESC").page(@page).per(8)) + @article_index = true + @organization_article_index = true + set_surrogate_key_header "articles-org-#{@organization.id}" + render template: "organizations/show" + end + + def handle_user_index + @user = User.find_by(username: params[:username].tr("@", "").downcase) + unless @user + redirect_to_changed_username_profile + return + end + assign_user_comments + @stories = ArticleDecorator.decorate_collection(@user.articles.published. + limited_column_select. + order("published_at DESC").page(@page).per(user_signed_in? ? 2 : 5)) + @article_index = true + @list_of = "articles" + redirect_if_view_param + return if performed? + + set_surrogate_key_header "articles-user-#{@user.id}" + render template: "users/show" + end + + def handle_podcast_show + set_surrogate_key_header @episode.record_key + @podcast_episode_show = true + @comments_to_show_count = 25 + @comment = Comment.new + render template: "podcast_episodes/show" + nil + end + + def redirect_if_view_param + redirect_to "/internal/users/#{@user.id}" if params[:view] == "moderate" + redirect_to "/admin/users/#{@user.id}/edit" if params[:view] == "admin" + end + + def redirect_if_show_view_param + redirect_to "/internal/articles/#{@article.id}" if params[:view] == "moderate" + end + + def handle_article_show + assign_article_show_variables + set_surrogate_key_header @article.record_key + redirect_if_show_view_param + return if performed? + + render template: "articles/show" + end + + def assign_article_show_variables + @article_show = true + @variant_number = params[:variant_version] || (user_signed_in? ? 0 : rand(2)) + assign_user_and_org + @comments_to_show_count = @article.cached_tag_list_array.include?("discuss") ? 50 : 30 + assign_second_and_third_user + not_found if permission_denied? + @comment = Comment.new(body_markdown: @article&.comment_template) + end + + def permission_denied? + !@article.published && params[:preview] != @article.password + end + + def assign_user_and_org + @user = @article.user || not_found + @organization = @article.organization if @article.organization_id.present? + end + + def assign_second_and_third_user + return if @article.second_user_id.blank? + + @second_user = User.find(@article.second_user_id) + @third_user = User.find(@article.third_user_id) if @article.third_user_id.present? + end + + def assign_user_comments + comment_count = params[:view] == "comments" ? 250 : 8 + @comments = if @user.comments_count.positive? + @user.comments.where(deleted: false). + order("created_at DESC").includes(:commentable).limit(comment_count) + else + [] + end + end + + def stories_by_timeframe + if %w[week month year infinity].include?(params[:timeframe]) + @stories.where("published_at > ?", Timeframer.new(params[:timeframe]).datetime). + order("positive_reactions_count DESC") + elsif params[:timeframe] == "latest" + @stories.where("score > ?", -40).order("published_at DESC") + else + @stories.order("hotness_score DESC") + end + end + + def assign_podcasts + return unless user_signed_in? + + @podcast_episodes = PodcastEpisode. + includes(:podcast). + order("published_at desc"). + select(:slug, :title, :podcast_id).limit(5) + end + + def article_finder(num_articles) + tag = params[:tag] + articles = Article.published.includes(:user).limited_column_select.page(@page).per(num_articles) + articles = articles.cached_tagged_with(tag) if tag.present? # More efficient than tagged_with + articles + end +end diff --git a/dev.to/app/helpers/application_helper.rb b/dev.to/app/helpers/application_helper.rb new file mode 100644 index 00000000..e429a1be --- /dev/null +++ b/dev.to/app/helpers/application_helper.rb @@ -0,0 +1,214 @@ +module ApplicationHelper + def user_logged_in_status + user_signed_in? ? "logged-in" : "logged-out" + end + + def current_page + "#{controller_name}-#{controller.action_name}" + end + + def view_class + "#{controller_name} #{controller_name}-#{controller.action_name}" + end + + def core_pages? + %w[ + articles + podcast_episodes + events + tags + registrations + users + pages + chat_channels + dashboards + moderations + videos + badges + stories + comments + notifications + reading_list_items + html_variants + ].include?(controller_name) + end + + def render_js? + article_pages = controller_name == "articles" && %(index show).include?(controller.action_name) + pulses_pages = controller_name == "pulses" + !(article_pages || pulses_pages) + end + + def title(page_title) + derived_title = if page_title.include?(ApplicationConfig["COMMUNITY_NAME"]) + page_title + else + page_title + " - #{ApplicationConfig['COMMUNITY_NAME']} Community 👩‍💻👨‍💻" + end + content_for(:title) { derived_title } + derived_title + end + + def title_with_timeframe(page_title:, timeframe:, content_for: false) + if timeframe.blank? + return content_for ? title(page_title) : page_title + end + + sub_titles = { + "week" => "Top posts this week", + "month" => "Top posts this month", + "year" => "Top posts this year", + "infinity" => "All posts", + "latest" => "Latest posts" + } + + title_text = "#{page_title} - #{sub_titles.fetch(timeframe)}" + content_for ? title(title_text) : title_text + end + + def icon(name, pixels = "20") + image_tag icon_url(name), alt: name, class: "icon-img", height: pixels, width: pixels + end + + def icon_url(name) + postfix = { + "twitter" => "v1456342401/twitter-logo-silhouette_1_letrqc.png", + "github" => "v1456342401/github-logo_m841aq.png", + "link" => "v1456342401/link-symbol_apfbll.png", + "volume" => "v1461589297/technology_1_aefet2.png", + "volume-mute" => "v1461589297/technology_jiugwb.png" + }.fetch(name, "v1456342953/star-in-black-of-five-points-shape_sor40l.png") + + "https://res.cloudinary.com/practicaldev/image/upload/#{postfix}" + end + + def cloudinary(url, width = nil, _quality = 80, _format = "jpg") + return url if ( Rails.env.development? || Rails.env.local_production? ) && (url.blank? || url.exclude?("http")) + + service_path = "https://res.cloudinary.com/practicaldev/image/fetch" + + if url&.size&.positive? + if width + "#{service_path}/c_scale,fl_progressive,q_auto,w_#{width}/f_auto/#{url}" + else + "#{service_path}/c_scale,fl_progressive,q_auto/f_auto/#{url}" + end + else + "#{service_path}/c_scale,fl_progressive,q_1/f_auto/https://pbs.twimg.com/profile_images/481625927911092224/iAVNQXjn_normal.jpeg" + end + end + + def cloud_cover_url(url) + return if url.blank? + return asset_path("triple-unicorn") if Rails.env.test? + return url if Rails.env.development?|| Rails.env.local_production? + + width = 1000 + height = 420 + quality = "auto" + + cl_image_path(url, + type: "fetch", + width: width, + height: height, + crop: "imagga_scale", + quality: quality, + flags: "progressive", + fetch_format: "auto", + sign_url: true) + end + + def cloud_social_image(article) + cache_key = "article-social-img-#{article}-#{article.updated_at}-#{article.comments_count}" + + Rails.cache.fetch(cache_key, expires_in: 1.hour) do + src = GeneratedImage.new(article).social_image + return src if src.start_with? "https://res.cloudinary.com/" + + cl_image_path(src, + type: "fetch", + width: "1000", + height: "500", + crop: "imagga_scale", + quality: "auto", + flags: "progressive", + fetch_format: "auto", + sign_url: true) + end + end + + def tag_colors(tag) + Rails.cache.fetch("view-helper-#{tag}/tag_colors", expires_in: 5.hours) do + if (found_tag = Tag.select(%i[bg_color_hex text_color_hex]).find_by(name: tag)) + { background: found_tag.bg_color_hex, color: found_tag.text_color_hex } + else + { background: "#d6d9e0", color: "#606570" } + end + end + end + + def beautified_url(url) + url.sub(/^((http[s]?|ftp):\/)?\//, "").sub(/\?.*/, "").chomp("/") + rescue StandardError + url + end + + def org_bg_or_white(org) + org&.bg_color_hex ? org.bg_color_hex : "#ffffff" + end + + def sanitize_rendered_markdown(processed_html) + ActionController::Base.helpers.sanitize processed_html.html_safe, + scrubber: RenderedMarkdownScrubber.new + end + + def sanitized_sidebar(text) + ActionController::Base.helpers.sanitize simple_format(text), + tags: %w[p b i em strike strong u br] + end + + def track_split_version(url, version) + "trackOutboundLink('#{url}','#{version}'); return false;" + end + + def follow_button(followable, style = "full") + tag :button, # Yikes + class: "cta follow-action-button", + data: { + info: { id: followable.id, className: followable.class.name, style: style }.to_json, + "follow-action-button" => true + } + end + + def user_colors_style(user) + "border: 2px solid #{user.decorate.darker_color}; \ + box-shadow: 5px 6px 0px #{user.decorate.darker_color}" + end + + def user_colors(user) + user.decorate.enriched_colors + end + + def timeframe_check(given_timeframe) + params[:timeframe] == given_timeframe + end + + def list_path + return "" if params[:tag].blank? + + "/t/#{params[:tag]}" + end + + def logo_svg + logo = if ApplicationConfig["LOGO_SVG"].present? + ApplicationConfig["LOGO_SVG"].html_safe + else + inline_svg("devplain.svg", class: "logo", size: "20% * 20%") + end + logo + end + + def community_qualified_name + "The #{ApplicationConfig['COMMUNITY_NAME']} Community" + end +end diff --git a/dev.to/app/views/layouts/_top_bar.html.erb b/dev.to/app/views/layouts/_top_bar.html.erb new file mode 100644 index 00000000..cbf76219 --- /dev/null +++ b/dev.to/app/views/layouts/_top_bar.html.erb @@ -0,0 +1,40 @@ + +
+ +
diff --git a/dev.to/bin/startup b/dev.to/bin/startup new file mode 100755 index 00000000..45e06510 --- /dev/null +++ b/dev.to/bin/startup @@ -0,0 +1,20 @@ +#!/usr/bin/env ruby +require "pathname" +require "fileutils" +include FileUtils + +# path to your application root. +APP_ROOT = Pathname.new File.expand_path("..", __dir__) + +def system!(*args) + system(*args) || abort("\n== Command #{args} failed ==") +end + +procfile = 'Procfile.falcon' if ENV['RACK_HANDLER'] +procfile ||= + Rails.env.development? ? 'Procfile.dev' : 'Procfile.local' + +chdir APP_ROOT do + puts "== STARTING UP ==" + system! "bundle exec foreman start -f #{procfile}" +end diff --git a/dev.to/config/environments/local_production.rb b/dev.to/config/environments/local_production.rb new file mode 100644 index 00000000..5e53349d --- /dev/null +++ b/dev.to/config/environments/local_production.rb @@ -0,0 +1,119 @@ +Rails.application.configure do + # Verifies that versions and hashed value of the package contents in the project's package.json + config.webpacker.check_yarn_integrity = false + + # Settings specified here will take precedence over those in config/application.rb. + + # Code is not reloaded between requests. + config.cache_classes = true + + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. + config.consider_all_requests_local = false + config.action_controller.perform_caching = true + + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like + # NGINX, varnish or squid. + # config.action_dispatch.rack_cache = true + config.read_encrypted_secrets = true + + # Disable serving static files from the `/public` folder by default since + # Apache or NGINX already handles this. + config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"].present? + config.public_file_server.headers = { + "Cache-Control" => "public, s-maxage=2592000, max-age=86400" + } + + # Compress JavaScripts and CSS. + config.assets.js_compressor = Uglifier.new(harmony: true) + # config.assets.css_compressor = :sass + + # Do not fallback to assets pipeline if a precompiled asset is missed. + config.assets.compile = true + + # Asset digests allow you to set far-future HTTP expiration dates on all assets, + # yet still be able to expire them through the digest params. + config.assets.digest = true + + # `config.assets.precompile` and `config.assets.version` + # have moved to config/initializers/assets.rb + + # Specifies the header that your server uses for sending files. + # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache + # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX + + # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. + if ENV['ENABLE_HTTPS'].present? + config.force_ssl = true + config.ssl_options = { hsts: { expires: 3600 } } + end + + # Use the lowest log level to ensure availability of diagnostic information + # when problems arise. + config.log_level = :debug + + # Prepend all log lines with the following tags. + config.log_tags = [:request_id] + + # Use a different logger for distributed setups. + # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) + + # Use a different cache store in production. + # config.cache_store = :mem_cache_store + + # Enable serving of images, stylesheets, and JavaScripts from an asset server. + config.action_mailer.perform_caching = false + + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + # config.action_mailer.raise_delivery_errors = false + + # Enable locale fallbacks for I18n (makes lookups for any locale fall back to + # the I18n.default_locale when a translation cannot be found). + config.i18n.fallbacks = [I18n.default_locale] + + # Send deprecation notices to registered listeners. + config.active_support.deprecation = :notify + + # Use default logging formatter so that PID and timestamp are not suppressed. + # config.log_formatter = ::Logger::Formatter.new + config.log_formatter = ::Logger::Formatter.new + if ENV["RAILS_LOG_TO_STDOUT"].present? + logger = ActiveSupport::Logger.new(STDOUT) + logger.formatter = config.log_formatter + config.logger = ActiveSupport::TaggedLogging.new(logger) + end + + # Do not dump schema after migrations. + config.active_record.dump_schema_after_migration = false + + config.cache_store = :dalli_store, + (ENV["MEMCACHIER_SERVERS"] || "").split(","), + { username: ENV["MEMCACHIER_USERNAME"], + password: ENV["MEMCACHIER_PASSWORD"], + failover: true, + socket_timeout: 1.5, + socket_failure_delay: 0.2 } + + config.app_domain = ENV["APP_DOMAIN"] + + config.action_mailer.delivery_method = :smtp + config.action_mailer.perform_deliveries = true + config.action_mailer.default_url_options = { host: ENV["APP_PROTOCOL"] + ENV["APP_DOMAIN"] } + ActionMailer::Base.smtp_settings = { + address: "smtp.sendgrid.net", + port: "587", + authentication: :plain, + user_name: ENV["SENDGRID_USERNAME_ACCEL"], + password: ENV["SENDGRID_PASSWORD_ACCEL"], + domain: "dev.to", + enable_starttls_auto: true + } +end diff --git a/dev.to/config/initializers/airbrake.rb b/dev.to/config/initializers/airbrake.rb new file mode 100644 index 00000000..e9e23910 --- /dev/null +++ b/dev.to/config/initializers/airbrake.rb @@ -0,0 +1,63 @@ +return if Rails.env.local_production? + +# Airbrake is an online tool that provides robust exception tracking in your Rails +# applications. In doing so, it allows you to easily review errors, tie an error +# to an individual piece of code, and trace the cause back to recent +# changes. Airbrake enables for easy categorization, searching, and prioritization +# of exceptions so that when errors occur, your team can quickly determine the +# root cause. +# +# Configuration details: +# https://github.com/airbrake/airbrake-ruby#configuration +Airbrake.configure do |c| + # You must set both project_id & project_key. To find your project_id and + # project_key navigate to your project's General Settings and copy the values + # from the right sidebar. + # https://github.com/airbrake/airbrake-ruby#project_id--project_key + c.project_id = ApplicationConfig["AIRBRAKE_PROJECT_ID"] + c.project_key = ApplicationConfig["AIRBRAKE_API_KEY"] + + # Configures the root directory of your project. Expects a String or a + # Pathname, which represents the path to your project. Providing this option + # helps us to filter out repetitive data from backtrace frames and link to + # GitHub files from our dashboard. + # https://github.com/airbrake/airbrake-ruby#root_directory + c.root_directory = Rails.root + + # By default, Airbrake Ruby outputs to STDOUT. In Rails apps it makes sense to + # use the Rails' logger. + # https://github.com/airbrake/airbrake-ruby#logger + c.logger = Rails.logger + + # Configures the environment the application is running in. Helps the Airbrake + # dashboard to distinguish between exceptions occurring in different + # environments. By default, it's not set. + # NOTE: This option must be set in order to make the 'ignore_environments' + # option work. + # https://github.com/airbrake/airbrake-ruby#environment + c.environment = Rails.env + + # Setting this option allows Airbrake to filter exceptions occurring in + # unwanted environments such as :test. By default, it is equal to an empty + # Array, which means Airbrake Ruby sends exceptions occurring in all + # environments. + # NOTE: This option *does not* work if you don't set the 'environment' option. + # https://github.com/airbrake/airbrake-ruby#ignore_environments + c.ignore_environments = %w[test development] + + # A list of parameters that should be filtered out of what is sent to + # Airbrake. By default, all "password" attributes will have their contents + # replaced. + # https://github.com/airbrake/airbrake-ruby#blacklist_keys + c.blacklist_keys = [/password/i] +end + +# If Airbrake doesn't send any expected exceptions, we suggest to uncomment the +# line below. It might simplify debugging of background Airbrake workers, which +# can silently die. +# Thread.abort_on_exception = ['test', 'development'].include?(Rails.env) + +Airbrake.add_filter do |notice| + notice.ignore! if notice[:errors].any? { |error| error[:type] == "Pundit::NotAuthorizedError" } + notice.ignore! if notice[:errors].any? { |error| error[:type] == "ActiveRecord::RecordNotFound" } +end diff --git a/dev.to/config/initializers/carrierwave.rb b/dev.to/config/initializers/carrierwave.rb new file mode 100644 index 00000000..31983c62 --- /dev/null +++ b/dev.to/config/initializers/carrierwave.rb @@ -0,0 +1,19 @@ +require "carrierwave/storage/abstract" +require "carrierwave/storage/file" +require "carrierwave/storage/fog" + +CarrierWave.configure do |config| + if Rails.env.development? || Rails.env.test? || Rails.env.local_production? + config.storage = :file + else + # config.fog_provider = 'fog-aws' + config.storage = :fog + config.fog_credentials = { + provider: "AWS", + aws_access_key_id: ApplicationConfig["AWS_ID"], + aws_secret_access_key: ApplicationConfig["AWS_SECRET"], + region: "us-east-1" + } + config.fog_directory = ApplicationConfig["AWS_BUCKET_NAME"] + end +end diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 00000000..48703320 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,88 @@ +version: "3.7" + +services: + app: + build: + context: . + stdin_open: true + tty: true + working_dir: /usr/src/app + networks: + courses-network: + aliases: + - app + ports: + - 127.0.1.1:3000:3000 + volumes: + - ./dev.to:/usr/src/app + - rubies:/opt/rubies + depends_on: + - db + - cache + - arc + environment: + RAILS_ENV: "local_production" + DATABASE_URL: "postgres://postgres:password@db/" + MEMCACHIER_SERVERS: "cache" + ENABLE_HTTPS: "true" + RAILS_SERVE_STATIC_FILES: "true" + RAILS_LOG_TO_STDOUT: "true" + RACK_HANDLER: "falcon" + entrypoint: /docker-entrypoint.sh + command: "bin/startup" + + web: + image: nginx:1.15.12-alpine + depends_on: + - app + networks: + courses-network: + aliases: + - web + volumes: + - ./.docker/certs:/etc/nginx/certs + - ./.docker/ssl.conf:/etc/nginx/conf.d/ssl.conf + ports: + - 127.0.1.1:3443:443 + + db: + image: postgres:10-alpine + networks: + courses-network: + aliases: + - db + + cache: + image: memcached:1.5.12-alpine + networks: + courses-network: + aliases: + - cache + + arc: + image: alpine:latest + volumes: + - rubies:/cont/rubies + - .docker:/host/docker + networks: + - courses-network + working_dir: /cont + entrypoint: tar -czf /host/docker/current.tgz rubies/ + + pghero: + image: ankane/pghero + depends_on: + - db + ports: + - 127.0.1.1:8080:8080 + environment: + DATABASE_URL: "postgres://postgres:password@db/task-4_development" + networks: + - courses-network + +networks: + courses-network: + name: courses-network + +volumes: + rubies: diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 00000000..28a3d0c0 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,6 @@ +#! /bin/bash -l + +chruby + +# Execute the given or default command: +exec "$@" \ No newline at end of file diff --git a/graphs/development.png b/graphs/development.png new file mode 100644 index 00000000..3cce014f Binary files /dev/null and b/graphs/development.png differ diff --git a/graphs/local_production_ssl_nginx.png b/graphs/local_production_ssl_nginx.png new file mode 100644 index 00000000..1b85ac8c Binary files /dev/null and b/graphs/local_production_ssl_nginx.png differ diff --git a/screenshots/server_push.png b/screenshots/server_push.png new file mode 100644 index 00000000..2fb2206c Binary files /dev/null and b/screenshots/server_push.png differ