govdelivery-tms 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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