Skip to content

Commit

Permalink
httpclient and github api
Browse files Browse the repository at this point in the history
  • Loading branch information
asielcabrera committed Sep 14, 2023
1 parent 662c84a commit 1a96ca6
Show file tree
Hide file tree
Showing 148 changed files with 6,572 additions and 240 deletions.
14 changes: 14 additions & 0 deletions Package.resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"pins" : [
{
"identity" : "swift-http-types",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-http-types",
"state" : {
"revision" : "68deedb6b98837564cf0231fa1df48de35881993",
"version" : "0.2.1"
}
}
],
"version" : 2
}
31 changes: 23 additions & 8 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,45 @@ import PackageDescription

let package = Package(
name: "Github-toolkit",
platforms: [.iOS(.v16), .macOS(.v10_13)],
products: [
// Products define the executables and libraries a package produces, and make them visible to other packages.
.library(
name: "Github-toolkit",
targets: ["Github-toolkit"]),
.library(name: "Core", targets: ["Core"]),
.library(name: "HttpClient", targets: ["HttpClient"]),
.library(name: "Github", targets: ["Github"])
],
dependencies: [],
dependencies: [
.package(url: "https://github.com/apple/swift-http-types", .upToNextMajor(from: "0.1.1"))
],
targets: [

// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages this package depends on.
.target(
name: "Github-toolkit",
dependencies: ["Core", "Github"]),
.target(
name: "Github-toolkit",
dependencies: ["Core", "Github"]),
.target(
name: "Core",
dependencies: []
),
dependencies: ["HttpClient"]
),
.target(
name: "Github",
dependencies: []
),
dependencies: [
.product(name: "HTTPTypes", package: "swift-http-types"),
.product(name: "HTTPTypesFoundation", package: "swift-http-types"),
"HttpClient"
]
),
.target(
name: "HttpClient",
dependencies: [
.product(name: "HTTPTypes", package: "swift-http-types"),
.product(name: "HTTPTypesFoundation", package: "swift-http-types"),
]
),
.testTarget(
name: "Github-toolkitTests",
dependencies: ["Github-toolkit"]),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//
// Notifications.swift
//
//
// Created by Asiel Cabrera Gonzalez on 9/14/23.
//

import Foundation
import HttpClient
import HTTPTypes

@available(macOS 13.0, *)
extension GitHub {
/// List notifications for the authenticated user
/// https://docs.github.com/en/rest/activity/notifications?apiVersion=2022-11-28#list-notifications-for-the-authenticated-user
/// - Parameters:
/// - all: If true, show notifications marked as read.
/// - participating: If true, only shows notifications in which the user is directly participating or mentioned.
/// - since: Only show results that were last updated after the given time.
/// - before: Only show notifications updated before the given time.
/// - perPage: The number of results per page (max 50).
/// - page: Page number of the results to fetch.
/// - Returns: [Notification]
public func notifications(
all: Bool = false,
participating: Bool = false,
since: Date? = nil,
before: Date? = nil,
perPage: Int = 30,
page: Int = 1
) async throws -> [Notification] {
let path = "/notifications"
let endpoint = baseURL.appending(path: path)
let method: HTTPRequest.Method = .get

var queries: [String: String] = [
"all": all.description,
"participating": participating.description,
"per_page": String(perPage),
"page": String(page),
]

let formatter = ISO8601DateFormatter()
since.map {
queries["since"] = formatter.string(from: $0)
}
before.map {
queries["before"] = formatter.string(from: $0)
}

let request = HTTPRequest(method: method, url: endpoint, queries: queries, headers: headers)

let (data, _) = try await session.data(for: request)

let notifications = try decode([Notification].self, from: data)

return notifications
}
}
44 changes: 44 additions & 0 deletions Sources/Github/GitHubAPI/Acitivity/Starring/Stargazers.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
//
// Stargazers.swift
//
//
// Created by Asiel Cabrera Gonzalez on 9/14/23.
//

import Foundation
import HttpClient
import HTTPTypes

@available(macOS 13.0, *)
extension GitHub {
/// Stargazers
/// https://docs.github.com/en/rest/activity/starring?apiVersion=2022-11-28#list-stargazers
/// - Parameters:
/// - ownerID: The account owner of the repository. The name is not case sensitive.
/// - repositoryName: The name of the repository without the .git extension. The name is not case sensitive.
/// - perPage: The number of results per page (max 100).
/// - page: Page number of the results to fetch.
/// - Returns: [User]
public func stargazers(
ownerID: String,
repositoryName: String,
perPage: Int = 30,
page: Int = 1
) async throws -> [User] {
let path = "/repos/\(ownerID)/\(repositoryName)/stargazers"
let endpoint = baseURL.appending(path: path)
let method: HTTPRequest.Method = .get
let queries: [String: String] = [
"per_page": String(perPage),
"page": String(page)
]

let request = HTTPRequest(method: method, url: endpoint, queries: queries, headers: headers)

let (data, _) = try await session.data(for: request)

let users = try decode([User].self, from: data)

return users
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
//
// CollaboratorAffiliationType.swift
//
//
// Created by Asiel Cabrera Gonzalez on 9/14/23.
//

import Foundation

public enum CollaboratorAffiliationType: String, Sendable {
case outside
case direct
case all
}
56 changes: 56 additions & 0 deletions Sources/Github/GitHubAPI/Collaborators/Collaborators.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
//
// Collaborators.swift
//
//
// Created by Asiel Cabrera Gonzalez on 9/14/23.
//

import Foundation
import HttpClient
import HTTPTypes



@available(macOS 13.0, *)
extension GitHub {
/// List repository collaborators
/// https://docs.github.com/en/rest/collaborators/collaborators?apiVersion=2022-11-28#list-repository-collaborators
/// - Parameters:
/// - ownerID: The account owner of the repository. The name is not case sensitive.
/// - repositoryName: The name of the repository without the .git extension. The name is not case sensitive.
/// - 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.
/// - permission: Filter collaborators by the permissions they have on the repository. If not specified, all collaborators will be returned.
/// - perPage: The number of results per page (max 100).
/// - page: Page number of the results to fetch.
/// - Returns: [Contributor]
public func collaborators(
ownerID: String,
repositoryName: String,
affiliation: CollaboratorAffiliationType = .all,
permission: PermissionType? = nil,
perPage: Int = 30,
page: Int = 1
) async throws -> [Collaborator] {
let path = "/repos/\(ownerID)/\(repositoryName)/collaborators"
let endpoint = baseURL.appending(path: path)
let method: HTTPRequest.Method = .get

var queries: [String: String] = [
"affiliation": affiliation.rawValue,
"per_page": String(perPage),
"page": String(page),
]

permission.map {
queries["permission"] = $0.rawValue
}

let request = HTTPRequest(method: method, url: endpoint, queries: queries, headers: headers)

let (data, _) = try await session.data(for: request)

let collaborators = try decode([Collaborator].self, from: data)

return collaborators
}
}
13 changes: 13 additions & 0 deletions Sources/Github/GitHubAPI/Discussions/DiscussionOrderField.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// DiscussionOrderField.swift
//
//
// Created by Asiel Cabrera Gonzalez on 9/14/23.
//

import Foundation

public enum DiscussionOrderField: String, Sendable {
case createdAt = "CREATED_AT"
case updatedAt = "UPDATED_AT"
}
71 changes: 71 additions & 0 deletions Sources/Github/GitHubAPI/Discussions/DiscussionRequest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
//
// DiscussionRequest.swift
//
//
// Created by Asiel Cabrera Gonzalez on 9/14/23.
//

import Foundation
import HttpClient
import HTTPTypes

@available(macOS 13.0, *)
extension GitHub {
public func discussion(
ownerID: String,
repositoryName: String,
discussionNumber: Int,
itemFirst: Int
) async throws -> Discussion {
try await self.discussion(
ownerID: ownerID,
repositoryName: repositoryName,
discussionNumber: discussionNumber,
itemFirst: itemFirst,
itemLast: nil
)
}

public func discussion(
ownerID: String,
repositoryName: String,
discussionNumber: Int,
itemLast: Int
) async throws -> Discussion {
try await self.discussion(
ownerID: ownerID,
repositoryName: repositoryName,
discussionNumber: discussionNumber,
itemFirst: nil,
itemLast: itemLast
)
}

private func discussion(
ownerID: String,
repositoryName: String,
discussionNumber: Int,
itemFirst: Int? = nil,
itemLast: Int? = nil
) async throws -> Discussion {
let endpoint = baseURL.appending(path: "/graphql")
let method: HTTPRequest.Method = .post

let query = """
query {
repository(owner: "\(ownerID)", name: "\(repositoryName)") {
discussion(number: \(discussionNumber)) \(discussionFields(first: itemFirst, last: itemLast))
}
}
"""

let httpRequest = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers)
var urlRequest = URLRequest(httpRequest: httpRequest)!
urlRequest.httpBody = try JSONEncoder().encode(["query": query])

let (data, _) = try await session.data(for: urlRequest)
let response = try decode(DiscussionResponse.self, from: data)

return response.discussion
}
}
46 changes: 46 additions & 0 deletions Sources/Github/GitHubAPI/Discussions/DiscussionResponse.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
//
// DiscussionResponse.swift
//
//
// Created by Asiel Cabrera Gonzalez on 9/14/23.
//

import Foundation

struct DiscussionResponse: Decodable, Sendable {
let discussion: Discussion

private enum CodingKeys: String, CodingKey {
case data
case repository
case discussion
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let dataContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
let repositoryContainer = try dataContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .repository)
let discussion = try repositoryContainer.decode(Discussion.self, forKey: .discussion)
self.discussion = discussion
}
}

struct DiscussionsResponse: Decodable, Sendable {
let discussions: [Discussion]

private enum CodingKeys: String, CodingKey {
case data
case repository
case discussions
case nodes
}

init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let dataContainer = try container.nestedContainer(keyedBy: CodingKeys.self, forKey: .data)
let repositoryContainer = try dataContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .repository)
let discussionsContainer = try repositoryContainer.nestedContainer(keyedBy: CodingKeys.self, forKey: .discussions)
let discussions = try discussionsContainer.decode([Discussion].self, forKey: .nodes)
self.discussions = discussions
}
}
Loading

0 comments on commit 1a96ca6

Please sign in to comment.