├── .env.sample ├── .gitignore ├── .rubocop.yml ├── .travis.yml ├── CONTRIBUTING.md ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Procfile ├── Procfile.dev ├── README.md ├── Rakefile ├── app ├── assets │ ├── config │ │ └── manifest.js │ ├── fonts │ │ └── font-awesome │ │ │ ├── fontawesome-webfont.eot │ │ │ ├── fontawesome-webfont.svg │ │ │ ├── fontawesome-webfont.ttf │ │ │ ├── fontawesome-webfont.woff │ │ │ └── fontawesome-webfont.woff2 │ ├── images │ │ ├── .keep │ │ ├── announcing-next.png │ │ ├── badges │ │ │ ├── 1000lemming.png │ │ │ ├── 100lemming.png │ │ │ ├── 24-continuous-sync.png │ │ │ ├── 24-participant.png │ │ │ ├── altrustic.png │ │ │ ├── bear.png │ │ │ ├── bear3.png │ │ │ ├── beaver-old.png │ │ │ ├── beaver.png │ │ │ ├── beaver3.png │ │ │ ├── changelogd.png │ │ │ ├── charity.png │ │ │ ├── coffee.png │ │ │ ├── comingsoon.png │ │ │ ├── cub.png │ │ │ ├── desertlocust.png │ │ │ ├── desertlocust3.png │ │ │ ├── earlyadopter.png │ │ │ ├── entrepreneur.png │ │ │ ├── epidexipteryx.png │ │ │ ├── epidexipteryx3.png │ │ │ ├── forked.png │ │ │ ├── forked1.png │ │ │ ├── forked100.png │ │ │ ├── forked20.png │ │ │ ├── forked50.png │ │ │ ├── general-skills.png │ │ │ ├── github-gameoff-honorable-mention-2012.png │ │ │ ├── github-gameoff-judge-2012.png │ │ │ ├── github-gameoff-participant-2012.png │ │ │ ├── github-gameoff-runner-up-2012.png │ │ │ ├── github-gameoff-winner-2012.png │ │ │ ├── go.png │ │ │ ├── go3.png │ │ │ ├── goruco.png │ │ │ ├── hackathon.png │ │ │ ├── hackathonCMU.png │ │ │ ├── hackathonStanford.png │ │ │ ├── honeybadger-brood.png │ │ │ ├── honeybadger-brood2.png │ │ │ ├── honeybadger-ko.png │ │ │ ├── honeybadger-ko3.png │ │ │ ├── honeybadger.png │ │ │ ├── honeybadger3.png │ │ │ ├── ko-best-design-2011.png │ │ │ ├── ko-best-design-2012.png │ │ │ ├── ko-champion-2011.png │ │ │ ├── ko-champion-2012.png │ │ │ ├── ko-contender-2011.png │ │ │ ├── ko-contender-2012.png │ │ │ ├── ko-judge-2011.png │ │ │ ├── ko-judge-2012.png │ │ │ ├── ko-most-complete-2011.png │ │ │ ├── ko-most-complete-2012.png │ │ │ ├── ko-most-innovative-2011.png │ │ │ ├── ko-most-innovative-2012.png │ │ │ ├── ko-most-useful-2011.png │ │ │ ├── ko-most-useful-2012.png │ │ │ ├── ko-most-votes-2011.png │ │ │ ├── ko-most-votes-2012.png │ │ │ ├── komododragon.png │ │ │ ├── komododragon3.png │ │ │ ├── labrador.png │ │ │ ├── labrador3.png │ │ │ ├── locked.png │ │ │ ├── mayor.png │ │ │ ├── mongoose.png │ │ │ ├── mongoose3.png │ │ │ ├── moongoose-rails.png │ │ │ ├── more.png │ │ │ ├── more2.png │ │ │ ├── narwhal.png │ │ │ ├── narwhal3.png │ │ │ ├── neo4j-challenge.png │ │ │ ├── neo4j-winner.png │ │ │ ├── neo4j.png │ │ │ ├── nephilakomaci.png │ │ │ ├── nephilakomaci3.png │ │ │ ├── octopussy.png │ │ │ ├── philanthropist.png │ │ │ ├── platypus.png │ │ │ ├── platypus3.png │ │ │ ├── python.png │ │ │ ├── python3.png │ │ │ ├── railsberry.png │ │ │ ├── railscamp.png │ │ │ ├── raven.png │ │ │ ├── screencapture-achievements.png │ │ │ ├── social-icons.png │ │ │ ├── trex.png │ │ │ ├── trex3.png │ │ │ ├── velociraptor.png │ │ │ ├── velociraptor3.png │ │ │ ├── walrus.png │ │ │ └── wrocloverb.png │ │ ├── conference-room.png │ │ ├── happy-cat.jpg │ │ ├── live-banner.jpg │ │ ├── live-banner.png │ │ ├── offline-holder.png │ │ └── pop.mp3 │ ├── javascripts │ │ ├── analytics.js.coffee │ │ ├── application_non_webpack.js.coffee │ │ ├── application_static.js │ │ ├── bsa.js.coffee │ │ └── textarea_with_file_drop_support.js.coffee │ └── stylesheets │ │ ├── application_non_webpack.scss │ │ ├── application_static.scss │ │ ├── basscss.scss │ │ ├── basscss │ │ ├── _align.scss │ │ ├── _background-colors.scss │ │ ├── _background-images.scss │ │ ├── _base-forms.scss │ │ ├── _base-reset.scss │ │ ├── _base-tables.scss │ │ ├── _base-typography.scss │ │ ├── _border-colors.scss │ │ ├── _borders.scss │ │ ├── _btn-outline.scss │ │ ├── _btn-primary.scss │ │ ├── _btn-sizes.scss │ │ ├── _btn.scss │ │ ├── _color-base.scss │ │ ├── _color-forms-dark.scss │ │ ├── _color-forms.scss │ │ ├── _color-input-range.scss │ │ ├── _color-progress.scss │ │ ├── _color-tables.scss │ │ ├── _colors.scss │ │ ├── _defaults.scss │ │ ├── _flex-object.scss │ │ ├── _grid.scss │ │ ├── _highlight.scss │ │ ├── _input-range.scss │ │ ├── _positions.scss │ │ ├── _progress.scss │ │ ├── _responsive-states.scss │ │ ├── _responsive-white-space.scss │ │ ├── _table-object.scss │ │ ├── _type-scale.scss │ │ ├── _ui-utility-groups.scss │ │ ├── _utility-headings.scss │ │ ├── _utility-layout.scss │ │ ├── _utility-typography.scss │ │ └── _white-space.scss │ │ ├── content.scss │ │ ├── dropdown.scss │ │ ├── font-awesome.scss │ │ ├── font-awesome │ │ ├── _animated.scss │ │ ├── _bordered-pulled.scss │ │ ├── _core.scss │ │ ├── _extras.scss │ │ ├── _fixed-width.scss │ │ ├── _icons.scss │ │ ├── _larger.scss │ │ ├── _list.scss │ │ ├── _mixins.scss │ │ ├── _path.scss │ │ ├── _rotated-flipped.scss │ │ ├── _stacked.scss │ │ └── _variables.scss │ │ ├── minimal.scss │ │ └── twilight_theme.scss ├── controllers │ ├── application_controller.rb │ ├── application_record.rb │ ├── badges_controller.rb │ ├── comments_controller.rb │ ├── concerns │ │ └── .keep │ ├── hooks_controller.rb │ ├── job_subscriptions_controller.rb │ ├── jobs_controller.rb │ ├── likes_controller.rb │ ├── pages_controller.rb │ ├── pictures_controller.rb │ ├── protips_controller.rb │ ├── sponsors_controller.rb │ ├── subscribers_controller.rb │ ├── teams_controller.rb │ └── users_controller.rb ├── helpers │ ├── application_helper.rb │ ├── font_awesome_helper.rb │ ├── jobs_helper.rb │ ├── likes_helper.rb │ ├── protips_helper.rb │ └── users_helper.rb ├── lib │ ├── avatar_uploader.rb │ ├── cloudfront_constraint.rb │ ├── coderwall_flavored_markdown.rb │ ├── legacy_badges.rb │ ├── picture_uploader.rb │ └── spaminator.rb ├── mailers │ ├── application_mailer.rb │ ├── comment_mailer.rb │ └── user_mailer.rb ├── models │ ├── .keep │ ├── article.rb │ ├── badge.rb │ ├── category.rb │ ├── comment.rb │ ├── concerns │ │ ├── .keep │ │ ├── time_ago_in_words_cache_buster.rb │ │ └── view_count_cache_buster.rb │ ├── github.rb │ ├── job.rb │ ├── job_subscription.rb │ ├── like.rb │ ├── picture.rb │ ├── protip.rb │ ├── secure_reply_to.rb │ ├── slack.rb │ ├── sponsor.rb │ ├── team.rb │ └── user.rb ├── serializers │ ├── badge_serializer.rb │ ├── comment_serializer.rb │ ├── current_user_serializer.rb │ ├── job_serializer.rb │ ├── picture_serializer.rb │ ├── protip_serializer.rb │ └── user_serializer.rb ├── services │ └── notification.rb └── views │ ├── badges │ └── _badge.html.haml │ ├── clearance_mailer │ ├── change_password.html.erb │ └── change_password.text.erb │ ├── comment_mailer │ └── new_comment.html.erb │ ├── comments │ ├── _comment.html.haml │ ├── _comment.json.jbuilder │ ├── index.html.haml │ ├── index.json.jbuilder │ └── show.js.erb │ ├── job_subscriptions │ └── new.html.haml │ ├── jobs │ ├── _job.html.haml │ ├── _mini.html.haml │ ├── index.html.haml │ └── new.html.haml │ ├── layouts │ ├── application.html.haml │ └── minimal.html.haml │ ├── pages │ ├── faq.html.haml │ ├── not_found.html.haml │ ├── privacy.html.haml │ ├── server_error.html.haml │ ├── styleguide.html.erb │ └── tos.html.haml │ ├── passwords │ ├── create.html.haml │ ├── edit.html.haml │ └── new.html.haml │ ├── protips │ ├── _protip.html.haml │ ├── home.html.haml │ ├── index.html.haml │ ├── new.html.haml │ └── show.html.haml │ ├── sessions │ └── new.html.haml │ ├── shared │ ├── _analytics.html.erb │ ├── _header.html.haml │ ├── _logo.html.erb │ └── _tracking.html.erb │ ├── teams │ └── show.html.haml │ ├── user_mailer │ ├── destroy_email.text.erb │ └── partnership_expired.text.erb │ └── users │ ├── edit.html.haml │ ├── new.html.haml │ └── show.html.haml ├── bin ├── bundle ├── rails ├── rake ├── setup ├── spring └── update ├── client ├── .babelrc ├── .eslintrc ├── actions │ ├── heartActions.js │ ├── jobActions.js │ └── protipActions.js ├── checkLinks.js ├── components │ ├── Heart.jsx │ ├── HeartButton.jsx │ ├── Icon.jsx │ ├── JobForm.jsx │ ├── NewJob.jsx │ ├── NewJobSubscription.jsx │ ├── ProtipSubscribeButton.jsx │ ├── Sponsors.jsx │ ├── ToggleWithLabel.jsx │ ├── TrackClick.jsx │ └── __tests__ │ │ ├── JobForm-test.jsx │ │ └── __snapshots__ │ │ └── JobForm-test.jsx.snap ├── lib │ ├── apiAuthInjector.js │ ├── createReducer.js │ ├── loadImage.js │ └── unauthorizedHandler.js ├── package.json ├── reducers │ ├── commentsReducer.js │ ├── currentProtipReducer.js │ ├── currentUserReducer.js │ ├── heartsReducer.js │ ├── index.js │ ├── jobReducer.js │ └── protipsReducer.js ├── server-rails-hot.js ├── startup │ ├── clientRegistration.jsx │ ├── confirm.js │ └── serverRegistration.jsx ├── stores │ └── store.js ├── webpack.client.base.config.js ├── webpack.client.rails.build.config.js ├── webpack.client.rails.hot.config.js ├── webpack.server.rails.build.config.js └── yarn.lock ├── config.ru ├── config ├── application.rb ├── boot.rb ├── cable.yml ├── database.yml ├── environment.rb ├── environments │ ├── development.rb │ ├── production.rb │ └── test.rb ├── initializers │ ├── application_controller_renderer.rb │ ├── assets.rb │ ├── backtrace_silencers.rb │ ├── carrierwave.rb │ ├── clearance.rb │ ├── cookies_serializer.rb │ ├── cors.rb │ ├── filter_parameter_logging.rb │ ├── inflections.rb │ ├── invisible_captcha.rb │ ├── meta_tags.rb │ ├── mime_types.rb │ ├── new_framework_defaults.rb │ ├── puma_worker_killer.rb │ ├── rack_mini_profiler.rb │ ├── rack_timeout.rb │ ├── sanitization.rb │ ├── session_store.rb │ ├── stripe.rb │ ├── time_formats.rb │ ├── webpack.rb │ └── wrap_parameters.rb ├── letsencrypt_plugin.yml ├── locales │ ├── clearance.en.yml │ ├── en.yml │ └── invisible_captcha.en.yml ├── puma.rb ├── routes.rb ├── secrets.yml └── spring.rb ├── db ├── migrate │ ├── 20160119204658_create_protips.rb │ ├── 20160119204714_create_users.rb │ ├── 20160120175334_add_clearance_to_users.rb │ ├── 20160120200829_create_comments.rb │ ├── 20160121012718_create_likes.rb │ ├── 20160121013050_create_badges.rb │ ├── 20160210220146_add_tags_and_count_columns.rb │ ├── 20160210221757_add_color_to_user.rb │ ├── 20160210222721_add_karma_to_user.rb │ ├── 20160212233312_add_flagged_to_protip.rb │ ├── 20160213012958_add_likes_cache.rb │ ├── 20160213015810_remove_login_counts.rb │ ├── 20160214213211_clean_up_likes.rb │ ├── 20160219071138_improving_database_performane.rb │ ├── 20160219190140_add_banned_users_for_migration.rb │ ├── 20160223063803_add_more_indexes.rb │ ├── 20160223064301_changing_user_to_citext.rb │ ├── 20160223065728_create_team.rb │ ├── 20160225042520_add_simple_user_id_index_on_like.rb │ ├── 20160225070301_auto_like_content.rb │ ├── 20160225171117_add_pictures.rb │ ├── 20160227000445_add_missing_foreign_keys.rb │ ├── 20160227010236_turn_fields_case_insensitive.rb │ ├── 20160307195201_clean_up_orphan_pictures.rb │ ├── 20160318212558_add_marketing_list_to_users.rb │ ├── 20160422205835_add_view_counts_to_team.rb │ ├── 20160422211004_create_jobs.rb │ ├── 20160422213924_move_job_id_over_to_uuid.rb │ ├── 20160422215652_trim_job.rb │ ├── 20160422234923_add_publish_attributes_to_jobs.rb │ ├── 20160425233554_create_job_views.rb │ ├── 20160513032303_add_stream_key_to_users.rb │ ├── 20160519204919_add_type_to_protips.rb │ ├── 20160519233923_rename_protip_id_on_comments_to_article_id.rb │ ├── 20160601015828_add_started_archived_fields_to_articles.rb │ ├── 20160607202132_add_recording_id_to_protips.rb │ ├── 20160608034824_add_recording_started_at_to_protips.rb │ ├── 20160830184552_create_job_subscriptions.rb │ ├── 20160909044024_add_indexes.rb │ ├── 20160913165240_add_comment_unsubscribed_to_users.rb │ ├── 20160916222644_add_subscribers_to_articles.rb │ ├── 20160919171618_remove_unsubscribed_comment_emails_at_from_users.rb │ ├── 20160922010312_create_letsencrypt_plugin_challenges.letsencrypt_plugin.rb │ ├── 20160922010313_create_letsencrypt_plugin_settings.letsencrypt_plugin.rb │ ├── 20160922185544_remove_job_views.rb │ ├── 20160923195619_add_partner_info.rb │ ├── 20161207222630_change_users_last_ip_from_integer_to_string.rb │ ├── 20170109215300_add_spam_detected_at_to_protips.rb │ ├── 20170110195008_add_spam_fields_to_protips.rb │ ├── 20170220093535_remove_stream_key_from_users.rb │ └── 20170328232725_add_bad_users_and_content.rb ├── schema.rb └── seeds.rb ├── lib └── tasks │ ├── cache.rake │ ├── clean.rake │ ├── contributions.rake │ ├── db.rake │ ├── marketing.rake │ ├── partners.rake │ ├── port.rake │ ├── report.rake │ ├── restore.rake │ ├── spam.rake │ └── tags.rake ├── log └── .keep ├── newrelic.yml ├── package.json ├── public ├── apple-touch-icon-152x152-precomposed.png ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── fav128x128.png ├── fav32x32.png ├── fav64x64.png ├── favicon.ico ├── favicon.png ├── robots.txt ├── touch-icon-ipad-retina.png ├── touch-icon-ipad.png ├── touch-icon-iphone-retina.png └── touch-icon-iphone.png ├── test ├── controllers │ ├── comments_controller_test.rb │ ├── protips_controller_test.rb │ ├── subscribers_controller_test.rb │ └── users_controller_test.rb ├── factories │ ├── comment.rb │ ├── protip.rb │ └── user.rb ├── fixtures │ └── jobs.yml ├── helpers │ └── .keep ├── integration │ └── .keep ├── mailers │ └── comment_mailer_test.rb ├── models │ ├── .keep │ ├── badge_test.rb │ ├── job_test.rb │ └── user_test.rb └── test_helper.rb ├── update-ssl.sh └── vendor └── assets ├── javascripts └── .keep └── stylesheets └── .keep /.env.sample: -------------------------------------------------------------------------------- 1 | AWS_ACCESS_ID= 2 | AWS_ACCESS_SECRET= 3 | AWS_BUCKET= 4 | AWS_REGION= 5 | GOOGLE_ANALYTICS_UA=UA-XXXXXXXX-X 6 | JOB_SUBSCRIPTION_CENTS=49900 7 | JWPLAYER_KEY= 8 | LEGACY_DB_URL=you_only_need_this_to_migrate 9 | LEGACY_REDIS_URL=you_only_need_this_to_migrate 10 | NEW_RELIC_APP_NAME=coderwall (development) 11 | NEW_RELIC_DEVELOPER_MODE=true 12 | NEW_RELIC_ERROR_COLLECTOR_IGNORE_ERRORS=ActiveRecord::RecordNotFound 13 | NEW_RELIC_LICENSE_KEY= 14 | PUSHER_APP_ID= 15 | PUSHER_KEY= 16 | PUSHER_SECRET= 17 | QUICKSTREAM_URL= 18 | REACT_ON_RAILS_ENV=HOT 19 | SLACK_API_TOKEN= 20 | SLACK_WEBHOOK_URL=https://hooks.slack.com/services/XXXX/XXXX/XXXX 21 | BSA_IDENTIFIER= 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore all logfiles and tempfiles. 11 | /log/* 12 | !/log/.keep 13 | /tmp 14 | .env 15 | public/uploads 16 | TODO 17 | info 18 | .DS_Store 19 | .byebug* 20 | coderwall-production.dump 21 | contributions.csv 22 | google.docs.config.json 23 | lib/tasks/recruiters.rake 24 | 25 | node_modules 26 | npm-debug.log 27 | client/node_modules 28 | client/npm-debug.log 29 | /app/assets/javascripts/application.js 30 | /app/assets/webpack/* 31 | server.crt 32 | server.key 33 | capybara-*.html 34 | debug.png 35 | scripts 36 | coderwall.com-* 37 | 38 | public/assets 39 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Rails: 2 | Enabled: true 3 | 4 | Metrics/LineLength: 5 | Max: 120 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.4.0 4 | cache: bundler 5 | sudo: false 6 | addons: 7 | postgresql: '9.3' 8 | apt: 9 | sources: 10 | - ubuntu-toolchain-r-test 11 | packages: 12 | - g++-4.9 13 | 14 | env: 15 | global: 16 | - RAILS_ENV=test 17 | - CXX=g++-4.9 18 | 19 | install: 20 | - bundle install --without production 21 | - nvm install 6.4 22 | - nvm use 6.4 23 | - npm install 24 | - bin/rake db:setup 25 | 26 | script: 27 | - npm test 28 | 29 | notifications: 30 | slack: 31 | template: 32 | - "<%{build_url}|#%{build_number}> (<%{compare_url}|%{commit}>) of %{repository}@%{branch} by %{author} %{result} in %{duration}" 33 | - "%{commit_subject}" 34 | - "${commit_message}" 35 | secure: mpNLTpZPaQ9NmHTAm8uSsPfwL07Esh750yPYWJfCSJzGrNMoz8IDleY8ddPNwTVOLIhhV4rVK7QyF5aAin8+riIlTzJkeLViEL257vl/VY+Th9ryYLdJ1hpa+HaZ8AeDinS5BTdtyjZYClUk+ALKqiFCxe2mm3oODgcSFIPjdhZ40CJKmHAMlj+S2+ypFMYg1Qy9F1xlwb952ZV7PnjwT8kjnzkMmAWtgpEFlTIBJVjBlO4FGh9nCqHda6KT3TjUxMa49Kt8cRBmZCPgkteLciUnOo1rjPeyJX4pjL0pThoCHkHFtFVffw/BxJ0b4WdIc/LKz7iFqJTSF3HChO55lAKhC8bbTaus5kr1AT+McNeC7+hcstjncSIzUEUabcPN2oF/po1SV/A3wR203JsddHRPN3nIGi71izNoT4rFAY+qNeUZS0VeAa2YWNDq46FMLvoCE/+i//HFx/nWYF8D6dLqRWaIeqeJAUeSZHmqey88Ff4SuuybuB3k6ryqWkYS/K+YvrjtuFNZUouscB5vktOjuLiwDTLAVVLQ6ybPBJ2YEj6CpOi2GmazJty9YQcfcYmWlqEf4nBAbcTCRPA/n2306k/26fH4tZygW1g4Zm/BUfGZjrWaHQqA6f4uo10qKVTOktd4vjIJl74SED1vgvoGUbmvOpFKtkQ9RuhBaig= 36 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We welcome ideas and contributions on Coderwall. If you want to contribute something, here's how: 4 | 5 | 1. Check the product [readme][readme] for the latest product direction and how to run the code locally. 6 | 7 | [readme]: https://github.com/coderwall/coderwall-next/compare/ 8 | 9 | 2. For new feature ideas, we recommend creating an issue first that briefly describes the feature you want to add. This gives others involved with Coderwall an opportunity to discuss and provide feedback. 10 | 11 | 3. [Submit a pull request][pr] with your code and design changes. 12 | 13 | [pr]: https://github.com/coderwall/coderwall-next/compare/ 14 | 15 | 16 | 3. Please give us up to a week to review the pull request and comment. We may suggest 17 | some changes or improvements or alternatives. If you don't receive a timely response you can escalate your PR by contacting support@coderwall.com 18 | 19 | ## Pull Request Guidelines 20 | 21 | Some things that will increase the chance that your pull request is accepted: 22 | 23 | * Write test for your changes and make sure all the tests pass. 24 | * Keep it as conventional and simple as possible. Coderwall serves 100,000 of devs each month on very minimal oversight. We want the product quick to support and easy to enhance. This includes being very thoughtful before adding external dependencies or deviating from the conventional vanilla rails project structure. 25 | * Use [basscss](http://www.basscss.com) for all css. It is a really really really good atomic class based CSS library. You should rarely have to add a new style or custom css but if you do, please only do so in application.scss. 26 | * Make any settings a configuration accessible through ENV with an example setting in .env.sample 27 | -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C ./config/puma.rb --quiet 2 | hot-assets: sh -c 'rm app/assets/webpack/* || true && HOT_RAILS_PORT=3500 npm run hot-assets' 3 | rails-server-assets: sh -c 'npm run build:dev:server' 4 | -------------------------------------------------------------------------------- /Procfile.dev: -------------------------------------------------------------------------------- 1 | web: bundle exec puma -C ./config/puma.rb 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Coderwall 2 | 3 | [![Build Status](https://travis-ci.org/coderwall/coderwall-next.svg?branch=master)](https://travis-ci.org/coderwall/coderwall-next) 4 | 5 | The codebase for [coderwall.com](https://coderwall.com). Coderwall is a developer community used by nearly half a million developers each month to learn and share programming tips. 6 | 7 | ## Prerequisites 8 | 9 | * Ruby 10 | * Postgres 11 | * Heroku Toolbelt (or foreman gem) 12 | 13 | ## Get Started 14 | 15 | ```bash 16 | cp .env.sample .env # (most settings are not required for core functionality) 17 | bundle install 18 | rake db:create db:migrate 19 | heroku local 20 | ``` 21 | 22 | ## Updating SSL 23 | 24 | ``` 25 | $ ./update-ssl.sh 26 | ``` 27 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Rails.application.load_tasks 7 | 8 | task default: :test -------------------------------------------------------------------------------- /app/assets/config/manifest.js: -------------------------------------------------------------------------------- 1 | //= link_tree ../images 2 | //= link_directory ../javascripts .js 3 | //= link_directory ../stylesheets .css 4 | -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/fonts/font-awesome/fontawesome-webfont.eot -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/fonts/font-awesome/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/fonts/font-awesome/fontawesome-webfont.woff -------------------------------------------------------------------------------- /app/assets/fonts/font-awesome/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/fonts/font-awesome/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /app/assets/images/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/.keep -------------------------------------------------------------------------------- /app/assets/images/announcing-next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/announcing-next.png -------------------------------------------------------------------------------- /app/assets/images/badges/1000lemming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/1000lemming.png -------------------------------------------------------------------------------- /app/assets/images/badges/100lemming.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/100lemming.png -------------------------------------------------------------------------------- /app/assets/images/badges/24-continuous-sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/24-continuous-sync.png -------------------------------------------------------------------------------- /app/assets/images/badges/24-participant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/24-participant.png -------------------------------------------------------------------------------- /app/assets/images/badges/altrustic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/altrustic.png -------------------------------------------------------------------------------- /app/assets/images/badges/bear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/bear.png -------------------------------------------------------------------------------- /app/assets/images/badges/bear3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/bear3.png -------------------------------------------------------------------------------- /app/assets/images/badges/beaver-old.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/beaver-old.png -------------------------------------------------------------------------------- /app/assets/images/badges/beaver.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/beaver.png -------------------------------------------------------------------------------- /app/assets/images/badges/beaver3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/beaver3.png -------------------------------------------------------------------------------- /app/assets/images/badges/changelogd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/changelogd.png -------------------------------------------------------------------------------- /app/assets/images/badges/charity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/charity.png -------------------------------------------------------------------------------- /app/assets/images/badges/coffee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/coffee.png -------------------------------------------------------------------------------- /app/assets/images/badges/comingsoon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/comingsoon.png -------------------------------------------------------------------------------- /app/assets/images/badges/cub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/cub.png -------------------------------------------------------------------------------- /app/assets/images/badges/desertlocust.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/desertlocust.png -------------------------------------------------------------------------------- /app/assets/images/badges/desertlocust3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/desertlocust3.png -------------------------------------------------------------------------------- /app/assets/images/badges/earlyadopter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/earlyadopter.png -------------------------------------------------------------------------------- /app/assets/images/badges/entrepreneur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/entrepreneur.png -------------------------------------------------------------------------------- /app/assets/images/badges/epidexipteryx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/epidexipteryx.png -------------------------------------------------------------------------------- /app/assets/images/badges/epidexipteryx3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/epidexipteryx3.png -------------------------------------------------------------------------------- /app/assets/images/badges/forked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/forked.png -------------------------------------------------------------------------------- /app/assets/images/badges/forked1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/forked1.png -------------------------------------------------------------------------------- /app/assets/images/badges/forked100.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/forked100.png -------------------------------------------------------------------------------- /app/assets/images/badges/forked20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/forked20.png -------------------------------------------------------------------------------- /app/assets/images/badges/forked50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/forked50.png -------------------------------------------------------------------------------- /app/assets/images/badges/general-skills.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/general-skills.png -------------------------------------------------------------------------------- /app/assets/images/badges/github-gameoff-honorable-mention-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/github-gameoff-honorable-mention-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/github-gameoff-judge-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/github-gameoff-judge-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/github-gameoff-participant-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/github-gameoff-participant-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/github-gameoff-runner-up-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/github-gameoff-runner-up-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/github-gameoff-winner-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/github-gameoff-winner-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/go.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/go.png -------------------------------------------------------------------------------- /app/assets/images/badges/go3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/go3.png -------------------------------------------------------------------------------- /app/assets/images/badges/goruco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/goruco.png -------------------------------------------------------------------------------- /app/assets/images/badges/hackathon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/hackathon.png -------------------------------------------------------------------------------- /app/assets/images/badges/hackathonCMU.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/hackathonCMU.png -------------------------------------------------------------------------------- /app/assets/images/badges/hackathonStanford.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/hackathonStanford.png -------------------------------------------------------------------------------- /app/assets/images/badges/honeybadger-brood.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/honeybadger-brood.png -------------------------------------------------------------------------------- /app/assets/images/badges/honeybadger-brood2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/honeybadger-brood2.png -------------------------------------------------------------------------------- /app/assets/images/badges/honeybadger-ko.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/honeybadger-ko.png -------------------------------------------------------------------------------- /app/assets/images/badges/honeybadger-ko3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/honeybadger-ko3.png -------------------------------------------------------------------------------- /app/assets/images/badges/honeybadger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/honeybadger.png -------------------------------------------------------------------------------- /app/assets/images/badges/honeybadger3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/honeybadger3.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-best-design-2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-best-design-2011.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-best-design-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-best-design-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-champion-2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-champion-2011.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-champion-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-champion-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-contender-2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-contender-2011.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-contender-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-contender-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-judge-2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-judge-2011.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-judge-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-judge-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-most-complete-2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-most-complete-2011.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-most-complete-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-most-complete-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-most-innovative-2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-most-innovative-2011.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-most-innovative-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-most-innovative-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-most-useful-2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-most-useful-2011.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-most-useful-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-most-useful-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-most-votes-2011.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-most-votes-2011.png -------------------------------------------------------------------------------- /app/assets/images/badges/ko-most-votes-2012.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/ko-most-votes-2012.png -------------------------------------------------------------------------------- /app/assets/images/badges/komododragon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/komododragon.png -------------------------------------------------------------------------------- /app/assets/images/badges/komododragon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/komododragon3.png -------------------------------------------------------------------------------- /app/assets/images/badges/labrador.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/labrador.png -------------------------------------------------------------------------------- /app/assets/images/badges/labrador3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/labrador3.png -------------------------------------------------------------------------------- /app/assets/images/badges/locked.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/locked.png -------------------------------------------------------------------------------- /app/assets/images/badges/mayor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/mayor.png -------------------------------------------------------------------------------- /app/assets/images/badges/mongoose.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/mongoose.png -------------------------------------------------------------------------------- /app/assets/images/badges/mongoose3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/mongoose3.png -------------------------------------------------------------------------------- /app/assets/images/badges/moongoose-rails.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/moongoose-rails.png -------------------------------------------------------------------------------- /app/assets/images/badges/more.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/more.png -------------------------------------------------------------------------------- /app/assets/images/badges/more2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/more2.png -------------------------------------------------------------------------------- /app/assets/images/badges/narwhal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/narwhal.png -------------------------------------------------------------------------------- /app/assets/images/badges/narwhal3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/narwhal3.png -------------------------------------------------------------------------------- /app/assets/images/badges/neo4j-challenge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/neo4j-challenge.png -------------------------------------------------------------------------------- /app/assets/images/badges/neo4j-winner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/neo4j-winner.png -------------------------------------------------------------------------------- /app/assets/images/badges/neo4j.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/neo4j.png -------------------------------------------------------------------------------- /app/assets/images/badges/nephilakomaci.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/nephilakomaci.png -------------------------------------------------------------------------------- /app/assets/images/badges/nephilakomaci3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/nephilakomaci3.png -------------------------------------------------------------------------------- /app/assets/images/badges/octopussy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/octopussy.png -------------------------------------------------------------------------------- /app/assets/images/badges/philanthropist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/philanthropist.png -------------------------------------------------------------------------------- /app/assets/images/badges/platypus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/platypus.png -------------------------------------------------------------------------------- /app/assets/images/badges/platypus3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/platypus3.png -------------------------------------------------------------------------------- /app/assets/images/badges/python.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/python.png -------------------------------------------------------------------------------- /app/assets/images/badges/python3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/python3.png -------------------------------------------------------------------------------- /app/assets/images/badges/railsberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/railsberry.png -------------------------------------------------------------------------------- /app/assets/images/badges/railscamp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/railscamp.png -------------------------------------------------------------------------------- /app/assets/images/badges/raven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/raven.png -------------------------------------------------------------------------------- /app/assets/images/badges/screencapture-achievements.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/screencapture-achievements.png -------------------------------------------------------------------------------- /app/assets/images/badges/social-icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/social-icons.png -------------------------------------------------------------------------------- /app/assets/images/badges/trex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/trex.png -------------------------------------------------------------------------------- /app/assets/images/badges/trex3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/trex3.png -------------------------------------------------------------------------------- /app/assets/images/badges/velociraptor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/velociraptor.png -------------------------------------------------------------------------------- /app/assets/images/badges/velociraptor3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/velociraptor3.png -------------------------------------------------------------------------------- /app/assets/images/badges/walrus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/walrus.png -------------------------------------------------------------------------------- /app/assets/images/badges/wrocloverb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/badges/wrocloverb.png -------------------------------------------------------------------------------- /app/assets/images/conference-room.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/conference-room.png -------------------------------------------------------------------------------- /app/assets/images/happy-cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/happy-cat.jpg -------------------------------------------------------------------------------- /app/assets/images/live-banner.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/live-banner.jpg -------------------------------------------------------------------------------- /app/assets/images/live-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/live-banner.png -------------------------------------------------------------------------------- /app/assets/images/offline-holder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/offline-holder.png -------------------------------------------------------------------------------- /app/assets/images/pop.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/assets/images/pop.mp3 -------------------------------------------------------------------------------- /app/assets/javascripts/analytics.js.coffee: -------------------------------------------------------------------------------- 1 | # https://developers.google.com/analytics/devguides/collection/analyticsjs/sending-hits 2 | document.addEventListener 'turbolinks:load', -> 3 | trackPageView() 4 | registerEventTracking() 5 | setTimeout registerBSATracking, 1500 6 | 7 | @trackPageView = -> 8 | if window.ga? 9 | ga('set', 'location', location.href.split('#')[0]) 10 | ga('send', 'pageview', { "title": document.title }) 11 | 12 | @registerEventTracking = -> 13 | # No JQuery, yay! 14 | document.querySelectorAll('a[ga-event-category]').forEach (item, i) -> 15 | item.addEventListener 'mousedown', (eventType) => 16 | ga 'send', 'event', 17 | eventCategory: item.getAttribute("ga-event-category") 18 | eventAction: item.getAttribute("ga-event-action") 19 | eventLabel: item.getAttribute("ga-event-label") 20 | transport: 'beacon' 21 | 22 | return true 23 | 24 | @registerBSATracking = -> 25 | document.querySelectorAll('.bsap > a').forEach (item, i) -> 26 | item.addEventListener 'mousedown', (eventType) => 27 | action = item.parentNode.parentNode.getAttribute('ga-location') + " - Banner" 28 | label = item.getAttribute("title") + ' - ' + item.getAttribute("id") 29 | ga 'send', 'event', 30 | eventCategory: 'Ads' 31 | eventAction: action 32 | eventLabel: label 33 | transport: 'beacon' 34 | 35 | return true 36 | -------------------------------------------------------------------------------- /app/assets/javascripts/application_non_webpack.js.coffee: -------------------------------------------------------------------------------- 1 | # Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 2 | # about supported directives. 3 | #= require bsa 4 | #= require analytics 5 | #= require textarea_with_file_drop_support 6 | 7 | document.addEventListener 'turbolinks:load', -> 8 | els = document.getElementsByTagName('textarea') 9 | for el in els 10 | el.addEventListener 'input', resizeTextAreaForNewInput 11 | 12 | el = document.querySelector('.js-popout') 13 | if el 14 | el.addEventListener('click', openPopout) 15 | 16 | unless document.current_user_id? 17 | setUserId() 18 | 19 | @setUserId = -> 20 | userId = document.querySelector("meta[property='current_user:id']").content 21 | document.current_user_id = userId if userId? 22 | 23 | @promptUserSignInOn401 = (xhr) -> 24 | if xhr.status == 401 25 | window.location.replace('/signin') 26 | return 27 | 28 | @resizeTextAreaForNewInput = -> 29 | textarea_to_resize = this 30 | textarea_new_hight = textarea_to_resize.scrollHeight 31 | textarea_to_resize.style.cssText = 'height:auto;' 32 | textarea_to_resize.style.cssText = 'height:' + textarea_new_hight + 'px' 33 | 34 | openPopout = -> 35 | w = window.open(@href, @target || "_blank", 'menubar=no,toolbar=no,location=no,directories=no,status=no,scrollbars=no,resizable=no,dependent,width=400,height=600,left=0,top=0') 36 | return !w 37 | -------------------------------------------------------------------------------- /app/assets/javascripts/application_static.js: -------------------------------------------------------------------------------- 1 | // This file is used in production to server generated JS assets. In development mode, we use the Webpack Dev Server 2 | // to provide assets. This allows for hot reloading of the JS and CSS. 3 | // See app/helpers/application_helper.rb for how the correct assets file is picked based on the Rails environment. 4 | // Those helpers are used here: app/views/layouts/application.html.erb 5 | 6 | // These assets are located in app/assets/webpack directory 7 | 8 | // Non-webpack assets incl turbolinks 9 | //= require application_non_webpack 10 | -------------------------------------------------------------------------------- /app/assets/javascripts/bsa.js.coffee: -------------------------------------------------------------------------------- 1 | (-> 2 | bsa = document.createElement('script') 3 | bsa.type = 'text/javascript' 4 | bsa.async = true 5 | bsa.src = document.location.protocol + '//s3.buysellads.com/ac/bsa.js' 6 | (document.getElementsByTagName('head')[0] or document.getElementsByTagName('body')[0]).appendChild(bsa) 7 | 8 | document.addEventListener 'turbolinks:load', -> 9 | if window._bsap? 10 | _bsap.reload() 11 | )() 12 | -------------------------------------------------------------------------------- /app/assets/javascripts/textarea_with_file_drop_support.js.coffee: -------------------------------------------------------------------------------- 1 | document.addEventListener 'turbolinks:load', -> 2 | textarea = document.querySelector('textarea[dropped-files-url]') 3 | if textarea 4 | textarea.addEventListener 'drop', (e) -> 5 | e.preventDefault() 6 | url = textarea.getAttribute('dropped-files-url') 7 | files = e.target.files || e.dataTransfer.files 8 | file = files[0] 9 | 10 | addUploadPlaceholder(textarea, file) 11 | uploadFile url, file, (data)-> 12 | replaceUploadPlaceholder(textarea, file, data) 13 | 14 | @uploadFile = (url, file, callback)-> 15 | data = new FormData 16 | data.append 'file', file 17 | 18 | request = new XMLHttpRequest() 19 | request.open('POST', url, true) 20 | request.setRequestHeader('X-CSRF-Token', document.getElementsByName('csrf-token')[0].content) 21 | request.setRequestHeader('Accept', 'text/javascript') 22 | request.send(data) 23 | request.onload = -> 24 | if (request.status >= 200 && request.status < 400) 25 | data = JSON.parse(request.responseText) 26 | callback(data) 27 | 28 | @addUploadPlaceholder = (el, file) -> 29 | insertTextAtCursor(el, uploadPlaceholder(file.name)) 30 | 31 | @insertTextAtCursor = (el, text)-> 32 | originalText = el.value 33 | newText = originalText + "\n" + text 34 | el.value = newText 35 | 36 | @uploadPlaceholder = (name) -> 37 | "![Uploading... #{name}]()" 38 | 39 | @replaceUploadPlaceholder = (el, file, data) -> 40 | picture = data.picture 41 | placeholder = uploadPlaceholder(file.name) 42 | replacement = if picture.type.match(/image|pdf|png|psd/) 43 | "![#{file.name}](#{picture.url})" 44 | else 45 | "[#{file.name}](#{picture.url})" 46 | el.value = el.value.replace(placeholder, replacement) 47 | -------------------------------------------------------------------------------- /app/assets/stylesheets/application_static.scss: -------------------------------------------------------------------------------- 1 | // Non-webpack assets 2 | @import 'application_non_webpack'; 3 | -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss.scss: -------------------------------------------------------------------------------- 1 | @import "basscss/defaults"; 2 | @import "basscss/base-reset"; 3 | @import "basscss/base-forms"; 4 | @import "basscss/base-tables"; 5 | @import "basscss/base-typography"; 6 | @import "basscss/color-base"; 7 | @import "basscss/color-forms"; 8 | @import "basscss/color-tables"; 9 | @import "basscss/btn"; 10 | @import "basscss/btn-primary"; 11 | @import "basscss/btn-outline"; 12 | @import "basscss/type-scale"; 13 | @import "basscss/utility-typography"; 14 | @import "basscss/utility-layout"; 15 | @import "basscss/align"; 16 | @import "basscss/white-space"; 17 | @import "basscss/positions"; 18 | @import "basscss/responsive-states"; 19 | @import "basscss/grid"; 20 | @import "basscss/flex-object"; 21 | @import "basscss/borders"; 22 | @import "basscss/colors"; 23 | @import "basscss/background-colors"; 24 | @import "basscss/background-images"; 25 | @import "basscss/border-colors"; 26 | @import "basscss/btn-sizes"; 27 | @import "basscss/color-forms-dark"; 28 | @import "basscss/color-input-range"; 29 | @import "basscss/color-progress"; 30 | @import "basscss/highlight"; 31 | @import "basscss/input-range"; 32 | @import "basscss/progress"; 33 | @import "basscss/responsive-white-space"; 34 | @import "basscss/table-object"; 35 | @import "basscss/ui-utility-groups"; 36 | @import "basscss/utility-headings"; 37 | -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_align.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | 5 | // Custom Media Query Variables 6 | 7 | 8 | /* Basscss Align */ 9 | 10 | .align-baseline { vertical-align: baseline } 11 | .align-top { vertical-align: top } 12 | .align-middle { vertical-align: middle } 13 | .align-bottom { vertical-align: bottom } -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_background-images.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | 5 | // Custom Media Query Variables 6 | 7 | 8 | /* Basscss Background Images */ 9 | 10 | .bg-cover { background-size: cover } 11 | .bg-contain { background-size: contain } 12 | 13 | .bg-center { background-position: center } 14 | .bg-top { background-position: top } 15 | .bg-right { background-position: right } 16 | .bg-bottom { background-position: bottom } 17 | .bg-left { background-position: left } -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_base-reset.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | 5 | // Custom Media Query Variables 6 | 7 | 8 | body { margin: 0 } 9 | img { max-width: 100% } 10 | svg { max-height: 100% } -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_positions.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | 5 | // Custom Media Query Variables 6 | 7 | 8 | /* Basscss Positions */ 9 | 10 | .relative { position: relative } 11 | .absolute { position: absolute } 12 | .fixed { position: fixed } 13 | 14 | .top-0 { top: 0 } 15 | .right-0 { right: 0 } 16 | .bottom-0 { bottom: 0 } 17 | .left-0 { left: 0 } 18 | 19 | .z1 { z-index: 1 } 20 | .z2 { z-index: 2 } 21 | .z3 { z-index: 3 } 22 | .z4 { z-index: 4 } -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_table-object.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | 5 | // Custom Media Query Variables 6 | 7 | $breakpoint-sm: '(min-width: 40em)' !default; 8 | $breakpoint-md: '(min-width: 52em)' !default; 9 | $breakpoint-lg: '(min-width: 64em)' !default; 10 | 11 | /* Basscss Table Object */ 12 | 13 | .table { 14 | display: table; 15 | width: 100%; 16 | } 17 | .table-cell { 18 | display: table-cell; 19 | vertical-align: middle; 20 | } 21 | 22 | .table-fixed { table-layout: fixed } 23 | @media #{$breakpoint-sm} { 24 | 25 | .sm-table { 26 | display: table; 27 | width: 100%; 28 | } 29 | .sm-table-cell { 30 | display: table-cell; 31 | vertical-align: middle; 32 | } 33 | 34 | } 35 | @media #{$breakpoint-md} { 36 | 37 | .md-table { 38 | display: table; 39 | width: 100%; 40 | } 41 | .md-table-cell { 42 | display: table-cell; 43 | vertical-align: middle; 44 | } 45 | 46 | } 47 | @media #{$breakpoint-lg} { 48 | 49 | .lg-table { 50 | display: table; 51 | width: 100%; 52 | } 53 | .lg-table-cell { 54 | display: table-cell; 55 | vertical-align: middle; 56 | } 57 | 58 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_ui-utility-groups.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | $border-width: 1px !default; 5 | 6 | // Custom Media Query Variables 7 | 8 | 9 | /* Basscss UI Utility Groups */ 10 | 11 | .x-group-item { margin-left: -$border-width } 12 | .x-group-item:first-of-type { margin-left: 0 } 13 | 14 | .y-group-item { margin-top: -$border-width } 15 | .y-group-item:first-of-type { margin-top: 0 } 16 | 17 | .x-group-item:focus, 18 | .y-group-item:focus { 19 | position: relative; 20 | z-index: 1; 21 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_utility-headings.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | $h00: 4rem !default; 5 | $h0: 3rem !default; 6 | $h00-responsive: 8vw !default; 7 | $h0-responsive: 6vw !default; 8 | $h1-responsive: 4vw !default; 9 | $h00-responsive-max: 7.68rem !default; 10 | $h0-responsive-max: 5.76rem !default; 11 | $h1-responsive-max: 3.84rem !default; 12 | 13 | // Custom Media Query Variables 14 | 15 | $breakpoint-md: '(min-width: 52em)' !default; 16 | $breakpoint-xl: '(min-width: 96em)' !default; 17 | 18 | /* Basscss Utility Headings */ 19 | 20 | .h00 { font-size: $h00 } 21 | .h0 { font-size: $h0 } 22 | 23 | @media #{$breakpoint-md} { 24 | .h00-responsive { font-size: $h00-responsive } 25 | .h0-responsive { font-size: $h0-responsive } 26 | .h1-responsive { font-size: $h1-responsive } 27 | } 28 | 29 | @media #{$breakpoint-xl} { 30 | .h00-responsive { font-size: $h00-responsive-max } 31 | .h0-responsive { font-size: $h0-responsive-max } 32 | .h1-responsive { font-size: $h1-responsive-max } 33 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_utility-layout.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | 5 | // Custom Media Query Variables 6 | 7 | 8 | /* Basscss Utility Layout */ 9 | 10 | .inline { display: inline } 11 | .block { display: block } 12 | .inline-block { display: inline-block } 13 | .table { display: table } 14 | .table-cell { display: table-cell } 15 | 16 | .overflow-hidden { overflow: hidden } 17 | .overflow-scroll { overflow: scroll } 18 | .overflow-auto { overflow: auto } 19 | 20 | .overflow-y-scroll { overflow-y: scroll } 21 | 22 | .clearfix:before, 23 | .clearfix:after { 24 | content: " "; 25 | display: table 26 | } 27 | .clearfix:after { clear: both } 28 | 29 | .left { float: left } 30 | .right { float: right } 31 | 32 | @media #{$breakpoint-sm} { 33 | .sm-right {float: right; } 34 | } 35 | 36 | .fit { max-width: 100% } 37 | 38 | .border-box { box-sizing: border-box } 39 | -------------------------------------------------------------------------------- /app/assets/stylesheets/basscss/_utility-typography.scss: -------------------------------------------------------------------------------- 1 | 2 | // Converted Variables 3 | 4 | 5 | // Custom Media Query Variables 6 | 7 | 8 | /* Basscss Utility Typography */ 9 | 10 | .bold { font-weight: $bold-font-weight /* Fallback value: bold */ } 11 | .regular { font-weight: normal } 12 | .italic { font-style: italic } 13 | .caps { text-transform: uppercase; letter-spacing: .2em; } 14 | 15 | .left-align { text-align: left } 16 | .center { text-align: center } 17 | .right-align { text-align: right } 18 | .justify { text-align: justify } 19 | 20 | .nowrap { white-space: nowrap } 21 | .break-word { word-wrap: break-word } 22 | 23 | .truncate { 24 | max-width: 100%; 25 | overflow: hidden; 26 | text-overflow: ellipsis; 27 | white-space: nowrap; 28 | } 29 | 30 | .list-reset { 31 | list-style: none; 32 | padding-left: 0; 33 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/content.scss: -------------------------------------------------------------------------------- 1 | .content{ 2 | line-height: 25px; 3 | 4 | &.small{ 5 | } 6 | 7 | a { 8 | word-break: break-all; 9 | } 10 | 11 | img{ 12 | max-width: 100%; 13 | display: block; 14 | margin: 0 auto; 15 | text-align: center; 16 | } 17 | 18 | strong { 19 | font-weight: normal; 20 | } 21 | 22 | hr { 23 | border: 0; 24 | height: 2px; 25 | } 26 | 27 | em { 28 | font-weight: normal; 29 | font-style: normal; 30 | } 31 | 32 | pre { 33 | background-color: rgba(250,250,250,0.7); 34 | 35 | code{ 36 | font-size: 13px; 37 | line-height: 1.4; 38 | } 39 | } 40 | 41 | code { 42 | // white-space:nowrap; 43 | a { 44 | } 45 | } 46 | 47 | p { 48 | 49 | &:last-child { 50 | margin-bottom: 0; 51 | } 52 | } 53 | 54 | h1, h2, h3, h4 { 55 | 56 | &:first-child { 57 | padding-top: 0; 58 | margin-top: 0; 59 | } 60 | 61 | a { 62 | } 63 | } 64 | 65 | blockquote { 66 | p { 67 | margin: 0; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /app/assets/stylesheets/dropdown.scss: -------------------------------------------------------------------------------- 1 | .dropdown { 2 | position: relative; 3 | display: inline-block; 4 | } 5 | 6 | .dropdown-content { 7 | display: none; 8 | position: absolute; 9 | } 10 | 11 | .dropdown:hover .dropdown-content { 12 | display: block; 13 | } 14 | 15 | .dropdown:hover .btn { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | * Font Awesome 4.5.0 by @davegandy - http://fontawesome.io - @fontawesome 3 | * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) 4 | */ 5 | 6 | @function fa-font-path($path) { 7 | @return font-path($path); 8 | } 9 | 10 | $fa-sass-asset-helper: true; 11 | 12 | @import "font-awesome/variables"; 13 | @import "font-awesome/mixins"; 14 | @import "font-awesome/path"; 15 | @import "font-awesome/core"; 16 | @import "font-awesome/larger"; 17 | @import "font-awesome/fixed-width"; 18 | @import "font-awesome/list"; 19 | @import "font-awesome/bordered-pulled"; 20 | @import "font-awesome/animated"; 21 | @import "font-awesome/rotated-flipped"; 22 | @import "font-awesome/stacked"; 23 | @import "font-awesome/icons"; 24 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_animated.scss: -------------------------------------------------------------------------------- 1 | // Spinning Icons 2 | // -------------------------- 3 | 4 | .#{$fa-css-prefix}-spin { 5 | -webkit-animation: fa-spin 2s infinite linear; 6 | animation: fa-spin 2s infinite linear; 7 | } 8 | 9 | .#{$fa-css-prefix}-pulse { 10 | -webkit-animation: fa-spin 1s infinite steps(8); 11 | animation: fa-spin 1s infinite steps(8); 12 | } 13 | 14 | @-webkit-keyframes fa-spin { 15 | 0% { 16 | -webkit-transform: rotate(0deg); 17 | transform: rotate(0deg); 18 | } 19 | 100% { 20 | -webkit-transform: rotate(359deg); 21 | transform: rotate(359deg); 22 | } 23 | } 24 | 25 | @keyframes fa-spin { 26 | 0% { 27 | -webkit-transform: rotate(0deg); 28 | transform: rotate(0deg); 29 | } 30 | 100% { 31 | -webkit-transform: rotate(359deg); 32 | transform: rotate(359deg); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_bordered-pulled.scss: -------------------------------------------------------------------------------- 1 | // Bordered & Pulled 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-border { 5 | padding: .2em .25em .15em; 6 | border: solid .08em $fa-border-color; 7 | border-radius: .1em; 8 | } 9 | 10 | .#{$fa-css-prefix}-pull-left { float: left; } 11 | .#{$fa-css-prefix}-pull-right { float: right; } 12 | 13 | .#{$fa-css-prefix} { 14 | &.#{$fa-css-prefix}-pull-left { margin-right: .3em; } 15 | &.#{$fa-css-prefix}-pull-right { margin-left: .3em; } 16 | } 17 | 18 | /* Deprecated as of 4.4.0 */ 19 | .pull-right { float: right; } 20 | .pull-left { float: left; } 21 | 22 | .#{$fa-css-prefix} { 23 | &.pull-left { margin-right: .3em; } 24 | &.pull-right { margin-left: .3em; } 25 | } 26 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_core.scss: -------------------------------------------------------------------------------- 1 | // Base Class Definition 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix} { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_extras.scss: -------------------------------------------------------------------------------- 1 | /* EXTRAS 2 | * -------------------------- */ 3 | 4 | /* Stacked and layered icon */ 5 | 6 | /* Animated rotating icon */ 7 | .#{$fa-css-prefix}-spin { 8 | -webkit-animation: spin 2s infinite linear; 9 | -moz-animation: spin 2s infinite linear; 10 | -o-animation: spin 2s infinite linear; 11 | animation: spin 2s infinite linear; 12 | } 13 | 14 | @-moz-keyframes spin { 15 | 0% { -moz-transform: rotate(0deg); } 16 | 100% { -moz-transform: rotate(359deg); } 17 | } 18 | @-webkit-keyframes spin { 19 | 0% { -webkit-transform: rotate(0deg); } 20 | 100% { -webkit-transform: rotate(359deg); } 21 | } 22 | @-o-keyframes spin { 23 | 0% { -o-transform: rotate(0deg); } 24 | 100% { -o-transform: rotate(359deg); } 25 | } 26 | @-ms-keyframes spin { 27 | 0% { -ms-transform: rotate(0deg); } 28 | 100% { -ms-transform: rotate(359deg); } 29 | } 30 | @keyframes spin { 31 | 0% { transform: rotate(0deg); } 32 | 100% { transform: rotate(359deg); } 33 | } 34 | 35 | 36 | // Icon rotations & flipping 37 | // ------------------------- 38 | 39 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 40 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 41 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 42 | 43 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 44 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 45 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_fixed-width.scss: -------------------------------------------------------------------------------- 1 | // Fixed Width Icons 2 | // ------------------------- 3 | .#{$fa-css-prefix}-fw { 4 | width: (18em / 14); 5 | text-align: center; 6 | } 7 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_larger.scss: -------------------------------------------------------------------------------- 1 | // Icon Sizes 2 | // ------------------------- 3 | 4 | /* makes the font 33% larger relative to the icon container */ 5 | .#{$fa-css-prefix}-lg { 6 | font-size: (4em / 3); 7 | line-height: (3em / 4); 8 | vertical-align: -15%; 9 | } 10 | .#{$fa-css-prefix}-2x { font-size: 2em; } 11 | .#{$fa-css-prefix}-3x { font-size: 3em; } 12 | .#{$fa-css-prefix}-4x { font-size: 4em; } 13 | .#{$fa-css-prefix}-5x { font-size: 5em; } 14 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_list.scss: -------------------------------------------------------------------------------- 1 | // List Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-ul { 5 | padding-left: 0; 6 | margin-left: $fa-li-width; 7 | list-style-type: none; 8 | > li { position: relative; } 9 | } 10 | .#{$fa-css-prefix}-li { 11 | position: absolute; 12 | left: -$fa-li-width; 13 | width: $fa-li-width; 14 | top: (2em / 14); 15 | text-align: center; 16 | &.#{$fa-css-prefix}-lg { 17 | left: -$fa-li-width + (4em / 14); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_mixins.scss: -------------------------------------------------------------------------------- 1 | // Mixins 2 | // -------------------------- 3 | 4 | @mixin fa-icon() { 5 | display: inline-block; 6 | font: normal normal normal #{$fa-font-size-base}/#{$fa-line-height-base} FontAwesome; // shortening font declaration 7 | font-size: inherit; // can't have font-size inherit on line above, so need to override 8 | text-rendering: auto; // optimizelegibility throws things off #1094 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | } 12 | 13 | @mixin fa-icon-rotate($degrees, $rotation) { 14 | -webkit-transform: rotate($degrees); 15 | -ms-transform: rotate($degrees); 16 | transform: rotate($degrees); 17 | } 18 | 19 | @mixin fa-icon-flip($horiz, $vert, $rotation) { 20 | -webkit-transform: scale($horiz, $vert); 21 | -ms-transform: scale($horiz, $vert); 22 | transform: scale($horiz, $vert); 23 | } 24 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_path.scss: -------------------------------------------------------------------------------- 1 | /* FONT PATH 2 | * -------------------------- */ 3 | 4 | @font-face { 5 | font-family: 'FontAwesome'; 6 | src: url(if($fa-sass-asset-helper, fa-font-path('#{$fa-font-path}#{$fa-font-name}.eot?v=#{$fa-version}'), '#{$fa-font-path}#{$fa-font-name}.eot?v=#{$fa-version}')); 7 | src: url(if($fa-sass-asset-helper, fa-font-path('#{$fa-font-path}#{$fa-font-name}.eot?v=#{$fa-version}#iefix'), '#{$fa-font-path}#{$fa-font-name}.eot?v=#{$fa-version}#iefix')) format('embedded-opentype'), 8 | url(if($fa-sass-asset-helper, fa-font-path('#{$fa-font-path}#{$fa-font-name}.woff2?v=#{$fa-version}'), '#{$fa-font-path}#{$fa-font-name}.woff2?v=#{$fa-version}')) format('woff2'), 9 | url(if($fa-sass-asset-helper, fa-font-path('#{$fa-font-path}#{$fa-font-name}.woff?v=#{$fa-version}'), '#{$fa-font-path}#{$fa-font-name}.woff?v=#{$fa-version}')) format('woff'), 10 | url(if($fa-sass-asset-helper, fa-font-path('#{$fa-font-path}#{$fa-font-name}.ttf?v=#{$fa-version}'), '#{$fa-font-path}#{$fa-font-name}.ttf?v=#{$fa-version}')) format('truetype'), 11 | url(if($fa-sass-asset-helper, fa-font-path('#{$fa-font-path}#{$fa-font-name}.svg?v=#{$fa-version}#fontawesomeregular'), '#{$fa-font-path}#{$fa-font-name}.svg?v=#{$fa-version}#fontawesomeregular')) format('svg'); 12 | font-weight: normal; 13 | font-style: normal; 14 | } 15 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_rotated-flipped.scss: -------------------------------------------------------------------------------- 1 | // Rotated & Flipped Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-rotate-90 { @include fa-icon-rotate(90deg, 1); } 5 | .#{$fa-css-prefix}-rotate-180 { @include fa-icon-rotate(180deg, 2); } 6 | .#{$fa-css-prefix}-rotate-270 { @include fa-icon-rotate(270deg, 3); } 7 | 8 | .#{$fa-css-prefix}-flip-horizontal { @include fa-icon-flip(-1, 1, 0); } 9 | .#{$fa-css-prefix}-flip-vertical { @include fa-icon-flip(1, -1, 2); } 10 | 11 | // Hook for IE8-9 12 | // ------------------------- 13 | 14 | :root .#{$fa-css-prefix}-rotate-90, 15 | :root .#{$fa-css-prefix}-rotate-180, 16 | :root .#{$fa-css-prefix}-rotate-270, 17 | :root .#{$fa-css-prefix}-flip-horizontal, 18 | :root .#{$fa-css-prefix}-flip-vertical { 19 | filter: none; 20 | } 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/font-awesome/_stacked.scss: -------------------------------------------------------------------------------- 1 | // Stacked Icons 2 | // ------------------------- 3 | 4 | .#{$fa-css-prefix}-stack { 5 | position: relative; 6 | display: inline-block; 7 | width: 2em; 8 | height: 2em; 9 | line-height: 2em; 10 | vertical-align: middle; 11 | } 12 | .#{$fa-css-prefix}-stack-1x, .#{$fa-css-prefix}-stack-2x { 13 | position: absolute; 14 | left: 0; 15 | width: 100%; 16 | text-align: center; 17 | } 18 | .#{$fa-css-prefix}-stack-1x { line-height: inherit; } 19 | .#{$fa-css-prefix}-stack-2x { font-size: 2em; } 20 | .#{$fa-css-prefix}-inverse { color: $fa-inverse; } 21 | -------------------------------------------------------------------------------- /app/assets/stylesheets/minimal.scss: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | } 4 | 5 | .full-height { 6 | height: 100%; 7 | } 8 | -------------------------------------------------------------------------------- /app/controllers/application_record.rb: -------------------------------------------------------------------------------- 1 | class ApplicationRecord < ActiveRecord::Base 2 | self.abstract_class = true 3 | end 4 | -------------------------------------------------------------------------------- /app/controllers/badges_controller.rb: -------------------------------------------------------------------------------- 1 | class BadgesController < ApplicationController 2 | end 3 | -------------------------------------------------------------------------------- /app/controllers/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/controllers/concerns/.keep -------------------------------------------------------------------------------- /app/controllers/hooks_controller.rb: -------------------------------------------------------------------------------- 1 | class HooksController < ApplicationController 2 | skip_before_action :verify_authenticity_token 3 | 4 | def sendgrid 5 | params[:_json].each do |data| 6 | puts data 7 | process_unsubscribe(data) if data['event'] == 'unsubscribe' 8 | end 9 | 10 | head(200) 11 | end 12 | 13 | # private 14 | 15 | def process_unsubscribe(data) 16 | User.where(email: data['email']).update_all(marketing_list: nil) 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /app/controllers/job_subscriptions_controller.rb: -------------------------------------------------------------------------------- 1 | class JobSubscriptionsController < ApplicationController 2 | def new 3 | @subscription = JobSubscription.new 4 | end 5 | 6 | def create 7 | @subscription = JobSubscription.new(subscription_params) 8 | if !@subscription.save 9 | render action: 'new' 10 | return 11 | end 12 | 13 | @subscription.charge!(params['stripeToken']) 14 | 15 | Slack.notify!(':moneybag:', "#{@subscription.company_name} (#{@subscription.contact_email}) just subscribed to post all jobs at #{@subscription.jobs_url}") 16 | 17 | flash[:notice] = "You're all set! You will receive a receipt and email shortly once we post your first jobs to Coderwall." 18 | redirect_to jobs_path 19 | 20 | rescue Stripe::CardError => e 21 | flash[:notice] = e.message 22 | redirect_to new_job_subscription_path(@subscription) 23 | end 24 | 25 | # private 26 | 27 | def subscription_params 28 | params.require(:job_subscription).permit( 29 | :jobs_url, 30 | :company_name, 31 | :contact_email, 32 | :stripe_customer_id, 33 | ) 34 | end 35 | 36 | end 37 | -------------------------------------------------------------------------------- /app/controllers/jobs_controller.rb: -------------------------------------------------------------------------------- 1 | class JobsController < ApplicationController 2 | def index 3 | if [:show_fulltime, :show_parttime, :show_contract].any?{|s| params[s].blank? } 4 | params[:show_fulltime] = 'true' 5 | params[:show_parttime] = 'true' 6 | params[:show_contract] = 'true' 7 | params[:show_remote] = 'false' 8 | end 9 | roles = [] 10 | roles.push(Job::FULLTIME) if params[:show_fulltime] == 'true' 11 | roles.push(Job::PARTTIME) if params[:show_parttime] == 'true' 12 | roles.push(Job::CONTRACT) if params[:show_contract] == 'true' 13 | @jobs = Job.active.order(created_at: :desc) 14 | @jobs = @jobs.where('jobs.role_type in (?)', roles) 15 | @jobs = @jobs.where(location: 'Remote') if params[:show_remote] == 'true' 16 | @jobs = @jobs.where('jobs.location ilike :q or jobs.title ilike :q or jobs.company ilike :q', q: "%#{params[:q]}%") unless params[:q].blank? 17 | 18 | if params[:posted] 19 | @jobs = @jobs.where.not(id: params[:posted]) 20 | @featured = Job.find(params[:posted]) 21 | end 22 | 23 | respond_to :html 24 | end 25 | 26 | def new 27 | @job = Job.new 28 | end 29 | 30 | def create 31 | @job = Job.new(job_params) 32 | if !@job.save 33 | render action: 'new' 34 | return 35 | end 36 | 37 | @job.charge!(params['stripeToken']) 38 | render json: @job 39 | 40 | rescue Stripe::CardError => e 41 | render json: { error: e.message }, status: 400 42 | end 43 | 44 | # private 45 | 46 | def job_params 47 | params.require(:job).permit( 48 | :author_email, 49 | :author_name, 50 | :company_logo, 51 | :company_url, 52 | :company, 53 | :location, 54 | :role_type, 55 | :source, 56 | :title 57 | ) 58 | end 59 | 60 | end 61 | -------------------------------------------------------------------------------- /app/controllers/likes_controller.rb: -------------------------------------------------------------------------------- 1 | class LikesController < ApplicationController 2 | before_action :require_login, only: :create 3 | 4 | def create 5 | @likeable = find_likeable 6 | @likeable.likes.create(user: current_user) unless current_user.likes?(@likeable) 7 | @likeable.try(:subscribe!, current_user) 8 | respond_to do |format| 9 | format.json { render(json: @likeable.likes_count, status: :ok) } 10 | end 11 | end 12 | 13 | protected 14 | def find_likeable 15 | params.each do |name, value| 16 | if name =~ /(.+)_id$/ 17 | klass = $1.classify.constantize 18 | if klass == Protip 19 | return klass.find_by_public_id!(value) 20 | else 21 | return klass.find(value) 22 | end 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /app/controllers/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class PagesController < ApplicationController 2 | skip_before_action :verify_authenticity_token 3 | 4 | def show 5 | args = params.permit(:page, :layout) 6 | status = 200 7 | status = 404 if args[:page].to_s == 'not_found' 8 | respond_to do |format| 9 | format.html { render(action: args[:page], status: status) } 10 | format.all { head(:not_found) } 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/controllers/pictures_controller.rb: -------------------------------------------------------------------------------- 1 | class PicturesController < ApplicationController 2 | before_action :require_login 3 | 4 | def create 5 | picture = current_user.pictures.create!(file: params[:file]) 6 | render json: picture 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/controllers/sponsors_controller.rb: -------------------------------------------------------------------------------- 1 | class SponsorsController < ApplicationController 2 | def show 3 | @sponsors = Sponsor.ads_for(remote_ip) 4 | render json: @sponsors 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /app/controllers/subscribers_controller.rb: -------------------------------------------------------------------------------- 1 | class SubscribersController < ApplicationController 2 | # TODO: shouldn't need this, not sure why X-CSRF-Token header isn't working 3 | skip_before_action :verify_authenticity_token 4 | 5 | before_action :require_login, only: [:create, :destroy, :mute] 6 | 7 | def create 8 | @protip = Protip.find(params[:protip_id]) 9 | @protip.subscribe!(current_user) 10 | render json: @protip, root: false 11 | end 12 | 13 | def destroy 14 | @protip = Protip.find(params[:protip_id]) 15 | @protip.unsubscribe!(current_user) 16 | render json: @protip, root: false 17 | end 18 | 19 | def mute 20 | @protip = Protip.find_by_public_id!(params[:protip_id]) 21 | if params[:signature] != current_user.unsubscribe_signature 22 | flash[:notice] = "Unsubscribe link is no longer valid" 23 | else 24 | @protip.unsubscribe!(current_user) 25 | flash[:notice] = "You will no longer receive new comment emails" 26 | end 27 | redirect_to seo_protip_path(@protip) 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/controllers/teams_controller.rb: -------------------------------------------------------------------------------- 1 | class TeamsController < ApplicationController 2 | 3 | def show 4 | if params[:slug] == 'random' 5 | @team = Team.order("random()").first 6 | else 7 | @team = Team.find_by_slug!(params[:slug]) 8 | end 9 | end 10 | 11 | end 12 | -------------------------------------------------------------------------------- /app/helpers/font_awesome_helper.rb: -------------------------------------------------------------------------------- 1 | module FontAwesomeHelper 2 | def icon(icon, text = nil, html_options = {}) 3 | text, html_options = nil, text if text.is_a?(Hash) 4 | 5 | content_class = "fa fa-#{icon}" 6 | content_class << " #{html_options[:class]}" if html_options.key?(:class) 7 | html_options[:class] = content_class 8 | 9 | html = content_tag(:i, nil, html_options) 10 | html << ' ' << text.to_s unless text.blank? 11 | html 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/helpers/jobs_helper.rb: -------------------------------------------------------------------------------- 1 | module JobsHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/likes_helper.rb: -------------------------------------------------------------------------------- 1 | module LikesHelper 2 | end 3 | -------------------------------------------------------------------------------- /app/helpers/users_helper.rb: -------------------------------------------------------------------------------- 1 | module UsersHelper 2 | 3 | def finishing_signup? 4 | params[:finish_signup] == true 5 | end 6 | 7 | def current_user_can_edit?(object) 8 | signed_in? && current_user.can_edit?(object) 9 | end 10 | 11 | def show_protips? 12 | !show_comments? 13 | end 14 | 15 | def show_comments? 16 | params[:comments].present? 17 | end 18 | 19 | def show_protips_active 20 | return 'active ' if show_protips? 21 | end 22 | 23 | def show_comments_active 24 | return 'active ' if show_comments? 25 | end 26 | 27 | def avatar_url(user) 28 | image_url user.avatar.url 29 | end 30 | 31 | def avatar_url_tag(user, options = {}) 32 | image_tag(avatar_url(user), options) if user.avatar? 33 | end 34 | 35 | end 36 | -------------------------------------------------------------------------------- /app/lib/avatar_uploader.rb: -------------------------------------------------------------------------------- 1 | class AvatarUploader < CarrierWave::Uploader::Base 2 | include CarrierWave::MiniMagick 3 | 4 | process resize_and_pad: [100, 100] 5 | 6 | def extension_white_list 7 | %w(jpg jpeg gif png) 8 | end 9 | 10 | def store_dir 11 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /app/lib/cloudfront_constraint.rb: -------------------------------------------------------------------------------- 1 | class CloudfrontConstraint 2 | def matches?(request) 3 | request.env['HTTP_USER_AGENT'] == 'Amazon CloudFront' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /app/lib/picture_uploader.rb: -------------------------------------------------------------------------------- 1 | class PictureUploader < CarrierWave::Uploader::Base 2 | include CarrierWave::MiniMagick 3 | 4 | process :auto_orient 5 | 6 | def auto_orient 7 | manipulate! do |image| 8 | image.collapse! 9 | image.auto_orient 10 | image 11 | end 12 | end 13 | 14 | def extension_white_list 15 | %w(jpg jpeg gif png psd pdf) 16 | end 17 | 18 | def store_dir 19 | "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" 20 | end 21 | 22 | end 23 | -------------------------------------------------------------------------------- /app/mailers/application_mailer.rb: -------------------------------------------------------------------------------- 1 | class ApplicationMailer < ActionMailer::Base 2 | def prevent_delivery 3 | mail.perform_deliveries = false 4 | end 5 | 6 | def list_headers(reply_address, thread_parts, message_parts, archive_url) 7 | thread_id = thread_parts.join('/') 8 | thread_address = "<#{thread_id}@assembly.com>" 9 | message_id = "<#{message_parts.join('/')}@assembly.com>" 10 | 11 | { 12 | "Reply-To" => "#{thread_parts.join('/')} <#{reply_address}>", 13 | 14 | "Message-ID" => message_id, 15 | "In-Reply-To" => thread_address, 16 | "References" => thread_address, 17 | 18 | "List-ID" => "#{thread_id} <#{thread_parts.join('.')}.assembly.com>", 19 | "List-Archive" => archive_url, 20 | "List-Post" => "", 21 | "Precedence" => "list", 22 | } 23 | end 24 | end 25 | -------------------------------------------------------------------------------- /app/mailers/comment_mailer.rb: -------------------------------------------------------------------------------- 1 | class CommentMailer < ApplicationMailer 2 | def new_comment(to, comment) 3 | @to = to 4 | @comment = comment 5 | 6 | return prevent_delivery if prevent_email?(@to) 7 | 8 | if rewrite = ENV['REWRITE_EMAILS'] 9 | @to.email = rewrite 10 | end 11 | 12 | @author = @comment.user 13 | @article = @comment.article 14 | @reply = SecureReplyTo.new(Article, @article.id, @to.username) 15 | 16 | thread_parts = [@article.id] 17 | message_parts = [@comment.id] 18 | options = list_headers( 19 | @reply, 20 | thread_parts, 21 | message_parts, 22 | url_for(@comment.url_params) 23 | ).merge( 24 | from: "#{@author.display_name} ", 25 | to: "#{@to.display_name} <#{@to.email}>", 26 | subject: "New Comment [Re: #{@article.title}]" 27 | ) 28 | 29 | mail(options) do |format| 30 | format.html { render layout: nil } 31 | end 32 | end 33 | 34 | protected 35 | 36 | def prevent_email?(user) 37 | user.banned_at? || user.email_invalid_at? 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/mailers/user_mailer.rb: -------------------------------------------------------------------------------- 1 | class UserMailer < ActionMailer::Base 2 | default from: "support@coderwall.com" 3 | 4 | def destroy_email(user) 5 | @user = user 6 | mail(to: 'support@coderwall.com', subject: "#{@user.username} deleted their account") 7 | end 8 | 9 | def partnership_expired(user) 10 | @user = user 11 | mail(to: user.partner_email, bcc: 'support@coderwall.com', subject: "Important Partner update on Coderwall") 12 | end 13 | 14 | end 15 | -------------------------------------------------------------------------------- /app/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/models/.keep -------------------------------------------------------------------------------- /app/models/badge.rb: -------------------------------------------------------------------------------- 1 | class Badge < ApplicationRecord 2 | belongs_to :user, required: true 3 | 4 | def path 5 | "badges/#{image_name}" 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ApplicationRecord 2 | include TimeAgoInWordsCacheBuster 3 | paginates_per 10 4 | 5 | VIDEO_LAG = 25.seconds # TODO: measure the real lag value 6 | 7 | after_create :auto_like_article_for_author 8 | 9 | belongs_to :user, touch: true, required: true 10 | belongs_to :article, touch: true, required: true 11 | has_many :likes, as: :likable, dependent: :destroy 12 | 13 | validates :body, length: { minimum: 2 } 14 | 15 | scope :on_protips, -> { joins(:article).where(protips: {type: 'Protip'}) } 16 | scope :visible_to, ->(user) { where(bad_content: false) unless user.try(:bad_user) } 17 | scope :recently_created, ->(count=10) { order(created_at: :desc).limit(count)} 18 | 19 | def auto_like_article_for_author 20 | article.likes.create(user: user) unless user.likes?(article) 21 | end 22 | 23 | def dom_id 24 | ActionView::RecordIdentifier.dom_id(self) 25 | end 26 | 27 | def hearts_count 28 | likes_count 29 | end 30 | 31 | def url_params 32 | [article, anchor: dom_id] 33 | end 34 | 35 | def video_timestamp 36 | (created_at - VIDEO_LAG).to_i 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/app/models/concerns/.keep -------------------------------------------------------------------------------- /app/models/concerns/time_ago_in_words_cache_buster.rb: -------------------------------------------------------------------------------- 1 | module TimeAgoInWordsCacheBuster 2 | extend ActiveSupport::Concern 3 | 4 | HOUR_IN_MINUTES = 60 5 | DAY_IN_MINUTES = HOUR_IN_MINUTES * 24 6 | WEEK_IN_MINUTES = DAY_IN_MINUTES * 7 7 | MONTH_IN_MINUTES = WEEK_IN_MINUTES * 4 8 | 9 | def cache_key 10 | "#{super}/t-#{time_interval_key}" 11 | end 12 | 13 | def time_interval_key 14 | minutes = ((Time.now - updated_at) / 60.0).round 15 | if minutes <= HOUR_IN_MINUTES 16 | (Time.now - updated_at) / 60.0 17 | elsif minutes <= DAY_IN_MINUTES 18 | (minutes / DAY_IN_MINUTES).round 19 | elsif minutes <= WEEK_IN_MINUTES 20 | (minutes / DAY_IN_MINUTES).round 21 | else 22 | (minutes / MONTH_IN_MINUTES).round 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /app/models/concerns/view_count_cache_buster.rb: -------------------------------------------------------------------------------- 1 | module ViewCountCacheBuster 2 | extend ActiveSupport::Concern 3 | 4 | def cache_key 5 | "#{super}/v-#{view_count_key}" 6 | end 7 | 8 | def view_count_key 9 | if views_count < 150 10 | bust_cache_every_three_views = views_count / 3 11 | else 12 | bust_cache_every_twenty_views = views_count / 20 13 | end 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /app/models/github.rb: -------------------------------------------------------------------------------- 1 | class Github 2 | class << self 3 | def user_comment_log 4 | fetch('/issues/comments').collect do |comment| 5 | { 6 | username: comment['user']['login'], 7 | user_id: comment['user']['id'], 8 | created_at: Time.parse(comment['created_at']) 9 | } 10 | end 11 | end 12 | 13 | def user_pr_log 14 | fetch('/pulls', state: 'all').collect do |pr| 15 | { 16 | username: pr['user']['login'], 17 | user_id: pr['user']['id'], 18 | created_at: Time.parse(pr['created_at']) 19 | } 20 | end 21 | end 22 | 23 | def user_issue_log 24 | fetch('/issues', state: 'all').collect do |pr| 25 | { 26 | username: pr['user']['login'], 27 | user_id: pr['user']['id'], 28 | created_at: Time.parse(pr['created_at']) 29 | } 30 | end 31 | end 32 | 33 | def fetch(path, options = {}, page = 1) 34 | repo = 'coderwall-next' 35 | owner = 'coderwall' 36 | connection = Faraday.new(url: "https://api.github.com") 37 | results = [] 38 | while true 39 | puts "[GitHub] Fetch #{path}: #{page}" 40 | response = connection.get("/repos/#{owner}/#{repo}/#{path}", options.merge({page: page})) 41 | results << JSON.parse(response.body) 42 | break if (response.headers['link'].to_s =~ /next/) == nil 43 | page = page + 1 44 | end 45 | results.flatten 46 | end 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /app/models/job.rb: -------------------------------------------------------------------------------- 1 | class Job < ApplicationRecord 2 | CENTS_PER_MONTH = 29900 3 | COST = CENTS_PER_MONTH/100 4 | FULLTIME = 'Full Time' 5 | PARTTIME = 'Part Time' 6 | CONTRACT = 'Contract' 7 | ROLES = [FULLTIME, PARTTIME, CONTRACT] 8 | 9 | validates :author_email, presence: true 10 | validates :author_name, presence: true 11 | validates :company, presence: true 12 | validates :location, presence: true 13 | validates :role_type, presence: true 14 | validates :source, presence: true 15 | validates :title, presence: true 16 | 17 | scope :active, -> { where("expires_at > ?", Time.now) } 18 | scope :latest, ->(count=1) { order(created_at: :desc).limit(count) } 19 | scope :featured, ->(count=1) { active.order("RANDOM()").limit(count) } 20 | 21 | def charge!(token) 22 | charge = Stripe::Charge.create( 23 | amount: CENTS_PER_MONTH, # amount in cents, again 24 | currency: "usd", 25 | source: token, 26 | description: "coderwall.com job posting" 27 | ) 28 | 29 | update!( 30 | stripe_charge: charge.id, 31 | expires_at: 1.month.from_now 32 | ) 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /app/models/job_subscription.rb: -------------------------------------------------------------------------------- 1 | class JobSubscription < ApplicationRecord 2 | CENTS_PER_MONTH = (ENV['JOB_SUBSCRIPTION_CENTS'].try(:to_i)) 3 | 4 | validates :jobs_url, presence: true 5 | validates :company_name, presence: true 6 | validates :contact_email, presence: true 7 | 8 | def charge!(token) 9 | customer = Stripe::Customer.create( 10 | source: token, 11 | plan: (ENV['JOBS_PLAN'] || 'jobs_monthly'), 12 | email: contact_email, 13 | ) 14 | 15 | update!( 16 | stripe_customer_id: customer.id, 17 | subscribed_at: Time.now, 18 | ) 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /app/models/like.rb: -------------------------------------------------------------------------------- 1 | class Like < ApplicationRecord 2 | belongs_to :user, required: true 3 | belongs_to :likable, polymorphic: true, counter_cache: true, touch: true, required: true 4 | 5 | def dom_id 6 | #Mimics ActionView::RecordIdentifier.dom_id without killing the database 7 | "#{temporarily_hacked_likable_type}_#{likable_id}".downcase 8 | end 9 | 10 | def temporarily_hacked_likable_type 11 | # the dom_id for these is protip, but in the database they're stored as Articles 12 | # this hack prevents hearting streams but that's ok for now 13 | likable_type == 'Article' ? 'Protip' : likable_type 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /app/models/picture.rb: -------------------------------------------------------------------------------- 1 | class Picture < ApplicationRecord 2 | mount_uploader :file, PictureUploader 3 | 4 | belongs_to :user, required: true 5 | end 6 | -------------------------------------------------------------------------------- /app/models/protip.rb: -------------------------------------------------------------------------------- 1 | class Protip < Article 2 | end 3 | -------------------------------------------------------------------------------- /app/models/secure_reply_to.rb: -------------------------------------------------------------------------------- 1 | require 'openssl' 2 | 3 | class SecureReplyTo 4 | attr_reader :object_type, :object_id, :user_id 5 | 6 | def initialize(object_type, object_id, user_id) 7 | @object_type, @object_id, @user_id = object_type.to_s, object_id, user_id 8 | @object_type = @object_type.underscore # it gets downcased somewhere in the pipe 9 | @user_id = @user_id.downcase 10 | @secret = ENV.fetch('REPLY_SECRET', 'r3ply_secr3t') 11 | end 12 | 13 | def self.parse(address) 14 | _, object_type, object_id, signature, user_id = address.split(/[@\+]/) 15 | address = new(object_type, object_id, user_id) 16 | raise 'Invalid Signature' if address.signature != signature 17 | address 18 | end 19 | 20 | def signature 21 | digest = OpenSSL::Digest.new('sha1') 22 | data = [object_id, user_id].join 23 | OpenSSL::HMAC.hexdigest(digest, @secret, data) 24 | end 25 | 26 | def find_thread! 27 | object_type.camelcase.constantize.find(object_id) 28 | end 29 | 30 | def to_s 31 | "reply+#{@object_type}+#{@object_id}+#{signature}+#{@user_id}@m.coderwall.com" 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/models/sponsor.rb: -------------------------------------------------------------------------------- 1 | Sponsor = Struct.new(:id, :title, :cta, :text, :click_url, :image_url, :pixel_urls) do 2 | HOST = "srv.buysellads.com" 3 | PATH = "/ads/#{ENV['BSA_IDENTIFIER']}.json" 4 | 5 | class << self 6 | def ads_for(ip) 7 | return [] unless ENV['BSA_IDENTIFIER'].present? 8 | params = { forwardedip: ip } 9 | params.merge!( testMode: true, ignore: true ) if Rails.env.development? 10 | uri = URI::HTTPS.build(host: HOST, path: PATH, query: params.to_query) 11 | 12 | error = nil 13 | results = begin 14 | start = Time.now 15 | response = Faraday.new(url: uri).get do |req| 16 | req.options.timeout = 2 # open/read timeout in seconds 17 | req.options.open_timeout = 1 # connection open timeout in seconds 18 | end 19 | 20 | JSON.parse(response.body) rescue nil 21 | rescue Faraday::TimeoutError, Net::OpenTimeout, Faraday::ConnectionFailed => e 22 | error = e 23 | nil 24 | end 25 | Rails.logger.info "sponsor=#{error ? 'fail' : 'ok'} seconds=#{"%.2f" % (Time.now - start)} error=#{error}" 26 | 27 | return [] if results.nil? 28 | results['ads'].select{|a| a['creativeid'] }.collect{ |data| build_sponsor(data) } 29 | end 30 | 31 | def build_sponsor(data) 32 | Sponsor.new( 33 | data['creativeid'], 34 | data['title'], 35 | data['callToAction'], 36 | data['description'], 37 | data['statlink'], 38 | data['image'], 39 | (data['pixel'] || '').split('||') 40 | ) 41 | end 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/models/team.rb: -------------------------------------------------------------------------------- 1 | class Team < ApplicationRecord 2 | 3 | end 4 | 5 | # total_views ? 6 | 7 | # t.string "highlight_tags" 8 | # t.text "branding" 9 | # t.text "headline" 10 | # t.text "big_quote" 11 | # t.string "big_image" 12 | # t.string "featured_banner_image" 13 | # t.text "benefit_name_1" 14 | # t.text "benefit_description_1" 15 | # t.text "benefit_name_2" 16 | # t.text "benefit_description_2" 17 | # t.text "benefit_name_3" 18 | # t.text "benefit_description_3" 19 | # t.text "reason_name_1" 20 | # t.text "reason_description_1" 21 | # t.text "reason_name_2" 22 | # t.text "reason_description_2" 23 | # t.text "reason_name_3" 24 | # t.text "reason_description_3" 25 | # t.text "why_work_image" 26 | # t.text "organization_way" 27 | # t.text "organization_way_name" 28 | # t.text "organization_way_photo" 29 | # t.text "blog_feed" 30 | # t.text "our_challenge" 31 | # t.text "your_impact" 32 | # t.text "hiring_tagline" 33 | # t.text "link_to_careers_page" 34 | # t.text "stack_list", :default => t.string "office_photos", :default => t.text "interview_steps", :default => 35 | -------------------------------------------------------------------------------- /app/serializers/badge_serializer.rb: -------------------------------------------------------------------------------- 1 | class BadgeSerializer < ActiveModel::Serializer 2 | attributes :name, 3 | :description, 4 | :created_at, 5 | :badge 6 | 7 | protected 8 | def badge 9 | ActionController::Base.helpers.asset_path(object.path) 10 | end 11 | 12 | end 13 | -------------------------------------------------------------------------------- /app/serializers/comment_serializer.rb: -------------------------------------------------------------------------------- 1 | class CommentSerializer < ActiveModel::Serializer 2 | attributes :id, 3 | :hearts, 4 | :heartableId 5 | 6 | protected 7 | def hearts 8 | object.hearts_count 9 | end 10 | 11 | def heartableId 12 | object.dom_id 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /app/serializers/current_user_serializer.rb: -------------------------------------------------------------------------------- 1 | class CurrentUserSerializer < UserSerializer 2 | attributes :email 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/job_serializer.rb: -------------------------------------------------------------------------------- 1 | class JobSerializer < ActiveModel::Serializer 2 | attributes :id 3 | end 4 | -------------------------------------------------------------------------------- /app/serializers/picture_serializer.rb: -------------------------------------------------------------------------------- 1 | class PictureSerializer < ActiveModel::Serializer 2 | attributes :name, 3 | :type, 4 | :created_at, 5 | :url 6 | 7 | protected 8 | def name 9 | object.file.file.filename 10 | end 11 | 12 | def type 13 | object.file.file.extension.downcase 14 | end 15 | 16 | def url 17 | object.file.url 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /app/serializers/protip_serializer.rb: -------------------------------------------------------------------------------- 1 | class ProtipSerializer < ActiveModel::Serializer 2 | include ActionView::Helpers 3 | 4 | attributes :id, 5 | :body, 6 | :created_at, 7 | :heartableId, 8 | :hearts, 9 | :html, 10 | :public_id, 11 | :subscribed, 12 | :tags, 13 | :title, 14 | :upvotes 15 | 16 | protected 17 | def title 18 | sanitize(object.title) 19 | end 20 | 21 | def body 22 | sanitize(object.body) 23 | end 24 | 25 | def html 26 | CoderwallFlavoredMarkdown.render_to_html(object.body) 27 | end 28 | 29 | def subscribed 30 | return false unless scope 31 | 32 | object.subscribers.include?(scope.id) 33 | end 34 | 35 | def heartableId 36 | object.dom_id 37 | end 38 | 39 | def hearts 40 | object.hearts_count 41 | end 42 | 43 | def upvotes 44 | object.hearts_count 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /app/serializers/user_serializer.rb: -------------------------------------------------------------------------------- 1 | class UserSerializer < ActiveModel::Serializer 2 | has_many :badges 3 | attributes :id, 4 | :username, 5 | :name, 6 | :location, 7 | :karma, 8 | :accounts, 9 | :about, 10 | :title, 11 | :company, 12 | :team, 13 | :thumbnail, 14 | :endorsements, 15 | :specialities 16 | 17 | protected 18 | def team 19 | object.team_id 20 | end 21 | 22 | #backwords compatibility 23 | def specialities 24 | [] 25 | end 26 | 27 | #backwords compatibility 28 | def endorsements 29 | karma 30 | end 31 | 32 | def accounts 33 | { github: object.github, twitter: object.twitter } 34 | end 35 | 36 | def thumbnail 37 | object.avatar.url 38 | end 39 | 40 | end 41 | -------------------------------------------------------------------------------- /app/services/notification.rb: -------------------------------------------------------------------------------- 1 | class Notification 2 | class LoggingClient 3 | def trigger(channel, event, data, options = {}) 4 | Rails.logger.info "[Pusher] #{channel} #{event} #{data.inspect}" 5 | end 6 | end 7 | 8 | class << self 9 | def pusher 10 | return LoggingClient.new if Rails.env.test? 11 | 12 | Pusher 13 | end 14 | 15 | def comment_added!(article, json, socket_id = nil) 16 | trigger(article, 'new-comment', json, socket_id) 17 | end 18 | 19 | protected 20 | 21 | def trigger(model, event, payload, socket_id) 22 | channel = to_chan(model) 23 | Rails.logger.info "[Pusher] #{channel} #{event} #{payload.inspect}" 24 | pusher.trigger(channel, event, payload, socket_id: socket_id) 25 | end 26 | 27 | def to_chan(model) 28 | # Pusher don't like global ids as channel names 29 | # this will convert it to something we can use 30 | model.to_global_id.to_s.split('/')[3..-1].join(',') 31 | end 32 | 33 | def self.to_model(chan) 34 | # and then convert it back to a global id 35 | gid = "gid://#{GlobalID.app}/#{chan.split(',').join('/')}" 36 | GlobalID.find(gid) 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /app/views/badges/_badge.html.haml: -------------------------------------------------------------------------------- 1 | .badge.clearfix.mb1 2 | .left.mr2=image_tag badge.path, width: 50, height: 50 3 | .overflow-hidden 4 | %h6.mt0="Unlocked #{badge.name}" 5 | .mt0=badge.description 6 | -------------------------------------------------------------------------------- /app/views/clearance_mailer/change_password.html.erb: -------------------------------------------------------------------------------- 1 |

<%= t(".opening") %>

2 | 3 |

4 | <%= link_to t(".link_text", default: "Change my password"), 5 | edit_user_password_url(@user, token: @user.confirmation_token.html_safe) %> 6 |

7 | 8 |

<%= raw t(".closing") %>

9 | -------------------------------------------------------------------------------- /app/views/clearance_mailer/change_password.text.erb: -------------------------------------------------------------------------------- 1 | <%= t(".opening") %>

2 | 3 | <%= edit_user_password_url(@user, token: @user.confirmation_token.html_safe) %> 4 | 5 | <%= raw t(".closing") %> 6 | -------------------------------------------------------------------------------- /app/views/comment_mailer/new_comment.html.erb: -------------------------------------------------------------------------------- 1 | 10 | 11 | <%= sanitize(CoderwallFlavoredMarkdown.render_to_html(@comment.body)) %> 12 | 13 |

14 | — 15 | You are receiving this because you either wrote or commented on this protip. 16 |
17 | View on Coderwall, 18 | or 19 | mute this thread. 20 |

21 | 22 | 34 | -------------------------------------------------------------------------------- /app/views/comments/_comment.html.haml: -------------------------------------------------------------------------------- 1 | - cache ['v3', comment, current_user_can_edit?(comment)] do 2 | - style ||= :large 3 | .inline-block.py1.mb1[comment]{class: ('border-top' if style != :small), style: 'width: 100%'} 4 | 5 | .left.mt1.mr2.avatar.small{style:"background-color: #{comment.user.color};"} 6 | =avatar_url_tag(comment.user) 7 | .overflow-hidden.py0.mt0 8 | .clearfix 9 | .author[:author] 10 | %a.bold.black.no-hover[:alternateName]{href: profile_path(username: comment.user.username)} 11 | =comment.user.username 12 | .content.small[:text]= preserve(sanitize(CoderwallFlavoredMarkdown.render_to_html(comment.body))) 13 | - if style != :small 14 | .diminish.mt1 15 | ==#{time_ago_in_words_with_ceiling(comment.created_at)} ago 16 | -if current_user_can_edit?(comment) 17 | · 18 | = button_to comment_path(comment), method: :delete, data: { confirm: "Are you sure you want to delete your comment?" }, form_class: "inline plain" do 19 | = icon('trash') 20 | -------------------------------------------------------------------------------- /app/views/comments/_comment.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! comment, :id 2 | json.created_at comment.video_timestamp 3 | json.authorUrl user_path(comment.user) 4 | json.authorUsername comment.user.username 5 | json.markup sanitize(CoderwallFlavoredMarkdown.render_to_html(comment.body)) 6 | -------------------------------------------------------------------------------- /app/views/comments/index.html.haml: -------------------------------------------------------------------------------- 1 | .container 2 | .clearfix 3 | .md-col.md-show.md-col-4 4 | .sm-col.sm-col.sm-col-12.md-col-8 5 | -@comments.each do |comment| 6 | %h6.mt1 7 | =link_to comment.article.title, seo_protip_path(comment.article) 8 | =render comment 9 | .bold.mb4 10 | =link_to("Delete #{comment.user.username} and their #{comment.user.comments.size} comments", user_path(comment.user), method: :delete, class: 'diminish mr1') 11 | 12 | .clearfix 13 | .btn.left= link_to_previous_page @comments, 'Previous' 14 | .btn.right= link_to_next_page @comments, 'Next' 15 | .md-col.md-show.md-col-4 16 | -------------------------------------------------------------------------------- /app/views/comments/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.comments @comments, partial: 'comments/comment', as: :comment 2 | -------------------------------------------------------------------------------- /app/views/comments/show.js.erb: -------------------------------------------------------------------------------- 1 | document.getElementById('comments'). 2 | appendChild("<%= escape_javascript(render partial: 'comment', locals: { comment: @comment, style: :small } ) %>"); 3 | var chat = document.getElementById('chat'); 4 | chat.scrollTop = chat.scrollHeight; 5 | -------------------------------------------------------------------------------- /app/views/jobs/_job.html.haml: -------------------------------------------------------------------------------- 1 | -cache ['v1', job, feature] do 2 | .job.card.clearfix.mb2[job]{ class: ('border' if feature) } 3 | .clearfix.p2 4 | .col.col-1.md-show 5 | - if job.company_logo.present? 6 | =image_tag(job.company_logo, width: 50) 7 | - else 8 |   9 | 10 | .col.col-8 11 | .ml1.mr1 12 | %h3.mt0 13 | %a.diminish-viewed[:title]{:href => job.source, rel: 'nofollow', target: '_blank', 'ga-event-category' => 'Jobs', 'ga-event-action' => 'Job Board - Job', 'ga-event-label' => "#{job.company} - #{job.id}"}=job.title 14 | .font-sm 15 | .bold.inline=link_to(truncate(job.company, length:20), job.company_url, rel: 'nofollow', target: '_blank', 'ga-event-category' => 'Jobs', 'ga-event-action' => 'Job Board - Job', 'ga-event-label' => "#{job.company} - #{job.id}") 16 | · 17 | .diminish.inline=job.role_type 18 | · 19 | .diminish.inline==posted #{time_ago_in_words(job.created_at)} ago 20 | 21 | .col.col-3 22 | .mt1.right-align.diminish 23 | =job.location 24 | -------------------------------------------------------------------------------- /app/views/jobs/_mini.html.haml: -------------------------------------------------------------------------------- 1 | .clearfix.py1 2 | %a.link.no-hover.mt2{:href => job.source, rel: 'nofollow', target: '_blank', 'ga-event-category' => 'Jobs', 'ga-event-action' => "#{location} - Featured Job", 'ga-event-label' => "#{job.company} - #{job.id}"} 3 | .col.col-3.md-col-2{class: (job.company_logo.present? ? '' : 'hide')} 4 | =image_tag(job.company_logo, class: '') if job.company_logo.present? 5 | .overflow-hidden.pl2 6 | .blue.bold 7 | =job.title 8 | .font-sm.black 9 | .inline=link_to(truncate(job.company, length:18), job.company_url, rel: 'nofollow', target: '_blank', 'ga-event-category' => 'Jobs', 'ga-event-action' => "#{location} - Featured Job", 'ga-event-label' => "#{job.company} - #{job.id}") 10 | · 11 | .inline=job.location 12 | · 13 | .inline=job.role_type 14 | -------------------------------------------------------------------------------- /app/views/jobs/new.html.haml: -------------------------------------------------------------------------------- 1 | -title 'Post a Job, find & hire great programmers' 2 | -description 'Need programming help to build something challenging? Post a job for 30 days for only $299.' 3 | 4 | %script(src="https://checkout.stripe.com/checkout.js") 5 | 6 | .container 7 | %h1 Find and hire great programmers 8 | .clearfix 9 | .sm-col.sm-col.sm-col-12.md-col-8 10 | .mb2.purple{style: "border-bottom:solid 5px;"} 11 | .card.p3 12 | %p 13 | Fill in your details about your job and we'll feature it to the entire Coderwall community for 14 | %strong 30 days for only $299. 15 | 16 | -@job.errors.full_messages.each do |error| 17 | %p.red.bold=error 18 | 19 | .mt2.diminish 20 | Coderwall securely accepts all major credit cards. 21 | 22 | .clearfix.mt2 23 | = link_to "Cancel", jobs_path 24 | 25 | .md-col.md-col-4.md-show 26 | .ml3 27 | .clearfix 28 | .bg-white.rounded.p2 29 | %p Need programming help to build something challenging? Post a job and we'll feature it to the best developers using Coderwall each month. 30 | 31 | %hr.mt1 32 | 33 | %h5.mt3.mb2 34 | =icon('smile-o', class: 'mr1') 35 | Guaranteed Happiness 36 | 37 | %p If you're not fully satisfied we'll give you a free listing or a full refund - your choice. Just let us know within 30 days after your listing expires. 38 | 39 | .clearfix.mt1 40 | %p.bold.p2 41 | Have questions? 42 | %a{href:'mailto:support@coderwall.com'} Contact us 43 | -------------------------------------------------------------------------------- /app/views/layouts/minimal.html.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html 3 | %head 4 | %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"} 5 | %meta{property: 'current_user:id', content: current_user.try(:id)} 6 | = display_meta_tags(default_meta_tags) 7 | = stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => 'reload' 8 | = stylesheet_link_tag 'minimal', media: 'all', 'data-turbolinks-track' => 'reload' 9 | = javascript_include_tag 'application', 'data-turbolinks-track' => 'reload' 10 | = javascript_include_tag 'https://content.jwplatform.com/libraries/pEaCoeG7.js' 11 | = csrf_meta_tags 12 | = render 'shared/analytics' 13 | = yield :head 14 | %body 15 | =yield 16 | 17 | -# gdpr disabled render 'shared/tracking' 18 | -------------------------------------------------------------------------------- /app/views/pages/faq.html.haml: -------------------------------------------------------------------------------- 1 | - title "FAQ" 2 | 3 | .container.clearfix 4 | %h1 FAQ 5 | %h3.mt3= link_to 'How do I delete my account?', '#', 'name' => 'deleteaccount' 6 | %p 7 | You must be logged in to delete your account. 8 | Once you are logged in visit 9 | %a{href: 'https://coderwall.com/delete_account', rel: 'nofollow'} https://coderwall.com/delete_account 10 | and locate the trash icon next to the edit button. Please note this action is irreversible. 11 | 12 | %h3.mt3= link_to 'What happened to the badges?!', '#', 'name' => 'profileupdates' 13 | %p We miss them too! We're still hoping we'll get them back into the site one day. 14 | 15 | %h3.mt3= link_to 'Can I help Coderwall?', '#', 'name' => 'source' 16 | %p You sure can! You can find the [source on GitHub.](https://github.com/coderwall/coderwall-next] 17 | 18 | 19 | :javascript 20 | var h = window.screen.height; 21 | var w = window.screen.width; 22 | var r = w / h; 23 | 24 | var base = document.getElementById('base-resolution'); 25 | var calc = document.getElementById('calc-resolution'); 26 | 27 | base.textContent = w + 'x' + h; 28 | calc.textContent = r == 1.6 ? ('1280x800') : base.textContent; 29 | -------------------------------------------------------------------------------- /app/views/pages/not_found.html.haml: -------------------------------------------------------------------------------- 1 | - title '404 : Unable to handle that url' 2 | 3 | .clearfix 4 | .col.col-12{style:'margin-top: 10%;'} 5 | .center 6 | %h1 404! Our feels when that url is used 7 | =icon('thumbs-o-down', style:'font-size:80px') 8 | -------------------------------------------------------------------------------- /app/views/pages/server_error.html.haml: -------------------------------------------------------------------------------- 1 | - title "500 : Coderwall is having some server issues" 2 | 3 | .clearfix 4 | .col.col-12{style:'margin-top: 10%;'} 5 | .center 6 | %h1 I don't feel so good 7 | %p.mb2.font-lg Coderwall is having some temporary server issues, sorry. 8 | =icon('frown-o', style:'font-size:80px') 9 | -------------------------------------------------------------------------------- /app/views/pages/styleguide.html.erb: -------------------------------------------------------------------------------- 1 |

Protips

2 | 3 |
4 |
5 |

6 |

7 |
8 | 9 | <%= pluralize(72, 'responses') %> 10 | 11 | · 12 | <%= time_ago_in_words_with_ceiling(4.hours.ago) %> ago 13 | · 14 |
15 |
16 | whatupdave 17 |
18 |
19 |
20 | -------------------------------------------------------------------------------- /app/views/passwords/create.html.haml: -------------------------------------------------------------------------------- 1 | - title 'A page to tell you about your incoming email' 2 | 3 | %h2 Wow, such a fancy address 4 | %p 5 | %strong If we have email address for your account 6 | then within the next few minutes you will receive an email. It contains a link to change your password. 7 | .clearfix.mt2 8 | %a.btn.mt1.rounded.bg-green.white{href: sign_in_path} Sign In 9 | -------------------------------------------------------------------------------- /app/views/passwords/edit.html.haml: -------------------------------------------------------------------------------- 1 | - title 'Change your password' 2 | 3 | %h2 Change your password 4 | %p Your password has been reset. Choose a new password below. 5 | = form_for :password_reset, | 6 | url: user_password_path(@user, token: @user.confirmation_token), | 7 | class: 'sm-col-6', | 8 | html: { method: :put } do |form| 9 | = form.label :password 10 | = form.password_field :password, class: 'block mb1 col-6 field' 11 | %button.btn.mt1.rounded.bg-green.white{type: 'submit'} Save password 12 | -------------------------------------------------------------------------------- /app/views/passwords/new.html.haml: -------------------------------------------------------------------------------- 1 | - title "Reset your password" 2 | 3 | .container 4 | %h2 Reset your password 5 | .sm-col-6 6 | %p Enter the email address for your Coderwall account to be emailed a link so you can reset your password. 7 | = form_for :password, url: passwords_path do |form| 8 | = form.label :email 9 | = form.text_field :email, type: 'email', class: 'field block col-10 mb1' 10 | %button.btn.mt1.rounded.bg-green.white{type: 'submit'} Reset password 11 | .clearfix.mt2 12 | You just remembered it? Nice! 13 | = link_to "Sign In", sign_in_path 14 | -------------------------------------------------------------------------------- /app/views/protips/_protip.html.haml: -------------------------------------------------------------------------------- 1 | -cache ['v4', protip, hide_on_profile] do 2 | .protip.card.clearfix.py2.likeable[protip]{id: dom_id(protip)} 3 | .col.col-1{class: hide_on_profile} 4 | .col.col-11.overflow-hidden 5 | %h3.mt0.mb0 6 | %a.diminish-viewed[:headline]{:href => seo_protip_path(protip)}=protip.title 7 | .font-sm 8 | %span{class: hide_on_profile} 9 | =link_to protip.user.try(:username), profile_path(username: protip.user.username) 10 | .diminish.inline · 11 | 12 | .diminish.inline 13 | %a[:url]{href: slug_protips_url(id: protip.public_id, slug: protip.slug)} 14 | =pluralize(protip.comments.size, 'response') 15 | · 16 | =protip.display_tags 17 | -------------------------------------------------------------------------------- /app/views/sessions/new.html.haml: -------------------------------------------------------------------------------- 1 | - title "Sign in" 2 | 3 | .container 4 | %h2 5 | Sign In or 6 | = link_to "Join Coderwall", sign_up_path 7 | .sm-col-6 8 | =form_for :session, url: session_path do |form| 9 | .mb2.font-sm.diminish 10 | NOTE: If you previously signed in using your Twitter or GitHub account, you'll now need to 11 | = link_to "reset your password", new_password_path 12 | to get a new first time password to further access your account. 13 | = form.label :email, "Email or Username" 14 | = form.text_field :email, type: 'text', class: 'field block col-10 mb1' 15 | = form.label :password 16 | = form.password_field :password, class: 'field block col-10 mb1' 17 | %button.btn.mt1.rounded.bg-green.white{type: 'submit'} Sign In 18 | .clearfix.mt3 19 | = link_to "Reset a forgotten password", new_password_path 20 | .clearfix.mt2 21 | Don't have an account? 22 | .inline.bold= link_to "Sign Up", sign_up_path 23 | -------------------------------------------------------------------------------- /app/views/shared/_analytics.html.erb: -------------------------------------------------------------------------------- 1 | <% if ENV['GOOGLE_ANALYTICS_UA'].present? %> 2 | 11 | <% else #LOG EVENTS DIRECTLY TO WEB CONSOLE %> 12 | 19 | <% end %> 20 | -------------------------------------------------------------------------------- /app/views/shared/_logo.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/views/shared/_tracking.html.erb: -------------------------------------------------------------------------------- 1 | <% if Rails.env.production? %> 2 | 14 | <% end %> 15 | -------------------------------------------------------------------------------- /app/views/teams/show.html.haml: -------------------------------------------------------------------------------- 1 | - title @team.name 2 | 3 | .container[@team] 4 | .clearfix 5 | .md-col.md-col-2.md-show   6 | .sm-col.sm-col.sm-col-12.md-col-8 7 | .card.p3{style: "border-top:solid 5px #{@team.color}"} 8 | %h1=@team.name 9 | .clearfix 10 | -if @team.twitter.present? 11 | =link_to icon("twitter", class: "fa-1x", style: "color: #{@team.color}"), "https://twitter.com/#{@team.twitter}" 12 | .hide_last_child.inline · 13 | -if @team.github.present? 14 | =link_to icon("github", class: "fa-1x", style: "color: #{@team.color}"), "http://github.com/#{@team.github}" 15 | .hide_last_child.inline · 16 | .content.mt1[:description] 17 | = sanitize CoderwallFlavoredMarkdown.render_to_html(@team.about) 18 | .md-col.md-col-2.md-show   19 | -------------------------------------------------------------------------------- /app/views/user_mailer/destroy_email.text.erb: -------------------------------------------------------------------------------- 1 | Created: <%= time_ago_in_words(@user.created_at) %> ago. 2 | Protips: <%= @user.protips.count %> 3 | Comments: <%= @user.comments.count %> 4 | Email: <%= @user.email %> 5 | GitHub: <%= @user.github %> 6 | -------------------------------------------------------------------------------- /app/views/user_mailer/partnership_expired.text.erb: -------------------------------------------------------------------------------- 1 | Hi <%= @user.username %> 2 | 3 | You are receiving this email because you were a member of Assembly and a Contributing Partner on Coderwall. Unfortunately your <%= @user.ownership %>% of Coderwall App Coins expired on <%= (@user.partner_last_contribution_at + 1.year).strftime("%m/%d/%Y") %>. This was because more then 1 year had passed since your made your last eligible contribution on <%= @user.partner_last_contribution_at.strftime("%m/%d/%Y")%>. Without Coderwall App Coins you are no longer eligible to receive proceeds from Coderwall's future earnings but you will still receive distributions of earnings for the months where you had valid Coderwall App Coins. 4 | 5 | If you would like to stay active and participate in Coderwall we'd like to extend to you a 90 day grace period from today. Any eligible contributions you make to Coderwall before <%= 90.days.from_now.strftime("%m/%d/%Y") %> will earn you back all of your expired Coderwall App Coins. Check out the latest guidelines[1] in the Coderwall Github Repository[2] to start contributing again. 6 | 7 | If you believe there was an error, have any questions, or would like to explore other ways to contribute you can contact us anytime at support@coderwall.com or by joining the Coderwall Partners' Slack Channel [3]. 8 | 9 | Regards 10 | Matt, Dave, and the Coderwall Team 11 | 12 | [1] https://github.com/coderwall/coderwall-next/blob/master/CONTRIBUTING.md 13 | [2] https://github.com/coderwall/coderwall-next 14 | [3] http://slack.coderwall.com 15 | -------------------------------------------------------------------------------- /app/views/users/new.html.haml: -------------------------------------------------------------------------------- 1 | - title 'Join today!' 2 | 3 | .container 4 | %h2 Join Coderwall Today 5 | .sm-col-6 6 | %p Become a better programmer. Discover helpful protips, unlock achievements, and connect with other developers 7 | -@user.errors.full_messages.each do |error| 8 | %p.red.bold=error 9 | = form_for @user do |form| 10 | = form.label :username 11 | = form.text_field :username, type: 'text', class: 'field block col-10 mb1' 12 | = form.label :email 13 | = form.text_field :email, type: 'email', class: 'field block col-10 mb1' 14 | = form.label :password 15 | = form.password_field :password, class: 'field block col-10 mb1' 16 | .g-recaptcha{"data-sitekey" => ENV['CAPTCHA_SITE_KEY']} 17 | %button.btn.mt1.rounded.bg-green.white{type: 'submit'} Sign Up 18 | .mt1.font-sm.diminish 19 | Creating an account means you’re okay with Coderwall's 20 | =link_to 'Terms of Service', tos_path 21 | .clearfix.mt3 22 | Already have an account? 23 | .inline.bold= link_to "Sign In", sign_in_path 24 | 25 | 26 | -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a starting point to setup your application. 15 | # Add necessary setup steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | # puts "\n== Copying sample files ==" 22 | # unless File.exist?('config/database.yml') 23 | # cp 'config/database.yml.sample', 'config/database.yml' 24 | # end 25 | 26 | puts "\n== Preparing database ==" 27 | system! 'bin/rails db:setup' 28 | 29 | puts "\n== Removing old logs and tempfiles ==" 30 | system! 'bin/rails log:clear tmp:clear' 31 | 32 | puts "\n== Restarting application server ==" 33 | system! 'bin/rails restart' 34 | end 35 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | if (match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m)) 11 | Gem.paths = { 'GEM_PATH' => [Bundler.bundle_path.to_s, *Gem.path].uniq } 12 | gem 'spring', match[1] 13 | require 'spring/binstub' 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'pathname' 3 | require 'fileutils' 4 | include FileUtils 5 | 6 | # path to your application root. 7 | APP_ROOT = Pathname.new File.expand_path('../../', __FILE__) 8 | 9 | def system!(*args) 10 | system(*args) || abort("\n== Command #{args} failed ==") 11 | end 12 | 13 | chdir APP_ROOT do 14 | # This script is a way to update your development environment automatically. 15 | # Add necessary update steps to this file. 16 | 17 | puts '== Installing dependencies ==' 18 | system! 'gem install bundler --conservative' 19 | system('bundle check') || system!('bundle install') 20 | 21 | puts "\n== Updating database ==" 22 | system! 'bin/rails db:migrate' 23 | 24 | puts "\n== Removing old logs and tempfiles ==" 25 | system! 'bin/rails log:clear tmp:clear' 26 | 27 | puts "\n== Restarting application server ==" 28 | system! 'bin/rails restart' 29 | end 30 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015", "stage-0", "react"] 3 | } 4 | -------------------------------------------------------------------------------- /client/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "parser": "babel-eslint", 4 | 5 | "rules": { 6 | "dot-location": ["error", "object"], 7 | "import/no-extraneous-dependencies": ["error", {"devDependencies": ["**/__tests__/**"]}], 8 | "import/no-named-as-default": 0, 9 | "no-global-assign": ["error", { exceptions: [] }], 10 | "quotes": 0, 11 | "react/jsx-closing-bracket-location": 0, 12 | "react/no-multi-comp": 0, 13 | "react/sort-comp": [2, { 14 | order: [ 15 | 'static-methods', 16 | 'constructor', 17 | 'render', 18 | 'lifecycle', 19 | 'everything-else' 20 | ] 21 | }], 22 | "semi": [2, "never"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/actions/heartActions.js: -------------------------------------------------------------------------------- 1 | import { CALL_API } from 'redux-api-middleware' 2 | 3 | export const HEART_REQUEST = 'HEART_REQUEST' 4 | export const HEART_SUCCESS = 'HEART_SUCCESS' 5 | export const HEART_FAILURE = 'HEART_FAILURE' 6 | 7 | export function heart(endpoint, heartableId, userId) { 8 | return { 9 | [CALL_API]: { 10 | endpoint, 11 | method: 'POST', 12 | types: [ 13 | { type: HEART_REQUEST, payload: { heartableId, userId } }, 14 | HEART_SUCCESS, 15 | HEART_FAILURE, 16 | ], 17 | }, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /client/actions/jobActions.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | import { CALL_API } from 'redux-api-middleware' 3 | 4 | export const JOB_POST_REQUEST = 'JOB_POST_REQUEST' 5 | export const JOB_POST_SUCCESS = 'JOB_POST_SUCCESS' 6 | export const JOB_POST_FAILURE = 'JOB_POST_FAILURE' 7 | 8 | export function createPost(stripeToken, job) { 9 | return { 10 | [CALL_API]: { 11 | endpoint: `/jobs`, 12 | method: 'POST', 13 | body: JSON.stringify({ stripeToken, job }), 14 | types: [ 15 | JOB_POST_REQUEST, 16 | JOB_POST_SUCCESS, 17 | JOB_POST_FAILURE, 18 | ], 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/actions/protipActions.js: -------------------------------------------------------------------------------- 1 | import { CALL_API } from 'redux-api-middleware' 2 | 3 | export const PROTIP_MUTE_REQUEST = 'PROTIP_MUTE_REQUEST' 4 | export const PROTIP_MUTE_SUCCESS = 'PROTIP_MUTE_SUCCESS' 5 | export const PROTIP_MUTE_FAILURE = 'PROTIP_MUTE_FAILURE' 6 | export const PROTIP_SUBSCRIBE_REQUEST = 'PROTIP_SUBSCRIBE_REQUEST' 7 | export const PROTIP_SUBSCRIBE_SUCCESS = 'PROTIP_SUBSCRIBE_SUCCESS' 8 | export const PROTIP_SUBSCRIBE_FAILURE = 'PROTIP_SUBSCRIBE_FAILURE' 9 | 10 | export function mute(protipId, userId) { 11 | return { 12 | [CALL_API]: { 13 | endpoint: `/p/${protipId}/subscribers`, 14 | method: 'DELETE', 15 | types: [ 16 | { type: PROTIP_MUTE_REQUEST, payload: { protipId, userId } }, 17 | PROTIP_MUTE_SUCCESS, 18 | PROTIP_MUTE_FAILURE, 19 | ], 20 | }, 21 | } 22 | } 23 | 24 | export function subscribe(protipId, userId) { 25 | return { 26 | [CALL_API]: { 27 | endpoint: `/p/${protipId}/subscribers`, 28 | method: 'POST', 29 | types: [ 30 | { type: PROTIP_SUBSCRIBE_REQUEST, payload: { protipId, userId } }, 31 | PROTIP_SUBSCRIBE_SUCCESS, 32 | PROTIP_SUBSCRIBE_FAILURE, 33 | ], 34 | }, 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client/checkLinks.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | /* eslint-disable */ 3 | const roboto = require('roboto') 4 | 5 | const crawler = new roboto.Crawler({ 6 | startUrls: [ 7 | 'http://coderwall.dev:5000/', 8 | ], 9 | // We don't want it crawling outside links. 10 | constrainToRootDomains: true, 11 | }) 12 | 13 | const deadLinks = [] 14 | crawler.on('httpError', (statusCode, href, referer) => { 15 | if (statusCode === 404) { 16 | console.log('Dead link: %s found on page: %s', href, referer) 17 | deadLinks.push({ 18 | href, 19 | referer, 20 | }) 21 | } 22 | }) 23 | 24 | crawler.on('finish', () => { 25 | for (let i = 0; i < deadLinks.length; i++) { 26 | const deadLink = deadLinks[i] 27 | console.log('Dead link: %s found on page: %s', deadLink.href, deadLink.referer) 28 | } 29 | }) 30 | 31 | crawler.crawl() 32 | -------------------------------------------------------------------------------- /client/components/Heart.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes as T } from 'react' 2 | import Icon from './Icon' 3 | 4 | const numberToHuman = (number) => { 5 | if (number > 0) { 6 | const s = ['', 'K', 'M'] 7 | const e = Math.floor(Math.log(number) / Math.log(1000)) 8 | return (number / Math.pow(1000, e)).toFixed(0) + s[e] 9 | } 10 | 11 | return 0 12 | } 13 | 14 | const renderCount = (cnt) => ( 15 |
16 | {numberToHuman(cnt)} 17 |
18 | ) 19 | 20 | const renderLabels = (hearted, [off, on]) => ( 21 | 22 | {hearted ? on : off} 23 | 24 | ) 25 | 26 | const Heart = ({ hearted, labels, count, onClick }) => { 27 | const icon = hearted ? 'heart' : 'heart-o' 28 | return ( 29 |
30 | 31 | 32 | 33 | 34 | 35 | {labels ? renderLabels(hearted, labels) : renderCount(count)} 36 |
37 | ) 38 | } 39 | 40 | Heart.propTypes = { 41 | count: T.number, 42 | hearted: T.bool, 43 | onClick: T.func, 44 | labels: T.arrayOf(T.string), 45 | } 46 | 47 | export default Heart 48 | -------------------------------------------------------------------------------- /client/components/HeartButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes as T } from 'react' 2 | import { connect } from 'react-redux' 3 | import Heart from './Heart' 4 | import { heart } from '../actions/heartActions' 5 | 6 | class HeartButton extends React.Component { 7 | static propTypes = { 8 | count: T.number, 9 | currentUser: T.object, 10 | dispatch: T.func.isRequired, 11 | heartableId: T.string, 12 | hearted: T.bool, 13 | href: T.string.isRequired, 14 | labels: T.arrayOf(T.string), 15 | } 16 | 17 | render() { 18 | return ( 19 | this.handleClick()} 23 | count={this.props.count} /> 24 | ) 25 | } 26 | 27 | handleClick() { 28 | if (this.props.hearted) { return } 29 | 30 | this.props.dispatch( 31 | heart( 32 | this.props.href, 33 | this.props.heartableId, 34 | this.props.currentUser && this.props.currentUser.id 35 | ) 36 | ) 37 | } 38 | } 39 | 40 | function mapStateToProps(state, ownProps) { 41 | const heartables = [ 42 | ...(state.protips.items || []), 43 | ...(state.comments.items || []), 44 | state.currentProtip.item, 45 | ].filter(h => h) 46 | 47 | const heartable = heartables.find(p => p.heartableId === ownProps.heartableId) 48 | 49 | if (!heartable) { return {} } 50 | 51 | const hearts = state.hearts.items || [] 52 | const hearted = hearts.indexOf(ownProps.heartableId) > -1 53 | 54 | return { 55 | hearted, 56 | count: heartable.hearts, 57 | } 58 | } 59 | 60 | export default connect(mapStateToProps)(HeartButton) 61 | -------------------------------------------------------------------------------- /client/components/Icon.jsx: -------------------------------------------------------------------------------- 1 | // http://fontawesome.io/icons/ 2 | import React, { PropTypes as T } from 'react' 3 | import classNames from 'classnames' 4 | 5 | const Icon = ({ icon, extraClasses }) => ( 6 | 7 | ) 8 | 9 | Icon.propTypes = { 10 | icon: T.string.isRequired, 11 | extraClasses: T.string, 12 | } 13 | 14 | export default Icon 15 | -------------------------------------------------------------------------------- /client/components/NewJob.jsx: -------------------------------------------------------------------------------- 1 | /* global alert, window, document, Image, StripeCheckout */ 2 | import React, { Component, PropTypes as T } from 'react' 3 | import { connect } from 'react-redux' 4 | 5 | import JobForm from './JobForm' 6 | 7 | import { createPost } from '../actions/jobActions' 8 | 9 | class NewJob extends Component { 10 | static propTypes = { 11 | cost: T.number.isRequired, 12 | dispatch: T.func.isRequired, 13 | error: T.string, 14 | job: T.object, 15 | stripePublishable: T.string.isRequired, 16 | } 17 | 18 | render() { 19 | return 20 | } 21 | 22 | componentDidUpdate(prevProps) { 23 | if (!prevProps.job && this.props.job && this.props.job.id) { 24 | window.location = `/jobs?posted=${this.props.job.id}` 25 | } 26 | 27 | if (!prevProps.error && this.props.error) { 28 | alert("Unable to charge this card. Please try again") // eslint-disable-line no-alert 29 | } 30 | } 31 | 32 | handleSubmit = (values) => new Promise((resolve) => { 33 | const onStripeTokenSet = token => this.props.dispatch(createPost(token.id, values)) 34 | 35 | this.checkout = this.checkout || StripeCheckout.configure({ 36 | key: this.props.stripePublishable, 37 | image: 'https://s3.amazonaws.com/stripe-uploads/A6CJ1PO8BNz85yiZbRZwpGOSsJc5yDvKmerchant-icon-356788-cwlogo.png', 38 | locale: 'auto', 39 | token: onStripeTokenSet, 40 | closed: resolve, 41 | }) 42 | 43 | this.checkout.open({ 44 | name: "Jobs @ coderwall.com", 45 | description: "30 day listing", 46 | amount: this.props.cost, 47 | }) 48 | }) 49 | } 50 | 51 | const mapStateToProps = (state) => ({ 52 | job: state.job.item, 53 | error: state.job.error, 54 | }) 55 | 56 | export default connect(mapStateToProps)(NewJob) 57 | -------------------------------------------------------------------------------- /client/components/ProtipSubscribeButton.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component, PropTypes as T } from 'react' 2 | import { connect } from 'react-redux' 3 | 4 | import ToggleWithLabel from './ToggleWithLabel' 5 | import { subscribe, mute } from '../actions/protipActions' 6 | 7 | class ProtipSubscribeButton extends Component { 8 | static propTypes = { 9 | currentUser: T.object, 10 | dispatch: T.func.isRequired, 11 | protipId: T.number, 12 | subscribed: T.bool, 13 | } 14 | 15 | render() { 16 | return ( 17 | 24 | ) 25 | } 26 | 27 | handleClick = () => { 28 | const action = this.props.subscribed ? mute : subscribe 29 | this.props.dispatch( 30 | action(this.props.protipId, this.props.currentUser && this.props.currentUser.id) 31 | ) 32 | } 33 | } 34 | 35 | function mapStateToProps(state) { 36 | const protip = state.currentProtip.item 37 | const subscribed = protip.subscribed 38 | 39 | return { 40 | protipId: protip.id, 41 | subscribed, 42 | } 43 | } 44 | 45 | export default connect(mapStateToProps)(ProtipSubscribeButton) 46 | -------------------------------------------------------------------------------- /client/components/ToggleWithLabel.jsx: -------------------------------------------------------------------------------- 1 | import React, { PropTypes as T } from 'react' 2 | import Icon from './Icon' 3 | 4 | const ToggleWithLabel = (props) => { 5 | const { icon, label } = props.on ? { 6 | icon: props.iconOn, 7 | label: props.labelOn, 8 | } : { 9 | icon: props.iconOff, 10 | label: props.labelOff, 11 | } 12 | return ( 13 |
14 | 15 | {label} 16 |
17 | ) 18 | } 19 | 20 | ToggleWithLabel.propTypes = { 21 | iconOff: T.string.isRequired, 22 | iconOn: T.string.isRequired, 23 | labelOff: T.string.isRequired, 24 | labelOn: T.string.isRequired, 25 | on: T.bool.isRequired, 26 | onClick: T.func.isRequired, 27 | } 28 | 29 | export default ToggleWithLabel 30 | -------------------------------------------------------------------------------- /client/components/TrackClick.jsx: -------------------------------------------------------------------------------- 1 | /* global ga */ 2 | import React, { Component, PropTypes as T } from 'react' 3 | 4 | export default class TrackClick extends Component { 5 | static propTypes = { 6 | action: T.string.isRequired, 7 | category: T.string.isRequired, 8 | children: T.node.isRequired, 9 | label: T.string.isRequired, 10 | } 11 | 12 | render() { 13 | return
{this.props.children}
14 | } 15 | 16 | handleClick = () => { 17 | ga('send', 'event', { 18 | eventCategory: this.props.category, 19 | eventAction: this.props.action, 20 | eventLabel: this.props.label, 21 | transport: 'beacon', 22 | }) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /client/components/__tests__/JobForm-test.jsx: -------------------------------------------------------------------------------- 1 | /* global test, expect */ 2 | import React from 'react' 3 | import renderer from 'react-test-renderer' 4 | import { combineReducers, createStore } from 'redux' 5 | import { Provider } from 'react-redux' 6 | import { reduxForm, reducer as form } from 'redux-form' 7 | 8 | import { JobForm } from '../JobForm' 9 | 10 | test('renders correctly', () => { 11 | const store = createStore( 12 | combineReducers({ form }), 13 | { form: {} } 14 | ) 15 | 16 | const Decorated = reduxForm({ form: 'testForm' })(JobForm) 17 | const tree = renderer.create( 18 | 19 | 20 | 21 | ) 22 | expect(tree).toMatchSnapshot() 23 | }) 24 | -------------------------------------------------------------------------------- /client/lib/apiAuthInjector.js: -------------------------------------------------------------------------------- 1 | /* global document */ 2 | import { CALL_API } from 'redux-api-middleware' 3 | 4 | export default () => next => action => { 5 | const callApi = action[CALL_API] 6 | 7 | // Check if this action is a redux-api-middleware action. 8 | if (callApi) { 9 | // Inject the CSRF token 10 | callApi.headers = { 11 | 'X-CSRF-Token': document.getElementsByName('csrf-token')[0].content, 12 | Accept: 'application/json', 13 | 'Content-Type': 'application/json', 14 | ...callApi.headers, 15 | } 16 | callApi.credentials = callApi.credentials || 'same-origin' 17 | } 18 | 19 | // Pass the FSA to the next action. 20 | return next(action) 21 | } 22 | -------------------------------------------------------------------------------- /client/lib/createReducer.js: -------------------------------------------------------------------------------- 1 | export default function createReducer(initialState, handlers) { 2 | if (Object.keys(handlers).filter(k => !k || k.length === 0 || k === 'undefined').length > 0) { 3 | throw new Error("Tried to create reducer with empty keys") 4 | } 5 | 6 | return (state, action) => { 7 | const handler = handlers[action.type] 8 | if (handler) { 9 | return { 10 | ...state, 11 | ...handler(action, state, initialState), 12 | } 13 | } 14 | 15 | return state || initialState 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /client/lib/loadImage.js: -------------------------------------------------------------------------------- 1 | /* global Image, Promise */ 2 | export default function loadImage(url, timeout = 5000) { 3 | return new Promise((resolve, reject) => { 4 | let timedOut = false 5 | let timer 6 | const img = new Image() 7 | img.onerror = img.onabort = () => { 8 | if (!timedOut) { 9 | clearTimeout(timer) 10 | reject() 11 | } 12 | } 13 | img.onload = () => { 14 | if (!timedOut) { 15 | clearTimeout(timer) 16 | resolve() 17 | } 18 | } 19 | img.src = url 20 | timer = setTimeout(() => { 21 | timedOut = true 22 | reject() 23 | }, timeout) 24 | }) 25 | } 26 | -------------------------------------------------------------------------------- /client/lib/unauthorizedHandler.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | export default () => next => action => { 3 | if (action.payload && action.payload.status === 401) { 4 | window.location = '/signin' 5 | return 6 | } 7 | 8 | next(action) 9 | } 10 | -------------------------------------------------------------------------------- /client/reducers/commentsReducer.js: -------------------------------------------------------------------------------- 1 | import createReducer from '../lib/createReducer' 2 | 3 | import { 4 | HEART_REQUEST, 5 | } from '../actions/heartActions' 6 | 7 | const incHeart = (comments, id) => { 8 | if (!comments) { return null } 9 | const index = comments.findIndex(p => p.heartableId === id) 10 | if (index === -1) { return comments } 11 | const heartable = comments[index] 12 | return [ 13 | ...comments.slice(0, index), 14 | { ...heartable, hearts: heartable.hearts + 1 }, 15 | ...comments.slice(index + 1), 16 | ] 17 | } 18 | 19 | export default createReducer({ 20 | items: null, 21 | }, { 22 | [HEART_REQUEST]: ({ payload: { heartableId } }, state) => ({ 23 | items: incHeart(state.items, heartableId), 24 | }), 25 | }) 26 | -------------------------------------------------------------------------------- /client/reducers/currentProtipReducer.js: -------------------------------------------------------------------------------- 1 | import createReducer from '../lib/createReducer' 2 | 3 | import { 4 | PROTIP_MUTE_REQUEST, 5 | PROTIP_MUTE_FAILURE, 6 | PROTIP_SUBSCRIBE_REQUEST, 7 | PROTIP_SUBSCRIBE_FAILURE, 8 | } from '../actions/protipActions' 9 | 10 | import { 11 | HEART_REQUEST, 12 | } from '../actions/heartActions' 13 | 14 | const incHeart = (tip, heartableId) => { 15 | // console.log({tip, heartableId}) 16 | if (!tip || tip.heartableId !== heartableId) { return tip } 17 | 18 | return { 19 | ...tip, 20 | hearts: tip.hearts + 1, 21 | subscribed: true, 22 | } 23 | } 24 | 25 | const setSubscribed = (subscribed) => (_, state) => ({ 26 | item: { ...state.item, subscribed }, 27 | }) 28 | 29 | export default createReducer({ 30 | item: null, 31 | }, { 32 | [PROTIP_SUBSCRIBE_REQUEST]: setSubscribed(true), 33 | [PROTIP_SUBSCRIBE_FAILURE]: setSubscribed(false), 34 | [PROTIP_MUTE_REQUEST]: setSubscribed(false), 35 | [PROTIP_MUTE_FAILURE]: setSubscribed(true), 36 | 37 | [HEART_REQUEST]: ({ payload: { heartableId } }, state) => ({ 38 | item: incHeart(state.item, heartableId), 39 | }), 40 | }) 41 | -------------------------------------------------------------------------------- /client/reducers/currentUserReducer.js: -------------------------------------------------------------------------------- 1 | import createReducer from '../lib/createReducer' 2 | 3 | export default createReducer({ 4 | item: null, 5 | }, {}) 6 | -------------------------------------------------------------------------------- /client/reducers/heartsReducer.js: -------------------------------------------------------------------------------- 1 | import createReducer from '../lib/createReducer' 2 | 3 | import { 4 | HEART_REQUEST, 5 | HEART_FAILURE, 6 | } from '../actions/heartActions' 7 | 8 | export default createReducer({ 9 | items: [], 10 | }, { 11 | [HEART_REQUEST]: ({ payload: { heartableId } }, state) => ({ 12 | items: [...state.items, heartableId], 13 | }), 14 | [HEART_FAILURE]: ({ payload: { heartableId } }, state) => ({ 15 | items: state.items.filter(i => i !== heartableId), 16 | }), 17 | }) 18 | -------------------------------------------------------------------------------- /client/reducers/index.js: -------------------------------------------------------------------------------- 1 | import comments from './commentsReducer' 2 | import currentProtip from './currentProtipReducer' 3 | import currentUser from './currentUserReducer' 4 | import hearts from './heartsReducer' 5 | import job from './jobReducer' 6 | import protips from './protipsReducer' 7 | 8 | export default { 9 | comments, 10 | currentProtip, 11 | currentUser, 12 | hearts, 13 | job, 14 | protips, 15 | } 16 | -------------------------------------------------------------------------------- /client/reducers/jobReducer.js: -------------------------------------------------------------------------------- 1 | import createReducer from '../lib/createReducer' 2 | 3 | import { 4 | JOB_POST_SUCCESS, 5 | JOB_POST_FAILURE, 6 | } from '../actions/jobActions' 7 | 8 | 9 | export default createReducer({ 10 | error: null, 11 | item: null, 12 | }, { 13 | [JOB_POST_SUCCESS]: ({ payload }) => ({ item: payload.job }), 14 | [JOB_POST_FAILURE]: ({ error }) => ({ error }), 15 | }) 16 | -------------------------------------------------------------------------------- /client/reducers/protipsReducer.js: -------------------------------------------------------------------------------- 1 | import createReducer from '../lib/createReducer' 2 | 3 | import { 4 | HEART_REQUEST, 5 | } from '../actions/heartActions' 6 | 7 | const incHeart = (protips, id) => { 8 | if (!protips) { return null } 9 | const index = protips.findIndex(p => p.heartableId === id) 10 | if (index === -1) { return protips } 11 | 12 | const heartable = protips[index] 13 | return [ 14 | ...protips.slice(0, index), 15 | { ...heartable, hearts: heartable.hearts + 1 }, 16 | ...protips.slice(index + 1), 17 | ] 18 | } 19 | 20 | export default createReducer({ 21 | items: null, 22 | }, { 23 | [HEART_REQUEST]: ({ payload: { heartableId } }, state) => ({ 24 | items: incHeart(state.items, heartableId), 25 | }), 26 | }) 27 | -------------------------------------------------------------------------------- /client/server-rails-hot.js: -------------------------------------------------------------------------------- 1 | /* eslint no-var: 0, no-console: 0, import/no-extraneous-dependencies: 0 */ 2 | 3 | import webpack from 'webpack' 4 | import WebpackDevServer from 'webpack-dev-server' 5 | 6 | import webpackConfig from './webpack.client.rails.hot.config' 7 | 8 | const hotRailsPort = process.env.HOT_RAILS_PORT || 3500 9 | 10 | const compiler = webpack(webpackConfig) 11 | 12 | const devServer = new WebpackDevServer(compiler, { 13 | contentBase: `http://lvh.me:${hotRailsPort}`, 14 | publicPath: webpackConfig.output.publicPath, 15 | hot: true, 16 | inline: true, 17 | historyApiFallback: true, 18 | quiet: false, 19 | noInfo: false, 20 | lazy: false, 21 | stats: { 22 | colors: true, 23 | hash: false, 24 | version: false, 25 | chunks: false, 26 | children: false, 27 | }, 28 | }) 29 | 30 | devServer.listen(hotRailsPort, 'localhost', err => { 31 | if (err) console.error(err) 32 | console.log( 33 | `=> 🔥 Webpack development server is running on port ${hotRailsPort}` 34 | ) 35 | }) 36 | -------------------------------------------------------------------------------- /client/startup/clientRegistration.jsx: -------------------------------------------------------------------------------- 1 | /* global document, $ */ 2 | // polyfills 3 | import 'whatwg-fetch' 4 | import 'pusher-js' 5 | import turbolinks from 'turbolinks' 6 | 7 | import { Provider } from 'react-redux' 8 | import React from 'react' 9 | import ReactOnRails from 'react-on-rails' 10 | 11 | import store from '../stores/store' 12 | import Heart from '../components/Heart' 13 | import HeartButton from '../components/HeartButton' 14 | import NewJob from '../components/NewJob' 15 | import NewJobSubscription from '../components/NewJobSubscription' 16 | import ProtipSubscribeButton from '../components/ProtipSubscribeButton' 17 | import Sponsors from '../components/Sponsors' 18 | 19 | turbolinks.start() 20 | 21 | ReactOnRails.setOptions({ 22 | traceTurbolinks: TRACE_TURBOLINKS, // eslint-disable-line no-undef 23 | }) 24 | ReactOnRails.registerStore({ store }) 25 | 26 | function withStore(c) { 27 | return props => React.createElement( 28 | Provider, 29 | { store: ReactOnRails.getStore('store') }, 30 | React.createElement(c, props) 31 | ) 32 | } 33 | 34 | function registerContainers(containers) { 35 | const containersWithStore = Object.keys(containers). 36 | reduce((h, k) => ({ ...h, [k]: withStore(containers[k]) }), {}) 37 | ReactOnRails.register(containersWithStore) 38 | } 39 | 40 | // Only container compoments need to be registered here 41 | // container components are rendered directly in view html 42 | // components that are children of containers don't need to be registered 43 | registerContainers({ 44 | Heart, 45 | HeartButton, 46 | NewJob, 47 | NewJobSubscription, 48 | Sponsors, 49 | ProtipSubscribeButton, 50 | }) 51 | 52 | require('./confirm') 53 | -------------------------------------------------------------------------------- /client/startup/confirm.js: -------------------------------------------------------------------------------- 1 | /* global console, document, window */ 2 | /* eslint no-alert: 0, no-console: 0 */ 3 | class Confirm { 4 | constructor(el) { 5 | this.message = el.getAttribute('data-confirm') 6 | if (this.message) { 7 | el.form.addEventListener('submit', this.confirm.bind(this)) 8 | } else { 9 | console.warn('No value specified in `data-confirm`', el) 10 | } 11 | } 12 | 13 | confirm(e) { 14 | if (!window.confirm(this.message)) { 15 | e.preventDefault() 16 | } 17 | } 18 | } 19 | 20 | document.addEventListener('turbolinks:load', () => { 21 | Array.from(document.querySelectorAll('[data-confirm]')).forEach((el) => new Confirm(el)) 22 | }) 23 | -------------------------------------------------------------------------------- /client/startup/serverRegistration.jsx: -------------------------------------------------------------------------------- 1 | // TODO server rendering 2 | 3 | // import { Provider } from 'react-redux' 4 | // import React from 'react' 5 | // import ReactOnRails from 'react-on-rails' 6 | // import store from '../stores/store' 7 | // import HeartButton from '../components/HeartButton' 8 | // 9 | // ReactOnRails.registerStore({ store }) 10 | // 11 | // function withStore(c) { 12 | // return props => React.createElement( 13 | // Provider, 14 | // { store: ReactOnRails.getStore('store') }, 15 | // React.createElement(c, props) 16 | // ) 17 | // } 18 | // 19 | // function registerContainers(containers) { 20 | // const containersWithStore = Object.keys(containers). 21 | // reduce((h, k) => ({ ...h, [k]: withStore(containers[k]) }), {}) 22 | // ReactOnRails.register(containersWithStore) 23 | // } 24 | // 25 | // // Only container compoments need to be registered here 26 | // // container components are rendered directly in view html 27 | // // components that are children of containers don't need to be registered 28 | // registerContainers({ 29 | // HeartButton, 30 | // }) 31 | -------------------------------------------------------------------------------- /client/stores/store.js: -------------------------------------------------------------------------------- 1 | /* global window */ 2 | import { combineReducers, applyMiddleware, createStore, compose } from 'redux' 3 | import promise from 'redux-promise' 4 | import thunk from 'redux-thunk' 5 | import { apiMiddleware } from 'redux-api-middleware' 6 | import { reducer as form } from 'redux-form' 7 | import apiAuthInjector from '../lib/apiAuthInjector' 8 | import unauthorizedHandler from '../lib/unauthorizedHandler' 9 | import reducers from '../reducers' 10 | 11 | export const STATE_HYDRATED = 'STATE_HYDRATED' 12 | 13 | export default function configureStore(props) { 14 | const store = createStore( 15 | combineReducers({ 16 | ...reducers, 17 | form, 18 | }), 19 | props, 20 | compose( 21 | applyMiddleware( 22 | thunk, 23 | promise, 24 | apiAuthInjector, 25 | apiMiddleware, 26 | unauthorizedHandler 27 | ), 28 | window.devToolsExtension ? window.devToolsExtension() : f => f 29 | ) 30 | ) 31 | 32 | store.dispatch({ type: STATE_HYDRATED, payload: store.getState() }) 33 | 34 | return store 35 | } 36 | -------------------------------------------------------------------------------- /client/webpack.client.rails.build.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 3 | 4 | const config = require('./webpack.client.base.config') 5 | 6 | const devBuild = process.env.NODE_ENV !== 'production' 7 | 8 | config.output = { 9 | filename: '[name]-bundle.js', 10 | path: '../app/assets/webpack', 11 | } 12 | 13 | // You can add entry points specific to rails here 14 | // config.entry.vendor.unshift( 15 | // 'es5-shim/es5-shim', 16 | // 'es5-shim/es5-sham' 17 | // ) 18 | 19 | // See webpack.common.config for adding modules common to both the webpack dev server and rails 20 | 21 | config.module.loaders.push( 22 | { 23 | test: /\.jsx?$/, 24 | loader: 'babel-loader', 25 | exclude: /node_modules/, 26 | }, 27 | { 28 | test: /\.css$/, 29 | loader: ExtractTextPlugin.extract( 30 | 'style', 31 | 'css?minimize&modules&importLoaders=1&localIdentName=[name]__[local]__[hash:base64:5]' + 32 | '!postcss' 33 | ), 34 | }, 35 | { 36 | test: /\.scss$/, 37 | loader: ExtractTextPlugin.extract( 38 | 'style', 39 | 'css?minimize&modules&importLoaders=3&localIdentName=[name]__[local]__[hash:base64:5]' + 40 | '!postcss' + 41 | '!sass' + 42 | '!sass-resources' 43 | ), 44 | } 45 | ) 46 | 47 | config.plugins.push( 48 | new ExtractTextPlugin('[name]-bundle.css', { allChunks: true }), 49 | new webpack.optimize.DedupePlugin() 50 | ) 51 | 52 | if (devBuild) { 53 | console.error('Webpack dev build for Rails') // eslint-disable-line no-console 54 | config.devtool = 'eval-source-map' 55 | } else { 56 | console.error('Webpack production build for Rails') // eslint-disable-line no-console 57 | config.devtool = 'source-map' 58 | } 59 | 60 | module.exports = config 61 | -------------------------------------------------------------------------------- /client/webpack.server.rails.build.config.js: -------------------------------------------------------------------------------- 1 | const webpack = require('webpack') 2 | const path = require('path') 3 | 4 | const devBuild = process.env.NODE_ENV !== 'production' 5 | const nodeEnv = devBuild ? 'development' : 'production' 6 | 7 | module.exports = { 8 | 9 | // the project dir 10 | context: __dirname, 11 | entry: [ 12 | 'babel-polyfill', 13 | './startup/serverRegistration', 14 | ], 15 | output: { 16 | filename: 'server-bundle.js', 17 | path: '../app/assets/webpack', 18 | }, 19 | resolve: { 20 | extensions: ['', '.js', '.jsx'], 21 | alias: { 22 | libs: path.join(process.cwd(), 'app', 'libs'), 23 | }, 24 | }, 25 | plugins: [ 26 | new webpack.DefinePlugin({ 27 | 'process.env': { 28 | NODE_ENV: JSON.stringify(nodeEnv), 29 | }, 30 | }), 31 | ], 32 | module: { 33 | loaders: [ 34 | { test: /\.jsx?$/, loader: 'babel-loader', exclude: /node_modules/ }, 35 | { 36 | test: /\.css$/, 37 | loaders: [ 38 | 'css/locals?modules&importLoaders=0&localIdentName=[name]__[local]__[hash:base64:5]', 39 | ], 40 | }, 41 | { 42 | test: /\.scss$/, 43 | loaders: [ 44 | 'css/locals?modules&importLoaders=2&localIdentName=[name]__[local]__[hash:base64:5]', 45 | 'sass', 46 | 'sass-resources', 47 | ], 48 | }, 49 | ], 50 | }, 51 | 52 | sassResources: ['./app/assets/styles/app-variables.scss'], 53 | 54 | } 55 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | require_relative 'boot' 2 | 3 | require 'rails/all' 4 | 5 | # Require the gems listed in Gemfile, including any gems 6 | # you've limited to :test, :development, or :production. 7 | Bundler.require(*Rails.groups) 8 | 9 | module CoderwallNext 10 | class Application < Rails::Application 11 | # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. 12 | # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. 13 | # config.time_zone = 'Central Time (US & Canada)' 14 | 15 | # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. 16 | # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] 17 | # config.i18n.default_locale = :de 18 | 19 | # Do not swallow errors in after_commit/after_rollback callbacks. 20 | # config.active_record.raise_in_transactional_callbacks = true 21 | config.assets.precompile += %w(.png .svg) 22 | config.exceptions_app = self.routes 23 | config.encoding = 'utf-8' 24 | 25 | config.lograge.enabled = true 26 | config.lograge.custom_options = lambda do |event| 27 | { 28 | params: event.payload[:params].reject { |k| %w(controller action).include?(k) } 29 | } 30 | end 31 | 32 | config.log_tags = [:uuid] 33 | config.log_level = ENV['LOG_LEVEL'] || :debug 34 | 35 | config.middleware.delete ActiveRecord::Migration::CheckPending 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: redis://localhost:6379/1 10 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ApplicationController.renderer.defaults.merge!( 4 | # http_host: 'example.org', 5 | # https: false 6 | # ) 7 | -------------------------------------------------------------------------------- /config/initializers/assets.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Version of your assets, change this if you want to expire all your assets. 4 | Rails.application.config.assets.version = '1.0' 5 | 6 | # Add additional assets to the asset load path 7 | # Rails.application.config.assets.paths << Emoji.images_path 8 | 9 | # Precompile additional assets. 10 | # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. 11 | # Rails.application.config.assets.precompile += %w( search.js ) 12 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/initializers/carrierwave.rb: -------------------------------------------------------------------------------- 1 | CarrierWave.configure do |config| 2 | if Rails.env.test? 3 | config.enable_processing = false 4 | config.storage = :file 5 | elsif Rails.env.development? 6 | config.enable_processing = true 7 | config.storage = :file 8 | config.asset_host = ActionController::Base.asset_host 9 | else 10 | config.root = Rails.root.join('tmp') 11 | config.cache_dir = "#{Rails.root}/tmp/uploads" 12 | config.enable_processing = true 13 | config.storage = :aws 14 | config.asset_host = "https://#{ENV['AWS_BUCKET']}.s3.amazonaws.com" 15 | config.aws_acl = 'public-read' 16 | config.aws_bucket = ENV['AWS_BUCKET'] 17 | config.aws_credentials = { 18 | access_key_id: ENV['AWS_ACCESS_ID'], 19 | secret_access_key: ENV['AWS_ACCESS_SECRET'], 20 | region: ENV['AWS_REGION'] 21 | } 22 | config.aws_attributes = { 23 | expires: 1.week.from_now.httpdate, 24 | cache_control: 'max-age=604800' 25 | } 26 | config.aws_authenticated_url_expiration = 60 * 60 * 24 * 7 27 | end 28 | end 29 | 30 | CarrierWave::SanitizedFile.sanitize_regexp = /[^[:word:]\.\-\+]/ 31 | -------------------------------------------------------------------------------- /config/initializers/clearance.rb: -------------------------------------------------------------------------------- 1 | # https://github.com/thoughtbot/clearance 2 | Clearance.configure do |config| 3 | config.httponly = true #cookies only accessed from server 4 | config.routes = false #disable clearance routes 5 | config.mailer_sender = "support@coderwall.com" 6 | config.cookie_expiration = ->(cookies){ 2.years.from_now.utc } 7 | config.rotate_csrf_on_sign_in = true 8 | 9 | if Rails.env.development? 10 | config.cookie_domain = 'localhost' 11 | elsif Rails.env.production? 12 | config.cookie_domain = '.coderwall.com' 13 | config.secure_cookie = true 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /config/initializers/cookies_serializer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Specify a serializer for the signed and encrypted cookie jars. 4 | # Valid options are :json, :marshal, and :hybrid. 5 | Rails.application.config.action_dispatch.cookies_serializer = :json 6 | -------------------------------------------------------------------------------- /config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | allow do 10 | origins '*' 11 | 12 | resource '/assets/*', 13 | headers: :any, 14 | methods: [:get, :options, :head] 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [ 5 | :password, 6 | :password_confirmation, 7 | :username, 8 | :email, 9 | :name, 10 | :avatar, 11 | :title, 12 | :country, 13 | :city, 14 | :state_name, 15 | :company, 16 | :about, 17 | :team_id, 18 | :last_ip 19 | ] 20 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /config/initializers/invisible_captcha.rb: -------------------------------------------------------------------------------- 1 | InvisibleCaptcha.setup do |config| 2 | config.honeypots = [ 3 | :city, 4 | :description, 5 | :subtitle, 6 | :website, 7 | :zip, 8 | ] 9 | config.visual_honeypots = false 10 | config.timestamp_threshold = 15 11 | config.timestamp_enabled = true 12 | end 13 | -------------------------------------------------------------------------------- /config/initializers/meta_tags.rb: -------------------------------------------------------------------------------- 1 | MetaTags.configure do |config| 2 | config.title_limit = 70 3 | config.description_limit = 160 4 | config.keywords_limit = 255 5 | config.keywords_separator = ', ' 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /config/initializers/new_framework_defaults.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | # 3 | # This file contains migration options to ease your Rails 5.0 upgrade. 4 | # 5 | # Once upgraded flip defaults one by one to migrate to the new default. 6 | # 7 | # Read the Guide for Upgrading Ruby on Rails for more info on each option. 8 | 9 | # Enable per-form CSRF tokens. Previous versions had false. 10 | Rails.application.config.action_controller.per_form_csrf_tokens = false 11 | 12 | # Enable origin-checking CSRF mitigation. Previous versions had false. 13 | Rails.application.config.action_controller.forgery_protection_origin_check = false 14 | 15 | # Make Ruby 2.4 preserve the timezone of the receiver when calling `to_time`. 16 | # Previous versions had false. 17 | ActiveSupport.to_time_preserves_timezone = false 18 | 19 | # Require `belongs_to` associations by default. Previous versions had false. 20 | Rails.application.config.active_record.belongs_to_required_by_default = false 21 | 22 | # Do not halt callback chains when a callback returns false. Previous versions had true. 23 | ActiveSupport.halt_callback_chains_on_return_false = true 24 | -------------------------------------------------------------------------------- /config/initializers/puma_worker_killer.rb: -------------------------------------------------------------------------------- 1 | # https://github.com/schneems/puma_worker_killer 2 | if Rails.env.production? 3 | PumaWorkerKiller.config do |config| 4 | config.ram = Integer(ENV['MAX_RAM'] || 512) # mb 5 | config.frequency = 20 # seconds 6 | config.percent_usage = 0.95 7 | config.rolling_restart_frequency = 12 * 3600 # 12 hours in seconds 8 | end 9 | PumaWorkerKiller.start 10 | end 11 | -------------------------------------------------------------------------------- /config/initializers/rack_mini_profiler.rb: -------------------------------------------------------------------------------- 1 | if ENV['ENABLE_PROFILER'] 2 | require 'rack-mini-profiler' 3 | 4 | # initialization is skipped so trigger it 5 | Rack::MiniProfilerRails.initialize!(Rails.application) 6 | end 7 | -------------------------------------------------------------------------------- /config/initializers/rack_timeout.rb: -------------------------------------------------------------------------------- 1 | # Rack::Timeout.service_timeout = ENV.fetch('RACK_TIMEOUT', 5).to_i 2 | -------------------------------------------------------------------------------- /config/initializers/sanitization.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.after_initialize do 2 | # Allow links to have rel=nofollow to discourage spam 3 | ActionView::Base.sanitized_allowed_attributes << 'rel' 4 | end 5 | -------------------------------------------------------------------------------- /config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.session_store :cookie_store, 2 | key: '_coderwall-next_session', 3 | :expire_after => 2.years 4 | -------------------------------------------------------------------------------- /config/initializers/stripe.rb: -------------------------------------------------------------------------------- 1 | Rails.configuration.stripe = { 2 | publishable_key: ENV['STRIPE_PUBLISHABLE_KEY'], 3 | secret_key: ENV['STRIPE_SECRET_KEY'] 4 | } 5 | 6 | Stripe.api_key = Rails.configuration.stripe[:secret_key] 7 | -------------------------------------------------------------------------------- /config/initializers/time_formats.rb: -------------------------------------------------------------------------------- 1 | Time::DATE_FORMATS[:explicitly_bold] = "%B %Y" 2 | Time::DATE_FORMATS[:seo] = "%B %d, %Y" 3 | -------------------------------------------------------------------------------- /config/initializers/webpack.rb: -------------------------------------------------------------------------------- 1 | Rails.application.config.assets.paths << Rails.root.join("app", "assets", "webpack") 2 | Rails.application.config.assets.precompile += %w( minimal.css live-banner.jpg happy-cat.jpg conference-room.png offline-holder.png server-bundle.js) 3 | Rails.application.config.assets.compile = true 4 | 5 | type = ENV["REACT_ON_RAILS_ENV"] == "HOT" ? "non_webpack" : "static" 6 | Rails.application.config.assets.precompile += [ 7 | "application_#{type}.js", 8 | "application_#{type}.css" 9 | ] 10 | 11 | # suppress annoying asset 404s 12 | if Rails.env.development? 13 | class ActionDispatch::DebugExceptions 14 | alias_method :old_log_error, :log_error 15 | def log_error(env, wrapper) 16 | if wrapper.exception.is_a? ActionController::RoutingError 17 | return 18 | else 19 | old_log_error env, wrapper 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /config/letsencrypt_plugin.yml: -------------------------------------------------------------------------------- 1 | production: 2 | endpoint: 'https://acme-v01.api.letsencrypt.org/' 3 | email: 'matt@assemblymade.com' 4 | domain: coderwall.com www.coderwall.com 5 | private_key_in_db: true 6 | -------------------------------------------------------------------------------- /config/locales/invisible_captcha.en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | invisible_captcha: 3 | sentence_for_humans: "Leave this field blank" 4 | timestamp_error_message: "Sorry, that was too quick! Please resubmit." 5 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | workers Integer(ENV['WEB_CONCURRENCY'] || 2) 2 | threads_count = Integer(ENV['MAX_THREADS'] || 5) 3 | threads threads_count, threads_count 4 | 5 | worker_timeout 15 6 | worker_shutdown_timeout 8 7 | 8 | preload_app! 9 | 10 | port ENV['PORT'] || 3000 11 | environment ENV['RACK_ENV'] || 'development' 12 | 13 | on_worker_boot do 14 | ActiveRecord::Base.establish_connection 15 | end 16 | -------------------------------------------------------------------------------- /config/secrets.yml: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure the secrets in this file are kept private 11 | # if you're sharing your code publicly. 12 | 13 | development: 14 | secret_key_base: 692564d1b453ed072faad3ef208ee6e1350ef0d0c84e4e96991690a353610a37a884e2181bc8fb2f8b963f3d053189dc962ef126aa9cd6128475e276db5f4c89 15 | 16 | test: 17 | secret_key_base: 5af0ed63c7b9c73a2d2d2e4ae54028c79096e0b6b5e465db6f04d940e0cb2b3bb135e54327e37db1512c3749d00eb7ba1755c204f7e39bb2ce2bfbdc1e2e2485 18 | 19 | # Do not keep production secrets in the repository, 20 | # instead read values from the environment. 21 | production: 22 | secret_key_base: <%= ENV["SECRET_KEY_BASE"] %> 23 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w( 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ).each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /db/migrate/20160119204658_create_protips.rb: -------------------------------------------------------------------------------- 1 | class CreateProtips < ActiveRecord::Migration 2 | 3 | # Omitted 4 | # kind :string(255) 5 | # created_by :string(255) default("self") 6 | # featured :boolean default(FALSE) 7 | # upvotes_value_cache :integer default(0), not null 8 | # inappropriate :integer default(0) 9 | # likes_count :integer default(0) 10 | # user_name :string(255) 11 | # user_email :string(255) 12 | # user_agent :string(255) 13 | # user_ip :inet 14 | # spam_reports_count :integer default(0) 15 | # state :string(255) default("active") 16 | 17 | def change 18 | create_table :protips do |t| 19 | t.string :public_id, :title, :slug 20 | t.text :body 21 | t.integer :user_id 22 | t.float :score, :boost_factor 23 | t.timestamp :featured_at 24 | t.timestamps null: false 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /db/migrate/20160119204714_create_users.rb: -------------------------------------------------------------------------------- 1 | class CreateUsers < ActiveRecord::Migration 2 | def up 3 | enable_extension("citext") 4 | 5 | create_table :users do |t| 6 | t.string :name, :avatar, :title, :location, :country, :city, :state_name 7 | t.string :company 8 | t.text :about 9 | t.integer :team_id 10 | t.string :api_key 11 | t.boolean :admin 12 | t.boolean :receive_newsletter, default: true 13 | t.boolean :receive_weekly_digest, default: true 14 | t.integer :login_count 15 | t.integer :last_ip 16 | t.timestamp :banned_at 17 | t.timestamp :last_email_sent, :last_request_at 18 | t.timestamps null: false 19 | end 20 | 21 | add_column :users, :username, :citext 22 | add_column :users, :email, :citext 23 | end 24 | 25 | def down 26 | drop_table :users 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /db/migrate/20160120175334_add_clearance_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddClearanceToUsers < ActiveRecord::Migration 2 | def self.up 3 | change_table :users do |t| 4 | t.string :encrypted_password, limit: 128 5 | t.string :confirmation_token, limit: 128 6 | t.string :remember_token, limit: 128 7 | end 8 | 9 | add_index :users, :email 10 | add_index :users, :remember_token 11 | 12 | users = select_all("SELECT id FROM users WHERE remember_token IS NULL") 13 | 14 | users.each do |user| 15 | update <<-SQL 16 | UPDATE users 17 | SET remember_token = '#{Clearance::Token.new}' 18 | WHERE id = '#{user['id']}' 19 | SQL 20 | end 21 | end 22 | 23 | def self.down 24 | change_table :users do |t| 25 | t.remove :encrypted_password, :confirmation_token, :remember_token 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /db/migrate/20160120200829_create_comments.rb: -------------------------------------------------------------------------------- 1 | class CreateComments < ActiveRecord::Migration 2 | def change 3 | create_table :comments do |t| 4 | t.text :body 5 | t.integer :protip_id, :user_id 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160121012718_create_likes.rb: -------------------------------------------------------------------------------- 1 | class CreateLikes < ActiveRecord::Migration 2 | def change 3 | create_table :likes do |t| 4 | t.integer :likable_id, :user_id, :value 5 | t.string :likable_type 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160121013050_create_badges.rb: -------------------------------------------------------------------------------- 1 | class CreateBadges < ActiveRecord::Migration 2 | def change 3 | create_table :badges do |t| 4 | t.integer :user_id 5 | t.string :name, :description, :why, :image_name, :provider 6 | t.timestamps null: false 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160210220146_add_tags_and_count_columns.rb: -------------------------------------------------------------------------------- 1 | class AddTagsAndCountColumns < ActiveRecord::Migration 2 | def change 3 | 4 | change_table :protips do |t| 5 | t.string :tags, array: true, default: [] 6 | t.integer :likes_count, default: 0 7 | t.integer :views_count, default: 0 8 | end 9 | 10 | add_index :protips, :tags, using: 'gin' 11 | 12 | change_table :users do |t| 13 | t.string :skills, array: true, default: [] 14 | t.string :github_id, :twitter_id, :github, :twitter 15 | end 16 | 17 | add_index :users, :skills, using: 'gin' 18 | 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /db/migrate/20160210221757_add_color_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddColorToUser < ActiveRecord::Migration 2 | def change 3 | add_column :users, :color, :string, default: '#111' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160210222721_add_karma_to_user.rb: -------------------------------------------------------------------------------- 1 | class AddKarmaToUser < ActiveRecord::Migration 2 | def change 3 | add_column :users, :karma, :integer, default: 1 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160212233312_add_flagged_to_protip.rb: -------------------------------------------------------------------------------- 1 | class AddFlaggedToProtip < ActiveRecord::Migration 2 | def change 3 | add_column :protips, :flagged, :boolean, default: false 4 | remove_column :protips, :boost_factor 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160213012958_add_likes_cache.rb: -------------------------------------------------------------------------------- 1 | class AddLikesCache < ActiveRecord::Migration 2 | def change 3 | add_column :comments, :likes_count, :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160213015810_remove_login_counts.rb: -------------------------------------------------------------------------------- 1 | class RemoveLoginCounts < ActiveRecord::Migration 2 | def change 3 | remove_column(:users, :login_count) 4 | remove_column(:users, :banned_at) 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160214213211_clean_up_likes.rb: -------------------------------------------------------------------------------- 1 | class CleanUpLikes < ActiveRecord::Migration 2 | def change 3 | remove_column :likes, :value 4 | add_index :likes, [:user_id, :likable_type, :likable_id], :unique => true 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160219071138_improving_database_performane.rb: -------------------------------------------------------------------------------- 1 | class ImprovingDatabasePerformane < ActiveRecord::Migration 2 | def change 3 | add_index "users", "username", unique: true 4 | add_index "protips", "public_id", unique: true 5 | add_index "protips", "score", unique: false 6 | add_index "comments", "protip_id", unique: false 7 | add_index "badges", "user_id", unique: false 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160219190140_add_banned_users_for_migration.rb: -------------------------------------------------------------------------------- 1 | class AddBannedUsersForMigration < ActiveRecord::Migration 2 | def change 3 | add_column :users, :banned_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160223063803_add_more_indexes.rb: -------------------------------------------------------------------------------- 1 | class AddMoreIndexes < ActiveRecord::Migration 2 | def change 3 | add_index :comments, :user_id, unique: false 4 | add_index :protips, :user_id, unique: false 5 | add_index :protips, :created_at, unique: false 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160223064301_changing_user_to_citext.rb: -------------------------------------------------------------------------------- 1 | class ChangingUserToCitext < ActiveRecord::Migration 2 | def change 3 | enable_extension :citext 4 | change_column :users, :email, :citext 5 | change_column :users, :username, :citext 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160223065728_create_team.rb: -------------------------------------------------------------------------------- 1 | class CreateTeam < ActiveRecord::Migration 2 | def change 3 | create_table :teams do |t| 4 | t.string :name 5 | t.string :avatar 6 | t.citext :slug 7 | t.string :website, :twitter, :facebook, :github 8 | t.string :youtube_url, :blog_feed 9 | t.string :location 10 | t.text :about 11 | t.string :color 12 | t.timestamps 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20160225042520_add_simple_user_id_index_on_like.rb: -------------------------------------------------------------------------------- 1 | class AddSimpleUserIdIndexOnLike < ActiveRecord::Migration 2 | def change 3 | add_index :likes, :user_id, unique: false 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160225070301_auto_like_content.rb: -------------------------------------------------------------------------------- 1 | class AutoLikeContent < ActiveRecord::Migration 2 | def up 3 | Protip.find_each do |protip| 4 | protip.likes.create(user: protip.user) unless protip.user.likes?(protip) 5 | end 6 | Comment.find_each do |comment| 7 | comment.protip.likes.create(user: comment.user) unless comment.user.likes?(comment.protip) 8 | end 9 | end 10 | 11 | def down 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /db/migrate/20160225171117_add_pictures.rb: -------------------------------------------------------------------------------- 1 | class AddPictures < ActiveRecord::Migration 2 | def up 3 | create_table :pictures do |t| 4 | t.integer :user_id 5 | t.string :file 6 | t.timestamps 7 | end 8 | 9 | add_index :pictures, :user_id, unique: false 10 | end 11 | 12 | def down 13 | drop_table :pictures 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /db/migrate/20160227000445_add_missing_foreign_keys.rb: -------------------------------------------------------------------------------- 1 | class AddMissingForeignKeys < ActiveRecord::Migration 2 | def change 3 | add_foreign_key "badges", "users", name: "badges_user_id_fk" 4 | add_foreign_key "comments", "protips", name: "comments_protip_id_fk" 5 | add_foreign_key "comments", "users", name: "comments_user_id_fk" 6 | add_foreign_key "likes", "users", name: "likes_user_id_fk" 7 | add_foreign_key "protips", "users", name: "protips_user_id_fk" 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20160227010236_turn_fields_case_insensitive.rb: -------------------------------------------------------------------------------- 1 | class TurnFieldsCaseInsensitive < ActiveRecord::Migration 2 | def change 3 | enable_extension :citext 4 | change_column :users, :username, :citext 5 | change_column :users, :email, :citext 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160307195201_clean_up_orphan_pictures.rb: -------------------------------------------------------------------------------- 1 | class CleanUpOrphanPictures < ActiveRecord::Migration 2 | def change 3 | 4 | count = Picture.where("user_id NOT IN (select id from users)").count 5 | puts "Picture: deleting #{count} orphans" 6 | puts Picture.where("user_id NOT IN (select id from users)").delete_all 7 | 8 | add_foreign_key "pictures", "users", name: "pictures_user_id_fk" 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20160318212558_add_marketing_list_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddMarketingListToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :marketing_list, :text 4 | add_column :users, :email_invalid_at, :datetime 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160422205835_add_view_counts_to_team.rb: -------------------------------------------------------------------------------- 1 | class AddViewCountsToTeam < ActiveRecord::Migration 2 | def change 3 | add_column "teams", "views_count", :integer, default: 0 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160422211004_create_jobs.rb: -------------------------------------------------------------------------------- 1 | class CreateJobs < ActiveRecord::Migration 2 | def change 3 | create_table :jobs do |t| 4 | t.timestamps null: false 5 | t.string :role_type 6 | t.string :title 7 | t.string :location 8 | t.string :source 9 | t.text :description 10 | t.text :how_to_apply 11 | t.string :company 12 | t.string :company_url 13 | t.string :company_logo 14 | t.string :author_name 15 | t.string :author_email 16 | end 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /db/migrate/20160422213924_move_job_id_over_to_uuid.rb: -------------------------------------------------------------------------------- 1 | class MoveJobIdOverToUuid < ActiveRecord::Migration 2 | def change 3 | enable_extension 'uuid-ossp' 4 | 5 | add_column :jobs, :uuid, :uuid, default: "uuid_generate_v4()", null: false 6 | 7 | change_table :jobs do |t| 8 | t.remove :id 9 | t.rename :uuid, :id 10 | end 11 | 12 | execute "ALTER TABLE jobs ADD PRIMARY KEY (id);" 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /db/migrate/20160422215652_trim_job.rb: -------------------------------------------------------------------------------- 1 | class TrimJob < ActiveRecord::Migration 2 | def change 3 | remove_column :jobs, :description 4 | remove_column :jobs, :how_to_apply 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160422234923_add_publish_attributes_to_jobs.rb: -------------------------------------------------------------------------------- 1 | class AddPublishAttributesToJobs < ActiveRecord::Migration 2 | def change 3 | add_column :jobs, :expires_at, :datetime 4 | add_column :jobs, :stripe_charge, :text 5 | 6 | add_index :jobs, :expires_at 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20160425233554_create_job_views.rb: -------------------------------------------------------------------------------- 1 | class CreateJobViews < ActiveRecord::Migration 2 | def change 3 | create_table :job_views do |t| 4 | # required 5 | t.datetime :created_at, null: false 6 | t.uuid :job_id, null: false 7 | 8 | # optional 9 | t.integer :user_id 10 | t.text :ip 11 | 12 | t.foreign_key :jobs 13 | t.foreign_key :users 14 | end 15 | end 16 | end 17 | -------------------------------------------------------------------------------- /db/migrate/20160513032303_add_stream_key_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddStreamKeyToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :stream_key, :text 4 | 5 | add_index :users, :stream_key, unique: true 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160519204919_add_type_to_protips.rb: -------------------------------------------------------------------------------- 1 | class AddTypeToProtips < ActiveRecord::Migration 2 | def up 3 | add_column :protips, :type, :text 4 | Article.update_all(type: Protip.name) 5 | change_column :protips, :type, :text, null: false 6 | add_index :protips, :type 7 | end 8 | 9 | def down 10 | remove_column :protips, :type, :text 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20160519233923_rename_protip_id_on_comments_to_article_id.rb: -------------------------------------------------------------------------------- 1 | class RenameProtipIdOnCommentsToArticleId < ActiveRecord::Migration 2 | def change 3 | rename_column :comments, :protip_id, :article_id 4 | Like.where(likable_type: 'Protip').update_all(likable_type: 'Article') 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /db/migrate/20160601015828_add_started_archived_fields_to_articles.rb: -------------------------------------------------------------------------------- 1 | class AddStartedArchivedFieldsToArticles < ActiveRecord::Migration 2 | def change 3 | add_column :protips, :published_at, :datetime 4 | add_column :protips, :archived_at, :datetime 5 | add_column :protips, :save_recording, :bool 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20160607202132_add_recording_id_to_protips.rb: -------------------------------------------------------------------------------- 1 | class AddRecordingIdToProtips < ActiveRecord::Migration 2 | def change 3 | add_column :protips, :recording_id, :text 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160608034824_add_recording_started_at_to_protips.rb: -------------------------------------------------------------------------------- 1 | class AddRecordingStartedAtToProtips < ActiveRecord::Migration 2 | def change 3 | add_column :protips, :recording_started_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160830184552_create_job_subscriptions.rb: -------------------------------------------------------------------------------- 1 | class CreateJobSubscriptions < ActiveRecord::Migration 2 | def change 3 | create_table :job_subscriptions, id: :uuid do |t| 4 | t.timestamps null: false 5 | t.string :jobs_url, null: false 6 | t.string :company_name, null: false 7 | t.string :contact_email, null: false 8 | t.string :stripe_customer_id 9 | t.string :subscribed_at 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /db/migrate/20160909044024_add_indexes.rb: -------------------------------------------------------------------------------- 1 | class AddIndexes < ActiveRecord::Migration 2 | def change 3 | add_index :protips, :views_count 4 | add_index :users, :receive_newsletter 5 | add_index :users, :marketing_list 6 | add_index :users, :email_invalid_at 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /db/migrate/20160913165240_add_comment_unsubscribed_to_users.rb: -------------------------------------------------------------------------------- 1 | class AddCommentUnsubscribedToUsers < ActiveRecord::Migration 2 | def change 3 | add_column :users, :unsubscribed_comment_emails_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160916222644_add_subscribers_to_articles.rb: -------------------------------------------------------------------------------- 1 | class AddSubscribersToArticles < ActiveRecord::Migration 2 | def change 3 | add_column :protips, :subscribers, :int, array: true, default: [], null: false 4 | Protip.includes(:comments, :likes).find_each do |p| 5 | commentors = p.comments.map(&:user_id).uniq 6 | likers = p.likes.map(&:user_id).uniq 7 | subscribers = ([p.user_id] | commentors | likers) 8 | p.update_columns(subscribers: subscribers) 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/migrate/20160919171618_remove_unsubscribed_comment_emails_at_from_users.rb: -------------------------------------------------------------------------------- 1 | class RemoveUnsubscribedCommentEmailsAtFromUsers < ActiveRecord::Migration 2 | def change 3 | remove_column :users, :unsubscribed_comment_emails_at 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160922010312_create_letsencrypt_plugin_challenges.letsencrypt_plugin.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from letsencrypt_plugin (originally 20151206135029) 2 | class CreateLetsencryptPluginChallenges < ActiveRecord::Migration 3 | def change 4 | create_table :letsencrypt_plugin_challenges do |t| 5 | t.text :response 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20160922010313_create_letsencrypt_plugin_settings.letsencrypt_plugin.rb: -------------------------------------------------------------------------------- 1 | # This migration comes from letsencrypt_plugin (originally 20160412195212) 2 | class CreateLetsencryptPluginSettings < ActiveRecord::Migration 3 | def change 4 | create_table :letsencrypt_plugin_settings do |t| 5 | t.text :private_key 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /db/migrate/20160922185544_remove_job_views.rb: -------------------------------------------------------------------------------- 1 | class RemoveJobViews < ActiveRecord::Migration 2 | def change 3 | drop_table :job_views 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20160923195619_add_partner_info.rb: -------------------------------------------------------------------------------- 1 | class AddPartnerInfo < ActiveRecord::Migration 2 | def change 3 | add_column :users, :partner_last_contribution_at, :datetime 4 | add_column :users, :partner_asm_username, :string 5 | add_column :users, :partner_slack_username, :string 6 | add_column :users, :partner_email, :string 7 | add_column :users, :partner_coins, :integer 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /db/migrate/20161207222630_change_users_last_ip_from_integer_to_string.rb: -------------------------------------------------------------------------------- 1 | class ChangeUsersLastIpFromIntegerToString < ActiveRecord::Migration 2 | def change 3 | change_column :users, :last_ip, :string 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170109215300_add_spam_detected_at_to_protips.rb: -------------------------------------------------------------------------------- 1 | class AddSpamDetectedAtToProtips < ActiveRecord::Migration 2 | def change 3 | add_column :protips, :spam_detected_at, :datetime 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170110195008_add_spam_fields_to_protips.rb: -------------------------------------------------------------------------------- 1 | class AddSpamFieldsToProtips < ActiveRecord::Migration 2 | def change 3 | add_column :protips, :user_ip, :string 4 | add_column :protips, :user_agent, :string 5 | add_column :protips, :referrer, :string 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /db/migrate/20170220093535_remove_stream_key_from_users.rb: -------------------------------------------------------------------------------- 1 | class RemoveStreamKeyFromUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | remove_column :users, :stream_key 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /db/migrate/20170328232725_add_bad_users_and_content.rb: -------------------------------------------------------------------------------- 1 | class AddBadUsersAndContent < ActiveRecord::Migration[5.0] 2 | def change 3 | add_column :users, :bad_user, :bool, null: false, default: false 4 | add_column :comments, :bad_content, :bool, null: false, default: false 5 | rename_column :protips, :flagged, :bad_content 6 | 7 | add_index :users, :bad_user 8 | add_index :protips, :bad_content 9 | add_index :comments, :bad_content 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }]) 7 | # Mayor.create(name: 'Emanuel', city: cities.first) 8 | -------------------------------------------------------------------------------- /lib/tasks/cache.rake: -------------------------------------------------------------------------------- 1 | namespace :cache do 2 | 3 | task :clear => :environment do 4 | Rails.cache.clear 5 | end 6 | 7 | namespace :score do 8 | task :recalculate => :environment do 9 | ActiveRecord::Base.logger.level = Logger::INFO #hide sql output 10 | Protip.order(created_at: :asc).find_each(batch_size: 100) do |p| 11 | score = p.calculate_score 12 | p.update_column(:score, score) 13 | sleep 0.1 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/tasks/db.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | desc 'Quiet ActiveRecord!' 3 | task :mute => :environment do 4 | ActiveRecord::Base.logger = nil 5 | end 6 | end 7 | -------------------------------------------------------------------------------- /lib/tasks/partners.rake: -------------------------------------------------------------------------------- 1 | namespace :partners do 2 | 3 | task :load => :environment do 4 | require 'csv' 5 | require 'open-uri' 6 | open("") do |file| 7 | CSV.parse(file, :headers => true) do |row| 8 | username = row[0] 9 | user = User.find_by_username(username) 10 | user.partner_asm_username = row[1] 11 | user.partner_slack_username = row[2] 12 | user.partner_email = row[3] 13 | user.partner_last_contribution_at = Date.strptime(row[4], "%m/%d/%Y") 14 | user.partner_coins = row[5] 15 | user.save! 16 | end 17 | end 18 | end 19 | 20 | task :update => :environment do 21 | flatten_to_latest(Github.user_pr_log).each do |username, contribution_date| 22 | if user = User.where(github: username).first 23 | user.partner_last_contribution_at = contribution_date 24 | user.save! 25 | end 26 | end 27 | end 28 | 29 | def flatten_to_latest(results) 30 | results.inject({}) do |users, row| 31 | user_id = row[:username] 32 | if users[user_id].blank? || users[user_id] < row[:created_at] 33 | users[user_id] = row[:created_at] 34 | end 35 | users 36 | end 37 | end 38 | 39 | task :email => :environment do 40 | User.where("partner_coins IS NOT NULL AND partner_last_contribution_at < ?", 1.year.ago).all.each do |user| 41 | UserMailer.partnership_expired(user).deliver_now 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/tasks/report.rake: -------------------------------------------------------------------------------- 1 | namespace :report do 2 | 3 | task :revenue => :environment do 4 | # https://github.com/stomita/heroku-buildpack-phantomjs 5 | require 'capybara/poltergeist' 6 | Capybara.register_driver :poltergeist do |app| 7 | Capybara::Poltergeist::Driver.new(app, js_errors: false) 8 | end 9 | Capybara.default_driver = :poltergeist 10 | browser = Capybara.current_session 11 | 12 | browser.visit 'https://www.newsletterdirectory.co/log-in/' 13 | browser.find('#email').set('matt@assemblymade.com') 14 | browser.find('#password').set(ENV['SPONSOR_PWD']) 15 | sleep 2 16 | browser.find("#logInButton").trigger('click') 17 | sleep 2 18 | browser.visit "https://www.newsletterdirectory.co/manage-text-adzone/?adzone=11384" 19 | 20 | labels = browser.all('.adzonestats .grid_4').collect{|div| div.text} 21 | values = browser.all('.adzonestats .grid_2').collect{|div| div.text} 22 | report = [ 23 | "Sponsor Revenue Performance", 24 | "#{values[0]} Avg CTR", 25 | "#{values[1]} Pending Monthly Earnings", 26 | "#{values[2]} Last Month's Earnings" 27 | ].join("\n") 28 | 29 | Slack.notify!(':moneybag:', report) if Rails.env.production? 30 | puts report 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lib/tasks/restore.rake: -------------------------------------------------------------------------------- 1 | namespace :db do 2 | 3 | task restore: [ 4 | 'db:download:generate', 5 | 'db:download:latest', 6 | 'db:drop', 7 | 'db:create', 8 | 'db:download:load', 9 | 'db:download:clean', 10 | 'db:migrate' 11 | ] 12 | 13 | namespace :download do 14 | def db_dump_file 15 | "coderwall-production.dump" 16 | end 17 | 18 | desc 'Create a production database backup' 19 | task :generate do 20 | Bundler.with_clean_env do 21 | cmd = "heroku pg:backups capture DATABASE_URL --app coderwall-next" 22 | sh(cmd) 23 | end 24 | end 25 | 26 | desc 'Download latest database backup' 27 | task :latest do 28 | unless File.exists?(db_dump_file) 29 | Bundler.with_clean_env do 30 | sh("curl `heroku pg:backups public-url --app coderwall-next` -o #{db_dump_file}") 31 | end 32 | end 33 | end 34 | 35 | desc 'Load local database backup into dev' 36 | task load: :environment do 37 | raise 'local dump not found' unless File.exists?(db_dump_file) 38 | puts 'Loading Production database locally' 39 | `pg_restore --verbose --clean --no-acl --no-owner -h localhost -d coderwall-next_development #{db_dump_file}` 40 | end 41 | 42 | task :clean do 43 | `rm #{db_dump_file}` 44 | end 45 | end 46 | 47 | end 48 | -------------------------------------------------------------------------------- /lib/tasks/spam.rake: -------------------------------------------------------------------------------- 1 | namespace :spam do 2 | task :sweep => :environment do 3 | since = 30.days.ago 4 | 5 | protips = Protip.where('created_at > ?', since).where(bad_content: false); nil 6 | good_protips = [] 7 | protips.each do |p| 8 | flags = Spaminator.new.protip_flags(p) 9 | if flags.any? && p.hearts_count <= 1 10 | Rails.logger.debug "#{p.id} – #{p.title} – #{p.body[0..100].gsub("\n", '')}" 11 | Rails.logger.debug "#{flags.inspect}" if flags.any? 12 | Rails.logger.debug 13 | 14 | p.bad_content = true 15 | p.user.bad_user = true 16 | p.save 17 | else 18 | good_protips << "https://coderwall.com/p/#{p.public_id} – #{p.title}" 19 | end 20 | end; nil 21 | 22 | good_users = [] 23 | users = User.where('created_at > ?', since).where(bad_user: false); nil 24 | users.each do |u| 25 | flags = Spaminator.new.user_flags(u) 26 | if flags.any? 27 | Rails.logger.debug "#{u.id} – #{u.username} – #{(u.about || '')[0..100].gsub("\n", '')}" 28 | Rails.logger.debug "#{flags.inspect}" if flags.any? 29 | Rails.logger.debug 30 | 31 | u.bad_user! 32 | else 33 | good_users << "https://coderwall.com/#{u.username}" 34 | end 35 | end; nil 36 | 37 | ["Good Users", good_users, "Good Protips", good_protips].flatten.each do |e| 38 | Rails.logger.debug e 39 | end 40 | 41 | Rails.logger.info("spam-sweep bad-users=#{users.size - good_users.size}/#{users.size} bad-protips=#{protips.size - good_protips.size}/#{protips.size}") 42 | end 43 | end -------------------------------------------------------------------------------- /lib/tasks/tags.rake: -------------------------------------------------------------------------------- 1 | namespace :tags do 2 | desc "Replace tags with canonical usages" 3 | task :clean do 4 | replacements = { 5 | 'javascript' => [ 6 | '#javascript', 7 | '.js', 8 | 'javascripts', 9 | ], 10 | 'nodejs' => [ 11 | 'javascript. node.js', 12 | 'node js', 13 | 'node-js', 14 | 'node.js node', 15 | 'node.js', 16 | ], 17 | 'rails' => [ 18 | 'ruby rails', 19 | 'ruby on rails', 20 | 'ruby in rails', 21 | 'ruby-on-rails', 22 | 'rubyonrails', 23 | ], 24 | } 25 | 26 | replacements.each do |canonical, tags| 27 | Protip.with_any_tagged(tags).each do |tip| 28 | clean = tip.tags.map{|t| tags.find {|wrong| t == wrong } ? canonical : t }.uniq 29 | tip.update_columns(tags: clean) 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /log/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/log/.keep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "coderwall", 3 | "description": "Programming tips, tools, and projects from our developer community.", 4 | "engines": { 5 | "node": "6.4.0", 6 | "npm": "3.10.3" 7 | }, 8 | "scripts": { 9 | "postinstall": "cd client && npm install", 10 | "test": "rake test && npm run lint && npm run test:client", 11 | "test:client": "(cd client && npm run test --silent)", 12 | "lint": "(cd client && npm run lint --silent)", 13 | "build:clean": "rm app/assets/webpack/*", 14 | "build:production": "(cd client && npm run build:production --silent)", 15 | "build:production:client": "(cd client && npm run build:production:client --silent)", 16 | "build:production:server": "(cd client && npm run build:production:server --silent)", 17 | "build:client": "(cd client && npm run build:client --silent)", 18 | "build:server": "(cd client && npm run build:server --silent)", 19 | "build:dev:client": "(cd client && npm run build:dev:client --silent)", 20 | "build:dev:server": "(cd client && npm run build:dev:server --silent)", 21 | "hot-assets": "(cd client && npm run hot-assets)", 22 | "start": "(cd client && npm run start --silent)" 23 | }, 24 | "repository": { 25 | "type": "git", 26 | "url": "https://github.com/coderwall/coderwall-next" 27 | }, 28 | "author": "mdeiters", 29 | "license": "AGPL-3.0", 30 | "bugs": { 31 | "url": "https://github.com/coderwall/coderwall-next/issues" 32 | }, 33 | "homepage": "https://coderwall.com", 34 | "cacheDirectories": [ 35 | "node_modules", 36 | "client/node_modules" 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /public/apple-touch-icon-152x152-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/apple-touch-icon-152x152-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/fav128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/fav128x128.png -------------------------------------------------------------------------------- /public/fav32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/fav32x32.png -------------------------------------------------------------------------------- /public/fav64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/fav64x64.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/favicon.ico -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/favicon.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file 2 | # 3 | # To ban all spiders from the entire site uncomment the next two lines: 4 | # User-agent: * 5 | # Disallow: / 6 | -------------------------------------------------------------------------------- /public/touch-icon-ipad-retina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/touch-icon-ipad-retina.png -------------------------------------------------------------------------------- /public/touch-icon-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/touch-icon-ipad.png -------------------------------------------------------------------------------- /public/touch-icon-iphone-retina.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/touch-icon-iphone-retina.png -------------------------------------------------------------------------------- /public/touch-icon-iphone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/public/touch-icon-iphone.png -------------------------------------------------------------------------------- /test/controllers/protips_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class ProtipsControllerTest < ActionController::TestCase 4 | test "show signed in" do 5 | protip = create(:protip) 6 | sign_in 7 | get :show, params: { id: protip.public_id, slug: protip.slug } 8 | assert_response :success 9 | end 10 | 11 | test "show signed out" do 12 | protip = create(:protip) 13 | get :show, params: { id: protip.public_id, slug: protip.slug } 14 | assert_response :success 15 | end 16 | 17 | test "create protip" do 18 | sign_in 19 | post :create, params: { protip: {editable_tags: %w[socker duby], body: 'Hey there', title: 'First!'} } 20 | assert_response :success 21 | end 22 | 23 | test "don't show bad content to signed out users" do 24 | create(:protip, bad_content: true) 25 | get :index 26 | assert_response :success 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/controllers/subscribers_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class SubscribersControllerTest < ActionController::TestCase 4 | test "create" do 5 | subscriber = create(:user) 6 | protip = create(:protip) 7 | sign_in_as subscriber 8 | 9 | assert_difference ->{ protip.reload.subscribers.size }, 1 do 10 | post :create, params: { protip_id: protip.id, format: :json } 11 | end 12 | 13 | assert_includes assigns(:protip).subscribers, subscriber.id 14 | end 15 | 16 | test "destroy" do 17 | subscriber = create(:user) 18 | protip = create(:protip, subscribers: [subscriber.id]) 19 | 20 | sign_in_as subscriber 21 | 22 | assert_difference ->{ protip.reload.subscribers.size }, -1 do 23 | delete :destroy, params: { protip_id: protip.id, format: :json } 24 | end 25 | 26 | assert_not_includes assigns(:protip).subscribers, subscriber.id 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /test/controllers/users_controller_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class UsersControllerTest < ActionController::TestCase 4 | test "profile" do 5 | user = create(:user) 6 | 7 | get :show, params: { username: user.username } 8 | assert_response :success 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/factories/comment.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :comment do 3 | association :article, factory: :protip 4 | user 5 | body { Faker::Lorem.words } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/factories/protip.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :protip do 3 | user 4 | title { Faker::Lorem.words } 5 | body { Faker::Lorem.paragraphs } 6 | tags { (1..5).map{|i| Faker::Lorem.word } } 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/factories/user.rb: -------------------------------------------------------------------------------- 1 | FactoryGirl.define do 2 | factory :user do 3 | sequence(:username) {|i| "user_#{i}" } 4 | email { Faker::Internet.email } 5 | password { Faker::Internet.password } 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/fixtures/jobs.yml: -------------------------------------------------------------------------------- 1 | # Read about fixtures at http://api.rubyonrails.org/classes/ActiveRecord/FixtureSet.html 2 | 3 | # This model initially had no columns defined. If you add columns to the 4 | # model remove the '{}' from the fixture names and add the columns immediately 5 | # below each fixture, per the syntax in the comments below 6 | # 7 | one: {} 8 | # column: value 9 | # 10 | two: {} 11 | # column: value 12 | -------------------------------------------------------------------------------- /test/helpers/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/test/helpers/.keep -------------------------------------------------------------------------------- /test/integration/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/test/integration/.keep -------------------------------------------------------------------------------- /test/mailers/comment_mailer_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class CommentMailerTest < ActionMailer::TestCase 4 | test 'new comment' do 5 | user = create(:user) 6 | comment = create(:comment) 7 | article = comment.article 8 | 9 | email = CommentMailer.new_comment(user, comment) 10 | 11 | assert_emails 1 do 12 | email.deliver_now 13 | end 14 | 15 | assert_equal ["notifications@coderwall.com"], email.from 16 | assert_equal [user.email], email.to 17 | assert_equal "New Comment [Re: #{article.title}]", email.subject 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/models/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/test/models/.keep -------------------------------------------------------------------------------- /test/models/badge_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../test_helper", __FILE__) 2 | 3 | class BadgeTest < ActiveSupport::TestCase 4 | should belong_to(:user) 5 | end 6 | -------------------------------------------------------------------------------- /test/models/job_test.rb: -------------------------------------------------------------------------------- 1 | require 'test_helper' 2 | 3 | class JobTest < ActiveSupport::TestCase 4 | # test "the truth" do 5 | # assert true 6 | # end 7 | end 8 | -------------------------------------------------------------------------------- /test/models/user_test.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../../test_helper", __FILE__) 2 | 3 | class UserTest < ActiveSupport::TestCase 4 | should have_many(:likes) 5 | should have_many(:pictures) 6 | should have_many(:protips) 7 | should have_many(:comments) 8 | should have_many(:badges) 9 | 10 | should validate_presence_of(:username) 11 | should validate_presence_of(:email) 12 | end 13 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV.delete('CAPTCHA_SECRET') # TODO: investigate this. Does rails test automatically pull in .env now?? 2 | 3 | ENV['RAILS_ENV'] ||= 'test' 4 | require File.expand_path('../../config/environment', __FILE__) 5 | require 'rails/test_help' 6 | require "clearance/test_unit" 7 | 8 | class ActiveSupport::TestCase 9 | include FactoryGirl::Syntax::Methods 10 | 11 | setup do 12 | ReactOnRails::TestHelper.ensure_assets_compiled 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /update-ssl.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | FILE=/tmp/coderwall-certs.txt 4 | 5 | extract_cert() { 6 | sed -n "/$1/,/END CERTIFICATE/p" $FILE | tail -n +2 7 | } 8 | 9 | heroku run rake letsencrypt_plugin > $FILE 10 | extract_cert coderwall.com-cert.pem > /tmp/coderwall.com-cert.pem 11 | extract_cert coderwall.com-key.pem > /tmp/coderwall.com-key.pem 12 | heroku certs:update /tmp/coderwall.com-cert.pem /tmp/coderwall.com-key.pem --confirm coderwall-next 13 | -------------------------------------------------------------------------------- /vendor/assets/javascripts/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/vendor/assets/javascripts/.keep -------------------------------------------------------------------------------- /vendor/assets/stylesheets/.keep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/coderwall/coderwall-next/5488c293178f4b1c7f2b3600c27733f1b4ad4eaf/vendor/assets/stylesheets/.keep --------------------------------------------------------------------------------