action_kit_api 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 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