psm 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|