psm 0.1.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.
- data/.gitignore +2 -0
- data/README.textile +31 -0
- data/VERSION +1 -0
- data/lib/psm.rb +61 -0
- data/lib/psm/session.rb +57 -0
- data/test/fake_service.rb +57 -0
- data/test/psm/session_test.rb +43 -0
- data/test/psm_test.rb +105 -0
- data/test/test_helper.rb +5 -0
- metadata +128 -0
data/.gitignore
ADDED
data/README.textile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
h2. Smail Webservice
|
2
|
+
|
3
|
+
p. Print Stuff Mail is snail mail webservice, allowing physical letters to be sent from the comfort of your own command line.
|
4
|
+
|
5
|
+
h2. Installation
|
6
|
+
|
7
|
+
@gem install psm@
|
8
|
+
|
9
|
+
That's it.
|
10
|
+
|
11
|
+
h2. Example
|
12
|
+
|
13
|
+
<pre>
|
14
|
+
<code>
|
15
|
+
require 'psm'
|
16
|
+
|
17
|
+
# PrintStuffMail, aliased as PSM, is our namespace.
|
18
|
+
# 1. set your account id.
|
19
|
+
PSM.account_id = 'abcdefg'
|
20
|
+
|
21
|
+
# 2. mail a letter!
|
22
|
+
# Since this method will eventually cause a credit card to be charged
|
23
|
+
# we've taken extra precautions to label it as dangerous. The exclamation,
|
24
|
+
# for one, calls attention to the method, but furthermore a block has to be
|
25
|
+
# passed in "confirming" the method.
|
26
|
+
PSM.mail!( 'message', 'address', 'return_address') {|c| c.confirm!}
|
27
|
+
|
28
|
+
# 3. there is no step three.
|
29
|
+
</code>
|
30
|
+
</pre>
|
31
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/lib/psm.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'psm/session'
|
3
|
+
|
4
|
+
# dependencies
|
5
|
+
require 'addressable/uri'
|
6
|
+
require 'json'
|
7
|
+
|
8
|
+
module PrintStuffMail
|
9
|
+
|
10
|
+
BASE_URL = 's.copypastel.com/psm'
|
11
|
+
|
12
|
+
class Confirmation
|
13
|
+
def initialize; @confirmed = false end
|
14
|
+
def confirm!; @confirmed = true end
|
15
|
+
def confirmed?; @confirmed end
|
16
|
+
end
|
17
|
+
|
18
|
+
class << self
|
19
|
+
attr_accessor :account_id
|
20
|
+
|
21
|
+
def mail!( message, address, return_address = nil )
|
22
|
+
raise(SecurityError, 'no account_id set.') unless account_id
|
23
|
+
raise(SecurityError, 'need to confirm!') unless block_given?
|
24
|
+
yield c = Confirmation.new
|
25
|
+
raise(SecurityError, 'need to confirm!') unless c.confirmed?
|
26
|
+
|
27
|
+
@session ||= Session.new account_id
|
28
|
+
raise unless @session.active? or @session.renew! # needs an error type
|
29
|
+
post_letter(message, address, return_address)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def get_session(account_id)
|
35
|
+
@session = Session.new(account_id)
|
36
|
+
end
|
37
|
+
|
38
|
+
def post_letter(message, address, return_address = nil)
|
39
|
+
uri = Addressable::URI.new :host => PSM::BASE_URL,
|
40
|
+
:path => '/letters/print'
|
41
|
+
params = { :message => message, :address => address,
|
42
|
+
:return_address => return_address,
|
43
|
+
:session => @session.id }
|
44
|
+
params.delete_if {|k, v| v.nil?}
|
45
|
+
uri.query_values = params
|
46
|
+
response = Net::HTTP.start(PrintStuffMail::BASE_URL, 80) do |http|
|
47
|
+
http.post(uri.path, uri.query)
|
48
|
+
end
|
49
|
+
JSON.parse response.body
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
|
54
|
+
def mail!( message, return_address = nil, &block )
|
55
|
+
PSM.mail!( message, self.address, return_address, block)
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
# Short acronym for quick reference
|
61
|
+
PSM = PrintStuffMail
|
data/lib/psm/session.rb
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'date'
|
3
|
+
|
4
|
+
# dependencies
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
module PrintStuffMail
|
8
|
+
|
9
|
+
BASE_URL = 's.copypastel.com/psm'
|
10
|
+
|
11
|
+
class Session
|
12
|
+
|
13
|
+
attr_reader :id, :last_response, :expiration
|
14
|
+
|
15
|
+
def initialize(account_id)
|
16
|
+
@account_id = account_id
|
17
|
+
raise "(PSM::Session) couldn't get a session token." unless renew! # We don't want to instantiate unless we can get a session
|
18
|
+
end
|
19
|
+
|
20
|
+
def time_left
|
21
|
+
@expiration - DateTime.now # returns Rational. Wonder which units...
|
22
|
+
end
|
23
|
+
|
24
|
+
def renew!
|
25
|
+
@last_response = get_response
|
26
|
+
return false unless @last_response['status'] == 201 # We don't have to error out if we can't renew
|
27
|
+
@expiration = DateTime.parse(@last_response['expires'])
|
28
|
+
@id = @last_response['id']
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
def expired?
|
33
|
+
(@expiration - DateTime.now) < 0
|
34
|
+
end
|
35
|
+
|
36
|
+
def active?
|
37
|
+
not expired?
|
38
|
+
end
|
39
|
+
|
40
|
+
alias_method :valid?, :active?
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def get_response
|
45
|
+
query = "account_id=#{@account_id}"
|
46
|
+
begin
|
47
|
+
response = Net::HTTP.start(PrintStuffMail::BASE_URL,80) do |http|
|
48
|
+
http.post("/sessions", query)
|
49
|
+
end
|
50
|
+
JSON.parse(response.body)
|
51
|
+
rescue
|
52
|
+
{}
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'sinatra/base'
|
2
|
+
require 'openssl'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class Letter < Struct.new(:message, :address, :return_address); end
|
6
|
+
|
7
|
+
module Helpers
|
8
|
+
|
9
|
+
@@encrypter = OpenSSL::PKey::RSA.new
|
10
|
+
@@decrypter = OpenSSL::PKey::RSA.new
|
11
|
+
|
12
|
+
def encrypt(payload)
|
13
|
+
CGI.escape @@encrypter.private_encrypt(params.to_json)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decrypt(payload)
|
17
|
+
JSON.parse @@encrypter.public_decrypt(CGI.unescape(payload))
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class FakePSM < Sinatra::Base
|
23
|
+
|
24
|
+
# may be extend
|
25
|
+
include Helpers
|
26
|
+
|
27
|
+
post '/psm/sessions' do
|
28
|
+
halt 400 unless params[:account_id] == 'john_smith'
|
29
|
+
{ :status => 201,
|
30
|
+
:id => 'abcde',
|
31
|
+
:expires => Time.now + 60*30
|
32
|
+
}.to_json
|
33
|
+
end
|
34
|
+
|
35
|
+
post '/psm/letters/print' do
|
36
|
+
halt 400 unless params[:message] and params[:address] and params[:session] == 'abcde'
|
37
|
+
@letter = Letter.new(params[:message], params[:address], params[:return_address])
|
38
|
+
{ :status => 201,
|
39
|
+
:id => 1,
|
40
|
+
:message => @letter.message,
|
41
|
+
:address => @letter.address,
|
42
|
+
:return_address => @letter.return_address,
|
43
|
+
:state => :processing }.to_json
|
44
|
+
end
|
45
|
+
|
46
|
+
get '/psm/letters/:id' do
|
47
|
+
halt 400 unless params[:token] == 'abcde'
|
48
|
+
halt 404 if @letter.nil? or params[:id].to_i != 1
|
49
|
+
{ :status => 200,
|
50
|
+
:id => 1,
|
51
|
+
:message => @letter.message,
|
52
|
+
:address => @letter.address,
|
53
|
+
:return_address => @letter.return_address,
|
54
|
+
:state => :processing }.to_json
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require File.expand_path( File.dirname(__FILE__) + '/../test_helper')
|
2
|
+
|
3
|
+
require 'psm/session'
|
4
|
+
|
5
|
+
class SessionTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
include PrintStuffMail
|
8
|
+
|
9
|
+
context "A Session" do
|
10
|
+
|
11
|
+
should "raise an exception if it can't get a token when initializing" do
|
12
|
+
assert_raise(RuntimeError) { Session.new 'not_a_valid_account_id' }
|
13
|
+
end
|
14
|
+
|
15
|
+
setup do
|
16
|
+
Artifice.activate_with(FakePSM)
|
17
|
+
@session = Session.new 'john_smith'
|
18
|
+
end
|
19
|
+
|
20
|
+
context "after being initialized" do
|
21
|
+
|
22
|
+
should "measure how long it has left before it needs to renew" do
|
23
|
+
assert @session.time_left > @session.time_left
|
24
|
+
end
|
25
|
+
|
26
|
+
should "be able to tell if its current token has expired" do
|
27
|
+
assert !@session.expired?
|
28
|
+
end
|
29
|
+
|
30
|
+
should "have syntaxtic sugar to check if it's token is still valid" do
|
31
|
+
assert @session.valid?
|
32
|
+
end
|
33
|
+
|
34
|
+
should "be able to renew itself" do
|
35
|
+
old_expiration = @session.expiration
|
36
|
+
sleep 1
|
37
|
+
assert @session.renew!
|
38
|
+
assert @session.expiration > old_expiration
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/test/psm_test.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require File.expand_path( File.dirname(__FILE__) + '/test_helper')
|
2
|
+
|
3
|
+
require 'psm'
|
4
|
+
|
5
|
+
class PrintStuffMailTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
context "PrintStuffMail" do
|
8
|
+
|
9
|
+
setup do
|
10
|
+
Artifice.activate_with(FakePSM)
|
11
|
+
@address = "Google\n1600 Amphitheatre Parkway\nMountain View, CA 94043"
|
12
|
+
@return_address = "Apple\n1 Infinite Loop\nCupertino, CA 95014"
|
13
|
+
@message = "Dear Eric,\nStop stealing my designs!\nLove,\nSteve"
|
14
|
+
end
|
15
|
+
|
16
|
+
should "be referenced by the shorter PSM acronym" do
|
17
|
+
assert_equal PrintStuffMail, PSM
|
18
|
+
end
|
19
|
+
|
20
|
+
context "as a singleton" do
|
21
|
+
|
22
|
+
should "define #mail! (it's a dangerous method)" do
|
23
|
+
assert_respond_to PSM, :mail!
|
24
|
+
end
|
25
|
+
|
26
|
+
should "allow to set an account_id" do
|
27
|
+
assert_respond_to PSM, :account_id
|
28
|
+
assert_respond_to PSM, :account_id=
|
29
|
+
end
|
30
|
+
|
31
|
+
should "raise a SecurityError unless @account_id isn't set" do
|
32
|
+
assert_raise(SecurityError) { PSM.mail!(@message, @address) {} }
|
33
|
+
end
|
34
|
+
|
35
|
+
context "with a set account_id" do
|
36
|
+
|
37
|
+
setup do
|
38
|
+
PSM.account_id = 'john_smith'
|
39
|
+
end
|
40
|
+
|
41
|
+
should "raise a SecurityError unless a 'confirm block' is passed (it's a really dangerous method)" do
|
42
|
+
assert_raise(SecurityError) { PSM.mail!(@message, @address) {} }
|
43
|
+
assert_nothing_raised { PSM.mail!(@message, @address) {|c| c.confirm! } }
|
44
|
+
end
|
45
|
+
|
46
|
+
should "raise an ArgumentError unless @message and @address are specified" do
|
47
|
+
assert_raise(ArgumentError) { PSM.mail! }
|
48
|
+
assert_raise(ArgumentError) { PSM.mail!(@message) }
|
49
|
+
assert_nothing_raised { PSM.mail!(@message, @address, &:confirm!) }
|
50
|
+
end
|
51
|
+
|
52
|
+
should "accept an optional @return_address" do
|
53
|
+
assert_nothing_raised do
|
54
|
+
PSM.mail!(@message, @address, @return_address, &:confirm!)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
context "as a module to be #include-d" do
|
63
|
+
|
64
|
+
setup do
|
65
|
+
class Tempfile; include PSM end
|
66
|
+
@tempfile = Tempfile.new
|
67
|
+
end
|
68
|
+
|
69
|
+
should "define #mail! in the including class' instances" do
|
70
|
+
assert_respond_to @tempfile, :mail!
|
71
|
+
end
|
72
|
+
|
73
|
+
should "raise an ArgumentError unless a @message is passed in" do
|
74
|
+
assert_raise(ArgumentError) { @tempfile.mail! }
|
75
|
+
assert_raise(NoMethodError) { @tempfile.mail!(@message) }
|
76
|
+
end
|
77
|
+
|
78
|
+
should "raise from within #mail! if #address isn't defined" do
|
79
|
+
assert !defined? @tempfile.address
|
80
|
+
assert_raise(NoMethodError) { @tempfile.mail!(@message) }
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
context "when accessing the webservice" do
|
86
|
+
|
87
|
+
should "post letters" do
|
88
|
+
letter = PSM.mail!(@message, @address, @return_address, &:confirm!)
|
89
|
+
assert_equal 'processing', letter['state']
|
90
|
+
end
|
91
|
+
|
92
|
+
should_eventually "be able to update a letter's status" do
|
93
|
+
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
|
98
|
+
teardown do
|
99
|
+
Artifice.deactivate
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: psm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- ecin
|
13
|
+
- daicoden
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-04-26 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: addressable
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 2
|
30
|
+
- 1
|
31
|
+
- 1
|
32
|
+
version: 2.1.1
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: json
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
segments:
|
43
|
+
- 1
|
44
|
+
- 2
|
45
|
+
- 3
|
46
|
+
version: 1.2.3
|
47
|
+
type: :runtime
|
48
|
+
version_requirements: *id002
|
49
|
+
- !ruby/object:Gem::Dependency
|
50
|
+
name: shoulda
|
51
|
+
prerelease: false
|
52
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
segments:
|
57
|
+
- 2
|
58
|
+
- 10
|
59
|
+
- 3
|
60
|
+
version: 2.10.3
|
61
|
+
type: :development
|
62
|
+
version_requirements: *id003
|
63
|
+
- !ruby/object:Gem::Dependency
|
64
|
+
name: artifice
|
65
|
+
prerelease: false
|
66
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
segments:
|
71
|
+
- 0
|
72
|
+
- 5
|
73
|
+
version: "0.5"
|
74
|
+
type: :development
|
75
|
+
version_requirements: *id004
|
76
|
+
description: Print Stuff Mail provides an RESTful API for sending snail mail. This library eases interfacing with it.
|
77
|
+
email: "@copypastel.com"
|
78
|
+
executables: []
|
79
|
+
|
80
|
+
extensions: []
|
81
|
+
|
82
|
+
extra_rdoc_files:
|
83
|
+
- README.textile
|
84
|
+
files:
|
85
|
+
- .gitignore
|
86
|
+
- README.textile
|
87
|
+
- VERSION
|
88
|
+
- lib/psm.rb
|
89
|
+
- lib/psm/session.rb
|
90
|
+
- test/fake_service.rb
|
91
|
+
- test/psm/session_test.rb
|
92
|
+
- test/psm_test.rb
|
93
|
+
- test/test_helper.rb
|
94
|
+
has_rdoc: true
|
95
|
+
homepage: http://github.com/copypastel/print_stuff_mail
|
96
|
+
licenses: []
|
97
|
+
|
98
|
+
post_install_message:
|
99
|
+
rdoc_options:
|
100
|
+
- --charset=UTF-8
|
101
|
+
require_paths:
|
102
|
+
- lib
|
103
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
104
|
+
requirements:
|
105
|
+
- - ">="
|
106
|
+
- !ruby/object:Gem::Version
|
107
|
+
segments:
|
108
|
+
- 0
|
109
|
+
version: "0"
|
110
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
111
|
+
requirements:
|
112
|
+
- - ">="
|
113
|
+
- !ruby/object:Gem::Version
|
114
|
+
segments:
|
115
|
+
- 0
|
116
|
+
version: "0"
|
117
|
+
requirements: []
|
118
|
+
|
119
|
+
rubyforge_project:
|
120
|
+
rubygems_version: 1.3.6
|
121
|
+
signing_key:
|
122
|
+
specification_version: 3
|
123
|
+
summary: Client library for Print Stuff Mail, the snail mail webservice.
|
124
|
+
test_files:
|
125
|
+
- test/fake_service.rb
|
126
|
+
- test/psm/session_test.rb
|
127
|
+
- test/psm_test.rb
|
128
|
+
- test/test_helper.rb
|