action_kit_api 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ credentials.rb
data/.rvmrc ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+
3
+ environment_id="ruby-1.8.7-p357@actionkitapi"
4
+
5
+ if [[ -d "${rvm_path:-$HOME/.rvm}/environments" && -s "${rvm_path:-$HOME/.rvm}/environments/$environment_id" ]]; then
6
+ \. "${rvm_path:-$HOME/.rvm}/environments/$environment_id"
7
+
8
+ if [[ -s "${rvm_path:-$HOME/.rvm}/hooks/after_use" ]]; then
9
+ . "${rvm_path:-$HOME/.rvm}/hooks/after_use"
10
+ fi
11
+ else
12
+ if ! rvm --create "$environment_id"; then
13
+ echo "Failed to create RVM environment '${environment_id}'."
14
+ return 1
15
+ fi
16
+ fi
17
+
18
+ if [[ $- == *i* ]]; then
19
+ echo "Using: $(tput setaf 2)$GEM_HOME$(tput sgr0)"a
20
+ else
21
+ echo "Using: $GEM_HOME"
22
+ fi
23
+
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2012 Democracy For America
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
4
+ this software and associated documentation files (the "Software"), to deal in
5
+ the Software without restriction, including without limitation the rights to
6
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
7
+ of the Software, and to permit persons to whom the Software is furnished to do
8
+ so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,18 @@
1
+ ## Post-clone Instructions
2
+ To use the spec and cucumber feature tests you need to have valid ActionKit
3
+ API credentials. These credentials need to be places in the file
4
+ /features/support/credentials.rb for RSpec and Cucumber to call. These calls
5
+ will create test users, and eventually pages, sign users up for things and
6
+ otherwise affect potentially live data.
7
+
8
+ ***Please be aware of what the tests are doing before running them.***
9
+
10
+ Now that you've read the warning this is what the contents of the credentials
11
+ file should look like.
12
+
13
+ # These are credentials that need to be set for testing against
14
+ # live ActionKit. These will never live in the repository
15
+
16
+ ACTIONKIT_HOSTNAME=""
17
+ ACTIONKIT_USERNAME=""
18
+ ACTIONKIT_PASSWORD=""
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "action_kit_api/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "action_kit_api"
7
+ s.version = ActionKitApi::VERSION
8
+ s.authors = ["Sam Stelfox"]
9
+ s.email = ["sstelfox@democracyforamerica.com"]
10
+ s.homepage = "http://democracyforamerica.com/"
11
+ s.license = "MIT"
12
+
13
+ s.summary = "Wrapper for the ActionKit API"
14
+ s.description = "Provides encapsulated object access through the ActionKit API"
15
+
16
+ s.rubyforge_project = "action_kit_api"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+
23
+ s.add_development_dependency "rspec"
24
+ s.add_development_dependency "cucumber"
25
+ end
@@ -0,0 +1,11 @@
1
+ Feature: Create a user
2
+ In order to add a user to a mailing lists
3
+ As a developer
4
+ I should be able to create a user through the API
5
+
6
+ Scenario: Create a user
7
+ Given I have instantiated the API
8
+ When I create a user with the email "tuser@example.org", first name "Test", last name "User", and zipcode "12345"
9
+ And I search for user email "tuser@example.org"
10
+ Then I should have a valid user
11
+
@@ -0,0 +1,14 @@
1
+ Feature: Find a page
2
+ In order to act on page
3
+ As a developer
4
+ I should be able to find a page
5
+
6
+ Scenario: Find a page by name
7
+ Given I have instantiated the API
8
+ When I search for page name "jstreetdonation"
9
+ Then I should have a valid page
10
+
11
+ Scenario: Find a page by id
12
+ Given I have instantiated the API
13
+ When I search for page ID "1"
14
+ Then I should have a valid page
@@ -0,0 +1,15 @@
1
+ Feature: Find a user
2
+ In order to update a user
3
+ As a developer
4
+ I should be able to find a user
5
+
6
+ Scenario: Find a user by email
7
+ Given I have instantiated the API
8
+ When I search for user email "tuser@example.org"
9
+ Then I should have a valid user
10
+
11
+ Scenario: Find a user by ID
12
+ Given I have instantiated the API
13
+ When I search for user ID "39221"
14
+ Then I should have a valid user
15
+
@@ -0,0 +1,6 @@
1
+ Given /^I have instantiated the API$/ do
2
+ # Uses information provided in credential file to setup the API's connection
3
+ ActionKitApi::Connection.connect(ACTIONKIT_USERNAME,
4
+ ACTIONKIT_PASSWORD, ACTIONKIT_HOSTNAME)
5
+ end
6
+
@@ -0,0 +1,11 @@
1
+ When /^I search for page name "([^"]*)"$/ do |name|
2
+ @page = ActionKitApi::Page.find_by_name(name)
3
+ end
4
+
5
+ When /^I search for page ID "([^"]*)"$/ do |id|
6
+ @page = ActionKitApi::Page.find_by_id(id)
7
+ end
8
+
9
+ Then /^I should have a valid page$/ do
10
+ @page.should be_valid
11
+ end
@@ -0,0 +1,25 @@
1
+ When /^I create a user with the email "([^"]*)", first name "([^"]*)", last name "([^"]*)", and zipcode "([^"]*)"$/ do |email, first_name, last_name, zip|
2
+ attrs = {
3
+ :email => email,
4
+ :first_name => first_name,
5
+ :last_name => last_name,
6
+ :zip => :zip
7
+ }
8
+
9
+ @user = ActionKitApi::User.new(attrs)
10
+ @user.save
11
+ end
12
+
13
+ When /^I search for user email "([^"]*)"$/ do |email|
14
+ @user = ActionKitApi::User.find_by_email(email)
15
+ end
16
+
17
+ When /^I search for user ID "([^"]*)"$/ do |id|
18
+ @user = ActionKitApi::User.find_by_id(id)
19
+ end
20
+
21
+ Then /^I should have a valid user$/ do
22
+ @user.should be_valid
23
+ end
24
+
25
+
@@ -0,0 +1,3 @@
1
+ $:.push File.expand_path("../../../lib", __FILE__)
2
+ require "action_kit_api"
3
+
@@ -0,0 +1,14 @@
1
+ # These are the primary portions of the API wrapper runtime
2
+ require "action_kit_api/connection"
3
+ require "action_kit_api/searchable"
4
+ require "action_kit_api/data_model"
5
+
6
+ # Extensions to the data model for specific information
7
+ require "action_kit_api/user"
8
+ require "action_kit_api/page"
9
+
10
+ # Page types
11
+ require "action_kit_api/page_types/petition"
12
+ require "action_kit_api/page_types/signup"
13
+ require "action_kit_api/page_types/unsubscribe"
14
+ require "action_kit_api/page_types/import"
@@ -0,0 +1,41 @@
1
+ require 'action_kit_api'
2
+
3
+ module ActionKitApi
4
+ class Action < ApiDataModel
5
+ include Searchable
6
+
7
+ # Required
8
+ attr_accessor :id, :page, :user
9
+
10
+ # Other/Active
11
+ attr_accessor :created_at, :created_user, :custom_fields, :link, :mailing,
12
+ :opq_id, :referring_mailing, :referring_user, :source, :status,
13
+ :subscribed_user, :taf_emails_sent, :updated_at
14
+
15
+ def initialize(*args)
16
+ @required_attrs = [:page, :user]
17
+ super
18
+ end
19
+
20
+ # Takes a page object and a user object and a hash of any additional
21
+ # attributes and records the action. Will return a Action object with
22
+ # status information and unique identifiers
23
+ def act(page, user, args = {})
24
+ return false unless page.valid? and user.valid?
25
+
26
+ # Ensure we have an ActionKit ID before performing the action
27
+ user.save if user.akid.nil?
28
+
29
+ # Include the supplied arguments overiding akid and page name if they
30
+ # were supplied with those in the page and user
31
+ act_attrs = args.update({
32
+ "page" => page.name,
33
+ "akid" => user.akid,
34
+ })
35
+
36
+ response = ActionKitApi::Connection.call('act', act_attrs)
37
+
38
+ self.new(response)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,167 @@
1
+ Certificate:
2
+ Data:
3
+ Version: 3 (0x2)
4
+ Serial Number: 145105 (0x236d1)
5
+ Signature Algorithm: sha1WithRSAEncryption
6
+ Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
7
+ Validity
8
+ Not Before: Feb 19 22:45:05 2010 GMT
9
+ Not After : Feb 18 22:45:05 2020 GMT
10
+ Subject: C=US, O=GeoTrust, Inc., CN=RapidSSL CA
11
+ Subject Public Key Info:
12
+ Public Key Algorithm: rsaEncryption
13
+ Public-Key: (2048 bit)
14
+ Modulus:
15
+ 00:c7:71:f8:56:c7:1e:d9:cc:b5:ad:f6:b4:97:a3:
16
+ fb:a1:e6:0b:50:5f:50:aa:3a:da:0f:fc:3d:29:24:
17
+ 43:c6:10:29:c1:fc:55:40:72:ee:bd:ea:df:9f:b6:
18
+ 41:f4:48:4b:c8:6e:fe:4f:57:12:8b:5b:fa:92:dd:
19
+ 5e:e8:ad:f3:f0:1b:b1:7b:4d:fb:cf:fd:d1:e5:f8:
20
+ e3:dc:e7:f5:73:7f:df:01:49:cf:8c:56:c1:bd:37:
21
+ e3:5b:be:b5:4f:8b:8b:f0:da:4f:c7:e3:dd:55:47:
22
+ 69:df:f2:5b:7b:07:4f:3d:e5:ac:21:c1:c8:1d:7a:
23
+ e8:e7:f6:0f:a1:aa:f5:6f:de:a8:65:4f:10:89:9c:
24
+ 03:f3:89:7a:a5:5e:01:72:33:ed:a9:e9:5a:1e:79:
25
+ f3:87:c8:df:c8:c5:fc:37:c8:9a:9a:d7:b8:76:cc:
26
+ b0:3e:e7:fd:e6:54:ea:df:5f:52:41:78:59:57:ad:
27
+ f1:12:d6:7f:bc:d5:9f:70:d3:05:6c:fa:a3:7d:67:
28
+ 58:dd:26:62:1d:31:92:0c:79:79:1c:8e:cf:ca:7b:
29
+ c1:66:af:a8:74:48:fb:8e:82:c2:9e:2c:99:5c:7b:
30
+ 2d:5d:9b:bc:5b:57:9e:7c:3a:7a:13:ad:f2:a3:18:
31
+ 5b:2b:59:0f:cd:5c:3a:eb:68:33:c6:28:1d:82:d1:
32
+ 50:8b
33
+ Exponent: 65537 (0x10001)
34
+ X509v3 extensions:
35
+ X509v3 Key Usage: critical
36
+ Certificate Sign, CRL Sign
37
+ X509v3 Subject Key Identifier:
38
+ 6B:69:3D:6A:18:42:4A:DD:8F:02:65:39:FD:35:24:86:78:91:16:30
39
+ X509v3 Authority Key Identifier:
40
+ keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
41
+
42
+ X509v3 Basic Constraints: critical
43
+ CA:TRUE, pathlen:0
44
+ X509v3 CRL Distribution Points:
45
+
46
+ Full Name:
47
+ URI:http://crl.geotrust.com/crls/gtglobal.crl
48
+
49
+ Authority Information Access:
50
+ OCSP - URI:http://ocsp.geotrust.com
51
+
52
+ Signature Algorithm: sha1WithRSAEncryption
53
+ ab:bc:bc:0a:5d:18:94:e3:c1:b1:c3:a8:4c:55:d6:be:b4:98:
54
+ f1:ee:3c:1c:cd:cf:f3:24:24:5c:96:03:27:58:fc:36:ae:a2:
55
+ 2f:8f:f1:fe:da:2b:02:c3:33:bd:c8:dd:48:22:2b:60:0f:a5:
56
+ 03:10:fd:77:f8:d0:ed:96:67:4f:fd:ea:47:20:70:54:dc:a9:
57
+ 0c:55:7e:e1:96:25:8a:d9:b5:da:57:4a:be:8d:8e:49:43:63:
58
+ a5:6c:4e:27:87:25:eb:5b:6d:fe:a2:7f:38:28:e0:36:ab:ad:
59
+ 39:a5:a5:62:c4:b7:5c:58:2c:aa:5d:01:60:a6:62:67:a3:c0:
60
+ c7:62:23:f4:e7:6c:46:ee:b5:d3:80:6a:22:13:d2:2d:3f:74:
61
+ 4f:ea:af:8c:5f:b4:38:9c:db:ae:ce:af:84:1e:a6:f6:34:51:
62
+ 59:79:d3:e3:75:dc:bc:d7:f3:73:df:92:ec:d2:20:59:6f:9c:
63
+ fb:95:f8:92:76:18:0a:7c:0f:2c:a6:ca:de:8a:62:7b:d8:f3:
64
+ ce:5f:68:bd:8f:3e:c1:74:bb:15:72:3a:16:83:a9:0b:e6:4d:
65
+ 99:9c:d8:57:ec:a8:01:51:c7:6f:57:34:5e:ab:4a:2c:42:f6:
66
+ 4f:1c:89:78:de:26:4e:f5:6f:93:4c:15:6b:27:56:4d:00:54:
67
+ 6c:7a:b7:b7
68
+ -----BEGIN CERTIFICATE-----
69
+ MIID1TCCAr2gAwIBAgIDAjbRMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
70
+ MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
71
+ YWwgQ0EwHhcNMTAwMjE5MjI0NTA1WhcNMjAwMjE4MjI0NTA1WjA8MQswCQYDVQQG
72
+ EwJVUzEXMBUGA1UEChMOR2VvVHJ1c3QsIEluYy4xFDASBgNVBAMTC1JhcGlkU1NM
73
+ IENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAx3H4Vsce2cy1rfa0
74
+ l6P7oeYLUF9QqjraD/w9KSRDxhApwfxVQHLuverfn7ZB9EhLyG7+T1cSi1v6kt1e
75
+ 6K3z8Buxe037z/3R5fjj3Of1c3/fAUnPjFbBvTfjW761T4uL8NpPx+PdVUdp3/Jb
76
+ ewdPPeWsIcHIHXro5/YPoar1b96oZU8QiZwD84l6pV4BcjPtqelaHnnzh8jfyMX8
77
+ N8iamte4dsywPuf95lTq319SQXhZV63xEtZ/vNWfcNMFbPqjfWdY3SZiHTGSDHl5
78
+ HI7PynvBZq+odEj7joLCniyZXHstXZu8W1eefDp6E63yoxhbK1kPzVw662gzxigd
79
+ gtFQiwIDAQABo4HZMIHWMA4GA1UdDwEB/wQEAwIBBjAdBgNVHQ4EFgQUa2k9ahhC
80
+ St2PAmU5/TUkhniRFjAwHwYDVR0jBBgwFoAUwHqYaI2J+6sFZAwRfap9ZbjKzE4w
81
+ EgYDVR0TAQH/BAgwBgEB/wIBADA6BgNVHR8EMzAxMC+gLaArhilodHRwOi8vY3Js
82
+ Lmdlb3RydXN0LmNvbS9jcmxzL2d0Z2xvYmFsLmNybDA0BggrBgEFBQcBAQQoMCYw
83
+ JAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmdlb3RydXN0LmNvbTANBgkqhkiG9w0B
84
+ AQUFAAOCAQEAq7y8Cl0YlOPBscOoTFXWvrSY8e48HM3P8yQkXJYDJ1j8Nq6iL4/x
85
+ /torAsMzvcjdSCIrYA+lAxD9d/jQ7ZZnT/3qRyBwVNypDFV+4ZYlitm12ldKvo2O
86
+ SUNjpWxOJ4cl61tt/qJ/OCjgNqutOaWlYsS3XFgsql0BYKZiZ6PAx2Ij9OdsRu61
87
+ 04BqIhPSLT90T+qvjF+0OJzbrs6vhB6m9jRRWXnT43XcvNfzc9+S7NIgWW+c+5X4
88
+ knYYCnwPLKbK3opie9jzzl9ovY8+wXS7FXI6FoOpC+ZNmZzYV+yoAVHHb1c0XqtK
89
+ LEL2TxyJeN4mTvVvk0wVaydWTQBUbHq3tw==
90
+ -----END CERTIFICATE-----
91
+ Certificate:
92
+ Data:
93
+ Version: 3 (0x2)
94
+ Serial Number: 144470 (0x23456)
95
+ Signature Algorithm: sha1WithRSAEncryption
96
+ Issuer: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
97
+ Validity
98
+ Not Before: May 21 04:00:00 2002 GMT
99
+ Not After : May 21 04:00:00 2022 GMT
100
+ Subject: C=US, O=GeoTrust Inc., CN=GeoTrust Global CA
101
+ Subject Public Key Info:
102
+ Public Key Algorithm: rsaEncryption
103
+ Public-Key: (2048 bit)
104
+ Modulus:
105
+ 00:da:cc:18:63:30:fd:f4:17:23:1a:56:7e:5b:df:
106
+ 3c:6c:38:e4:71:b7:78:91:d4:bc:a1:d8:4c:f8:a8:
107
+ 43:b6:03:e9:4d:21:07:08:88:da:58:2f:66:39:29:
108
+ bd:05:78:8b:9d:38:e8:05:b7:6a:7e:71:a4:e6:c4:
109
+ 60:a6:b0:ef:80:e4:89:28:0f:9e:25:d6:ed:83:f3:
110
+ ad:a6:91:c7:98:c9:42:18:35:14:9d:ad:98:46:92:
111
+ 2e:4f:ca:f1:87:43:c1:16:95:57:2d:50:ef:89:2d:
112
+ 80:7a:57:ad:f2:ee:5f:6b:d2:00:8d:b9:14:f8:14:
113
+ 15:35:d9:c0:46:a3:7b:72:c8:91:bf:c9:55:2b:cd:
114
+ d0:97:3e:9c:26:64:cc:df:ce:83:19:71:ca:4e:e6:
115
+ d4:d5:7b:a9:19:cd:55:de:c8:ec:d2:5e:38:53:e5:
116
+ 5c:4f:8c:2d:fe:50:23:36:fc:66:e6:cb:8e:a4:39:
117
+ 19:00:b7:95:02:39:91:0b:0e:fe:38:2e:d1:1d:05:
118
+ 9a:f6:4d:3e:6f:0f:07:1d:af:2c:1e:8f:60:39:e2:
119
+ fa:36:53:13:39:d4:5e:26:2b:db:3d:a8:14:bd:32:
120
+ eb:18:03:28:52:04:71:e5:ab:33:3d:e1:38:bb:07:
121
+ 36:84:62:9c:79:ea:16:30:f4:5f:c0:2b:e8:71:6b:
122
+ e4:f9
123
+ Exponent: 65537 (0x10001)
124
+ X509v3 extensions:
125
+ X509v3 Basic Constraints: critical
126
+ CA:TRUE
127
+ X509v3 Subject Key Identifier:
128
+ C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
129
+ X509v3 Authority Key Identifier:
130
+ keyid:C0:7A:98:68:8D:89:FB:AB:05:64:0C:11:7D:AA:7D:65:B8:CA:CC:4E
131
+
132
+ Signature Algorithm: sha1WithRSAEncryption
133
+ 35:e3:29:6a:e5:2f:5d:54:8e:29:50:94:9f:99:1a:14:e4:8f:
134
+ 78:2a:62:94:a2:27:67:9e:d0:cf:1a:5e:47:e9:c1:b2:a4:cf:
135
+ dd:41:1a:05:4e:9b:4b:ee:4a:6f:55:52:b3:24:a1:37:0a:eb:
136
+ 64:76:2a:2e:2c:f3:fd:3b:75:90:bf:fa:71:d8:c7:3d:37:d2:
137
+ b5:05:95:62:b9:a6:de:89:3d:36:7b:38:77:48:97:ac:a6:20:
138
+ 8f:2e:a6:c9:0c:c2:b2:99:45:00:c7:ce:11:51:22:22:e0:a5:
139
+ ea:b6:15:48:09:64:ea:5e:4f:74:f7:05:3e:c7:8a:52:0c:db:
140
+ 15:b4:bd:6d:9b:e5:c6:b1:54:68:a9:e3:69:90:b6:9a:a5:0f:
141
+ b8:b9:3f:20:7d:ae:4a:b5:b8:9c:e4:1d:b6:ab:e6:94:a5:c1:
142
+ c7:83:ad:db:f5:27:87:0e:04:6c:d5:ff:dd:a0:5d:ed:87:52:
143
+ b7:2b:15:02:ae:39:a6:6a:74:e9:da:c4:e7:bc:4d:34:1e:a9:
144
+ 5c:4d:33:5f:92:09:2f:88:66:5d:77:97:c7:1d:76:13:a9:d5:
145
+ e5:f1:16:09:11:35:d5:ac:db:24:71:70:2c:98:56:0b:d9:17:
146
+ b4:d1:e3:51:2b:5e:75:e8:d5:d0:dc:4f:34:ed:c2:05:66:80:
147
+ a1:cb:e6:33
148
+ -----BEGIN CERTIFICATE-----
149
+ MIIDVDCCAjygAwIBAgIDAjRWMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
150
+ MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
151
+ YWwgQ0EwHhcNMDIwNTIxMDQwMDAwWhcNMjIwNTIxMDQwMDAwWjBCMQswCQYDVQQG
152
+ EwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMSR2VvVHJ1c3Qg
153
+ R2xvYmFsIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2swYYzD9
154
+ 9BcjGlZ+W988bDjkcbd4kdS8odhM+KhDtgPpTSEHCIjaWC9mOSm9BXiLnTjoBbdq
155
+ fnGk5sRgprDvgOSJKA+eJdbtg/OtppHHmMlCGDUUna2YRpIuT8rxh0PBFpVXLVDv
156
+ iS2Aelet8u5fa9IAjbkU+BQVNdnARqN7csiRv8lVK83Qlz6cJmTM386DGXHKTubU
157
+ 1XupGc1V3sjs0l44U+VcT4wt/lAjNvxm5suOpDkZALeVAjmRCw7+OC7RHQWa9k0+
158
+ bw8HHa8sHo9gOeL6NlMTOdReJivbPagUvTLrGAMoUgRx5aszPeE4uwc2hGKceeoW
159
+ MPRfwCvocWvk+QIDAQABo1MwUTAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTA
160
+ ephojYn7qwVkDBF9qn1luMrMTjAfBgNVHSMEGDAWgBTAephojYn7qwVkDBF9qn1l
161
+ uMrMTjANBgkqhkiG9w0BAQUFAAOCAQEANeMpauUvXVSOKVCUn5kaFOSPeCpilKIn
162
+ Z57QzxpeR+nBsqTP3UEaBU6bS+5Kb1VSsyShNwrrZHYqLizz/Tt1kL/6cdjHPTfS
163
+ tQWVYrmm3ok9Nns4d0iXrKYgjy6myQzCsplFAMfOEVEiIuCl6rYVSAlk6l5PdPcF
164
+ PseKUgzbFbS9bZvlxrFUaKnjaZC2mqUPuLk/IH2uSrW4nOQdtqvmlKXBx4Ot2/Un
165
+ hw4EbNX/3aBd7YdStysVAq45pmp06drE57xNNB6pXE0zX5IJL4hmXXeXxx12E6nV
166
+ 5fEWCRE11azbJHFwLJhWC9kXtNHjUStedejV0NxPNO3CBWaAocvmMw==
167
+ -----END CERTIFICATE-----
@@ -0,0 +1,50 @@
1
+ require 'xmlrpc/client'
2
+
3
+ # This file adds the connection information for the ActionKit API
4
+ # which is used throughout the rest of the Gem (a connection being
5
+ # required for ... well every operation ... this is a remote API
6
+ # gem :)
7
+
8
+ module ActionKitApi
9
+ class NoConnection < Exception
10
+ # Nothing special, will be raised in the event something
11
+ # trys to use a connection and one doesn't exist
12
+ end
13
+
14
+ class Connection
15
+ # This was need to prevent empty responses from the remote
16
+ # server from triggering an exception on calls
17
+ XMLRPC::Config::ENABLE_NIL_PARSER = true
18
+
19
+ @@connection = nil
20
+
21
+ def self.connect(user, password, host)
22
+ # Build the arguments for the XMLRPC::Client object
23
+ conn_args = {
24
+ :user => user,
25
+ :password => password,
26
+ :host => host,
27
+ :use_ssl => true,
28
+ :path => "/api"
29
+ }
30
+
31
+ @@connection = XMLRPC::Client.new_from_hash(conn_args)
32
+
33
+ # Unfortunately XMLRPC::Client does not expose it's SSL settings so
34
+ # we need to dive into it's instance methods to set SSL verification
35
+ # and add a CA to authenticate against
36
+ @@connection.instance_variable_get("@http").verify_mode = OpenSSL::SSL::VERIFY_PEER
37
+ @@connection.instance_variable_get("@http").ca_file = File.join(File.dirname(__FILE__), "actionkit_ca_chain.pem")
38
+ end
39
+
40
+ def self.call(command, args = {})
41
+ raise NoConnection if @@connection.nil?
42
+
43
+ @@connection.call(command, args)
44
+ end
45
+
46
+ def self.version
47
+ self.call("version")
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,64 @@
1
+ require 'action_kit_api'
2
+
3
+ module ActionKitApi
4
+ class MissingRequiredAttributeException < Exception
5
+ end
6
+
7
+ class ApiDataModel
8
+
9
+ def initialize(hash = {})
10
+ @required_attrs = []
11
+
12
+ self.update(hash)
13
+ end
14
+
15
+ def save
16
+ class_name = self.class.to_s.split("::").last
17
+
18
+ raise MissingRequiredAttributeException "Unable to save incomplete object" unless self.valid?
19
+
20
+ response = ActionKitApi::Connection.call("#{class_name}.save_or_create", self.to_hash)
21
+
22
+ # Update ourselves to include the data that the server populated
23
+ self.update(response)
24
+ end
25
+
26
+ # Updates all the instance variables in our local object with
27
+ # the values in the hash. This will selectively update only the
28
+ # keys in the hash that is passed, and will not update/add non-existant
29
+ # attributes
30
+ def update(hash = {})
31
+ hash.each do |k,v|
32
+ # The name of the setter for the value of k
33
+ setter = "#{k}="
34
+
35
+ # Check if there is a matching setter
36
+ if self.respond_to?(setter)
37
+ # Yes, there is. Call the setter with the value
38
+ self.send(setter, v)
39
+ end
40
+ end
41
+ end
42
+
43
+ def valid?
44
+ @required_attrs.each do |k|
45
+ return false if self.send(k).nil?
46
+ end
47
+
48
+ true
49
+ end
50
+
51
+ def to_hash
52
+ user_hash = {}
53
+
54
+ self.instance_variables.each do |iv|
55
+ key = iv.delete("@").to_sym
56
+ next if key == :required_attrs
57
+ user_hash[key] = self.instance_variable_get(iv)
58
+ end
59
+
60
+ user_hash
61
+ end
62
+ end
63
+ end
64
+
@@ -0,0 +1,24 @@
1
+ require 'action_kit_api'
2
+
3
+ module ActionKitApi
4
+ # Please note that this class should almost never be directly called
5
+ # every 'real' page is a specific sub-page type that should extend this
6
+ # class. Please refer to the ActionKit API documentation for more
7
+ # information about pages
8
+ class Page < ApiDataModel
9
+ include Searchable
10
+
11
+ # Required
12
+ attr_accessor :id, :name, :title
13
+ attr_reader :type # Page type can't change
14
+
15
+ # Other/Active
16
+ attr_accessor :goal, :goal_type, :lang, :list, :required_fields,
17
+ :status, :tags, :url
18
+
19
+ def initialize(*args)
20
+ @required_attrs = [:name, :title, :type]
21
+ super
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ #require 'action_kit_api'
2
+
3
+ # This page is a bit more complicated and might be the one that we want
4
+ # unfotunately, I need to read through ActionKitApi's "Importing User Data"
5
+ # notes before implementing this page type
6
+
7
+ #module ActionKitApi
8
+ # class ImportPage < Page
9
+ # def initialize(*args)
10
+ # @type = 'import'
11
+ # super
12
+ # end
13
+ # end
14
+ #end
@@ -0,0 +1,10 @@
1
+ require 'action_kit_api'
2
+
3
+ module ActionKitApi
4
+ class PetitionPage < Page
5
+ def initialize(*args)
6
+ @type = 'petition'
7
+ super
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'action_kit_api'
2
+
3
+ module ActionKitApi
4
+ class SignupPage < Page
5
+ def initialize(*args)
6
+ @type = 'signup'
7
+ super
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ require 'action_kit_api'
2
+
3
+ module ActionKitApi
4
+ class UnsubscribePage < Page
5
+ def initialize(*args)
6
+ @type = 'unsubscribe'
7
+ super
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,84 @@
1
+ module ActionKitApi
2
+ module Searchable
3
+ # This layer is needed to have the methods added at a class level
4
+ # rather than an instance level
5
+ def self.included(class_name)
6
+
7
+ # Override's method missing to dynamically add find_by_* and
8
+ # find_all_by_*.
9
+ def class_name.method_missing(method, *args, &block)
10
+ case method.to_s
11
+ when /^find_by_(.+)$/
12
+ run_find_by_method($1, *args)
13
+ when /^find_all_by_(.+)$/
14
+ run_find_all_by_method($1, *args, &block)
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ # Perform an API search for an individual object
21
+ def class_name.run_find_by_method(attrs, *args)
22
+ # Get the name of the class that this module has been mixed into
23
+ class_name = self.to_s.split("::").last
24
+
25
+ search = parse_attributes(attrs, args)
26
+ response = ActionKitApi::Connection.call("#{class_name}.get", search)
27
+
28
+ # Build a new object with the response, this might be an empty
29
+ # object if the search failed or was invalid
30
+ self.new(response)
31
+ end
32
+
33
+ # Perform an API search for all objects that match the criteria, remote
34
+ # API limit is 1000 results per request so this is added explicitely to
35
+ # the call to prevent errors, in the future this request probably could
36
+ # be paginated in a loop and using count to get all of the results that
37
+ # match.
38
+ def class_name.run_find_all_by_method(attrs, *args, &block)
39
+ # Get the name of the class that this module has been mixed into
40
+ class_name = self.to_s.split("::").last
41
+
42
+ search = parse_attributes(attrs, args)
43
+
44
+ # For when I get to retrieving all results, through pagination
45
+ # Would be a good candidate for yielding (probably in batches of 50-100)
46
+ #response_count = ActionKitApi::Connection.call("#{class_name}.count", search)
47
+ search["limit"] = 1000
48
+ #search["offset"] = 0
49
+
50
+ response = ActionKitApi::Connection.call("#{class_name}.get", search)
51
+
52
+ response_objects = []
53
+ response.each do |obj|
54
+ response_objects << self.new(obj)
55
+ end
56
+
57
+ response_objects
58
+ end
59
+
60
+ # Helper method for method_missing and the find_* virtual methods that
61
+ # parses part of a method name and arguments into a hash usuable for the
62
+ # API calls
63
+ def class_name.parse_attributes(attrs, args)
64
+ attrs = attrs.split('_and_')
65
+ attrs_with_args = attrs.zip(args)
66
+ Hash[attrs_with_args]
67
+ end
68
+
69
+ # Provide a means to check if the searchable module can respond to
70
+ # virtual methods, could be more accurate by checking to ensure the
71
+ # attributes exist
72
+ def class_name.respond_to?(method)
73
+ case method.to_s
74
+ when /^find_by_(.*)$/
75
+ true
76
+ when /^find_all_by_(.*)$/
77
+ true
78
+ else
79
+ super
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,41 @@
1
+ require 'action_kit_api'
2
+
3
+ module ActionKitApi
4
+ class User < ApiDataModel
5
+ include Searchable
6
+
7
+ # Required
8
+ attr_accessor :id, :email, :first_name, :last_name, :zip
9
+
10
+ # Other/Active
11
+ attr_accessor :address1, :address2, :akid, :city, :country, :created_at,
12
+ :custom_fields, :lang, :middle_name, :password, :plus4,
13
+ :postal, :prefix, :rand_id, :region, :state_name, :source,
14
+ :state, :subscription_status, :suffix, :token, :updated_at
15
+
16
+ def initialize(*args)
17
+ @required_attrs = [:email, :first_name, :last_name, :zip]
18
+ super
19
+ end
20
+
21
+ # Below are methods that haven't been completed yet, but shouldn't
22
+ # be difficult the functionality is all there in the API, they just weren't
23
+ # important for us to use RIGHT NOW
24
+
25
+ #def subscribed?(list_id = nil)
26
+ #end
27
+
28
+ #def subscriptions
29
+ #end
30
+
31
+ #def subscription_history
32
+ #end
33
+
34
+ #def unsubscribe(list_id)
35
+ #end
36
+
37
+ #def unsubscribe_all
38
+ #end
39
+ end
40
+ end
41
+
@@ -0,0 +1,3 @@
1
+ module ActionKitApi
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,24 @@
1
+ require "spec_helper.rb"
2
+
3
+ describe "ActionKitApi Connection" do
4
+ it "should be able to create a new connection" do
5
+ ActionKitApi::Connection.connect('testuser', 'testpass',
6
+ 'host.example.local')
7
+ end
8
+
9
+ describe "Tests That Need a Connection", :remote => true do
10
+ before(:all) do
11
+ ActionKitApi::Connection.connect(ACTIONKIT_USERNAME,
12
+ ACTIONKIT_PASSWORD, ACTIONKIT_HOSTNAME)
13
+ end
14
+
15
+ it "should be able to ask ActionKit for the version of it's API" do
16
+ ActionKitApi::Connection.version.should =~ /ActionKit \d\.\d\.\d+/
17
+ end
18
+
19
+ it "should be able to make arbitrary calls to the remote API" do
20
+ ActionKitApi::Connection.call("version").should =~ /ActionKit \d\.\d\.\d+/
21
+ end
22
+ end
23
+ end
24
+
@@ -0,0 +1,26 @@
1
+ require "spec_helper"
2
+
3
+ # These are tricky because they don't really verify much on our end and use
4
+ # command line utilities to do the verification, however if we aren't checking
5
+ # this if ActionKit changes their server certificate it will break our gem and
6
+ # need to be fixed by supplying new Certificate Authority information.
7
+
8
+ # These tests are not run by default. To run them add the --tag @certificates
9
+
10
+ describe "Remote API Server Certificate", :certificates => true do
11
+ it "should be valid using system's certificates" do
12
+ command_output = `echo "" |
13
+ openssl s_client -connect dfa.actionkit.com:443 2>&1 |
14
+ grep "Verify return code"`
15
+ command_output =~ /[a-zA-Z]+: ([0-9]+)/
16
+ $1.to_i.should == 0
17
+ end
18
+
19
+ it "should be valid using this gem's certificates" do
20
+ command_output = `echo "" |
21
+ openssl s_client -CAfile lib/action_kit_api/actionkit_ca_chain.pem -connect dfa.actionkit.com:443 2>&1 |
22
+ grep "Verify return code"`
23
+ command_output =~ /[a-zA-Z]+: ([0-9]+)/
24
+ $1.to_i.should == 0
25
+ end
26
+ end
@@ -0,0 +1,10 @@
1
+ require 'rspec'
2
+ require 'action_kit_api'
3
+ require 'features/support/credentials'
4
+
5
+ RSpec.configure do |config|
6
+ config.filter_run_excluding :certificates => true, :remote => true
7
+
8
+ config.color_enabled = true
9
+ config.formatter = 'documentation'
10
+ end
metadata ADDED
@@ -0,0 +1,124 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: action_kit_api
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
+ - Sam Stelfox
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2012-02-17 00:00:00 -05:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: rspec
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 0
29
+ version: "0"
30
+ type: :development
31
+ version_requirements: *id001
32
+ - !ruby/object:Gem::Dependency
33
+ name: cucumber
34
+ prerelease: false
35
+ requirement: &id002 !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ segments:
40
+ - 0
41
+ version: "0"
42
+ type: :development
43
+ version_requirements: *id002
44
+ description: Provides encapsulated object access through the ActionKit API
45
+ email:
46
+ - sstelfox@democracyforamerica.com
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files: []
52
+
53
+ files:
54
+ - .gitignore
55
+ - .rvmrc
56
+ - Gemfile
57
+ - LICENSE
58
+ - README.md
59
+ - Rakefile
60
+ - action_kit_api.gemspec
61
+ - features/create_a_user.feature
62
+ - features/find_a_page.feature
63
+ - features/find_a_user.feature
64
+ - features/step_definitions/general_api_steps.rb
65
+ - features/step_definitions/page_steps.rb
66
+ - features/step_definitions/user_steps.rb
67
+ - features/support/env.rb
68
+ - lib/action_kit_api.rb
69
+ - lib/action_kit_api/action.rb
70
+ - lib/action_kit_api/actionkit_ca_chain.pem
71
+ - lib/action_kit_api/connection.rb
72
+ - lib/action_kit_api/data_model.rb
73
+ - lib/action_kit_api/page.rb
74
+ - lib/action_kit_api/page_types/import.rb
75
+ - lib/action_kit_api/page_types/petition.rb
76
+ - lib/action_kit_api/page_types/signup.rb
77
+ - lib/action_kit_api/page_types/unsubscribe.rb
78
+ - lib/action_kit_api/searchable.rb
79
+ - lib/action_kit_api/user.rb
80
+ - lib/action_kit_api/version.rb
81
+ - spec/connections/action_kit_api_connection_spec.rb
82
+ - spec/remote_certificates_spec.rb
83
+ - spec/spec_helper.rb
84
+ has_rdoc: true
85
+ homepage: http://democracyforamerica.com/
86
+ licenses:
87
+ - MIT
88
+ post_install_message:
89
+ rdoc_options: []
90
+
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ segments:
98
+ - 0
99
+ version: "0"
100
+ required_rubygems_version: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ segments:
105
+ - 0
106
+ version: "0"
107
+ requirements: []
108
+
109
+ rubyforge_project: action_kit_api
110
+ rubygems_version: 1.3.6
111
+ signing_key:
112
+ specification_version: 3
113
+ summary: Wrapper for the ActionKit API
114
+ test_files:
115
+ - features/create_a_user.feature
116
+ - features/find_a_page.feature
117
+ - features/find_a_user.feature
118
+ - features/step_definitions/general_api_steps.rb
119
+ - features/step_definitions/page_steps.rb
120
+ - features/step_definitions/user_steps.rb
121
+ - features/support/env.rb
122
+ - spec/connections/action_kit_api_connection_spec.rb
123
+ - spec/remote_certificates_spec.rb
124
+ - spec/spec_helper.rb