From b5c25fb4233b2ebc2534274b8aaf985bcc50d0fd Mon Sep 17 00:00:00 2001 From: "V. Elenhaupt" Date: Thu, 13 Jul 2017 18:20:06 +0300 Subject: [PATCH] Autotweets (#1) * Sidekiq to tweet announcements * Add staging env * Sidekiq number of workers * Correct twitter env vars * Add deployment notes --- Procfile | 2 ++ README.md | 15 +++++++-- config/application.cr | 31 ++++++------------ config/site.yml | 4 +++ db/seed.cr | 4 +-- shard.lock | 28 ++++++++++++++++ shard.yml | 7 ++++ src/controllers/announcement_controller.cr | 4 +-- src/crystal-ann.cr | 9 ++++++ src/models/announcement.cr | 4 +++ src/sidekiq.cr | 7 ++++ src/workers/tweet_announcement.cr | 37 ++++++++++++++++++++++ 12 files changed, 124 insertions(+), 28 deletions(-) create mode 100644 Procfile create mode 100644 src/sidekiq.cr create mode 100644 src/workers/tweet_announcement.cr diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..3b392fb --- /dev/null +++ b/Procfile @@ -0,0 +1,2 @@ +web: ./app +worker: ./sidekiq -c 3 diff --git a/README.md b/README.md index 85c4be8..9cd3ddd 100644 --- a/README.md +++ b/README.md @@ -20,11 +20,22 @@ $ crystal db/seed.cr $ amber watch ``` -## Deployment +## Deployment to Heroku ``` $ heroku create app-name --buildpack https://github.com/crystal-lang/heroku-buildpack-crystal.git +$ heroku buildpacks:add https://github.com/veelenga/heroku-buildpack-sidekiq.cr +$ git push heroku master +``` + +And set environment variables: + +``` +$ heroku config:set AMBER_ENV=production $ heroku config:set GITHUB_ID=github_client_id $ heroku config:set GITHUB_SECRET=github_client_secret -$ git push heroku master +$ heroku config:set TWITTER_CONSUMER_KEY=twitter_consumer_key +$ heroku config:set TWITTER_CONSUMER_SECRET=twitter_consumer_secret +$ heroku config:set TWITTER_ACCESS_TOKEN=twitter_access_token +$ heroku config:set TWITTER_ACCESS_TOKEN_SECRET=twitter_access_token_secret ``` diff --git a/config/application.cr b/config/application.cr index ca7b8f5..8c31c1b 100644 --- a/config/application.cr +++ b/config/application.cr @@ -1,32 +1,21 @@ +require "amber" +require "yaml" +require "sidekiq" +require "multi_auth" require "option_parser" -OptionParser.parse! do |opts| - opts.on("-p PORT", "--port PORT", "") do |opt_port| - end -end +AMBER_PORT = ENV["PORT"]? || 3008 +AMBER_ENV = ENV["AMBER_ENV"]? || "development" -AMBER_ENV = ENV["AMBER_ENV"]? || "development" +SITE = YAML.parse(File.read "config/site.yml")[AMBER_ENV] +Sidekiq::Client.default_context = Sidekiq::Client::Context.new +MultiAuth.config("github", ENV.fetch("GITHUB_ID", ""), ENV.fetch("GITHUB_SECRET", "")) Amber::Server.instance.config do |app| app_path = __FILE__ app.name = "Crystal [ANN] web application." - app.port = (ENV["PORT"]? || "3008").to_i + app.port = AMBER_PORT.to_i app.env = AMBER_ENV.to_s app.log = ::Logger.new(STDOUT) app.log.level = ::Logger::INFO end - -require "yaml" -SITE = YAML.parse(File.read "config/site.yml")[AMBER_ENV] - -require "multi_auth" -MultiAuth.config("github", ENV.fetch("GITHUB_ID", ""), ENV.fetch("GITHUB_SECRET", "")) - -require "micrate" -require "pg" -if AMBER_ENV != "development" - puts "Migrating data" - Micrate::DB.connection_url = ENV["DATABASE_URL"]? - Micrate::Cli.run_up - puts "Migration finished" -end diff --git a/config/site.yml b/config/site.yml index b6262d8..9148ad2 100644 --- a/config/site.yml +++ b/config/site.yml @@ -6,6 +6,10 @@ development: <<: *default url: http://localhost:3008 +staging: + <<: *default + url: https://crystal-ann-staging.herokuapp.com + production: <<: *default url: https://crystal-ann.com diff --git a/db/seed.cr b/db/seed.cr index 34d40a4..a43e2cd 100644 --- a/db/seed.cr +++ b/db/seed.cr @@ -1,8 +1,6 @@ require "amber" -require "../src/controllers/**" +require "../config/application" require "../src/models/**" -require "../src/views/**" -require "../config/*" require "./seeds/*" ENV["AMBER_ENV"] = "development" diff --git a/shard.lock b/shard.lock index 50d9582..ff0af62 100644 --- a/shard.lock +++ b/shard.lock @@ -4,6 +4,10 @@ shards: github: Amber-Crystal/amber version: 0.1.4 + baked_file_system: + github: schovi/baked_file_system + version: 0.9.4 + db: github: crystal-lang/crystal-db version: 0.4.2 @@ -12,6 +16,18 @@ shards: github: kemalyst/granite-orm version: 0.6.2 + kemal: + github: kemalcr/kemal + version: 0.19.0 + + kemal-csrf: + github: kemalcr/kemal-csrf + version: 0.2.0 + + kemal-session: + github: kemalcr/kemal-session + commit: 5cb3b5b207db92852651a7b7ec4f95d6478f8580 + kemalyst-validators: github: kemalyst/kemalyst-validators version: 0.2.0 @@ -32,6 +48,10 @@ shards: github: will/crystal-pg version: 0.13.3 + pool: + github: ysbaddaden/pool + version: 0.2.3 + radix: github: luislavena/radix commit: 211418416adba540b594af2260e1b5d0c8877c9e @@ -40,7 +60,15 @@ shards: github: stefanwille/crystal-redis version: 1.8.0 + sidekiq: + github: mperham/sidekiq.cr + commit: d91d68f8064ea83d252a4002cf492f65516c7a70 + slang: github: jeromegn/slang commit: b817c89c7e5ae39562710c0d6c7f42cee685e14f + twitter: + github: veelenga/twitter.cr + commit: d12bd67edcfcfe0867b33c9e5ebae2052b991857 + diff --git a/shard.yml b/shard.yml index 19758d9..9468fae 100644 --- a/shard.yml +++ b/shard.yml @@ -28,3 +28,10 @@ dependencies: micrate: github: juanedi/micrate + + sidekiq: + github: mperham/sidekiq.cr + branch: master + + twitter: + github: veelenga/twitter.cr diff --git a/src/controllers/announcement_controller.cr b/src/controllers/announcement_controller.cr index 54ee4c6..4cb16ae 100644 --- a/src/controllers/announcement_controller.cr +++ b/src/controllers/announcement_controller.cr @@ -1,4 +1,5 @@ require "./application_controller" +require "../workers/tweet_announcement" class AnnouncementController < ApplicationController PER_PAGE = 10 @@ -33,10 +34,9 @@ class AnnouncementController < ApplicationController announcement.user_id = current_user!.id if announcement.valid? && announcement.save - flash["success"] = "Created Announcement successfully." + Workers::TweetAnnouncement.async.perform(announcement.id.not_nil!) redirect_to "/announcements" else - flash["danger"] = "Could not create Announcement!" render("new.slang") end end diff --git a/src/crystal-ann.cr b/src/crystal-ann.cr index 418edd5..fb13812 100644 --- a/src/crystal-ann.cr +++ b/src/crystal-ann.cr @@ -1,8 +1,17 @@ require "amber" +require "micrate" +require "pg" require "./controllers/**" require "./mailers/**" require "./models/**" require "./views/**" require "../config/*" +if AMBER_ENV != "development" + puts "Migrating data" + Micrate::DB.connection_url = ENV["DATABASE_URL"]? + Micrate::Cli.run_up + puts "Migration finished" +end + Amber::Server.instance.run diff --git a/src/models/announcement.cr b/src/models/announcement.cr index c58c535..dfd51d6 100644 --- a/src/models/announcement.cr +++ b/src/models/announcement.cr @@ -76,4 +76,8 @@ class Announcement < Granite::ORM def user User.find(user_id) end + + def path + "/announcements/#{id}" + end end diff --git a/src/sidekiq.cr b/src/sidekiq.cr new file mode 100644 index 0000000..7bc7dca --- /dev/null +++ b/src/sidekiq.cr @@ -0,0 +1,7 @@ +require "../config/application" +require "./workers/**" +require "sidekiq/cli" + +cli = Sidekiq::CLI.new +server = cli.configure { } +cli.run(server) diff --git a/src/workers/tweet_announcement.cr b/src/workers/tweet_announcement.cr new file mode 100644 index 0000000..c311906 --- /dev/null +++ b/src/workers/tweet_announcement.cr @@ -0,0 +1,37 @@ +require "sidekiq" +require "twitter" +require "../models/announcement" + +module Workers + class TweetAnnouncement + include Sidekiq::Worker + + def perform(id : Int64) + if announcement = Announcement.find(id) + tweet(announcement) + end + end + + def twitter_client + @twitter_client ||= + Twitter::REST::Client.new ENV["TWITTER_CONSUMER_KEY"], + ENV["TWITTER_CONSUMER_SECRET"], + ENV["TWITTER_ACCESS_TOKEN"], + ENV["TWITTER_ACCESS_TOKEN_SECRET"] + end + + def tweet(announcement) + logger.error "Tweeting Announcement ##{announcement.id}" + status = tweet_template announcement + twitter_client.post("/1.1/statuses/update.json", {"status" => status}) + rescue e + logger.error "Unable to tweet Announcement ##{announcement.id} (#{status})" + logger.error "Reason: #{e.message}" + end + + def tweet_template(announcement) + site_url = SITE["url"] + "#{announcement.title} #{site_url}#{announcement.path} #crystallang" + end + end +end