Skip to content

Commit 3b35bac

Browse files
committed
Merge pull request octokit#34 from octokit/notifications
Notifications API
2 parents 5a23822 + 7ca8c7f commit 3b35bac

18 files changed

+436
-68
lines changed

External/Mantle

OctoKit.xcodeproj/project.pbxproj

Lines changed: 63 additions & 37 deletions
Large diffs are not rendered by default.

OctoKit/OCTClient.h

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#import "AFNetworking.h"
1010
#import <ReactiveCocoa/ReactiveCocoa.h>
1111

12+
@class OCTNotification;
1213
@class OCTOrganization;
1314
@class OCTServer;
1415
@class OCTTeam;
@@ -158,6 +159,11 @@ extern NSString * const OCTClientErrorHTTPStatusCodeKey;
158159
// point, the returned signal will send it immediately, then terminate.
159160
- (RACSignal *)enqueueConditionalRequestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters notMatchingEtag:(NSString *)etag resultClass:(Class)resultClass;
160161

162+
// Behaves like
163+
// -enqueueConditionalRequestWithMethod:path:parameters:notMatchingEtag:resultClass:,
164+
// but allows a full URL to be passed in.
165+
- (RACSignal *)enqueueConditionalRequestWithMethod:(NSString *)method URL:(NSURL *)URL parameters:(NSDictionary *)parameters notMatchingEtag:(NSString *)etag resultClass:(Class)resultClass;
166+
161167
@end
162168

163169
@interface OCTClient (User)
@@ -243,10 +249,37 @@ extern NSString * const OCTClientErrorHTTPStatusCodeKey;
243249
// the latest data matches `etag`, the call does not count toward the API rate
244250
// limit.
245251
//
246-
// Returns a signal which will send zero or more OCTEvents if new data was
247-
// downloaded. Unrecognized events will be omitted from the result. On success,
248-
// the signal will send completed regardless of whether there was new data. If
249-
// no `user` is set, the signal will error immediately.
252+
// Returns a signal which will send zero or more OCTResponses (of OCTEvents) if
253+
// new data was downloaded. Unrecognized events will be omitted from the result.
254+
// On success, the signal will send completed regardless of whether there was
255+
// new data. If no `user` is set, the signal will error immediately.
250256
- (RACSignal *)fetchUserEventsNotMatchingEtag:(NSString *)etag;
251257

252258
@end
259+
260+
@interface OCTClient (Notifications)
261+
262+
// Conditionally fetch unread notifications for the user. If the latest data
263+
// matches `etag`, the call does not count toward the API rate limit.
264+
//
265+
// etag - An Etag from a previous request, used to avoid downloading
266+
// unnecessary data.
267+
// includeRead - Whether to include notifications that have already been read.
268+
// since - If not nil, only notifications updated after this date will be
269+
// included.
270+
//
271+
// Returns a signal which will zero or more OCTResponses (of OCTNotifications)
272+
// if new data was downloaded. On success, the signal will send completed
273+
// regardless of whether there was new data. If the client is not
274+
// `authenticated`, the signal will error immediately.
275+
- (RACSignal *)fetchNotificationsNotMatchingEtag:(NSString *)etag includeReadNotifications:(BOOL)includeRead updatedSince:(NSDate *)since;
276+
277+
// Mark the notification has having been read.
278+
//
279+
// notification - The notification to mark as read. Cannot be nil.
280+
//
281+
// Returns a signal which will send completed on success. If the client is not
282+
// `authenticated`, the signal will error immediately.
283+
- (RACSignal *)markNotificationAsRead:(OCTNotification *)notification;
284+
285+
@end

OctoKit/OCTClient.m

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
//
88

99
#import "OCTClient.h"
10+
#import "NSDateFormatter+OCTFormattingAdditions.h"
1011
#import "OCTEvent.h"
1112
#import "OCTObject+Private.h"
1213
#import "OCTOrganization.h"
@@ -16,6 +17,7 @@
1617
#import "OCTServer.h"
1718
#import "OCTTeam.h"
1819
#import "OCTUser.h"
20+
#import "OCTNotification.h"
1921

2022
NSString * const OCTClientErrorDomain = @"OCTClientErrorDomain";
2123
const NSInteger OCTClientErrorAuthenticationFailed = 666;
@@ -47,8 +49,8 @@ + (NSError *)authenticationRequiredError;
4749
//
4850
// method - The HTTP method to use.
4951
// relativePath - The path to fetch, relative to the user object. For example,
50-
// to request `user/orgs` or `users/:user/orgs`, simply pass in
51-
// `orgs`.
52+
// to request `user/orgs` or `users/:user/orgs`, simply pass in
53+
// `orgs`.
5254
// parameters - HTTP parameters to encode and send with the request.
5355
// resultClass - The class that response data should be returned as.
5456
//
@@ -57,6 +59,17 @@ + (NSError *)authenticationRequiredError;
5759
// will error immediately.
5860
- (RACSignal *)enqueueUserRequestWithMethod:(NSString *)method relativePath:(NSString *)relativePath parameters:(NSDictionary *)parameters resultClass:(Class)resultClass;
5961

62+
// Creates a request.
63+
//
64+
// method - The HTTP method to use in the request (e.g., "GET" or "POST").
65+
// path - The path to request, relative to the base API endpoint. This path
66+
// should _not_ begin with a forward slash.
67+
// etag - An ETag to compare the server data against, previously retrieved
68+
// from an instance of OCTResponse.
69+
//
70+
// Returns a request which can be modified further before being enqueued.
71+
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters notMatchingEtag:(NSString *)etag;
72+
6073
@end
6174

6275
@implementation OCTClient
@@ -102,6 +115,27 @@ - (id)initWithServer:(OCTServer *)server {
102115
return self;
103116
}
104117

118+
#pragma mark Request Creation
119+
120+
- (NSMutableURLRequest *)requestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters notMatchingEtag:(NSString *)etag {
121+
NSParameterAssert(method != nil);
122+
123+
parameters = [parameters mtl_dictionaryByAddingEntriesFromDictionary:@{
124+
@"per_page": @100
125+
}];
126+
127+
NSMutableURLRequest *request = [self requestWithMethod:method path:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] parameters:parameters];
128+
129+
if (etag != nil) {
130+
[request setValue:etag forHTTPHeaderField:@"If-None-Match"];
131+
} else {
132+
// Ignore cache data so we definitely re-fetch from the server.
133+
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
134+
}
135+
136+
return request;
137+
}
138+
105139
#pragma mark Request Enqueuing
106140

107141
- (RACSignal *)enqueueRequestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters resultClass:(Class)resultClass {
@@ -116,21 +150,18 @@ - (RACSignal *)enqueueConditionalRequestWithMethod:(NSString *)method path:(NSSt
116150
}
117151

118152
- (RACSignal *)enqueueConditionalRequestWithMethod:(NSString *)method path:(NSString *)path parameters:(NSDictionary *)parameters notMatchingEtag:(NSString *)etag resultClass:(Class)resultClass fetchAllPages:(BOOL)fetchAllPages {
119-
NSParameterAssert(method != nil);
153+
NSURLRequest *request = [self requestWithMethod:method path:path parameters:parameters notMatchingEtag:etag];
154+
return [self enqueueRequest:request resultClass:resultClass fetchAllPages:fetchAllPages];
155+
}
120156

121-
parameters = [parameters mtl_dictionaryByAddingEntriesFromDictionary:@{
122-
@"per_page": @100
123-
}];
124-
125-
NSMutableURLRequest *request = [self requestWithMethod:method path:[path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding] parameters:parameters];
126-
if (etag != nil) {
127-
[request setValue:etag forHTTPHeaderField:@"If-None-Match"];
128-
} else {
129-
// ignore cache data so we definitely re-fatch from the server
130-
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
131-
}
157+
- (RACSignal *)enqueueConditionalRequestWithMethod:(NSString *)method URL:(NSURL *)URL parameters:(NSDictionary *)parameters notMatchingEtag:(NSString *)etag resultClass:(Class)resultClass {
158+
NSMutableURLRequest *request = [self requestWithMethod:method path:@"" parameters:parameters notMatchingEtag:etag];
159+
request.URL = URL;
160+
return [self enqueueRequest:request resultClass:resultClass fetchAllPages:YES];
161+
}
132162

133-
return [self enqueueRequest:request resultClass:resultClass fetchAllPages:fetchAllPages];
163+
- (RACSignal *)enqueueRequest:(NSURLRequest *)request resultClass:(Class)resultClass {
164+
return [self enqueueRequest:request resultClass:resultClass fetchAllPages:YES];
134165
}
135166

136167
- (RACSignal *)enqueueRequest:(NSURLRequest *)request resultClass:(Class)resultClass fetchAllPages:(BOOL)fetchAllPages {
@@ -503,3 +534,32 @@ - (RACSignal *)fetchUserEventsNotMatchingEtag:(NSString *)etag {
503534
}
504535

505536
@end
537+
538+
@implementation OCTClient (Notifications)
539+
540+
- (RACSignal *)fetchNotificationsNotMatchingEtag:(NSString *)etag includeReadNotifications:(BOOL)includeRead updatedSince:(NSDate *)since {
541+
if (!self.authenticated) return [RACSignal error:self.class.authenticationRequiredError];
542+
543+
NSMutableDictionary *parameters = [NSMutableDictionary dictionary];
544+
parameters[@"all"] = @(includeRead);
545+
546+
if (since != nil) {
547+
parameters[@"since"] = [NSDateFormatter oct_stringFromDate:since];
548+
}
549+
550+
return [self enqueueConditionalRequestWithMethod:@"GET" path:@"notifications" parameters:parameters notMatchingEtag:etag resultClass:OCTNotification.class];
551+
}
552+
553+
- (RACSignal *)markNotificationAsRead:(OCTNotification *)notification {
554+
return [self patchNotification:notification withReadStatus:YES];
555+
}
556+
557+
- (RACSignal *)patchNotification:(OCTNotification *)notification withReadStatus:(BOOL)read {
558+
NSParameterAssert(notification != nil);
559+
560+
if (!self.authenticated) return [RACSignal error:self.class.authenticationRequiredError];
561+
562+
return [[self enqueueConditionalRequestWithMethod:@"PATCH" URL:notification.threadURL parameters:@{ @"read": @(read) } notMatchingEtag:nil resultClass:nil] ignoreElements];
563+
}
564+
565+
@end

OctoKit/OCTCommitComment.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@
1717
// The SHA of the commit being commented upon.
1818
@property (nonatomic, copy, readonly) NSString *commitSHA;
1919

20+
// The login of the user who created this comment.
21+
@property (nonatomic, copy, readonly) NSString *commenterLogin;
22+
2023
@end

OctoKit/OCTCommitComment.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ + (NSDictionary *)JSONKeyPathsByPropertyKey {
1616
return [super.JSONKeyPathsByPropertyKey mtl_dictionaryByAddingEntriesFromDictionary:@{
1717
@"HTMLURL": @"html_url",
1818
@"commitSHA": @"commit_id",
19+
@"commenterLogin": @"user.login",
1920
}];
2021
}
2122

OctoKit/OCTIssueComment.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,10 @@
1111
// A single comment on an issue.
1212
@interface OCTIssueComment : OCTObject
1313

14+
// The webpage URL for this comment.
15+
@property (nonatomic, copy, readonly) NSURL *HTMLURL;
16+
17+
// The login of the user who created this comment.
18+
@property (nonatomic, copy, readonly) NSString *commenterLogin;
19+
1420
@end

OctoKit/OCTIssueComment.m

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,17 @@
1010

1111
@implementation OCTIssueComment
1212

13+
#pragma mark MTLJSONSerializing
14+
15+
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
16+
return [super.JSONKeyPathsByPropertyKey mtl_dictionaryByAddingEntriesFromDictionary:@{
17+
@"HTMLURL": @"html_url",
18+
@"commenterLogin": @"user.login",
19+
}];
20+
}
21+
22+
+ (NSValueTransformer *)HTMLURLJSONTransformer {
23+
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
24+
}
25+
1326
@end

OctoKit/OCTNotification.h

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
//
2+
// OCTNotification.h
3+
// OctoKit
4+
//
5+
// Created by Josh Abernathy on 1/22/13.
6+
// Copyright (c) 2013 GitHub. All rights reserved.
7+
//
8+
9+
#import "OCTObject.h"
10+
11+
@class OCTRepository;
12+
13+
// The type of the notification.
14+
//
15+
// OCTNotificationTypeUnknown - An unknown type of notification.
16+
// OCTNotificationTypeIssue - A new issue, or a new comment on one.
17+
// OCTNotificationTypePullRequest - A new pull request, or a new comment on one.
18+
// OCTNotificationTypeCommit - A new comment on a commit.
19+
typedef enum : NSUInteger {
20+
OCTNotificationTypeUnknown,
21+
OCTNotificationTypeIssue,
22+
OCTNotificationTypePullRequest,
23+
OCTNotificationTypeCommit,
24+
} OCTNotificationType;
25+
26+
// A notification of some type of activity.
27+
@interface OCTNotification : OCTObject
28+
29+
// The title of the notification.
30+
@property (nonatomic, readonly, copy) NSString *title;
31+
32+
// The API URL to the notification's thread.
33+
@property (nonatomic, readonly, copy) NSURL *threadURL;
34+
35+
// The API URL to the subject that the notification was generated for (e.g., the
36+
// issue or pull request).
37+
@property (nonatomic, readonly, copy) NSURL *subjectURL;
38+
39+
// The API URL to the latest comment in the thread.
40+
//
41+
// If the notification does not represent a comment, this will be the same as
42+
// the subjectURL.
43+
@property (nonatomic, readonly, copy) NSURL *latestCommentURL;
44+
45+
// The notification type.
46+
@property (nonatomic, readonly, assign) OCTNotificationType type;
47+
48+
// The repository to which the notification belongs.
49+
@property (nonatomic, readonly, strong) OCTRepository *repository;
50+
51+
// The date on which the notification was last updated.
52+
@property (nonatomic, readonly, strong) NSDate *lastUpdatedDate;
53+
54+
// Whether this notification has yet to be read.
55+
@property (nonatomic, readonly, getter = isUnread) BOOL unread;
56+
57+
@end

OctoKit/OCTNotification.m

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// OCTNotification.m
3+
// OctoKit
4+
//
5+
// Created by Josh Abernathy on 1/22/13.
6+
// Copyright (c) 2013 GitHub. All rights reserved.
7+
//
8+
9+
#import "OCTNotification.h"
10+
#import "NSValueTransformer+OCTPredefinedTransformerAdditions.h"
11+
#import "OCTRepository.h"
12+
13+
@implementation OCTNotification
14+
15+
#pragma mark MTLModel
16+
17+
+ (NSDictionary *)JSONKeyPathsByPropertyKey {
18+
return [super.JSONKeyPathsByPropertyKey mtl_dictionaryByAddingEntriesFromDictionary:@{
19+
@"title": @"subject.title",
20+
@"threadURL": @"url",
21+
@"subjectURL": @"subject.url",
22+
@"latestCommentURL": @"subject.latest_comment_url",
23+
@"type": @"subject.type",
24+
@"repository": @"repository",
25+
@"lastUpdatedDate": @"updated_at",
26+
}];
27+
}
28+
29+
+ (NSValueTransformer *)objectIDJSONTransformer {
30+
return [MTLValueTransformer transformerWithBlock:^ id (id num) {
31+
if ([num isKindOfClass:NSString.class]) {
32+
return num;
33+
} else if ([num isKindOfClass:NSNumber.class]) {
34+
return [num stringValue];
35+
} else {
36+
return nil;
37+
}
38+
}];
39+
}
40+
41+
+ (NSValueTransformer *)threadURLJSONTransformer {
42+
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
43+
}
44+
45+
+ (NSValueTransformer *)subjectURLJSONTransformer {
46+
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
47+
}
48+
49+
+ (NSValueTransformer *)latestCommentURLJSONTransformer {
50+
return [NSValueTransformer valueTransformerForName:MTLURLValueTransformerName];
51+
}
52+
53+
+ (NSValueTransformer *)repositoryJSONTransformer {
54+
return [MTLValueTransformer mtl_JSONDictionaryTransformerWithModelClass:OCTRepository.class];
55+
}
56+
57+
+ (NSValueTransformer *)lastUpdatedDateJSONTransformer {
58+
return [NSValueTransformer valueTransformerForName:OCTDateValueTransformerName];
59+
}
60+
61+
+ (NSValueTransformer *)typeJSONTransformer {
62+
NSDictionary *typesByName = @{
63+
@"Issue": @(OCTNotificationTypeIssue),
64+
@"PullRequest": @(OCTNotificationTypePullRequest),
65+
@"Commit": @(OCTNotificationTypeCommit),
66+
};
67+
68+
return [MTLValueTransformer
69+
reversibleTransformerWithForwardBlock:^(NSString *name) {
70+
return typesByName[name] ?: @(OCTNotificationTypeUnknown);
71+
} reverseBlock:^(NSNumber *type) {
72+
return [typesByName allKeysForObject:type].lastObject;
73+
}];
74+
}
75+
76+
@end

OctoKit/OCTPullRequestComment.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,7 @@
1717
// The API URL for the pull request upon which this comment appears.
1818
@property (nonatomic, copy, readonly) NSURL *pullRequestAPIURL;
1919

20+
// The login of the user who created this comment.
21+
@property (nonatomic, copy, readonly) NSString *commenterLogin;
22+
2023
@end

OctoKit/OCTPullRequestComment.m

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ + (NSDictionary *)JSONKeyPathsByPropertyKey {
1616
return [super.JSONKeyPathsByPropertyKey mtl_dictionaryByAddingEntriesFromDictionary:@{
1717
@"HTMLURL": @"_links.html.href",
1818
@"pullRequestAPIURL": @"_links.pull_request.href",
19+
@"commenterLogin": @"user.login",
1920
}];
2021
}
2122

0 commit comments

Comments
 (0)