From 1a96ca6d9fa4f9d200d50c0994287a8d0ac0f16b Mon Sep 17 00:00:00 2001 From: Asiel Cabrera Date: Thu, 14 Sep 2023 13:21:39 -0400 Subject: [PATCH] httpclient and github api --- Package.resolved | 14 + Package.swift | 31 +- .../Notifications/Notifications.swift | 59 +++ .../Acitivity/Starring/Stargazers.swift | 44 ++ .../CollaboratorAffiliationType.swift | 14 + .../Collaborators/Collaborators.swift | 56 +++ .../Discussions/DiscussionOrderField.swift | 13 + .../Discussions/DiscussionRequest.swift | 71 +++ .../Discussions/DiscussionResponse.swift | 46 ++ .../GitHubAPI/Discussions/Discussions.swift | 220 ++++++++++ .../Gitignore/AllGitignoreTemplateNames.swift | 30 ++ .../Gitignore/GitIgnoreTemplate.swift | 32 ++ Sources/Github/GitHubAPI/Issue/Comments.swift | 82 ++++ .../Github/GitHubAPI/Issue/IssueRequest.swift | 38 ++ .../GitHubAPI/Issue/IssueSearchSortType.swift | 14 + .../GitHubAPI/Issue/IssueSearchState.swift | 16 + Sources/Github/GitHubAPI/Issue/Issues.swift | 81 ++++ .../Github/GitHubAPI/License/Licenses.swift | 36 ++ Sources/Github/GitHubAPI/Oauth/OAuth.swift | 88 ++++ .../Github/GitHubAPI/Oauth/OAuthRequest.swift | 60 +++ .../GitHubAPI/Oauth/OAuthResponse.swift | 30 ++ Sources/Github/GitHubAPI/Oauth/Scope.swift | 46 ++ .../Github/GitHubAPI/Oauth/TokenType.swift | 12 + .../Github/GitHubAPI/Pull/PullRequest.swift | 38 ++ .../GitHubAPI/Pull/PullSearchType.swift | 14 + .../Github/GitHubAPI/Pull/PullSortType.swift | 15 + Sources/Github/GitHubAPI/Pull/Pulls.swift | 61 +++ .../GitHubAPI/Releases/ReleaseRequest.swift | 63 +++ .../Github/GitHubAPI/Releases/Releases.swift | 45 ++ .../Repositories/Foks/CreateFork.swift | 55 +++ .../GitHubAPI/Repositories/Foks/Forks.swift | 48 ++ .../Foks/ForksSearchSortType.swift | 15 + .../ProtentionTags/CreateProtectionTag.swift | 41 ++ .../ProtentionTags/DeleteProtectionTag.swift | 37 ++ .../ProtentionTags/ProtectionTags.swift | 36 ++ .../Repositories/Contributors.swift | 46 ++ .../Repositories/CreateRepository.swift | 43 ++ .../CreateRepositoryWithTemplate.swift | 69 +++ .../Repositories/DeleteRepository.swift | 35 ++ .../Repositories/Repositories/Languages.swift | 36 ++ .../Repositories/NewRepository.swift | 110 +++++ .../OrganizationRepositories.swift | 52 +++ .../OrganizationRepositorySearchType.swift | 18 + .../Repositories/OwnedRepositories.swift | 82 ++++ .../Repositories/Repositories.swift | 57 +++ .../Repositories/RepositoryRequest.swift | 41 ++ .../Repositories/RepositorySearchType.swift | 14 + .../Repositories/RepositorySortType.swift | 15 + .../Repositories/Repositories/SetTopics.swift | 42 ++ .../Repositories/Repositories/Tags.swift | 45 ++ .../Repositories/Repositories/Teams.swift | 45 ++ .../Repositories/Repositories/Topics.swift | 45 ++ .../Repositories/TopicsResponse.swift | 12 + .../Repositories/UpdateRepository.swift | 177 ++++++++ .../Search/RepositorySearchSortType.swift | 15 + .../GitHubAPI/Search/SearchRepositories.swift | 56 +++ .../Github/GitHubAPI/Search/SearchUsers.swift | 56 +++ .../GitHubAPI/Search/UserSortType.swift | 14 + Sources/Github/GitHubAPI/User/Followers.swift | 41 ++ Sources/Github/GitHubAPI/User/Following.swift | 41 ++ Sources/Github/GitHubAPI/User/me.swift | 33 ++ Sources/Github/Github.swift | 9 - Sources/Github/Models/Branch/AutoMerge.swift | 26 ++ Sources/Github/Models/Branch/Branch.swift | 38 ++ Sources/Github/Models/Branch/MergeState.swift | 14 + .../Models/Collaborators/Collaborator.swift | 39 ++ .../Github/Models/Collaborators/Role.swift | 12 + Sources/Github/Models/Comments/Comment.swift | 74 ++++ .../Models/Contributors/Contributor.swift | 39 ++ .../Github/Models/Discussions/Category.swift | 39 ++ .../Models/Discussions/Discussion.swift | 204 +++++++++ .../Discussions/DiscussionComment.swift | 59 +++ .../Models/Discussions/DiscussionLabel.swift | 41 ++ .../Models/Discussions/DiscussionPoll.swift | 46 ++ .../Discussions/DiscussionPollOption.swift | 26 ++ .../Discussions/DiscussionReaction.swift | 29 ++ .../Discussions/DiscussionStateReason.swift | 17 + .../Models/Discussions/DiscussionUser.swift | 29 ++ .../Discussions/SubscriptionState.swift | 14 + .../Models/Gitignore/GitignoreTemplate.swift | 21 + Sources/Github/Models/Issue/Issue.swift | 154 +++++++ Sources/Github/Models/Issue/IssueState.swift | 15 + .../Models/Issue/IssueStateReason.swift | 16 + Sources/Github/Models/Label/Label.swift | 46 ++ Sources/Github/Models/License/Encoding.swift | 12 + Sources/Github/Models/License/License.swift | 66 +++ .../Github/Models/License/LicenseType.swift | 12 + .../Github/Models/Milestone/Milestone.swift | 82 ++++ .../Models/Milestone/MilestoneState.swift | 15 + .../Models/Notifications/Notification.swift | 54 +++ .../Notifications/NotificationReason.swift | 17 + .../Github/Models/Notifications/Subject.swift | 34 ++ .../Models/Notifications/SubjectType.swift | 17 + .../Models/Other/ActiveLockReason.swift | 21 + .../Models/Other/AuthorAssociation.swift | 19 + .../Models/Other/PerformGitHubApp.swift | 82 ++++ .../Github/Models/Other/PermissionType.swift | 16 + Sources/Github/Models/Other/Plan.swift | 34 ++ Sources/Github/Models/Pull/Pull.swift | 158 +++++++ Sources/Github/Models/Pull/PullState.swift | 15 + Sources/Github/Models/Pull/SimplePull.swift | 38 ++ Sources/Github/Models/Reaction/Reaction.swift | 58 +++ Sources/Github/Models/Realeases/Asset.swift | 70 +++ .../Github/Models/Realeases/AssetState.swift | 15 + .../Github/Models/Realeases/ContentType.swift | 20 + Sources/Github/Models/Realeases/Release.swift | 110 +++++ .../Models/Repository/CodeConduct.swift | 38 ++ .../Models/Repository/MergeCommit.swift | 19 + .../Github/Models/Repository/Permission.swift | 30 ++ .../Models/Repository/ProtectionTag.swift | 38 ++ .../Repository/RepositoriesResponse.swift | 20 + .../Github/Models/Repository/Repository.swift | 414 ++++++++++++++++++ .../Models/Repository/SecurityAnalytics.swift | 89 ++++ .../Models/Repository/SimpleLicense.swift | 38 ++ .../Models/Repository/SquashMergeCommit.swift | 19 + Sources/Github/Models/Repository/Tag.swift | 53 +++ .../Repository/TemplateRepository.swift | 402 +++++++++++++++++ .../Github/Models/Repository/Visibility.swift | 13 + .../Models/Teams/NotificationSettings.swift | 12 + Sources/Github/Models/Teams/ParentTeam.swift | 66 +++ Sources/Github/Models/Teams/Privacy.swift | 12 + Sources/Github/Models/Teams/Team.swift | 70 +++ Sources/Github/Models/User/User.swift | 174 ++++++++ Sources/Github/Models/User/UserType.swift | 15 + .../Github/Models/User/UsersResponse.swift | 20 + Sources/Github/exported.swift | 8 + Sources/HttpClient/Authentication/Auth.swift | 8 - Sources/HttpClient/AuthorizationType.swift | 13 + Sources/HttpClient/Codable++.swift | 37 ++ Sources/HttpClient/Decoder.swift | 24 + Sources/HttpClient/ErrorResponse.swift | 28 ++ Sources/HttpClient/GitHub.swift | 40 ++ Sources/HttpClient/GitHubErrorError.swift | 14 + Sources/HttpClient/HTTPRequest++.swift | 28 ++ Sources/HttpClient/OrderType.swift | 13 + .../Protocols/HttpClientProtocol.swift | 20 - .../Protocols/HttpClientResponse.swift | 20 - .../HttpClient/Protocols/RequestHandler.swift | 14 - .../HttpClient/Protocols/RequestInfo.swift | 20 - .../HttpClient/Protocols/RequestOptions.swift | 23 - .../HttpClient/Protocols/TypedResponse.swift | 14 - Sources/HttpClient/RequestError.swift | 16 + Sources/HttpClient/UnknownError.swift | 13 + Sources/HttpClient/Utils/Headers.swift | 12 - .../HttpClient/Utils/HttpClientError.swift | 14 - Sources/HttpClient/Utils/HttpCodes.swift | 38 -- Sources/HttpClient/Utils/MediaTypes.swift | 12 - Sources/HttpClient/Utils/Utils.swift | 28 -- 148 files changed, 6572 insertions(+), 240 deletions(-) create mode 100644 Package.resolved create mode 100644 Sources/Github/GitHubAPI/Acitivity/Notifications/Notifications.swift create mode 100644 Sources/Github/GitHubAPI/Acitivity/Starring/Stargazers.swift create mode 100644 Sources/Github/GitHubAPI/Collaborators/CollaboratorAffiliationType.swift create mode 100644 Sources/Github/GitHubAPI/Collaborators/Collaborators.swift create mode 100644 Sources/Github/GitHubAPI/Discussions/DiscussionOrderField.swift create mode 100644 Sources/Github/GitHubAPI/Discussions/DiscussionRequest.swift create mode 100644 Sources/Github/GitHubAPI/Discussions/DiscussionResponse.swift create mode 100644 Sources/Github/GitHubAPI/Discussions/Discussions.swift create mode 100644 Sources/Github/GitHubAPI/Gitignore/AllGitignoreTemplateNames.swift create mode 100644 Sources/Github/GitHubAPI/Gitignore/GitIgnoreTemplate.swift create mode 100644 Sources/Github/GitHubAPI/Issue/Comments.swift create mode 100644 Sources/Github/GitHubAPI/Issue/IssueRequest.swift create mode 100644 Sources/Github/GitHubAPI/Issue/IssueSearchSortType.swift create mode 100644 Sources/Github/GitHubAPI/Issue/IssueSearchState.swift create mode 100644 Sources/Github/GitHubAPI/Issue/Issues.swift create mode 100644 Sources/Github/GitHubAPI/License/Licenses.swift create mode 100644 Sources/Github/GitHubAPI/Oauth/OAuth.swift create mode 100644 Sources/Github/GitHubAPI/Oauth/OAuthRequest.swift create mode 100644 Sources/Github/GitHubAPI/Oauth/OAuthResponse.swift create mode 100644 Sources/Github/GitHubAPI/Oauth/Scope.swift create mode 100644 Sources/Github/GitHubAPI/Oauth/TokenType.swift create mode 100644 Sources/Github/GitHubAPI/Pull/PullRequest.swift create mode 100644 Sources/Github/GitHubAPI/Pull/PullSearchType.swift create mode 100644 Sources/Github/GitHubAPI/Pull/PullSortType.swift create mode 100644 Sources/Github/GitHubAPI/Pull/Pulls.swift create mode 100644 Sources/Github/GitHubAPI/Releases/ReleaseRequest.swift create mode 100644 Sources/Github/GitHubAPI/Releases/Releases.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Foks/CreateFork.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Foks/Forks.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Foks/ForksSearchSortType.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/ProtentionTags/CreateProtectionTag.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/ProtentionTags/DeleteProtectionTag.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/ProtentionTags/ProtectionTags.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/Contributors.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/CreateRepository.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/CreateRepositoryWithTemplate.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/DeleteRepository.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/Languages.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/NewRepository.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/OrganizationRepositories.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/OrganizationRepositorySearchType.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/OwnedRepositories.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/Repositories.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/RepositoryRequest.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/RepositorySearchType.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/RepositorySortType.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/SetTopics.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/Tags.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/Teams.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/Topics.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/TopicsResponse.swift create mode 100644 Sources/Github/GitHubAPI/Repositories/Repositories/UpdateRepository.swift create mode 100644 Sources/Github/GitHubAPI/Search/RepositorySearchSortType.swift create mode 100644 Sources/Github/GitHubAPI/Search/SearchRepositories.swift create mode 100644 Sources/Github/GitHubAPI/Search/SearchUsers.swift create mode 100644 Sources/Github/GitHubAPI/Search/UserSortType.swift create mode 100644 Sources/Github/GitHubAPI/User/Followers.swift create mode 100644 Sources/Github/GitHubAPI/User/Following.swift create mode 100644 Sources/Github/GitHubAPI/User/me.swift delete mode 100644 Sources/Github/Github.swift create mode 100644 Sources/Github/Models/Branch/AutoMerge.swift create mode 100644 Sources/Github/Models/Branch/Branch.swift create mode 100644 Sources/Github/Models/Branch/MergeState.swift create mode 100644 Sources/Github/Models/Collaborators/Collaborator.swift create mode 100644 Sources/Github/Models/Collaborators/Role.swift create mode 100644 Sources/Github/Models/Comments/Comment.swift create mode 100644 Sources/Github/Models/Contributors/Contributor.swift create mode 100644 Sources/Github/Models/Discussions/Category.swift create mode 100644 Sources/Github/Models/Discussions/Discussion.swift create mode 100644 Sources/Github/Models/Discussions/DiscussionComment.swift create mode 100644 Sources/Github/Models/Discussions/DiscussionLabel.swift create mode 100644 Sources/Github/Models/Discussions/DiscussionPoll.swift create mode 100644 Sources/Github/Models/Discussions/DiscussionPollOption.swift create mode 100644 Sources/Github/Models/Discussions/DiscussionReaction.swift create mode 100644 Sources/Github/Models/Discussions/DiscussionStateReason.swift create mode 100644 Sources/Github/Models/Discussions/DiscussionUser.swift create mode 100644 Sources/Github/Models/Discussions/SubscriptionState.swift create mode 100644 Sources/Github/Models/Gitignore/GitignoreTemplate.swift create mode 100644 Sources/Github/Models/Issue/Issue.swift create mode 100644 Sources/Github/Models/Issue/IssueState.swift create mode 100644 Sources/Github/Models/Issue/IssueStateReason.swift create mode 100644 Sources/Github/Models/Label/Label.swift create mode 100644 Sources/Github/Models/License/Encoding.swift create mode 100644 Sources/Github/Models/License/License.swift create mode 100644 Sources/Github/Models/License/LicenseType.swift create mode 100644 Sources/Github/Models/Milestone/Milestone.swift create mode 100644 Sources/Github/Models/Milestone/MilestoneState.swift create mode 100644 Sources/Github/Models/Notifications/Notification.swift create mode 100644 Sources/Github/Models/Notifications/NotificationReason.swift create mode 100644 Sources/Github/Models/Notifications/Subject.swift create mode 100644 Sources/Github/Models/Notifications/SubjectType.swift create mode 100644 Sources/Github/Models/Other/ActiveLockReason.swift create mode 100644 Sources/Github/Models/Other/AuthorAssociation.swift create mode 100644 Sources/Github/Models/Other/PerformGitHubApp.swift create mode 100644 Sources/Github/Models/Other/PermissionType.swift create mode 100644 Sources/Github/Models/Other/Plan.swift create mode 100644 Sources/Github/Models/Pull/Pull.swift create mode 100644 Sources/Github/Models/Pull/PullState.swift create mode 100644 Sources/Github/Models/Pull/SimplePull.swift create mode 100644 Sources/Github/Models/Reaction/Reaction.swift create mode 100644 Sources/Github/Models/Realeases/Asset.swift create mode 100644 Sources/Github/Models/Realeases/AssetState.swift create mode 100644 Sources/Github/Models/Realeases/ContentType.swift create mode 100644 Sources/Github/Models/Realeases/Release.swift create mode 100644 Sources/Github/Models/Repository/CodeConduct.swift create mode 100644 Sources/Github/Models/Repository/MergeCommit.swift create mode 100644 Sources/Github/Models/Repository/Permission.swift create mode 100644 Sources/Github/Models/Repository/ProtectionTag.swift create mode 100644 Sources/Github/Models/Repository/RepositoriesResponse.swift create mode 100644 Sources/Github/Models/Repository/Repository.swift create mode 100644 Sources/Github/Models/Repository/SecurityAnalytics.swift create mode 100644 Sources/Github/Models/Repository/SimpleLicense.swift create mode 100644 Sources/Github/Models/Repository/SquashMergeCommit.swift create mode 100644 Sources/Github/Models/Repository/Tag.swift create mode 100644 Sources/Github/Models/Repository/TemplateRepository.swift create mode 100644 Sources/Github/Models/Repository/Visibility.swift create mode 100644 Sources/Github/Models/Teams/NotificationSettings.swift create mode 100644 Sources/Github/Models/Teams/ParentTeam.swift create mode 100644 Sources/Github/Models/Teams/Privacy.swift create mode 100644 Sources/Github/Models/Teams/Team.swift create mode 100644 Sources/Github/Models/User/User.swift create mode 100644 Sources/Github/Models/User/UserType.swift create mode 100644 Sources/Github/Models/User/UsersResponse.swift create mode 100644 Sources/Github/exported.swift delete mode 100644 Sources/HttpClient/Authentication/Auth.swift create mode 100644 Sources/HttpClient/AuthorizationType.swift create mode 100644 Sources/HttpClient/Codable++.swift create mode 100644 Sources/HttpClient/Decoder.swift create mode 100644 Sources/HttpClient/ErrorResponse.swift create mode 100644 Sources/HttpClient/GitHub.swift create mode 100644 Sources/HttpClient/GitHubErrorError.swift create mode 100644 Sources/HttpClient/HTTPRequest++.swift create mode 100644 Sources/HttpClient/OrderType.swift delete mode 100644 Sources/HttpClient/Protocols/HttpClientProtocol.swift delete mode 100644 Sources/HttpClient/Protocols/HttpClientResponse.swift delete mode 100644 Sources/HttpClient/Protocols/RequestHandler.swift delete mode 100644 Sources/HttpClient/Protocols/RequestInfo.swift delete mode 100644 Sources/HttpClient/Protocols/RequestOptions.swift delete mode 100644 Sources/HttpClient/Protocols/TypedResponse.swift create mode 100644 Sources/HttpClient/RequestError.swift create mode 100644 Sources/HttpClient/UnknownError.swift delete mode 100644 Sources/HttpClient/Utils/Headers.swift delete mode 100644 Sources/HttpClient/Utils/HttpClientError.swift delete mode 100644 Sources/HttpClient/Utils/HttpCodes.swift delete mode 100644 Sources/HttpClient/Utils/MediaTypes.swift delete mode 100644 Sources/HttpClient/Utils/Utils.swift diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..ffe041a --- /dev/null +++ b/Package.resolved @@ -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 +} diff --git a/Package.swift b/Package.swift index fa1b30c..d305719 100644 --- a/Package.swift +++ b/Package.swift @@ -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"]), diff --git a/Sources/Github/GitHubAPI/Acitivity/Notifications/Notifications.swift b/Sources/Github/GitHubAPI/Acitivity/Notifications/Notifications.swift new file mode 100644 index 0000000..340a122 --- /dev/null +++ b/Sources/Github/GitHubAPI/Acitivity/Notifications/Notifications.swift @@ -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 + } +} diff --git a/Sources/Github/GitHubAPI/Acitivity/Starring/Stargazers.swift b/Sources/Github/GitHubAPI/Acitivity/Starring/Stargazers.swift new file mode 100644 index 0000000..9dc00a3 --- /dev/null +++ b/Sources/Github/GitHubAPI/Acitivity/Starring/Stargazers.swift @@ -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 + } +} diff --git a/Sources/Github/GitHubAPI/Collaborators/CollaboratorAffiliationType.swift b/Sources/Github/GitHubAPI/Collaborators/CollaboratorAffiliationType.swift new file mode 100644 index 0000000..b4f229a --- /dev/null +++ b/Sources/Github/GitHubAPI/Collaborators/CollaboratorAffiliationType.swift @@ -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 +} diff --git a/Sources/Github/GitHubAPI/Collaborators/Collaborators.swift b/Sources/Github/GitHubAPI/Collaborators/Collaborators.swift new file mode 100644 index 0000000..d80fef6 --- /dev/null +++ b/Sources/Github/GitHubAPI/Collaborators/Collaborators.swift @@ -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 + } +} diff --git a/Sources/Github/GitHubAPI/Discussions/DiscussionOrderField.swift b/Sources/Github/GitHubAPI/Discussions/DiscussionOrderField.swift new file mode 100644 index 0000000..29a907e --- /dev/null +++ b/Sources/Github/GitHubAPI/Discussions/DiscussionOrderField.swift @@ -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" +} diff --git a/Sources/Github/GitHubAPI/Discussions/DiscussionRequest.swift b/Sources/Github/GitHubAPI/Discussions/DiscussionRequest.swift new file mode 100644 index 0000000..1cc0fe1 --- /dev/null +++ b/Sources/Github/GitHubAPI/Discussions/DiscussionRequest.swift @@ -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 + } +} diff --git a/Sources/Github/GitHubAPI/Discussions/DiscussionResponse.swift b/Sources/Github/GitHubAPI/Discussions/DiscussionResponse.swift new file mode 100644 index 0000000..c4ef6cf --- /dev/null +++ b/Sources/Github/GitHubAPI/Discussions/DiscussionResponse.swift @@ -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 + } +} diff --git a/Sources/Github/GitHubAPI/Discussions/Discussions.swift b/Sources/Github/GitHubAPI/Discussions/Discussions.swift new file mode 100644 index 0000000..ee6a965 --- /dev/null +++ b/Sources/Github/GitHubAPI/Discussions/Discussions.swift @@ -0,0 +1,220 @@ +// +// Discussions.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + public func discussions( + ownerID: String, + repositoryName: String, + first: Int, + orderBy: DiscussionOrderField = .updatedAt, + direction: OrderType = .desc + ) async throws -> [Discussion] { + try await self.discussions( + ownerID: ownerID, + repositoryName: repositoryName, + first: first, + last: nil, + orderBy: orderBy, + direction: direction + ) + } + + public func discussions( + ownerID: String, + repositoryName: String, + last: Int, + orderBy: DiscussionOrderField = .updatedAt, + direction: OrderType = .desc + ) async throws -> [Discussion] { + try await self.discussions( + ownerID: ownerID, + repositoryName: repositoryName, + first: nil, + last: last, + orderBy: orderBy, + direction: direction + ) + } + + private func discussions( + ownerID: String, + repositoryName: String, + first: Int? = nil, + last: Int? = nil, + orderBy: DiscussionOrderField = .updatedAt, + direction: OrderType = .desc + ) async throws -> [Discussion] { + let endpoint = baseURL.appending(path: "/graphql") + let method: HTTPRequest.Method = .post + + let query = """ + query { + repository(owner: "\(ownerID)", name: "\(repositoryName)") { + discussions( + \(first.map { "first: \($0),"} ?? "") + \(last.map { "last: \($0),"} ?? "") + orderBy: {field: \(orderBy.rawValue), direction: \(direction.rawValue.uppercased())} + ) { + nodes \(discussionFields(first: first, last: last)) + } + } + } + """ + + 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(DiscussionsResponse.self, from: data) + + return response.discussions + } + + func discussionFields(first: Int?, last: Int?) -> String { + """ + { + id + number + url + title + updatedAt + upvoteCount + stateReason + author \(userFields()) + createdAt + activeLockReason + authorAssociation + body + bodyHTML + bodyText + createdViaEmail + databaseId + editor \(userFields()) + includesCreatedEdit + lastEditedAt + locked + viewerCanClose + viewerCanDelete + viewerCanReact + viewerCanReopen + viewerCanSubscribe + viewerCanUpdate + viewerCanUpvote + viewerDidAuthor + viewerHasUpvoted + viewerSubscription + poll \(pollFields(first: first, last: last)) + category \(categoryFields()) + comments(\(last.map { "last: \($0)"} ?? "") \(first.map { "first: \($0)"} ?? "")) { + nodes \(commentFields()) + } + labels(\(last.map { "last: \($0)"} ?? "") \(first.map { "first: \($0)"} ?? "")) { + nodes \(labelFields()) + } + reactions(\(last.map { "last: \($0)"} ?? "") \(first.map { "first: \($0)"} ?? "")) { + nodes \(reactionFields()) + } + } + """ + } + + private func reactionFields() -> String { + """ + { + content + createdAt + databaseId + user \(userFields()) + } + """ + } + + private func labelFields() -> String { + """ + { + name + color + createdAt + description + isDefault + resourcePath + updatedAt + url + } + """ + } + private func categoryFields() -> String { + """ + { + createdAt + description + emoji + emojiHTML + isAnswerable + name + slug + updatedAt + } + """ + } + + private func pollFields(first: Int?, last: Int? = nil) -> String { + """ + { + totalVoteCount + question + viewerCanVote + viewerHasVoted + options(\(last.map { "last: \($0)"} ?? "") \(first.map { "first: \($0)"} ?? "")) { + nodes { + option + totalVoteCount + viewerHasVoted + } + } + } + """ + } + + private func userFields() -> String { + """ + { + login + avatarUrl + resourcePath + url + } + """ + } + + private func commentFields() -> String { + """ + { + id + author \(userFields()) + body + bodyHTML + bodyText + createdAt + createdViaEmail + editor \(userFields()) + authorAssociation + includesCreatedEdit + lastEditedAt + publishedAt + updatedAt + viewerDidAuthor + } + """ + } +} diff --git a/Sources/Github/GitHubAPI/Gitignore/AllGitignoreTemplateNames.swift b/Sources/Github/GitHubAPI/Gitignore/AllGitignoreTemplateNames.swift new file mode 100644 index 0000000..67f49fa --- /dev/null +++ b/Sources/Github/GitHubAPI/Gitignore/AllGitignoreTemplateNames.swift @@ -0,0 +1,30 @@ +// +// AllGitignoreTemplateNames.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get all gitignore templates + /// https://docs.github.com/en/rest/gitignore/gitignore#get-all-gitignore-templates + /// - Returns: [String] + public func allGitignoreTemplateNames() async throws -> [String] { + let path = "/gitignore/templates" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let templates = try decode([String].self, from: data) + + return templates + } +} diff --git a/Sources/Github/GitHubAPI/Gitignore/GitIgnoreTemplate.swift b/Sources/Github/GitHubAPI/Gitignore/GitIgnoreTemplate.swift new file mode 100644 index 0000000..dda23d5 --- /dev/null +++ b/Sources/Github/GitHubAPI/Gitignore/GitIgnoreTemplate.swift @@ -0,0 +1,32 @@ +// +// GitIgnoreTemplate.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get a gitignore template + /// https://docs.github.com/en/rest/gitignore/gitignore#get-a-gitignore-template + /// - Returns: GitignoreTemplate + public func gitignoreTemplate( + name: String + ) async throws -> GitignoreTemplate { + let path = "/gitignore/templates/\(name)" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let template = try decode(GitignoreTemplate.self, from: data) + + return template + } +} diff --git a/Sources/Github/GitHubAPI/Issue/Comments.swift b/Sources/Github/GitHubAPI/Issue/Comments.swift new file mode 100644 index 0000000..a2d19db --- /dev/null +++ b/Sources/Github/GitHubAPI/Issue/Comments.swift @@ -0,0 +1,82 @@ +// +// Comments.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List issue comments + /// https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#list-issue-comments + /// - 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. + /// - issueNumber: The number that identifies the issue. + /// - since: Only show results that were last updated after the given time. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: [Comment] + public func comments( + ownerID: String, + repositoryName: String, + issueNumber: Int, + since: Date? = nil, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Comment] { + let path = "/repos/\(ownerID)/\(repositoryName)/issues/\(issueNumber)/comments" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + var queries: [String: String] = [ + "per_page": String(perPage), + "page": String(page), + ] + + since.map { + let formatter = ISO8601DateFormatter() + queries["since"] = formatter.string(from: $0) + } + + let request = HTTPRequest(method: method, url: endpoint, queries: queries, headers: headers) + + let (data, _) = try await session.data(for: request) + + let comments = try decode([Comment].self, from: data) + + return comments + } + + /// List issue comments + /// https://docs.github.com/en/rest/issues/comments?apiVersion=2022-11-28#list-issue-comments + /// - 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. + /// - pullNumber: The number that identifies the pull. + /// - since: Only show results that were last updated after the given time. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: [Comment] + public func comments( + ownerID: String, + repositoryName: String, + pullNumber: Int, + since: Date? = nil, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Comment] { + try await comments( + ownerID: ownerID, + repositoryName: repositoryName, + issueNumber: pullNumber, + since: since, + perPage: perPage, + page: page + ) + } +} diff --git a/Sources/Github/GitHubAPI/Issue/IssueRequest.swift b/Sources/Github/GitHubAPI/Issue/IssueRequest.swift new file mode 100644 index 0000000..c179108 --- /dev/null +++ b/Sources/Github/GitHubAPI/Issue/IssueRequest.swift @@ -0,0 +1,38 @@ +// +// IssueRequest.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get an issue + /// https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#get-an-issue + /// - 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. + /// - issueNumber: The number that identifies the issue. + /// - Returns: Issue + public func issue( + ownerID: String, + repositoryName: String, + issueNumber: Int + ) async throws -> Issue { + let path = "/repos/\(ownerID)/\(repositoryName)/issues/\(issueNumber)" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let issue = try decode(Issue.self, from: data) + + return issue + } +} diff --git a/Sources/Github/GitHubAPI/Issue/IssueSearchSortType.swift b/Sources/Github/GitHubAPI/Issue/IssueSearchSortType.swift new file mode 100644 index 0000000..319b9e8 --- /dev/null +++ b/Sources/Github/GitHubAPI/Issue/IssueSearchSortType.swift @@ -0,0 +1,14 @@ +// +// IssueSearchSortType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum IssueSearchSortType: String, Sendable { + case created + case updated + case comments +} diff --git a/Sources/Github/GitHubAPI/Issue/IssueSearchState.swift b/Sources/Github/GitHubAPI/Issue/IssueSearchState.swift new file mode 100644 index 0000000..86c2fdf --- /dev/null +++ b/Sources/Github/GitHubAPI/Issue/IssueSearchState.swift @@ -0,0 +1,16 @@ +// +// IssueSearchState.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum IssueSearchState: String, CaseIterable, Identifiable, Sendable { + case open + case closed + case all + + public var id: Self { self } +} diff --git a/Sources/Github/GitHubAPI/Issue/Issues.swift b/Sources/Github/GitHubAPI/Issue/Issues.swift new file mode 100644 index 0000000..7827dc4 --- /dev/null +++ b/Sources/Github/GitHubAPI/Issue/Issues.swift @@ -0,0 +1,81 @@ +// +// Issues.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List issues in a repository. Only open issues will be listed. + /// https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#list-repository-issues + /// - 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. + /// - milestone: If an integer is passed, it should refer to a milestone by its number field. If the string * is passed, issues with any milestone are accepted. If the string none is passed, issues without milestones are returned. + /// - state: Indicates the state of the issues to return. + /// - assignee: Can be the name of a user. Pass in none for issues with no assigned user, and * for issues assigned to any user. + /// - creator: The user that created the issue. + /// - mentioned: A user that's mentioned in the issue. + /// - labels: A list of comma separated label names. Example: bug,ui,@high + /// - sort: What to sort results by. + /// - direction: The direction to sort the results by. + /// - since: Only show notifications updated after the given time. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: [Issue] + public func issues( + ownerID: String, + repositoryName: String, + milestone: Int? = nil, + state: IssueSearchState = .open, + assignee: String? = nil, + creator: String? = nil, + mentioned: String? = nil, + labels: [String] = [], + sort: IssueSearchSortType = .created, + direction: OrderType = .desc, + since: Date? = nil, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Issue] { + let path = "/repos/\(ownerID)/\(repositoryName)/issues" + let method: HTTPRequest.Method = .get + let endpoint = baseURL.appending(path: path) + + var queries: [String: String] = [ + "state": state.rawValue, + "sort": sort.rawValue, + "direction": direction.rawValue, + "per_page": String(perPage), + "page": String(page), + ] + + milestone.map { queries["milestone"] = String($0) } + assignee.map { queries["assignee"] = String($0) } + creator.map { queries["creator"] = String($0) } + mentioned.map { queries["mentioned"] = String($0) } + if !labels.isEmpty { queries["labels"] = labels.joined(separator: ",") } + since.map { + let formatter = ISO8601DateFormatter() + queries["since"] = formatter.string(from: $0) + } + + let request = HTTPRequest( + method: method, + url: endpoint, + queries: queries, + headers: headers + ) + + let (data, _) = try await session.data(for: request) + + let response = try decode([Issue].self, from: data) + + return response + } +} diff --git a/Sources/Github/GitHubAPI/License/Licenses.swift b/Sources/Github/GitHubAPI/License/Licenses.swift new file mode 100644 index 0000000..09405ed --- /dev/null +++ b/Sources/Github/GitHubAPI/License/Licenses.swift @@ -0,0 +1,36 @@ +// +// Licenses.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get the license for a repository + /// https://docs.github.com/en/rest/licenses/licenses?apiVersion=2022-11-28#get-the-license-for-a-repository + /// - 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. + /// - Returns: License + public func license( + ownerID: String, + repositoryName: String + ) async throws -> License { + let path = "/repos/\(ownerID)/\(repositoryName)/license" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let license = try decode(License.self, from: data) + + return license + } +} diff --git a/Sources/Github/GitHubAPI/Oauth/OAuth.swift b/Sources/Github/GitHubAPI/Oauth/OAuth.swift new file mode 100644 index 0000000..267353d --- /dev/null +++ b/Sources/Github/GitHubAPI/Oauth/OAuth.swift @@ -0,0 +1,88 @@ +// +// OAuth.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +/// OAuth +/// https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#2-users-are-redirected-back-to-your-site-by-github +@available(macOS 13.0, *) +public struct OAuth: Sendable { + public enum ResponseType: String, Sendable { + case `default` + case json = "application/json" + case xml = "application/xml" + } + + public var baseURL = URL(string: "https://github.com/login")! + private let path = "/oauth/access_token" + + public var clientID: String + public var clientSecret: String + public var code: String + public var redirectURL: URL? + + public init( + clientID: String, + clientSecret: String, + code: String, + redirectURL: URL? = nil, + responseType: ResponseType = .default + ) { + self.clientID = clientID + self.clientSecret = clientSecret + self.code = code + self.redirectURL = redirectURL + } + + public func request(responseType: ResponseType = .default) -> HTTPRequest { + let endpoint = baseURL.appending(path: path) + + var queries: [String: String] = [ + "client_id": clientID, + "client_secret": clientSecret, + "code": code + ] + + redirectURL.map { queries["redirect_uri"] = $0.absoluteString } + + var urlComponents = URLComponents(url: endpoint, resolvingAgainstBaseURL: true)! + urlComponents.queryItems = queries.map { .init(name: $0.key, value: $0.value) } + + var headers: [String: String] = [:] + if responseType != .default { + headers["Accept"] = responseType.rawValue + } + + return HTTPRequest( + method: .post, + url: endpoint, + queries: queries, + headers: headers + ) + } + + public func authorize(session: URLSession = .shared) async throws -> OAuthResponse { + let request = request(responseType: .json) + let data: Data + let response: HTTPResponse + + do { + (data, response) = try await session.data(for: request) + } catch { + throw GitHubError.request(request: request) + } + + do { + let oauthResponse = try JSONDecoder.github.decode(OAuthResponse.self, from: data) + return oauthResponse + } catch { + throw GitHubError.decode(data: data, response: response) + } + } +} diff --git a/Sources/Github/GitHubAPI/Oauth/OAuthRequest.swift b/Sources/Github/GitHubAPI/Oauth/OAuthRequest.swift new file mode 100644 index 0000000..4aaacde --- /dev/null +++ b/Sources/Github/GitHubAPI/Oauth/OAuthRequest.swift @@ -0,0 +1,60 @@ +// +// OAuthRequest.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +/// OAuth +/// https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/authorizing-oauth-apps#1-request-a-users-github-identity +@available(macOS 13.0, *) +public struct OAuthRequest: Sendable { + public var baseURL = URL(string: "https://github.com")! + public let path = "login/oauth/authorize" + public var clientID: String + public var redirectURL: URL? + public var userID: String? + public var scopes: [Scope] + public var state: String? + public var allowSignUp: Bool + + public init( + clientID: String, + redirectURL: URL? = nil, + userID: String? = nil, + scopes: [Scope] = [], + state: String? = nil, + allowSignUp: Bool = true + ) { + self.clientID = clientID + self.redirectURL = redirectURL + self.userID = userID + self.scopes = scopes + self.state = state + self.allowSignUp = allowSignUp + } + + public func authorizingURL() -> URL { + let endpoint = baseURL.appending(path: path) + var queries: [String: String] = [ + "client_id": clientID, + "allow_signup": allowSignUp.description, + ] + + redirectURL.map { queries["redirect_uri"] = $0.absoluteString } + userID.map { queries["login"] = $0 } + state.map { queries["state"] = $0 } + + if !scopes.isEmpty { + queries["scope"] = scopes.map(\.rawValue).joined(separator: " ") + } + + var urlComponents = URLComponents(url: endpoint, resolvingAgainstBaseURL: true)! + urlComponents.queryItems = queries.map { .init(name: $0.key, value: $0.value) } + + + return urlComponents.url! + } +} diff --git a/Sources/Github/GitHubAPI/Oauth/OAuthResponse.swift b/Sources/Github/GitHubAPI/Oauth/OAuthResponse.swift new file mode 100644 index 0000000..d05e062 --- /dev/null +++ b/Sources/Github/GitHubAPI/Oauth/OAuthResponse.swift @@ -0,0 +1,30 @@ +// +// OAuthResponse.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct OAuthResponse: Decodable, Sendable { + public let accessToken: String + public let scopes: [Scope] + public let tokenType: TokenType + + private enum CodingKeys: String, CodingKey { + case accessToken = "access_token" + case scopes = "scope" + case tokenType = "token_type" + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.accessToken = try container.decode(String.self, forKey: .accessToken) + let rawScopes = try container.decode(String.self, forKey: .scopes) + self.scopes = rawScopes.split(separator: ",").map { .init(rawValue: String($0))! } + + let rawTokenType = try container.decode(String.self, forKey: .tokenType) + self.tokenType = .init(rawValue: rawTokenType)! + } +} diff --git a/Sources/Github/GitHubAPI/Oauth/Scope.swift b/Sources/Github/GitHubAPI/Oauth/Scope.swift new file mode 100644 index 0000000..b21d553 --- /dev/null +++ b/Sources/Github/GitHubAPI/Oauth/Scope.swift @@ -0,0 +1,46 @@ +// +// Scope.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +// Scope +/// https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps +public enum Scope: String, CaseIterable, Sendable { + case repo + case repoStatus + case repoDeployment = "repo_deployment" + case publicRepo = "public_repo" + case repoInvite = "repo:invite" + case securityEvents = "security_events" + case adminRepoHook = "admin:repo_hook" + case writeRepoHook = "write:repo_hook" + case readRepoHook = "read:repo_hook" + case adminOrg = "admin:org" + case writeOrg = "write:org" + case readOrg = "read:org" + case adminPublicKey = "admin:public_key" + case writePublicKey = "write:public_key" + case readPublicKey = "readPublicKey" + case adminOrgHook = "admin:org_hook" + case gist + case notifications + case user + case readUser = "read.user" + case userEmail = "user.email" + case userFollow = "user:follow" + case project + case readProject = "read:project" + case deleteRepo = "delete_repo" + case writePackage = "write:packages" + case readPackage = "read:packages" + case deletePackage = "delete:packages" + case adminGpgKey = "admin:gpg_key" + case writeGpgKey = "write:gpg_key" + case readGpgKey = "read:gpg_key" + case codespace + case workflow +} diff --git a/Sources/Github/GitHubAPI/Oauth/TokenType.swift b/Sources/Github/GitHubAPI/Oauth/TokenType.swift new file mode 100644 index 0000000..8a78998 --- /dev/null +++ b/Sources/Github/GitHubAPI/Oauth/TokenType.swift @@ -0,0 +1,12 @@ +// +// TokenType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum TokenType: String, Sendable { + case bearer +} diff --git a/Sources/Github/GitHubAPI/Pull/PullRequest.swift b/Sources/Github/GitHubAPI/Pull/PullRequest.swift new file mode 100644 index 0000000..4e08e82 --- /dev/null +++ b/Sources/Github/GitHubAPI/Pull/PullRequest.swift @@ -0,0 +1,38 @@ +// +// PullRequest.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get a pull request + /// https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#get-a-pull-request + /// - 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. + /// - pullNumber: The number that identifies the pull request. + /// - Returns: Pull + public func pull( + ownerID: String, + repositoryName: String, + pullNumber: Int + ) async throws -> Pull { + let path = "/repos/\(ownerID)/\(repositoryName)/pulls/\(pullNumber)" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let pull = try decode(Pull.self, from: data) + + return pull + } +} diff --git a/Sources/Github/GitHubAPI/Pull/PullSearchType.swift b/Sources/Github/GitHubAPI/Pull/PullSearchType.swift new file mode 100644 index 0000000..facc226 --- /dev/null +++ b/Sources/Github/GitHubAPI/Pull/PullSearchType.swift @@ -0,0 +1,14 @@ +// +// PullSearchType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum PullSearchType: String, Sendable { + case open + case closed + case all +} diff --git a/Sources/Github/GitHubAPI/Pull/PullSortType.swift b/Sources/Github/GitHubAPI/Pull/PullSortType.swift new file mode 100644 index 0000000..2b25fbf --- /dev/null +++ b/Sources/Github/GitHubAPI/Pull/PullSortType.swift @@ -0,0 +1,15 @@ +// +// PullSortType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum PullSortType: String, Sendable { + case created + case updated + case popularity + case longRunning = "long-running" +} diff --git a/Sources/Github/GitHubAPI/Pull/Pulls.swift b/Sources/Github/GitHubAPI/Pull/Pulls.swift new file mode 100644 index 0000000..e6e9f03 --- /dev/null +++ b/Sources/Github/GitHubAPI/Pull/Pulls.swift @@ -0,0 +1,61 @@ +// +// Pulls.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List pull requests + /// https://docs.github.com/en/rest/pulls/pulls?apiVersion=2022-11-28#list-pull-requests + /// - 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. + /// - state: Either open, closed, or all to filter by state. + /// - head: Filter pulls by head user or head organization and branch name in the format of user:ref-name or organization:ref-name. For example: github:new-script-format or octocat:test-branch. + /// - branchName: Filter pulls by base branch name. Example: gh-pages. + /// - sort: What to sort results by. popularity will sort by the number of comments. long-running will sort by date created and will limit the results to pull requests that have been open for more than a month and have had activity within the past month. + /// - direction: The direction of the sort. Default: desc when sort is created or sort is not specified, otherwise asc. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: [Pull] + public func pulls( + ownerID: String, + repositoryName: String, + state: PullSearchType = .open, + head: String? = nil, + branchName: String? = nil, + sort: PullSortType = .created, + direction: OrderType = .asc, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Pull] { + let path = "/repos/\(ownerID)/\(repositoryName)/pulls" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + var queries: [String: String] = [ + "state": state.rawValue, + "sort": sort.rawValue, + "direction": direction.rawValue, + "per_page": String(perPage), + "page": String(page), + ] + + head.map { queries["head"] = $0 } + branchName.map { queries["base"] = $0 } + + let request = HTTPRequest(method: method, url: endpoint, queries: queries, headers: headers) + + let (data, _) = try await session.data(for: request) + + let pulls = try decode([Pull].self, from: data) + + return pulls + } +} diff --git a/Sources/Github/GitHubAPI/Releases/ReleaseRequest.swift b/Sources/Github/GitHubAPI/Releases/ReleaseRequest.swift new file mode 100644 index 0000000..052cb49 --- /dev/null +++ b/Sources/Github/GitHubAPI/Releases/ReleaseRequest.swift @@ -0,0 +1,63 @@ +// +// ReleaseRequest.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get a release + /// https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-a-release + /// - 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. + /// - releaseID: The unique identifier of the release. + /// - Returns: Release + public func release( + ownerID: String, + repositoryName: String, + releaseID: Int + ) async throws -> Release { + let path = "/repos/\(ownerID)/\(repositoryName)/releases/\(releaseID)" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let release = try decode(Release.self, from: data) + + return release + } + + /// Get a release by tag name + /// https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#get-a-release-by-tag-name + /// - 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. + /// - tag: tag parameter + /// - Returns: Release + public func release( + ownerID: String, + repositoryName: String, + tag: String + ) async throws -> Release { + let path = "/repos/\(ownerID)/\(repositoryName)/releases/tags/\(tag)" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let release = try decode(Release.self, from: data) + + return release + } +} diff --git a/Sources/Github/GitHubAPI/Releases/Releases.swift b/Sources/Github/GitHubAPI/Releases/Releases.swift new file mode 100644 index 0000000..9d31c92 --- /dev/null +++ b/Sources/Github/GitHubAPI/Releases/Releases.swift @@ -0,0 +1,45 @@ +// +// Releases.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List releases + /// https://docs.github.com/en/rest/releases/releases?apiVersion=2022-11-28#list-releases + /// - 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: [Release] + public func releases( + ownerID: String, + repositoryName: String, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Release] { + let path = "/repos/\(ownerID)/\(repositoryName)/releases" + 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 releases = try decode([Release].self, from: data) + + return releases + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Foks/CreateFork.swift b/Sources/Github/GitHubAPI/Repositories/Foks/CreateFork.swift new file mode 100644 index 0000000..75cc229 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Foks/CreateFork.swift @@ -0,0 +1,55 @@ +// +// CreateFork.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Create a fork for the authenticated user. + /// https://docs.github.com/en/rest/repos/forks?apiVersion=2022-11-28#create-a-fork + /// - 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. + /// - organization: Optional parameter to specify the organization name if forking into an organization. + /// - name: When forking from an existing repository, a new name for the fork. + /// - defaultBranchOnly: When forking from an existing repository, fork with only the default branch. + /// - Returns: Repository + public func createFork( + ownerID: String, + repositoryName: String, + organization: String? = nil, + name: String, + defaultBranchOnly: Bool + ) async throws -> Repository { + let path = "/repos/\(ownerID)/\(repositoryName)/forks" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .post + + var body: [String: String] = [ + "name": name, + "default_branch_only": defaultBranchOnly.description, + ] + + organization.map { + body["organization"] = $0 + } + + let bodyData = try JSONEncoder().encode(body) + + let httpRequest = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + var urlRequest = URLRequest(httpRequest: httpRequest)! + urlRequest.httpBody = bodyData + + let (data, _) = try await session.data(for: urlRequest) + + let repository = try decode(Repository.self, from: data) + + return repository + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Foks/Forks.swift b/Sources/Github/GitHubAPI/Repositories/Foks/Forks.swift new file mode 100644 index 0000000..021155f --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Foks/Forks.swift @@ -0,0 +1,48 @@ +// +// Forks.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List forks + /// https://docs.github.com/en/rest/repos/forks?apiVersion=2022-11-28#list-forks + /// - 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. + /// - sort: The sort order. stargazers will sort by star count. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: [Repository] + public func forks( + ownerID: String, + repositoryName: String, + sort: ForksSearchSortType = .newest, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Repository] { + let path = "/repos/\(ownerID)/\(repositoryName)/forks" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let queries: [String: String] = [ + "sort": sort.rawValue, + "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 repositories = try decode([Repository].self, from: data) + + return repositories + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Foks/ForksSearchSortType.swift b/Sources/Github/GitHubAPI/Repositories/Foks/ForksSearchSortType.swift new file mode 100644 index 0000000..9376b10 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Foks/ForksSearchSortType.swift @@ -0,0 +1,15 @@ +// +// ForksSearchSortType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum ForksSearchSortType: String, Sendable { + case newest + case oldest + case stargazers + case watchers +} diff --git a/Sources/Github/GitHubAPI/Repositories/ProtentionTags/CreateProtectionTag.swift b/Sources/Github/GitHubAPI/Repositories/ProtentionTags/CreateProtectionTag.swift new file mode 100644 index 0000000..9fcea3c --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/ProtentionTags/CreateProtectionTag.swift @@ -0,0 +1,41 @@ +// +// CreateProtectionTag.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Create a tag protection state for a repository + /// https://docs.github.com/en/rest/repos/tags?apiVersion=2022-11-28#create-a-tag-protection-state-for-a-repository + /// - 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. + /// - pattern: An optional glob pattern to match against when enforcing tag protection. + /// - Returns: ProtectionTag + public func createProtectionTag( + ownerID: String, + repositoryName: String, + pattern: String + ) async throws -> ProtectionTag { + let path = "/repos/\(ownerID)/\(repositoryName)/tags/protection" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .post + + let body = try JSONEncoder.github.encode(["pattern": pattern]) + let httpRequest = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + var urlRequest = URLRequest(httpRequest: httpRequest)! + urlRequest.httpBody = body + + let (data, _) = try await session.data(for: urlRequest) + + let tag = try decode(ProtectionTag.self, from: data) + + return tag + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/ProtentionTags/DeleteProtectionTag.swift b/Sources/Github/GitHubAPI/Repositories/ProtentionTags/DeleteProtectionTag.swift new file mode 100644 index 0000000..a581aff --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/ProtentionTags/DeleteProtectionTag.swift @@ -0,0 +1,37 @@ +// +// DeleteProtectionTag.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Delete a tag protection state for a repository + /// https://docs.github.com/en/rest/repos/tags?apiVersion=2022-11-28#delete-a-tag-protection-state-for-a-repository + /// - 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. + /// - tagID: The unique identifier of the tag protection. + public func deleteProtectionTag( + ownerID: String, + repositoryName: String, + tagID: ProtectionTag.ID + ) async throws { + let path = "/repos/\(ownerID)/\(repositoryName)/tags/protection/\(tagID)" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .delete + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, response) = try await session.data(for: request) + + if response.status.code != 204 { + throw RequestError.deleteProtectionTag(data: data) + } + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/ProtentionTags/ProtectionTags.swift b/Sources/Github/GitHubAPI/Repositories/ProtentionTags/ProtectionTags.swift new file mode 100644 index 0000000..289b00f --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/ProtentionTags/ProtectionTags.swift @@ -0,0 +1,36 @@ +// +// ProtectionTags.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List tag protection states for a repository + /// https://docs.github.com/en/rest/repos/tags?apiVersion=2022-11-28#list-tag-protection-states-for-a-repository + /// - 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. + /// - Returns: [Tag] + public func protectionTags( + ownerID: String, + repositoryName: String + ) async throws -> [ProtectionTag] { + let path = "/repos/\(ownerID)/\(repositoryName)/tags/protection" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let tags = try decode([ProtectionTag].self, from: data) + + return tags + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/Contributors.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/Contributors.swift new file mode 100644 index 0000000..99ecd76 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/Contributors.swift @@ -0,0 +1,46 @@ +// +// Contributors.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List repository contributors + /// https://docs.github.com/ja/rest/repos/repos?apiVersion=2022-11-28#list-repository-contributors + /// - 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 contributors( + ownerID: String, + repositoryName: String, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Contributor] { + let path = "/repos/\(ownerID)/\(repositoryName)/contributors" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let queries: [String: String] = [ + "anon": "false", // Set False to not get anonymous contributors. anonymous contributors has invalid data model + "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 contributors = try decode([Contributor].self, from: data) + + return contributors + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/CreateRepository.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/CreateRepository.swift new file mode 100644 index 0000000..1f5cff5 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/CreateRepository.swift @@ -0,0 +1,43 @@ +// +// CreateRepository.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get a repository + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository + /// - 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. + /// - Returns: Repository + public func createRepository( + repository: NewRepository + ) async throws -> Repository { + let path = "/user/repos" + let method: HTTPRequest.Method = .post + let endpoint = baseURL.appending(path: path) + + let httpRequest = HTTPRequest( + method: method, + url: endpoint, + queries: [:], + headers: headers + ) + + var urlRequest = URLRequest(httpRequest: httpRequest)! + urlRequest.httpBody = try JSONEncoder.github.encode(repository) + + let (data, _) = try await session.data(for: urlRequest) + + let repository = try decode(Repository.self, from: data) + + return repository + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/CreateRepositoryWithTemplate.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/CreateRepositoryWithTemplate.swift new file mode 100644 index 0000000..d42fccb --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/CreateRepositoryWithTemplate.swift @@ -0,0 +1,69 @@ +// +// CreateRepositoryWithTemplate.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Create a repository using a template + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#create-a-repository-using-a-template + /// - Parameters: + /// - templateOwnerID: The account owner of the template repository. The name is not case sensitive. + /// - templateRepositoryName: The name of the template repository without the .git extension. The name is not case sensitive. + /// - name: The name of the new repository. + /// - description: A short description of the new repository. + /// - includeAllBranches: Set to true to include the directory structure and files from all branches in the template repository, and not just the default branch. + /// - isPrivate: Either true to create a new private repository or false to create a new public one. + /// - Returns: Repository + public func createRepository( + templateOwnerID: String, + templateRepositoryName: String, + name: String, + description: String? = nil, + includeAllBranches: Bool = false, + isPrivate: Bool = false + ) async throws -> Repository { + let path = "/repos/\(templateOwnerID)/\(templateRepositoryName)/generate" + let method: HTTPRequest.Method = .post + let endpoint = baseURL.appending(path: path) + + let newRepository = NewRepositoryWithTemplate( + name: name, + description: description, + includeAllBranches: includeAllBranches, + isPrivate: isPrivate + ) + + let body = try JSONEncoder.github.encode(newRepository) + + let httpRequest = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers ) + var urlRequest = URLRequest(httpRequest: httpRequest)! + urlRequest.httpBody = body + + let (data, _) = try await session.data(for: urlRequest) + + let repository = try decode(Repository.self, from: data) + + return repository + } +} + +struct NewRepositoryWithTemplate: Encodable { + let name: String + let description: String? + let includeAllBranches: Bool + let isPrivate: Bool + + private enum CodingKeys: String, CodingKey { + case name + case description + case includeAllBranches = "include_all_branches" + case isPrivate = "private" + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/DeleteRepository.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/DeleteRepository.swift new file mode 100644 index 0000000..eab66e0 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/DeleteRepository.swift @@ -0,0 +1,35 @@ +// +// DeleteRepository.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Delete a repository + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#delete-a-repository + /// - 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. + public func deleteRepository( + ownerID: String, + repositoryName: String + ) async throws { + let path = "/repos/\(ownerID)/\(repositoryName)" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .delete + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, httpResponse) = try await session.data(for: request) + + if httpResponse.status.code != 204 { + throw RequestError.deleteRepository(data: data) + } + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/Languages.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/Languages.swift new file mode 100644 index 0000000..55a7e47 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/Languages.swift @@ -0,0 +1,36 @@ +// +// Languages.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List repository languages + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-languages + /// - 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. + /// - Returns: [String: Int]: [Language Name: Bytes of Code] + public func languages( + ownerID: String, + repositoryName: String + ) async throws -> [String: Int] { + let path = "/repos/\(ownerID)/\(repositoryName)/languages" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let request = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + + let (data, _) = try await session.data(for: request) + + let languages = try decode([String: Int].self, from: data) + + return languages + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/NewRepository.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/NewRepository.swift new file mode 100644 index 0000000..35dfab7 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/NewRepository.swift @@ -0,0 +1,110 @@ +// +// NewRepository.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct NewRepository: Encodable, Sendable { + public var name: String + public var description: String? + public var homepage: String? + public var isPrivate: Bool + public var hasIssues: Bool + public var hasProjects: Bool + public var hasWiki: Bool + public var hasDiscussions: Bool + public var teamID: String? + public var autoInit: Bool + public var gitignoreTemplate: String? + public var licenseTemplate: String? + public var allowSquashMerge: Bool + public var allowMergeCommit: Bool + public var allowRebaseMerge: Bool + public var allowAutoMerge: Bool + public var deleteBranchOnMerge: Bool + public var squashMergeCommitTitle: SquashMergeCommitTitle? + public var squashMergeCommitMessage: SquashMergeCommitMessage? + public var mergeCommitTitle: MergeCommitTitle? + public var mergeCommitMessage: MergeCommitMessage? + public var hasDownloads: Bool + public var isTemplate: Bool + + public init( + name: String, + description: String? = nil, + homepage: String? = nil, + isPrivate: Bool = false, + hasIssues: Bool = true, + hasProjects: Bool = true, + hasWiki: Bool = true, + hasDiscussions: Bool = false, + teamID: String? = nil, + autoInit: Bool = false, + gitignoreTemplate: String? = nil, + licenseTemplate: String? = nil, + allowSquashMerge: Bool = true, + allowMergeCommit: Bool = true, + allowRebaseMerge: Bool = true, + allowAutoMerge: Bool = false, + deleteBranchOnMerge: Bool = false, + squashMergeCommitTitle: SquashMergeCommitTitle? = nil, + squashMergeCommitMessage: SquashMergeCommitMessage? = nil, + mergeCommitTitle: MergeCommitTitle? = nil, + mergeCommitMessage: MergeCommitMessage? = nil, + hasDownloads: Bool = true, + isTemplate: Bool = false + ) { + self.name = name + self.description = description + self.homepage = homepage + self.isPrivate = isPrivate + self.hasIssues = hasIssues + self.hasProjects = hasProjects + self.hasWiki = hasWiki + self.hasDiscussions = hasDiscussions + self.teamID = teamID + self.autoInit = autoInit + self.gitignoreTemplate = gitignoreTemplate + self.licenseTemplate = licenseTemplate + self.allowSquashMerge = allowSquashMerge + self.allowMergeCommit = allowMergeCommit + self.allowRebaseMerge = allowRebaseMerge + self.allowAutoMerge = allowAutoMerge + self.deleteBranchOnMerge = deleteBranchOnMerge + self.squashMergeCommitTitle = squashMergeCommitTitle + self.squashMergeCommitMessage = squashMergeCommitMessage + self.mergeCommitTitle = mergeCommitTitle + self.mergeCommitMessage = mergeCommitMessage + self.hasDownloads = hasDownloads + self.isTemplate = isTemplate + } + + private enum CodingKeys: String, CodingKey { + case name + case description + case homepage + case isPrivate = "private" + case hasIssues = "has_issues" + case hasProjects = "has_projects" + case hasWiki = "has_wiki" + case hasDiscussions = "has_discussions" + case teamID = "team_id" + case autoInit = "auto_init" + case gitignoreTemplate = "gitignore_template" + case licenseTemplate = "license_template" + case allowSquashMerge = "allow_squash_merge" + case allowMergeCommit = "allow_merge_commit" + case allowRebaseMerge = "allow_rebase_merge" + case allowAutoMerge = "allow_auto_merge" + case deleteBranchOnMerge = "delete_branch_on_merge" + case squashMergeCommitTitle = "squash_merge_commit_title" + case squashMergeCommitMessage = "squash_merge_commit_message" + case mergeCommitTitle = "merge_commit_title" + case mergeCommitMessage = "merge_commit_message" + case hasDownloads = "has_downloads" + case isTemplate = "is_template" + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/OrganizationRepositories.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/OrganizationRepositories.swift new file mode 100644 index 0000000..03c686b --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/OrganizationRepositories.swift @@ -0,0 +1,52 @@ +// +// OrganizationRepositories.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List organization repositories + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-organization-repositories + /// - Parameters: + /// - organization: The organization name. The name is not case sensitive. + /// - type: Specifies the types of repositories you want returned. + /// - sort: The property to sort the results by + /// - direction: The order to sort by. Default: asc when using full_name, otherwise desc. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: [Repository] + func repositories( + organization: String, + type: OrganizationRepositorySearchType = .all, + sort: RepositorySortType = .created, + direction: OrderType = .asc, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Repository] { + let path = "/orgs/\(organization)/repos" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .get + + let queries: [String: String] = [ + "type": type.rawValue, + "sort": sort.rawValue, + "order": direction.rawValue, + "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 repositories = try decode([Repository].self, from: data) + + return repositories + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/OrganizationRepositorySearchType.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/OrganizationRepositorySearchType.swift new file mode 100644 index 0000000..7e6d7f3 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/OrganizationRepositorySearchType.swift @@ -0,0 +1,18 @@ +// +// OrganizationRepositorySearchType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum OrganizationRepositorySearchType: String, Sendable { + case all + case `public` + case `private` + case forks + case sources + case member +} + diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/OwnedRepositories.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/OwnedRepositories.swift new file mode 100644 index 0000000..32088dc --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/OwnedRepositories.swift @@ -0,0 +1,82 @@ +// +// OwnedRepositories.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +public enum RepositoryAffiliationType: String, Sendable { + case owner + case collaborator + case organizationMember = "organization_member" +} + +public enum RepositorySearchVisibility: String, Sendable { + case all + case `public` + case `private` +} + + +@available(macOS 13.0, *) +extension GitHub { + /// List repositories for the authenticated user + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-the-authenticated-user + /// - Parameters: + /// - visibility: Limit results to repositories with the specified visibility. + /// - affiliation: Repository affiliation + /// - type: Limit results to repositories of the specified type. Will cause a 422 error if used in the same request as visibility or affiliation. + /// - sort: The property to sort the results by. + /// - direction: The order to sort by. Default: asc when using full_name, otherwise desc. + /// - since: Only show repositories updated after the given time. + /// - before: Only show repositories updated before the given time. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: [Repository] + public func ownedRepositories( + visibility: RepositorySearchVisibility? = .all, + affiliation: Set? = [.collaborator, .organizationMember, .owner], + type: RepositorySearchType? = .all, + sort: RepositorySortType = .fullName, + direction: OrderType = .asc, + since: Date? = nil, + before: Date? = nil, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Repository] { + let path = "/user/repos" + let method: HTTPRequest.Method = .get + let endpoint = baseURL.appending(path: path) + + var queries: [String: String] = [ + "sort": sort.rawValue, + "direction": direction.rawValue, + "per_page": String(perPage), + "page": String(page), + ] + + visibility.map { queries["visibility"] = $0.rawValue } + affiliation.map { queries["affiliation"] = $0.map(\.rawValue).joined(separator: ",") } + type.map { queries["type"] = $0.rawValue } + + 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 response = try decode([Repository].self, from: data) + return response + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/Repositories.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/Repositories.swift new file mode 100644 index 0000000..1f9337b --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/Repositories.swift @@ -0,0 +1,57 @@ +// +// Repositories.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List repositories for a user + /// Lists public repositories for the specified user. Note: For GitHub AE, this endpoint will list internal repositories for the specified user. + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repositories-for-a-user + /// - Parameters: + /// - ownerID: The handle for the GitHub user account. + /// - type: Limit results to repositories of the specified type. + /// - sort: The property to sort the results by. + /// - direction: The order to sort by. Default: asc when using full_name, otherwise desc. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: [Repository] + public func repositories( + ownerID: String, + type: RepositorySearchType = .owner, + sort: RepositorySortType = .fullName, + direction: OrderType = .asc, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Repository] { + let path = "/users/\(ownerID)/repos" + let method: HTTPRequest.Method = .get + let endpoint = baseURL.appending(path: path) + + let queries: [String: String] = [ + "type": type.rawValue, + "sort": sort.rawValue, + "direction": direction.rawValue, + "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 response = try decode([Repository].self, from: data) + return response + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/RepositoryRequest.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/RepositoryRequest.swift new file mode 100644 index 0000000..a3cdb55 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/RepositoryRequest.swift @@ -0,0 +1,41 @@ +// +// RepositoryRequest.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get a repository + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-a-repository + /// - 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. + /// - Returns: Repository + public func repository( + ownerID: String, + repositoryName: String + ) async throws -> Repository { + let path = "/repos/\(ownerID)/\(repositoryName)" + let method: HTTPRequest.Method = .get + let endpoint = baseURL.appending(path: path) + + let request = HTTPRequest( + method: method, + url: endpoint, + queries: [:], + headers: headers + ) + + let (data, _) = try await session.data(for: request) + + let repository = try decode(Repository.self, from: data) + + return repository + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/RepositorySearchType.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/RepositorySearchType.swift new file mode 100644 index 0000000..248792f --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/RepositorySearchType.swift @@ -0,0 +1,14 @@ +// +// RepositorySearchType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum RepositorySearchType: String, Sendable { + case all + case owner + case member +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/RepositorySortType.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/RepositorySortType.swift new file mode 100644 index 0000000..615c8e0 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/RepositorySortType.swift @@ -0,0 +1,15 @@ +// +// RepositorySortType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum RepositorySortType: String, Sendable { + case created + case updated + case pushed + case fullName = "full_name" +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/SetTopics.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/SetTopics.swift new file mode 100644 index 0000000..d8b13ea --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/SetTopics.swift @@ -0,0 +1,42 @@ +// +// SetTopics.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Replace all repository topics + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#replace-all-repository-topics + /// - 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. + /// - topics: An array of topics to add to the repository. Pass one or more topics to replace the set of existing topics. Send an empty array ([]) to clear all topics from the repository. Note: Topic names cannot contain uppercase letters. + /// - Returns: [String] + public func setTopics( + ownerID: String, + repositoryName: String, + topics: [String] + ) async throws -> [String] { + let path = "/repos/\(ownerID)/\(repositoryName)/topics" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .put + + let body = TopicsResponse(names: topics) + + let httpRequest = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + var urlRequest = URLRequest(httpRequest: httpRequest)! + urlRequest.httpBody = try JSONEncoder.github.encode(body) + + let (data, _) = try await session.data(for: urlRequest) + + let response = try decode(TopicsResponse.self, from: data) + + return response.names + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/Tags.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/Tags.swift new file mode 100644 index 0000000..e3acfe7 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/Tags.swift @@ -0,0 +1,45 @@ +// +// Tags.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List repository tags + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-tags + /// - 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: [Tag] + public func tags( + ownerID: String, + repositoryName: String, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Tag] { + let path = "/repos/\(ownerID)/\(repositoryName)/tags" + 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 tags = try decode([Tag].self, from: data) + + return tags + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/Teams.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/Teams.swift new file mode 100644 index 0000000..62c32f7 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/Teams.swift @@ -0,0 +1,45 @@ +// +// Teams.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// List repository teams + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-teams + /// - 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: [Team] + public func teams( + ownerID: String, + repositoryName: String, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [Team] { + let path = "/repos/\(ownerID)/\(repositoryName)/teams" + 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 teams = try decode([Team].self, from: data) + + return teams + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/Topics.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/Topics.swift new file mode 100644 index 0000000..296e436 --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/Topics.swift @@ -0,0 +1,45 @@ +// +// Topics.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get all repository topics + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#get-all-repository-topics + /// - 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: [String] + public func topics( + ownerID: String, + repositoryName: String, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [String] { + let path = "/repos/\(ownerID)/\(repositoryName)/topics" + 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 response = try decode(TopicsResponse.self, from: data) + + return response.names + } +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/TopicsResponse.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/TopicsResponse.swift new file mode 100644 index 0000000..27cbdfc --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/TopicsResponse.swift @@ -0,0 +1,12 @@ +// +// TopicsResponse.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +struct TopicsResponse: Codable { + let names: [String] +} diff --git a/Sources/Github/GitHubAPI/Repositories/Repositories/UpdateRepository.swift b/Sources/Github/GitHubAPI/Repositories/Repositories/UpdateRepository.swift new file mode 100644 index 0000000..a265b9d --- /dev/null +++ b/Sources/Github/GitHubAPI/Repositories/Repositories/UpdateRepository.swift @@ -0,0 +1,177 @@ +// +// UpdateRepository.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Update a repository + /// https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#update-a-repository + /// - 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. + /// - repository: Update Repository + /// - Returns: Repository + public func updateRepository( + ownerID: String, + repositoryName: String, + repository: UpdateRepository + ) async throws -> Repository { + let path = "/repos/\(ownerID)/\(repositoryName)" + let endpoint = baseURL.appending(path: path) + let method: HTTPRequest.Method = .patch + + let body = try JSONEncoder.github.encode(repository) + + let httpRequest = HTTPRequest(method: method, url: endpoint, queries: [:], headers: headers) + var urlRequest = URLRequest(httpRequest: httpRequest)! + urlRequest.httpBody = body + + let (data, _) = try await session.data(for: urlRequest) + + let repository = try decode(Repository.self, from: data) + + return repository + } +} + +public struct UpdateRepository: Encodable, Sendable { + public var name: String? + public var homepage: String? + public var isPrivate: Bool? + public var visibility: Visibility? + public var securityAnalytics: SecurityAnalytics? + public var hasIssues: Bool? + public var hasProjects: Bool? + public var hasWiki: Bool? + public var isTemplate: Bool? + public var defaultBranch: String? + public var allowSquashMerge: Bool? + public var allowMergeCommit: Bool? + public var allowRebaseMerge: Bool? + public var allowAutoMerge: Bool? + public var deleteBranchOnMerge: Bool? + public var allowUpdateBranch: Bool? + public var useSquashPrTitleAsDefault: Bool? + public var squashMergeCommitTitle: SquashMergeCommitTitle? + public var squashMergeCommitMessage: SquashMergeCommitMessage? + public var mergeCommitTitle: MergeCommitTitle? + public var mergeCommitMessage: MergeCommitMessage? + public var isArchived: Bool? + public var allowForking: Bool? + public var webCommitSignoffRequired: Bool? + + public init( + name: String? = nil, + homepage: String? = nil, + isPrivate: Bool? = nil, + visibility: Visibility? = nil, + securityAnalytics: SecurityAnalytics? = nil, + hasIssues: Bool? = nil, + hasProjects: Bool? = nil, + hasWiki: Bool? = nil, + isTemplate: Bool? = nil, + defaultBranch: String? = nil, + allowSquashMerge: Bool? = nil, + allowMergeCommit: Bool? = nil, + allowRebaseMerge: Bool? = nil, + allowAutoMerge: Bool? = nil, + deleteBranchOnMerge: Bool? = nil, + allowUpdateBranch: Bool? = nil, + useSquashPrTitleAsDefault: Bool? = nil, + squashMergeCommitTitle: SquashMergeCommitTitle? = nil, + squashMergeCommitMessage: SquashMergeCommitMessage? = nil, + mergeCommitTitle: MergeCommitTitle? = nil, + mergeCommitMessage: MergeCommitMessage? = nil, + isArchived: Bool? = nil, + allowForking: Bool? = nil, + webCommitSignoffRequired: Bool? = nil + ) { + self.name = name + self.homepage = homepage + self.isPrivate = isPrivate + self.visibility = visibility + self.securityAnalytics = securityAnalytics + self.hasIssues = hasIssues + self.hasProjects = hasProjects + self.hasWiki = hasWiki + self.isTemplate = isTemplate + self.defaultBranch = defaultBranch + self.allowSquashMerge = allowSquashMerge + self.allowMergeCommit = allowMergeCommit + self.allowRebaseMerge = allowRebaseMerge + self.allowAutoMerge = allowAutoMerge + self.deleteBranchOnMerge = deleteBranchOnMerge + self.allowUpdateBranch = allowUpdateBranch + self.useSquashPrTitleAsDefault = useSquashPrTitleAsDefault + self.squashMergeCommitTitle = squashMergeCommitTitle + self.squashMergeCommitMessage = squashMergeCommitMessage + self.mergeCommitTitle = mergeCommitTitle + self.mergeCommitMessage = mergeCommitMessage + self.isArchived = isArchived + self.allowForking = allowForking + self.webCommitSignoffRequired = webCommitSignoffRequired + } + + private enum CodingKeys: String, CodingKey { + case name + case homepage + case isPrivate = "private" + case visibility + case securityAnalytics = "security_and_analysis" + case hasIssues = "has_issues" + case hasProjects = "has_projects" + case hasWiki = "has_wiki" + case isTemplate = "is_template" + case defaultBranch = "default_branch" + case allowSquashMerge = "allow_squash_merge" + case allowMergeCommit = "allow_merge_commit" + case allowRebaseMerge = "allow_rebase_merge" + case allowAutoMerge = "allow_auto_merge" + case deleteBranchOnMerge = "delete_branch_on_merge" + case allowUpdateBranch = "allow_update_branch" + case useSquashPrTitleAsDefault = "use_squash_pr_title_as_default" + case squashMergeCommitTitle = "squash_merge_commit_title" + case squashMergeCommitMessage = "squash_merge_commit_message" + case mergeCommitTitle = "merge_commit_title" + case mergeCommitMessage = "merge_commit_message" + case isArchived = "archived" + case allowForking = "allow_forking" + case webCommitSignoffRequired = "web_commit_signoff_required" + } +} + +extension UpdateRepository { + public init(repository: Repository) { + self.name = repository.name + self.homepage = repository.homepage + self.isPrivate = repository.isPrivate + self.visibility = repository.visibility + self.securityAnalytics = repository.securityAnalytics + self.hasIssues = repository.hasIssues + self.hasProjects = repository.hasProjects + self.hasWiki = repository.hasWiki + self.isTemplate = repository.isTemplate + self.defaultBranch = repository.defaultBranch + self.allowSquashMerge = repository.allowSquashMerge + self.allowMergeCommit = repository.allowMergeCommit + self.allowRebaseMerge = repository.allowRebaseMerge + self.allowAutoMerge = repository.allowAutoMerge + self.deleteBranchOnMerge = repository.deleteBranchOnMerge + self.allowUpdateBranch = repository.allowUpdateBranch + self.useSquashPrTitleAsDefault = repository.useSquashPrTitleAsDefault + self.squashMergeCommitTitle = repository.squashMergeCommitTitle + self.squashMergeCommitMessage = repository.squashMergeCommitMessage + self.mergeCommitTitle = repository.mergeCommitTitle + self.mergeCommitMessage = repository.mergeCommitMessage + self.isArchived = repository.isArchived + self.allowForking = repository.allowForking + self.webCommitSignoffRequired = repository.webCommitSignoffRequired + } +} diff --git a/Sources/Github/GitHubAPI/Search/RepositorySearchSortType.swift b/Sources/Github/GitHubAPI/Search/RepositorySearchSortType.swift new file mode 100644 index 0000000..1f259a6 --- /dev/null +++ b/Sources/Github/GitHubAPI/Search/RepositorySearchSortType.swift @@ -0,0 +1,15 @@ +// +// RepositorySearchSortType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum RepositorySearchSortType: String, Sendable { + case stars + case forks + case helpWantedIssues = "help-wanted-issues" + case updated +} diff --git a/Sources/Github/GitHubAPI/Search/SearchRepositories.swift b/Sources/Github/GitHubAPI/Search/SearchRepositories.swift new file mode 100644 index 0000000..f1893c9 --- /dev/null +++ b/Sources/Github/GitHubAPI/Search/SearchRepositories.swift @@ -0,0 +1,56 @@ +// +// SearchRepositories.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Search repositories + /// https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-repositories + /// - Parameters: + /// - query: The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The REST API supports the same qualifiers as the web interface for GitHub. To learn more about the format of the query, see Constructing a search query. See "Searching for repositories" for a detailed list of qualifiers. + /// - sort: Sorts the results of your query by number of stars, forks, or help-wanted-issues or how recently the items were updated. Default: best match + /// - order: Determines whether the first search result returned is the highest number of matches (desc) or lowest number of matches (asc). This parameter is ignored unless you provide sort. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + /// - Returns: RepositoriesResponse + public func searchRepositories( + query: String, + sort: RepositorySearchSortType? = nil, + order: OrderType = .desc, + perPage: Int = 30, + page: Int = 1 + ) async throws -> RepositoriesResponse { + let path = "/search/repositories" + let method: HTTPRequest.Method = .get + let endpoint = baseURL.appending(path: path) + + var queries: [String: String] = [ + "q": query, + "order": order.rawValue, + "per_page": String(perPage), + "page": String(page) + ] + + sort.map { queries["sort"] = $0.rawValue } + + let request = HTTPRequest( + method: method, + url: endpoint, + queries: queries, + headers: headers + ) + + let (data, _) = try await session.data(for: request) + + let response = try decode(RepositoriesResponse.self, from: data) + + return response + } +} diff --git a/Sources/Github/GitHubAPI/Search/SearchUsers.swift b/Sources/Github/GitHubAPI/Search/SearchUsers.swift new file mode 100644 index 0000000..0eed078 --- /dev/null +++ b/Sources/Github/GitHubAPI/Search/SearchUsers.swift @@ -0,0 +1,56 @@ +// +// SearchUsers.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Find users via various criteria. This method returns up to 100 results + /// https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-users + /// - Parameter query: The query contains one or more search keywords and qualifiers. Qualifiers allow you to limit your search to specific areas of GitHub. The REST API supports the same qualifiers as the web interface for GitHub. To learn more about the format of the query, see Constructing a search query. See "Searching users" for a detailed list of qualifiers. + /// - Parameter sort: Sorts the results of your query by number of followers or repositories, or when the person joined GitHub. Default: best match + /// - Parameter order: Determines whether the first search result returned is the highest number of matches (desc) or lowest number of matches (asc). This parameter is ignored unless you provide sort. + /// - Parameter perPage: The number of results per page (max 100). + /// - Parameter page: Page number of the results to fetch. + /// - Returns: UsersResponse + public func searchUsers( + query: String, + sort: UserSortType? = nil, + order: OrderType = .desc, + perPage: Int = 30, + page: Int = 1 + ) async throws -> UsersResponse { + let path = "/search/users" + let method: HTTPRequest.Method = .get + + let endpoint = baseURL.appending(path: path) + + var queries: [String: String] = [ + "q": query, + "order": order.rawValue, + "per_page": String(perPage), + "page": String(page), + ] + + sort.map { queries["sort"] = $0.rawValue } + + let request = HTTPRequest( + method: method, + url: endpoint, + queries: queries, + headers: headers + ) + + let (data, _) = try await session.data(for: request) + + let response = try decode(UsersResponse.self, from: data) + + return response + } +} diff --git a/Sources/Github/GitHubAPI/Search/UserSortType.swift b/Sources/Github/GitHubAPI/Search/UserSortType.swift new file mode 100644 index 0000000..4056a02 --- /dev/null +++ b/Sources/Github/GitHubAPI/Search/UserSortType.swift @@ -0,0 +1,14 @@ +// +// UserSortType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum UserSortType: String, Sendable { + case followers + case repositories + case joined +} diff --git a/Sources/Github/GitHubAPI/User/Followers.swift b/Sources/Github/GitHubAPI/User/Followers.swift new file mode 100644 index 0000000..5ce1b52 --- /dev/null +++ b/Sources/Github/GitHubAPI/User/Followers.swift @@ -0,0 +1,41 @@ +// +// Followers.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Lists the people following the specified user. + /// https://docs.github.com/en/rest/users/followers?apiVersion=2022-11-28#list-followers-of-a-user + /// - Parameters: + /// - userID: The handle for the GitHub user account. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + public func followers( + userID: String, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [User] { + let path = "/users/\(userID)/followers" + 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 + } +} diff --git a/Sources/Github/GitHubAPI/User/Following.swift b/Sources/Github/GitHubAPI/User/Following.swift new file mode 100644 index 0000000..23f22e9 --- /dev/null +++ b/Sources/Github/GitHubAPI/User/Following.swift @@ -0,0 +1,41 @@ +// +// Following.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Lists the people who the specified user follows. + /// https://docs.github.com/en/rest/users/followers?apiVersion=2022-11-28#list-the-people-a-user-follows + /// - Parameters: + /// - userID: The handle for the GitHub user account. + /// - perPage: The number of results per page (max 100). + /// - page: Page number of the results to fetch. + public func following( + userID: String, + perPage: Int = 30, + page: Int = 1 + ) async throws -> [User] { + let path = "/users/\(userID)/following" + 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 + } +} diff --git a/Sources/Github/GitHubAPI/User/me.swift b/Sources/Github/GitHubAPI/User/me.swift new file mode 100644 index 0000000..7c183e9 --- /dev/null +++ b/Sources/Github/GitHubAPI/User/me.swift @@ -0,0 +1,33 @@ +// +// me.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HttpClient +import HTTPTypes + +@available(macOS 13.0, *) +extension GitHub { + /// Get the authenticated user + /// - Returns: User + public func me() async throws -> User { + let path = "/user" + let method: HTTPRequest.Method = .get + let endpoint = baseURL.appending(path: path) + + let request = HTTPRequest( + method: method, + url: endpoint, + queries: [:], + headers: headers + ) + + let (data, _) = try await session.data(for: request) + + let user = try decode(User.self, from: data) + return user + } +} diff --git a/Sources/Github/Github.swift b/Sources/Github/Github.swift deleted file mode 100644 index 8a73cd3..0000000 --- a/Sources/Github/Github.swift +++ /dev/null @@ -1,9 +0,0 @@ -// -// Github.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/18/23. -// - -import Foundation - diff --git a/Sources/Github/Models/Branch/AutoMerge.swift b/Sources/Github/Models/Branch/AutoMerge.swift new file mode 100644 index 0000000..cf3ca47 --- /dev/null +++ b/Sources/Github/Models/Branch/AutoMerge.swift @@ -0,0 +1,26 @@ +// +// AutoMerge.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct AutoMerge: Codable, Hashable, Sendable { + public let enabledBy: User + public let method: Method + public let commitTitle: String? + public let commitMessage: String + + public init(enabledBy: User, method: Method, commitTitle: String?, commitMessage: String) { + self.enabledBy = enabledBy + self.method = method + self.commitTitle = commitTitle + self.commitMessage = commitMessage + } + + private enum CodingKeys: String, CodingKey { + case enabledBy = "enabled_by", method = "merge_method", commitTitle = "commit_title", commitMessage = "commit_message" + } +} diff --git a/Sources/Github/Models/Branch/Branch.swift b/Sources/Github/Models/Branch/Branch.swift new file mode 100644 index 0000000..758138a --- /dev/null +++ b/Sources/Github/Models/Branch/Branch.swift @@ -0,0 +1,38 @@ +// +// Branch.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Branch: Codable, Sendable, Hashable { + public let label: String? + public let ref: String + public let sha: String + public let user: User? + public let repository: Repository? + + public init( + label: String?, + ref: String, + sha: String, + user: User?, + repository: Repository? + ) { + self.label = label + self.ref = ref + self.sha = sha + self.user = user + self.repository = repository + } + + private enum CodingKeys: String, CodingKey { + case label + case ref + case sha + case user + case repository = "repo" + } +} diff --git a/Sources/Github/Models/Branch/MergeState.swift b/Sources/Github/Models/Branch/MergeState.swift new file mode 100644 index 0000000..f986931 --- /dev/null +++ b/Sources/Github/Models/Branch/MergeState.swift @@ -0,0 +1,14 @@ +// +// MergeState.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension AutoMerge { + public enum Method: String, Codable, Sendable { + case merge, squash, rebase + } +} diff --git a/Sources/Github/Models/Collaborators/Collaborator.swift b/Sources/Github/Models/Collaborators/Collaborator.swift new file mode 100644 index 0000000..cee3193 --- /dev/null +++ b/Sources/Github/Models/Collaborators/Collaborator.swift @@ -0,0 +1,39 @@ +// +// Collaborator.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Collaborator: Codable, Sendable, Hashable, Identifiable { + public var id: Int { user.id } + public let user: User + public let role: Role + + public init( + user: User, + role: Role + ) { + self.user = user + self.role = role + } + + private enum CodingKeys: String, CodingKey { + case role = "role_name" + } + + public init(from decoder: any Decoder) throws { + self.user = try .init(from: decoder) + let container = try decoder.container(keyedBy: CodingKeys.self) + self.role = try container.decode(Role.self, forKey: .role) + } + + public func encode(to encoder: any Encoder) throws { + try self.user.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(role, forKey: .role) + } +} diff --git a/Sources/Github/Models/Collaborators/Role.swift b/Sources/Github/Models/Collaborators/Role.swift new file mode 100644 index 0000000..737e039 --- /dev/null +++ b/Sources/Github/Models/Collaborators/Role.swift @@ -0,0 +1,12 @@ +// +// Role.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum Role: String, Sendable, Codable { + case admin +} diff --git a/Sources/Github/Models/Comments/Comment.swift b/Sources/Github/Models/Comments/Comment.swift new file mode 100644 index 0000000..0fbf9f3 --- /dev/null +++ b/Sources/Github/Models/Comments/Comment.swift @@ -0,0 +1,74 @@ +// +// Comment.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Comment: Codable, Sendable, Hashable, Identifiable { + public let id: Int + public let nodeID: String + public let url: URL + public let body: String + public let bodyText: String? + public let bodyHTML: String? + public let htmlURL: URL + public let user: User + public let createdAt: Date + public let updatedAt: Date + public let issueURL: URL + public let authorAssociation: AuthorAssociation + public let reactions: Reaction + public let performGitHubApp: PerformGitHubApp? + + public init( + id: Int, + nodeID: String, + url: URL, + body: String, + bodyText: String?, + bodyHTML: String?, + htmlURL: URL, + user: User, + createdAt: Date, + updatedAt: Date, + issueURL: URL, + authorAssociation: AuthorAssociation, + reactions: Reaction, + performGitHubApp: PerformGitHubApp? + ) { + self.id = id + self.nodeID = nodeID + self.url = url + self.body = body + self.bodyText = bodyText + self.bodyHTML = bodyHTML + self.htmlURL = htmlURL + self.user = user + self.createdAt = createdAt + self.updatedAt = updatedAt + self.issueURL = issueURL + self.authorAssociation = authorAssociation + self.reactions = reactions + self.performGitHubApp = performGitHubApp + } + + enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case url + case body + case bodyText = "body_text" + case bodyHTML = "body_html" + case htmlURL = "html_url" + case user + case createdAt = "created_at" + case updatedAt = "updated_at" + case issueURL = "issue_url" + case authorAssociation = "author_association" + case reactions + case performGitHubApp = "performed_via_github_app" + } +} diff --git a/Sources/Github/Models/Contributors/Contributor.swift b/Sources/Github/Models/Contributors/Contributor.swift new file mode 100644 index 0000000..534fd53 --- /dev/null +++ b/Sources/Github/Models/Contributors/Contributor.swift @@ -0,0 +1,39 @@ +// +// Contributor.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Contributor: Codable, Sendable, Hashable, Identifiable { + public var id: Int { user.id } + public let user: User + public let contributionCount: Int + + public init( + user: User, + contributionCount: Int + ) { + self.user = user + self.contributionCount = contributionCount + } + + private enum CodingKeys: String, CodingKey { + case contributionCount = "contributions" + } + + public init(from decoder: any Decoder) throws { + self.user = try .init(from: decoder) + let container = try decoder.container(keyedBy: CodingKeys.self) + self.contributionCount = try container.decode(Int.self, forKey: .contributionCount) + } + + public func encode(to encoder: any Encoder) throws { + try self.user.encode(to: encoder) + + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(contributionCount, forKey: .contributionCount) + } +} diff --git a/Sources/Github/Models/Discussions/Category.swift b/Sources/Github/Models/Discussions/Category.swift new file mode 100644 index 0000000..a7023bb --- /dev/null +++ b/Sources/Github/Models/Discussions/Category.swift @@ -0,0 +1,39 @@ +// +// Category.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Category: Codable, Hashable, Sendable { + public let createdAt: Date + public let description: String + public let emoji: String + public let emojiHTML: String + public let isAnswerable: Bool + public let name: String + public let slug: String + public let updateAt: Date? + + public init( + createdAt: Date, + description: String, + emoji: String, + emojiHTML: String, + isAnswerable: Bool, + name: String, + slug: String, + updateAt: Date? + ) { + self.createdAt = createdAt + self.description = description + self.emoji = emoji + self.emojiHTML = emojiHTML + self.isAnswerable = isAnswerable + self.name = name + self.slug = slug + self.updateAt = updateAt + } +} diff --git a/Sources/Github/Models/Discussions/Discussion.swift b/Sources/Github/Models/Discussions/Discussion.swift new file mode 100644 index 0000000..5026368 --- /dev/null +++ b/Sources/Github/Models/Discussions/Discussion.swift @@ -0,0 +1,204 @@ +// +// Discussion.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Discussion: Codable, Hashable, Sendable, Identifiable { + public let id: String + public let number: Int + public let url: URL + public let author: User? + public let createdAt: Date + public let title: String + public let updatedAt: Date + public let upvoteCount: Int + public let stateReason: StateReason? + public let activeLockReason: ActiveLockReason? + public let authorAssociation: AuthorAssociation + public let body: String + public let bodyHTML: String + public let bodyText: String + public let createdViaEmail: Bool + public let databaseId: Int + public let editor: User? + public let includesCreatedEdit: Bool + public let lastEditedAt: Date? + public let locked: Bool + public let viewerCanClose: Bool + public let viewerCanDelete: Bool + public let viewerCanReact: Bool + public let viewerCanReopen: Bool + public let viewerCanSubscribe: Bool + public let viewerCanUpdate: Bool + public let viewerCanUpvote: Bool + public let viewerDidAuthor: Bool + public let viewerHasUpvoted: Bool + public let viewerSubscription: SubscriptionState + public let comments: [Comment] + public let category: Category + public let labels: [Label] + public let reactions: [Reaction] + public let poll: Poll? + + public init( + id: String, + number: Int, + url: URL, + author: User?, + createdAt: Date, + title: String, + updatedAt: Date, + upvoteCount: Int, + stateReason: StateReason?, + activeLockReason: ActiveLockReason?, + authorAssociation: AuthorAssociation, + body: String, + bodyHTML: String, + bodyText: String, + createdViaEmail: Bool, + databaseId: Int, + editor: User?, + includesCreatedEdit: Bool, + lastEditedAt: Date?, + locked: Bool, + viewerCanClose: Bool, + viewerCanDelete: Bool, + viewerCanReact: Bool, + viewerCanReopen: Bool, + viewerCanSubscribe: Bool, + viewerCanUpdate: Bool, + viewerCanUpvote: Bool, + viewerDidAuthor: Bool, + viewerHasUpvoted: Bool, + viewerSubscription: SubscriptionState, + comments: [Comment], + category: Category, + labels: [Label], + reactions: [Reaction], + poll: Poll? + ) { + self.id = id + self.number = number + self.url = url + self.author = author + self.createdAt = createdAt + self.title = title + self.updatedAt = updatedAt + self.upvoteCount = upvoteCount + self.stateReason = stateReason + self.activeLockReason = activeLockReason + self.authorAssociation = authorAssociation + self.body = body + self.bodyHTML = bodyHTML + self.bodyText = bodyText + self.createdViaEmail = createdViaEmail + self.databaseId = databaseId + self.editor = editor + self.includesCreatedEdit = includesCreatedEdit + self.lastEditedAt = lastEditedAt + self.locked = locked + self.viewerCanClose = viewerCanClose + self.viewerCanDelete = viewerCanDelete + self.viewerCanReact = viewerCanReact + self.viewerCanReopen = viewerCanReopen + self.viewerCanSubscribe = viewerCanSubscribe + self.viewerCanUpdate = viewerCanUpdate + self.viewerCanUpvote = viewerCanUpvote + self.viewerDidAuthor = viewerDidAuthor + self.viewerHasUpvoted = viewerHasUpvoted + self.viewerSubscription = viewerSubscription + self.comments = comments + self.category = category + self.labels = labels + self.reactions = reactions + self.poll = poll + } + + private enum CodingKeys: String, CodingKey { + case id + case number + case url + case author + case createdAt + case title + case updatedAt + case upvoteCount + case activeLockReason + case authorAssociation + case body + case bodyHTML + case bodyText + case createdViaEmail + case databaseId + case editor + case includesCreatedEdit + case lastEditedAt + case locked + case viewerCanClose + case viewerCanDelete + case viewerCanReact + case viewerCanReopen + case viewerCanSubscribe + case viewerCanUpdate + case viewerCanUpvote + case viewerDidAuthor + case viewerHasUpvoted + case viewerSubscription + case comments + case category + case stateReason + case labels + case reactions + case poll + } + + private enum NodesCodingKeys: String, CodingKey { + case nodes + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.id = try container.decode(String.self, forKey: .id) + self.number = try container.decode(Int.self, forKey: .number) + self.url = try container.decode(URL.self, forKey: .url) + self.author = try container.decodeIfPresent(Discussion.User.self, forKey: .author) + self.createdAt = try container.decode(Date.self, forKey: .createdAt) + self.title = try container.decode(String.self, forKey: .title) + self.updatedAt = try container.decode(Date.self, forKey: .updatedAt) + self.upvoteCount = try container.decode(Int.self, forKey: .upvoteCount) + self.stateReason = try container.decodeIfPresent(StateReason.self, forKey: .stateReason) + self.activeLockReason = try container.decodeIfPresent(ActiveLockReason.self, forKey: .activeLockReason) + self.authorAssociation = try container.decode(AuthorAssociation.self, forKey: .authorAssociation) + self.body = try container.decode(String.self, forKey: .body) + self.bodyHTML = try container.decode(String.self, forKey: .bodyHTML) + self.bodyText = try container.decode(String.self, forKey: .bodyText) + self.createdViaEmail = try container.decode(Bool.self, forKey: .createdViaEmail) + self.databaseId = try container.decode(Int.self, forKey: .databaseId) + self.editor = try container.decodeIfPresent(Discussion.User.self, forKey: .editor) + self.includesCreatedEdit = try container.decode(Bool.self, forKey: .includesCreatedEdit) + self.lastEditedAt = try container.decodeIfPresent(Date.self, forKey: .lastEditedAt) + self.locked = try container.decode(Bool.self, forKey: .locked) + self.viewerCanClose = try container.decode(Bool.self, forKey: .viewerCanClose) + self.viewerCanDelete = try container.decode(Bool.self, forKey: .viewerCanDelete) + self.viewerCanReact = try container.decode(Bool.self, forKey: .viewerCanReact) + self.viewerCanReopen = try container.decode(Bool.self, forKey: .viewerCanReopen) + self.viewerCanSubscribe = try container.decode(Bool.self, forKey: .viewerCanSubscribe) + self.viewerCanUpdate = try container.decode(Bool.self, forKey: .viewerCanUpdate) + self.viewerCanUpvote = try container.decode(Bool.self, forKey: .viewerCanUpvote) + self.viewerDidAuthor = try container.decode(Bool.self, forKey: .viewerDidAuthor) + self.viewerHasUpvoted = try container.decode(Bool.self, forKey: .viewerHasUpvoted) + self.viewerSubscription = try container.decode(SubscriptionState.self, forKey: .viewerSubscription) + let commentsContainer = try container.nestedContainer(keyedBy: NodesCodingKeys.self, forKey: .comments) + self.comments = try commentsContainer.decode([Comment].self, forKey: .nodes) + self.category = try container.decode(Category.self, forKey: .category) + let labelsContainer = try container.nestedContainer(keyedBy: NodesCodingKeys.self, forKey: .labels) + self.labels = try labelsContainer.decode([Label].self, forKey: .nodes) + let reactionsContainer = try container.nestedContainer(keyedBy: NodesCodingKeys.self, forKey: .reactions) + self.reactions = try reactionsContainer.decode([Reaction].self, forKey: .nodes) + self.poll = try container.decodeIfPresent(Poll.self, forKey: .poll) + } +} diff --git a/Sources/Github/Models/Discussions/DiscussionComment.swift b/Sources/Github/Models/Discussions/DiscussionComment.swift new file mode 100644 index 0000000..44417cb --- /dev/null +++ b/Sources/Github/Models/Discussions/DiscussionComment.swift @@ -0,0 +1,59 @@ +// +// DiscussionComment.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Discussion { + public struct Comment: Codable, Sendable, Hashable, Identifiable { + public let id: String + public let author: Discussion.User + public let body: String + public let bodyHTML: String + public let bodyText: String + public let createdAt: Date + public let createdViaEmail: Bool + public let editor: Discussion.User? + public let authorAssociation: AuthorAssociation + public let includesCreatedEdit: Bool + public let lastEditedAt: Date? + public let publishedAt: Date + public let updatedAt:Date + public let viewerDidAuthor: Bool + + public init( + id: String, + author: Discussion.User, + body: String, + bodyHTML: String, + bodyText: String, + createdAt: Date, + createdViaEmail: Bool, + editor: Discussion.User?, + authorAssociation: AuthorAssociation, + includesCreatedEdit: Bool, + lastEditedAt: Date?, + publishedAt: Date, + updatedAt: Date, + viewerDidAuthor: Bool + ) { + self.id = id + self.author = author + self.body = body + self.bodyHTML = bodyHTML + self.bodyText = bodyText + self.createdAt = createdAt + self.createdViaEmail = createdViaEmail + self.editor = editor + self.authorAssociation = authorAssociation + self.includesCreatedEdit = includesCreatedEdit + self.lastEditedAt = lastEditedAt + self.publishedAt = publishedAt + self.updatedAt = updatedAt + self.viewerDidAuthor = viewerDidAuthor + } + } +} diff --git a/Sources/Github/Models/Discussions/DiscussionLabel.swift b/Sources/Github/Models/Discussions/DiscussionLabel.swift new file mode 100644 index 0000000..35ec66f --- /dev/null +++ b/Sources/Github/Models/Discussions/DiscussionLabel.swift @@ -0,0 +1,41 @@ +// +// DiscussionLabel.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Discussion { + public struct Label: Codable, Sendable, Hashable { + public let name: String + public let color: String + public let createdAt: Date + public let description: String + public let isDefault: Bool + public let resourcePath: String + public let updatedAt: Date + public let url: URL + + public init( + name: String, + color: String, + createdAt: Date, + description: String, + isDefault: Bool, + resourcePath: String, + updatedAt: Date, + url: URL + ) { + self.name = name + self.color = color + self.createdAt = createdAt + self.description = description + self.isDefault = isDefault + self.resourcePath = resourcePath + self.updatedAt = updatedAt + self.url = url + } + } +} diff --git a/Sources/Github/Models/Discussions/DiscussionPoll.swift b/Sources/Github/Models/Discussions/DiscussionPoll.swift new file mode 100644 index 0000000..9525eab --- /dev/null +++ b/Sources/Github/Models/Discussions/DiscussionPoll.swift @@ -0,0 +1,46 @@ +// +// File.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Discussion { + public struct Poll: Codable, Sendable, Hashable { + public let question: String + public let totalVoteCount: Int + public let viewerCanVote: Bool + public let viewerHasVoted: Bool + public let options: [Option] + + public init( + question: String, + totalVoteCount: Int, + viewerCanVote: Bool, + viewerHasVoted: Bool, + options: [Option] + ) { + self.question = question + self.totalVoteCount = totalVoteCount + self.viewerCanVote = viewerCanVote + self.viewerHasVoted = viewerHasVoted + self.options = options + } + + private enum NodeCodingKeys: String, CodingKey { + case nodes + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.question = try container.decode(String.self, forKey: .question) + self.totalVoteCount = try container.decode(Int.self, forKey: .totalVoteCount) + self.viewerCanVote = try container.decode(Bool.self, forKey: .viewerCanVote) + self.viewerHasVoted = try container.decode(Bool.self, forKey: .viewerHasVoted) + let optionsContainer = try container.nestedContainer(keyedBy: NodeCodingKeys.self, forKey: .options) + self.options = try optionsContainer.decode([Discussion.Poll.Option].self, forKey: .nodes) + } + } +} diff --git a/Sources/Github/Models/Discussions/DiscussionPollOption.swift b/Sources/Github/Models/Discussions/DiscussionPollOption.swift new file mode 100644 index 0000000..1c7365a --- /dev/null +++ b/Sources/Github/Models/Discussions/DiscussionPollOption.swift @@ -0,0 +1,26 @@ +// +// File.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Discussion.Poll { + public struct Option: Codable, Sendable, Hashable { + public let option: String + public let totalVoteCount: Int + public let viewerHasVoted: Bool + + public init( + option: String, + totalVoteCount: Int, + viewerHasVoted: Bool + ) { + self.option = option + self.totalVoteCount = totalVoteCount + self.viewerHasVoted = viewerHasVoted + } + } +} diff --git a/Sources/Github/Models/Discussions/DiscussionReaction.swift b/Sources/Github/Models/Discussions/DiscussionReaction.swift new file mode 100644 index 0000000..a28a9eb --- /dev/null +++ b/Sources/Github/Models/Discussions/DiscussionReaction.swift @@ -0,0 +1,29 @@ +// +// DiscussionReaction.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Discussion { + public struct Reaction: Codable, Sendable, Hashable { + public let content: String + public let createdAt: Date + public let databaseId: Int + public let user: User + + public init( + content: String, + createdAt: Date, + databaseId: Int, + user: User + ) { + self.content = content + self.createdAt = createdAt + self.databaseId = databaseId + self.user = user + } + } +} diff --git a/Sources/Github/Models/Discussions/DiscussionStateReason.swift b/Sources/Github/Models/Discussions/DiscussionStateReason.swift new file mode 100644 index 0000000..7fd6be5 --- /dev/null +++ b/Sources/Github/Models/Discussions/DiscussionStateReason.swift @@ -0,0 +1,17 @@ +// +// DiscussionStateReason.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Discussion { + public enum StateReason: String, Sendable, Codable { + case duplicate = "DUPLICATE" + case outdated = "OUTDATED" + case reOpened = "REOPENED" + case resolved = "RESOLVED" + } +} diff --git a/Sources/Github/Models/Discussions/DiscussionUser.swift b/Sources/Github/Models/Discussions/DiscussionUser.swift new file mode 100644 index 0000000..4d289cc --- /dev/null +++ b/Sources/Github/Models/Discussions/DiscussionUser.swift @@ -0,0 +1,29 @@ +// +// DiscussionUser.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Discussion { + public struct User: Codable, Hashable, Sendable { + public let login: String + public let avatarUrl: URL + public let resourcePath: String + public let url: URL + + public init( + login: String, + avatarUrl: URL, + resourcePath: String, + url: URL + ) { + self.login = login + self.avatarUrl = avatarUrl + self.resourcePath = resourcePath + self.url = url + } + } +} diff --git a/Sources/Github/Models/Discussions/SubscriptionState.swift b/Sources/Github/Models/Discussions/SubscriptionState.swift new file mode 100644 index 0000000..1267511 --- /dev/null +++ b/Sources/Github/Models/Discussions/SubscriptionState.swift @@ -0,0 +1,14 @@ +// +// SubscriptionState.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum SubscriptionState: String, Codable, Sendable { + case ignored = "IGNORED" + case subscribed = "SUBSCRIBED" + case unSubscribed = "UNSUBSCRIBED" +} diff --git a/Sources/Github/Models/Gitignore/GitignoreTemplate.swift b/Sources/Github/Models/Gitignore/GitignoreTemplate.swift new file mode 100644 index 0000000..02c54cf --- /dev/null +++ b/Sources/Github/Models/Gitignore/GitignoreTemplate.swift @@ -0,0 +1,21 @@ +// +// GitignoreTemplate.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct GitignoreTemplate: Codable, Sendable, Hashable { + public let name: String + public let source: String + + public init( + name: String, + source: String + ) { + self.name = name + self.source = source + } +} diff --git a/Sources/Github/Models/Issue/Issue.swift b/Sources/Github/Models/Issue/Issue.swift new file mode 100644 index 0000000..fce59fe --- /dev/null +++ b/Sources/Github/Models/Issue/Issue.swift @@ -0,0 +1,154 @@ +// +// Issue.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Issue: Codable, Sendable, Identifiable, Hashable { + public let id: Int + public let number: Int + public let title: String + public let body: String? + public let bodyHTML: String? + public let bodyText: String? + public let user: User + public let nodeID: String + public let url: URL + public let repositoryURL: URL + public let labelsURL: URL + public let commentsURL: URL + public let eventsURL: URL + public let htmlURL: URL + public let labels: [Label] + public let state: State + public let locked: Bool + public let assignee: User? + public let assignees: [User] + public let milestone: Milestone? + public let commentsCount: Int + public let createdAt: Date + public let updatedAt: Date + public let closedAt: Date? + public let authorAssociation: AuthorAssociation + public let activeLockReason: ActiveLockReason? + public let draft: Bool? + public let pullRequest: SimplePull? + public let reactions: Reaction? + public let timelineURL: URL? + public let performedViaGitHubApp: PerformGitHubApp? + public let stateReason: StateReason? + public let closedBy: User? + public let repository: Repository? + + public init( + id: Int, + number: Int, + title: String, + body: String?, + bodyHTML: String?, + bodyText: String?, + user: User, + nodeID: String, + url: URL, + repositoryURL: URL, + labelsURL: URL, + commentsURL: URL, + eventsURL: URL, + htmlURL: URL, + labels: [Label], + state: State, + locked: Bool, + assignee: User?, + assignees: [User], + milestone: Milestone?, + commentsCount: Int, + createdAt: Date, + updatedAt: Date, + closedAt: Date?, + authorAssociation: AuthorAssociation, + activeLockReason: ActiveLockReason?, + draft: Bool?, + pullRequest: SimplePull?, + reactions: Reaction?, + timelineURL: URL?, + performedViaGitHubApp: PerformGitHubApp?, + stateReason: StateReason?, + closedBy: User?, + repository: Repository? + ) { + self.id = id + self.number = number + self.title = title + self.body = body + self.bodyHTML = bodyHTML + self.bodyText = bodyText + self.user = user + self.nodeID = nodeID + self.url = url + self.repositoryURL = repositoryURL + self.labelsURL = labelsURL + self.commentsURL = commentsURL + self.eventsURL = eventsURL + self.htmlURL = htmlURL + self.labels = labels + self.state = state + self.locked = locked + self.assignee = assignee + self.assignees = assignees + self.milestone = milestone + self.commentsCount = commentsCount + self.createdAt = createdAt + self.updatedAt = updatedAt + self.closedAt = closedAt + self.authorAssociation = authorAssociation + self.activeLockReason = activeLockReason + self.draft = draft + self.pullRequest = pullRequest + self.reactions = reactions + self.timelineURL = timelineURL + self.performedViaGitHubApp = performedViaGitHubApp + self.stateReason = stateReason + self.closedBy = closedBy + self.repository = repository + } + + private enum CodingKeys: String, CodingKey { + case id + case number + case title + case body + case bodyHTML = "body_html" + case bodyText = "body_text" + case user + case nodeID = "node_id" + case url + case repositoryURL = "repository_url" + case labelsURL = "labels_url" + case commentsURL = "comments_url" + case eventsURL = "events_url" + case htmlURL = "html_url" + case labels + case state + case locked + case assignee + case assignees + case milestone + case commentsCount = "comments" + case createdAt = "created_at" + case updatedAt = "updated_at" + case closedAt = "closed_at" + case authorAssociation = "author_association" + case activeLockReason = "active_lock_reason" + case draft + case pullRequest = "pull_request" + case reactions + case timelineURL = "timeline_url" + case performedViaGitHubApp = "performed_via_github_app" + case stateReason = "state_reason" + case closedBy = "closed_by" + case repository + } +} diff --git a/Sources/Github/Models/Issue/IssueState.swift b/Sources/Github/Models/Issue/IssueState.swift new file mode 100644 index 0000000..415f229 --- /dev/null +++ b/Sources/Github/Models/Issue/IssueState.swift @@ -0,0 +1,15 @@ +// +// IssueState.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Issue { + public enum State: String, Codable, Sendable { + case open + case closed + } +} diff --git a/Sources/Github/Models/Issue/IssueStateReason.swift b/Sources/Github/Models/Issue/IssueStateReason.swift new file mode 100644 index 0000000..874c51a --- /dev/null +++ b/Sources/Github/Models/Issue/IssueStateReason.swift @@ -0,0 +1,16 @@ +// +// IssueStateReason.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Issue { + public enum StateReason: String, Codable, Sendable { + case completed + case reopened + case notPlanned = "not_planned" + } +} diff --git a/Sources/Github/Models/Label/Label.swift b/Sources/Github/Models/Label/Label.swift new file mode 100644 index 0000000..d1516c8 --- /dev/null +++ b/Sources/Github/Models/Label/Label.swift @@ -0,0 +1,46 @@ +// +// Label.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Label: Codable, Hashable, Sendable, Identifiable { + public let id: Int + public let nodeID: String + public let url: URL + public let name: String + public let color: String + public let isDefault: Bool + public let description: String? + + public init( + id: Int, + nodeID: String, + url: URL, + name: String, + color: String, + isDefault: Bool, + description: String? + ) { + self.id = id + self.nodeID = nodeID + self.url = url + self.name = name + self.color = color + self.isDefault = isDefault + self.description = description + } + + private enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case url + case name + case color + case isDefault = "default" + case description + } +} diff --git a/Sources/Github/Models/License/Encoding.swift b/Sources/Github/Models/License/Encoding.swift new file mode 100644 index 0000000..63107ed --- /dev/null +++ b/Sources/Github/Models/License/Encoding.swift @@ -0,0 +1,12 @@ +// +// Encoding.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum Encoding: String, Codable, Sendable { + case base64 +} diff --git a/Sources/Github/Models/License/License.swift b/Sources/Github/Models/License/License.swift new file mode 100644 index 0000000..16ffb82 --- /dev/null +++ b/Sources/Github/Models/License/License.swift @@ -0,0 +1,66 @@ +// +// License.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct License: Codable, Sendable, Hashable { + public let name: String + public let path: String + public let sha: String + public let size: Int + public let url: URL + public let htmlURL: URL + public let gitURL: URL + public let downloadURL: URL + public let type: LicenseType + public let content: String + public let encoding: Encoding + public let license: SimpleLicense + + public init( + name: String, + path: String, + sha: String, + size: Int, + url: URL, + htmlURL: URL, + gitURL: URL, + downloadURL: URL, + type: LicenseType, + content: String, + encoding: Encoding, + license: SimpleLicense + ) { + self.name = name + self.path = path + self.sha = sha + self.size = size + self.url = url + self.htmlURL = htmlURL + self.gitURL = gitURL + self.downloadURL = downloadURL + self.type = type + self.content = content + self.encoding = encoding + self.license = license + } + + private enum CodingKeys: String, CodingKey { + case name + case path + case sha + case size + case url + case htmlURL = "html_url" + case gitURL = "git_url" + case downloadURL = "download_url" + case type + case content + case encoding + case license + } +} diff --git a/Sources/Github/Models/License/LicenseType.swift b/Sources/Github/Models/License/LicenseType.swift new file mode 100644 index 0000000..7e187fa --- /dev/null +++ b/Sources/Github/Models/License/LicenseType.swift @@ -0,0 +1,12 @@ +// +// LicenseType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum LicenseType: String, Codable, Sendable { + case file +} diff --git a/Sources/Github/Models/Milestone/Milestone.swift b/Sources/Github/Models/Milestone/Milestone.swift new file mode 100644 index 0000000..b27e257 --- /dev/null +++ b/Sources/Github/Models/Milestone/Milestone.swift @@ -0,0 +1,82 @@ +// +// Milestone.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Milestone: Codable, Sendable, Hashable, Identifiable { + public let id: Int + public let nodeID: String + public let state: State + public let number: Int + public let title: String + public let description: String? + public let url: URL + public let htmlURL: URL + public let labelsURL: URL + public let creator: User + public let openIssueCount: Int + public let closedIssueCount: Int + public let createdAt: Date + public let updatedAt: Date + public let closedAt: Date? + public let dueOn: Date? + + public init( + id: Int, + nodeID: String, + state: State, + number: Int, + title: String, + description: String?, + url: URL, + htmlURL: URL, + labelsURL: URL, + creator: User, + openIssueCount: Int, + closedIssueCount: Int, + createdAt: Date, + updatedAt: Date, + closedAt: Date?, + dueOn: Date? + ) { + self.id = id + self.nodeID = nodeID + self.state = state + self.number = number + self.title = title + self.description = description + self.url = url + self.htmlURL = htmlURL + self.labelsURL = labelsURL + self.creator = creator + self.openIssueCount = openIssueCount + self.closedIssueCount = closedIssueCount + self.createdAt = createdAt + self.updatedAt = updatedAt + self.closedAt = closedAt + self.dueOn = dueOn + } + + private enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case state + case number + case title + case description + case url + case htmlURL = "html_url" + case labelsURL = "labels_url" + case creator + case openIssueCount = "open_issues" + case closedIssueCount = "closed_issues" + case createdAt = "created_at" + case updatedAt = "updated_at" + case closedAt = "closed_at" + case dueOn = "due_on" + } +} diff --git a/Sources/Github/Models/Milestone/MilestoneState.swift b/Sources/Github/Models/Milestone/MilestoneState.swift new file mode 100644 index 0000000..bf33fdd --- /dev/null +++ b/Sources/Github/Models/Milestone/MilestoneState.swift @@ -0,0 +1,15 @@ +// +// MilestoneState.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Milestone { + public enum State: String, Codable, Sendable { + case open + case closed + } +} diff --git a/Sources/Github/Models/Notifications/Notification.swift b/Sources/Github/Models/Notifications/Notification.swift new file mode 100644 index 0000000..f877f59 --- /dev/null +++ b/Sources/Github/Models/Notifications/Notification.swift @@ -0,0 +1,54 @@ +// +// Notification.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Notification: Codable, Sendable, Hashable, Identifiable { + public let id: String + public let repository: Repository + public let subject: Subject + public let reason: Reason + public let unread: Bool + public let updatedAt: Date + public let lastReadAt: Date? + public let url: URL + public let subscriptionURL: URL + + public init( + id: String, + repository: Repository, + subject: Subject, + reason: Reason, + unread: Bool, + updatedAt: Date, + lastReadAt: Date, + url: URL, + subscriptionURL: URL + ) { + self.id = id + self.repository = repository + self.subject = subject + self.reason = reason + self.unread = unread + self.updatedAt = updatedAt + self.lastReadAt = lastReadAt + self.url = url + self.subscriptionURL = subscriptionURL + } + + private enum CodingKeys: String, CodingKey { + case id + case repository + case subject + case reason + case unread + case updatedAt = "updated_at" + case lastReadAt = "last_read_at" + case url + case subscriptionURL = "subscription_url" + } +} diff --git a/Sources/Github/Models/Notifications/NotificationReason.swift b/Sources/Github/Models/Notifications/NotificationReason.swift new file mode 100644 index 0000000..5b84875 --- /dev/null +++ b/Sources/Github/Models/Notifications/NotificationReason.swift @@ -0,0 +1,17 @@ +// +// NotificationReason.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Notification { + public enum Reason: String, Codable, Sendable { + case subscribed + case stateChange = "state_change" + case author + case mention + } +} diff --git a/Sources/Github/Models/Notifications/Subject.swift b/Sources/Github/Models/Notifications/Subject.swift new file mode 100644 index 0000000..d509637 --- /dev/null +++ b/Sources/Github/Models/Notifications/Subject.swift @@ -0,0 +1,34 @@ +// +// Subject.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Subject: Codable, Hashable, Sendable { + public let title: String + public let url: URL? + public let latestCommitURL: URL? + public let type: SubjectType + + public init( + title: String, + url: URL?, + latestCommitURL: URL?, + type: SubjectType + ) { + self.title = title + self.url = url + self.latestCommitURL = latestCommitURL + self.type = type + } + + private enum CodingKeys: String, CodingKey { + case title + case url + case latestCommitURL = "latest_comment_url" + case type + } +} diff --git a/Sources/Github/Models/Notifications/SubjectType.swift b/Sources/Github/Models/Notifications/SubjectType.swift new file mode 100644 index 0000000..6b412d4 --- /dev/null +++ b/Sources/Github/Models/Notifications/SubjectType.swift @@ -0,0 +1,17 @@ +// +// SubjectType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Subject { + public enum SubjectType: String, Codable, Sendable { + case issue = "Issue" + case release = "Release" + case pullRequest = "PullRequest" + case discussion = "Discussion" + } +} diff --git a/Sources/Github/Models/Other/ActiveLockReason.swift b/Sources/Github/Models/Other/ActiveLockReason.swift new file mode 100644 index 0000000..8ddf740 --- /dev/null +++ b/Sources/Github/Models/Other/ActiveLockReason.swift @@ -0,0 +1,21 @@ +// +// ActiveLockReason.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum ActiveLockReason: String, Codable, Sendable { + case tooHeated = "too heated" + case offTopic = "off-topic" + case resolved + case spam + + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + let rawValue = try container.decode(String.self) + self.init(rawValue: rawValue.lowercased())! + } +} diff --git a/Sources/Github/Models/Other/AuthorAssociation.swift b/Sources/Github/Models/Other/AuthorAssociation.swift new file mode 100644 index 0000000..5053a3d --- /dev/null +++ b/Sources/Github/Models/Other/AuthorAssociation.swift @@ -0,0 +1,19 @@ +// +// AuthorAssociation.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum AuthorAssociation: String, Codable, Sendable { + case collaborator = "COLLABORATOR" + case contributor = "CONTRIBUTOR" + case firstTimer = "FIRST_TIMER" + case firstTimeContributor = "FIRST_TIME_CONTRIBUTOR" + case mannequin = "MANNEQUIN" + case member = "MEMBER" + case none = "NONE" + case owner = "OWNER" +} diff --git a/Sources/Github/Models/Other/PerformGitHubApp.swift b/Sources/Github/Models/Other/PerformGitHubApp.swift new file mode 100644 index 0000000..da75f2d --- /dev/null +++ b/Sources/Github/Models/Other/PerformGitHubApp.swift @@ -0,0 +1,82 @@ +// +// PerformGitHubApp.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct PerformGitHubApp: Codable, Sendable, Hashable, Identifiable { + public let id: Int + public let slug: String? + public let nodeID: String + public let owner: User + public let name: String + public let description: String + public let externalURL: URL + public let htmlURL: URL + public let createdAt: Date + public let updatedAt: Date + public let permissions: Permission + public let installationsCount: Int? + public let clientID: String? + public let clientSecret: String? + public let webhookSecret: String? + public let pem: String? + + public init( + id: Int, + slug: String?, + nodeID: String, + owner: User, + name: String, + description: String, + externalURL: URL, + htmlURL: URL, + createdAt: Date, + updatedAt: Date, + permissions: Permission, + installationsCount: Int?, + clientID: String?, + clientSecret: String?, + webhookSecret: String?, + pem: String? + ) { + self.id = id + self.slug = slug + self.nodeID = nodeID + self.owner = owner + self.name = name + self.description = description + self.externalURL = externalURL + self.htmlURL = htmlURL + self.createdAt = createdAt + self.updatedAt = updatedAt + self.permissions = permissions + self.installationsCount = installationsCount + self.clientID = clientID + self.clientSecret = clientSecret + self.webhookSecret = webhookSecret + self.pem = pem + } + + private enum CodingKeys: String, CodingKey { + case id + case slug + case nodeID = "node_id" + case owner + case name + case description + case externalURL = "external_url" + case htmlURL = "html_url" + case createdAt = "created_at" + case updatedAt = "updated_at" + case permissions + case installationsCount = "installations_count" + case clientID = "client_id" + case clientSecret = "client_secret" + case webhookSecret = "webhook_secret" + case pem + } +} diff --git a/Sources/Github/Models/Other/PermissionType.swift b/Sources/Github/Models/Other/PermissionType.swift new file mode 100644 index 0000000..6cfb58d --- /dev/null +++ b/Sources/Github/Models/Other/PermissionType.swift @@ -0,0 +1,16 @@ +// +// PermissionType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum PermissionType: String, Codable, Sendable { + case pull + case triage + case push + case maintain + case admin +} diff --git a/Sources/Github/Models/Other/Plan.swift b/Sources/Github/Models/Other/Plan.swift new file mode 100644 index 0000000..578d29b --- /dev/null +++ b/Sources/Github/Models/Other/Plan.swift @@ -0,0 +1,34 @@ +// +// Plan.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Plan: Codable, Sendable, Hashable { + public let name: String + public let space: Int + public let collaboratorCount: Int + public let privateRepoCount: Int + + public init( + name: String, + space: Int, + collaboratorCount: Int, + privateRepoCount: Int + ) { + self.name = name + self.space = space + self.collaboratorCount = collaboratorCount + self.privateRepoCount = privateRepoCount + } + + private enum CodingKeys: String, CodingKey { + case name + case space + case collaboratorCount = "collaborators" + case privateRepoCount = "private_repos" + } +} diff --git a/Sources/Github/Models/Pull/Pull.swift b/Sources/Github/Models/Pull/Pull.swift new file mode 100644 index 0000000..11edfe2 --- /dev/null +++ b/Sources/Github/Models/Pull/Pull.swift @@ -0,0 +1,158 @@ +// +// Pull.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Pull: Hashable, Sendable, Identifiable, Codable { + public let id: Int + public let nodeID: String + public let number: Int + public let state: State + public let locked: Bool + public let title: String + public let user: User + public let body: String? + public let createdAt: Date + public let updateAt: Date + public let closedAt: Date? + public let url: URL + public let htmlURL: URL + public let diffURL: URL + public let patchURL: URL + public let issueURL: URL + public let mergedAt: Date? + public let mergeCommitSha: String? + public let assignee: User? + public let assignees: [User] + public let requestedReviewers: [User] + public let requestedTeams: [Team] + public let labels: [Label] + public let milestone: Milestone? + public let isDraft: Bool + public let commitsURL: URL + public let reviewCommentsURL: URL + public let reviewCommentURL: URL + public let commentsURL: URL + public let statusesURL: URL + public let head: Branch + public let base: Branch + public let authorAssociation: AuthorAssociation + public let autoMerge: AutoMerge? + public let activeLockReason: ActiveLockReason? + + public init( + id: Int, + nodeID: String, + number: Int, + state: State, + locked: Bool, + title: String, + user: User, + body: String?, + createdAt: Date, + updateAt: Date, + closedAt: Date, + url: URL, + htmlURL: URL, + diffURL: URL, + patchURL: URL, + issueURL: URL, + mergedAt: Date?, + mergeCommitSha: String?, + assignee: User?, + assignees: [User], + requestedReviewers: [User], + requestedTeams: [Team], + labels: [Label], + milestone: Milestone?, + isDraft: Bool, + commitsURL: URL, + reviewCommentsURL: URL, + reviewCommentURL: URL, + commentsURL: URL, + statusesURL: URL, + head: Branch, + base: Branch, + authorAssociation: AuthorAssociation, + autoMerge: AutoMerge?, + activeLockReason: ActiveLockReason? + ) { + self.id = id + self.nodeID = nodeID + self.number = number + self.state = state + self.locked = locked + self.title = title + self.user = user + self.body = body + self.createdAt = createdAt + self.updateAt = updateAt + self.closedAt = closedAt + self.url = url + self.htmlURL = htmlURL + self.diffURL = diffURL + self.patchURL = patchURL + self.issueURL = issueURL + self.mergedAt = mergedAt + self.mergeCommitSha = mergeCommitSha + self.assignee = assignee + self.assignees = assignees + self.requestedReviewers = requestedReviewers + self.requestedTeams = requestedTeams + self.labels = labels + self.milestone = milestone + self.isDraft = isDraft + self.commitsURL = commitsURL + self.reviewCommentsURL = reviewCommentsURL + self.reviewCommentURL = reviewCommentURL + self.commentsURL = commentsURL + self.statusesURL = statusesURL + self.head = head + self.base = base + self.authorAssociation = authorAssociation + self.autoMerge = autoMerge + self.activeLockReason = activeLockReason + } + + private enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case number + case state + case locked + case title + case user + case body + case createdAt = "created_at" + case updateAt = "updated_at" + case closedAt = "closed_at" + case url + case htmlURL = "html_url" + case diffURL = "diff_url" + case patchURL = "patch_url" + case issueURL = "issue_url" + case mergedAt = "merged_at" + case mergeCommitSha = "merge_commit_sha" + case assignee + case assignees + case requestedReviewers = "requested_reviewers" + case requestedTeams = "requested_teams" + case labels + case milestone + case isDraft = "draft" + case commitsURL = "commits_url" + case reviewCommentsURL = "review_comments_url" + case reviewCommentURL = "review_comment_url" + case commentsURL = "comments_url" + case statusesURL = "statuses_url" + case head + case base + case authorAssociation = "author_association" + case autoMerge = "auto_merge" + case activeLockReason = "active_lock_reason" + } +} diff --git a/Sources/Github/Models/Pull/PullState.swift b/Sources/Github/Models/Pull/PullState.swift new file mode 100644 index 0000000..16725e9 --- /dev/null +++ b/Sources/Github/Models/Pull/PullState.swift @@ -0,0 +1,15 @@ +// +// PullState.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Pull { + public enum State: String, Codable, Sendable { + case open + case closed + } +} diff --git a/Sources/Github/Models/Pull/SimplePull.swift b/Sources/Github/Models/Pull/SimplePull.swift new file mode 100644 index 0000000..72132b1 --- /dev/null +++ b/Sources/Github/Models/Pull/SimplePull.swift @@ -0,0 +1,38 @@ +// +// SimplePull.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct SimplePull: Codable, Hashable, Sendable { + public let url: URL + public let htmlURL: URL + public let diffURL: URL + public let patchURL: URL + public let mergedAt: Date? + + public init( + url: URL, + htmlURL: URL, + diffURL: URL, + patchURL: URL, + mergedAt: Date? + ) { + self.url = url + self.htmlURL = htmlURL + self.diffURL = diffURL + self.patchURL = patchURL + self.mergedAt = mergedAt + } + + private enum CodingKeys: String, CodingKey { + case url + case htmlURL = "html_url" + case diffURL = "diff_url" + case patchURL = "patch_url" + case mergedAt = "merged_at" + } +} diff --git a/Sources/Github/Models/Reaction/Reaction.swift b/Sources/Github/Models/Reaction/Reaction.swift new file mode 100644 index 0000000..c5cabcb --- /dev/null +++ b/Sources/Github/Models/Reaction/Reaction.swift @@ -0,0 +1,58 @@ +// +// Reaction.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Reaction: Codable, Sendable, Hashable { + public let url: URL + public let totalCount: Int + public let plusOne: Int + public let minusOne: Int + public let laughCount: Int + public let hoorayCount: Int + public let confusedCount: Int + public let heartCount: Int + public let rockerCount: Int + public let eyesCount: Int + + public init( + url: URL, + totalCount: Int, + plusOne: Int, + minusOne: Int, + laughCount: Int, + hoorayCount: Int, + confusedCount: Int, + heartCount: Int, + rockerCount: Int, + eyesCount: Int + ) { + self.url = url + self.totalCount = totalCount + self.plusOne = plusOne + self.minusOne = minusOne + self.laughCount = laughCount + self.hoorayCount = hoorayCount + self.confusedCount = confusedCount + self.heartCount = heartCount + self.rockerCount = rockerCount + self.eyesCount = eyesCount + } + + private enum CodingKeys: String, CodingKey { + case url + case totalCount = "total_count" + case plusOne = "+1" + case minusOne = "-1" + case laughCount = "laugh" + case hoorayCount = "hooray" + case confusedCount = "confused" + case heartCount = "heart" + case rockerCount = "rocket" + case eyesCount = "eyes" + } +} diff --git a/Sources/Github/Models/Realeases/Asset.swift b/Sources/Github/Models/Realeases/Asset.swift new file mode 100644 index 0000000..a087696 --- /dev/null +++ b/Sources/Github/Models/Realeases/Asset.swift @@ -0,0 +1,70 @@ +// +// Asset.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Asset: Codable, Sendable, Hashable, Identifiable { + public let id: Int + public let nodeID: String + public let label: String? + public let state: State + public let name: String + public let url: URL + public let browserDownloadURL: URL + public let contentType: ContentType + public let size: Int + public let downloadCount: Int + public let createdAt: Date + public let updatedAt: Date + public let uploader: User + + public init( + id: Int, + nodeID: String, + label: String?, + state: State, + name: String, + url: URL, + browserDownloadURL: URL, + contentType: ContentType, + size: Int, + downloadCount: Int, + createdAt: Date, + updatedAt: Date, + uploader: User + ) { + self.id = id + self.nodeID = nodeID + self.label = label + self.state = state + self.name = name + self.url = url + self.browserDownloadURL = browserDownloadURL + self.contentType = contentType + self.size = size + self.downloadCount = downloadCount + self.createdAt = createdAt + self.updatedAt = updatedAt + self.uploader = uploader + } + + private enum CodingKeys: String, CodingKey { + case url + case browserDownloadURL = "browser_download_url" + case id + case nodeID = "node_id" + case name + case label + case state + case contentType = "content_type" + case size + case downloadCount = "download_count" + case createdAt = "created_at" + case updatedAt = "updated_at" + case uploader + } +} diff --git a/Sources/Github/Models/Realeases/AssetState.swift b/Sources/Github/Models/Realeases/AssetState.swift new file mode 100644 index 0000000..1b64973 --- /dev/null +++ b/Sources/Github/Models/Realeases/AssetState.swift @@ -0,0 +1,15 @@ +// +// AssetState.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Asset { + public enum State: String, Codable, Sendable { + case uploaded + case open + } +} diff --git a/Sources/Github/Models/Realeases/ContentType.swift b/Sources/Github/Models/Realeases/ContentType.swift new file mode 100644 index 0000000..4b87be6 --- /dev/null +++ b/Sources/Github/Models/Realeases/ContentType.swift @@ -0,0 +1,20 @@ +// +// ContentType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +extension Asset { + public enum ContentType: String, Codable, Sendable { + case zip = "application/zip" + case octet_stream = "application/octet-stream" + case x_tar = "application/x-tar" + case pgp_signature = "application/pgp-signature" + case x_debian_package = "application/x-debian-package" + case x_gzip = "application/x-gzip" + case x_msdownload = "application/x-msdownload" + } +} diff --git a/Sources/Github/Models/Realeases/Release.swift b/Sources/Github/Models/Realeases/Release.swift new file mode 100644 index 0000000..c221474 --- /dev/null +++ b/Sources/Github/Models/Realeases/Release.swift @@ -0,0 +1,110 @@ +// +// Release.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Release: Codable, Sendable, Hashable, Identifiable { + public let id: Int + public let nodeID: String + public let name: String + public let url: URL + public let htmlURL: URL + public let assetsURL: URL + public let uploadURL: URL + public let tarballURL: URL + public let zipballURL: URL + public let tagName: String + public let targetCommitish: String + public let body: String? + public let draft: Bool + public let prerelease: Bool + public let createdAt: Date + public let publishedAt: Date + public let author: User + public let assets: [Asset] + public let bodyHTML: String? + public let bodyText: String? + public let mentionsCount: Int? + public let discussionURL: URL? + public let reactions: Reaction? + + public init( + id: Int, + nodeID: String, + name: String, + url: URL, + htmlURL: URL, + assetsURL: URL, + uploadURL: URL, + tarballURL: URL, + zipballURL: URL, + tagName: String, + targetCommitish: String, + body: String, + draft: Bool, + prerelease: Bool, + createdAt: Date, + publishedAt: Date, + author: User, + assets: [Asset], + bodyHTML: String?, + bodyText: String?, + mentionsCount: Int?, + discussionURL: URL?, + reactions: Reaction? + ) { + self.id = id + self.nodeID = nodeID + self.name = name + self.url = url + self.htmlURL = htmlURL + self.assetsURL = assetsURL + self.uploadURL = uploadURL + self.tarballURL = tarballURL + self.zipballURL = zipballURL + self.tagName = tagName + self.targetCommitish = targetCommitish + self.body = body + self.draft = draft + self.prerelease = prerelease + self.createdAt = createdAt + self.publishedAt = publishedAt + self.author = author + self.assets = assets + self.bodyHTML = bodyHTML + self.bodyText = bodyText + self.mentionsCount = mentionsCount + self.discussionURL = discussionURL + self.reactions = reactions + } + + private enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case name + case url + case htmlURL = "html_url" + case assetsURL = "assets_url" + case uploadURL = "upload_url" + case tarballURL = "tarball_url" + case zipballURL = "zipball_url" + case tagName = "tag_name" + case targetCommitish = "target_commitish" + case body + case draft + case prerelease + case createdAt = "created_at" + case publishedAt = "published_at" + case author + case assets + case bodyHTML = "body_html" + case bodyText = "body_text" + case mentionsCount = "mentions_count" + case discussionURL = "discussion_url" + case reactions + } +} diff --git a/Sources/Github/Models/Repository/CodeConduct.swift b/Sources/Github/Models/Repository/CodeConduct.swift new file mode 100644 index 0000000..7bce318 --- /dev/null +++ b/Sources/Github/Models/Repository/CodeConduct.swift @@ -0,0 +1,38 @@ +// +// CodeConduct.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct CodeConduct: Codable, Sendable, Hashable { + public let key: String + public let name: String + public let url: URL + public let body: String? + public let htmlURL: URL + + public init( + key: String, + name: String, + url: URL, + body: String?, + htmlURL: URL + ) { + self.key = key + self.name = name + self.url = url + self.body = body + self.htmlURL = htmlURL + } + + private enum CodingKeys: String, CodingKey { + case key + case name + case url + case body + case htmlURL = "html_url" + } +} diff --git a/Sources/Github/Models/Repository/MergeCommit.swift b/Sources/Github/Models/Repository/MergeCommit.swift new file mode 100644 index 0000000..b5ab7ab --- /dev/null +++ b/Sources/Github/Models/Repository/MergeCommit.swift @@ -0,0 +1,19 @@ +// +// MergeCommit.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum MergeCommitMessage: String, Codable, Sendable { + case prTitle = "PR_TITLE" + case prBody = "PR_BODY" + case blank = "BLANK" +} + +public enum MergeCommitTitle: String, Codable, Sendable { + case prTitle = "PR_TITLE" + case mergeMessage = "MERGE_MESSAGE" +} diff --git a/Sources/Github/Models/Repository/Permission.swift b/Sources/Github/Models/Repository/Permission.swift new file mode 100644 index 0000000..f79f33e --- /dev/null +++ b/Sources/Github/Models/Repository/Permission.swift @@ -0,0 +1,30 @@ +// +// Permission.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Permission: Codable, Hashable, Sendable { + public let admin: Bool + public let maintain: Bool + public let push: Bool + public let triage: Bool + public let pull: Bool + + public init( + admin: Bool, + maintain: Bool, + push: Bool, + triage: Bool, + pull: Bool + ) { + self.admin = admin + self.maintain = maintain + self.push = push + self.triage = triage + self.pull = pull + } +} diff --git a/Sources/Github/Models/Repository/ProtectionTag.swift b/Sources/Github/Models/Repository/ProtectionTag.swift new file mode 100644 index 0000000..9fbda3e --- /dev/null +++ b/Sources/Github/Models/Repository/ProtectionTag.swift @@ -0,0 +1,38 @@ +// +// ProtectionTag.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct ProtectionTag: Codable, Sendable, Hashable, Identifiable { + public let id: Int + public let createdAt: Date? + public let updatedAt: Date? + public let enabled: Bool? + public let pattern: String + + public init( + id: Int, + createdAt: Date?, + updatedAt: Date?, + enabled: Bool?, + pattern: String + ) { + self.id = id + self.createdAt = createdAt + self.updatedAt = updatedAt + self.enabled = enabled + self.pattern = pattern + } + + private enum CodingKeys: String, CodingKey { + case id + case createdAt = "created_at" + case updatedAt = "updated_at" + case enabled + case pattern + } +} diff --git a/Sources/Github/Models/Repository/RepositoriesResponse.swift b/Sources/Github/Models/Repository/RepositoriesResponse.swift new file mode 100644 index 0000000..3c41d60 --- /dev/null +++ b/Sources/Github/Models/Repository/RepositoriesResponse.swift @@ -0,0 +1,20 @@ +// +// RepositoriesResponse.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct RepositoriesResponse: Codable { + public let totalCount: Int + public let incompleteResults: Bool + public let repositories: [Repository] + + private enum CodingKeys: String, CodingKey { + case totalCount = "total_count" + case incompleteResults = "incomplete_results" + case repositories = "items" + } +} diff --git a/Sources/Github/Models/Repository/Repository.swift b/Sources/Github/Models/Repository/Repository.swift new file mode 100644 index 0000000..e2b97e2 --- /dev/null +++ b/Sources/Github/Models/Repository/Repository.swift @@ -0,0 +1,414 @@ +// +// Repository.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Repository: Codable, Sendable, Hashable, Identifiable { + public let id: Int + public let nodeID: String + public let name: String + public let fullName: String + public let isPrivate: Bool + public let owner: User? + public let organization: User? + public let htmlURL: URL + public let description: String? + public let isFork: Bool + public let url: URL + public let forksURL: URL + public let keysURL: URL + public let collaboratorsURL: URL + public let teamsURL: URL + public let hooksURL: URL + public let issueEventsURL: URL + public let eventsURL: URL + public let assigneesURL: URL + public let branchesURL: URL + public let tagsURL: URL + public let blobsURL: URL + public let gitTagsURL: URL + public let gitRefsURL: URL + public let treesURL: URL + public let statusesURL: URL + public let languagesURL: URL + public let stargazersURL: URL + public let contributorsURL: URL + public let subscribersURL: URL + public let subscriptionURL: URL + public let commitsURL: URL + public let gitCommitsURL: URL + public let commentsURL: URL + public let issueCommentURL: URL + public let contentsURL: URL + public let compareURL: URL + public let mergesURL: URL + public let archiveURL: URL + public let downloadsURL: URL + public let issuesURL: URL + public let pullsURL: URL + public let milestonesURL: URL + public let notificationsURL: URL + public let labelsURL: URL + public let releasesURL: URL + public let deploymentsURL: URL + public let createdAt: Date? + public let updatedAt: Date? + public let pushedAt: Date? + public let gitURL: URL? + public let sshURL: URL? + public let cloneURL: URL? + public let svnURL: URL? + public let homepage: String? + public let size: Int? + public let stargazersCount: Int? + public let watchersCount: Int? + public let language: String? + public let hasIssues: Bool? + public let hasProjects: Bool? + public let hasDownloads: Bool? + public let hasWiki: Bool? + public let hasPages: Bool? + public let hasDiscussions: Bool? + public let forksCount: Int? + public let mirrorURL: URL? + public let isArchived: Bool? + public let disabled: Bool? + public let openIssuesCount: Int? + public let license: SimpleLicense? + public let allowForking: Bool? + public let isTemplate: Bool? + public let webCommitSignoffRequired: Bool? + public let topics: [String]? + public let visibility: Visibility? + public let defaultBranch: String? + public let permissions: Permission? + public let roleName: Role? + public let tempCloneToken: String? + public let deleteBranchOnMerge: Bool? + public let subscribersCount: Int? + public let networkCount: Int? + public let codeConduct: CodeConduct? + public let securityAnalytics: SecurityAnalytics? + public let anonymousAccessEnabled: Bool? + public let starredAt: Date? + public let masterBranch: String? + public let allowMergeCommit: Bool? + public let mergeCommitMessage: MergeCommitMessage? + public let mergeCommitTitle: MergeCommitTitle? + public let squashMergeCommitMessage: SquashMergeCommitMessage? + public let squashMergeCommitTitle: SquashMergeCommitTitle? + public let useSquashPrTitleAsDefault: Bool? + public let allowUpdateBranch: Bool? + public let allowAutoMerge: Bool? + public let allowSquashMerge: Bool? + public let templateRepository: TemplateRepository? + public let allowRebaseMerge: Bool? + + public init( + id: Int, + nodeID: String, + name: String, + fullName: String, + isPrivate: Bool, + owner: User?, + organization: User?, + htmlURL: URL, + description: String?, + isFork: Bool, + url: URL, + forksURL: URL, + keysURL: URL, + collaboratorsURL: URL, + teamsURL: URL, + hooksURL: URL, + issueEventsURL: URL, + eventsURL: URL, + assigneesURL: URL, + branchesURL: URL, + tagsURL: URL, + blobsURL: URL, + gitTagsURL: URL, + gitRefsURL: URL, + treesURL: URL, + statusesURL: URL, + languagesURL: URL, + stargazersURL: URL, + contributorsURL: URL, + subscribersURL: URL, + subscriptionURL: URL, + commitsURL: URL, + gitCommitsURL: URL, + commentsURL: URL, + issueCommentURL: URL, + contentsURL: URL, + compareURL: URL, + mergesURL: URL, + archiveURL: URL, + downloadsURL: URL, + issuesURL: URL, + pullsURL: URL, + milestonesURL: URL, + notificationsURL: URL, + labelsURL: URL, + releasesURL: URL, + deploymentsURL: URL, + createdAt: Date?, + updatedAt: Date?, + pushedAt: Date?, + gitURL: URL?, + sshURL: URL?, + cloneURL: URL?, + svnURL: URL?, + homepage: String?, + size: Int?, + stargazersCount: Int?, + watchersCount: Int?, + language: String?, + hasIssues: Bool?, + hasProjects: Bool?, + hasDownloads: Bool?, + hasWiki: Bool?, + hasPages: Bool?, + hasDiscussions: Bool?, + forksCount: Int?, + mirrorURL: URL?, + isArchived: Bool?, + disabled: Bool?, + openIssuesCount: Int?, + license: SimpleLicense?, + allowForking: Bool?, + isTemplate: Bool?, + webCommitSignoffRequired: Bool?, + topics: [String]?, + visibility: Visibility?, + defaultBranch: String?, + permissions: Permission?, + roleName: Role?, + tempCloneToken: String?, + deleteBranchOnMerge: Bool?, + subscribersCount: Int?, + networkCount: Int?, + codeConduct: CodeConduct?, + securityAnalytics: SecurityAnalytics?, + anonymousAccessEnabled: Bool?, + starredAt: Date?, + masterBranch: String?, + allowMergeCommit: Bool?, + mergeCommitMessage: MergeCommitMessage?, + mergeCommitTitle: MergeCommitTitle?, + squashMergeCommitMessage: SquashMergeCommitMessage?, + squashMergeCommitTitle: SquashMergeCommitTitle?, + useSquashPrTitleAsDefault: Bool?, + allowUpdateBranch: Bool?, + allowAutoMerge: Bool?, + allowSquashMerge: Bool?, + allowRebaseMerge: Bool?, + templateRepository: TemplateRepository? + ) { + self.id = id + self.nodeID = nodeID + self.name = name + self.fullName = fullName + self.isPrivate = isPrivate + self.owner = owner + self.organization = organization + self.htmlURL = htmlURL + self.description = description + self.isFork = isFork + self.url = url + self.forksURL = forksURL + self.keysURL = keysURL + self.collaboratorsURL = collaboratorsURL + self.teamsURL = teamsURL + self.hooksURL = hooksURL + self.issueEventsURL = issueEventsURL + self.eventsURL = eventsURL + self.assigneesURL = assigneesURL + self.branchesURL = branchesURL + self.tagsURL = tagsURL + self.blobsURL = blobsURL + self.gitTagsURL = gitTagsURL + self.gitRefsURL = gitRefsURL + self.treesURL = treesURL + self.statusesURL = statusesURL + self.languagesURL = languagesURL + self.stargazersURL = stargazersURL + self.contributorsURL = contributorsURL + self.subscribersURL = subscribersURL + self.subscriptionURL = subscriptionURL + self.commitsURL = commitsURL + self.gitCommitsURL = gitCommitsURL + self.commentsURL = commentsURL + self.issueCommentURL = issueCommentURL + self.contentsURL = contentsURL + self.compareURL = compareURL + self.mergesURL = mergesURL + self.archiveURL = archiveURL + self.downloadsURL = downloadsURL + self.issuesURL = issuesURL + self.pullsURL = pullsURL + self.milestonesURL = milestonesURL + self.notificationsURL = notificationsURL + self.labelsURL = labelsURL + self.releasesURL = releasesURL + self.deploymentsURL = deploymentsURL + self.createdAt = createdAt + self.updatedAt = updatedAt + self.pushedAt = pushedAt + self.gitURL = gitURL + self.sshURL = sshURL + self.cloneURL = cloneURL + self.svnURL = svnURL + self.homepage = homepage + self.size = size + self.stargazersCount = stargazersCount + self.watchersCount = watchersCount + self.language = language + self.hasIssues = hasIssues + self.hasProjects = hasProjects + self.hasDownloads = hasDownloads + self.hasWiki = hasWiki + self.hasPages = hasPages + self.hasDiscussions = hasDiscussions + self.forksCount = forksCount + self.mirrorURL = mirrorURL + self.isArchived = isArchived + self.disabled = disabled + self.openIssuesCount = openIssuesCount + self.license = license + self.allowForking = allowForking + self.isTemplate = isTemplate + self.webCommitSignoffRequired = webCommitSignoffRequired + self.topics = topics + self.visibility = visibility + self.defaultBranch = defaultBranch + self.permissions = permissions + self.roleName = roleName + self.tempCloneToken = tempCloneToken + self.deleteBranchOnMerge = deleteBranchOnMerge + self.subscribersCount = subscribersCount + self.networkCount = networkCount + self.codeConduct = codeConduct + self.securityAnalytics = securityAnalytics + self.anonymousAccessEnabled = anonymousAccessEnabled + self.starredAt = starredAt + self.masterBranch = masterBranch + self.allowMergeCommit = allowMergeCommit + self.mergeCommitMessage = mergeCommitMessage + self.mergeCommitTitle = mergeCommitTitle + self.squashMergeCommitMessage = squashMergeCommitMessage + self.squashMergeCommitTitle = squashMergeCommitTitle + self.useSquashPrTitleAsDefault = useSquashPrTitleAsDefault + self.allowUpdateBranch = allowUpdateBranch + self.allowAutoMerge = allowAutoMerge + self.allowSquashMerge = allowSquashMerge + self.allowRebaseMerge = allowRebaseMerge + self.templateRepository = templateRepository + } + + private enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case name + case fullName = "full_name" + case isPrivate = "private" + case owner + case organization + case htmlURL = "html_url" + case description + case isFork = "fork" + case url + case forksURL = "forks_url" + case keysURL = "keys_url" + case collaboratorsURL = "collaborators_url" + case teamsURL = "teams_url" + case hooksURL = "hooks_url" + case issueEventsURL = "issue_events_url" + case eventsURL = "events_url" + case assigneesURL = "assignees_url" + case branchesURL = "branches_url" + case tagsURL = "tags_url" + case blobsURL = "blobs_url" + case gitTagsURL = "git_tags_url" + case gitRefsURL = "git_refs_url" + case treesURL = "trees_url" + case statusesURL = "statuses_url" + case languagesURL = "languages_url" + case stargazersURL = "stargazers_url" + case contributorsURL = "contributors_url" + case subscribersURL = "subscribers_url" + case subscriptionURL = "subscription_url" + case commitsURL = "commits_url" + case gitCommitsURL = "git_commits_url" + case commentsURL = "comments_url" + case issueCommentURL = "issue_comment_url" + case contentsURL = "contents_url" + case compareURL = "compare_url" + case mergesURL = "merges_url" + case archiveURL = "archive_url" + case downloadsURL = "downloads_url" + case issuesURL = "issues_url" + case pullsURL = "pulls_url" + case milestonesURL = "milestones_url" + case notificationsURL = "notifications_url" + case labelsURL = "labels_url" + case releasesURL = "releases_url" + case deploymentsURL = "deployments_url" + case createdAt = "created_at" + case updatedAt = "updated_at" + case pushedAt = "pushed_at" + case gitURL = "git_url" + case sshURL = "ssh_url" + case cloneURL = "clone_url" + case svnURL = "svn_url" + case homepage + case size + case stargazersCount = "stargazers_count" + case watchersCount = "watchers_count" + case language + case hasIssues = "has_issues" + case hasProjects = "has_projects" + case hasDownloads = "has_downloads" + case hasWiki = "has_wiki" + case hasPages = "has_pages" + case hasDiscussions = "has_discussions" + case forksCount = "forks_count" + case mirrorURL = "mirror_url" + case isArchived = "archived" + case disabled + case openIssuesCount = "open_issues_count" + case license + case allowForking = "allow_forking" + case isTemplate = "is_template" + case webCommitSignoffRequired = "web_commit_signoff_required" + case topics + case visibility + case defaultBranch = "default_branch" + case permissions + case roleName = "role_name" + case tempCloneToken = "temp_clone_token" + case deleteBranchOnMerge = "delete_branch_on_merge" + case subscribersCount = "subscribers_count" + case networkCount = "network_count" + case codeConduct = "code_of_conduct" + case securityAnalytics = "security_and_analysis" + case anonymousAccessEnabled = "anonymous_access_enabled" + case starredAt = "starred_at" + case masterBranch = "master_branch" + case allowMergeCommit = "allow_merge_commit" + case mergeCommitMessage = "merge_commit_message" + case mergeCommitTitle = "merge_commit_title" + case squashMergeCommitMessage = "squash_merge_commit_message" + case squashMergeCommitTitle = "squash_merge_commit_title" + case useSquashPrTitleAsDefault = "use_squash_pr_title_as_default" + case allowUpdateBranch = "allow_update_branch" + case allowAutoMerge = "allow_auto_merge" + case allowSquashMerge = "allow_squash_merge" + case templateRepository = "template_repository" + case allowRebaseMerge = "allow_rebase_merge" + } +} diff --git a/Sources/Github/Models/Repository/SecurityAnalytics.swift b/Sources/Github/Models/Repository/SecurityAnalytics.swift new file mode 100644 index 0000000..199e8e1 --- /dev/null +++ b/Sources/Github/Models/Repository/SecurityAnalytics.swift @@ -0,0 +1,89 @@ +// +// SecurityAnalytics.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct SecurityAnalytics: Codable, Sendable, Hashable { + public let advancedSecurity: Bool? + public let dependabotSecurityUpdates: Bool + public let secretScanning: Bool + public let secretScanningPushProtection: Bool + + private enum CodingKeys: String, CodingKey { + case advancedSecurity = "advanced_security" + case dependabotSecurityUpdates = "dependabot_security_updates" + case secretScanning = "secret_scanning" + case secretScanningPushProtection = "secret_scanning_push_protection" + } + + private enum StatusCodingKeys: CodingKey { + case status + } + + private enum Status: String, Codable { + case enabled + case disabled + } + + public init( + advancedSecurity: Bool?, + dependabotSecurityUpdates: Bool, + secretScanning: Bool, + secretScanningPushProtection: Bool + ) { + self.advancedSecurity = advancedSecurity + self.dependabotSecurityUpdates = dependabotSecurityUpdates + self.secretScanning = secretScanning + self.secretScanningPushProtection = secretScanningPushProtection + } + + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + do { + let nestedContainer = try? container.nestedContainer(keyedBy: StatusCodingKeys.self, forKey: .advancedSecurity) + self.advancedSecurity = try nestedContainer?.decode(Status.self,forKey: .status) == .enabled + } + do { + let nestedContainer = try container.nestedContainer(keyedBy: StatusCodingKeys.self, forKey: .dependabotSecurityUpdates) + self.dependabotSecurityUpdates = try nestedContainer.decode(Status.self,forKey: .status) == .enabled + } + do { + let nestedContainer = try container.nestedContainer(keyedBy: StatusCodingKeys.self, forKey: .secretScanning) + self.secretScanning = try nestedContainer.decode(Status.self,forKey: .status) == .enabled + } + do { + let nestedContainer = try container.nestedContainer(keyedBy: StatusCodingKeys.self, forKey: .secretScanningPushProtection) + self.secretScanningPushProtection = try nestedContainer.decode(Status.self,forKey: .status) == .enabled + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + do { + var nestedContainer = container.nestedContainer(keyedBy: StatusCodingKeys.self, forKey: .advancedSecurity) + if let advancedSecurity { + let value: Status = advancedSecurity ? .enabled : .disabled + try nestedContainer.encode(value.rawValue, forKey: .status) + } + } + do { + var nestedContainer = container.nestedContainer(keyedBy: StatusCodingKeys.self, forKey: .dependabotSecurityUpdates) + let value: Status = dependabotSecurityUpdates ? .enabled : .disabled + try nestedContainer.encode(value.rawValue, forKey: .status) + } + do { + var nestedContainer = container.nestedContainer(keyedBy: StatusCodingKeys.self, forKey: .secretScanning) + let value: Status = secretScanning ? .enabled : .disabled + try nestedContainer.encode(value.rawValue, forKey: .status) + } + do { + var nestedContainer = container.nestedContainer(keyedBy: StatusCodingKeys.self, forKey: .secretScanningPushProtection) + let value: Status = secretScanningPushProtection ? .enabled : .disabled + try nestedContainer.encode(value.rawValue, forKey: .status) + } + } +} diff --git a/Sources/Github/Models/Repository/SimpleLicense.swift b/Sources/Github/Models/Repository/SimpleLicense.swift new file mode 100644 index 0000000..333aa05 --- /dev/null +++ b/Sources/Github/Models/Repository/SimpleLicense.swift @@ -0,0 +1,38 @@ +// +// SimpleLicense.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct SimpleLicense: Codable, Hashable, Sendable { + public let key: String + public let name: String + public let spdxID: String + public let url: URL? + public let nodeID: String + + public init( + key: String, + name: String, + spdxID: String, + url: URL?, + nodeID: String + ) { + self.key = key + self.name = name + self.spdxID = spdxID + self.url = url + self.nodeID = nodeID + } + + private enum CodingKeys: String, CodingKey { + case key + case name + case spdxID = "spdx_id" + case url + case nodeID = "node_id" + } +} diff --git a/Sources/Github/Models/Repository/SquashMergeCommit.swift b/Sources/Github/Models/Repository/SquashMergeCommit.swift new file mode 100644 index 0000000..48360f4 --- /dev/null +++ b/Sources/Github/Models/Repository/SquashMergeCommit.swift @@ -0,0 +1,19 @@ +// +// SquashMergeCommit.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum SquashMergeCommitMessage: String, Codable, Sendable { + case prBody = "PR_BODY" + case commitMessages = "COMMIT_MESSAGES" + case blank = "BLANK" +} + +public enum SquashMergeCommitTitle: String, Codable, Sendable { + case prTitle = "PR_TITLE" + case commitOrPrTitle = "COMMIT_OR_PR_TITLE" +} diff --git a/Sources/Github/Models/Repository/Tag.swift b/Sources/Github/Models/Repository/Tag.swift new file mode 100644 index 0000000..6d748d2 --- /dev/null +++ b/Sources/Github/Models/Repository/Tag.swift @@ -0,0 +1,53 @@ +// +// Tag.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Tag: Codable, Sendable, Hashable { + public let name: String + public let commit: Commit + public let zipBallURL: URL + public let tarBallURL: URL + public let nodeID: String + + public init( + name: String, + commit: Commit, + zipBallURL: URL, + tarBallURL: URL, + nodeID: String + ) { + self.name = name + self.commit = commit + self.zipBallURL = zipBallURL + self.tarBallURL = tarBallURL + self.nodeID = nodeID + } + + private enum CodingKeys: String, CodingKey { + case name + case commit + case zipBallURL = "zipball_url" + case tarBallURL = "tarball_url" + case nodeID = "node_id" + } +} + +extension Tag { + public struct Commit: Codable, Sendable, Hashable { + public let sha: String + public let url: URL + + public init( + sha: String, + url: URL + ) { + self.sha = sha + self.url = url + } + } +} diff --git a/Sources/Github/Models/Repository/TemplateRepository.swift b/Sources/Github/Models/Repository/TemplateRepository.swift new file mode 100644 index 0000000..2346203 --- /dev/null +++ b/Sources/Github/Models/Repository/TemplateRepository.swift @@ -0,0 +1,402 @@ +// +// TemplateRepository.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct TemplateRepository: Codable, Sendable, Hashable, Identifiable { + public let id: Int + public let nodeID: String + public let name: String + public let fullName: String + public let isPrivate: Bool + public let owner: User? + public let organization: User? + public let htmlURL: URL + public let description: String? + public let isFork: Bool + public let url: URL + public let forksURL: URL + public let keysURL: URL + public let collaboratorsURL: URL + public let teamsURL: URL + public let hooksURL: URL + public let issueEventsURL: URL + public let eventsURL: URL + public let assigneesURL: URL + public let branchesURL: URL + public let tagsURL: URL + public let blobsURL: URL + public let gitTagsURL: URL + public let gitRefsURL: URL + public let treesURL: URL + public let statusesURL: URL + public let languagesURL: URL + public let stargazersURL: URL + public let contributorsURL: URL + public let subscribersURL: URL + public let subscriptionURL: URL + public let commitsURL: URL + public let gitCommitsURL: URL + public let commentsURL: URL + public let issueCommentURL: URL + public let contentsURL: URL + public let compareURL: URL + public let mergesURL: URL + public let archiveURL: URL + public let downloadsURL: URL + public let issuesURL: URL + public let pullsURL: URL + public let milestonesURL: URL + public let notificationsURL: URL + public let labelsURL: URL + public let releasesURL: URL + public let deploymentsURL: URL + public let createdAt: Date? + public let updatedAt: Date? + public let pushedAt: Date? + public let gitURL: URL? + public let sshURL: URL? + public let cloneURL: URL? + public let svnURL: URL? + public let homepage: String? + public let size: Int? + public let stargazersCount: Int? + public let watchersCount: Int? + public let language: String? + public let hasIssues: Bool + public let hasProjects: Bool + public let hasDownloads: Bool + public let hasWiki: Bool + public let hasPages: Bool + public let hasDiscussions: Bool + public let forksCount: Int? + public let mirrorURL: URL? + public let isArchived: Bool? + public let disabled: Bool? + public let openIssuesCount: Int? + public let isTemplate: Bool? + public let webCommitSignoffRequired: Bool? + public let topics: [String]? + public let visibility: Visibility? + public let defaultBranch: String? + public let permissions: Permission? + public let roleName: Role? + public let tempCloneToken: String? + public let deleteBranchOnMerge: Bool? + public let subscribersCount: Int? + public let networkCount: Int? + public let codeConduct: CodeConduct? + public let securityAnalytics: SecurityAnalytics? + public let anonymousAccessEnabled: Bool? + public let starredAt: Date? + public let masterBranch: String? + public let allowMergeCommit: Bool? + public let mergeCommitMessage: MergeCommitMessage? + public let mergeCommitTitle: MergeCommitTitle? + public let squashMergeCommitMessage: SquashMergeCommitMessage? + public let squashMergeCommitTitle: SquashMergeCommitTitle? + public let useSquashPrTitleAsDefault: Bool? + public let allowUpdateBranch: Bool? + public let allowAutoMerge: Bool? + public let allowSquashMerge: Bool? + public let allowRebaseMerge: Bool? + + public init( + id: Int, + nodeID: String, + name: String, + fullName: String, + isPrivate: Bool, + owner: User?, + organization: User?, + htmlURL: URL, + description: String?, + isFork: Bool, + url: URL, + forksURL: URL, + keysURL: URL, + collaboratorsURL: URL, + teamsURL: URL, + hooksURL: URL, + issueEventsURL: URL, + eventsURL: URL, + assigneesURL: URL, + branchesURL: URL, + tagsURL: URL, + blobsURL: URL, + gitTagsURL: URL, + gitRefsURL: URL, + treesURL: URL, + statusesURL: URL, + languagesURL: URL, + stargazersURL: URL, + contributorsURL: URL, + subscribersURL: URL, + subscriptionURL: URL, + commitsURL: URL, + gitCommitsURL: URL, + commentsURL: URL, + issueCommentURL: URL, + contentsURL: URL, + compareURL: URL, + mergesURL: URL, + archiveURL: URL, + downloadsURL: URL, + issuesURL: URL, + pullsURL: URL, + milestonesURL: URL, + notificationsURL: URL, + labelsURL: URL, + releasesURL: URL, + deploymentsURL: URL, + createdAt: Date?, + updatedAt: Date?, + pushedAt: Date?, + gitURL: URL?, + sshURL: URL?, + cloneURL: URL?, + svnURL: URL?, + homepage: String?, + size: Int?, + stargazersCount: Int?, + watchersCount: Int?, + language: String?, + hasIssues: Bool, + hasProjects: Bool, + hasDownloads: Bool, + hasWiki: Bool, + hasPages: Bool, + hasDiscussions: Bool, + forksCount: Int?, + mirrorURL: URL?, + isArchived: Bool?, + disabled: Bool?, + openIssuesCount: Int?, + isTemplate: Bool?, + webCommitSignoffRequired: Bool?, + topics: [String]?, + visibility: Visibility?, + defaultBranch: String?, + permissions: Permission?, + roleName: Role?, + tempCloneToken: String?, + deleteBranchOnMerge: Bool?, + subscribersCount: Int?, + networkCount: Int?, + codeConduct: CodeConduct?, + securityAnalytics: SecurityAnalytics?, + anonymousAccessEnabled: Bool?, + starredAt: Date?, + masterBranch: String?, + allowMergeCommit: Bool?, + mergeCommitMessage: MergeCommitMessage?, + mergeCommitTitle: MergeCommitTitle?, + squashMergeCommitMessage: SquashMergeCommitMessage?, + squashMergeCommitTitle: SquashMergeCommitTitle?, + useSquashPrTitleAsDefault: Bool?, + allowUpdateBranch: Bool?, + allowAutoMerge: Bool?, + allowSquashMerge: Bool?, + allowRebaseMerge: Bool? + ) { + self.id = id + self.nodeID = nodeID + self.name = name + self.fullName = fullName + self.isPrivate = isPrivate + self.owner = owner + self.organization = organization + self.htmlURL = htmlURL + self.description = description + self.isFork = isFork + self.url = url + self.forksURL = forksURL + self.keysURL = keysURL + self.collaboratorsURL = collaboratorsURL + self.teamsURL = teamsURL + self.hooksURL = hooksURL + self.issueEventsURL = issueEventsURL + self.eventsURL = eventsURL + self.assigneesURL = assigneesURL + self.branchesURL = branchesURL + self.tagsURL = tagsURL + self.blobsURL = blobsURL + self.gitTagsURL = gitTagsURL + self.gitRefsURL = gitRefsURL + self.treesURL = treesURL + self.statusesURL = statusesURL + self.languagesURL = languagesURL + self.stargazersURL = stargazersURL + self.contributorsURL = contributorsURL + self.subscribersURL = subscribersURL + self.subscriptionURL = subscriptionURL + self.commitsURL = commitsURL + self.gitCommitsURL = gitCommitsURL + self.commentsURL = commentsURL + self.issueCommentURL = issueCommentURL + self.contentsURL = contentsURL + self.compareURL = compareURL + self.mergesURL = mergesURL + self.archiveURL = archiveURL + self.downloadsURL = downloadsURL + self.issuesURL = issuesURL + self.pullsURL = pullsURL + self.milestonesURL = milestonesURL + self.notificationsURL = notificationsURL + self.labelsURL = labelsURL + self.releasesURL = releasesURL + self.deploymentsURL = deploymentsURL + self.createdAt = createdAt + self.updatedAt = updatedAt + self.pushedAt = pushedAt + self.gitURL = gitURL + self.sshURL = sshURL + self.cloneURL = cloneURL + self.svnURL = svnURL + self.homepage = homepage + self.size = size + self.stargazersCount = stargazersCount + self.watchersCount = watchersCount + self.language = language + self.hasIssues = hasIssues + self.hasProjects = hasProjects + self.hasDownloads = hasDownloads + self.hasWiki = hasWiki + self.hasPages = hasPages + self.hasDiscussions = hasDiscussions + self.forksCount = forksCount + self.mirrorURL = mirrorURL + self.isArchived = isArchived + self.disabled = disabled + self.openIssuesCount = openIssuesCount + self.isTemplate = isTemplate + self.webCommitSignoffRequired = webCommitSignoffRequired + self.topics = topics + self.visibility = visibility + self.defaultBranch = defaultBranch + self.permissions = permissions + self.roleName = roleName + self.tempCloneToken = tempCloneToken + self.deleteBranchOnMerge = deleteBranchOnMerge + self.subscribersCount = subscribersCount + self.networkCount = networkCount + self.codeConduct = codeConduct + self.securityAnalytics = securityAnalytics + self.anonymousAccessEnabled = anonymousAccessEnabled + self.starredAt = starredAt + self.masterBranch = masterBranch + self.allowMergeCommit = allowMergeCommit + self.mergeCommitMessage = mergeCommitMessage + self.mergeCommitTitle = mergeCommitTitle + self.squashMergeCommitMessage = squashMergeCommitMessage + self.squashMergeCommitTitle = squashMergeCommitTitle + self.useSquashPrTitleAsDefault = useSquashPrTitleAsDefault + self.allowUpdateBranch = allowUpdateBranch + self.allowAutoMerge = allowAutoMerge + self.allowSquashMerge = allowSquashMerge + self.allowRebaseMerge = allowRebaseMerge + } + + private enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case name + case fullName = "full_name" + case isPrivate = "private" + case owner + case organization + case htmlURL = "html_url" + case description + case isFork = "fork" + case url + case forksURL = "forks_url" + case keysURL = "keys_url" + case collaboratorsURL = "collaborators_url" + case teamsURL = "teams_url" + case hooksURL = "hooks_url" + case issueEventsURL = "issue_events_url" + case eventsURL = "events_url" + case assigneesURL = "assignees_url" + case branchesURL = "branches_url" + case tagsURL = "tags_url" + case blobsURL = "blobs_url" + case gitTagsURL = "git_tags_url" + case gitRefsURL = "git_refs_url" + case treesURL = "trees_url" + case statusesURL = "statuses_url" + case languagesURL = "languages_url" + case stargazersURL = "stargazers_url" + case contributorsURL = "contributors_url" + case subscribersURL = "subscribers_url" + case subscriptionURL = "subscription_url" + case commitsURL = "commits_url" + case gitCommitsURL = "git_commits_url" + case commentsURL = "comments_url" + case issueCommentURL = "issue_comment_url" + case contentsURL = "contents_url" + case compareURL = "compare_url" + case mergesURL = "merges_url" + case archiveURL = "archive_url" + case downloadsURL = "downloads_url" + case issuesURL = "issues_url" + case pullsURL = "pulls_url" + case milestonesURL = "milestones_url" + case notificationsURL = "notifications_url" + case labelsURL = "labels_url" + case releasesURL = "releases_url" + case deploymentsURL = "deployments_url" + case createdAt = "created_at" + case updatedAt = "updated_at" + case pushedAt = "pushed_at" + case gitURL = "git_url" + case sshURL = "ssh_url" + case cloneURL = "clone_url" + case svnURL = "svn_url" + case homepage + case size + case stargazersCount = "stargazers_count" + case watchersCount = "watchers_count" + case language + case hasIssues = "has_issues" + case hasProjects = "has_projects" + case hasDownloads = "has_downloads" + case hasWiki = "has_wiki" + case hasPages = "has_pages" + case hasDiscussions = "has_discussions" + case forksCount = "forks_count" + case mirrorURL = "mirror_url" + case isArchived = "archived" + case disabled + case openIssuesCount = "open_issues_count" + case isTemplate = "is_template" + case webCommitSignoffRequired = "web_commit_signoff_required" + case topics + case visibility + case defaultBranch = "default_branch" + case permissions + case roleName = "role_name" + case tempCloneToken = "temp_clone_token" + case deleteBranchOnMerge = "delete_branch_on_merge" + case subscribersCount = "subscribers_count" + case networkCount = "network_count" + case codeConduct = "code_of_conduct" + case securityAnalytics = "security_and_analysis" + case anonymousAccessEnabled = "anonymous_access_enabled" + case starredAt = "starred_at" + case masterBranch = "master_branch" + case allowMergeCommit = "allow_merge_commit" + case mergeCommitMessage = "merge_commit_message" + case mergeCommitTitle = "merge_commit_title" + case squashMergeCommitMessage = "squash_merge_commit_message" + case squashMergeCommitTitle = "squash_merge_commit_title" + case useSquashPrTitleAsDefault = "use_squash_pr_title_as_default" + case allowUpdateBranch = "allow_update_branch" + case allowAutoMerge = "allow_auto_merge" + case allowSquashMerge = "allow_squash_merge" + case allowRebaseMerge = "allow_rebase_merge" + } +} diff --git a/Sources/Github/Models/Repository/Visibility.swift b/Sources/Github/Models/Repository/Visibility.swift new file mode 100644 index 0000000..135080a --- /dev/null +++ b/Sources/Github/Models/Repository/Visibility.swift @@ -0,0 +1,13 @@ +// +// Visibility.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum Visibility: String, Codable, Hashable, Sendable { + case `public` + case `private` +} diff --git a/Sources/Github/Models/Teams/NotificationSettings.swift b/Sources/Github/Models/Teams/NotificationSettings.swift new file mode 100644 index 0000000..479ec02 --- /dev/null +++ b/Sources/Github/Models/Teams/NotificationSettings.swift @@ -0,0 +1,12 @@ +// +// NotificationSettings.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum NotificationSettings: String, Codable, Sendable { + case enabled = "notifications_enabled" +} diff --git a/Sources/Github/Models/Teams/ParentTeam.swift b/Sources/Github/Models/Teams/ParentTeam.swift new file mode 100644 index 0000000..aec3dc9 --- /dev/null +++ b/Sources/Github/Models/Teams/ParentTeam.swift @@ -0,0 +1,66 @@ +// +// ParentTeam.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct ParentTeam: Hashable, Sendable, Identifiable, Codable { + public let id: Int + public let nodeID: String + public let membersURL: URL + public let name: String + public let description: String? + public let permission: PermissionType + public let privacy: Privacy + public let notificationSetting: NotificationSettings + public let htmlURL: URL + public let repositoriesURL: URL + public let slug: String + public let ldapDn: String? + + public init( + id: Int, + nodeID: String, + membersURL: URL, + name: String, + description: String?, + permission: PermissionType, + privacy: Privacy, + notificationSetting: NotificationSettings, + htmlURL: URL, + repositoriesURL: URL, + slug: String, + ldapDn: String? + ) { + self.id = id + self.nodeID = nodeID + self.membersURL = membersURL + self.name = name + self.description = description + self.permission = permission + self.privacy = privacy + self.notificationSetting = notificationSetting + self.htmlURL = htmlURL + self.repositoriesURL = repositoriesURL + self.slug = slug + self.ldapDn = ldapDn + } + + enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case membersURL = "members_url" + case name + case description + case permission + case privacy + case notificationSetting = "notification_setting" + case htmlURL = "html_url" + case repositoriesURL = "repositories_url" + case slug + case ldapDn = "ldap_dn" + } +} diff --git a/Sources/Github/Models/Teams/Privacy.swift b/Sources/Github/Models/Teams/Privacy.swift new file mode 100644 index 0000000..3cbf643 --- /dev/null +++ b/Sources/Github/Models/Teams/Privacy.swift @@ -0,0 +1,12 @@ +// +// Privacy.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum Privacy: String, Codable, Sendable { + case closed = "closed" +} diff --git a/Sources/Github/Models/Teams/Team.swift b/Sources/Github/Models/Teams/Team.swift new file mode 100644 index 0000000..1308977 --- /dev/null +++ b/Sources/Github/Models/Teams/Team.swift @@ -0,0 +1,70 @@ +// +// Team.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct Team: Hashable, Sendable, Identifiable, Codable { + public let id: Int + public let nodeID: String + public let name: String + public let slug: String + public let description: String? + public let privacy: Privacy + public let notificationSetting: NotificationSettings + public let permission: PermissionType + public let url: URL + public let htmlURL: URL + public let membersURL: URL + public let repositoriesURL: URL + public let parent: ParentTeam? + + public init( + id: Int, + nodeID: String, + name: String, + slug: String, + description: String?, + privacy: Privacy, + notificationSetting: NotificationSettings, + permission: PermissionType, + url: URL, + htmlURL: URL, + membersURL: URL, + repositoriesURL: URL, + parent: ParentTeam? + ) { + self.id = id + self.nodeID = nodeID + self.name = name + self.slug = slug + self.description = description + self.privacy = privacy + self.notificationSetting = notificationSetting + self.permission = permission + self.url = url + self.htmlURL = htmlURL + self.membersURL = membersURL + self.repositoriesURL = repositoriesURL + self.parent = parent + } + + private enum CodingKeys: String, CodingKey { + case id + case nodeID = "node_id" + case name + case slug + case description + case privacy + case notificationSetting = "notification_setting" + case permission + case url + case htmlURL = "html_url" + case membersURL = "members_url" + case repositoriesURL = "repositories_url" + case parent + } +} diff --git a/Sources/Github/Models/User/User.swift b/Sources/Github/Models/User/User.swift new file mode 100644 index 0000000..f7a067b --- /dev/null +++ b/Sources/Github/Models/User/User.swift @@ -0,0 +1,174 @@ +// +// User.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct User: Codable, Hashable, Sendable, Identifiable { + public let id: Int + public let userID: String + public let userName: String? + public let nodeID: String + public let avatarURL: URL + public let gravatarID: String + public let url: URL + public let htmlURL: URL + public let followersURL: URL + public let followingURL: URL + public let subscriptionsURL: URL + public let organizationsURL: URL + public let reposURL: URL + public let eventsURL: URL + public let receivedEventsURL: URL + public let gistsURL: URL + public let starredURL: URL + public let publicRepoCount: Int? + public let totalPrivateRepoCount: Int? + public let ownedPrivateRepoCount: Int? + public let publicGistsCount: Int? + public let privateGistsCount: Int? + public let followerCount: Int? + public let followingCount: Int? + public let createdAt: Date? + public let updatedAt: Date? + public let bio: String? + public let email: String? + public let location: String? + public let hireable: Bool? + public let type: UserType + public let score: Int? + public let siteAdmin: Bool + public let twitterUserName: String? + public let company: String? + public let diskUsage: Int? + public let collaboratorCount: Int? + public let twoFactorAuthentication: Bool? + public let plan: Plan? + + public init( + id: Int, + userID: String, + userName: String?, + nodeID: String, + avatarURL: URL, + gravatarID: String, + url: URL, + htmlURL: URL, + followersURL: URL, + followingURL: URL, + subscriptionsURL: URL, + organizationsURL: URL, + reposURL: URL, + eventsURL: URL, + receivedEventsURL: URL, + gistsURL: URL, + starredURL: URL, + publicRepoCount: Int?, + totalPrivateRepoCount: Int?, + ownedPrivateRepoCount: Int?, + publicGistsCount: Int?, + privateGistsCount: Int?, + followerCount: Int?, + followingCount: Int?, + createdAt: Date?, + updatedAt: Date?, + bio: String?, + email: String?, + location: String?, + hireable: Bool?, + type: UserType, + score: Int?, + siteAdmin: Bool, + twitterUserName: String?, + company: String?, + diskUsage: Int?, + collaboratorCount: Int?, + twoFactorAuthentication: Bool?, + plan: Plan? + ) { + self.id = id + self.userID = userID + self.userName = userName + self.nodeID = nodeID + self.avatarURL = avatarURL + self.gravatarID = gravatarID + self.url = url + self.htmlURL = htmlURL + self.followersURL = followersURL + self.followingURL = followingURL + self.subscriptionsURL = subscriptionsURL + self.organizationsURL = organizationsURL + self.reposURL = reposURL + self.eventsURL = eventsURL + self.receivedEventsURL = receivedEventsURL + self.gistsURL = gistsURL + self.starredURL = starredURL + self.publicRepoCount = publicRepoCount + self.totalPrivateRepoCount = totalPrivateRepoCount + self.ownedPrivateRepoCount = ownedPrivateRepoCount + self.publicGistsCount = publicGistsCount + self.privateGistsCount = privateGistsCount + self.followerCount = followerCount + self.followingCount = followingCount + self.createdAt = createdAt + self.updatedAt = updatedAt + self.bio = bio + self.email = email + self.location = location + self.hireable = hireable + self.type = type + self.score = score + self.siteAdmin = siteAdmin + self.twitterUserName = twitterUserName + self.company = company + self.diskUsage = diskUsage + self.collaboratorCount = collaboratorCount + self.twoFactorAuthentication = twoFactorAuthentication + self.plan = plan + } + + private enum CodingKeys: String, CodingKey { + case id + case userID = "login" + case userName = "name" + case nodeID = "node_id" + case avatarURL = "avatar_url" + case gravatarID = "gravatar_id" + case url + case htmlURL = "html_url" + case followersURL = "followers_url" + case followingURL = "following_url" + case gistsURL = "gists_url" + case starredURL = "starred_url" + case subscriptionsURL = "subscriptions_url" + case organizationsURL = "organizations_url" + case reposURL = "repos_url" + case eventsURL = "events_url" + case receivedEventsURL = "received_events_url" + case type + case siteAdmin = "site_admin" + case score + case publicRepoCount = "public_repos" + case totalPrivateRepoCount = "total_private_repos" + case ownedPrivateRepoCount = "owned_private_repos" + case publicGistsCount = "public_gists" + case privateGistsCount = "private_gists" + case followerCount = "followers" + case followingCount = "following" + case createdAt = "created_at" + case updatedAt = "updated_at" + case bio + case email + case location + case hireable + case twitterUserName = "twitter_username" + case company + case diskUsage = "disk_usage" + case collaboratorCount = "collaborators" + case twoFactorAuthentication = "two_factor_authentication" + case plan + } +} diff --git a/Sources/Github/Models/User/UserType.swift b/Sources/Github/Models/User/UserType.swift new file mode 100644 index 0000000..4500de5 --- /dev/null +++ b/Sources/Github/Models/User/UserType.swift @@ -0,0 +1,15 @@ +// +// UserType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum UserType: String, Codable, Sendable { + case user = "User" + case organization = "Organization" + case mannequin = "Mannequin" + case bot = "Bot" +} diff --git a/Sources/Github/Models/User/UsersResponse.swift b/Sources/Github/Models/User/UsersResponse.swift new file mode 100644 index 0000000..52f213e --- /dev/null +++ b/Sources/Github/Models/User/UsersResponse.swift @@ -0,0 +1,20 @@ +// +// UsersResponse.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct UsersResponse: Codable, Sendable { + public let totalCount: Int + public let incompleteResults: Bool + public let users: [User] + + private enum CodingKeys: String, CodingKey { + case totalCount = "total_count" + case incompleteResults = "incomplete_results" + case users = "items" + } +} diff --git a/Sources/Github/exported.swift b/Sources/Github/exported.swift new file mode 100644 index 0000000..4b49b78 --- /dev/null +++ b/Sources/Github/exported.swift @@ -0,0 +1,8 @@ +// +// exported.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +@_exported import HttpClient diff --git a/Sources/HttpClient/Authentication/Auth.swift b/Sources/HttpClient/Authentication/Auth.swift deleted file mode 100644 index e3e3d9b..0000000 --- a/Sources/HttpClient/Authentication/Auth.swift +++ /dev/null @@ -1,8 +0,0 @@ -// -// Auth.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation diff --git a/Sources/HttpClient/AuthorizationType.swift b/Sources/HttpClient/AuthorizationType.swift new file mode 100644 index 0000000..41ea8b0 --- /dev/null +++ b/Sources/HttpClient/AuthorizationType.swift @@ -0,0 +1,13 @@ +// +// AuthorizationType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum AuthorizationType: Sendable { + case bearerToken(accessToken: String) + case withoutToken +} diff --git a/Sources/HttpClient/Codable++.swift b/Sources/HttpClient/Codable++.swift new file mode 100644 index 0000000..cc51bb5 --- /dev/null +++ b/Sources/HttpClient/Codable++.swift @@ -0,0 +1,37 @@ +// +// Codable++.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public extension JSONDecoder { + static var github: JSONDecoder { + let decoder = JSONDecoder() + + decoder.dateDecodingStrategy = .custom { decoder in + let container = try decoder.singleValueContainer() + let stringData = try container.decode(String.self) + let formatter = ISO8601DateFormatter() + + if let date = formatter.date(from: stringData) { + return date + } + + formatter.formatOptions = .withFractionalSeconds + return formatter.date(from: stringData)! + } + + return decoder + } +} + +public extension JSONEncoder { + static var github: JSONEncoder { + let encoder = JSONEncoder() + encoder.dateEncodingStrategy = .iso8601 + return encoder + } +} diff --git a/Sources/HttpClient/Decoder.swift b/Sources/HttpClient/Decoder.swift new file mode 100644 index 0000000..ec0cbe5 --- /dev/null +++ b/Sources/HttpClient/Decoder.swift @@ -0,0 +1,24 @@ +// +// Decoder.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public extension GitHub { + func decode(_ type: T.Type, from data: Data) throws -> T { + let decoder = JSONDecoder.github + do { + return try decoder.decode(type.self, from: data) + } catch let decodeError { + let errorResponse = try JSONDecoder.github.decode(ErrorResponse.self, from: data) + if let error = errorResponse.error { + throw error + } else { + throw UnknownError(decodeError: decodeError, data: data) + } + } + } +} diff --git a/Sources/HttpClient/ErrorResponse.swift b/Sources/HttpClient/ErrorResponse.swift new file mode 100644 index 0000000..17f5206 --- /dev/null +++ b/Sources/HttpClient/ErrorResponse.swift @@ -0,0 +1,28 @@ +// +// ErrorResponse.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct ErrorResponse: Codable, Sendable { + public let message: String + public let documentationURL: URL + + private enum CodingKeys: String, CodingKey { + case message + case documentationURL = "documentation_url" + } + + public var error: RequestError? { + switch message { + case "Not Found": return .notFound + case "Bad credentials": return .notAuthorized + case "Requires authentication": return .requireAuthorization + case "This endpoint requires you to be authenticated.": return .requireAuthorization + default: return nil + } + } +} diff --git a/Sources/HttpClient/GitHub.swift b/Sources/HttpClient/GitHub.swift new file mode 100644 index 0000000..0b9d707 --- /dev/null +++ b/Sources/HttpClient/GitHub.swift @@ -0,0 +1,40 @@ +// +// GitHubAPI.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct GitHub: Sendable { + public var baseURL = URL(string: "https://api.github.com")! + public var authorizationType: AuthorizationType + public var session: URLSession + + public init( + type authorizationType: AuthorizationType, + session: URLSession = .shared + ) { + self.authorizationType = authorizationType + self.session = session + } + + public init( + accessToken: String, + session: URLSession = .shared + ) { + self.authorizationType = .bearerToken(accessToken: accessToken) + self.session = session + } + + public var headers: [String: String] { + var headers: [String: String] = [ + "Accept": "application/vnd.github+json" + ] + if case .bearerToken(accessToken: let token) = authorizationType { + headers["Authorization"] = "Bearer \(token)" + } + return headers + } +} diff --git a/Sources/HttpClient/GitHubErrorError.swift b/Sources/HttpClient/GitHubErrorError.swift new file mode 100644 index 0000000..5476162 --- /dev/null +++ b/Sources/HttpClient/GitHubErrorError.swift @@ -0,0 +1,14 @@ +// +// GitHubError.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HTTPTypes + +public enum GitHubError: Error, Sendable { + case request(request: HTTPRequest) + case decode(data: Data, response: HTTPResponse) +} diff --git a/Sources/HttpClient/HTTPRequest++.swift b/Sources/HttpClient/HTTPRequest++.swift new file mode 100644 index 0000000..348beac --- /dev/null +++ b/Sources/HttpClient/HTTPRequest++.swift @@ -0,0 +1,28 @@ +// +// HTTPRequest++.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation +import HTTPTypes +import HTTPTypesFoundation + +public extension HTTPRequest { + init( + method: HTTPRequest.Method, + url: URL, + queries: [String: String], + headers: [String: String] + ) { + var components = URLComponents(url: url, resolvingAgainstBaseURL: true)! + components.queryItems = queries.map { .init(name: $0.key, value: $0.value) } + + let headerFields: HTTPFields = headers.reduce(into: HTTPFields()) { fields, dic in + fields[HTTPField.Name(dic.key)!] = dic.value + } + + self.init(method: method, url: components.url!, headerFields: headerFields) + } +} diff --git a/Sources/HttpClient/OrderType.swift b/Sources/HttpClient/OrderType.swift new file mode 100644 index 0000000..3c3e8eb --- /dev/null +++ b/Sources/HttpClient/OrderType.swift @@ -0,0 +1,13 @@ +// +// OrderType.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum OrderType: String, Sendable { + case desc + case asc +} diff --git a/Sources/HttpClient/Protocols/HttpClientProtocol.swift b/Sources/HttpClient/Protocols/HttpClientProtocol.swift deleted file mode 100644 index 6466f71..0000000 --- a/Sources/HttpClient/Protocols/HttpClientProtocol.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// HttpClientProtocol.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -public protocol HttpClient { - func options() {} - func `get`() {} - func del() {} - func post(){} - func patch(){} - func put(){} - func request(){} - func requestRaw(){} - func requestRawWithCallback() {} -} diff --git a/Sources/HttpClient/Protocols/HttpClientResponse.swift b/Sources/HttpClient/Protocols/HttpClientResponse.swift deleted file mode 100644 index 53b01a2..0000000 --- a/Sources/HttpClient/Protocols/HttpClientResponse.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// HttpClientResponse.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -class HttpClientResponse { - var message: IncomeMessage - - init(message: IncomeMessage) { - self.message = message - } - - public func readBody() -> String async { - return "" - } -} diff --git a/Sources/HttpClient/Protocols/RequestHandler.swift b/Sources/HttpClient/Protocols/RequestHandler.swift deleted file mode 100644 index d0ecf37..0000000 --- a/Sources/HttpClient/Protocols/RequestHandler.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// RequestHandler.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -public protocol RequestHandler { - public prepareRequest(_ options: String) {} - public canHandleAuthentication(_ response: HttpClientResponse) -> Bool {} - public handleAuthentication(httpClient: HttpClient, requestInfo: RequestInfo, data: String) -> HttpClientResponse {} -} diff --git a/Sources/HttpClient/Protocols/RequestInfo.swift b/Sources/HttpClient/Protocols/RequestInfo.swift deleted file mode 100644 index d9d7738..0000000 --- a/Sources/HttpClient/Protocols/RequestInfo.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// RequestInfo.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -public protocol RequestInfo { - var options: RequestOptions - var parsedUrl: URL - var httpModule: HttpModule -} - - -enum HttpModule { - case http - case https -} diff --git a/Sources/HttpClient/Protocols/RequestOptions.swift b/Sources/HttpClient/Protocols/RequestOptions.swift deleted file mode 100644 index 9520823..0000000 --- a/Sources/HttpClient/Protocols/RequestOptions.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// RequestOptions.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -public protocol RequestOptions { -// var headers?: OutgoingHttpHeaders - var socketTimeout: Int? - var ignoreSslError: Bool? - var allowRedirects: Bool? - var allowRedirectDowngrade: Bool? - var maxRedirects: Int? - var maxSockets: Int? - var keepAlive: Bool? - var deserializeDates: Bool? - // Allows retries only on Read operations (since writes may not be idempotent) - var allowRetries: Bool? - var maxRetries: Int? -} diff --git a/Sources/HttpClient/Protocols/TypedResponse.swift b/Sources/HttpClient/Protocols/TypedResponse.swift deleted file mode 100644 index 531c172..0000000 --- a/Sources/HttpClient/Protocols/TypedResponse.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// TypedResponse.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -public protocol TypedResponse: T { - var statusCode: Int - var result: T -// var headers: IncomingHttpHeaders -} diff --git a/Sources/HttpClient/RequestError.swift b/Sources/HttpClient/RequestError.swift new file mode 100644 index 0000000..d999845 --- /dev/null +++ b/Sources/HttpClient/RequestError.swift @@ -0,0 +1,16 @@ +// +// RequestError.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public enum RequestError: Error, Sendable { + case deleteRepository(data: Data) + case deleteProtectionTag(data: Data) + case notFound + case notAuthorized + case requireAuthorization +} diff --git a/Sources/HttpClient/UnknownError.swift b/Sources/HttpClient/UnknownError.swift new file mode 100644 index 0000000..ae41c88 --- /dev/null +++ b/Sources/HttpClient/UnknownError.swift @@ -0,0 +1,13 @@ +// +// UnknownError.swift +// +// +// Created by Asiel Cabrera Gonzalez on 9/14/23. +// + +import Foundation + +public struct UnknownError: Error, Sendable { + public let decodeError: any Error + public let data: Data +} diff --git a/Sources/HttpClient/Utils/Headers.swift b/Sources/HttpClient/Utils/Headers.swift deleted file mode 100644 index a5a9f7c..0000000 --- a/Sources/HttpClient/Utils/Headers.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Headers.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -enum Headers: String { - case Accept = "accept", ContentType = "content-type" -} diff --git a/Sources/HttpClient/Utils/HttpClientError.swift b/Sources/HttpClient/Utils/HttpClientError.swift deleted file mode 100644 index d6f5cf6..0000000 --- a/Sources/HttpClient/Utils/HttpClientError.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// HttpClientError.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - - -enum HttpClientError: Error { - var statusCode: Int - var result?: any -} diff --git a/Sources/HttpClient/Utils/HttpCodes.swift b/Sources/HttpClient/Utils/HttpCodes.swift deleted file mode 100644 index acfa38e..0000000 --- a/Sources/HttpClient/Utils/HttpCodes.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// HttpCodes.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -enum HttpCodes { - case OK = 200, - MultipleChoices = 300, - MovedPermanently = 301, - ResourceMoved = 302, - SeeOther = 303, - NotModified = 304, - UseProxy = 305, - SwitchProxy = 306, - TemporaryRedirect = 307, - PermanentRedirect = 308, - BadRequest = 400, - Unauthorized = 401, - PaymentRequired = 402, - Forbidden = 403, - NotFound = 404, - MethodNotAllowed = 405, - NotAcceptable = 406, - ProxyAuthenticationRequired = 407, - RequestTimeout = 408, - Conflict = 409, - Gone = 410, - TooManyRequests = 429, - InternalServerError = 500, - NotImplemented = 501, - BadGateway = 502, - ServiceUnavailable = 503, - GatewayTimeout = 504 -} diff --git a/Sources/HttpClient/Utils/MediaTypes.swift b/Sources/HttpClient/Utils/MediaTypes.swift deleted file mode 100644 index d6d9e5a..0000000 --- a/Sources/HttpClient/Utils/MediaTypes.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// MediaTypes.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -enum MediaTypes: String { - case ApplicationJson = "application/json" -} diff --git a/Sources/HttpClient/Utils/Utils.swift b/Sources/HttpClient/Utils/Utils.swift deleted file mode 100644 index 5c3d074..0000000 --- a/Sources/HttpClient/Utils/Utils.swift +++ /dev/null @@ -1,28 +0,0 @@ -// -// File.swift -// -// -// Created by Asiel Cabrera Gonzalez on 5/16/23. -// - -import Foundation - -struct Utils { - let HttpRedirectCodes: [Int] = [ - HttpCodes.MovedPermanently, - HttpCodes.ResourceMoved, - HttpCodes.SeeOther, - HttpCodes.TemporaryRedirect, - HttpCodes.PermanentRedirect - ] - - let HttpResponseRetryCodes: [Int] = [ - HttpCodes.BadGateway, - HttpCodes.ServiceUnavailable, - HttpCodes.GatewayTimeout - ] - - let RetryableHttpVerbs: [String] = ["OPTIONS", "GET", "DELETE", "HEAD"] - let ExponentialBackoffCeiling = 10 - let ExponentialBackoffTimeSlice = 5 -}