From dcedf4e030123042e9776d6e52e9d2684817594f Mon Sep 17 00:00:00 2001 From: Alexander Mancevice Date: Sat, 3 Dec 2022 11:20:45 -0500 Subject: [PATCH] clean repo --- .rspec | 3 - .ruby-version | 2 +- .terraform.lock.hcl | 42 - Gemfile | 13 - Gemfile.lock | 121 -- Rakefile | 26 - lib/.bundle/config | 4 - lib/Gemfile | 7 - lib/Gemfile.lock | 58 - lib/http.rb | 25 - lib/lib/common.rb | 36 - lib/lib/reddit/brutalism.rb | 69 - lib/lib/reddit/post.rb | 118 -- lib/lib/slack/auth.rb | 48 - lib/lib/twitter/brutalismbot.rb | 49 - lib/reddit.rb | 28 - lib/slack.rb | 8 - lib/tweet.rb | 15 - .../bundle/ruby/2.7.0/cache/yake-0.4.2.gem | Bin 10240 -> 0 bytes .../bundle/ruby/2.7.0/gems/yake-0.4.2/LICENSE | 21 - .../ruby/2.7.0/gems/yake-0.4.2/README.md | 218 -- .../ruby/2.7.0/gems/yake-0.4.2/lib/yake.rb | 6 - .../2.7.0/gems/yake-0.4.2/lib/yake/api.rb | 110 - .../2.7.0/gems/yake-0.4.2/lib/yake/datadog.rb | 44 - .../2.7.0/gems/yake-0.4.2/lib/yake/dsl.rb | 38 - .../2.7.0/gems/yake-0.4.2/lib/yake/errors.rb | 63 - .../2.7.0/gems/yake-0.4.2/lib/yake/logger.rb | 52 - .../2.7.0/gems/yake-0.4.2/lib/yake/version.rb | 5 - .../2.7.0/specifications/yake-0.4.2.gemspec | 20 - spec/http_spec.rb | 39 - spec/reddit_spec.rb | 36 - spec/slack_spec.rb | 34 - spec/spec_helper.rb | 24 - spec/tweet_spec.rb | 69 - tasks/db.rake | 22 - tasks/logs.rake | 17 - tasks/states.rake | 22 - tasks/terraform.rake | 47 - tasks/vendor.rake | 13 - terraform.tf | 1880 ----------------- 40 files changed, 1 insertion(+), 3451 deletions(-) delete mode 100644 .rspec delete mode 100644 .terraform.lock.hcl delete mode 100644 Gemfile delete mode 100644 Gemfile.lock delete mode 100644 Rakefile delete mode 100644 lib/.bundle/config delete mode 100644 lib/Gemfile delete mode 100644 lib/Gemfile.lock delete mode 100644 lib/http.rb delete mode 100644 lib/lib/common.rb delete mode 100644 lib/lib/reddit/brutalism.rb delete mode 100644 lib/lib/reddit/post.rb delete mode 100644 lib/lib/slack/auth.rb delete mode 100644 lib/lib/twitter/brutalismbot.rb delete mode 100644 lib/reddit.rb delete mode 100644 lib/slack.rb delete mode 100644 lib/tweet.rb delete mode 100644 lib/vendor/bundle/ruby/2.7.0/cache/yake-0.4.2.gem delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/LICENSE delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/README.md delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake.rb delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/api.rb delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/datadog.rb delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/dsl.rb delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/errors.rb delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/logger.rb delete mode 100644 lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/version.rb delete mode 100644 lib/vendor/bundle/ruby/2.7.0/specifications/yake-0.4.2.gemspec delete mode 100644 spec/http_spec.rb delete mode 100644 spec/reddit_spec.rb delete mode 100644 spec/slack_spec.rb delete mode 100644 spec/spec_helper.rb delete mode 100644 spec/tweet_spec.rb delete mode 100644 tasks/db.rake delete mode 100644 tasks/logs.rake delete mode 100644 tasks/states.rake delete mode 100644 tasks/terraform.rake delete mode 100644 tasks/vendor.rake delete mode 100644 terraform.tf diff --git a/.rspec b/.rspec deleted file mode 100644 index 34c5164..0000000 --- a/.rspec +++ /dev/null @@ -1,3 +0,0 @@ ---format documentation ---color ---require spec_helper diff --git a/.ruby-version b/.ruby-version index a4dd9db..1f7da99 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -2.7.4 +2.7.7 diff --git a/.terraform.lock.hcl b/.terraform.lock.hcl deleted file mode 100644 index f1e3f50..0000000 --- a/.terraform.lock.hcl +++ /dev/null @@ -1,42 +0,0 @@ -# This file is maintained automatically by "terraform init". -# Manual edits may be lost in future updates. - -provider "registry.terraform.io/hashicorp/archive" { - version = "2.2.0" - constraints = "~> 2.2" - hashes = [ - "h1:2K5LQkuWRS2YN1/YoNaHn9MAzjuTX8Gaqy6i8Mbfv8Y=", - "h1:62mVchC1L6vOo5QS9uUf52uu0emsMM+LsPQJ1BEaTms=", - "zh:06bd875932288f235c16e2237142b493c2c2b6aba0e82e8c85068332a8d2a29e", - "zh:0c681b481372afcaefddacc7ccdf1d3bb3a0c0d4678a526bc8b02d0c331479bc", - "zh:100fc5b3fc01ea463533d7bbfb01cb7113947a969a4ec12e27f5b2be49884d6c", - "zh:55c0d7ddddbd0a46d57c51fcfa9b91f14eed081a45101dbfc7fd9d2278aa1403", - "zh:73a5dd68379119167934c48afa1101b09abad2deb436cd5c446733e705869d6b", - "zh:841fc4ac6dc3479981330974d44ad2341deada8a5ff9e3b1b4510702dfbdbed9", - "zh:91be62c9b41edb137f7f835491183628d484e9d6efa82fcb75cfa538c92791c5", - "zh:acd5f442bd88d67eb948b18dc2ed421c6c3faee62d3a12200e442bfff0aa7d8b", - "zh:ad5720da5524641ad718a565694821be5f61f68f1c3c5d2cfa24426b8e774bef", - "zh:e63f12ea938520b3f83634fc29da28d92eed5cfbc5cc8ca08281a6a9c36cca65", - "zh:f6542918faa115df46474a36aabb4c3899650bea036b5f8a5e296be6f8f25767", - ] -} - -provider "registry.terraform.io/hashicorp/aws" { - version = "3.66.0" - constraints = "~> 3.38" - hashes = [ - "h1:1XTVIymToI4D/GoPckiPwk+VdSsPp7EqRuLQKUHu9RA=", - "h1:V2HWB95PQn9c6P1GsPaUKZHRSBiAyh45tngFVsg4SxQ=", - "zh:21331ca956428f207c276c3e57ac8aa854b950b740e20318da727928460da42c", - "zh:2ae02a92abac8c99095c98272176543fefc9a3e584a5ca279c9d346cae1825f1", - "zh:578e06c8559a124b9d9c904d526ba4d1abe1fe4c66c5c130b7d4a595ff6338ff", - "zh:699f58910307d42d6320d8ccf10d909a262fe7b5fc2dd7bf9e15eaa5a1374d4d", - "zh:8f61bf009a6f5ebfa8e1588df8d4f75ae275c6ef636eea1ef377aa4e6a388be5", - "zh:931b0beafcbd7e11fa59e554fd41b54e548cb994908e89f31ea9d612fce26eb9", - "zh:b3d5abde76e4bda153a63a11c1fa5aec19f6f977cbf1e26adcd2916ec93c1b6f", - "zh:b5a4f97335387b21fa38eed7c22b3c92f6b1157ed301a0880548277136962e48", - "zh:bfaf6c7fa7cc13cb833015d4eef700df652f40c6e658e76cf23aebfa4c8b7afe", - "zh:d56f612da07dee639e8eb75ce6a6f1b1255ec964e6fe9333c6a8fddd7ccf931f", - "zh:f695dfb0dbea6f48f6e6eb0ff5766f98d1f1708b77889df39c67113fbcd8a3cd", - ] -} diff --git a/Gemfile b/Gemfile deleted file mode 100644 index f394379..0000000 --- a/Gemfile +++ /dev/null @@ -1,13 +0,0 @@ -source 'https://rubygems.org' - -gem 'aws-sdk-cloudwatch', '~> 1.0' -gem 'aws-sdk-dynamodb', '~> 1.0' -gem 'aws-sdk-s3', '~> 1.0' -gem 'aws-sdk-secretsmanager', '~> 1.0' -gem 'dotenv', '~> 2.7', require: 'dotenv/load' -gem 'pry', '~> 0.13' -gem 'rake', '~> 13.0' -gem 'rspec', '~> 3.9', require: 'rspec/core/rake_task' -gem 'simplecov', '~> 0.21' -gem 'twitter', '~> 7.0' -gem 'yake', '~> 0.3' diff --git a/Gemfile.lock b/Gemfile.lock deleted file mode 100644 index 2a8d168..0000000 --- a/Gemfile.lock +++ /dev/null @@ -1,121 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - aws-eventstream (1.2.0) - aws-partitions (1.510.0) - aws-sdk-cloudwatch (1.55.0) - aws-sdk-core (~> 3, >= 3.120.0) - aws-sigv4 (~> 1.1) - aws-sdk-core (3.121.1) - aws-eventstream (~> 1, >= 1.0.2) - aws-partitions (~> 1, >= 1.239.0) - aws-sigv4 (~> 1.1) - jmespath (~> 1.0) - aws-sdk-dynamodb (1.63.0) - aws-sdk-core (~> 3, >= 3.120.0) - aws-sigv4 (~> 1.1) - aws-sdk-kms (1.48.0) - aws-sdk-core (~> 3, >= 3.120.0) - aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.103.0) - aws-sdk-core (~> 3, >= 3.120.0) - aws-sdk-kms (~> 1) - aws-sigv4 (~> 1.4) - aws-sdk-secretsmanager (1.49.0) - aws-sdk-core (~> 3, >= 3.120.0) - aws-sigv4 (~> 1.1) - aws-sigv4 (1.4.0) - aws-eventstream (~> 1, >= 1.0.2) - buftok (0.2.0) - coderay (1.1.3) - diff-lcs (1.4.4) - docile (1.4.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - dotenv (2.7.6) - equalizer (0.0.11) - ffi (1.15.4) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - http (4.4.1) - addressable (~> 2.3) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - http-parser (~> 1.2.0) - http-cookie (1.0.4) - domain_name (~> 0.5) - http-form_data (2.3.0) - http-parser (1.2.3) - ffi-compiler (>= 1.0, < 2.0) - http_parser.rb (0.6.0) - jmespath (1.6.1) - memoizable (0.4.2) - thread_safe (~> 0.3, >= 0.3.1) - method_source (1.0.0) - multipart-post (2.1.1) - naught (1.1.0) - pry (0.14.1) - coderay (~> 1.1) - method_source (~> 1.0) - public_suffix (4.0.6) - rake (13.0.6) - rspec (3.10.0) - rspec-core (~> 3.10.0) - rspec-expectations (~> 3.10.0) - rspec-mocks (~> 3.10.0) - rspec-core (3.10.1) - rspec-support (~> 3.10.0) - rspec-expectations (3.10.1) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-mocks (3.10.2) - diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.10.0) - rspec-support (3.10.2) - simple_oauth (0.3.1) - simplecov (0.21.2) - docile (~> 1.1) - simplecov-html (~> 0.11) - simplecov_json_formatter (~> 0.1) - simplecov-html (0.12.3) - simplecov_json_formatter (0.1.3) - thread_safe (0.3.6) - twitter (7.0.0) - addressable (~> 2.3) - buftok (~> 0.2.0) - equalizer (~> 0.0.11) - http (~> 4.0) - http-form_data (~> 2.0) - http_parser.rb (~> 0.6.0) - memoizable (~> 0.4.0) - multipart-post (~> 2.0) - naught (~> 1.0) - simple_oauth (~> 0.3.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.8) - yake (0.3.0) - -PLATFORMS - arm64-darwin-20 - ruby - x86_64-darwin-20 - -DEPENDENCIES - aws-sdk-cloudwatch (~> 1.0) - aws-sdk-dynamodb (~> 1.0) - aws-sdk-s3 (~> 1.0) - aws-sdk-secretsmanager (~> 1.0) - dotenv (~> 2.7) - pry (~> 0.13) - rake (~> 13.0) - rspec (~> 3.9) - simplecov (~> 0.21) - twitter (~> 7.0) - yake (~> 0.3) - -BUNDLED WITH - 2.2.21 diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 6b23926..0000000 --- a/Rakefile +++ /dev/null @@ -1,26 +0,0 @@ -require 'dotenv/load' -require 'rake/clean' -require 'rspec/core/rake_task' - -# Load tasks -Dir['tasks/*'].map do |task| load task end - -# Create RSpec task -RSpec::Core::RakeTask.new :spec => :vendor - -task :default => :spec - -desc 'Run terraform init' -task :init => :'terraform:init' - -desc 'Run terraform plan' -task :plan => :'terraform:plan' - -desc 'Run terraform apply' -task :apply => :'terraform:apply' - -desc 'Run terraform apply -auto-approve' -task :'apply:auto' => :'terraform:apply:auto' - -desc 'List state' -task :state => :'terraform:state:list' diff --git a/lib/.bundle/config b/lib/.bundle/config deleted file mode 100644 index c07f225..0000000 --- a/lib/.bundle/config +++ /dev/null @@ -1,4 +0,0 @@ ---- -BUNDLE_CLEAN: "true" -BUNDLE_PATH: "vendor/bundle" -BUNDLE_WITHOUT: "development" diff --git a/lib/Gemfile b/lib/Gemfile deleted file mode 100644 index 53a000d..0000000 --- a/lib/Gemfile +++ /dev/null @@ -1,7 +0,0 @@ -source 'https://rubygems.org/' - -gem 'yake', '~> 0.4' - -group :development do - gem 'twitter', '~> 7.0' -end diff --git a/lib/Gemfile.lock b/lib/Gemfile.lock deleted file mode 100644 index 3c40349..0000000 --- a/lib/Gemfile.lock +++ /dev/null @@ -1,58 +0,0 @@ -GEM - remote: https://rubygems.org/ - specs: - addressable (2.8.0) - public_suffix (>= 2.0.2, < 5.0) - buftok (0.2.0) - domain_name (0.5.20190701) - unf (>= 0.0.5, < 1.0.0) - equalizer (0.0.11) - ffi (1.15.3) - ffi-compiler (1.0.1) - ffi (>= 1.0.0) - rake - http (4.4.1) - addressable (~> 2.3) - http-cookie (~> 1.0) - http-form_data (~> 2.2) - http-parser (~> 1.2.0) - http-cookie (1.0.4) - domain_name (~> 0.5) - http-form_data (2.3.0) - http-parser (1.2.3) - ffi-compiler (>= 1.0, < 2.0) - http_parser.rb (0.6.0) - memoizable (0.4.2) - thread_safe (~> 0.3, >= 0.3.1) - multipart-post (2.1.1) - naught (1.1.0) - public_suffix (4.0.6) - rake (13.0.6) - simple_oauth (0.3.1) - thread_safe (0.3.6) - twitter (7.0.0) - addressable (~> 2.3) - buftok (~> 0.2.0) - equalizer (~> 0.0.11) - http (~> 4.0) - http-form_data (~> 2.0) - http_parser.rb (~> 0.6.0) - memoizable (~> 0.4.0) - multipart-post (~> 2.0) - naught (~> 1.0) - simple_oauth (~> 0.3.0) - unf (0.1.4) - unf_ext - unf_ext (0.0.7.7) - yake (0.4.2) - -PLATFORMS - ruby - x86_64-darwin-20 - -DEPENDENCIES - twitter (~> 7.0) - yake (~> 0.4) - -BUNDLED WITH - 2.2.22 diff --git a/lib/http.rb b/lib/http.rb deleted file mode 100644 index b45a45c..0000000 --- a/lib/http.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'net/http' - -require 'yake' - -require_relative 'lib/common' - -def request(klass, event) - uri = URI event[:url] - ssl = uri.scheme == 'https' - hed = event[:headers] || {} - req = klass.new(uri, **hed) - Net::HTTP.start(uri.host, uri.port, use_ssl: ssl) do |http| - res = http.request req, event[:body] - - { - statusCode: res.code, - headers: res.each_header.to_h, - body: res.body - } - end -end - -handler :get do |event| request Net::HTTP::Get, event.symbolize_names end -handler :head do |event| request Net::HTTP::Head, event.symbolize_names end -handler :post do |event| request Net::HTTP::Post, event.symbolize_names end diff --git a/lib/lib/common.rb b/lib/lib/common.rb deleted file mode 100644 index 35a975c..0000000 --- a/lib/lib/common.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'base64' -require 'json' -require 'time' - -class Hash - def encode64() to_json.encode64 end - def symbolize_names() JSON.parse to_json, symbolize_names: true end - def to_form() URI.encode_www_form(self) end -end - -class Integer - def days() hours * 24 end - def hours() minutes * 60 end - def minutes() seconds * 60 end - def seconds() self end - def utc() UTC.at(self) end -end - -class String - def camel_case() split(/_/).map(&:capitalize).join end - def decode64() Base64.strict_decode64(self) end - def encode64() Base64.strict_encode64(self) end - def snake_case() gsub(/([a-z])([A-Z])/, '\1_\2').downcase end - def to_h_from_json(**params) JSON.parse(self, **params) end - def to_h_from_form() URI.decode_www_form(self).to_h end -end - -class Symbol - def camel_case() to_s.camel_case.to_sym end - def snake_case() to_s.snake_case.to_sym end -end - -class UTC < Time - def self.at(...) super.utc end - def self.now() super.utc end -end diff --git a/lib/lib/reddit/brutalism.rb b/lib/lib/reddit/brutalism.rb deleted file mode 100644 index 18973f4..0000000 --- a/lib/lib/reddit/brutalism.rb +++ /dev/null @@ -1,69 +0,0 @@ -require 'open-uri' -require 'time' - -require 'aws-sdk-dynamodb' -require 'yake/logger' - -require_relative 'post' -require_relative '../common' - -module Reddit - class Brutalism - include Enumerable - include Yake::Logger - - attr_reader :headers, :table - - TABLE_NAME = ENV['TABLE_NAME'] || 'Brutalismbot' - USER_AGENT = ENV['REDDIT_USER_AGENT'] || 'Brutalismbot' - - def initialize(resource = :new, table = nil, **headers) - @uri = URI "https://www.reddit.com/r/brutalism/#{ resource }.json?raw_json=1" - @table = table || Aws::DynamoDB::Table.new(name: TABLE_NAME) - @headers = { 'user-agent' => USER_AGENT, **headers } - end - - def each - logger.info("GET #{ @uri }") - URI.open(@uri, **@headers) do |stream| - stream.read.to_h_from_json.symbolize_names.dig(:data, :children).each do |child| - yield Post.new child[:data] - end - end - end - - def all - to_a - end - - def latest - params = { key: { GUID: 'STATS/MAX', SORT: 'REDDIT/POST' }, projection_expression: 'CREATED_UTC' } - logger.info("GET ITEM #{ params.to_json }") - item = @table.get_item(**params).item || {} - start = Time.parse item.fetch('CREATED_UTC', '1970-01-01T00:00:00Z') - after(start).reject(&:is_self?).sort_by(&:created_utc) - end - - def after(start) - select { |post| post.created_utc > start } - end - - def between(start, stop) - select { |post| post.created_utc > start && post.created_utc < stop } - end - - def before(stop) - select { |post| post.created_utc < stop } - end - - class << self - def hot(table = nil, **headers) - new(:hot, table, **headers) - end - - def top(table = nil, **headers) - new(:top, table, **headers) - end - end - end -end diff --git a/lib/lib/reddit/post.rb b/lib/lib/reddit/post.rb deleted file mode 100644 index 170a649..0000000 --- a/lib/lib/reddit/post.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'json' -require 'time' - -module Reddit - class Post < OpenStruct - def initialize(...) - super - yield self if block_given? - end - - def inspect - "#<#{ self.class } #{ permalink }>" - end - - def created_after?(time) - created_utc > time - end - - def created_before?(time) - created_utc < time - end - - def created_utc - begin Time.at(self['created_utc']&.to_f).utc rescue TypeError end - end - - def is_gallery? - is_gallery || false - end - - def is_self? - is_self || false - end - - def media - if is_self? - [] - elsif is_gallery? - media_gallery - else - media_preview - end - end - - def permalink_url - "https://www.reddit.com#{ permalink }" - end - - def to_h - @table.sort.to_h - end - - def to_json - to_h.to_json - end - - def to_slack - blocks = media.map(&:last).each_with_index.map do |image,i| - { - type: 'image', - image_url: image[:u], - alt_text: title, - title: { - type: 'plain_text', - text: "/r/brutalism [#{ i + 1 }/#{ media.count }]", - emoji: true - } - } - end << { - type: 'context', - elements: [ - { - type: 'mrkdwn', - text: "<#{ permalink_url }|#{ title }>" - } - ] - } - - { text: title, blocks: blocks } - end - - def to_twitter - # Get status - max = 279 - permalink_url.length - status = title.length <= max ? title : "#{ title[0..max] }…" - status << "\n#{ permalink_url }" - - # Zip status with media - size = (media.count % 4).between?(1, 2) ? 3 : 4 - updates = media.each_slice(size).zip([status]).map do |media, status| - { status: status, media: media.map { |x| x.last[:u] } }.compact - end - - # Return updates - { updates: updates, count: updates.count } - end - - private - - ## - # Get media URLs from gallery - def media_gallery - media_metadata.values.map do |m| - (m[:p] + [ m[:s] ]).sort_by { |i| i[:x] * i[:y] } - end - end - - ## - # Get media URLs from previews - def media_preview - (preview&.dig(:images) || []).map do |m| - (m[:resolutions] + [ m[:source] ]).map do |i| - i.transform_keys { |k| { :url => :u, :width => :x, :height => :y }[k] }.slice(:y, :x, :u) - end.sort_by { |i| i[:x] * i[:y] } - end - end - end -end diff --git a/lib/lib/slack/auth.rb b/lib/lib/slack/auth.rb deleted file mode 100644 index 4ee0114..0000000 --- a/lib/lib/slack/auth.rb +++ /dev/null @@ -1,48 +0,0 @@ -module Slack - class Auth < OpenStruct - def initialize(...) - super - yield self if block_given? - end - - def inspect - "#<#{ self.class } team_id: #{ team_id }, channel_id: #{ channel_id }>" - end - - def to_h - @table.to_h.sort.to_h - end - - def to_json - to_h.to_json - end - - def key - File.join(team_id, channel_id) - end - - def channel_id - dig(:incoming_webhook, :channel_id) - end - - def channel_name - dig(:incoming_webhook, :channel) - end - - def team_id - dig(:team, :id) || dig(:team_id) - end - - def team_name - dig(:team, :name) || dig(:team_name) - end - - def url - URI(dig :incoming_webhook, :url) - end - - def user_id - dig(:authed_user, :id) || dig(:user_id) - end - end -end diff --git a/lib/lib/twitter/brutalismbot.rb b/lib/lib/twitter/brutalismbot.rb deleted file mode 100644 index c22bf75..0000000 --- a/lib/lib/twitter/brutalismbot.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'json' -require 'open-uri' - -require 'aws-sdk-secretsmanager' -require 'yake/logger' - -module Twitter - class Brutalismbot - include Yake::Logger - - SECRET_ID = ENV['SECRET_ID'] || 'brutalismbot/twitter' - - def initialize(client:nil) - @client = client - end - - def client - @client ||= begin - params = { secret_id: SECRET_ID } - logger.info("GET SECRET #{ params.to_json }") - secret = OpenStruct.new JSON.parse Aws::SecretsManager::Client.new.get_secret_value(**params).secret_string - require 'twitter' - Twitter::REST::Client.new do |config| - config.access_token = secret.TWITTER_ACCESS_TOKEN - config.access_token_secret = secret.TWITTER_ACCESS_TOKEN_SECRET - config.consumer_key = secret.TWITTER_CONSUMER_KEY - config.consumer_secret = secret.TWITTER_CONSUMER_SECRET - end - end - end - - def post(updates:, count:, **opts) - updates.each_with_index do |update, i| - logger.info("PUSH twitter://@brutalismbot [#{i + 1}/#{count}]") - status = update[:status] - media = update[:media].map do |url| - logger.info("GET #{ url }") - URI.open(url) - end - client.update_with_media(status, media, opts).tap do |res| - opts[:in_reply_to_status_id] = res.id - update.update id: res.id - end - end - - { updates: updates, count: count } - end - end -end diff --git a/lib/reddit.rb b/lib/reddit.rb deleted file mode 100644 index e2d44da..0000000 --- a/lib/reddit.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'aws-sdk-dynamodb' -require 'yake' - -require_relative 'lib/common' -require_relative 'lib/reddit/brutalism' - -TABLE = Aws::DynamoDB::Table.new name: ENV['TABLE_NAME'] || 'Brutalismbot' -R_BRUTALISM = Reddit::Brutalism.new :new, TABLE - -LAG = (ENV['LAG_HOURS'] || '8').to_i.hours -TTL = (ENV['TTL_DAYS'] || '14').to_i.days - -handler :dequeue do |event| - queue = R_BRUTALISM.latest - post = queue.shift if queue.first&.created_before?(UTC.now - LAG) - - { - QueueSize: queue.size, - NextPost: post.nil? ? nil : { - CREATED_UTC: post.created_utc.iso8601, - DATA: post.to_h, - NAME: post.name, - PERMALINK: post.permalink, - TITLE: post.title, - TTL: post.created_utc.to_i + TTL, - } - } -end diff --git a/lib/slack.rb b/lib/slack.rb deleted file mode 100644 index bd1e364..0000000 --- a/lib/slack.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'yake' - -require_relative 'lib/common' -require_relative 'lib/reddit/post' - -handler :transform do |event| - Reddit::Post.new(event.symbolize_names).to_slack -end diff --git a/lib/tweet.rb b/lib/tweet.rb deleted file mode 100644 index 80703fc..0000000 --- a/lib/tweet.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'yake' - -require_relative 'lib/common' -require_relative 'lib/reddit/post' -require_relative 'lib/twitter/brutalismbot' - -TWITTER = Twitter::Brutalismbot.new - -handler :transform do |event| - Reddit::Post.new(event.symbolize_names).to_twitter -end - -handler :post do |event| - TWITTER.post(**event.symbolize_names) -end diff --git a/lib/vendor/bundle/ruby/2.7.0/cache/yake-0.4.2.gem b/lib/vendor/bundle/ruby/2.7.0/cache/yake-0.4.2.gem deleted file mode 100644 index 0b131c72d5f4ac988acfa6da9b34f3ac764f5fde..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10240 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 'application/json' - -get '/fizz' do - respond 200, { ok: true }.to_json -end - -handler :lambda_handler do |event| - route event -rescue => err - respond 500, { message: err.message }.to_json -end - -# Handler signature: `lambda_function.lambda_handler` -``` - -## Installation - -Add this line to your application's Gemfile: - -```ruby -gem 'yake' -``` - -And then execute: - -```bash -bundle install -``` - -Or install it yourself as: - -```bash -gem install yake -``` - -## Why Is It Called "yake"? - -"λ" + Rake, but "λ" is hard to type and I think "y" looks like a funny little upside-down-and-backwards Lambda symbol. - -## Why Use It? - -So why use `yake` for your Lambda functions? - -#### Event Logging - -By default, the `handler` function wraps its block in log lines formatted to match the style of Amazon's native Lambda logs sent to CloudWatch. Each invocation of the handler will log both the _input event_ and the _returned value_, prefixed with the ID of the request: - -``` -START RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf Version: $LATEST -INFO RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf EVENT { … } -… -INFO RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf RETURN { … } -END RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf -REPORT RequestId: 149c500f-028a-4b57-8977-0ef568cf8caf Duration: 43.97 ms Billed Duration: 44 ms Memory Size: 128 MB Max Memory Used: 77 MB -``` - -Logging the request ID in this way makes gathering logs lines for a particular execution in CloudWatch much easier. - -You can or disable the logger: - -```ruby -logging :off # disables logging entirely -logging pretty: false # Logs event/result in compact JSON -logging :on, MyLogger.new # Use a custom logger -``` - -Include `Yake::Logger` on a class to access this logger: - -```ruby -class Fizz - include Yake::Logger -end - -Fizz.new.logger == Yake.logger -# => true -``` - -#### API Routes - -A common use of Lambda functions is as a proxy for API Gateway. Oftentimes users will deploy a single Lambda function to handle all requests coming from API Gateway. - -Requiring the `yake/api` module will add the API-specific DSL into your handler. - -Define API routes using Sinatra-like syntax - -```ruby -any '/…' do |event| - # Handle 'ANY /…' route key events -end - -delete '/…' do |event| - # Handle 'DELETE /…' route key events -end - -get '/…' do |event| - # Handle 'GET /…' route key events -end - -head '/…' do |event| - # Handle 'HEAD /…' route key events -end - -options '/…' do |event| - # Handle 'OPTIONS /…' route key events -end - -patch '/…' do |event| - # Handle 'PATCH /…' route key events -end - -post '/…' do |event| - # Handle 'POST /…' route key events -end - -put '/…' do |event| - # Handle 'PUT /…' route key events -end -``` - -Helper methods are also made available to help produce a response for API Gateway: - -Set a default header for ALL responses: - -```ruby -header 'content-type' => 'application/json; charset=utf-8' -header 'x-custom-header' => 'fizz' -``` - -Produce an API Gateway-style response object: - -```ruby -respond 200, { ok: true }.to_json, 'x-extra-header' => 'buzz' -# { -# "statusCode" => 200, -# "body" => '{"ok":true}', -# "headers" => { "x-extra-header" => "buzz" } -# } -``` - -Route an event to one of the declared routes: - -```ruby -handler :lambda_handler do |event| - route event -rescue Yake::UndeclaredRoute => err - respond 404, { message: err.message }.to_json -rescue => err - respond 500, { message: err.message }.to_json -end -``` - -#### Zero Dependencies - -Finally, `yake` does not depend on any other gems, using the Ruby stdlib only. This helps keep your Lambda packages slim & speedy. - -## Datadog Integration - -As of `~> 0.4`, `yake` comes with a helper for writing Lambdas that integrate with Datadog's `datadog-ruby` gem. - -Creating a Lambda handler that wraps the Datadog tooling is easy: - -```ruby -require 'aws-sdk-someservice' -require 'yake/datadog' - -# Configure Datadog to use AWS tracing -Datadog::Lambda.configure_apm { |config| config.use :aws } - -datadog :handler do |event| - # … -end -``` - -## Deployment - -After writing your Lambda handler code you can deploy it to AWS using any number of tools. I recommend the following tools: - -- [Terraform](https://www.terraform.io) — my personal favorite Infrastructure-as-Code tool -- [AWS SAM](https://aws.amazon.com/serverless/sam/) — a great alternative with less configuration than Terraform -- [Serverless](https://www.serverless.com) — Supposedly the most popular option, though I have not used it - -## Development - -After checking out the repo, run `bundle` to install dependencies. Then, run `rake spec` to run the tests. - -## Contributing - -Bug reports and pull requests are welcome on GitHub at https://github.com/amancevice/yake. - -## License - -The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake.rb b/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake.rb deleted file mode 100644 index 13d0f93..0000000 --- a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake.rb +++ /dev/null @@ -1,6 +0,0 @@ -# frozen_string_literal: true - -require_relative "yake/version" -require_relative "yake/logger" -require_relative "yake/errors" -require_relative "yake/dsl" diff --git a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/api.rb b/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/api.rb deleted file mode 100644 index 0f9a4c5..0000000 --- a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/api.rb +++ /dev/null @@ -1,110 +0,0 @@ -# frozen_string_literal: true - -require 'base64' -require 'json' - -require 'yake' -require_relative 'errors' - -module Yake - module API - module DSL - ## - # Proxy handler for HTTP requests from Slack - def route(event, context = nil, &block) - # Extract route method - method = event['routeKey'] - raise Yake::Errors::UndeclaredRoute, method unless respond_to?(method) - - # Normalize headers - event['headers']&.transform_keys!(&:downcase) - - # Decode body if Base64-encoded - if event['isBase64Encoded'] - body = Base64.strict_decode64(event['body']) - event.update('body' => body, 'isBase64Encoded' => false) - end - - # Execute request - res = send(method, event, context) - block_given? ? yield(res) : res - end - - ## - # Transform to API Gateway response - def respond(status_code, body = nil, **headers) - # Log response - log = "RESPONSE [#{ status_code }] #{ body }" - Yake.logger&.send(status_code.to_i >= 400 ? :error : :info, log) - - # Set headers - content_length = (body&.bytesize || 0).to_s - to_s_downcase = -> (key) { key.to_s.downcase } - headers = { - 'content-length' => content_length, - **(@headers || {}), - **headers, - }.transform_keys(&to_s_downcase).compact - - # Send response - { statusCode: status_code, headers: headers, body: body }.compact - end - - ## - # Set default header - def header(headers) - (@headers ||= {}).update(headers) - end - - ## - # Define ANY route - def any(path, &block) - define_singleton_method("ANY #{ path }") { |*args| instance_exec(*args, &block) } - end - - ## - # Define DELETE route - def delete(path, &block) - define_singleton_method("DELETE #{ path }") { |*args| instance_exec(*args, &block) } - end - - ## - # Define GET route - def get(path, &block) - define_singleton_method("GET #{ path }") { |*args| instance_exec(*args, &block) } - end - - ## - # Define HEAD route - def head(path, &block) - define_singleton_method("HEAD #{ path }") { |*args| instance_exec(*args, &block) } - end - - ## - # Define OPTIONS route - def options(path, &block) - define_singleton_method("OPTIONS #{ path }") { |*args| instance_exec(*args, &block) } - end - - ## - # Define PATCH route - def patch(path, &block) - define_singleton_method("PATCH #{ path }") { |*args| instance_exec(*args, &block) } - end - - ## - # Define POST route - def post(path, &block) - define_singleton_method("POST #{ path }") { |*args| instance_exec(*args, &block) } - end - - ## - # Define PUT route - def put(path, &block) - define_singleton_method("PUT #{ path }") { |*args| instance_exec(*args, &block) } - end - end - end -end - -extend Yake::API::DSL diff --git a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/datadog.rb b/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/datadog.rb deleted file mode 100644 index d82a562..0000000 --- a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/datadog.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true - -require 'datadog/lambda' -require 'yake' - -module Yake - module Datadog - class MockContext < Struct.new( - :clock_diff, - :deadline_ms, - :aws_request_id, - :invoked_function_arn, - :log_group_name, - :log_stream_name, - :function_name, - :memory_limit_in_mb, - :function_version) - - def invoked_function_arn - @invoked_function_arn ||= begin - region = ENV['AWS_REGION'] || ENV['AWS_DEFAULT_REGION'] || 'us-east-1' - "arn:aws:lambda:#{region}:123456789012:function-name" - end - end - end - - module DSL - include Yake::DSL - - ## - # Datadog handler wrapper - def datadog(name, &block) - define_method(name) do |event:nil, context:nil| - context ||= MockContext.new - ::Datadog::Lambda.wrap(event, context) do - Yake.wrap(event, context, &block) - end - end - end - end - end -end - -extend Yake::Datadog::DSL diff --git a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/dsl.rb b/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/dsl.rb deleted file mode 100644 index cef6a2e..0000000 --- a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/dsl.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -require 'json' - -require_relative 'logger' - -module Yake - module DSL - ## - # Lambda handler task wrapper - def handler(name, &block) - define_method(name) do |event:nil, context:nil| - Yake.wrap(event, context, &block) - end - end - - ## - # Helper to get logger - def logger - Yake.logger - end - - ## - # Turn logging on/off - def logging(switch = :on, logger = nil, pretty: true) - Yake.pretty = pretty - if switch == :on - Yake.logger = logger - elsif switch == :off - Yake.logger = ::Logger.new(nil) - else - raise Errors::UnknownLoggingSetting, switch - end - end - end -end - -extend Yake::DSL diff --git a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/errors.rb b/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/errors.rb deleted file mode 100644 index 0cd9c4f..0000000 --- a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/errors.rb +++ /dev/null @@ -1,63 +0,0 @@ -# frozen_string_literal: true - -module Yake - module Errors - class Error < StandardError; end - - class UndeclaredRoute < Error - def initialize(method = nil) - super("No route declared for '#{ method }'") - end - end - - class UnknownLoggingSetting < Error - def initialize(method = nil) - super("Unknown logging setting '#{ method }'. Use :on or :off") - end - end - - # HTTP Errors - - class BadRequest < Error; end # HTTP 400 - class Unauthorized < Error; end # HTTP 401 - class PaymentRequired < Error; end # HTTP 402 - class Forbidden < Error; end # HTTP 403 - class NotFound < Error; end # HTTP 404 - class MethodNotAllowed < Error; end # HTTP 405 - class NotAcceptable < Error; end # HTTP 406 - class ProxyAuthenticationRequired < Error; end # HTTP 407 - class RequestTimeout < Error; end # HTTP 408 - class Conflict < Error; end # HTTP 409 - class Gone < Error; end # HTTP 410 - class LengthRequired < Error; end # HTTP 410 - class PreconditionFailed < Error; end # HTTP 412 - class PayloadTooLarge < Error; end # HTTP 413 - class UriTooLong < Error; end # HTTP 414 - class UnsupportedMediaType < Error; end # HTTP 415 - class RangeNotSatisfiable < Error; end # HTTP 416 - class ExpectationFailed < Error; end # HTTP 417 - class ImATeapot < Error; end # HTTP 418 - class EnhanceYourCalm < Error; end # HTTP 420 - class MisdirectedRequest < Error; end # HTTP 421 - class UnprocessableEntity < Error; end # HTTP 422 - class Locked < Error; end # HTTP 423 - class FailedDependency < Error; end # HTTP 424 - class TooEarly < Error; end # HTTP 425 - class UpgradeRequired < Error; end # HTTP 426 - class PreconditionRequired < Error; end # HTTP 428 - class TooManyRequests < Error; end # HTTP 429 - class RequestHeaderFieldsTooLarge < Error; end # HTTP 431 - class UnavailableForLegalReasons < Error; end # HTTP 451 - class InternalServerError < Error; end # HTTP 500 - class NotImplemented < Error; end # HTTP 501 - class BadGateway < Error; end # HTTP 502 - class ServiceUnavailable < Error; end # HTTP 503 - class GatewayTimeout < Error; end # HTTP 504 - class HttpVersionNotSupported < Error; end # HTTP 505 - class VariantAlsoNegotiates < Error; end # HTTP 506 - class InsufficientStorage < Error; end # HTTP 507 - class LoopDetected < Error; end # HTTP 508 - class NotExtended < Error; end # HTTP 510 - class NetworkAuthenticationRequired < Error; end # HTTP 507 - end -end diff --git a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/logger.rb b/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/logger.rb deleted file mode 100644 index 14590db..0000000 --- a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/logger.rb +++ /dev/null @@ -1,52 +0,0 @@ -# frozen_string_literal: true - -require 'json' -require 'logger' - -module Yake - module Logger - attr_writer :logger - - def logger - @logger ||= Yake.logger - end - - class << self - def new(logdev = $stdout, **params) - ::Logger.new(logdev, formatter: Formatter.new, **params) - end - end - - class Formatter < ::Logger::Formatter - Format = "%s %s %s\n" - - def call(severity, time, progname, msg) - Format % [ severity, progname.nil? ? '-' : "RequestId: #{ progname }", msg2str(msg).strip ] - end - end - end - - class << self - attr_writer :logger, :pretty - - def logger - @logger ||= Logger.new - end - - def pretty? - @pretty != false - end - - def wrap(event = nil, context = nil, &block) - original_progname = logger.progname - logger.progname = context&.aws_request_id - jsonify = -> (obj) { pretty? ? JSON.pretty_generate(obj) : obj.to_json } - logger.info("EVENT #{ jsonify === event }") - yield(event, context).tap do |res| - logger.info("RETURN #{ jsonify === res }") - end - ensure - logger.progname = original_progname - end - end -end diff --git a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/version.rb b/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/version.rb deleted file mode 100644 index 9162bc8..0000000 --- a/lib/vendor/bundle/ruby/2.7.0/gems/yake-0.4.2/lib/yake/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -# frozen_string_literal: true - -module Yake - VERSION = '0.4.2' -end diff --git a/lib/vendor/bundle/ruby/2.7.0/specifications/yake-0.4.2.gemspec b/lib/vendor/bundle/ruby/2.7.0/specifications/yake-0.4.2.gemspec deleted file mode 100644 index 4b7fed7..0000000 --- a/lib/vendor/bundle/ruby/2.7.0/specifications/yake-0.4.2.gemspec +++ /dev/null @@ -1,20 +0,0 @@ -# -*- encoding: utf-8 -*- -# stub: yake 0.4.2 ruby lib - -Gem::Specification.new do |s| - s.name = "yake".freeze - s.version = "0.4.2" - - s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= - s.require_paths = ["lib".freeze] - s.authors = ["Alexander Mancevice".freeze] - s.date = "2021-11-04" - s.email = ["alexander.mancevice@hey.com".freeze] - s.homepage = "https://github.com/amancevice/yake".freeze - s.licenses = ["MIT".freeze] - s.required_ruby_version = Gem::Requirement.new(">= 2.2.0".freeze) - s.rubygems_version = "3.1.6".freeze - s.summary = "Rake-like DSL for declaring AWS Lambda function handlers".freeze - - s.installed_by_version = "3.1.6" if s.respond_to? :installed_by_version -end diff --git a/spec/http_spec.rb b/spec/http_spec.rb deleted file mode 100644 index 5e3ea04..0000000 --- a/spec/http_spec.rb +++ /dev/null @@ -1,39 +0,0 @@ -RSpec.describe :http do - let :url do 'https://example.com/' end - let :json do { fizz: 'buzz' }.to_json end - let :form do { fizz: 'buzz' }.to_form end - let :body do { ok:true }.to_json end - let :headers do { 'content-length' => body.length.to_s, 'content-type' => 'application/json' } end - - before { require_relative '../lib/http' } - - context :get do - let :event do { url: url } end - let :res do OpenStruct.new code: '200', body: body, each_header: headers.each end - - it 'should execute a GET request' do - expect_any_instance_of(Net::HTTP).to receive(:request).with(an_instance_of(Net::HTTP::Get), nil).and_return(res) - expect(get event: event).to eq statusCode: '200', body: body, headers: headers - end - end - - context :head do - let :event do { url: url } end - let :res do OpenStruct.new code: '200', body: nil, each_header: headers.each end - - it 'should execute a HEAD request' do - expect_any_instance_of(Net::HTTP).to receive(:request).with(an_instance_of(Net::HTTP::Head), nil).and_return(res) - expect(head event: event).to eq statusCode: '200', body: nil, headers: headers - end - end - - context :post do - let :event do { url: url, body: body } end - let :res do OpenStruct.new code: '200', body: body, each_header: headers.each end - - it 'should execute a POST request' do - expect_any_instance_of(Net::HTTP).to receive(:request).with(an_instance_of(Net::HTTP::Post), {ok:true}.to_json).and_return(res) - expect(post event: event).to eq statusCode: '200', body: body, headers: headers - end - end -end diff --git a/spec/reddit_spec.rb b/spec/reddit_spec.rb deleted file mode 100644 index d2e94c6..0000000 --- a/spec/reddit_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -RSpec.describe :reddit do - before { require_relative '../lib/reddit.rb' } - - context 'dequeue' do - let :post do - Reddit::Post.new do |post| - post.created_utc = 1234567890 - post.name = 't3_abcdefg' - post.permalink = '' - post.title = '' - end - end - - let :res do - OpenStruct.new read: { data: { - children: 3.times.map do { data: post.to_h } end - } }.to_json - end - - it 'should dequeue the next post' do - expect(URI).to receive(:open).and_yield res - expect(R_BRUTALISM.table).to receive(:get_item).and_return OpenStruct.new - expect(dequeue).to eq( - QueueSize: 2, - NextPost: { - CREATED_UTC: '2009-02-13T23:31:30Z', - DATA: post.to_h, - NAME: 't3_abcdefg', - PERMALINK: '<path>', - TITLE: '<title>', - TTL: 1234567890 + TTL - } - ) - end - end -end diff --git a/spec/slack_spec.rb b/spec/slack_spec.rb deleted file mode 100644 index 5bb17f1..0000000 --- a/spec/slack_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -RSpec.describe :slack do - before { require_relative '../lib/slack.rb' } - - context 'transform' do - let :event do - { - name: 't3_abcdefg', - permalink: '<path>', - title: '<title>', - } - end - - let :exp do - { - text: '<title>', - blocks: [ - { - type: 'context', - elements: [ - { - text: '<https://www.reddit.com<path>|<title>>', - type: 'mrkdwn' - } - ] - } - ] - } - end - - it 'should transform a post' do - expect(transform event:event).to eq exp - end - end -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb deleted file mode 100644 index 7bc40eb..0000000 --- a/spec/spec_helper.rb +++ /dev/null @@ -1,24 +0,0 @@ -require 'bundler/setup' -Bundler.require - -SimpleCov.start - -Aws.config = { - cloudwatch: { stub_responses: true }, - dynamodb: { stub_responses: true }, - secretsmanager: { stub_responses: true }, -} - -logging :off - -RSpec.configure do |config| - # Enable flags like --only-failures and --next-failure - config.example_status_persistence_file_path = '.rspec_status' - - # Disable RSpec exposing methods globally on `Module` and `main` - config.disable_monkey_patching! - - config.expect_with :rspec do |c| - c.syntax = :expect - end -end diff --git a/spec/tweet_spec.rb b/spec/tweet_spec.rb deleted file mode 100644 index cf51789..0000000 --- a/spec/tweet_spec.rb +++ /dev/null @@ -1,69 +0,0 @@ -RSpec.describe :tweet do - before { require_relative '../lib/tweet' } - - context 'transform' do - let :preview do - { - name: 't3_abcdefg', - permalink: '/<path>', - title: '<title>', - preview: { - images: [ - { - resolutions: [], - source: { - url: 'https://preview.redd.it/', - width: 1024, - height: 1024, - } - } - ] - } - } - end - - let :gallery do - { - name: 't3_abcdefg', - permalink: '/<path>', - title: '<title>', - is_gallery: true, - media_metadata: { - '<image-id>': { - id: '<image-id>', - status: 'valid', - e: "Image", - m: "image/jpg", - s: { y:1024, x:1024, u:'https://preview.redd.it/' }, - p: [], - } - } - } - end - - let :exp do - { - count: 1, - updates: [ - { - status: "<title>\nhttps://www.reddit.com/<path>", - media: [ "https://preview.redd.it/" ] - } - ] - } - end - - it 'should transform a preview post' do - expect(transform event:preview).to eq exp - end - - it 'should transform a gallery post' do - expect(transform event:gallery).to eq exp - end - end - - context 'post' do - it 'should post a tweet' do - end - end -end diff --git a/tasks/db.rake b/tasks/db.rake deleted file mode 100644 index 45f88f2..0000000 --- a/tasks/db.rake +++ /dev/null @@ -1,22 +0,0 @@ -namespace :db do - namespace :list do - namespace :slack do - desc 'List SLACK/AUTH items' - task :auths do - sh <<~SH - echo - aws dynamodb query \ - --table-name Brutalismbot \ - --index-name Chrono \ - --key-condition-expression '#SORT = :SORT' \ - --expression-attribute-names '{"#SORT":"SORT"}' \ - --expression-attribute-values '{":SORT":{"S":"SLACK/AUTH"}}' \ - | jq -r '.Items[] | .TEAM_NAME.S+"|"+.CHANNEL_NAME.S+"|"+.GUID.S' \ - | sort \ - | column -t -s '|' - echo - SH - end - end - end -end diff --git a/tasks/logs.rake b/tasks/logs.rake deleted file mode 100644 index 7e9df3e..0000000 --- a/tasks/logs.rake +++ /dev/null @@ -1,17 +0,0 @@ -def tail(function_name, args) - sh %{aws logs tail /aws/lambda/#{ function_name } --follow --since #{ args[:'15m'] || '15m' }} -end - -namespace :logs do - desc 'Tail HTTP API Lambda logs' - task :'http-api', [:'15m'] do |t,args| - tail 'brutalismbot-slack-api-proxy', args - end - - namespace :'http-api' do - desc 'Tail HTTP API Lambda logs [beta]' - task :beta, [:'15m'] do |t,args| - tail 'brutalismbot-slack-beta-api-proxy', args - end - end -end diff --git a/tasks/states.rake b/tasks/states.rake deleted file mode 100644 index 8d8d113..0000000 --- a/tasks/states.rake +++ /dev/null @@ -1,22 +0,0 @@ -namespace :states do - desc 'Start dequeue state machine' - task :dequeue do - sh <<~SH - terraform output -raw state_machine_reddit_dequeue_arn \ - | xargs aws stepfunctions start-execution --input '{}' --state-machine-arn \ - | jq - SH - end - - namespace :dequeue do - desc 'Start dequeue state machine and open web console' - task :open do - sh <<~SH - terraform output -raw state_machine_reddit_dequeue_arn \ - | xargs aws stepfunctions start-execution --input '{}' --state-machine-arn \ - | jq -r '"https://console.aws.amazon.com/states/home?#/executions/details/" + .executionArn' \ - | xargs open - SH - end - end -end diff --git a/tasks/terraform.rake b/tasks/terraform.rake deleted file mode 100644 index 6373831..0000000 --- a/tasks/terraform.rake +++ /dev/null @@ -1,47 +0,0 @@ -require 'dotenv/load' -require 'rake/clean' - -CLOBBER.include '.terraform', 'pkg' - -namespace :terraform do - desc 'Run terraform plan' - task :plan => :init do - sh 'terraform plan -detailed-exitcode' - end - - desc 'Run terraform apply' - task :apply => :init do - sh 'terraform apply' - end - - namespace :apply do - desc 'Run terraform auto -auto-approve' - task :auto => :init do - sh 'terraform apply -auto-approve' - end - end - - desc 'Run terraform init' - task :init => %i[vendor .terraform] - - namespace :'state-machines' do - desc 'Taint State Machines' - task :reset do - sh <<~SH - terraform state list | grep aws_sfn_state_machine | xargs -n1 terraform taint - terraform state list | grep aws_sfn_state_machine | tac | xargs -n1 terraform apply -auto-approve -target - SH - end - end - - namespace :state do - desc 'List state' - task :list do - sh %{terraform state list} - end - end - - directory '.terraform' do - sh 'terraform init' - end -end diff --git a/tasks/vendor.rake b/tasks/vendor.rake deleted file mode 100644 index e10cacc..0000000 --- a/tasks/vendor.rake +++ /dev/null @@ -1,13 +0,0 @@ -require 'rake/clean' - -CLEAN.include 'lib/vendor' - -desc 'Vendor dependencies' -task :vendor => 'lib/vendor' - -directory 'lib/vendor' => 'lib/Gemfile' do - cd 'lib' do - rm_rf 'vendor' - sh 'bundle' - end -end diff --git a/terraform.tf b/terraform.tf deleted file mode 100644 index ed6130f..0000000 --- a/terraform.tf +++ /dev/null @@ -1,1880 +0,0 @@ -terraform { - required_version = "~> 1.0" - - backend "s3" { - bucket = "brutalismbot" - key = "terraform/brutalismbot.tfstate" - region = "us-east-1" - } - - required_providers { - archive = { - source = "hashicorp/archive" - version = "~> 2.2" - } - - aws = { - source = "hashicorp/aws" - version = "~> 3.38" - } - } -} - -provider "aws" { - region = "us-east-1" - - default_tags { - tags = { - App = "Brutalismbot" - Name = "Brutalismbot" - Repo = "https://github.com/brutalismbot/brutalismbot" - } - } -} - -locals { - is_enabled = true - lag_hours = "8" - ttl_days = "14" - - tags = { - App = "core" - Name = "brutalismbot" - Repo = "https://github.com/brutalismbot/brutalismbot" - } -} - -# S3 - -resource "aws_s3_bucket" "brutalismbot" { - acl = "private" - bucket = "brutalismbot" - force_destroy = false - - versioning { - enabled = true - } -} - -resource "aws_s3_bucket_public_access_block" "brutalismbot" { - bucket = aws_s3_bucket.brutalismbot.id - block_public_acls = true - block_public_policy = true - ignore_public_acls = true - restrict_public_buckets = true -} - -# DYNAMODB - -resource "aws_dynamodb_table" "brutalismbot" { - billing_mode = "PAY_PER_REQUEST" - hash_key = "GUID" - name = "Brutalismbot" - range_key = "SORT" - read_capacity = 0 - write_capacity = 0 - - attribute { - name = "GUID" - type = "S" - } - - attribute { - name = "SORT" - type = "S" - } - - attribute { - name = "NAME" - type = "S" - } - - attribute { - name = "CREATED_UTC" - type = "S" - } - - attribute { - name = "TEAM_ID" - type = "S" - } - - ttl { - attribute_name = "TTL" - enabled = true - } - - global_secondary_index { - name = "Chrono" - hash_key = "SORT" - range_key = "CREATED_UTC" - projection_type = "ALL" - read_capacity = 0 - write_capacity = 0 - } - - global_secondary_index { - name = "RedditName" - hash_key = "NAME" - range_key = "GUID" - projection_type = "ALL" - read_capacity = 0 - write_capacity = 0 - } - - global_secondary_index { - name = "SlackTeam" - hash_key = "TEAM_ID" - projection_type = "ALL" - read_capacity = 0 - write_capacity = 0 - } -} - -# EVENTBRIDGE - -resource "aws_cloudwatch_event_bus" "brutalismbot" { - name = "brutalismbot" -} - -# EVENTBRIDGE :: REDDIT DEQUEUE - -resource "aws_cloudwatch_event_rule" "reddit_dequeue" { - description = "Dequeue next post from /r/brutalism" - event_bus_name = "default" - is_enabled = true - name = "brutalismbot-reddit-dequeue" - schedule_expression = "rate(1 hour)" -} - -resource "aws_cloudwatch_event_target" "reddit_dequeue" { - arn = aws_sfn_state_machine.reddit_dequeue.id - input = jsonencode({}) - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.reddit_dequeue.name -} - -# EVENTBRIDGE :: REDDIT POST - -resource "aws_cloudwatch_event_rule" "reddit_post" { - description = "Handle new posts from Reddit" - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - is_enabled = local.is_enabled - name = "reddit-post" - - event_pattern = jsonencode({ - source = ["reddit"] - detail-type = ["post"] - }) -} - -resource "aws_cloudwatch_event_target" "reddit_post" { - arn = aws_sfn_state_machine.reddit_post.id - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - input_path = "$.detail" - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.reddit_post.name -} - -# EVENTBRIDGE :: SLACK POST - -resource "aws_cloudwatch_event_rule" "reddit_post_slack" { - description = "Handle new posts from Reddit for Slack" - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - is_enabled = local.is_enabled - name = "reddit-post-slack" - - event_pattern = jsonencode({ - source = ["reddit"] - detail-type = ["post/slack"] - }) -} - -resource "aws_cloudwatch_event_target" "reddit_post_slack" { - arn = aws_sfn_state_machine.slack_post.id - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - input_path = "$.detail" - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.reddit_post_slack.name -} - -# EVENTBRIDGE :: SLACK POST AUTH - -resource "aws_cloudwatch_event_rule" "reddit_post_slack_channel" { - description = "Handle new posts for a Slack workspace" - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - is_enabled = local.is_enabled - name = "reddit-post-slack-channel" - - event_pattern = jsonencode({ - source = ["reddit"] - detail-type = ["post/slack/channel"] - }) -} - -resource "aws_cloudwatch_event_target" "reddit_post_slack_channel" { - arn = aws_sfn_state_machine.slack_post_channel.id - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - input_path = "$.detail" - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.reddit_post_slack_channel.name -} - -# EVENTBRIDGE :: SLACK BETA APP HOME OPENED - -resource "aws_cloudwatch_event_rule" "slack_beta_app_home_opened" { - description = "Slack Beta app home opened events" - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - is_enabled = true - name = "slack-beta-app-home-opened" - - event_pattern = jsonencode({ - source = ["slack/beta"] - detail-type = ["event"] - detail = { event = { type = ["app_home_opened"] } } - }) -} - -resource "aws_cloudwatch_event_target" "slack_beta_app_home_opened" { - arn = aws_sfn_state_machine.slack_beta_app_home_opened.id - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - input_path = "$.detail" - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.slack_beta_app_home_opened.name -} - -# EVENTBRIDGE :: SLACK BETA ENABLE/DISABLE - -resource "aws_cloudwatch_event_rule" "slack_beta_enable_disable" { - description = "Slack Beta app home opened events" - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - is_enabled = true - name = "slack-beta-enable-disable" - - event_pattern = jsonencode({ - source = ["slack/beta"] - detail-type = ["callback"] - detail = { view = { callback_id = ["enable_disable"] } } - }) -} - -resource "aws_cloudwatch_event_target" "slack_beta_enable_disable" { - arn = aws_sfn_state_machine.slack_beta_enable_disable.id - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - input_path = "$.detail" - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.slack_beta_enable_disable.name -} - -# EVENTBRIDGE :: SLACK INSTALL - -resource "aws_cloudwatch_event_rule" "slack_install" { - description = "Slack install events" - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - is_enabled = true - name = "slack-install" - - event_pattern = jsonencode({ - source = ["slack", "slack/beta"] - detail-type = ["oauth"] - }) -} - -resource "aws_cloudwatch_event_target" "slack_install" { - arn = aws_sfn_state_machine.slack_install.id - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - input_path = "$.detail" - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.slack_install.name -} - -# EVENTBRIDGE :: SLACK UNINSTALL - -resource "aws_cloudwatch_event_rule" "slack_uninstall" { - description = "Slack uninstall events" - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - is_enabled = true - name = "slack-uninstall" - - event_pattern = jsonencode({ - source = ["slack", "slack/beta"] - detail-type = ["event"] - detail = { event = { type = ["app_uninstalled"] } } - }) -} - -resource "aws_cloudwatch_event_target" "slack_uninstall" { - arn = aws_sfn_state_machine.slack_uninstall.id - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - input_path = "$.detail" - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.slack_uninstall.name -} - -# EVENTBRIDGE :: TWITTER - -resource "aws_cloudwatch_event_rule" "reddit_post_twitter" { - description = "Handle new posts from Reddit for Twitter" - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - is_enabled = local.is_enabled - name = "reddit-post-twitter" - - event_pattern = jsonencode({ - source = ["reddit"] - detail-type = ["post/twitter"] - }) -} - -resource "aws_cloudwatch_event_target" "reddit_post_twitter" { - arn = aws_sfn_state_machine.twitter_post.id - event_bus_name = aws_cloudwatch_event_bus.brutalismbot.name - input_path = "$.detail" - role_arn = aws_iam_role.events.arn - rule = aws_cloudwatch_event_rule.reddit_post_twitter.name -} - - -# IAM :: EVENTS - -data "aws_iam_policy_document" "trust_events" { - statement { - sid = "AssumeEvents" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["events.amazonaws.com"] - } - } -} - -data "aws_iam_policy_document" "access_events" { - statement { - sid = "StatesStartExecution" - actions = ["states:StartExecution"] - - resources = [ - aws_sfn_state_machine.reddit_dequeue.arn, - aws_sfn_state_machine.reddit_post.arn, - aws_sfn_state_machine.slack_beta_app_home_opened.arn, - aws_sfn_state_machine.slack_beta_enable_disable.arn, - aws_sfn_state_machine.slack_install.arn, - aws_sfn_state_machine.slack_post.arn, - aws_sfn_state_machine.slack_post_channel.arn, - aws_sfn_state_machine.slack_uninstall.arn, - aws_sfn_state_machine.twitter_post.arn, - ] - } -} - -resource "aws_iam_role" "events" { - assume_role_policy = data.aws_iam_policy_document.trust_events.json - name = "brutalismbot-events" -} - -resource "aws_iam_role_policy" "events" { - name = "access" - policy = data.aws_iam_policy_document.access_events.json - role = aws_iam_role.events.name -} - -# IAM :: LAMBDA - -data "aws_kms_alias" "brutalismbot" { name = "alias/brutalismbot" } - -data "aws_secretsmanager_secret" "twitter" { name = "brutalismbot/twitter" } - -data "aws_iam_policy_document" "trust_lambda" { - statement { - sid = "AssumeEvents" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["lambda.amazonaws.com"] - } - } -} - -data "aws_iam_policy_document" "access_lambda" { - statement { - sid = "DynamoDB" - actions = ["dynamodb:*"] - resources = ["${aws_dynamodb_table.brutalismbot.arn}*"] - } - - statement { - sid = "Logs" - actions = ["logs:*"] - resources = ["*"] - } - - statement { - sid = "Secrets" - - actions = [ - "kms:Decrypt", - "secretsmanager:GetSecretValue", - ] - - resources = [ - data.aws_kms_alias.brutalismbot.target_key_arn, - data.aws_secretsmanager_secret.twitter.arn, - ] - } - - statement { - sid = "StatesSendTask" - actions = ["states:SendTask*"] - resources = ["*"] - } -} - -resource "aws_iam_role" "lambda" { - assume_role_policy = data.aws_iam_policy_document.trust_lambda.json - name = "brutalismbot-lambda" -} - -resource "aws_iam_role_policy" "lambda" { - name = "access" - policy = data.aws_iam_policy_document.access_lambda.json - role = aws_iam_role.lambda.name -} - -# IAM :: STATES - -data "aws_iam_policy_document" "trust_states" { - statement { - sid = "AssumeEvents" - actions = ["sts:AssumeRole"] - - principals { - type = "Service" - identifiers = ["states.amazonaws.com"] - } - } -} - -data "aws_iam_policy_document" "access_states" { - statement { - sid = "CloudWatch" - actions = ["cloudwatch:PutMetricData"] - resources = ["*"] - } - - statement { - sid = "DynamoDB" - actions = ["dynamodb:*"] - resources = ["${aws_dynamodb_table.brutalismbot.arn}*"] - } - - statement { - sid = "EventBridge" - resources = [aws_cloudwatch_event_bus.brutalismbot.arn] - actions = ["events:PutEvents"] - } - - statement { - sid = "EventBridgeToggle" - resources = [aws_cloudwatch_event_rule.reddit_dequeue.arn] - - actions = [ - "events:DescribeRule", - "events:DisableRule", - "events:EnableRule", - ] - } - - statement { - sid = "Lambda" - actions = ["lambda:InvokeFunction"] - - resources = [ - aws_lambda_function.http_get.arn, - aws_lambda_function.http_head.arn, - aws_lambda_function.http_post.arn, - aws_lambda_function.reddit_dequeue.arn, - aws_lambda_function.slack_transform.arn, - aws_lambda_function.twitter_post.arn, - aws_lambda_function.twitter_transform.arn, - ] - } - - statement { - sid = "StepFunctions" - actions = ["states:StartExecution"] - - resources = [ - aws_sfn_state_machine.slack_uninstall.arn, - aws_sfn_state_machine.slack_post.arn, - ] - } -} - -resource "aws_iam_role" "states" { - assume_role_policy = data.aws_iam_policy_document.trust_states.json - name = "brutalismbot-states" -} - -resource "aws_iam_role_policy" "states" { - name = "access" - policy = data.aws_iam_policy_document.access_states.json - role = aws_iam_role.states.name -} - -# LAMBDA FUNCTIONS - -data "archive_file" "package" { - output_file_mode = "0666" - output_path = "${path.module}/pkg/package.zip" - source_dir = "${path.module}/lib" - type = "zip" -} - -# LAMBDA FUNCTIONS :: HTTP :: GET - -resource "aws_lambda_function" "http_get" { - architectures = ["arm64"] - description = "Do HTTP GET" - filename = data.archive_file.package.output_path - function_name = "brutalismbot-http-get" - handler = "http.get" - memory_size = 512 - role = aws_iam_role.lambda.arn - runtime = "ruby2.7" - source_code_hash = data.archive_file.package.output_base64sha256 - timeout = 29 -} - -resource "aws_cloudwatch_log_group" "http_get" { - name = "/aws/lambda/${aws_lambda_function.http_get.function_name}" - retention_in_days = 14 -} - -# LAMBDA FUNCTIONS :: HTTP :: GET - -resource "aws_lambda_function" "http_head" { - architectures = ["arm64"] - description = "Do HTTP HEAD" - filename = data.archive_file.package.output_path - function_name = "brutalismbot-http-head" - handler = "http.head" - memory_size = 512 - role = aws_iam_role.lambda.arn - runtime = "ruby2.7" - source_code_hash = data.archive_file.package.output_base64sha256 - timeout = 29 -} - -resource "aws_cloudwatch_log_group" "http_head" { - name = "/aws/lambda/${aws_lambda_function.http_head.function_name}" - retention_in_days = 14 -} - -# LAMBDA FUNCTIONS :: HTTP :: POST - -resource "aws_lambda_function" "http_post" { - architectures = ["arm64"] - description = "Do HTTP POST" - filename = data.archive_file.package.output_path - function_name = "brutalismbot-http-post" - handler = "http.post" - memory_size = 512 - role = aws_iam_role.lambda.arn - runtime = "ruby2.7" - source_code_hash = data.archive_file.package.output_base64sha256 - timeout = 29 -} - -resource "aws_cloudwatch_log_group" "http_post" { - name = "/aws/lambda/${aws_lambda_function.http_post.function_name}" - retention_in_days = 14 -} - -# LAMBDA FUNCTIONS :: REDDIT :: DEQUEUE - -resource "aws_lambda_function" "reddit_dequeue" { - architectures = ["arm64"] - description = "Dequeue next post from /r/brutalism" - filename = data.archive_file.package.output_path - function_name = "brutalismbot-reddit-dequeue" - handler = "reddit.dequeue" - memory_size = 512 - role = aws_iam_role.lambda.arn - runtime = "ruby2.7" - source_code_hash = data.archive_file.package.output_base64sha256 - timeout = 10 - - environment { - variables = { - LAG_HOURS = local.lag_hours - TTL_DAYS = local.ttl_days - } - } -} - -resource "aws_cloudwatch_log_group" "reddit_dequeue" { - name = "/aws/lambda/${aws_lambda_function.reddit_dequeue.function_name}" - retention_in_days = 14 -} - -# LAMBDA FUNCTIONS :: SLACK :: TRANSFORM - -resource "aws_lambda_function" "slack_transform" { - architectures = ["arm64"] - description = "Transform Reddit post to Slack" - filename = data.archive_file.package.output_path - function_name = "brutalismbot-slack-transform" - handler = "slack.transform" - role = aws_iam_role.lambda.arn - runtime = "ruby2.7" - source_code_hash = data.archive_file.package.output_base64sha256 -} - -resource "aws_cloudwatch_log_group" "slack_transform" { - name = "/aws/lambda/${aws_lambda_function.slack_transform.function_name}" - retention_in_days = 14 -} - -# LAMBDA FUNCTIONS :: TWITTER - -data "archive_file" "twitter" { - output_file_mode = "0666" - output_path = "${path.module}/pkg/twitter.zip" - source_dir = "${path.module}/lib" - type = "zip" - - excludes = ["http.rb"] -} - -data "aws_lambda_layer_version" "twitter" { layer_name = "twitter-ruby2-7" } - -# LAMBDA FUNCTIONS :: TWITTER :: POST - -resource "aws_lambda_function" "twitter_post" { - architectures = ["x86_64"] - description = "Post to Twitter" - filename = data.archive_file.twitter.output_path - function_name = "brutalismbot-twitter-post" - handler = "tweet.post" - layers = [data.aws_lambda_layer_version.twitter.arn] - memory_size = 1024 - role = aws_iam_role.lambda.arn - runtime = "ruby2.7" - source_code_hash = data.archive_file.twitter.output_base64sha256 - timeout = 60 -} - -resource "aws_cloudwatch_log_group" "twitter_post" { - name = "/aws/lambda/${aws_lambda_function.twitter_post.function_name}" - retention_in_days = 14 -} - -# LAMBDA FUNCTIONS :: TWITTER :: TRANSFORM - -resource "aws_lambda_function" "twitter_transform" { - architectures = ["arm64"] - description = "Transform Reddit post to Twitter" - filename = data.archive_file.package.output_path - function_name = "brutalismbot-twitter-transform" - handler = "tweet.transform" - layers = [data.aws_lambda_layer_version.twitter.arn] - role = aws_iam_role.lambda.arn - runtime = "ruby2.7" - source_code_hash = data.archive_file.package.output_base64sha256 -} - -resource "aws_cloudwatch_log_group" "twitter_transform" { - name = "/aws/lambda/${aws_lambda_function.twitter_transform.function_name}" - retention_in_days = 14 -} - -# STATE MACHINES :: REDDIT - -resource "aws_sfn_state_machine" "reddit_dequeue" { - name = "brutalismbot-reddit-dequeue" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "DequeueNext" - States = { - DequeueNext = { - Type = "Task" - Resource = aws_lambda_function.reddit_dequeue.arn - Next = "PutEventsAndMetrics" - ResultSelector = { - CLOUDWATCH = { - Namespace = "Brutalismbot" - MetricData = [{ - MetricName = "QueueSize" - Unit = "Count" - "Value.$" = "$.QueueSize" - Dimensions = [{ - Name = "QueueName" - Value = "/r/brutalism" - }] - }] - } - EVENTBRIDGE = { - EventBusName = aws_cloudwatch_event_bus.brutalismbot.name - Source = "reddit" - DetailType = "post" - Detail = { - "AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$" = "$$.Execution.Id" - "POST.$" = "$.NextPost" - } - } - } - Retry = [{ - BackoffRate = 2 - IntervalSeconds = 60 - MaxAttempts = 3 - ErrorEquals = [ - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.ServiceException", - "Lambda.Unknown", - ] - }] - } - PutEventsAndMetrics = { - Type = "Parallel" - End = true - OutputPath = "$[0]" - Branches = [ - { - StartAt = "NextPost?" - States = { - "NextPost?" = { - Type = "Choice" - Default = "Finish" - InputPath = "$.EVENTBRIDGE" - Choices = [{ - Next = "PutEvent" - Variable = "$.Detail.POST" - IsNull = false - }] - } - PutEvent = { - Type = "Task" - Resource = "arn:aws:states:::events:putEvents" - End = true - Parameters = { "Entries.$" = "States.Array($)" } - } - Finish = { Type = "Succeed" } - } - }, - { - StartAt = "SendMetrics" - States = { - SendMetrics = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:cloudwatch:putMetricData" - End = true - InputPath = "$.CLOUDWATCH" - Parameters = { - "Namespace.$" = "$.Namespace" - "MetricData.$" = "$.MetricData" - }, - "Retry" : [{ - BackoffRate = 2 - ErrorEquals = ["CloudWatch.SdkClientException"] - IntervalSeconds = 60 - MaxAttempts = 3 - }] - } - } - } - ] - } - } - }) -} - -resource "aws_sfn_state_machine" "reddit_post" { - name = "brutalismbot-reddit-post" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "Parallelize" - States = { - Parallelize = { - Type = "Parallel" - Next = "GetEvents" - ResultSelector = { - "MAX_CREATED_UTC.$" = "$[0]" - "POST.$" = "$[1]" - } - Branches = [ - { - StartAt = "GetMaxCreatedUTC" - States = { - GetMaxCreatedUTC = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:getItem" - End = true - OutputPath = "$.Item.CREATED_UTC.S" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - ProjectionExpression = "CREATED_UTC" - Key = { - GUID = { S = "STATS/MAX" } - SORT = { S = "REDDIT/POST" } - } - } - } - } - }, - { - StartAt = "PutItem" - States = { - PutItem = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:putItem" - End = true - InputPath = "$.POST" - ResultPath = "$.DYNAMODB" - OutputPath = "$.POST" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - Item = { - SORT = { S = "REDDIT/POST" } - GUID = { "S.$" = "$.NAME" } - CREATED_UTC = { "S.$" = "$.CREATED_UTC" } - JSON = { "S.$" = "$.DATA" } - NAME = { "S.$" = "$.NAME" } - PERMALINK = { "S.$" = "$.PERMALINK" } - TITLE = { "S.$" = "$.TITLE" } - TTL = { "N.$" = "States.JsonToString($.TTL)" } - } - } - } - } - } - ] - } - GetEvents = { - Type = "Parallel" - Next = "PutEvents" - ResultSelector = { - Entries = [ - { - EventBusName = aws_cloudwatch_event_bus.brutalismbot.name - Source = "reddit" - DetailType = "post/slack" - Detail = { - "AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$" = "$$.Execution.Id" - "POST.$" = "$[0].POST" - } - }, - { - EventBusName = aws_cloudwatch_event_bus.brutalismbot.name - Source = "reddit" - DetailType = "post/twitter" - Detail = { - "AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$" = "$$.Execution.Id" - "POST.$" = "$[1].POST" - } - } - ] - } - Branches = [ - { - StartAt = "GetSlack" - States = { - GetSlack = { - Type = "Task" - Resource = aws_lambda_function.slack_transform.arn - End = true - InputPath = "$.POST.DATA" - ResultPath = "$.POST.DATA" - Retry = [{ - BackoffRate = 2 - IntervalSeconds = 3 - MaxAttempts = 4 - ErrorEquals = [ - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.ServiceException", - ] - }] - } - } - }, - { - StartAt = "GetTwitter" - States = { - GetTwitter = { - Type = "Task" - Resource = aws_lambda_function.twitter_transform.arn - End = true - InputPath = "$.POST.DATA" - ResultPath = "$.POST.DATA" - Retry = [{ - BackoffRate = 2 - IntervalSeconds = 3 - MaxAttempts = 4 - ErrorEquals = [ - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.ServiceException", - ] - }] - } - } - }, - { - StartAt = "NewMaxCreatedUTC?" - States = { - "NewMaxCreatedUTC?" = { - Type = "Choice" - Default = "Finish" - Choices = [{ - Next = "UpdateMaxCreatedUTC" - Variable = "$.MAX_CREATED_UTC" - StringLessThanPath = "$.POST.CREATED_UTC" - }] - } - Finish = { Type = "Succeed" } - UpdateMaxCreatedUTC = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:updateItem" - End = true - InputPath = "$.POST" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - UpdateExpression = "SET CREATED_UTC = :CREATED_UTC, #NAME = :NAME" - ExpressionAttributeNames = { "#NAME" = "NAME" } - ExpressionAttributeValues = { - ":CREATED_UTC" = { "S.$" = "$.CREATED_UTC" } - ":NAME" = { "S.$" = "$.NAME" } - } - Key = { - GUID = { S = "STATS/MAX" } - SORT = { S = "REDDIT/POST" } - } - } - } - } - } - ] - } - PutEvents = { - Type = "Task" - Resource = "arn:aws:states:::events:putEvents" - End = true - Parameters = { "Entries.$" = "$.Entries" } - } - } - }) -} - -# STATE MACHINES :: SLACK - -resource "aws_sfn_state_machine" "slack_beta_app_home_opened" { - name = "brutalismbot-slack-beta-app-home-opened" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "GetView" - States = { - GetView = { - Type = "Parallel" - Next = "EncodeView" - ResultSelector = { - url = "https://slack.com/api/views.publish" - headers = { - "authorization.$" = "States.Format('Bearer {}', $[1].Items[0].ACCESS_TOKEN.S)" - "content-type" = "application/json; charset=utf8" - } - body = { - "user_id.$" = "$[1].Items[0].USER_ID.S" - view = { - callback_id = "enable_disable" - type = "home" - title = { - type = "plain_text" - text = "Brutalismbot Beta" - emoji = true - } - blocks = [{ - type = "actions" - "elements.$" = "States.Array($[0])" - }] - } - } - } - Branches = [ - { - StartAt = "GetState" - States = { - GetState = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:eventbridge:describeRule" - Next = "Enabled?" - Parameters = { Name = aws_cloudwatch_event_rule.reddit_dequeue.name } - } - "Enabled?" = { - Type = "Choice" - Default = "GetDisableButton" - Choices = [{ - Next = "GetEnableButton" - Variable = "$.State" - StringEquals = "DISABLED" - }] - } - GetDisableButton = { - Type = "Pass" - End = true - Result = { - style = "danger" - type = "button" - value = "disable" - text = { - emoji = true - text = "Disable" - type = "plain_text" - } - } - } - GetEnableButton = { - Type = "Pass" - End = true - Result = { - style = "primary" - type = "button" - value = "enable" - text = { - emoji = true - text = "Enable" - type = "plain_text" - } - } - } - } - }, - { - StartAt = "GetRequest" - States = { - GetRequest = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:query" - End = true - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - IndexName = "Chrono" - KeyConditionExpression = "#SORT = :SORT" - FilterExpression = "APP_ID = :APP_ID AND TEAM_ID = :TEAM_ID AND USER_ID = :USER_ID" - ProjectionExpression = "ACCESS_TOKEN,USER_ID" - ExpressionAttributeNames = { "#SORT" = "SORT" } - ExpressionAttributeValues = { - ":SORT" = { S = "SLACK/AUTH" } - ":APP_ID" = { "S.$" = "$.api_app_id" } - ":TEAM_ID" = { "S.$" = "$.team_id" } - ":USER_ID" = { "S.$" = "$.event.user" } - } - } - } - } - } - ] - } - EncodeView = { - Type = "Pass" - Next = "SendRequest" - Parameters = { - "url.$" = "$.url" - "headers.$" = "$.headers" - body = { - "user_id.$" = "$.body.user_id" - "view.$" = "States.JsonToString($.body.view)" - } - } - } - SendRequest = { - Type = "Task" - Resource = aws_lambda_function.http_post.arn - End = true - ResultSelector = { - "statusCode.$" = "$.statusCode" - "headers.$" = "$.headers" - "body.$" = "States.StringToJson($.body)" - } - Parameters = { - "url.$" = "$.url" - "headers.$" = "$.headers" - "body.$" = "States.JsonToString($.body)" - } - } - } - }) -} - -resource "aws_sfn_state_machine" "slack_beta_enable_disable" { - name = "brutalismbot-slack-beta-enable-disable" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "Disable?" - States = { - "Disable?" = { - Type = "Choice" - Default = "Enable" - Choices = [{ - Next = "Disable" - Variable = "$.actions[0].value" - StringEquals = "disable" - }] - } - Disable = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:eventbridge:disableRule" - Next = "UpdateHome" - ResultPath = "$.state" - Parameters = { Name = aws_cloudwatch_event_rule.reddit_dequeue.name } - } - Enable = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:eventbridge:enableRule" - Next = "UpdateHome" - ResultPath = "$.state" - Parameters = { Name = aws_cloudwatch_event_rule.reddit_dequeue.name } - } - UpdateHome = { - Type = "Task" - Resource = "arn:aws:states:::events:putEvents" - End = true - Parameters = { - Entries = [{ - EventBusName = aws_cloudwatch_event_bus.brutalismbot.name - Source = "slack/beta" - DetailType = "event" - Detail = { - "team_id.$" = "$.user.team_id" - "api_app_id.$" = "$.api_app_id" - event = { - type = "app_home_opened" - "user.$" = "$.user.id" - } - } - }] - } - } - } - }) -} - -resource "aws_sfn_state_machine" "slack_install" { - name = "brutalismbot-slack-install" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "Parallelize" - States = { - Parallelize = { - Type = "Parallel" - Next = "PutEvents" - ResultSelector = { - EventBusName = aws_cloudwatch_event_bus.brutalismbot.name - Source = "reddit" - DetailType = "post/slack/channel" - Detail = { - "AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$" = "$$.Execution.Id" - "POST.$" = "$[0]" - "SLACK.$" = "$[1]" - } - } - Branches = [ - { - StartAt = "GetLastPostName" - States = { - GetLastPostName = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:getItem" - Next = "GetLastPost" - ResultSelector = { "NAME.$" = "$.Item.NAME.S" } - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - ProjectionExpression = "#NAME" - ExpressionAttributeNames = { "#NAME" = "NAME" } - Key = { - GUID = { S = "STATS/MAX" } - SORT = { S = "REDDIT/POST" } - } - } - } - GetLastPost = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:getItem" - Next = "TransformPost" - ResultSelector = { - "DATA.$" = "States.StringToJson($.Item.JSON.S)" - "CREATED_UTC.$" = "$.Item.CREATED_UTC.S" - "NAME.$" = "$.Item.NAME.S" - "PERMALINK.$" = "$.Item.PERMALINK.S" - "TITLE.$" = "$.Item.TITLE.S" - "TTL.$" = "States.StringToJson($.Item.TTL.N)" - } - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - ProjectionExpression = "CREATED_UTC,JSON,#NAME,PERMALINK,TITLE,#TTL" - ExpressionAttributeNames = { - "#NAME" = "NAME" - "#TTL" = "TTL" - } - Key = { - GUID = { "S.$" = "$.NAME" } - SORT = { S = "REDDIT/POST" } - } - } - } - TransformPost = { - Type = "Task" - Resource = aws_lambda_function.slack_transform.arn - End = true - InputPath = "$.DATA" - ResultPath = "$.DATA" - Retry = [{ - BackoffRate = 2 - IntervalSeconds = 3 - MaxAttempts = 4 - ErrorEquals = [ - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.ServiceException", - ] - }] - } - } - }, - { - StartAt = "GetSlack" - States = { - GetSlack = { - Type = "Pass" - End = true - Parameters = { - "ACCESS_TOKEN.$" = "$.access_token" - "APP_ID.$" = "$.app_id" - "CHANNEL_ID.$" = "$.incoming_webhook.channel_id" - "CHANNEL_NAME.$" = "$.incoming_webhook.channel" - "TEAM_ID.$" = "$.team.id" - "TEAM_NAME.$" = "$.team.name" - "WEBHOOK_URL.$" = "$.incoming_webhook.url" - } - } - } - }, - { - StartAt = "GetDynamoDBItem" - States = { - GetDynamoDBItem = { - Type = "Pass" - Next = "PutDynamoDBItem" - Parameters = { - SORT = { S = "SLACK/AUTH" } - ACCESS_TOKEN = { "S.$" = "$.access_token" } - APP_ID = { "S.$" = "$.app_id" } - CHANNEL_ID = { "S.$" = "$.incoming_webhook.channel_id" } - CHANNEL_NAME = { "S.$" = "$.incoming_webhook.channel" } - CREATED_UTC = { "S.$" = "$$.Execution.StartTime" } - GUID = { "S.$" = "States.Format('{}/{}/{}', $.app_id, $.team.id, $.incoming_webhook.channel_id)" } - JSON = { "S.$" = "$" } - SCOPE = { "S.$" = "$.scope" } - TEAM_ID = { "S.$" = "$.team.id" } - TEAM_NAME = { "S.$" = "$.team.name" } - USER_ID = { "S.$" = "$.authed_user.id" } - WEBHOOK_URL = { "S.$" = "$.incoming_webhook.url" } - } - } - PutDynamoDBItem = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:putItem" - End = true - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - "Item.$" = "$" - } - } - } - } - ] - } - PutEvents = { - Type = "Task" - Resource = "arn:aws:states:::events:putEvents" - End = true - Parameters = { "Entries.$" = "States.Array($)" } - } - } - }) -} - -resource "aws_sfn_state_machine" "slack_uninstall" { - name = "brutalismbot-slack-uninstall" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "GetQuery?" - States = { - "GetQuery?" = { - Type = "Choice" - Default = "GetQuery" - Choices = [{ - Next = "GetItems" - Variable = "$.QUERY" - IsPresent = true - }] - } - GetQuery = { - Type = "Pass" - Next = "GetItems" - Parameters = { - QUERY = { - TableName = aws_dynamodb_table.brutalismbot.name - IndexName = "SlackTeam" - Limit = 25 - ExclusiveStartKey = null - ProjectionExpression = "GUID,SORT" - KeyConditionExpression = "TEAM_ID = :TEAM_ID" - FilterExpression = "APP_ID = :APP_ID" - ExpressionAttributeValues = { - ":APP_ID" = { "S.$" = "$.api_app_id" } - ":TEAM_ID" = { "S.$" = "$.team_id" } - } - } - } - } - GetItems = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:query" - Next = "NextPage?" - InputPath = "$.QUERY" - ResultPath = "$.RESULT" - Parameters = { - "TableName.$" = "$.TableName" - "IndexName.$" = "$.IndexName" - "Limit.$" = "$.Limit" - "ExclusiveStartKey.$" = "$.ExclusiveStartKey" - "ProjectionExpression.$" = "$.ProjectionExpression" - "KeyConditionExpression.$" = "$.KeyConditionExpression" - "FilterExpression.$" = "$.FilterExpression" - "ExpressionAttributeValues.$" = "$.ExpressionAttributeValues" - } - } - "NextPage?" = { - Type = "Choice" - Default = "GetRequestItems" - Choices = [ - { - Next = "NextPage" - Variable = "$.RESULT.LastEvaluatedKey" - IsPresent = true - }, - { - Next = "Finish" - Variable = "$.RESULT.Count" - NumericEquals = 0 - } - ] - }, - NextPage = { - Type = "Task" - Resource = "arn:aws:states:::states:startExecution" - Next = "GetRequestItems" - ResultPath = "$.STATES" - Parameters = { - "StateMachineArn.$" = "$$.StateMachine.Id" - Input = { - "AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$" = "$$.Execution.Id" - "QUERY" = { - "TableName.$" = "$.QUERY.TableName" - "IndexName.$" = "$.QUERY.IndexName" - "Limit.$" = "$.QUERY.Limit" - "ProjectionExpression.$" = "$.QUERY.ProjectionExpression" - "KeyConditionExpression.$" = "$.QUERY.KeyConditionExpression" - "FilterExpression.$" = "$.QUERY.FilterExpression" - "ExpressionAttributeValues.$" = "$.QUERY.ExpressionAttributeValues" - "ExclusiveStartKey.$" = "$.RESULT.LastEvaluatedKey" - } - } - } - } - Finish = { Type = "Succeed" } - GetRequestItems = { - Type = "Map" - Next = "BatchWriteItem" - ItemsPath = "$.RESULT.Items" - ResultSelector = { "RequestItems" = { "${aws_dynamodb_table.brutalismbot.name}.$" = "$" } } - Iterator = { - StartAt = "GetRequestItem" - States = { - GetRequestItem = { - Type = "Pass" - End = true - Parameters = { - DeleteRequest = { - Key = { - "GUID.$" = "$.GUID" - "SORT.$" = "$.SORT" - } - } - } - } - } - } - } - BatchWriteItem = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:batchWriteItem" - End = true - Parameters = { "RequestItems.$" = "$.RequestItems" } - } - } - }) -} - -resource "aws_sfn_state_machine" "slack_post" { - name = "brutalismbot-slack-post" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "GetQuery?" - States = { - "GetQuery?" = { - Type = "Choice" - Default = "GetQuery" - Choices = [{ - Next = "ListAuths" - Variable = "$.QUERY" - IsPresent = true - }] - } - GetQuery = { - Type = "Pass" - Next = "ListAuths" - ResultPath = "$.QUERY" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - IndexName = "Chrono" - Limit = 10 - KeyConditionExpression = "SORT = :SORT" - FilterExpression = "attribute_not_exists(DISABLED)" - ProjectionExpression = "ACCESS_TOKEN,APP_ID,CHANNEL_ID,CHANNEL_NAME,#SCOPE,TEAM_ID,TEAM_NAME,USER_ID,WEBHOOK_URL" - ExpressionAttributeNames = { "#SCOPE" = "SCOPE" } - ExpressionAttributeValues = { ":SORT" = { S = "SLACK/AUTH" } } - ExclusiveStartKey = null - } - } - ListAuths = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:query" - Next = "NextPage?" - InputPath = "$.QUERY" - ResultPath = "$.RESULT" - Parameters = { - "TableName.$" = "$.TableName" - "IndexName.$" = "$.IndexName" - "KeyConditionExpression.$" = "$.KeyConditionExpression" - "FilterExpression.$" = "$.FilterExpression" - "Limit.$" = "$.Limit" - "ProjectionExpression.$" = "$.ProjectionExpression" - "ExpressionAttributeNames.$" = "$.ExpressionAttributeNames" - "ExpressionAttributeValues.$" = "$.ExpressionAttributeValues" - "ExclusiveStartKey.$" = "$.ExclusiveStartKey" - } - } - "NextPage?" = { - Type = "Choice" - Default = "GetEvents" - Choices = [ - { - Next = "NextPage" - Variable = "$.RESULT.LastEvaluatedKey" - IsPresent = true - }, - { - Next = "Finish" - Variable = "$.RESULT.Count" - NumericEquals = 0 - } - ] - }, - NextPage = { - Type = "Task" - Resource = "arn:aws:states:::states:startExecution" - Next = "GetEvents" - ResultPath = "$.STATES" - Parameters = { - "StateMachineArn.$" = "$$.StateMachine.Id" - Input = { - "AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$" = "$$.Execution.Id" - "POST.$" = "$.POST" - QUERY = { - "TableName.$" = "$.QUERY.TableName" - "IndexName.$" = "$.QUERY.IndexName" - "KeyConditionExpression.$" = "$.QUERY.KeyConditionExpression" - "FilterExpression.$" = "$.QUERY.FilterExpression" - "Limit.$" = "$.QUERY.Limit" - "ProjectionExpression.$" = "$.QUERY.ProjectionExpression" - "ExpressionAttributeNames.$" = "$.QUERY.ExpressionAttributeNames" - "ExpressionAttributeValues.$" = "$.QUERY.ExpressionAttributeValues" - "ExclusiveStartKey.$" = "$.RESULT.LastEvaluatedKey" - } - } - } - } - Finish = { Type = "Succeed" } - GetEvents = { - Type = "Map" - Next = "PublishEvents" - ItemsPath = "$.RESULT.Items" - ResultSelector = { "Entries.$" = "$" } - Parameters = { - "POST.$" = "$.POST" - SLACK = { - "ACCESS_TOKEN.$" = "$$.Map.Item.Value.ACCESS_TOKEN.S" - "APP_ID.$" = "$$.Map.Item.Value.APP_ID.S" - "CHANNEL_ID.$" = "$$.Map.Item.Value.CHANNEL_ID.S" - "CHANNEL_NAME.$" = "$$.Map.Item.Value.CHANNEL_NAME.S" - "SCOPE.$" = "$$.Map.Item.Value.SCOPE.S" - "TEAM_ID.$" = "$$.Map.Item.Value.TEAM_ID.S" - "TEAM_NAME.$" = "$$.Map.Item.Value.TEAM_NAME.S" - "USER_ID.$" = "$$.Map.Item.Value.USER_ID.S" - "WEBHOOK_URL.$" = "$$.Map.Item.Value.WEBHOOK_URL.S" - } - } - Iterator = { - StartAt = "GetEvent" - States = { - GetEvent = { - Type = "Pass" - End = true - Parameters = { - EventBusName = aws_cloudwatch_event_bus.brutalismbot.name - Source = "reddit" - DetailType = "post/slack/channel" - Detail = { - "AWS_STEP_FUNCTIONS_STARTED_BY_EXECUTION_ID.$" = "$$.Execution.Id" - "POST.$" = "$.POST" - "SLACK.$" = "$.SLACK" - } - } - } - } - } - } - PublishEvents = { - Type = "Task" - Resource = "arn:aws:states:::events:putEvents" - End = true - Parameters = { "Entries.$" = "$.Entries" } - } - } - }) -} - -resource "aws_sfn_state_machine" "slack_post_channel" { - name = "brutalismbot-slack-post-channel" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "PutItem" - States = { - PutItem = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:putItem" - Next = "PostMethod" - ResultPath = "$.DYNAMODB" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - Item = { - SORT = { S = "SLACK/POST" } - APP_ID = { "S.$" = "$.SLACK.APP_ID" } - CHANNEL_ID = { "S.$" = "$.SLACK.CHANNEL_ID" } - CREATED_UTC = { "S.$" = "$.POST.CREATED_UTC" } - GUID = { "S.$" = "States.Format('{}/{}/{}/{}', $.SLACK.APP_ID, $.SLACK.TEAM_ID, $.SLACK.CHANNEL_ID, $.POST.NAME)" } - JSON = { "S.$" = "$.POST.DATA" } - NAME = { "S.$" = "$.POST.NAME" } - SCOPE = { "S.$" = "$.SLACK.SCOPE" } - TEAM_ID = { "S.$" = "$.SLACK.TEAM_ID" } - TTL = { "N.$" = "States.JsonToString($.POST.TTL)" } - } - } - } - "PostMethod" = { - Type = "Choice" - Default = "SendWebhook" - Choices = [ - { - Next = "AddUser" - And = [ - { - Variable = "$.SLACK.SCOPE" - IsPresent = true - }, - { - Variable = "$.SLACK.SCOPE" - StringMatches = "*chat:write*" - }, - { - Variable = "$.SLACK.CHANNEL_ID" - StringMatches = "D*" - } - ] - }, - { - Next = "AddChannel" - And = [ - { - Variable = "$.SLACK.SCOPE" - IsPresent = true - }, - { - Variable = "$.SLACK.SCOPE" - StringMatches = "*im:write*" - } - ] - } - ] - } - AddChannel = { - Type = "Pass" - Next = "SendChat" - InputPath = "$.SLACK.CHANNEL_ID" - ResultPath = "$.POST.DATA.channel" - } - AddUser = { - Type = "Pass" - Next = "SendChat" - InputPath = "$.SLACK.USER_ID" - ResultPath = "$.POST.DATA.channel" - } - SendChat = { - Type = "Task" - Resource = aws_lambda_function.http_post.arn - Next = "GetChatUpdate" - ResultPath = "$.HTTP" - ResultSelector = { - "statusCode.$" = "$.statusCode" - "headers.$" = "$.headers" - "body.$" = "States.StringToJson($.body)" - } - Parameters = { - url = "https://slack.com/api/chat.postMessage" - "body.$" = "States.JsonToString($.POST.DATA)" - headers = { - "authorization.$" = "States.Format('Bearer {}', $.SLACK.ACCESS_TOKEN)" - "content-type" = "application/json; charset=utf-8" - } - } - Retry = [{ - BackoffRate = 2 - IntervalSeconds = 3 - MaxAttempts = 4 - ErrorEquals = [ - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.ServiceException", - ] - }] - } - SendWebhook = { - Type = "Task" - Resource = aws_lambda_function.http_post.arn - Next = "GetWebhookUpdate" - ResultPath = "$.HTTP" - Parameters = { - "url.$" = "$.SLACK.WEBHOOK_URL" - "body.$" = "States.JsonToString($.POST.DATA)" - headers = { - "authorization.$" = "States.Format('Bearer {}', $.SLACK.ACCESS_TOKEN)" - "content-type" = "application/json; charset=utf-8" - } - } - Retry = [{ - BackoffRate = 2 - IntervalSeconds = 3 - MaxAttempts = 4 - ErrorEquals = [ - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.ServiceException", - ] - }] - } - GetChatUpdate = { - Type = "Pass" - Next = "UpdateItem" - ResultPath = "$.DYNAMODB" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - UpdateExpression = "SET BODY = :BODY, EXECUTION_ID = :EXECUTION_ID, HEADERS = :HEADERS, STATUS_CODE = :STATUS_CODE, TS = :TS" - ExpressionAttributeValues = { - ":BODY" = { "S.$" = "$.HTTP.body" } - ":EXECUTION_ID" = { "S.$" = "$$.Execution.Id" } - ":HEADERS" = { "S.$" = "$.HTTP.headers" } - ":STATUS_CODE" = { "S.$" = "$.HTTP.statusCode" } - ":TS" = { "S.$" = "$.HTTP.body.ts" } - } - Key = { - GUID = { "S.$" = "States.Format('{}/{}/{}/{}', $.SLACK.APP_ID, $.SLACK.TEAM_ID, $.SLACK.CHANNEL_ID, $.POST.NAME)" } - SORT = { S = "SLACK/POST" } - } - } - } - GetWebhookUpdate = { - Type = "Pass" - Next = "UpdateItem" - ResultPath = "$.DYNAMODB" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - UpdateExpression = "SET BODY = :BODY, EXECUTION_ID = :EXECUTION_ID, HEADERS = :HEADERS, STATUS_CODE = :STATUS_CODE" - ExpressionAttributeValues = { - ":BODY" = { "S.$" = "$.HTTP.body" } - ":EXECUTION_ID" = { "S.$" = "$$.Execution.Id" } - ":HEADERS" = { "S.$" = "$.HTTP.headers" } - ":STATUS_CODE" = { "S.$" = "$.HTTP.statusCode" } - } - Key = { - GUID = { "S.$" = "States.Format('{}/{}/{}/{}', $.SLACK.APP_ID, $.SLACK.TEAM_ID, $.SLACK.CHANNEL_ID, $.POST.NAME)" } - SORT = { S = "SLACK/POST" } - } - } - } - UpdateItem = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:updateItem" - Next = "OK?" - InputPath = "$.DYNAMODB" - ResultPath = "$.DYNAMODB" - Parameters = { - "TableName.$" = "$.TableName" - "UpdateExpression.$" = "$.UpdateExpression" - "ExpressionAttributeValues.$" = "$.ExpressionAttributeValues" - "Key.$" = "$.Key" - } - Retry = [{ - BackoffRate = 2 - IntervalSeconds = 3 - MaxAttempts = 4 - ErrorEquals = ["DynamoDD.InternalServerErrorException"] - }] - } - "OK?" = { - Type = "Choice" - Default = "Fail" - Choices = [ - { - Next = "Succeed" - And = [ - { - Variable = "$.HTTP.statusCode" - StringEquals = "200" - }, - { - Variable = "$.HTTP.body" - StringEquals = "ok" - } - ] - }, - { - Next = "Succeed" - And = [ - { - Variable = "$.HTTP.statusCode" - StringEquals = "200" - }, - { - Variable = "$.HTTP.body.ok" - IsPresent = true - }, - { - Variable = "$.HTTP.body.ok" - BooleanEquals = true - } - ] - } - ] - } - Succeed = { Type = "Succeed" } - Fail = { Type = "Fail" } - } - }) -} - -# STATE MACHINES :: TWITTER - -resource "aws_sfn_state_machine" "twitter_post" { - name = "brutalismbot-twitter-post" - role_arn = aws_iam_role.states.arn - - definition = jsonencode({ - StartAt = "PutItem" - States = { - PutItem = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:putItem" - Next = "SendTweet" - InputPath = "$.POST" - ResultPath = "$.PUT" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - Item = { - SORT = { S = "TWITTER/POST" } - GUID = { "S.$" = "States.Format('@brutalismbot/{}', $.NAME)" } - CREATED_UTC = { "S.$" = "$.CREATED_UTC" } - JSON = { "S.$" = "$.DATA" } - NAME = { "S.$" = "$.NAME" } - PERMALINK = { "S.$" = "$.PERMALINK" } - TITLE = { "S.$" = "$.TITLE" } - TTL = { "N.$" = "States.JsonToString($.TTL)" } - } - } - } - SendTweet = { - Type = "Task" - Resource = aws_lambda_function.twitter_post.arn - Next = "UpdateItem" - InputPath = "$.POST.DATA" - ResultPath = "$.POST.DATA" - Retry = [{ - BackoffRate = 2 - IntervalSeconds = 3 - MaxAttempts = 4 - ErrorEquals = [ - "Lambda.AWSLambdaException", - "Lambda.SdkClientException", - "Lambda.ServiceException", - ] - }] - } - UpdateItem = { - Type = "Task" - Resource = "arn:aws:states:::aws-sdk:dynamodb:updateItem" - End = true - InputPath = "$.POST" - ResultPath = "$.UPDATE" - Parameters = { - TableName = aws_dynamodb_table.brutalismbot.name - UpdateExpression = "SET EXECUTION_ID = :EXECUTION_ID, JSON = :JSON" - ExpressionAttributeValues = { - ":EXECUTION_ID" = { "S.$" = "$$.Execution.Id" } - ":JSON" = { "S.$" = "$.DATA" } - } - Key = { - GUID = { "S.$" = "States.Format('@brutalismbot/{}', $.NAME)" } - SORT = { S = "TWITTER/POST" } - } - } - } - } - }) -} - -# OUTPUTS - -output "state_machine_reddit_dequeue_arn" { - description = "Reddit dequeue state machine ARN" - value = aws_sfn_state_machine.reddit_dequeue.id -}