Skip to content

Commit 1a96ca6

Browse files
committed
httpclient and github api
1 parent 662c84a commit 1a96ca6

148 files changed

Lines changed: 6572 additions & 240 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Package.resolved

Lines changed: 14 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,45 @@ import PackageDescription
55

66
let package = Package(
77
name: "Github-toolkit",
8+
platforms: [.iOS(.v16), .macOS(.v10_13)],
89
products: [
910
// Products define the executables and libraries a package produces, and make them visible to other packages.
1011
.library(
1112
name: "Github-toolkit",
1213
targets: ["Github-toolkit"]),
1314
.library(name: "Core", targets: ["Core"]),
15+
.library(name: "HttpClient", targets: ["HttpClient"]),
1416
.library(name: "Github", targets: ["Github"])
1517
],
16-
dependencies: [],
18+
dependencies: [
19+
.package(url: "https://github.com/apple/swift-http-types", .upToNextMajor(from: "0.1.1"))
20+
],
1721
targets: [
1822

1923
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
2024
// Targets can depend on other targets in this package, and on products in packages this package depends on.
21-
.target(
22-
name: "Github-toolkit",
23-
dependencies: ["Core", "Github"]),
25+
.target(
26+
name: "Github-toolkit",
27+
dependencies: ["Core", "Github"]),
2428
.target(
2529
name: "Core",
26-
dependencies: []
27-
),
30+
dependencies: ["HttpClient"]
31+
),
2832
.target(
2933
name: "Github",
30-
dependencies: []
31-
),
34+
dependencies: [
35+
.product(name: "HTTPTypes", package: "swift-http-types"),
36+
.product(name: "HTTPTypesFoundation", package: "swift-http-types"),
37+
"HttpClient"
38+
]
39+
),
40+
.target(
41+
name: "HttpClient",
42+
dependencies: [
43+
.product(name: "HTTPTypes", package: "swift-http-types"),
44+
.product(name: "HTTPTypesFoundation", package: "swift-http-types"),
45+
]
46+
),
3247
.testTarget(
3348
name: "Github-toolkitTests",
3449
dependencies: ["Github-toolkit"]),
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
//
2+
// Notifications.swift
3+
//
4+
//
5+
// Created by Asiel Cabrera Gonzalez on 9/14/23.
6+
//
7+
8+
import Foundation
9+
import HttpClient
10+
import HTTPTypes
11+
12+
@available(macOS 13.0, *)
13+
extension GitHub {
14+
/// List notifications for the authenticated user
15+
/// https://docs.github.com/en/rest/activity/notifications?apiVersion=2022-11-28#list-notifications-for-the-authenticated-user
16+
/// - Parameters:
17+
/// - all: If true, show notifications marked as read.
18+
/// - participating: If true, only shows notifications in which the user is directly participating or mentioned.
19+
/// - since: Only show results that were last updated after the given time.
20+
/// - before: Only show notifications updated before the given time.
21+
/// - perPage: The number of results per page (max 50).
22+
/// - page: Page number of the results to fetch.
23+
/// - Returns: [Notification]
24+
public func notifications(
25+
all: Bool = false,
26+
participating: Bool = false,
27+
since: Date? = nil,
28+
before: Date? = nil,
29+
perPage: Int = 30,
30+
page: Int = 1
31+
) async throws -> [Notification] {
32+
let path = "/notifications"
33+
let endpoint = baseURL.appending(path: path)
34+
let method: HTTPRequest.Method = .get
35+
36+
var queries: [String: String] = [
37+
"all": all.description,
38+
"participating": participating.description,
39+
"per_page": String(perPage),
40+
"page": String(page),
41+
]
42+
43+
let formatter = ISO8601DateFormatter()
44+
since.map {
45+
queries["since"] = formatter.string(from: $0)
46+
}
47+
before.map {
48+
queries["before"] = formatter.string(from: $0)
49+
}
50+
51+
let request = HTTPRequest(method: method, url: endpoint, queries: queries, headers: headers)
52+
53+
let (data, _) = try await session.data(for: request)
54+
55+
let notifications = try decode([Notification].self, from: data)
56+
57+
return notifications
58+
}
59+
}
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
//
2+
// Stargazers.swift
3+
//
4+
//
5+
// Created by Asiel Cabrera Gonzalez on 9/14/23.
6+
//
7+
8+
import Foundation
9+
import HttpClient
10+
import HTTPTypes
11+
12+
@available(macOS 13.0, *)
13+
extension GitHub {
14+
/// Stargazers
15+
/// https://docs.github.com/en/rest/activity/starring?apiVersion=2022-11-28#list-stargazers
16+
/// - Parameters:
17+
/// - ownerID: The account owner of the repository. The name is not case sensitive.
18+
/// - repositoryName: The name of the repository without the .git extension. The name is not case sensitive.
19+
/// - perPage: The number of results per page (max 100).
20+
/// - page: Page number of the results to fetch.
21+
/// - Returns: [User]
22+
public func stargazers(
23+
ownerID: String,
24+
repositoryName: String,
25+
perPage: Int = 30,
26+
page: Int = 1
27+
) async throws -> [User] {
28+
let path = "/repos/\(ownerID)/\(repositoryName)/stargazers"
29+
let endpoint = baseURL.appending(path: path)
30+
let method: HTTPRequest.Method = .get
31+
let queries: [String: String] = [
32+
"per_page": String(perPage),
33+
"page": String(page)
34+
]
35+
36+
let request = HTTPRequest(method: method, url: endpoint, queries: queries, headers: headers)
37+
38+
let (data, _) = try await session.data(for: request)
39+
40+
let users = try decode([User].self, from: data)
41+
42+
return users
43+
}
44+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
//
2+
// CollaboratorAffiliationType.swift
3+
//
4+
//
5+
// Created by Asiel Cabrera Gonzalez on 9/14/23.
6+
//
7+
8+
import Foundation
9+
10+
public enum CollaboratorAffiliationType: String, Sendable {
11+
case outside
12+
case direct
13+
case all
14+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// Collaborators.swift
3+
//
4+
//
5+
// Created by Asiel Cabrera Gonzalez on 9/14/23.
6+
//
7+
8+
import Foundation
9+
import HttpClient
10+
import HTTPTypes
11+
12+
13+
14+
@available(macOS 13.0, *)
15+
extension GitHub {
16+
/// List repository collaborators
17+
/// https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#list-repository-collaborators
18+
/// - Parameters:
19+
/// - ownerID: The account owner of the repository. The name is not case sensitive.
20+
/// - repositoryName: The name of the repository without the .git extension. The name is not case sensitive.
21+
/// - affiliation: Filter collaborators returned by their affiliation. outside means all outside collaborators of an organization-owned repository. direct means all collaborators with permissions to an organization-owned repository, regardless of organization membership status. all means all collaborators the authenticated user can see.
22+
/// - permission: Filter collaborators by the permissions they have on the repository. If not specified, all collaborators will be returned.
23+
/// - perPage: The number of results per page (max 100).
24+
/// - page: Page number of the results to fetch.
25+
/// - Returns: [Contributor]
26+
public func collaborators(
27+
ownerID: String,
28+
repositoryName: String,
29+
affiliation: CollaboratorAffiliationType = .all,
30+
permission: PermissionType? = nil,
31+
perPage: Int = 30,
32+
page: Int = 1
33+
) async throws -> [Collaborator] {
34+
let path = "/repos/\(ownerID)/\(repositoryName)/collaborators"
35+
let endpoint = baseURL.appending(path: path)
36+
let method: HTTPRequest.Method = .get
37+
38+
var queries: [String: String] = [
39+
"affiliation": affiliation.rawValue,
40+
"per_page": String(perPage),
41+
"page": String(page),
42+
]
43+
44+
permission.map {
45+
queries["permission"] = $0.rawValue
46+
}
47+
48+
let request = HTTPRequest(method: method, url: endpoint, queries: queries, headers: headers)
49+
50+
let (data, _) = try await session.data(for: request)
51+
52+
let collaborators = try decode([Collaborator].self, from: data)
53+
54+
return collaborators
55+
}
56+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//
2+
// DiscussionOrderField.swift
3+
//
4+
//
5+
// Created by Asiel Cabrera Gonzalez on 9/14/23.
6+
//
7+
8+
import Foundation
9+
10+
public enum DiscussionOrderField: String, Sendable {
11+
case createdAt = "CREATED_AT"
12+
case updatedAt = "UPDATED_AT"
13+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// DiscussionRequest.swift
3+
//
4+
//
5+
// Created by Asiel Cabrera Gonzalez on 9/14/23.
6+
//
7+
8+
import Foundation
9+
import HttpClient
10+
import HTTPTypes
11+
12+
@available(macOS 13.0, *)
13+
extension GitHub {
14+
public func discussion(
15+
ownerID: String,
16+
repositoryName: String,
17+
discussionNumber: Int,
18+
itemFirst: Int
19+
) async throws -> Discussion {
20+
try await self.discussion(
21+
ownerID: ownerID,
22+
repositoryName: repositoryName,
23+
discussionNumber: discussionNumber,
24+
itemFirst: itemFirst,
25+
itemLast: nil
26+
)
27+
}
28+
29+
public func discussion(
30+
ownerID: String,
31+
repositoryName: String,
32+
discussionNumber: Int,
33+
itemLast: Int
34+
) async throws -> Discussion {
35+
try await self.discussion(
36+
ownerID: ownerID,
37+
repositoryName: repositoryName,
38+
discussionNumber: discussionNumber,
39+
itemFirst: nil,
40+
itemLast: itemLast
41+
)
42+
}
43+
44+
private func discussion(
45+
ownerID: String,
46+
repositoryName: String,
47+
discussionNumber: Int,
48+
itemFirst: Int? = nil,
49+
itemLast: Int? = nil
50+
) async throws -> Discussion {
51+
let endpoint = baseURL.appending(path: "/graphql")
52+
let method: HTTPRequest.Method = .post
53+
54+
let query = """
55+
query {
56+
repository(owner: "\(ownerID)", name: "\(repositoryName)") {
57+
discussion(number: \(discussionNumber)) \(discussionFields(first: itemFirst, last: itemLast))
58+
}
59+
}
60+
"""
61+
62+
let httpRequest = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers)
63+
var urlRequest = URLRequest(httpRequest: httpRequest)!
64+
urlRequest.httpBody = try JSONEncoder().encode(["query": query])
65+
66+
let (data, _) = try await session.data(for: urlRequest)
67+
let response = try decode(DiscussionResponse.self, from: data)
68+
69+
return response.discussion
70+
}
71+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// DiscussionResponse.swift
3+
//
4+
//
5+
// Created by Asiel Cabrera Gonzalez on 9/14/23.
6+
//
7+
8+
import Foundation
9+
10+
struct DiscussionResponse: Decodable, Sendable {
11+
let discussion: Discussion
12+
13+
private enum CodingKeys: String, CodingKey {
14+
case data
15+
case repository
16+
case discussion
17+
}
18+
19+
init(from decoder: any Decoder) throws {
20+
let container = try decoder.container(keyedBy: CodingKeys.self)
21+
let dataContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
22+
let repositoryContainer = try dataContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .repository)
23+
let discussion = try repositoryContainer.decode(Discussion.self, forKey: .discussion)
24+
self.discussion = discussion
25+
}
26+
}
27+
28+
struct DiscussionsResponse: Decodable, Sendable {
29+
let discussions: [Discussion]
30+
31+
private enum CodingKeys: String, CodingKey {
32+
case data
33+
case repository
34+
case discussions
35+
case nodes
36+
}
37+
38+
init(from decoder: any Decoder) throws {
39+
let container = try decoder.container(keyedBy: CodingKeys.self)
40+
let dataContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
41+
let repositoryContainer = try dataContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .repository)
42+
let discussionsContainer = try repositoryContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .discussions)
43+
let discussions = try discussionsContainer.decode([Discussion].self, forKey: .nodes)
44+
self.discussions = discussions
45+
}
46+
}

0 commit comments

Comments
 (0)