govdelivery-tms 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +14 -0
  3. data/README.md +324 -0
  4. data/Rakefile +20 -0
  5. data/govdelivery-tms.gemspec +30 -0
  6. data/lib/govdelivery-tms.rb +43 -0
  7. data/lib/govdelivery-tms/base.rb +37 -0
  8. data/lib/govdelivery-tms/client.rb +97 -0
  9. data/lib/govdelivery-tms/collection_resource.rb +54 -0
  10. data/lib/govdelivery-tms/connection.rb +34 -0
  11. data/lib/govdelivery-tms/errors.rb +58 -0
  12. data/lib/govdelivery-tms/instance_resource.rb +219 -0
  13. data/lib/govdelivery-tms/link_header.rb +223 -0
  14. data/lib/govdelivery-tms/logger.rb +36 -0
  15. data/lib/govdelivery-tms/mail/delivery_method.rb +63 -0
  16. data/lib/govdelivery-tms/resource/collections.rb +98 -0
  17. data/lib/govdelivery-tms/resource/command.rb +25 -0
  18. data/lib/govdelivery-tms/resource/command_action.rb +17 -0
  19. data/lib/govdelivery-tms/resource/command_type.rb +20 -0
  20. data/lib/govdelivery-tms/resource/email_message.rb +81 -0
  21. data/lib/govdelivery-tms/resource/email_recipient.rb +31 -0
  22. data/lib/govdelivery-tms/resource/email_recipient_click.rb +8 -0
  23. data/lib/govdelivery-tms/resource/email_recipient_open.rb +8 -0
  24. data/lib/govdelivery-tms/resource/email_template.rb +15 -0
  25. data/lib/govdelivery-tms/resource/from_address.rb +10 -0
  26. data/lib/govdelivery-tms/resource/inbound_sms_message.rb +8 -0
  27. data/lib/govdelivery-tms/resource/ipaws_acknowledgement.rb +9 -0
  28. data/lib/govdelivery-tms/resource/ipaws_alert.rb +38 -0
  29. data/lib/govdelivery-tms/resource/ipaws_category.rb +7 -0
  30. data/lib/govdelivery-tms/resource/ipaws_cog_profile.rb +29 -0
  31. data/lib/govdelivery-tms/resource/ipaws_event_code.rb +7 -0
  32. data/lib/govdelivery-tms/resource/ipaws_nwem_area.rb +18 -0
  33. data/lib/govdelivery-tms/resource/ipaws_nwem_authorization.rb +9 -0
  34. data/lib/govdelivery-tms/resource/ipaws_nwem_auxilary_data.rb +8 -0
  35. data/lib/govdelivery-tms/resource/ipaws_response_type.rb +7 -0
  36. data/lib/govdelivery-tms/resource/ipaws_static_resource.rb +8 -0
  37. data/lib/govdelivery-tms/resource/keyword.rb +30 -0
  38. data/lib/govdelivery-tms/resource/recipient.rb +10 -0
  39. data/lib/govdelivery-tms/resource/sms_message.rb +35 -0
  40. data/lib/govdelivery-tms/resource/webhook.rb +20 -0
  41. data/lib/govdelivery-tms/util/core_ext.rb +27 -0
  42. data/lib/govdelivery-tms/util/hal_link_parser.rb +50 -0
  43. data/lib/govdelivery-tms/version.rb +3 -0
  44. data/spec/client_spec.rb +41 -0
  45. data/spec/command_types_spec.rb +29 -0
  46. data/spec/email_message_spec.rb +102 -0
  47. data/spec/email_template_spec.rb +149 -0
  48. data/spec/errors_spec.rb +13 -0
  49. data/spec/from_address_spec.rb +86 -0
  50. data/spec/inbound_sms_messages_spec.rb +19 -0
  51. data/spec/instance_resource_spec.rb +61 -0
  52. data/spec/ipaws_acknowledgement_spec.rb +16 -0
  53. data/spec/ipaws_alerts_spec.rb +192 -0
  54. data/spec/ipaws_cog_profile_spec.rb +75 -0
  55. data/spec/ipaws_event_codes_spec.rb +35 -0
  56. data/spec/ipaws_nwem_areas_spec.rb +58 -0
  57. data/spec/ipaws_nwem_authorization_spec.rb +16 -0
  58. data/spec/keyword_spec.rb +62 -0
  59. data/spec/keywords_spec.rb +21 -0
  60. data/spec/mail/delivery_method_spec.rb +52 -0
  61. data/spec/sms_message_spec.rb +63 -0
  62. data/spec/sms_messages_spec.rb +21 -0
  63. data/spec/spec_helper.rb +31 -0
  64. data/spec/tms_spec.rb +7 -0
  65. metadata +172 -0
@@ -0,0 +1,223 @@
1
+ # link_header, Copyright (c) 2009 Mike Burrows
2
+
3
+ # Permission is hereby granted, free of charge, to any person obtaining
4
+ # a copy of this software and associated documentation files (the
5
+ # "Software"), to deal in the Software without restriction, including
6
+ # without limitation the rights to use, copy, modify, merge, publish,
7
+ # distribute, sublicense, and/or sell copies of the Software, and to
8
+ # permit persons to whom the Software is furnished to do so, subject to
9
+ # the following conditions:
10
+
11
+ # The above copyright notice and this permission notice shall be
12
+ # included in all copies or substantial portions of the Software.
13
+
14
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21
+
22
+ require "strscan"
23
+
24
+ #
25
+ # Represents an HTTP link header of the form described in the draft spec http://tools.ietf.org/id/draft-nottingham-http-link-header-06.txt.
26
+ # It is simply a list of LinkHeader::Link objects and some conversion functions.
27
+ #
28
+ class LinkHeader
29
+
30
+ # An array of Link objects
31
+ attr_reader :links
32
+
33
+ #
34
+ # Initialize from a collection of either LinkHeader::Link objects or data from which Link objects can be created.
35
+ #
36
+ # From a list of LinkHeader::Link objects:
37
+ #
38
+ # LinkHeader.new([
39
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"]]),
40
+ # LinkHeader::Link.new("http://example.com/", [["rel", "up"]])])
41
+ #
42
+ # From the equivalent JSON-friendly raw data:
43
+ #
44
+ # LinkHeader.new([
45
+ # ["http://example.com/foo", [["rel", "self"]]],
46
+ # ["http://example.com/", [["rel", "up"]]]]).to_s
47
+ #
48
+ # See also LinkHeader.parse
49
+ #
50
+ def initialize(links=[])
51
+ if links
52
+ @links = links.map{|l| l.kind_of?(Link) ? l : Link.new(*l)}
53
+ else
54
+ @links = []
55
+ end
56
+ end
57
+
58
+ #
59
+ # Convert to a JSON-friendly array
60
+ #
61
+ # LinkHeader.parse('<http://example.com/foo>; rel="self", <http://example.com/>; rel = "up"').to_a
62
+ # #=> [["http://example.com/foo", [["rel", "self"]]],
63
+ # ["http://example.com/", [["rel", "up"]]]]
64
+ #
65
+ def to_a
66
+ links.map{|l| l.to_a}
67
+ end
68
+
69
+ #
70
+ # Convert to string representation as per the link header spec
71
+ #
72
+ # LinkHeader.new([
73
+ # ["http://example.com/foo", [["rel", "self"]]],
74
+ # ["http://example.com/", [["rel", "up"]]]]).to_s
75
+ # #=> '<http://example.com/foo>; rel="self", <http://example.com/>; rel = "up"'
76
+ #
77
+ def to_s
78
+ links.join(', ')
79
+ end
80
+
81
+ #
82
+ # Regexes for link header parsing. TOKEN and QUOTED in particular should conform to RFC2616.
83
+ #
84
+ # Acknowledgement: The QUOTED regexp is based on
85
+ # http://stackoverflow.com/questions/249791/regexp-for-quoted-string-with-escaping-quotes/249937#249937
86
+ #
87
+ HREF = / *< *([^>]*) *> *;? */ #:nodoc: note: no attempt to check URI validity
88
+ TOKEN = /([^()<>@,;:\"\[\]?={}\s]+)/ #:nodoc: non-empty sequence of non-separator characters
89
+ QUOTED = /"((?:[^"\\]|\\.)*)"/ #:nodoc: double-quoted strings with backslash-escaped double quotes
90
+ ATTR = /#{TOKEN} *= *(#{TOKEN}|#{QUOTED}) */ #:nodoc:
91
+ SEMI = /; */ #:nodoc:
92
+ COMMA = /, */ #:nodoc:
93
+
94
+ #
95
+ # Parse a link header, returning a new LinkHeader object
96
+ #
97
+ # LinkHeader.parse('<http://example.com/foo>; rel="self", <http://example.com/>; rel = "up"').to_a
98
+ # #=> [["http://example.com/foo", [["rel", "self"]]],
99
+ # ["http://example.com/", [["rel", "up"]]]]
100
+ #
101
+ def self.parse(link_header)
102
+ return new unless link_header
103
+
104
+ scanner = StringScanner.new(link_header)
105
+ links = []
106
+ while scanner.scan(HREF)
107
+ href = scanner[1]
108
+ attrs = []
109
+ while scanner.scan(ATTR)
110
+ attr_name, token, quoted = scanner[1], scanner[3], scanner[4].gsub(/\\"/, '"')
111
+ attrs.push([attr_name, token || quoted])
112
+ break unless scanner.scan(SEMI)
113
+ end
114
+ links.push(Link.new(href, attrs))
115
+ break unless scanner.scan(COMMA)
116
+ end
117
+
118
+ new(links)
119
+ end
120
+
121
+ #
122
+ # Find a member link that has the given attributes
123
+ #
124
+ def find_link(*attr_pairs)
125
+ links.detect do |link|
126
+ !attr_pairs.detect do |pair|
127
+ !link.attr_pairs.include?(pair)
128
+ end
129
+ end
130
+ end
131
+
132
+ #
133
+ # Render as a list of HTML link elements
134
+ #
135
+ def to_html(separator="\n")
136
+ links.map{|link| link.to_html}.join(separator)
137
+ end
138
+
139
+ #
140
+ # Represents a link - an href and a list of attributes (key value pairs)
141
+ #
142
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"]]).to_s
143
+ # => '<http://example.com/foo>; rel="self"'
144
+ #
145
+ class Link
146
+ #
147
+ # The link's URI string
148
+ #
149
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"]]).href
150
+ # => 'http://example.com/foo>'
151
+ #
152
+ attr_reader :href
153
+
154
+ #
155
+ # The link's attributes, an array of key-value pairs
156
+ #
157
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"], ["rel", "canonical"]]).attr_pairs
158
+ # => [["rel", "self"], ["rel", "canonical"]]
159
+ #
160
+ attr_reader :attr_pairs
161
+
162
+ #
163
+ # Initialize a Link from an href and attribute list
164
+ #
165
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"]]).to_s
166
+ # => '<http://example.com/foo>; rel="self"'
167
+ #
168
+ def initialize(href, attr_pairs)
169
+ @href, @attr_pairs = href, attr_pairs
170
+ end
171
+
172
+ #
173
+ # Lazily convert the attribute list to a Hash
174
+ #
175
+ # Beware repeated attribute names (it's safer to use #attr_pairs if this is risk):
176
+ #
177
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"], ["rel", "canonical"]]).attrs
178
+ # => {"rel" =>"canonical"}
179
+ #
180
+ def attrs
181
+ @attrs ||= Hash[*attr_pairs.flatten]
182
+ end
183
+
184
+ #
185
+ # Access #attrs by key
186
+ #
187
+ def [](key)
188
+ attrs[key]
189
+ end
190
+
191
+ #
192
+ # Convert to a JSON-friendly Array
193
+ #
194
+ # LinkHeader::Link.new("http://example.com/foo", [["rel", "self"], ["rel", "canonical"]]).to_a
195
+ # => ["http://example.com/foo", [["rel", "self"], ["rel", "canonical"]]]
196
+ #
197
+ def to_a
198
+ [href, attr_pairs]
199
+ end
200
+
201
+ #
202
+ # Convert to string representation as per the link header spec. This includes backspace-escaping doublequote characters in
203
+ # quoted attribute values.
204
+ #
205
+ # Convert to string representation as per the link header spec
206
+ #
207
+ # LinkHeader::Link.new(["http://example.com/foo", [["rel", "self"]]]).to_s
208
+ # #=> '<http://example.com/foo>; rel="self"'
209
+ #
210
+ def to_s
211
+ (["<#{href}>"] + attr_pairs.map{|k, v| "#{k}=\"#{v.gsub(/"/, '\"')}\""}).join('; ')
212
+ end
213
+
214
+ #
215
+ # Bonus! Render as an HTML link element
216
+ #
217
+ # LinkHeader::Link.new(["http://example.com/foo", [["rel", "self"]]]).to_html
218
+ # #=> '<link href="http://example.com/foo" rel="self">'
219
+ def to_html
220
+ ([%Q(<link href="#{href}")] + attr_pairs.map{|k, v| "#{k}=\"#{v.gsub(/"/, '\"')}\""}).join(' ')
221
+ end
222
+ end
223
+ end
@@ -0,0 +1,36 @@
1
+ module TMS #:nodoc:
2
+ class Logger < Faraday::Response::Middleware #:nodoc:
3
+ extend Forwardable
4
+
5
+ def initialize(app, logger = nil)
6
+ super(app)
7
+ @logger = logger || begin
8
+ require 'logger'
9
+ ::Logger.new(STDOUT)
10
+ end
11
+ end
12
+
13
+ def_delegators :@logger, :debug, :info, :warn, :error, :fatal
14
+
15
+ def call(env)
16
+ debug "performing #{env[:method].to_s.upcase.ljust(7)} #{env[:url]}"
17
+
18
+ start = Time.now
19
+
20
+ # In order to log request duration in a threadsafe way, `start` must be a local variable instead of instance variable.
21
+ @app.call(env).on_complete do |environment|
22
+ on_complete(environment)
23
+ log_stuff(start, environment)
24
+ end
25
+ end
26
+
27
+ private
28
+
29
+ def log_stuff(start, environment)
30
+ duration = Time.now - start
31
+ info "#{environment[:method].to_s.upcase.ljust(7)}#{environment[:status].to_s.ljust(4)}#{environment[:url]} (#{duration} seconds)"
32
+ debug('response headers') { JSON.pretty_generate environment[:response_headers] }
33
+ debug('response body') { environment[:body] }
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,63 @@
1
+ require 'govdelivery-tms'
2
+ require 'mail'
3
+ require 'mail/check_delivery_params'
4
+ module TMS
5
+ module Mail
6
+ # Use TMS from the mail gem or ActionMailer as a delivery method.
7
+ #
8
+ # # Gemfile
9
+ # gem 'govdelivery-tms', :require=>'govdelivery-tms/mail/delivery_method'
10
+ #
11
+ # # config/environment.rb
12
+ # config.action_mailer.delivery_method = :govdelivery_tms
13
+ # config.action_mailer.govdelivery_tms_settings = {
14
+ # :auth_token=>'auth_token',
15
+ # :api_root=>'https://stage-tms.govdelivery.com'
16
+ # }
17
+ class DeliveryMethod
18
+ include ::Mail::CheckDeliveryParams
19
+
20
+ def initialize(values)
21
+ self.settings = values
22
+ end
23
+
24
+ attr_accessor :settings
25
+
26
+ def deliver!(mail)
27
+ raise TMS::Errors::NoRelation.new('email_messages', client) unless client.respond_to?(:email_messages)
28
+
29
+ envelope_from = mail.return_path || mail.sender || mail.from_addrs.first
30
+
31
+ body = case
32
+ when mail.html_part
33
+ mail.html_part.body
34
+ when mail.text_part
35
+ mail.text_part.body
36
+ else
37
+ mail.body
38
+ end.decoded
39
+
40
+ tms_message = client.email_messages.build(
41
+ :from_name => mail[:from].display_names.first,
42
+ :subject => mail.subject,
43
+ :body => body
44
+ )
45
+
46
+ mail.to.each { |recip| tms_message.recipients.build(:email => recip) }
47
+ tms_message.post!
48
+ tms_message
49
+ end
50
+
51
+ def client
52
+ @client ||= TMS::Client.new(settings[:auth_token], settings)
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ if defined?(ActionMailer)
59
+ ActionMailer::Base.add_delivery_method :govdelivery_tms, TMS::Mail::DeliveryMethod, {
60
+ :auth_token => nil,
61
+ :logger => ActionMailer::Base.logger,
62
+ :api_root => TMS::Client::DEFAULTS[:api_root]}
63
+ end
@@ -0,0 +1,98 @@
1
+ class TMS::Emails
2
+ include TMS::CollectionResource
3
+ end
4
+
5
+ class TMS::SmsMessages
6
+ include TMS::CollectionResource
7
+ end
8
+
9
+ class TMS::EmailMessages
10
+ include TMS::CollectionResource
11
+ end
12
+
13
+ class TMS::Recipients
14
+ include TMS::CollectionResource
15
+ end
16
+
17
+ class TMS::EmailRecipients
18
+ include TMS::CollectionResource
19
+ end
20
+
21
+ class TMS::EmailRecipientOpens
22
+ include TMS::CollectionResource
23
+ end
24
+
25
+ class TMS::EmailRecipientClicks
26
+ include TMS::CollectionResource
27
+ end
28
+
29
+ # A collection of Keyword objects.
30
+ #
31
+ # @example
32
+ # keywords = client.keywords.get
33
+ #
34
+ class TMS::Keywords
35
+ include TMS::CollectionResource
36
+ end
37
+
38
+ class TMS::InboundSmsMessages
39
+ include TMS::CollectionResource
40
+ end
41
+
42
+ # A collection of CommandType instances.
43
+ # This resource changes infrequently. It may be used to dynamically construct a
44
+ # user interface for configuring arbitrary SMS keywords for an account.
45
+ #
46
+ # This resource is read-only.
47
+ #
48
+ # @example
49
+ # client.command_types.get
50
+ # client.command_types.collection.each {|at| ... }
51
+ class TMS::CommandTypes
52
+ include TMS::CollectionResource
53
+ end
54
+
55
+ class TMS::Commands
56
+ include TMS::CollectionResource
57
+ end
58
+
59
+ class TMS::CommandActions
60
+ include TMS::CollectionResource
61
+ end
62
+
63
+ class TMS::IpawsEventCodes
64
+ include TMS::CollectionResource
65
+ end
66
+
67
+ class TMS::IpawsCategories
68
+ include TMS::CollectionResource
69
+ end
70
+
71
+ class TMS::IpawsResponseTypes
72
+ include TMS::CollectionResource
73
+ end
74
+
75
+ class TMS::IpawsAlerts
76
+ include TMS::CollectionResource
77
+ end
78
+
79
+ class TMS::IpawsNwemAreas
80
+ include TMS::CollectionResource
81
+ end
82
+
83
+ class TMS::Webhooks
84
+ include TMS::CollectionResource
85
+ end
86
+
87
+ # A collection of Email Template objects.
88
+ #
89
+ # @example
90
+ # email_template = client.email_template.get
91
+ #
92
+ class TMS::EmailTemplates
93
+ include TMS::CollectionResource
94
+ end
95
+
96
+ class TMS::FromAddresses
97
+ include TMS::CollectionResource
98
+ end
@@ -0,0 +1,25 @@
1
+ module TMS #:nodoc:
2
+ # A command is a combination of behavior and parameters that should be executed
3
+ # when an incoming SMS message matches the associated Keyword.
4
+ #
5
+ # @attr name [String] The name of the command. This will default to the command_type if not supplied.
6
+ # @attr command_type [String] The type of this command. A list of valid types can be found by querying the CommandType list.
7
+ # @attr params [Hash] A Hash of string/string pairs used as configuration for this command.
8
+ #
9
+ # @example
10
+ # command = keyword.commands.build(:name => "subscribe to news", :command_type => "dcm_subscribe", :dcm_account_code => "NEWS", :dcm_topic_codes => "NEWS_1, NEWS_2")
11
+ # command.post
12
+ # command.dcm_topic_codes += ", NEWS_5"
13
+ # command.put
14
+ # command.delete
15
+ class Command
16
+ include InstanceResource
17
+
18
+ # @!parse attr_accessor :name, :command_type, :params
19
+ writeable_attributes :name, :command_type, :params
20
+
21
+ # @!parse attr_reader :created_at, :updated_at
22
+ readonly_attributes :created_at, :updated_at
23
+
24
+ end
25
+ end