ketchup 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENCE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Paul Campbell, Pat Allan
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,123 @@
1
+ h1. Ketchup
2
+
3
+ This is the official Ruby interface to the API for "Ketchup":http://www.useketchup.com: A place for all your meeting notes.
4
+
5
+ h2. Installation
6
+
7
+ <pre><code>gem install ketchup</code></pre>
8
+
9
+ h2. Usage
10
+
11
+ Everything in Ketchup comes down to the user you're logged in as, and this library reflects that - you create a new connection and manage everything through a profile object:
12
+
13
+ <pre><code>profile = Ketchup.authenticate('user@domain.com', 'password')</code></pre>
14
+
15
+ None of the usage is particularly complex, but if you're confused with any of the notes below, either read "the documentation":http://rdoc.info/projects/hypertiny/ketchup or "the sauce":http://github.com/hypertiny/ketchup.
16
+
17
+ h3. Meetings
18
+
19
+ A profile object provides access to all your meetings simply enough:
20
+
21
+ <pre><code>profile.meetings.each do |meeting|
22
+ puts meeting.title
23
+ end
24
+ meeting = profile.meetings.create 'title' => 'Important Discussion'
25
+ meeting.title = 'Nothing Important'
26
+ meeting.save</code></pre>
27
+
28
+ All of a meeting's details are accesible through these meeting objects.
29
+
30
+ <pre><code>meeting.location #=> 'Abbey Road'
31
+ meeting.attendees #=> 'John, Paul, George and Ringo'</code></pre>
32
+
33
+ Meeting dates are parsed from natural language, just like in Ketchup's web interface.
34
+
35
+ <pre><code>meeting.date = 'Tomorrow at 4pm'</code></pre>
36
+
37
+ You can also filter meetings by the same categories as the web interface:
38
+
39
+ <pre><code>profile.meetings.today
40
+ profile.meetings.upcoming
41
+ profile.meetings.previous</code></pre>
42
+
43
+ h3. Items
44
+
45
+ Agenda items can be manipulated for each meeting. They're pretty slim objects through - the only editable attribute is their content.
46
+
47
+ <pre><code>meeting.items.each do |item|
48
+ puts item.content
49
+ end
50
+ item = meeting.items.build 'content' => 'What are we talking about?'
51
+ item.content = 'Minutes from Last Meeting'
52
+ item.save</code></pre>
53
+
54
+ You can also re-order a full set of items for a given meeting:
55
+
56
+ <pre><code>meeting.items.reorder item_b, item_c, item_a</code></pre>
57
+
58
+ h3. Notes
59
+
60
+ Notes are the bullet points under each item, and behave in much the same way. Also, the only piece of information that they have of any interest is the content attribute.
61
+
62
+ <pre><code>item.notes.each do |note|
63
+ puts note.content
64
+ end
65
+ note = item.notes.create 'content' => 'Are we done yet?'
66
+ note.content = 'Next meeting will be shorter'
67
+ note.save</code></pre>
68
+
69
+ Just like with items, you can re-order a full set of notes:
70
+
71
+ <pre><code>item.notes.reorder note_b, note_c, note_a</code></pre>
72
+
73
+ h3. Projects
74
+
75
+ Projects are implicitly created through meetings:
76
+
77
+ <pre><code>profile.meetings.create(
78
+ 'title' => 'Monday',
79
+ 'project_name' => 'Stand ups'
80
+ )</code></pre>
81
+
82
+ You can access these projects through the profile object, and the meetings that are in each of them:
83
+
84
+ <pre><code>profile.projects.each do |project|
85
+ puts project.name
86
+ end
87
+ project = profiles.project.first
88
+ project.meetings.each do |meeting|
89
+ puts meeting.title
90
+ end</code></pre>
91
+
92
+ You can also change the names of projects through these objects:
93
+
94
+ <pre><code>project.name = 'Sit downs'
95
+ project.save</code></pre>
96
+
97
+ h3. User Accounts
98
+
99
+ Your profile object allows you to change the email, name and timezone associated with that account:
100
+
101
+ <pre><code>profile.email = 'baz@foo.com'
102
+ profile.name = 'Bazza'
103
+ profile.timezone = 'Melbourne'
104
+ profile.save</code></pre>
105
+
106
+ You can also change the password, if you so desire:
107
+
108
+ <pre><code>profile.change_password '12345'</code></pre>
109
+
110
+ And there's also the ability to create completely new accounts:
111
+
112
+ <pre><code>Ketchup::Profile.create 'who@first.base.com', 'secret',
113
+ 'name' => 'Who', 'timezone' => 'Dublin'</code></pre>
114
+
115
+ The name and timezone arguments are optional, though - and please note that you don't get a profile object back from that request, but will need to authenticate like normal to get access to that account's meetings.
116
+
117
+ h2. Contribution
118
+
119
+ Fork and patch as you see fit - and please send pull requests if you think it's useful for others. Don't forget to write specs and features first, and don't mess with the version numbers please (or at least: only do so in a different branch).
120
+
121
+ h2. Copyright
122
+
123
+ Copyright (c) 2010 "Pat Allan":http://freelancing-gods.com and "Paul Campbell":http://pabcas.com/, released under an open licence.
@@ -0,0 +1,36 @@
1
+ # Ketchup Ruby API
2
+ #
3
+ # @author Pat Allan
4
+ # @see http://useketchup.com
5
+ #
6
+ module Ketchup
7
+ # An exception thrown when invalid authentication details are provided.
8
+ #
9
+ class AccessDenied < StandardError
10
+ end
11
+
12
+ # Creates a new profile authenticated against the Ketchup API. This is the
13
+ # recommended method to get access to an account's meetings, notes and items.
14
+ #
15
+ # @example
16
+ # profile = Ketchup.authenticate 'foo@bar.com', 'secret'
17
+ #
18
+ # @param [String] email The user's email address
19
+ # @param [String] password The user's passwords
20
+ # @return [Ketchup::Profile] A new profile instance
21
+ # @raise [Ketchup::AccessDenied] when the email or password is incorrect
22
+ #
23
+ def self.authenticate(email, password)
24
+ Ketchup::Profile.load(email, password)
25
+ end
26
+ end
27
+
28
+ require 'ketchup/api'
29
+ require 'ketchup/item'
30
+ require 'ketchup/item_array'
31
+ require 'ketchup/meeting'
32
+ require 'ketchup/meeting_array'
33
+ require 'ketchup/note'
34
+ require 'ketchup/note_array'
35
+ require 'ketchup/profile'
36
+ require 'ketchup/project'
@@ -0,0 +1,80 @@
1
+ require 'httparty'
2
+
3
+ # The direct interface with Ketchup's RESTful API. You should generally not have
4
+ # any need to use this class yourself, unless you're playing around with the
5
+ # internals. That said, it's a simple class - with get, post, put and delete
6
+ # instance methods for matching HTTP requests, that automatically include the
7
+ # access token.
8
+ #
9
+ # For pure access to the API, without any implicit authentication, use the class
10
+ # level methods, which are provided by the mixed-in HTTParty module.
11
+ #
12
+ # @see http://rdoc.info/projects/jnunemaker/httparty HTTParty
13
+ #
14
+ class Ketchup::API
15
+ include HTTParty
16
+ base_uri 'https://useketchup.com/api/v1'
17
+
18
+ attr_reader :access_token
19
+
20
+ # Creates a new API instance for the given profile, as determined by the email
21
+ # and password. This API object can then be used by all other objects to
22
+ # interact with the API without having to re-authenticate.
23
+ #
24
+ # @param [String] email The user's email address
25
+ # @param [String] password The user's password
26
+ # @raise [Ketchup::AccessDenied] if the email or password are incorrect
27
+ #
28
+ def initialize(email, password)
29
+ response = self.class.get '/profile.json', :basic_auth => {
30
+ :username => email,
31
+ :password => password
32
+ }
33
+
34
+ if response == 'Access Denied'
35
+ raise Ketchup::AccessDenied
36
+ else
37
+ @access_token = response['single_access_token']
38
+ end
39
+ end
40
+
41
+ # Execute a GET request to the API server using the stored access token.
42
+ #
43
+ # @param [String] path The URL path
44
+ # @param [Hash] data Parameters for the request
45
+ # @return The web response
46
+ #
47
+ def get(path, data = {})
48
+ self.class.get path, :query => data.merge(:u => access_token)
49
+ end
50
+
51
+ # Execute a POST request to the API server using the stored access token.
52
+ #
53
+ # @param [String] path The URL path
54
+ # @param [Hash] data Parameters for the request
55
+ # @return The web response
56
+ #
57
+ def post(path, data = {})
58
+ self.class.post path, :body => data.merge(:u => access_token)
59
+ end
60
+
61
+ # Execute a PUT request to the API server using the stored access token.
62
+ #
63
+ # @param [String] path The URL path
64
+ # @param [Hash] data Parameters for the request
65
+ # @return The web response
66
+ #
67
+ def put(path, data = {})
68
+ self.class.put path, :body => data.merge(:u => access_token)
69
+ end
70
+
71
+ # Execute a DELETE request to the API server using the stored access token.
72
+ #
73
+ # @param [String] path The URL path
74
+ # @param [Hash] data Parameters for the request
75
+ # @return The web response
76
+ #
77
+ def delete(path, data = {})
78
+ self.class.delete path, :body => data.merge(:u => access_token)
79
+ end
80
+ end
@@ -0,0 +1,75 @@
1
+ # Represents a list/agenda item in a meeting. Only the content is editable.
2
+ #
3
+ # If you want to change the order of items, you can do that using
4
+ # Ketchup::ItemArray (the meeting's item array).
5
+ #
6
+ class Ketchup::Item
7
+ attr_reader :shortcode_url, :updated_at, :created_at, :meeting_id, :notes,
8
+ :position, :meeting, :api
9
+ attr_accessor :content
10
+
11
+ # Create a new item for a given meeting, using an existing api connection. You
12
+ # generally won't want to call this yourself - the better interface to create
13
+ # new items is via a meeting's item array.
14
+ #
15
+ # However, if you are doing some internal hacking, it's worth noting that all
16
+ # keys for parameters in the hash should be strings, not symbols.
17
+ #
18
+ # @example
19
+ # Ketchup::Item.new api, meeting, 'content' => "Treasurer's Report"
20
+ #
21
+ # @param [Ketchup::API] api The API connection
22
+ # @param [Ketchup::Meeting] meeting The meeting the item will belong to
23
+ # @param [Hash] params Any other details for the item.
24
+ # @see Ketchup::ItemArray#build
25
+ # @see Ketchup::ItemArray#create
26
+ #
27
+ def initialize(api, meeting, params = {})
28
+ @api = api
29
+ @meeting = meeting
30
+
31
+ overwrite params
32
+
33
+ @notes = Ketchup::NoteArray.new @api, self, (params['notes'] || [])
34
+ end
35
+
36
+ # Indicates whether the item exists on the server yet.
37
+ #
38
+ # @return [Boolean] True if the item does not exist on the server.
39
+ #
40
+ def new_record?
41
+ shortcode_url.nil?
42
+ end
43
+
44
+ # Saves the item to the server, whether it's a new item or an existing one.
45
+ # This does not save underlying notes.
46
+ #
47
+ def save
48
+ if new_record?
49
+ overwrite @api.post("/meetings/#{meeting.shortcode_url}/items.json",
50
+ 'content' => content)
51
+ else
52
+ overwrite @api.put("/items/#{shortcode_url}.json", 'content' => content)
53
+ end
54
+ end
55
+
56
+ # Deletes the item from the server. If the item has never been saved, this
57
+ # method will do nothing at all.
58
+ #
59
+ def destroy
60
+ return if new_record?
61
+
62
+ @api.delete "/items/#{shortcode_url}.json"
63
+ end
64
+
65
+ private
66
+
67
+ def overwrite(attributes = {})
68
+ @shortcode_url = attributes['shortcode_url']
69
+ @created_at = attributes['created_at']
70
+ @updated_at = attributes['updated_at']
71
+ @meeting_id = attributes['meeting_id']
72
+ @position = attributes['position']
73
+ @content = attributes['content']
74
+ end
75
+ end
@@ -0,0 +1,85 @@
1
+ # A collection of items for a specific meeting, and allows for the creation of
2
+ # new items, and re-ordering of the full set of items. It's really just a
3
+ # slightly special subclass of Array.
4
+ #
5
+ class Ketchup::ItemArray < Array
6
+ # Create a new ItemArray for a given api, meeting, and set of items. The set
7
+ # of items should be hashes taken from the API server, not actual item
8
+ # objects.
9
+ #
10
+ # Like many of the methods in this library, you really don't need to call this
11
+ # one yourself. A meeting object will create its own ItemArray without any
12
+ # help.
13
+ #
14
+ # @example
15
+ # Ketchup::ItemArray.new api, meeting, [{'content' => 'foo', #... }]
16
+ #
17
+ # @param [Ketchup::API] api A connected API instance
18
+ # @param [Ketchup::Meeting] meeting The meeting all items are attached to.
19
+ # @param [Array] items An array of hashes that map to attributes of items.
20
+ #
21
+ def initialize(api, meeting, items = [])
22
+ @api = api
23
+ @meeting = meeting
24
+
25
+ replace items.collect { |hash|
26
+ Ketchup::Item.new(@api, @meeting, hash)
27
+ }
28
+ end
29
+
30
+ # Create a new unsaved item object. The only parameter you really need to
31
+ # worry about is the content - the rest is all internal values, managed by the
32
+ # API server.
33
+ #
34
+ # @example
35
+ # meeting.items.build 'content' => 'Petty Cash'
36
+ #
37
+ # @param [Hash] params The item's details
38
+ # @return [Ketchup::Item] An unsaved item
39
+ #
40
+ def build(params = {})
41
+ item = Ketchup::Item.new @api, @meeting, params
42
+ push item
43
+ item
44
+ end
45
+
46
+ # Create a new (saved) item object. The only parameter you really need to
47
+ # worry about is the content - the rest is all internal values, managed by the
48
+ # API server.
49
+ #
50
+ # @example
51
+ # meeting.items.create 'content' => 'Petty Cash'
52
+ #
53
+ # @param [Hash] params The item's details
54
+ # @return [Ketchup::Item] An item, already saved to the server.
55
+ #
56
+ def create(params = {})
57
+ item = build(params)
58
+ item.save
59
+ item
60
+ end
61
+
62
+ # Re-order the items in this array, by passing them through in the order you
63
+ # would prefer. This makes the change directly to the API server, as well as
64
+ # within this array.
65
+ #
66
+ # Make sure you're passing in all the items that are in this array, and no
67
+ # others. This can't be used as a shortcut to add new items.
68
+ #
69
+ # @example
70
+ # meeting.items.reorder second_item, fourth_item, first_item, third_item
71
+ #
72
+ # @param [Ketchup::Item] items The items of the array, in a new order.
73
+ # @raise ArgumentError if you don't provide the exact same set of items.
74
+ #
75
+ def reorder(*items)
76
+ if items.collect(&:shortcode_url).sort != collect(&:shortcode_url).sort
77
+ raise ArgumentError, 'cannot sort a different set of items'
78
+ end
79
+
80
+ @api.put "/meetings/#{@meeting.shortcode_url}/sort_items.json", {
81
+ 'items' => items.collect(&:shortcode_url)
82
+ }
83
+ replace items
84
+ end
85
+ end
@@ -0,0 +1,96 @@
1
+ # A Meeting - the cornerstone of Ketchup. This object tracks the meeting's
2
+ # key details: the title, whether it's public or not, the date, attendees, a
3
+ # description, location, project name, and the agenda items.
4
+ #
5
+ # While you won't want to create a new meeting from this class itself - it's far
6
+ # easier to do so using a profile's meetings array - this is a good reference
7
+ # point for changing and deleting meetings.
8
+ #
9
+ # @see Ketchup::MeetingArray#build
10
+ # @see Ketchup::MeetingArray#create
11
+ #
12
+ class Ketchup::Meeting
13
+ attr_reader :shortcode_url, :public_url, :user_id, :created_at, :updated_at,
14
+ :project_id, :items, :api
15
+
16
+ WriteableAttributes = [:title, :quick, :public, :date, :attendees,
17
+ :description, :project_name, :location]
18
+ attr_accessor *WriteableAttributes
19
+
20
+ # Create a new meeting, using an active API object. Keep in mind that all
21
+ # parameter keys must be provided as strings. The key attributes are the title
22
+ # and the date (the rest are optional).
23
+ #
24
+ # The date can be defined using natural language, and the server will figure
25
+ # out what you mean (just like when you create a meeting on the website).
26
+ #
27
+ # @example
28
+ # Ketchup::Meeting.new api,
29
+ # 'title' => 'White Album Cover Brainstorming',
30
+ # 'date' => 'Tomorrow at 4pm',
31
+ # 'attendees' => 'John, Paul, George and Ringo',
32
+ # 'location' => 'Abbey Road',
33
+ # 'project_name' => 'New Releases'
34
+ #
35
+ # @param [Ketchup::API] api A connected API instance
36
+ # @param [Hash] params The attributes for the meeting.
37
+ #
38
+ def initialize(api, params = {})
39
+ @api = api
40
+
41
+ overwrite params
42
+
43
+ @items = Ketchup::ItemArray.new @api, self, (params['items'] || [])
44
+ end
45
+
46
+ # Saves the meeting to the server, whether it's a new meeting or an existing
47
+ # one. This does not save underlying items or notes.
48
+ #
49
+ def save
50
+ if new_record?
51
+ overwrite @api.post("/meetings.json", writeable_attributes)
52
+ else
53
+ overwrite @api.put("/meetings/#{shortcode_url}.json",
54
+ writeable_attributes)
55
+ end
56
+ end
57
+
58
+ # Deletes the meeting from the server. If the meeting has never been saved,
59
+ # this method will do nothing at all.
60
+ #
61
+ def destroy
62
+ return if new_record?
63
+
64
+ @api.delete "/meetings/#{shortcode_url}.json"
65
+ end
66
+
67
+ # Indicates whether the meeting exists on the server yet.
68
+ #
69
+ # @return [Boolean] True if the meeting does not exist on the server.
70
+ #
71
+ def new_record?
72
+ shortcode_url.nil?
73
+ end
74
+
75
+ private
76
+
77
+ def overwrite(attributes)
78
+ @shortcode_url = attributes['shortcode_url']
79
+ @public_url = attributes['public_url']
80
+ @user_id = attributes['user_id']
81
+ @created_at = attributes['created_at']
82
+ @updated_at = attributes['updated_at']
83
+ @project_id = attributes['project_id']
84
+
85
+ WriteableAttributes.each do |attrib|
86
+ instance_variable_set "@#{attrib}".to_sym, attributes[attrib.to_s]
87
+ end
88
+ end
89
+
90
+ def writeable_attributes
91
+ WriteableAttributes.inject({}) do |hash, attrib|
92
+ hash[attrib.to_s] = instance_variable_get "@#{attrib}".to_sym
93
+ hash
94
+ end
95
+ end
96
+ end