diff --git a/.analysis_options b/.analysis_options deleted file mode 100644 index 117985e8..00000000 --- a/.analysis_options +++ /dev/null @@ -1,49 +0,0 @@ -analyzer: - strong-mode: true - -linter: - rules: - - camel_case_types - - empty_constructor_bodies - - always_declare_return_types -# - avoid_as - - camel_case_types - - constant_identifier_names - - empty_constructor_bodies - - implementation_imports - - library_names - - library_prefixes - - non_constant_identifier_names - - one_member_abstracts - - package_api_docs - - package_prefixed_library_names - - prefer_is_not_empty - - slash_for_doc_comments - - super_goes_last - - type_annotate_public_apis - - type_init_formals -# - unnecessary_brace_in_string_interp - - unnecessary_getters_setters - - avoid_empty_else - - package_names - - unrelated_type_equality_checks - - throw_in_finally - - close_sinks - - comment_references - - control_flow_in_finally - - empty_statements - - hash_and_equals - - iterable_contains_unrelated_type - - list_remove_unrelated_type - - test_types_in_equals - - throw_in_finally - - valid_regexps - - annotate_overrides - - avoid_init_to_null - - avoid_return_types_on_setters - - await_only_futures - - empty_catches - - prefer_is_not_empty - - sort_constructors_first - - sort_unnamed_constructors_first - - unawaited_futures diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 00000000..ad6ef734 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,13 @@ +# Dependabot configuration file. +version: 2 + +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + + - package-ecosystem: "pub" + directory: "/" + schedule: + interval: "monthly" diff --git a/.github/workflows/create_release.yml b/.github/workflows/create_release.yml new file mode 100644 index 00000000..2c071305 --- /dev/null +++ b/.github/workflows/create_release.yml @@ -0,0 +1,16 @@ +name: Create Release +on: + push: + tags: + - '*' +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Create a Release + uses: elgohr/Github-Release-Action@v5 + env: + GITHUB_TOKEN: "${{ secrets.RELEASE_TOKEN }}" + with: + title: ${{ github.ref }} diff --git a/.github/workflows/create_tag.yml b/.github/workflows/create_tag.yml new file mode 100644 index 00000000..c5793f0c --- /dev/null +++ b/.github/workflows/create_tag.yml @@ -0,0 +1,25 @@ +name: Release + +# Runs when a PR merges. +# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#running-your-workflow-when-a-pull-request-merges +on: + pull_request: + types: + - closed + +jobs: + release: + if: github.event.pull_request.merged == true + runs-on: ubuntu-latest + container: dart + permissions: + contents: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: master + - uses: jacopocarlini/action-autotag@3.0.0 + with: + GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml new file mode 100644 index 00000000..63b8adab --- /dev/null +++ b/.github/workflows/dart.yml @@ -0,0 +1,33 @@ +name: Dart Checks + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + # Test with at least the declared minimum Dart version + sdk: ['3.5', stable] + steps: + - uses: actions/checkout@v4 + - uses: dart-lang/setup-dart@v1 + with: + sdk: ${{ matrix.sdk }} + + - name: Install dependencies + run: dart pub get + - name: Dart Analyzer + run: dart analyze + - name: Check Dart Format + if: ${{ matrix.sdk == 'stable' }} + run: dart format --set-exit-if-changed -onone . + - name: Unit tests + run: dart test + - name: Check if Publishable + run: dart pub publish --dry-run diff --git a/.github/workflows/publish_demos.yml b/.github/workflows/publish_demos.yml new file mode 100644 index 00000000..b6b6169e --- /dev/null +++ b/.github/workflows/publish_demos.yml @@ -0,0 +1,30 @@ +name: Publish Demos +on: + push: + branches: + - master +jobs: + build-and-deploy: + runs-on: ubuntu-latest + container: + image: dart + steps: + - name: Checkout 🛎️ + uses: actions/checkout@v4 + + - name: Install rsync 📚 + run: | + apt-get update && apt-get install -y rsync + + - name: Install and Build 🔧 + run: | + dart pub global activate webdev + dart pub get + dart pub global run webdev build -o build -- --delete-conflicting-outputs + rm build/example/packages + + - name: Publish 🚀 + uses: JamesIves/github-pages-deploy-action@v4.6.1 + with: + branch: gh-pages # The branch the action should deploy to. + folder: build/example # The folder the action should deploy. diff --git a/.github/workflows/publish_pubdev.yml b/.github/workflows/publish_pubdev.yml new file mode 100644 index 00000000..336e0cf5 --- /dev/null +++ b/.github/workflows/publish_pubdev.yml @@ -0,0 +1,14 @@ +# .github/workflows/publish.yml +name: Publish to pub.dev + +on: + push: + tags: + - '[0-9]+.[0-9]+.[0-9]+*' + +# Publish using the reusable workflow from dart-lang. +jobs: + publish: + permissions: + id-token: write # Required for authentication using OIDC + uses: dart-lang/setup-dart/.github/workflows/publish.yml@v1 \ No newline at end of file diff --git a/.github/workflows/triage.yml b/.github/workflows/triage.yml new file mode 100644 index 00000000..4d51d30c --- /dev/null +++ b/.github/workflows/triage.yml @@ -0,0 +1,33 @@ +name: Triage Issues +on: + issues: + types: [opened] + +jobs: + assignRob: + name: Assign Rob + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Apply untriaged label + uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['untriaged','unreleased'] + }) + - name: Comment On New Issues + uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '👋 Thanks for reporting! @robrbecker will take a look.' + }) diff --git a/.gitignore b/.gitignore index 890a906e..43dbe764 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,5 @@ -build/ +.dart_tool +.packages .pub packages pubspec.lock -.packages -.idea/ -.settings -.buildlog -out/ -*.iml -.c9* diff --git a/.pubignore b/.pubignore new file mode 100644 index 00000000..09134047 --- /dev/null +++ b/.pubignore @@ -0,0 +1,3 @@ +tool +test +integration_test \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6edc34c6..8c92ab04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,472 @@ +## 9.25.0 + +* Require Dart 3.5 +* Require `package:http` `^1.0.0`. +* Fix pagination logic to use `next` link. + +## 9.24.0 + +* Bug fixes to the `Issue.isOpen` and `Issue.isClosed` getters. + +## 9.23.0 + +* Require Dart 3.0. +* Update to the latest `package:lints`. + +## 9.22.0 + +* Add support for the `Ghost` user when the Github user is deleted. + +## 9.21.0 + +* Update MiscService.getApiStatus() to use the v2 API + * `APIStatus` has been refactored to match, now exposing `page` and `status` + +## 9.20.0 + +* Add a Changes object to the PullRequestEvent object so we can see what changed in edited PR events by @ricardoamador in https://github.com/SpinlockLabs/github.dart/pull/390 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.19.0...9.20.0 + +## 9.19.0 + +* Revert "Add the 'PushEvent' webhook and associated PushCommit object" by @robrbecker in https://github.com/SpinlockLabs/github.dart/pull/387 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.18.0...9.19.0 + +## 9.18.0 + +- Bad Release. Was: Add the 'PushEvent' webhook and associated PushCommit + +## 9.17.0 + +* Add bearerToken constructor to Authentication class by @kevmoo in https://github.com/SpinlockLabs/github.dart/pull/381 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.16.0...9.17.0 + +## 9.16.0 + +* Fix links and spelling nits in markdown files by @kevmoo in https://github.com/SpinlockLabs/github.dart/pull/379 +* Support latest pkg:http by @kevmoo in https://github.com/SpinlockLabs/github.dart/pull/380 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.15.1...9.16.0 + +## 9.15.1 + +* Revert immutable auth by @CaseyHillers in https://github.com/SpinlockLabs/github.dart/pull/378 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.15.0...9.15.1 + +## 9.15.0 + +* Implement IssuesService.lock/unlock by @Hixie in https://github.com/SpinlockLabs/github.dart/pull/376 +* Bump JamesIves/github-pages-deploy-action from 4.4.1 to 4.4.2 by @dependabot in https://github.com/SpinlockLabs/github.dart/pull/371 +* Make GitHub.auth non-nullable by @CaseyHillers in https://github.com/SpinlockLabs/github.dart/pull/377 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.14.0...9.15.0 + +## 9.14.0 + +* Add optional filter params on Repositories.listCommits by @CaseyHillers in https://github.com/SpinlockLabs/github.dart/pull/368 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.13.0...9.14.0 + +## 9.13.0 + +* Add node_id to the pull request model by @ricardoamador in https://github.com/SpinlockLabs/github.dart/pull/367 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.12.0...9.13.0 + +## 9.12.0 + +* Add support for issue and PR timeline events via `Issue.listTimeline`. + +## 9.11.0 + +* expose IssueLabel.description; update labels REST APIs by @devoncarew in https://github.com/SpinlockLabs/github.dart/pull/355 + +## 9.10.1 + +* Pass required User-Agent HTTP header on all requests + * If `Authentication.basic` is used, it will be your GitHub username/application + * Otherwise, it will default to `github.dart` + +## 9.10.0-dev + +* Require Dart 2.18 +* Expose `CheckSuitesService` and `ChuckRunsService` classes. + +## 9.9.0 + +* Add "author_association" field to the IssueComment object by @ricardoamador in https://github.com/SpinlockLabs/github.dart/pull/348 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.8.0...9.9.0 + +## 9.8.0 + +* Add "head_branch" field to CheckSuite object by @nehalvpatel in https://github.com/SpinlockLabs/github.dart/pull/347 + +## New Contributors +* @nehalvpatel made their first contribution in https://github.com/SpinlockLabs/github.dart/pull/347 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.7.0...9.8.0 + +## 9.7.0 +* Add calendar versioning by @CaseyHillers in https://github.com/SpinlockLabs/github.dart/pull/338 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.6.0...9.7.0 +## 9.6.0 + +* Require Dart 2.17 +* Update to allow different merge methods in pulls_service by @ricardoamador in https://github.com/SpinlockLabs/github.dart/pull/333 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.5.1...9.6.0 + +## 9.5.1 + +* Fix up unit tests & run them in CI by @robrbecker in https://github.com/SpinlockLabs/github.dart/pull/336 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.5.0...9.5.1 + +## 9.5.0 + +* Add 'commits' member to GitHubComparison object by @fuzzybinary in https://github.com/SpinlockLabs/github.dart/pull/330 + +## New Contributors +* @fuzzybinary made their first contribution in https://github.com/SpinlockLabs/github.dart/pull/330 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.4.1...9.5.0 + +## 9.4.1 + +* Update to github-script 6 by @robbecker-wf in https://github.com/SpinlockLabs/github.dart/pull/331 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.4.0...9.4.1 + +## 9.4.0 + +* Fix publish release workflow by @CaseyHillers in https://github.com/SpinlockLabs/github.dart/pull/316 +* Add support for toString to the Checkrun object. by @ricardoamador in https://github.com/SpinlockLabs/github.dart/pull/318 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.3.0...9.4.0 + +## 9.3.0 + +* Added a new conclusion state to support flutter autosubmit bot by @ricardoamador in https://github.com/SpinlockLabs/github.dart/pull/315 + +## New Contributors +* @ricardoamador made their first contribution in https://github.com/SpinlockLabs/github.dart/pull/315 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.2.0...9.3.0 + +## 9.2.0 + +* test auto-release by @robrbecker in https://github.com/SpinlockLabs/github.dart/pull/307 +* test PR for auto-release by @robrbecker in https://github.com/SpinlockLabs/github.dart/pull/308 +* Added assignees to Issue model for #289 by @sjhorn in https://github.com/SpinlockLabs/github.dart/pull/290 + +## New Contributors +* @sjhorn made their first contribution in https://github.com/SpinlockLabs/github.dart/pull/290 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.1.1...9.2.0 + +## 9.1.1 + +* Don't add state query param twice by @passsy in https://github.com/SpinlockLabs/github.dart/pull/264 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.1.0...9.1.1 + +## 9.1.0 + +* add 'create' github webhook event to hooks.dart by @XilaiZhang in https://github.com/SpinlockLabs/github.dart/pull/304 + +## New Contributors +* @XilaiZhang made their first contribution in https://github.com/SpinlockLabs/github.dart/pull/304 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.0.3...9.1.0 + +## 9.0.3 + +* Update Language Colors March 13th 2022 by @robrbecker in https://github.com/SpinlockLabs/github.dart/pull/302 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/9.0.2...9.0.3 + +## 9.0.2 +- Switched to use the lints package instead of pedantic https://github.com/SpinlockLabs/github.dart/pull/301 + +## 9.0.1 +- Add `conclusion` property in class `CheckRun` + +## 9.0.0 + +**Breaking change:** In the Gist class, the old type of files was +```dart +List? files; +``` +and the new type is +```dart +Map? files; +``` + +**Breaking change:** In the GistFile class, the name property is now filename + +* Fix getting gists by @robrbecker in https://github.com/SpinlockLabs/github.dart/pull/294 + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/8.5.0...9.0.0 + +## 8.5.0 + +* Adds listing and creating PR Reviews, listing users in an org by @robrbecker in https://github.com/SpinlockLabs/github.dart/pull/287 + + +**Full Changelog**: https://github.com/SpinlockLabs/github.dart/compare/8.4.0...8.5.0 + +## 8.4.0 +- added `updateComment` to update issue comments https://github.com/SpinlockLabs/github.dart/pull/286 + +## 8.3.0 +- Support `files` field in class `GitHubComparison` + +## 8.2.5 +- no library code changes +- Add auto pub publish on new releases + +## 8.2.4 +- Make CheckRunConclusion nullable + +## 8.2.3 +- Added `generateReleaseNotes` boolean to CreateRelase class to have github auto-create release notes +- Added `generateReleaseNotes` method to RepositoriesService to have github create release notes + between to tags (without creating a release) and return the name and body. This is helpful when you want to add the release notes to a CHANGELOG.md before making the actual release +## 8.2.2 +- Up minimum json_serializable to ^6.0.0, json_annotation to ^4.3.0 +- Cleanup and regenerate generated files +- Require Dart SDK 2.14 + +## 8.2.1 +- Add `CheckSuiteEvent` and `CheckRunEvent` + +## 8.2.0 + - add more fields to the PullRequest class and fixed JSON naming bugs + - Added: + - requestedReviewers + - reviewCommentCount + - milestone + - rebaseable + - mergeableState + - maintainerCanModify + - authorAssociation + - Fixed (these were previously always null) + - commentsCount + - commitsCount + - additionsCount + - deletionsCount + - changedFilesCount + +## 8.1.3 + - Add per page parameter to stars related activities https://github.com/SpinlockLabs/github.dart/pull/265 + +## 8.1.2 + - Fixes `RateLimit.fromRateLimitResponse` to not double cast int + +## 8.1.1 + - Fix up examples and license file https://github.com/SpinlockLabs/github.dart/pull/255 https://github.com/SpinlockLabs/github.dart/pull/254 https://github.com/SpinlockLabs/github.dart/pull/253 + +## 8.1.0 + - `RateLimit` queries `/rate_limit` and no longer uses quota + +## 8.0.1 + - Minor tweaks to improve pub score + +## 8.0.0 + - Allow start page, per_page, number of pages options to pagination helper + - Allow page options for listTags + +## 8.0.0-nullsafe.1 + - Update to null safety + +## 7.0.4 + - Add hasPages attribute to Repository https://github.com/SpinlockLabs/github.dart/pull/238 + +## 7.0.3 + - Export `languageColors` as part of the library. This is the map of github languages to their colors https://github.com/SpinlockLabs/github.dart/pull/232 + +## 7.0.2 + - https://github.com/SpinlockLabs/github.dart/pull/231 + +## 7.0.1 + - Add `getLatestRelease()` to RepositoriesService + - Add `listCurrentUserFollowing()` function to `UsersService` + +## 7.0.0 + - Removed deprecated CloneUrls property on Repository class + +## 6.2.3 + - Add twitter username to User class https://github.com/SpinlockLabs/github.dart/pull/228 + - Improve pub.dev score + +## 6.2.2 + - Fixed typo in documentation + +## 6.2.1 + - Consolidated utils from src/util.dart into src/common/utils/utils.dart + - Added a new top level entry point `hooks.dart` to improve dartdocs and IDE usability when writing hooks + +## 6.2.0 + - Added Checks API https://github.com/SpinlockLabs/github.dart/pull/182 + - Bug fix: Fix setRepositorySubscription to be a PUT instead of a POST https://github.com/SpinlockLabs/github.dart/commit/5b5d7656ce9ce1cb06e15651da06e7e192bc19e1 + - Bug fix: Repository clone URLs were null. DEPRECATED `Repository.cloneUrls` use `cloneUrl`,`gitUrl`,`sshUrl`, or `svnUrl` instead. + - Bug fix: Use a shared json encoder util to remove nulls from maps and lists, encode all dates for github. https://github.com/SpinlockLabs/github.dart/pull/182 + +## 6.1.3 + - Add missing fields for Notification https://github.com/SpinlockLabs/github.dart/pull/210 + - Can now create draft PRs https://github.com/SpinlockLabs/github.dart/pull/212 + +## 6.1.2 + - Update default language color to match github https://github.com/SpinlockLabs/github.dart/pull/208 + +## 6.1.1 + - Use pedantic and address some lint https://github.com/SpinlockLabs/github.dart/pull/205 + - Add missing download url for repos contents https://github.com/SpinlockLabs/github.dart/pull/206 + +## 6.1.0 + - Add (experimental) `listReactions` method to `IssueService`. + +## 6.0.6 + - Clean up lints https://github.com/SpinlockLabs/github.dart/pull/202 + +## 6.0.5 + - Fix null errors issue https://github.com/SpinlockLabs/github.dart/issues/199 + +## 6.0.4 + - This fixes #196 (https://github.com/SpinlockLabs/github.dart/issues/196) + +## 6.0.3 + - Add archived and disabled fields to the Repository class + +## 6.0.2 + - Fixed `GitHubFile.text` to properly decode `content`. + +## 6.0.1 + - Fix https://github.com/SpinlockLabs/github.dart/issues/190 + +## 6.0.0 + +- There's a single entrypoint now: `package:github/github.dart` +- For web: browser specific helper methods have moved. use import `package:github/browser_helper.dart` (renderMarkdown, and createAvatorImage) +- `createGithubClient(...)` has been removed. Just create a GitHub object directly now. +- `findAuthenticationFromEnvironment` now works in both server/flutter and web environments + - On the web, it will check the query string first, then session storage +- all static methods are now factory constructors +- fromJSON is now fromJson everywhere +- toJSON is now toJson everywhere +- Use JsonSerializable everywhere +- removed deprecated items +- renamed some fields with ID at the end to be Id +- most model constructors now have named parameters for all properties +- `GitHubFile.content` is now exactly the content returned from the JSON API + without newlines removed. + +## v5.5.0 + +- Implement markThreadRead https://github.com/SpinlockLabs/github.dart/pull/185 +- Fix for activity service https://github.com/SpinlockLabs/github.dart/issues/187 + +## v5.4.0 + +- Implement rate-limiting https://github.com/SpinlockLabs/github.dart/pull/172 +- Back off when server fails (HTTP 50x) https://github.com/SpinlockLabs/github.dart/pull/173 +- All remaining methods in repos_service.dart (accessible via the getter `repositories` from `GitHub` client class) have been implemented. `isCollaborator`, `listSingleCommitComments`, `listCommitComments`, `createCommitComment`, `getComment`, `updateComment`, `deleteComment` +- Fixed issues.get to correctly return Future https://github.com/SpinlockLabs/github.dart/pull/180 + +## v5.3.0 + +- Add the ability to upload release assets. +- Add the ability to get an existing release by tag name. + +Deprecations: + +- The `draft` and `prerelease` properties in the CreateRelease and Release +- classes have been renamed to `isDraft` and `isPrerelease` for clarity. +- Release.targetCommitsh has been renamed to Release.targetCommitish. +- The `release` parameter in RepositoriesService.createRelease +has been renamed to `createRelease`. +- `RepositoriesService.getRelease` has been renamed to `RepositoriesService.getReleaseById` + +## v5.2.0 + + - Add access to labels on Pull Requests https://github.com/SpinlockLabs/github.dart/pull/163 + - Adding draft property to PR model https://github.com/SpinlockLabs/github.dart/pull/162 + - updateFile request must be a PUT https://github.com/SpinlockLabs/github.dart/pull/160 + +## v5.1.0 + + - `Repository`: added `updatedAt` and `license` fields. + - Require at least Dart `2.3.0`. + - Bump version constraint on `json_annotation` + - Add contents_url to PullRequestFile https://github.com/SpinlockLabs/github.dart/pull/159 + +## v5.0.2 + - Fixed pollPublicEventsReceivedByUser to use the correct API URL https://github.com/SpinlockLabs/github.dart/pull/150 + +## v5.0.1 + - Fixed a runtime exception (https://github.com/SpinlockLabs/github.dart/issues/139) + - Added an optional `base` argument when editing a PR (https://github.com/SpinlockLabs/github.dart/pull/145) + +## v5.0.0 + +- **BREAKING** `RepositoriesService.listCollaborators` now returns + `Stream` instead of `Stream`. + - `Collaborator` is a new type that includes collaborator-specific + information. + +## v4.1.1 + +- Require at least Dart `2.1.0`. + +## v4.1.0 + +- Fix return type of `RepositoriesService.listContributors`. +- Fix return type of `RepositoriesService.createRelease`. +- Fixed `RepositoriesService.listContributorStats`. + - Removed unsupported `limit` parameter. + - Removed flaky retry logic. Instead, `NotReady` is thrown, which can be used + to decide to retry at the call site. + - Made associated classes `ContributorStatistics` and + `ContributorWeekStatistics` immutable. Since these classes are only meant as + return values, we're not treating this as a breaking change. +- Added `Stream github.search.code(...)` search API + - Made `CodeSearchResults` class to hold search results + - Made `CodeSearchItem` class to hold each search result item + - Added a code search example + +## v4.0.1 + +- Fix cast errors in event and issue queries. + +## v4.0.0 + +- Make fields in many objects read-only. +- Initial support for comparing commits. +- Require at least Dart `2.0.0-dev.36`. +- Fix a number of type issues dealing with JSON. +- *BREAKING* Removed `ExploreService` – `GitHub.explore`. +- *BREAKING* Removed `MiscService.listOctodex`. +- *BREAKING* Removed `BlogService` - `GitHub.blog`. + ## v3.0.0 - *BREAKING* Removed a number of top-level methods from the public API. @@ -54,87 +523,87 @@ - Markdown Generation Library ## v1.3.0 -- [Button Tweaks](https://github.com/DirectMyFile/github.dart/commit/5f4b5caee79758a9a2ea9eeac1521836d95eb9bd) -- [Added Emoji Searches](https://github.com/DirectMyFile/github.dart/commit/8ca46c665f844794dca56aa4eeaab5e2c9d2c245) -- [Combined all Less stylesheets into one](https://github.com/DirectMyFile/github.dart/commit/dd786c4342d70533c2d5446b33888bb42fac40e8) -- [Dates Library Cleanup](https://github.com/DirectMyFile/github.dart/commit/0518a3b0ae072e481fc1579c91c5280ff1978821) -- [String to represent Unix timestamps](https://github.com/DirectMyFile/github.dart/commit/cf93c0fe6790a27c6bbf14f1c7d64f7b6eab5247) -- [Fix date/time parsing](https://github.com/DirectMyFile/github.dart/commit/a6e459ae16a40c2c1f12cace6d84a60dd97b3332) -- [Slack Notifications for TravisCI](https://github.com/DirectMyFile/github.dart/commit/de08f8718d5a90a369cf9edf0d0f90c22ccb1e2a) +- [Button Tweaks](https://github.com/SpinlockLabs/github.dart/commit/5f4b5caee79758a9a2ea9eeac1521836d95eb9bd) +- [Added Emoji Searches](https://github.com/SpinlockLabs/github.dart/commit/8ca46c665f844794dca56aa4eeaab5e2c9d2c245) +- [Combined all Less stylesheets into one](https://github.com/SpinlockLabs/github.dart/commit/dd786c4342d70533c2d5446b33888bb42fac40e8) +- [Dates Library Cleanup](https://github.com/SpinlockLabs/github.dart/commit/0518a3b0ae072e481fc1579c91c5280ff1978821) +- [String to represent Unix timestamps](https://github.com/SpinlockLabs/github.dart/commit/cf93c0fe6790a27c6bbf14f1c7d64f7b6eab5247) +- [Fix date/time parsing](https://github.com/SpinlockLabs/github.dart/commit/a6e459ae16a40c2c1f12cace6d84a60dd97b3332) +- [Slack Notifications for TravisCI](https://github.com/SpinlockLabs/github.dart/commit/de08f8718d5a90a369cf9edf0d0f90c22ccb1e2a) ## v1.0.1 -- [Octicons](https://github.com/DirectMyFile/github.dart/commit/28cff468272066b8f70998ac9235fc6c813a88d5) +- [Octicons](https://github.com/SpinlockLabs/github.dart/commit/28cff468272066b8f70998ac9235fc6c813a88d5) ## v1.0.0 -- [Support for Creating Milestones](https://github.com/DirectMyFile/github.dart/commit/2e613d9ef662da6e5d4adee576ac3c149d15e037) +- [Support for Creating Milestones](https://github.com/SpinlockLabs/github.dart/commit/2e613d9ef662da6e5d4adee576ac3c149d15e037) ## v0.6.7 -- [Hook Server now only handles request at `/hook`](https://github.com/DirectMyFile/github.dart/commit/da0524cd054082bb016193cf167865fd6aeb5631) -- [Octodex Support](https://github.com/DirectMyFile/github.dart/commit/4481f094dca7960268447c579f1745337bbd6c25) -- [Zen API Support](https://github.com/DirectMyFile/github.dart/commit/bcf2ed540a327957485b7e610647f956d02bfa21) -- [Ability to delete issue comments](https://github.com/DirectMyFile/github.dart/commit/2316f5c6af5246d3039fb378fab6c77ac61c5e6b) -- [Client Creation Helper](https://github.com/DirectMyFile/github.dart/commit/2316f5c6af5246d3039fb378fab6c77ac61c5e6b) -- [New Hook Server Middleware](https://github.com/DirectMyFile/github.dart/commit/3af13b647291bc31d644a9ca1554861892ac7b76) -- [Issue Label Management](https://github.com/DirectMyFile/github.dart/commit/8cfe4b318d8683dc6be59ab0c6d5968325a461d9) -- [Ability to change title, body, and state of an issue](https://github.com/DirectMyFile/github.dart/commit/dabc32a66678e92321d017912c9aae60084e908f) -- [Repository Status API Support](https://github.com/DirectMyFile/github.dart/commit/b17da3befae20bbde9b8d8bfd351bf8ff3227fa6) -- [Creating/Deleting/Listing Repository Labels](https://github.com/DirectMyFile/github.dart/commit/2eb1ea81aa3fdfe99c7ed39316a946897c67ebc0) -- [Issue Assignees](https://github.com/DirectMyFile/github.dart/commit/e5e92d2c1d16ab4912522392e84d1e16a2f353ab) +- [Hook Server now only handles request at `/hook`](https://github.com/SpinlockLabs/github.dart/commit/da0524cd054082bb016193cf167865fd6aeb5631) +- [Octodex Support](https://github.com/SpinlockLabs/github.dart/commit/4481f094dca7960268447c579f1745337bbd6c25) +- [Zen API Support](https://github.com/SpinlockLabs/github.dart/commit/bcf2ed540a327957485b7e610647f956d02bfa21) +- [Ability to delete issue comments](https://github.com/SpinlockLabs/github.dart/commit/2316f5c6af5246d3039fb378fab6c77ac61c5e6b) +- [Client Creation Helper](https://github.com/SpinlockLabs/github.dart/commit/2316f5c6af5246d3039fb378fab6c77ac61c5e6b) +- [New Hook Server Middleware](https://github.com/SpinlockLabs/github.dart/commit/3af13b647291bc31d644a9ca1554861892ac7b76) +- [Issue Label Management](https://github.com/SpinlockLabs/github.dart/commit/8cfe4b318d8683dc6be59ab0c6d5968325a461d9) +- [Ability to change title, body, and state of an issue](https://github.com/SpinlockLabs/github.dart/commit/dabc32a66678e92321d017912c9aae60084e908f) +- [Repository Status API Support](https://github.com/SpinlockLabs/github.dart/commit/b17da3befae20bbde9b8d8bfd351bf8ff3227fa6) +- [Creating/Deleting/Listing Repository Labels](https://github.com/SpinlockLabs/github.dart/commit/2eb1ea81aa3fdfe99c7ed39316a946897c67ebc0) +- [Issue Assignees](https://github.com/SpinlockLabs/github.dart/commit/e5e92d2c1d16ab4912522392e84d1e16a2f353ab) ## v0.6.6 -- [Fix Typos](https://github.com/DirectMyFile/github.dart/commit/7b3fd733a306230410a0318abbfc5c15cdd79345) +- [Fix Typos](https://github.com/SpinlockLabs/github.dart/commit/7b3fd733a306230410a0318abbfc5c15cdd79345) ## v0.6.5 -- [Add Issue State Information](https://github.com/DirectMyFile/github.dart/commit/571bb4101f2c90927ecaaab0bb226c277ad7b4be) +- [Add Issue State Information](https://github.com/SpinlockLabs/github.dart/commit/571bb4101f2c90927ecaaab0bb226c277ad7b4be) ## v0.6.4 -- [Pull Request State Information](https://github.com/DirectMyFile/github.dart/commit/fef13177f959903cd1b6b2a3c17f476bea59aeaf) -- [Widen Constraint on yaml](https://github.com/DirectMyFile/github.dart/commit/faa180922b3cd1a21a3b437eb8b590529d529e23) -- [Bug Fixes in Pull Requests](https://github.com/DirectMyFile/github.dart/commit/4b9ec19a2563d4c0bf4220703d11399dee96fbb3) +- [Pull Request State Information](https://github.com/SpinlockLabs/github.dart/commit/fef13177f959903cd1b6b2a3c17f476bea59aeaf) +- [Widen Constraint on yaml](https://github.com/SpinlockLabs/github.dart/commit/faa180922b3cd1a21a3b437eb8b590529d529e23) +- [Bug Fixes in Pull Requests](https://github.com/SpinlockLabs/github.dart/commit/4b9ec19a2563d4c0bf4220703d11399dee96fbb3) ## v0.6.3 -- [Pull Request Manipulation](https://github.com/DirectMyFile/github.dart/commit/37c5323a48a403c5a88300e960e38e773a000d81) -- [Access to Issue Comments](https://github.com/DirectMyFile/github.dart/commit/82020c242998624cac31e0e879c54f63d0cab012) -- [CreateStatus Request](https://github.com/DirectMyFile/github.dart/commit/202bacdd01a132e34d63ff96124f997e6e3c18d5) -- [Widen crypto constraint](https://github.com/DirectMyFile/github.dart/commit/caaa3f9ea14025d4d9c3a966a911489f2deedc26) -- [Team Management](https://github.com/DirectMyFile/github.dart/commit/2a47b14ba975c2396e728ec4260a30dfb8048178) -- [Fix Missing Dependency](https://github.com/DirectMyFile/github.dart/commit/233c4f38f33b1a5e3886e1f4617ca34a66159080) -- [Pull Request Comment Creation](https://github.com/DirectMyFile/github.dart/commit/cab4fa151426e0461ca1ef6ac570ed1e342fe3d8) -- [Fix Bugs in Commit Model](https://github.com/DirectMyFile/github.dart/commit/58a7616baaf4ce963e6e135c2547b9315f0b2e65) -- [Pagination Bug Fix](https://github.com/DirectMyFile/github.dart/commit/b68806939ef9b7d7e5c15983dec2bb6b86343afb) -- [Event Polling](https://github.com/DirectMyFile/github.dart/commit/71d16834b6bdcfd70f9f80ce3f81af9bcabfa066) -- [Octocat Wisdom Support](https://github.com/DirectMyFile/github.dart/commit/6273170787bb2b041c8320afabec304a9f2d6bab) -- [GitHub Blog Posts Support](https://github.com/DirectMyFile/github.dart/commit/845146f5b880ed3dd2b4c73c0a4d568da7b3e2b8) -- [Deploy Key Management](https://github.com/DirectMyFile/github.dart/commit/d72d97127fe96315ae9686daf964000a54ea8806) -- [Public Key Management](https://github.com/DirectMyFile/github.dart/commit/63a0d6b66ae7f5b595979ccdf759fea101607ff1) +- [Pull Request Manipulation](https://github.com/SpinlockLabs/github.dart/commit/37c5323a48a403c5a88300e960e38e773a000d81) +- [Access to Issue Comments](https://github.com/SpinlockLabs/github.dart/commit/82020c242998624cac31e0e879c54f63d0cab012) +- [CreateStatus Request](https://github.com/SpinlockLabs/github.dart/commit/202bacdd01a132e34d63ff96124f997e6e3c18d5) +- [Widen crypto constraint](https://github.com/SpinlockLabs/github.dart/commit/caaa3f9ea14025d4d9c3a966a911489f2deedc26) +- [Team Management](https://github.com/SpinlockLabs/github.dart/commit/2a47b14ba975c2396e728ec4260a30dfb8048178) +- [Fix Missing Dependency](https://github.com/SpinlockLabs/github.dart/commit/233c4f38f33b1a5e3886e1f4617ca34a66159080) +- [Pull Request Comment Creation](https://github.com/SpinlockLabs/github.dart/commit/cab4fa151426e0461ca1ef6ac570ed1e342fe3d8) +- [Fix Bugs in Commit Model](https://github.com/SpinlockLabs/github.dart/commit/58a7616baaf4ce963e6e135c2547b9315f0b2e65) +- [Pagination Bug Fix](https://github.com/SpinlockLabs/github.dart/commit/b68806939ef9b7d7e5c15983dec2bb6b86343afb) +- [Event Polling](https://github.com/SpinlockLabs/github.dart/commit/71d16834b6bdcfd70f9f80ce3f81af9bcabfa066) +- [Octocat Wisdom Support](https://github.com/SpinlockLabs/github.dart/commit/6273170787bb2b041c8320afabec304a9f2d6bab) +- [GitHub Blog Posts Support](https://github.com/SpinlockLabs/github.dart/commit/845146f5b880ed3dd2b4c73c0a4d568da7b3e2b8) +- [Deploy Key Management](https://github.com/SpinlockLabs/github.dart/commit/d72d97127fe96315ae9686daf964000a54ea8806) +- [Public Key Management](https://github.com/SpinlockLabs/github.dart/commit/63a0d6b66ae7f5b595979ccdf759fea101607ff1) ## v0.6.2 -- [Bug Fixes in Organizations](https://github.com/DirectMyFile/github.dart/commit/0cd55093fc3da97cfadc9ffd29e3705a1e25f3ec) -- [Pull Request Comment Model](https://github.com/DirectMyFile/github.dart/commit/611588e76163c17ee4830a9b9e0609ebf5beb165) -- [Moved to Stream-based API](https://github.com/DirectMyFile/github.dart/commit/bd827ffd30a162b4e71f8d12d466e6e24383bf1e) -- [Support for Forking a Repository](https://github.com/DirectMyFile/github.dart/commit/0c61d9a8ca874c23eb4f16dd63db1d53a65f2562) -- [Gist Comments Support](https://github.com/DirectMyFile/github.dart/commit/fc0d690debae4ac857f9021d7d8265ae2e4549be) -- [Merging Support](https://github.com/DirectMyFile/github.dart/commit/56d5e4d05bb3b685cac19c61f91f81f22281bd4a) -- [Emoji Support](https://github.com/DirectMyFile/github.dart/commit/9ac77b3364a060dd2e4e202e4e38f24b2079ff9e) -- [Repository Search Support](https://github.com/DirectMyFile/github.dart/commit/305d1bcb439b188fac9553c6a07ea33f0e3505bd) -- [Notifications API Support](https://github.com/DirectMyFile/github.dart/commit/11398495adebf68958ef3bce20903acd909f514c) +- [Bug Fixes in Organizations](https://github.com/SpinlockLabs/github.dart/commit/0cd55093fc3da97cfadc9ffd29e3705a1e25f3ec) +- [Pull Request Comment Model](https://github.com/SpinlockLabs/github.dart/commit/611588e76163c17ee4830a9b9e0609ebf5beb165) +- [Moved to Stream-based API](https://github.com/SpinlockLabs/github.dart/commit/bd827ffd30a162b4e71f8d12d466e6e24383bf1e) +- [Support for Forking a Repository](https://github.com/SpinlockLabs/github.dart/commit/0c61d9a8ca874c23eb4f16dd63db1d53a65f2562) +- [Gist Comments Support](https://github.com/SpinlockLabs/github.dart/commit/fc0d690debae4ac857f9021d7d8265ae2e4549be) +- [Merging Support](https://github.com/SpinlockLabs/github.dart/commit/56d5e4d05bb3b685cac19c61f91f81f22281bd4a) +- [Emoji Support](https://github.com/SpinlockLabs/github.dart/commit/9ac77b3364a060dd2e4e202e4e38f24b2079ff9e) +- [Repository Search Support](https://github.com/SpinlockLabs/github.dart/commit/305d1bcb439b188fac9553c6a07ea33f0e3505bd) +- [Notifications API Support](https://github.com/SpinlockLabs/github.dart/commit/11398495adebf68958ef3bce20903acd909f514c) ## v0.6.1 -- [Fix Bug in Release API](https://github.com/DirectMyFile/github.dart/commit/64499a376df313f08df1669782f042a912751794) +- [Fix Bug in Release API](https://github.com/SpinlockLabs/github.dart/commit/64499a376df313f08df1669782f042a912751794) ## v0.6.0 -- [Custom HTTP System](https://github.com/DirectMyFile/github.dart/commit/3e1bfe7e45e7b83c32bf0bceb154a791ea3b68d7) -- [Gists Support](https://github.com/DirectMyFile/github.dart/commit/fe733a36ed1cd7cce89d309e61b14b8b7f8666d8) -- [API Status Information](https://github.com/DirectMyFile/github.dart/commit/c790bf9edb8e2fb99d879818a8b2ae77b5325f7c) +- [Custom HTTP System](https://github.com/SpinlockLabs/github.dart/commit/3e1bfe7e45e7b83c32bf0bceb154a791ea3b68d7) +- [Gists Support](https://github.com/SpinlockLabs/github.dart/commit/fe733a36ed1cd7cce89d309e61b14b8b7f8666d8) +- [API Status Information](https://github.com/SpinlockLabs/github.dart/commit/c790bf9edb8e2fb99d879818a8b2ae77b5325f7c) ## v0.5.9 @@ -143,12 +612,12 @@ All the things! ## v0.3.0 - Updated Documentation -- [Better Organization Support](https://github.com/DirectMyFile/github.dart/commit/cc9de92f625918eafd01a72b4e2c0921580075bb) -- [Added Organization Demos](https://github.com/DirectMyFile/github.dart/commit/cc9de92f625918eafd01a72b4e2c0921580075bb) +- [Better Organization Support](https://github.com/SpinlockLabs/github.dart/commit/cc9de92f625918eafd01a72b4e2c0921580075bb) +- [Added Organization Demos](https://github.com/SpinlockLabs/github.dart/commit/cc9de92f625918eafd01a72b4e2c0921580075bb) ## v0.2.0 -- [Organization Support](https://github.com/DirectMyFile/github.dart/commit/3de085c0fa2d629a8bebff89bdaf1a5aaf833195) +- [Organization Support](https://github.com/SpinlockLabs/github.dart/commit/3de085c0fa2d629a8bebff89bdaf1a5aaf833195) ## v0.1.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a45a8024..b574e5a8 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -5,9 +5,9 @@ GitHub.dart is of course Open Source! We love it when people contribute! ## Getting Started - Make sure you have a [GitHub Account](https://github.com/signup/free). -- Make sure the [Dart SDK](https://www.dartlang.org/tools/sdk/) is installed on your system. +- Make sure the [Dart SDK](https://dart.dev/tools/sdk) is installed on your system. - Make sure you have [Git](http://git-scm.com/) installed on your system. -- [Fork](https://help.github.com/articles/fork-a-repo) the [repository](https://github.com/DirectMyFile/github.dart) on GitHub. +- [Fork](https://help.github.com/articles/fork-a-repo) the [repository](https://github.com/SpinlockLabs/github.dart) on GitHub. ## Making Changes @@ -15,12 +15,12 @@ GitHub.dart is of course Open Source! We love it when people contribute! - [Commit your code](http://git-scm.com/book/en/Git-Basics-Recording-Changes-to-the-Repository) for each logical change (see [tips for creating better commit messages](http://robots.thoughtbot.com/5-useful-tips-for-a-better-commit-message)). - [Push your change](https://help.github.com/articles/pushing-to-a-remote) to your fork. - [Create a Pull Request](https://help.github.com/articles/creating-a-pull-request) on GitHub for your change. -- Wait for reviewers (usually kaendfinger) to give feedback. +- Wait for reviewers (usually robrbecker) to give feedback. - When the reviewers think that the Pull Request is ready, they will merge it. ## Code Style -GitHub.dart follows the [Dart Style Guide](https://www.dartlang.org/articles/style-guide/). Please note that if your code is not formatted according to the guide as much as possible, we will reject your Pull Request until it is fixed. Some things such as long lines will generally be accepted, however try to make it smaller if possible. +GitHub.dart follows the [Dart Style Guide](https://dart.dev/effective-dart/style). Please note that if your code is not formatted according to the guide as much as possible, we will reject your Pull Request until it is fixed. Some things such as long lines will generally be accepted, however try to make it smaller if possible. ## Efficiency @@ -28,13 +28,38 @@ GitHub.dart is committed to efficiency as much as possible. If your code is not ## Rejections -Pull Request rejections are not a bad thing. It just means you need to fix something. Perhaps it is important to define 'rejection' as it is used in this case. A rejection is when a GitHub.dart committer comments on a Pull Request with a comment like 'rejected due to incorrect formatting'. +Pull Request rejections are not a bad thing. It just means you need to fix something. Perhaps it is important to define 'rejection' as it is used in this case. A rejection is when a `GitHub.dart` committer comments on a Pull Request with a comment like 'rejected due to incorrect formatting'. + +## Generated code + +To regenerate the JSON logic for the models, run: + +```sh +dart run build_runner build -d +``` + +## Tests + +`dart test` will only run the unit tests. + +To run the complete test suite you will need to install +`octokit/fixtures-server`. + +``` +npm install --global @octokit/fixtures-server +``` + +Tests can be run using `make test`, which will start up a local mock +GitHub and execute tests against it using your localhost port 3000. ## Contacting Us -- IRC: `#directcode on irc.esper.net and irc.freenode.net` -- Email: `kaendfinger@gmail.com` +File issues at https://github.com/SpinlockLabs/github.dart/issues + +## Releases -## Becoming a Committer +Merged pull requests that edit the `pubspec.yaml` version will create new releases. +Once CI is green, it will create a tag for that commit based on the version, which +gets published by pub.dev. -If you get on IRC and ask us, we can review your work and add you as a committer if we think you should have it. +If no new version was created, nothing will be published. diff --git a/LICENSE.md b/LICENSE similarity index 99% rename from LICENSE.md rename to LICENSE index 838d36d8..e4f06dbe 100644 --- a/LICENSE.md +++ b/LICENSE @@ -1,4 +1,3 @@ -``` The MIT License (MIT) Copyright (c) 2014 DirectCode @@ -20,4 +19,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -``` diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..c146167b --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +.DEFAULT_GOAL := help +SHELL=/bin/bash -o pipefail + +# Cite: https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +.PHONY: help +help: ## Display this help page + @grep -E '^[a-zA-Z0-9/_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}' + +.PHONY: fixtures +fixtures: ## Run octokit-fixtures-server for scenario tests + @npx octokit-fixtures-server & + +.PHONY: stop +stop: ## Stop the fixtures server + @killall node + +.PHONY: test +test: fixtures ## Run tests + @dart test -P all + make stop \ No newline at end of file diff --git a/README.md b/README.md index ddb1eaed..08dc8e09 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,38 @@ # GitHub for Dart -This is a Client Library for GitHub in Dart. I wrote this out of necessity, and then when I got a great reaction from the Dart community, I decided to put a lot of effort into it. +[![Dart Checks](https://github.com/SpinlockLabs/github.dart/actions/workflows/dart.yml/badge.svg)](https://github.com/SpinlockLabs/github.dart/actions/workflows/dart.yml) +[![Pub](https://img.shields.io/pub/v/github.svg)](https://pub.dev/packages/github) -Please submit issues and pull requests, join my IRC channel (#directcode on irc.esper.net), help out, or just give me encouragement. +This is a library for interacting with GitHub in Dart. It works on all platforms including web, server, and Flutter. +Please submit issues and pull requests, help out, or just give encouragement. -**Notice**: We are looking for major contributors. Contact us by email or on IRC! - -## Links - -- [Library Demos](http://github.directcode.org/demos/) -- [Pub Package](https://pub.dartlang.org/packages/github) -- [Wiki](https://github.com/DirectMyFile/github.dart/wiki) +**Notice**: This is not an official GitHub project. It is maintained by volunteers. +We are looking for contributors. If you're interested or have questions, head over to discussions https://github.com/SpinlockLabs/github.dart/discussions ## Features -### Current - -- Works on the Server and in the Browser +- Works on the Server, Browser, and Flutter - Really Fast -- Plugable API +- Pluggable API - Supports Authentication - Builtin OAuth2 Flow - Hook Server Helper -### Work in Progress - -- Support all the GitHub APIs (Progress: 98%) - -## Getting Started - -First, add the following to your pubspec.yaml: - -```yaml -dependencies: - github: "^3.0.0" -``` - -Then import the library and use it: - -**For the Server** -```dart -import 'package:github/server.dart'; - -void main() { - /* Creates a GitHub Client */ - var github = createGitHubClient(); - - github.repositories.getRepository(new RepositorySlug("DirectMyFile", "github.dart")).then((Repository repo) { - /* Do Something */ - }); -} -``` +## Links -**For the Browser** -```dart -import 'package:github/browser.dart'; +- [Library Demos](https://spinlocklabs.github.io/github.dart/) (based on the [sample code](https://github.com/SpinlockLabs/github.dart/tree/master/example)) +- [Pub Package](https://pub.dev/packages/github) +- [Wiki](https://github.com/SpinlockLabs/github.dart/wiki) +- [Latest API reference](https://pub.dev/documentation/github/latest/) -void main() { - /* Creates a GitHub Client */ - var github = createGitHubClient(); - - github.repositories.getRepository(new RepositorySlug("DirectMyFile", "github.dart")).then((Repository repo) { - /* Do Something */ - }); -} -``` +## Examples -## Authentication +See the examples in the example directory to learn how to use some of the features! -To use a GitHub token: +## Contacting Us -```dart -var github = createGitHubClient(auth: new Authentication.withToken("YourTokenHere")); -``` +Post a question or idea: https://github.com/SpinlockLabs/github.dart/discussions -## Contacting Us +## Star History -You can find us on `irc.esper.net and irc.freenode.net` at `#directcode`. +[![Star History Chart](https://api.star-history.com/svg?repos=SpinlockLabs/github.dart&type=Date)](https://star-history.com/#SpinlockLabs/github.dart&Date) diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 00000000..f2974469 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,58 @@ +include: package:lints/recommended.yaml + +analyzer: + language: + #strict-casts: true + +linter: + rules: + - always_put_control_body_on_new_line + - always_put_required_named_parameters_first + - avoid_catching_errors + - avoid_classes_with_only_static_members + - avoid_double_and_int_checks + - avoid_field_initializers_in_const_classes + - avoid_implementing_value_types + - avoid_js_rounded_ints + - avoid_private_typedef_functions + - avoid_returning_this + - avoid_setters_without_getters + - avoid_slow_async_io + - avoid_unused_constructor_parameters + - avoid_void_async + - cancel_subscriptions + - close_sinks + - comment_references + - diagnostic_describe_all_properties + - directives_ordering + - join_return_with_assignment + - literal_only_boolean_expressions + - no_adjacent_strings_in_list + - omit_local_variable_types + - one_member_abstracts + - only_throw_errors + - prefer_asserts_in_initializer_lists + - prefer_const_constructors + - prefer_const_constructors_in_immutables + - prefer_const_declarations + - prefer_const_literals_to_create_immutables + - prefer_constructors_over_static_methods + - prefer_final_in_for_each + - prefer_foreach + - prefer_if_elements_to_conditional_expressions + - prefer_int_literals + - prefer_mixin + - sort_child_properties_last + - sort_pub_dependencies + - sort_unnamed_constructors_first + - test_types_in_equals + - throw_in_finally + - type_annotate_public_apis + - unnecessary_await_in_return + - unnecessary_lambdas + - unnecessary_parenthesis + - unnecessary_statements + - use_full_hex_values_for_flutter_colors + - use_setters_to_change_properties + - use_string_buffers + - use_to_and_as_if_applicable diff --git a/benchmark/config.dart b/benchmark/config.dart deleted file mode 100644 index 94ac49d8..00000000 --- a/benchmark/config.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of github.benchmark; - -final RepositorySlug REPOSITORY_SLUG = - new RepositorySlug("DirectMyFile", "github.dart"); - -final String TOKEN = "5fdec2b77527eae85f188b7b2bfeeda170f26883"; diff --git a/benchmark/harness.dart b/benchmark/harness.dart deleted file mode 100644 index def29fb8..00000000 --- a/benchmark/harness.dart +++ /dev/null @@ -1,63 +0,0 @@ -part of github.benchmark; - -typedef Future Benchmark(); - -class BenchmarkHelper { - static void prettyPrint(Map> results) { - print("Results:"); - results.forEach((name, result) { - int total = - result.map((it) => it.elapsedMilliseconds).reduce((a, b) => a + b); - num avg = total / result.length; - print(" - ${name}:"); - print(" - Average: ${avg}ms"); - print(" - Times:"); - - for (var resultz in result) { - print(" - ${resultz.elapsedMilliseconds}ms"); - } - }); - } -} - -Future> runBenchmark(int times, Benchmark benchmark) { - var group = new FutureGroup(); - for (var i = 0; i < times; i++) { - group.add(benchmark()); - } - return group.future; -} - -void warmup() { - print("Warming Up"); - int fib(int n) { - if (n < 2) return n; - return fib(n - 1) + fib(n - 2); - } - - for (var i = 1; i <= 5; i++) { - fib(20); - } - - print("Warm Up Complete"); -} - -Future>> runBenchmarks( - int times, Map benchmarks) { - warmup(); - - var group = new FutureGroup(); - var results = {}; - benchmarks.forEach((String name, Benchmark benchmark) { - results[name] = []; - for (var i = 0; i < times; i++) { - group.add(benchmark().then((watch) { - results[name].add(watch); - })); - } - }); - - return group.future.then((_) { - return results; - }); -} diff --git a/benchmark/main.dart b/benchmark/main.dart deleted file mode 100644 index e24e35e2..00000000 --- a/benchmark/main.dart +++ /dev/null @@ -1,26 +0,0 @@ -library github.benchmark; - -import "dart:async"; -import "dart:io"; - -import "package:github/server.dart"; - -import "package:quiver/async.dart"; - -part 'repository.dart'; -part 'harness.dart'; -part 'config.dart'; - -GitHub github; - -void main() { - int times = 10; - github = createGitHubClient(auth: new Authentication.withToken(TOKEN)); - runBenchmarks(times, { - "Fetch Repository": fetchRepository, - "Fetch Commits": fetchCommits - }).then((results) { - BenchmarkHelper.prettyPrint(results); - exit(0); - }); -} diff --git a/benchmark/repository.dart b/benchmark/repository.dart deleted file mode 100644 index ccad5cd3..00000000 --- a/benchmark/repository.dart +++ /dev/null @@ -1,21 +0,0 @@ -part of github.benchmark; - -Future fetchRepository() { - var watch = new Stopwatch()..start(); - return github.repositories.getRepository(REPOSITORY_SLUG).then((repo) { - watch.stop(); - return watch; - }); -} - -Future fetchCommits() { - var watch = new Stopwatch()..start(); - - return github.repositories - .listCommits(REPOSITORY_SLUG) - .toList() - .then((commits) { - watch.stop(); - return watch; - }); -} diff --git a/build.yaml b/build.yaml new file mode 100644 index 00000000..fdbbd17d --- /dev/null +++ b/build.yaml @@ -0,0 +1,8 @@ +targets: + $default: + builders: + json_serializable: + options: + # Options configure how source code is generated for every + # `@JsonSerializable`-annotated class in the package. + field_rename: snake diff --git a/dart_test.yaml b/dart_test.yaml new file mode 100644 index 00000000..04f3491b --- /dev/null +++ b/dart_test.yaml @@ -0,0 +1,15 @@ +tags: + scenarios: + skip: | + Not run by default when running dart test. To run: + npx octokit-fixtures-server + dart test -P scenarios + or run all tests with: + make test + +presets: + scenarios: + include_tags: scenarios + run_skipped: true + all: + run_skipped: true \ No newline at end of file diff --git a/example/common.dart b/example/common.dart index b7e75180..e508d0ee 100644 --- a/example/common.dart +++ b/example/common.dart @@ -1,39 +1,39 @@ -import "dart:html"; - -import "package:github/browser.dart"; -import "package:github/markdown.dart" as markdown; - -void init(String script, {void onReady()}) { - var stopwatch = new Stopwatch(); - - if (onReady != null) { - document.onReadyStateChange.listen((event) { - if (document.readyState == ReadyState.COMPLETE) { - stopwatch.stop(); - print( - "Document Finished Loading in ${stopwatch.elapsedMilliseconds}ms"); - onReady(); - } - }); - } - - document.querySelector("#view-source").onClick.listen((_) { - var popup = window.open("view_source.html", "View Source"); - String code; +import 'dart:async'; +// ignore: deprecated_member_use +import 'dart:html'; + +import 'package:github/github.dart'; + +export 'package:github/browser_helper.dart'; +export 'package:github/github.dart'; + +/// Wires up a listener to a button with an id of view-source, +/// if it exists, to show the script source +/// If you don't care about showing the source, or don't have a +/// view source button, then you don't need to call this method +Future initViewSourceButton(String script) async { + // query the DOM for the view source button, handle clicks + document.querySelector('#view-source')?.onClick.listen((_) { + final popup = window.open( + 'https://github.com/SpinlockLabs/github.dart/blob/master/example/$script', + 'View Source'); + String? code; var fetched = false; var ready = false; void sendCode() { popup - .postMessage({"command": "code", "code": code}, window.location.href); + .postMessage({'command': 'code', 'code': code}, window.location.href); } - window.addEventListener("message", (event) { - if (event.data['command'] == "ready") { - ready = true; - if (fetched) { - sendCode(); + window.addEventListener('message', (event) { + if (event is MessageEvent) { + if (event.data['command'] == 'ready') { + ready = true; + if (fetched) { + sendCode(); + } } } }); @@ -51,12 +51,4 @@ void init(String script, {void onReady()}) { Map queryString = Uri.parse(window.location.href).queryParameters; -GitHub _createGitHub() { - initGitHub(); - return new GitHub( - auth: queryString["token"] != null - ? new Authentication.withToken(queryString["token"]) - : new Authentication.anonymous()); -} - -GitHub github = _createGitHub(); +GitHub github = GitHub(auth: findAuthenticationFromEnvironment()); diff --git a/example/emoji.dart b/example/emoji.dart index 9747a188..663269dd 100644 --- a/example/emoji.dart +++ b/example/emoji.dart @@ -1,54 +1,51 @@ -import "dart:html"; +import 'dart:async'; +// ignore: deprecated_member_use +import 'dart:html'; -import "common.dart"; +import 'common.dart'; -DivElement emojiDiv; -Map emojis; +Element? emojiDiv; -void main() { - init("emoji.dart", onReady: () { - emojiDiv = querySelector("#emojis"); - loadEmojis(); - var searchBox = querySelector("#search-box") as InputElement; - searchBox.onKeyUp.listen((event) { - filter(searchBox.value); - }); +Future main() async { + await initViewSourceButton('emoji.dart'); + emojiDiv = querySelector('#emojis'); + await loadEmojis(); + final searchBox = querySelector('#search-box') as InputElement; + searchBox.onKeyUp.listen((event) { + filter(searchBox.value); }); } -void loadEmojis() { - github.misc.listEmojis().then((info) { - emojis = info; - info.forEach((name, url) { - var h = new DivElement(); - h.classes.add("box"); - h.classes.add("item"); - h.classes.add("emoji-box"); - h.style.textAlign = "center"; - h.append(new ImageElement(src: url, width: 64, height: 64) - ..classes.add("emoji")); - h.append(new ParagraphElement()..text = ":${name}:"); - emojiDiv.append(h); - }); +Future loadEmojis() async { + final emojis = await github.misc.listEmojis(); + + emojis.forEach((name, url) { + final h = DivElement(); + h.className = 'emojibox'; + h.style.textAlign = 'center'; + h.append( + ImageElement(src: url, width: 64, height: 64)..classes.add('emoji')); + h.append(ParagraphElement()..text = ':$name:'); + emojiDiv!.append(h); }); } -String lastQuery; +String? lastQuery; -void filter(String query) { +void filter(String? query) { if (lastQuery != null && lastQuery == query) { return; } lastQuery = query; - var boxes = emojiDiv.children; - for (var box in boxes) { - var boxName = box.querySelector("p"); - var t = boxName.text; - var name = t.substring(1, t.length - 1); - if (name.contains(query)) { - box.style.display = "inline"; + final boxes = emojiDiv!.children; + for (final box in boxes) { + final boxName = box.querySelector('p')!; + final t = boxName.text!; + final name = t.substring(1, t.length - 1); + if (name.contains(query!)) { + box.style.display = 'inline'; } else { - box.style.display = "none"; + box.style.display = 'none'; } } } diff --git a/example/emoji.html b/example/emoji.html index 61c228a3..bdafb143 100644 --- a/example/emoji.html +++ b/example/emoji.html @@ -3,25 +3,26 @@ GitHub Emoji - - - + - -
-

GitHub Emoji

-    -
View the Source
-   - -

-
+ +

GitHub Emoji

+ +
- - + + \ No newline at end of file diff --git a/example/gist.dart b/example/gist.dart new file mode 100755 index 00000000..0541ba58 --- /dev/null +++ b/example/gist.dart @@ -0,0 +1,7 @@ +import 'package:github/github.dart'; + +Future main() async { + final github = GitHub(auth: findAuthenticationFromEnvironment()); + var g = await github.gists.getGist('c14da36c866b9fe6f84f5d774b76570b'); + print(g.files); +} diff --git a/example/gist.html b/example/gist.html new file mode 100755 index 00000000..e25fd0bb --- /dev/null +++ b/example/gist.html @@ -0,0 +1,36 @@ + + + + + + + + Gist + + + + +

Gist

+ + +

+ +
+

+
+
+ + + + + + \ No newline at end of file diff --git a/example/index.dart b/example/index.dart new file mode 100644 index 00000000..535acd07 --- /dev/null +++ b/example/index.dart @@ -0,0 +1,13 @@ +// ignore: deprecated_member_use +import 'dart:html'; + +import 'common.dart'; + +void main() { + final tokenInput = querySelector('#token') as InputElement; + tokenInput.value = github.auth.token ?? ''; + window.sessionStorage['GITHUB_TOKEN'] = tokenInput.value!; + tokenInput.onKeyUp.listen((_) { + window.sessionStorage['GITHUB_TOKEN'] = tokenInput.value!; + }); +} diff --git a/example/index.html b/example/index.html index a0c42619..81e054e8 100644 --- a/example/index.html +++ b/example/index.html @@ -6,23 +6,42 @@ GitHub for Dart - Demos - - - +

GitHub for Dart - Demos

+
+ These demos will work without a github token, but it is easy to exhaust the rate limit + without specifying a token. The demos will stop working when that happens. To use a + personal github token, enter it in the input below. It will be saved in session storage, + which goes away when you close your browser. Alternatively, you can add a + querystring parameter to specify your token on any of the examples like + ?GITHUB_TOKEN=[yourtoken] -

Repositories

-

Organization

-

Users

-

User Information

-

Language Breakdown

-

Releases

-

Stars

-

Emoji

-

Zen

+

Github Token:

+ + Readme + Repositories + Organization + Users + User Information + Language Breakdown + Releases + Pull Request + Stars + Code Search + Emoji + Markdown + Zen + + - + \ No newline at end of file diff --git a/example/languages.dart b/example/languages.dart index c52cff0d..aa8b7ec1 100644 --- a/example/languages.dart +++ b/example/languages.dart @@ -1,56 +1,41 @@ -import "dart:html"; +// ignore: deprecated_member_use +import 'dart:html'; -import 'markdown.dart' as markdown; -import "package:github/browser.dart"; -import "common.dart"; +import 'common.dart'; -DivElement tableDiv; +DivElement? tableDiv; -LanguageBreakdown breakdown; +late LanguageBreakdown breakdown; -void main() { - initGitHub(); - init("languages.dart", onReady: () { - tableDiv = querySelector("#table"); - loadRepository(); - }); +Future main() async { + await initViewSourceButton('languages.dart'); + tableDiv = querySelector('#table') as DivElement?; + await loadRepository(); } -void loadRepository() { - var user = "dart-lang"; - var reponame = "bleeding_edge"; - - var params = queryString; - - if (params.containsKey("user")) { - user = params["user"]; - } - - if (params.containsKey("repo")) { - reponame = params["repo"]; - } +Future loadRepository() async { + final params = queryString; + var user = params['user'] ?? 'dart-lang'; + var reponame = params['repo'] ?? 'sdk'; - document.getElementById("name").setInnerHtml("${user}/${reponame}"); + document.getElementById('name')!.text = '$user/$reponame'; - github.repositories - .listLanguages(new RepositorySlug(user, reponame)) - .then((b) { - breakdown = b; - reloadTable(); - }); + final repo = RepositorySlug(user, reponame); + breakdown = await github.repositories.listLanguages(repo); + reloadTable(); } bool isReloadingTable = false; -void reloadTable({int accuracy: 4}) { +void reloadTable({int accuracy = 4}) { if (isReloadingTable) { return; } isReloadingTable = true; - - github.misc.renderMarkdown(generateMarkdown(accuracy)).then((html) { - tableDiv.setInnerHtml(html, treeSanitizer: NodeTreeSanitizer.trusted); + final md = generateMarkdown(accuracy); + github.misc.renderMarkdown(md).then((html) { + tableDiv!.setInnerHtml(html, treeSanitizer: NodeTreeSanitizer.trusted); isReloadingTable = false; }); } @@ -60,22 +45,20 @@ int totalBytes(LanguageBreakdown breakdown) { } String generateMarkdown(int accuracy) { - int total = totalBytes(breakdown); - var data = breakdown.toList(); - - var tableData = []; + final total = totalBytes(breakdown); + final data = breakdown.toList(); + var md = StringBuffer(''' +|Name|Bytes|Percentage| +|-----|-----|-----| +'''); data.sort((a, b) => b[1].compareTo(a[1])); - data.forEach((info) { - String name = info[0]; - int bytes = info[1]; - num percentage = ((bytes / total) * 100); - tableData.add({ - "Name": name, - "Bytes": bytes, - "Percentage": "${percentage.toStringAsFixed(accuracy)}%" - }); - }); - return markdown.table(tableData); + for (final info in data) { + final String? name = info[0]; + final int bytes = info[1]; + final num percentage = (bytes / total) * 100; + md.writeln('|$name|$bytes|${percentage.toStringAsFixed(accuracy)}|'); + } + return md.toString(); } diff --git a/example/languages.html b/example/languages.html index 79276407..92c48de3 100755 --- a/example/languages.html +++ b/example/languages.html @@ -6,27 +6,31 @@ Repository Languages - - - - + - -
-

Repository Languages

+ +

Repository Languages

-
View the Source
-

-
+ +

- - + + - + \ No newline at end of file diff --git a/example/markdown.dart b/example/markdown.dart index 56d1d0e1..e04150a0 100644 --- a/example/markdown.dart +++ b/example/markdown.dart @@ -1,8 +1,6 @@ -import "common.dart"; -import "package:github/browser.dart"; +import 'common.dart'; -void main() { - init("markdown.dart", onReady: () { - GitHubBrowserHelper.renderMarkdown(github, "*[markdown]"); - }); +Future main() async { + await initViewSourceButton('markdown.dart'); + renderMarkdown(github, '*[markdown]'); } diff --git a/example/markdown.html b/example/markdown.html index 50f45df5..add1371b 100644 --- a/example/markdown.html +++ b/example/markdown.html @@ -3,49 +3,48 @@ GitHub Markdown Rendering - + - - -
-
View the Source
+

- - + + \ No newline at end of file diff --git a/example/octocat.dart b/example/octocat.dart deleted file mode 100644 index 0db72e61..00000000 --- a/example/octocat.dart +++ /dev/null @@ -1,31 +0,0 @@ -import "dart:html"; - -import "dart:math" show Random; - -import "common.dart"; - -DivElement $octocat; - -Random random = new Random(); - -void main() { - init("octocat.dart", onReady: () { - $octocat = querySelector("#octocat"); - loadCat(); - }); -} - -void loadCat() { - github.misc.listOctodex(cors: true).toList().then((cats) { - print("${cats.length} octocats"); - var index = random.nextInt(cats.length); - var cat = cats[index]; - print("Selected Octocat at ${index} (${cat.name})"); - $octocat.appendHtml( - """ -

${cat.name}

- - """, - treeSanitizer: NodeTreeSanitizer.trusted); - }); -} diff --git a/example/octocat.html b/example/octocat.html deleted file mode 100644 index ec03f087..00000000 --- a/example/octocat.html +++ /dev/null @@ -1,24 +0,0 @@ - - - - - GitHub - Octocat - - - - - - - -
-
View the Source
-

-
- -
- - - - - - \ No newline at end of file diff --git a/example/organization.dart b/example/organization.dart index f5d92150..d11d0fdd 100644 --- a/example/organization.dart +++ b/example/organization.dart @@ -1,54 +1,39 @@ -import "dart:html"; +import 'dart:async'; +// ignore: deprecated_member_use +import 'dart:html'; -import "package:github/browser.dart"; -import "common.dart"; +import 'common.dart'; -DivElement $org; +DivElement? $output; +InputElement? $input; +ButtonElement? $btn; -void main() { - init("organization.dart", onReady: () { - $org = querySelector("#org"); - loadOrganization(); +Future main() async { + await initViewSourceButton('organization.dart'); + $output = querySelector('#output') as DivElement?; + $input = querySelector('#input') as InputElement?; + $btn = querySelector('#submit') as ButtonElement?; + $input!.onChange.listen((_) { + loadOrganization($input!.value); }); + $btn!.onClick.listen((_) { + loadOrganization($input!.value); + }); + $btn!.click(); } -void loadOrganization() { - var org = "DirectMyFile"; - - if (queryString["name"] != null) { - org = queryString["name"]; +Future loadOrganization(String? orgToLoad) async { + try { + final org = await github.organizations.get(orgToLoad); + final html = ''' +
Name: ${org.name} +
Id: ${org.id} +
Company: ${org.company} +
Followers: ${org.followersCount} +
Following: ${org.followingCount} +'''; + $output!.innerHtml = html; + } on OrganizationNotFound { + $output!.innerHtml = 'Not found.'; } - - github.organizations.get(org).then((Organization org) { - return github.organizations.listTeams(org.name).toList(); - }).then((List teams) { - for (var team in teams) { - var e = new DivElement()..id = "team-${team.name}"; - e.classes.add("team"); - $org.append(e); - e.append(new HeadingElement.h3()..text = team.name); - github.organizations - .listTeamMembers(team.id) - .toList() - .then((List members) { - var divs = members.map((member) { - var h = new DivElement(); - h.classes.add("box"); - h.classes.add("user"); - h.style.textAlign = "center"; - h.append( - new ImageElement(src: member.avatarUrl, width: 64, height: 64) - ..classes.add("avatar")); - h.append(new AnchorElement(href: member.htmlUrl) - ..append(new ParagraphElement()..text = member.login)); - return h; - }); - divs.forEach(e.append); - }); - } - }).catchError((error) { - if (error is OrganizationNotFound) { - window.alert(error.message); - } - }); } diff --git a/example/organization.html b/example/organization.html index ad782694..861bbc75 100644 --- a/example/organization.html +++ b/example/organization.html @@ -3,22 +3,20 @@ GitHub Organization - - - -

GitHub Organization

-
View the Source
+
+ +
-
+
+ + - - \ No newline at end of file diff --git a/example/pr.dart b/example/pr.dart new file mode 100644 index 00000000..660d4c45 --- /dev/null +++ b/example/pr.dart @@ -0,0 +1,17 @@ +import 'dart:async'; +// ignore: deprecated_member_use +import 'dart:html'; + +import 'common.dart'; + +Future main() async { + await initViewSourceButton('pr.dart'); + var pr = await github.pullRequests + .get(RepositorySlug('flutter', 'flutter'), 90295); + renderPr(pr); +} + +void renderPr(PullRequest pr) { + var prDiv = querySelector('#pr')!; + prDiv.innerText = pr.toJson().toString(); +} diff --git a/example/pr.html b/example/pr.html new file mode 100644 index 00000000..d9973493 --- /dev/null +++ b/example/pr.html @@ -0,0 +1,23 @@ + + + + + GitHub for Dart - Pull Request + + + +
+

GitHub for Dart - Pull Request

+ + +

+
+ + Pull Request JSON: +

+
+  
+
+
+
+
\ No newline at end of file
diff --git a/example/readme.dart b/example/readme.dart
index 70847991..1920cca4 100644
--- a/example/readme.dart
+++ b/example/readme.dart
@@ -1,22 +1,13 @@
-import "dart:html";
-
-import "package:github/browser.dart";
-
-import "common.dart";
-
-DivElement readmeDiv;
-
-void main() {
-  init("readme.dart", onReady: () {
-    readmeDiv = querySelector("#readme");
-    loadReadme();
-  });
-}
-
-void loadReadme() {
-  github.repositories
-      .getReadme(new RepositorySlug("DirectMyFile", "github.dart"))
-      .then((file) => github.misc.renderMarkdown(file.content))
-      .then((html) =>
-          readmeDiv.appendHtml(html, validator: NodeTreeSanitizer.trusted));
+// ignore: deprecated_member_use
+import 'dart:html';
+
+import 'common.dart';
+
+Future main() async {
+  await initViewSourceButton('readme.dart');
+  var readmeDiv = querySelector('#readme')!;
+  var repo = RepositorySlug('SpinlockLabs', 'github.dart');
+  final readme = await github.repositories.getReadme(repo);
+  final html = await github.misc.renderMarkdown(readme.text);
+  readmeDiv.appendHtml(html, treeSanitizer: NodeTreeSanitizer.trusted);
 }
diff --git a/example/readme.html b/example/readme.html
index d22733a4..00321b71 100644
--- a/example/readme.html
+++ b/example/readme.html
@@ -3,23 +3,13 @@
 
 
   GitHub.dart README
-  
-  
-  
-  
-  
 
 
-
-  
-
View the Source
-

-
- + + +

- - - + \ No newline at end of file diff --git a/example/readme.md b/example/readme.md new file mode 100644 index 00000000..0781c2ba --- /dev/null +++ b/example/readme.md @@ -0,0 +1,52 @@ +## Getting Started + +First, add the following to your pubspec.yaml: + +```yaml +dependencies: + github: ^6.0.0 +``` + +Then import the library + +```dart +import 'package:github/github.dart'; +``` + +and then use it: + +### Example + +```dart +import 'package:github/github.dart'; + +Future main() async { + /* Create a GitHub Client, with anonymous authentication by default */ + var github = GitHub(); + + /* + or Create a GitHub Client and have it try to find your token or credentials automatically + In Flutter and in server environments this will search environment variables in this order + GITHUB_ADMIN_TOKEN + GITHUB_DART_TOKEN + GITHUB_API_TOKEN + GITHUB_TOKEN + HOMEBREW_GITHUB_API_TOKEN + MACHINE_GITHUB_API_TOKEN + and then GITHUB_USERNAME and GITHUB_PASSWORD + + In a browser it will search keys in the same order first through the query string parameters + and then in window sessionStorage + */ + var github = GitHub(auth: findAuthenticationFromEnvironment()); + + /* or Create a GitHub Client using an auth token */ + var github = GitHub(auth: Authentication.withToken('YourTokenHere')); + + /* or Create a GitHub Client using a username and password */ + var github = GitHub(auth: Authentication.basic('username', 'password')); + + Repository repo = await github.repositories.getRepository(RepositorySlug('user_or_org', 'repo_name')); + /* Do Something with repo */ +} +``` diff --git a/example/release_notes.dart b/example/release_notes.dart new file mode 100644 index 00000000..27835cdd --- /dev/null +++ b/example/release_notes.dart @@ -0,0 +1,63 @@ +// ignore: deprecated_member_use +import 'dart:html'; + +import 'package:pub_semver/pub_semver.dart'; + +import 'common.dart'; + +late DivElement releasesDiv; + +Future main() async { + await initViewSourceButton('release_notes.dart'); + releasesDiv = querySelector('#release_notes')! as DivElement; + releasesDiv.innerText = await loadReleaseNotes(); +} + +Future loadReleaseNotes() async { + var slug = RepositorySlug.full('robrbecker/experiment'); + // var slug = RepositorySlug.full('SpinlockLabs/github.dart'); + + var latestRelease = await github.repositories.getLatestRelease(slug); + var latestTag = latestRelease.tagName!; + var latestVersion = Version.parse(latestTag); + + var unreleasedPRs = await github.search + .issues( + 'repo:${slug.fullName} is:pull-request label:unreleased state:closed', + sort: 'desc') + .toList(); + if (unreleasedPRs.isEmpty) { + print('No unreleased PRs'); + return ''; + } + var semvers = {}; + for (final pr in unreleasedPRs) { + var prlabels = pr.labels + .where((element) => element.name.startsWith('semver:')) + .toList(); + for (final l in prlabels) { + semvers.add(l.name); + } + } + print(latestTag); + print(unreleasedPRs.first.toJson()); + print(semvers); + + var newVersion = ''; + if (semvers.contains('semver:major')) { + newVersion = latestVersion.nextMajor.toString(); + } else if (semvers.contains('semver:minor')) { + newVersion = latestVersion.nextMinor.toString(); + } else if (semvers.contains('semver:patch')) { + newVersion = latestVersion.nextPatch.toString(); + } + print(newVersion); + if (newVersion.isEmpty) { + return ''; + } + + var notes = await github.repositories.generateReleaseNotes(CreateReleaseNotes( + slug.owner, slug.name, newVersion, + previousTagName: latestTag)); + return '${notes.name}\n${notes.body}'; +} diff --git a/example/release_notes.html b/example/release_notes.html new file mode 100644 index 00000000..9544974f --- /dev/null +++ b/example/release_notes.html @@ -0,0 +1,21 @@ + + + + + GitHub Release Notes + + + +
+

GitHub Release Notes

+ +

+
+ +
+ + + + + + \ No newline at end of file diff --git a/example/releases.dart b/example/releases.dart index 984f4570..c244c962 100644 --- a/example/releases.dart +++ b/example/releases.dart @@ -1,40 +1,36 @@ -import "dart:html"; +// ignore: deprecated_member_use +import 'dart:html'; -import "package:github/browser.dart"; +import 'common.dart'; -import "common.dart"; +DivElement? releasesDiv; -DivElement releasesDiv; - -void main() { - init("releases.dart", onReady: () { - releasesDiv = querySelector("#releases"); - loadReleases(); - }); +Future main() async { + await initViewSourceButton('releases.dart'); + releasesDiv = querySelector('#releases') as DivElement?; + loadReleases(); } void loadReleases() { github.repositories - .listReleases(new RepositorySlug("twbs", "bootstrap")) + .listReleases(RepositorySlug('Workiva', 'w_common')) + .take(10) .toList() .then((releases) { - for (var release in releases) { - releasesDiv.appendHtml( - """ + for (final release in releases) { + releasesDiv!.appendHtml('''

${release.name}

- """, - treeSanitizer: NodeTreeSanitizer.trusted); - var rel = releasesDiv.querySelector("#release-${release.id}"); + ''', treeSanitizer: NodeTreeSanitizer.trusted); + final rel = releasesDiv!.querySelector('#release-${release.id}'); void append(String key, String value) { - if (value == null) return; - rel.appendHtml("
${key}: ${value}", + rel!.appendHtml('
$key: $value', treeSanitizer: NodeTreeSanitizer.trusted); } - append("Tag", '${release.tagName}'); - append("Download", + append('Tag', '${release.tagName}'); + append('Download', 'TAR | ZIP'); } }); diff --git a/example/releases.html b/example/releases.html index bc83c95a..81e42972 100644 --- a/example/releases.html +++ b/example/releases.html @@ -3,22 +3,19 @@ GitHub Releases - - - -

GitHub Releases

-
View the Source

+ +

- - + + \ No newline at end of file diff --git a/example/repos.dart b/example/repos.dart index 57b2ca8d..409417ab 100644 --- a/example/repos.dart +++ b/example/repos.dart @@ -1,66 +1,58 @@ -import "dart:html"; +import 'dart:async'; +// ignore: deprecated_member_use +import 'dart:html'; -import "package:github/browser.dart"; -import "package:github/dates.dart"; +import 'common.dart'; -import "common.dart"; - -DivElement repositoriesDiv; -List repos; +DivElement? repositoriesDiv; +List? repos; Map> sorts = { - "stars": (Repository a, Repository b) => + 'stars': (Repository a, Repository b) => b.stargazersCount.compareTo(a.stargazersCount), - "forks": (Repository a, Repository b) => b.forksCount.compareTo(a.forksCount), - "created": (Repository a, Repository b) => b.createdAt.compareTo(a.createdAt), - "pushed": (Repository a, Repository b) => b.pushedAt.compareTo(a.pushedAt), - "size": (Repository a, Repository b) => b.size.compareTo(a.size) + 'forks': (Repository a, Repository b) => b.forksCount.compareTo(a.forksCount), + 'created': (Repository a, Repository b) => + b.createdAt!.compareTo(a.createdAt!), + 'pushed': (Repository a, Repository b) => b.pushedAt!.compareTo(a.pushedAt!), + 'size': (Repository a, Repository b) => b.size.compareTo(a.size) }; -void main() { - var stopwatch = new Stopwatch(); - stopwatch.start(); +Future main() async { + await initViewSourceButton('repos.dart'); - repositoriesDiv = querySelector("#repos"); + repositoriesDiv = querySelector('#repos') as DivElement?; - document.onReadyStateChange.listen((event) { - if (document.readyState == ReadyState.COMPLETE) { - stopwatch.stop(); - print("Document Finished Loading in ${stopwatch.elapsedMilliseconds}ms"); - loadRepos(); - } - }); + loadRepos(); - querySelector("#reload").onClick.listen((event) { + querySelector('#reload')!.onClick.listen((event) { loadRepos(); }); - sorts.keys.forEach((name) { - querySelector("#sort-${name}").onClick.listen((event) { + for (final name in sorts.keys) { + querySelector('#sort-$name')!.onClick.listen((event) { if (_reposCache == null) { loadRepos(sorts[name]); } - updateRepos(_reposCache, sorts[name]); + updateRepos(_reposCache!, sorts[name]); }); - }); - - init("repos.dart"); + } } -List _reposCache; +List? _reposCache; -void updateRepos(List repos, - [int compare(Repository a, Repository b)]) { - document.querySelector("#repos").children.clear(); +void updateRepos( + List repos, [ + int Function(Repository a, Repository b)? compare, +]) { + document.querySelector('#repos')!.children.clear(); repos.sort(compare); - for (var repo in repos) { - repositoriesDiv.appendHtml( - """ + for (final repo in repos) { + repositoriesDiv!.appendHtml('''

${repo.name}

- ${repo.description != "" && repo.description != null ? "Description: ${repo.description}
" : ""} - Language: ${repo.language != null ? repo.language : "Unknown"} + ${repo.description != "" ? "Description: ${repo.description}
" : ""} + Language: ${repo.language}
Default Branch: ${repo.defaultBranch}
@@ -68,42 +60,39 @@ void updateRepos(List repos,
Forks: ${repo.forksCount}
- Created: ${friendlyDateTime(repo.createdAt)} + Created: ${repo.createdAt}
Size: ${repo.size} bytes

- """, - treeSanitizer: NodeTreeSanitizer.trusted); + ''', treeSanitizer: NodeTreeSanitizer.trusted); } } -void loadRepos([int compare(Repository a, Repository b)]) { - var title = querySelector("#title"); - if (title.text.contains("(")) { - title.replaceWith(new HeadingElement.h2() - ..text = "GitHub for Dart - Repositories" - ..id = "title"); +void loadRepos([int Function(Repository a, Repository b)? compare]) { + final title = querySelector('#title')!; + if (title.text!.contains('(')) { + title.replaceWith(HeadingElement.h2() + ..text = 'GitHub for Dart - Repositories' + ..id = 'title'); } - var user = "DirectMyFile"; + String? user = 'SpinlockLabs'; - if (queryString.containsKey("user")) { + if (queryString.containsKey('user')) { user = queryString['user']; } - if (queryString.containsKey("sort") && compare == null) { - var sorter = queryString['sort']; + if (queryString.containsKey('sort') && compare == null) { + final sorter = queryString['sort']; if (sorts.containsKey(sorter)) { - compare = sorts[sorter]; + compare = sorts[sorter!]; } } - if (compare == null) { - compare = (a, b) => a.name.compareTo(b.name); - } + compare ??= (a, b) => a.name.compareTo(b.name); - github.repositories.listUserRepositories(user).toList().then((repos) { + github.repositories.listUserRepositories(user!).toList().then((repos) { _reposCache = repos; updateRepos(repos, compare); }); diff --git a/example/repos.html b/example/repos.html index 05ef0782..d142119b 100644 --- a/example/repos.html +++ b/example/repos.html @@ -3,29 +3,25 @@ GitHub for Dart - Repositories - - - -

GitHub for Dart - Repositories

-
View the Source
-
Reload
-
Sort by Stars
-
Sort by Forks
-
Sort by Creation Date
-
Sort by Last Push
-
Sort by Size
+ + + + + + +

- - + + \ No newline at end of file diff --git a/example/search.dart b/example/search.dart new file mode 100644 index 00000000..aeee9cbb --- /dev/null +++ b/example/search.dart @@ -0,0 +1,52 @@ +// ignore: deprecated_member_use +import 'dart:html'; + +import 'common.dart'; + +Future main() async { + await initViewSourceButton('search.dart'); + + final searchBtn = querySelector('#submit')!; + searchBtn.onClick.listen(search); +} + +Future search(_) async { + final resultsStream = github.search.code( + val('query')!, + language: val('language'), + filename: val('filename'), + user: val('user'), + repo: val('repo'), + org: val('org'), + extension: val('ext'), + fork: val('fork'), + path: val('path'), + size: val('size'), + inFile: isChecked('infile')!, + inPath: isChecked('inpath')!, + perPage: int.tryParse(val('perpage')!), + pages: int.tryParse(val('pages')!), + ); + final resultsDiv = querySelector('#results') as DivElement; + resultsDiv.innerHtml = ''; + + var count = 0; + await for (final results in resultsStream) { + count += results.items!.length; + querySelector('#nresults')!.text = + '${results.totalCount} result${results.totalCount == 1 ? "" : "s"} (showing $count)'; + + for (final item in results.items!) { + final url = item.htmlUrl; + final path = item.path; + resultsDiv.append(DivElement() + ..append(AnchorElement(href: url.toString()) + ..text = path + ..target = '_blank')); + } + } +} + +String? val(String id) => (querySelector('#$id') as InputElement).value; +bool? isChecked(String id) => + (querySelector('#$id') as CheckboxInputElement).checked; diff --git a/example/search.html b/example/search.html new file mode 100644 index 00000000..16f41b72 --- /dev/null +++ b/example/search.html @@ -0,0 +1,37 @@ + + + + + GitHub Code Search + + + +

GitHub Search

+
Repo:
+
Language:
+
Filename:
+
Extension:
+
Username:
+
Org:
+
Fork: (true, only)
+
Path:
+
Size:
+
Query:
+
+ In File Contents
+ In Path +
+
+ Per Page: + Pages: +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/example/stars.dart b/example/stars.dart index 56a989e2..2bc50b4c 100644 --- a/example/stars.dart +++ b/example/stars.dart @@ -1,45 +1,36 @@ -import "dart:html"; +// ignore: deprecated_member_use +import 'dart:html'; -import "package:github/browser.dart"; -import "common.dart"; +import 'common.dart'; -DivElement $stars; +DivElement? $stars; -void main() { - init("stars.dart", onReady: () { - $stars = querySelector("#stars"); - loadStars(); - }); +Future main() async { + await initViewSourceButton('stars.dart'); + $stars = querySelector('#stars') as DivElement?; + loadStars(); } void loadStars() { - var user = "DirectMyFile"; - var repo = "github.dart"; - - if (queryString.containsKey("user")) { - user = queryString["user"]; - } - - if (queryString.containsKey("repo")) { - repo = queryString["repo"]; - } + var user = queryString['user'] ?? 'SpinlockLabs'; + var repo = queryString['repo'] ?? 'github.dart'; - querySelector("#title").appendText(" for ${user}/${repo}"); + querySelector('#title')!.appendText(' for $user/$repo'); github.activity - .listStargazers(new RepositorySlug(user, repo)) + .listStargazers(RepositorySlug(user, repo)) .listen((stargazer) { - var h = new DivElement(); - h.classes.add("box"); - h.classes.add("user"); - h.style.textAlign = "center"; - h.append(new ImageElement(src: stargazer.avatarUrl, width: 64, height: 64) - ..classes.add("avatar")); - h.append(new AnchorElement(href: stargazer.htmlUrl) - ..append(new ParagraphElement()..text = stargazer.login)); - $stars.append(h); + final h = DivElement(); + h.classes.add('box'); + h.classes.add('user'); + h.style.textAlign = 'center'; + h.append(ImageElement(src: stargazer.avatarUrl, width: 64, height: 64) + ..classes.add('avatar')); + h.append(AnchorElement(href: stargazer.htmlUrl) + ..append(ParagraphElement()..text = stargazer.login)); + $stars!.append(h); }).onDone(() { - querySelector("#total") - .appendText(querySelectorAll(".user").length.toString() + " stars"); + querySelector('#total')! + .appendText('${querySelectorAll('.user').length} stars'); }); } diff --git a/example/stars.html b/example/stars.html index 24fb81c8..d6175d62 100644 --- a/example/stars.html +++ b/example/stars.html @@ -3,25 +3,21 @@ GitHub Stars - - - -

GitHub Stars

   -
View the Source
+

- - + + \ No newline at end of file diff --git a/example/status.dart b/example/status.dart deleted file mode 100644 index 10ee6b88..00000000 --- a/example/status.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'dart:async'; -import "dart:html"; -import "dart:convert"; - -Future main() async { - var request = await HttpRequest.request( - "https://status.github.com/api/status.json", - requestHeaders: {"Origin": window.location.origin}); - - var text = request.responseText; - var json = JSON.decode(text); - - querySelector("#status") - ..appendText(json["status"]) - ..classes.add("status-${json["status"]}"); -} diff --git a/example/status.html b/example/status.html deleted file mode 100644 index e12d0adc..00000000 --- a/example/status.html +++ /dev/null @@ -1,22 +0,0 @@ - - - - - GitHub Status - - - - - - - - -
-

-
- - - - - - diff --git a/example/user_info.dart b/example/user_info.dart index 7601a981..656207b1 100644 --- a/example/user_info.dart +++ b/example/user_info.dart @@ -1,61 +1,67 @@ -import "dart:html"; +// ignore: deprecated_member_use +import 'dart:html'; -import "package:github/browser.dart"; -import "common.dart"; +import 'common.dart'; -DivElement info; +DivElement? info; -void main() { - init("user_info.dart", onReady: () { - info = document.getElementById("info"); - loadUser(); - }); +Future main() async { + await initViewSourceButton('user_info.dart'); + info = document.getElementById('info') as DivElement?; + loadUser(); } -GitHub createClient(String token) { - return new GitHub(auth: new Authentication.withToken(token)); +GitHub createClient(String? token) { + return GitHub(auth: Authentication.withToken(token)); } void loadUser() { - var token = document.getElementById("token") as InputElement; - document.getElementById("load").onClick.listen((event) { - if (token.value == null || token.value.isEmpty) { - window.alert("Please Enter a Token"); + final localToken = document.getElementById('token') as InputElement?; + + final loadBtn = document.getElementById('load')!; + loadBtn.onClick.listen((event) { + if (localToken!.value == null || localToken.value!.isEmpty) { + window.alert('Please Enter a Token'); return; } - github = createClient(token.value); + github = createClient(localToken.value); - github.users.getCurrentUser().then((CurrentUser user) { - info.children.clear(); - info.hidden = false; - info.appendHtml(""" + github.users.getCurrentUser().then((final CurrentUser user) { + info!.children.clear(); + info!.hidden = false; + info!.appendHtml(''' Name: ${user.name} - """); + '''); void append(String name, dynamic value) { if (value != null) { - info.appendHtml(""" + info!.appendHtml('''
- ${name}: ${value.toString()} - """); + $name: ${value.toString()} + '''); } } - append("Biography", user.bio); - append("Company", user.company); - append("Email", user.email); - append("Followers", user.followersCount); - append("Following", user.followingCount); - append("Disk Usage", user.diskUsage); - append("Plan Name", user.plan.name); - append("Created", user.createdAt); - document.getElementById("load").hidden = true; - document.getElementById("token").hidden = true; + append('Biography', user.bio); + append('Company', user.company); + append('Email', user.email); + append('Followers', user.followersCount); + append('Following', user.followingCount); + append('Disk Usage', user.diskUsage); + append('Plan Name', user.plan!.name); + append('Created', user.createdAt); + document.getElementById('load')!.hidden = true; + document.getElementById('token')!.hidden = true; }).catchError((e) { if (e is AccessForbidden) { - window.alert("Invalid Token"); + window.alert('Invalid Token'); } }); }); + + if (github.auth.token != null) { + localToken!.value = github.auth.token; + loadBtn.click(); + } } diff --git a/example/user_info.html b/example/user_info.html index 06016735..5808fd28 100644 --- a/example/user_info.html +++ b/example/user_info.html @@ -6,28 +6,26 @@ GitHub - User Information - - -

GitHub User Information

-

Gets information about you from GitHub. Input a personal token that has the 'user' permission. This token is not stored.

+

Gets information about you from GitHub. Input a personal token that has the 'user' permission. This token is not + stored.

- + Github Token:   -
Load Information
-
View the Source
-

+ + +

- - + + - + \ No newline at end of file diff --git a/example/users.dart b/example/users.dart index 50578244..003d3f5d 100644 --- a/example/users.dart +++ b/example/users.dart @@ -1,62 +1,46 @@ -import "dart:html"; +import 'dart:async'; +// ignore: deprecated_member_use +import 'dart:html'; -import "package:github/browser.dart"; -import "package:github/dates.dart"; +import 'common.dart'; -import "common.dart"; +DivElement? usersDiv; -DivElement usersDiv; - -void main() { - init("users.dart", onReady: () { - usersDiv = querySelector("#users"); - loadUsers(); - }); +Future main() async { + await initViewSourceButton('users.dart'); + usersDiv = querySelector('#users') as DivElement?; + loadUsers(); } void loadUsers() { - String column = "left"; - github.users.listUsers(pages: 2).take(12).listen((User baseUser) { github.users.getUser(baseUser.login).then((user) { - var m = new DivElement(); - - m.classes.addAll(["box", "user", "middle", "center"]); + final userDiv = DivElement(); - var h = new DivElement()..classes.add("middle"); - - for (int i = 1; i <= 2; i++) { - h.append(new BRElement()); + for (var i = 1; i <= 2; i++) { + userDiv.append(BRElement()); } - h.append(GitHubBrowserHelper.createAvatarImage(user, - width: 64, height: 64)..classes.add("avatar")); - var buff = new StringBuffer(); + userDiv.append(createAvatarImage(user, width: 64, height: 64) + ..classes.add('avatar')); + final buff = StringBuffer(); buff - ..writeln("Username: ${user.login}") - ..writeln("Created: ${friendlyDateTime(user.createdAt)}") - ..writeln("Updated: ${friendlyDateTime(user.updatedAt)}"); + ..writeln('Username: ${user.login}') + ..writeln('Created: ${user.createdAt}') + ..writeln('Updated: ${user.updatedAt}'); - if (user.company != null && user.company.isNotEmpty) { - buff.writeln("Company: ${user.company}"); + if (user.company != null && user.company!.isNotEmpty) { + buff.writeln('Company: ${user.company}'); } - buff.writeln("Followers: ${user.followersCount}"); + buff.writeln('Followers: ${user.followersCount}'); - h.append(new ParagraphElement() - ..appendHtml(buff.toString().replaceAll("\n", "
"), + userDiv.append(ParagraphElement() + ..appendHtml(buff.toString().replaceAll('\n', '
'), treeSanitizer: NodeTreeSanitizer.trusted)); - m.append(h); - - usersDiv.querySelector("#${column}"); - - if (column == "left") { - column = "right"; - } else { - column = "left"; - } + usersDiv!.append(userDiv); }); }); } diff --git a/example/users.html b/example/users.html index 71ef7b2e..12673d2e 100644 --- a/example/users.html +++ b/example/users.html @@ -3,26 +3,17 @@ GitHub - Oldest Users - - - -

GitHub - Oldest Users

-
View the Source
+

-
-
-
- - - + \ No newline at end of file diff --git a/example/view_source.html b/example/view_source.html deleted file mode 100755 index 38b4b3af..00000000 --- a/example/view_source.html +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - View Source - - - - - - - -
- - - - - - diff --git a/example/view_source.js b/example/view_source.js deleted file mode 100755 index 01ca35f1..00000000 --- a/example/view_source.js +++ /dev/null @@ -1,81 +0,0 @@ -var args = document.location.search.substring(1).split('&'); - -var opts = {}; - -for (var i = 0; i < args.length; i++) { - var arg = window.decodeURIComponent(args[i]); - - if (arg.indexOf('=') == -1) { - opts[arg.trim()] = true; - } else { - var kvp = arg.split('='); - opts[kvp[0].trim()] = kvp[1].trim(); - } -} - -function opt(name, def) { - if (Object.keys(opts).indexOf(name) !== -1) { - return opts[name]; - } else { - return def; - } -} - -function createEditor(code) { - var editor = ace.edit("editor"); - - editor.focus(); - editor.setReadOnly(opts['editable'] ? true : false); - - editor.commands.addCommand({ - name: 'saveFile', - bindKey: { - win: 'Ctrl-S', - mac: 'Command-S', - sender: 'editor|cli' - }, - exec: function() {} - }); - - editor.setTheme("ace/theme/" + opt("theme", "github")); - editor.getSession().setMode("ace/mode/" + opt("mode", "dart")); - editor.setShowPrintMargin(false); - editor.setValue(code, 0); - editor.clearSelection(); - editor.moveCursorTo(0, 0); - editor.setReadOnly(true); -} - -function receiveMessage(event) { - var msg = event.data; - - if (msg.command === "code") { - createEditor(msg.code); - } -} - -if (window.opener !== null) { - window.addEventListener("message", receiveMessage); -} else { - if (Object.keys(opts).indexOf("path") !== -1) { - var req = new XMLHttpRequest(); - req.open("GET", opts.path); - - req.onreadystatechange = function() { - if (req.readyState === XMLHttpRequest.DONE) { - if (req.status === 200) { - createEditor(req.responseText); - } else { - createEditor("ERROR: " + opts.path + " was not found."); - } - } - }; - req.send(); - } -} - -ready(function () { - if (window.opener) { - window.opener.postMessage({ "command": "ready" }, "*"); - } -}); \ No newline at end of file diff --git a/example/zen.dart b/example/zen.dart index b8414173..34c55c87 100644 --- a/example/zen.dart +++ b/example/zen.dart @@ -1,15 +1,10 @@ -import "dart:html"; -import "common.dart"; +// ignore: deprecated_member_use +import 'dart:html'; -DivElement $zen; +import 'common.dart'; -void main() { - init("zen.dart", onReady: () { - $zen = querySelector("#zen"); - loadZen(); - }); -} - -void loadZen() { - github.misc.getZen().then((zen) => $zen.appendText(zen)); +Future main() async { + await initViewSourceButton('zen.dart'); + final msg = await github.misc.getZen(); + querySelector('#zen')!.text = msg; } diff --git a/example/zen.html b/example/zen.html index e1d99665..0149ee16 100644 --- a/example/zen.html +++ b/example/zen.html @@ -3,25 +3,19 @@ GitHub Zen - - - -

GitHub Zen

-
View the Source
+

-
-

-
+

Loading...

+ + - - \ No newline at end of file diff --git a/integration_test/git_integration_test.dart b/integration_test/git_integration_test.dart new file mode 100644 index 00000000..3ff4d113 --- /dev/null +++ b/integration_test/git_integration_test.dart @@ -0,0 +1,162 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:github/github.dart'; +import 'package:test/test.dart'; + +void main() { + String? firstCommitSha; + String? firstCommitTreeSha; + + String? createdTreeSha; + String? createdCommitSha; + + late GitHub github; + late RepositorySlug slug; + + setUpAll(() { + final authToken = Platform.environment['GITHUB_API_TOKEN']; + final repoOwner = Platform.environment['GITHUB_DART_TEST_REPO_OWNER']; + final repoName = Platform.environment['GITHUB_DART_TEST_REPO_NAME']; + if (repoName == null || repoOwner == null) { + throw AssertionError('config incorrect'); + } + github = GitHub(auth: Authentication.withToken(authToken)); + slug = RepositorySlug(repoOwner, repoName); + }); + + tearDownAll(() { + github.dispose(); + }); + + // Test definitions. + test('get last commit of master', () async { + final branch = await github.repositories.getBranch(slug, 'master'); + firstCommitSha = branch.commit!.sha; + firstCommitTreeSha = branch.commit!.commit!.sha; + }); + + test('create and get a new blob', () async { + var newBlob = CreateGitBlob('bbb', 'utf-8'); + + // createBlob() + final createdBlob = await github.git.createBlob(slug, newBlob); + final createdBlobSha = createdBlob.sha; + + final fetchedBlob = await github.git.getBlob(slug, createdBlobSha); + + final base64Decoded = base64Decode(fetchedBlob.content!); + + expect(utf8.decode(base64Decoded), equals('bbb')); + expect(fetchedBlob.encoding, equals('base64')); + expect( + fetchedBlob.url, + equals( + 'https://api.github.com/repos/${slug.fullName}/git/blobs/$createdBlobSha')); + expect(fetchedBlob.sha, equals(createdBlobSha)); + expect(fetchedBlob.size, equals(3)); + }); + + test('create and get a new tree', () async { + var entry1 = CreateGitTreeEntry('README.md', '100644', 'blob', + content: 'This is a repository for integration tests.'); + var entry2 = CreateGitTreeEntry('subdir/asdf.txt', '100644', 'blob', + content: 'Some file in a folder.'); + + final newTree = CreateGitTree([entry1, entry2]) + ..baseTree = firstCommitTreeSha; + + // createTree() + final createdTree = await github.git.createTree(slug, newTree); + createdTreeSha = createdTree.sha; + + // getTree() + final fetchedTree = await github.git.getTree(slug, createdTreeSha); + + expect(fetchedTree.sha, equals(createdTreeSha)); + expect(fetchedTree.entries!.length, equals(2)); + }); + + test('create and get a new commit', () async { + final newCommit = CreateGitCommit('My test commit', createdTreeSha) + ..parents = [firstCommitSha]; + + // createCommit() + final createdCommit = await github.git.createCommit(slug, newCommit); + createdCommitSha = createdCommit.sha; + + // getCommit() + final fetchedCommit = await github.git.getCommit(slug, createdCommitSha); + expect(fetchedCommit.sha, equals(createdCommitSha)); + expect(fetchedCommit.message, equals('My test commit')); + expect(fetchedCommit.tree!.sha, equals(createdTreeSha)); + expect(fetchedCommit.parents!.first.sha, equals(firstCommitSha)); + }); + + test('update heads/master reference to new commit', () { + return github.git.editReference(slug, 'heads/master', createdCommitSha); + }); + + test('create and get a new reference (branch)', () async { + final branchName = _randomGitName(); + + await github.git + .createReference(slug, 'refs/heads/$branchName', createdCommitSha); + + final fetchedRef = await github.git.getReference(slug, 'heads/$branchName'); + expect(fetchedRef.ref, equals('refs/heads/$branchName')); + expect(fetchedRef.object!.type, equals('commit')); + expect(fetchedRef.object!.sha, equals(createdCommitSha)); + }); + + test('create and get a new tag', () async { + final tagName = 'v${_randomGitName()}'; + + final newTag = CreateGitTag(tagName, 'Version 0.0.1', createdCommitSha, + 'commit', GitCommitUser('aName', 'aEmail', DateTime.now())); + + // createTag() + final createdTag = await github.git.createTag(slug, newTag); + final createdTagSha = createdTag.sha; + + // getTag() + final fetchedTag = await github.git.getTag(slug, createdTagSha); + expect(fetchedTag.tag, equals(tagName)); + expect(fetchedTag.sha, equals(createdTagSha)); + expect(fetchedTag.message, equals('Version 0.0.1')); + expect(fetchedTag.tagger!.name, equals('aName')); + expect(fetchedTag.object!.sha, equals(createdCommitSha)); + + // Create a reference for the tag. + await github.git.createReference(slug, 'refs/tags/$tagName', createdTagSha); + }); + + group('create and query issues', () { + test('query issues', () async { + var issues = await github.issues.listByRepo(slug).toList(); + + final count = issues.length; + + final issueRequest = + IssueRequest(title: 'new issue - ${_randomGitName()}'); + + await github.issues.create(slug, issueRequest); + + issues = await github.issues + .listByRepo(slug, sort: 'updated', direction: 'desc') + .toList(); + + expect(issues, hasLength(count + 1)); + + final issue = issues.first; + + expect(issue.title, issueRequest.title); + }); + }); +} + +String _randomGitName() { + final now = DateTime.now().toIso8601String().replaceAll(':', '_'); + + return now.toString(); +} diff --git a/lib/browser.dart b/lib/browser.dart deleted file mode 100644 index 5a3d0c24..00000000 --- a/lib/browser.dart +++ /dev/null @@ -1,19 +0,0 @@ -/// GitHub for the Browser -/// -/// This contains a few utilities that are browser specific. - -library github.browser; - -import 'package:http/browser_client.dart'; - -import "src/common.dart"; - -export "src/browser/helper.dart"; -export "src/common.dart"; - -/// Creates a GitHub Client -GitHub createGitHubClient( - {Authentication auth, String endpoint: "https://api.github.com"}) { - return new GitHub( - auth: auth, client: new BrowserClient(), endpoint: endpoint); -} diff --git a/lib/browser_helper.dart b/lib/browser_helper.dart new file mode 100644 index 00000000..39ebd393 --- /dev/null +++ b/lib/browser_helper.dart @@ -0,0 +1,41 @@ +// ignore: deprecated_member_use +import 'dart:html'; + +import 'package:github/src/common.dart'; + +/// Renders Markdown in HTML using the GitHub API +/// +/// TODO: Remove the requirement of [indent] and auto-detect it. +/// +/// [github] is the GitHub instance to use. +/// [selector] is the selector to use to find markdown elements. +/// [indent] is the indent that needs to be stripped out. +void renderMarkdown(GitHub github, String selector, {int indent = 4}) { + final elements = document.querySelectorAll(selector); + + elements.removeWhere((Element it) => it.attributes.containsKey('rendered')); + + for (final e in elements) { + final txt = e.text!; + + final md = txt.split('\n').map((it) { + return it.length >= indent ? it.substring(indent) : it; + }).join('\n'); + + github.misc.renderMarkdown(md).then((html) { + e.hidden = false; + e.setAttribute('rendered', ''); + e.classes.add('markdown-body'); + e.setInnerHtml(html, treeSanitizer: NodeTreeSanitizer.trusted); + }); + } +} + +/// Creates an Image Element from a User that has the user's avatar. +ImageElement createAvatarImage( + User user, { + int width = 128, + int height = 128, +}) { + return ImageElement(src: user.avatarUrl, width: width, height: height); +} diff --git a/lib/github.dart b/lib/github.dart new file mode 100644 index 00000000..6b79e1c4 --- /dev/null +++ b/lib/github.dart @@ -0,0 +1,7 @@ +export 'package:github/src/common.dart'; + +/// Do a conditional export of the right cross platform pieces depending on +/// if dart.html or dart.io is available. +export 'package:github/src/common/xplat_common.dart' + if (dart.library.html) 'package:github/src/browser/xplat_browser.dart' + if (dart.library.io) 'package:github/src/server/xplat_server.dart'; diff --git a/lib/hooks.dart b/lib/hooks.dart new file mode 100644 index 00000000..19fc3fae --- /dev/null +++ b/lib/hooks.dart @@ -0,0 +1,12 @@ +/// This entrypoint is here so that dartdoc will generate documentation for +/// files under lib/src/server. This is only necessary because conditional +/// import/export isn't well supported in the Dart ecosystem. +/// +/// `import 'package:github/hooks.dart';` +/// +/// Add this import if you are in a non-web environment and writing something +/// that uses github hooks. For more information, see github hooks documentation +/// https://developer.github.com/v3/repos/hooks/ +library; + +export 'src/server/xplat_server.dart'; diff --git a/lib/server.dart b/lib/server.dart deleted file mode 100644 index 735e72bf..00000000 --- a/lib/server.dart +++ /dev/null @@ -1,72 +0,0 @@ -/// GitHub for the Server -library github.server; - -import "dart:io"; - -import "src/common.dart"; - -export "src/common.dart"; -export "src/server/hooks.dart"; - -/// Creates a GitHub Client. -/// If [auth] is not specified, then it will be automatically configured -/// from the environment as per [findAuthenticationFromEnvironment]. -GitHub createGitHubClient( - {Authentication auth, String endpoint: "https://api.github.com"}) { - if (auth == null) { - auth = findAuthenticationFromEnvironment(); - } - - return new GitHub(auth: auth, endpoint: endpoint); -} - -const List COMMON_GITHUB_TOKEN_ENV_KEYS = const [ - "GITHUB_ADMIN_TOKEN", - "GITHUB_DART_TOKEN", - "GITHUB_API_TOKEN", - "GITHUB_TOKEN", - "HOMEBREW_GITHUB_API_TOKEN", - "MACHINE_GITHUB_API_TOKEN" -]; - -/// Looks for GitHub Authentication Information in the current process environment. -/// -/// Checks all the environment variables in [COMMON_GITHUB_TOKEN_ENV_KEYS] for tokens. -/// If the above fails, the GITHUB_USERNAME and GITHUB_PASSWORD keys will be checked. -Authentication findAuthenticationFromEnvironment() { - if (Platform.isMacOS) { - try { - var result = Process.runSync("security", - const ["find-internet-password", "-g", "-s", "github.com"]); - - if (result.exitCode != 0) { - throw "Don't use keychain."; - } - String out = result.stdout.toString(); - - String username = out.split('"acct"="')[1]; - username = username.substring(0, username.indexOf("\n")); - username = username.substring(0, username.length - 1); - String password = result.stderr.toString().split("password:")[1].trim(); - password = password.substring(1, password.length - 1); - return new Authentication.basic(username.trim(), password.trim()); - } catch (e) { - print(e); - } - } - - Map env = Platform.environment; - - for (String key in COMMON_GITHUB_TOKEN_ENV_KEYS) { - if (env[key] is String) { - return new Authentication.withToken(env[key]); - } - } - - if (env["GITHUB_USERNAME"] is String && env["GITHUB_PASSWORD"] is String) { - return new Authentication.basic( - env["GITHUB_USERNAME"], env["GITHUB_PASSWORD"]); - } - - return new Authentication.anonymous(); -} diff --git a/lib/src/browser/helper.dart b/lib/src/browser/helper.dart deleted file mode 100644 index 970e2438..00000000 --- a/lib/src/browser/helper.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:html' hide Client; - -import '../common.dart'; - -/// Browser-Specific Helpers -class GitHubBrowserHelper { - /// Renders Markdown in HTML using the GitHub API - /// - /// TODO: Remove the requirement of [indent] and auto-detect it. - /// - /// [github] is the GitHub instance to use. - /// [selector] is the selector to use to find markdown elements. - /// [indent] is the indent that needs to be stripped out. - static void renderMarkdown(GitHub github, String selector, {int indent: 4}) { - ElementList elements = document.querySelectorAll(selector); - - elements.removeWhere((Element it) => it.attributes.containsKey("rendered")); - - for (Element e in elements) { - var txt = e.text; - - var md = txt.split("\n").map((it) { - return it.length >= indent ? it.substring(indent) : it; - }).join("\n"); - - github.misc.renderMarkdown(md).then((html) { - e.hidden = false; - e.setAttribute("rendered", ""); - e.classes.add("markdown-body"); - e.setInnerHtml(html, treeSanitizer: NodeTreeSanitizer.trusted); - }); - } - } - - /// Creates an Image Element from a User that has the user's avatar. - static ImageElement createAvatarImage(User user, - {int width: 128, int height: 128}) { - return new ImageElement(src: user.avatarUrl, width: width, height: height); - } -} diff --git a/lib/src/browser/xplat_browser.dart b/lib/src/browser/xplat_browser.dart new file mode 100644 index 00000000..c54a2530 --- /dev/null +++ b/lib/src/browser/xplat_browser.dart @@ -0,0 +1,46 @@ +// ignore: deprecated_member_use +import 'dart:html'; + +import 'package:github/src/common.dart'; +import 'package:github/src/common/xplat_common.dart' + show findAuthenticationInMap; + +/// Looks for GitHub Authentication information from the browser +/// +/// Checks for query strings first, then local storage using keys in [COMMON_GITHUB_TOKEN_ENV_KEYS]. +/// If the above fails, the GITHUB_USERNAME and GITHUB_PASSWORD keys will be checked. +Authentication findAuthenticationFromEnvironment() { + // search the query string parameters first + var auth = findAuthenticationInMap(_parseQuery(window.location.href)); + auth ??= findAuthenticationInMap(window.sessionStorage); + return auth ?? const Authentication.anonymous(); +} + +/// Parse the query string to a parameter `Map` +Map _parseQuery(String path) { + final params = {}; + if (!path.contains('?')) { + return params; + } + final queryStr = path.substring(path.indexOf('?') + 1); + queryStr.split('&').forEach((String keyValPair) { + final keyVal = _parseKeyVal(keyValPair); + final key = keyVal[0]; + if (key.isNotEmpty) { + params[key] = Uri.decodeComponent(keyVal[1]); + } + }); + return params; +} + +/// Parse a key value pair (`"key=value"`) and returns a list of `["key", "value"]`. +List _parseKeyVal(String kvPair) { + if (kvPair.isEmpty) { + return const ['', '']; + } + final splitPoint = kvPair.indexOf('='); + + return (splitPoint == -1) + ? [kvPair, ''] + : [kvPair.substring(0, splitPoint), kvPair.substring(splitPoint + 1)]; +} diff --git a/lib/src/common.dart b/lib/src/common.dart index 4ea2e459..df783a26 100644 --- a/lib/src/common.dart +++ b/lib/src/common.dart @@ -1,62 +1,54 @@ /// The Core of GitHub for Dart. /// Contains the Models and other GitHub stuff. -library github.common; +library; -import "dart:async"; -import "dart:convert" show BASE64, JSON, UTF8; - -import "package:html/dom.dart" as html; -import "package:html/parser.dart" as html_parser; -import "package:http/http.dart" as http; -import "package:quiver/async.dart" show FutureGroup; -import "package:xml/xml.dart" as xml; - -import 'util.dart'; - -part "common/activity_service.dart"; -part "common/authorizations_service.dart"; -part "common/blog_service.dart"; -part "common/explore_service.dart"; -part "common/gists_service.dart"; -part "common/git_service.dart"; -part "common/github.dart"; -part "common/issues_service.dart"; -part "common/misc_service.dart"; -part "common/model/activity.dart"; -part "common/model/authorizations.dart"; -part "common/model/blog.dart"; -part "common/model/explore.dart"; -part "common/model/gists.dart"; -part "common/model/git.dart"; -part "common/model/issues.dart"; -part "common/model/keys.dart"; -part "common/model/misc.dart"; -part "common/model/notifications.dart"; -part "common/model/orgs.dart"; -part "common/model/pulls.dart"; -part "common/model/repos.dart"; -part "common/model/repos_commits.dart"; -part "common/model/repos_contents.dart"; -part "common/model/repos_forks.dart"; -part "common/model/repos_hooks.dart"; -part "common/model/repos_merging.dart"; -part "common/model/repos_pages.dart"; -part "common/model/repos_releases.dart"; -part "common/model/repos_stats.dart"; -part "common/model/repos_statuses.dart"; -part "common/model/search.dart"; -part "common/model/users.dart"; -part "common/orgs_service.dart"; -part "common/pulls_service.dart"; -part "common/repos_service.dart"; -part "common/search_service.dart"; -part "common/url_shortener_service.dart"; -part "common/users_service.dart"; -part "common/util/auth.dart"; -part "common/util/crawler.dart"; -part "common/util/errors.dart"; -part "common/util/json.dart"; -part "common/util/oauth2.dart"; -part "common/util/pagination.dart"; -part "common/util/service.dart"; -part "common/util/utils.dart"; +export 'package:github/src/common/activity_service.dart'; +export 'package:github/src/common/authorizations_service.dart'; +export 'package:github/src/common/checks_service.dart'; +export 'package:github/src/common/gists_service.dart'; +export 'package:github/src/common/git_service.dart'; +export 'package:github/src/common/github.dart'; +export 'package:github/src/common/issues_service.dart'; +export 'package:github/src/common/misc_service.dart'; +export 'package:github/src/common/model/activity.dart'; +export 'package:github/src/common/model/authorizations.dart'; +export 'package:github/src/common/model/checks.dart'; +export 'package:github/src/common/model/gists.dart'; +export 'package:github/src/common/model/git.dart'; +export 'package:github/src/common/model/issues.dart'; +export 'package:github/src/common/model/keys.dart'; +export 'package:github/src/common/model/misc.dart'; +export 'package:github/src/common/model/notifications.dart'; +export 'package:github/src/common/model/orgs.dart'; +export 'package:github/src/common/model/pulls.dart'; +export 'package:github/src/common/model/reaction.dart'; +export 'package:github/src/common/model/repos.dart'; +export 'package:github/src/common/model/repos_commits.dart'; +export 'package:github/src/common/model/repos_contents.dart'; +export 'package:github/src/common/model/repos_forks.dart'; +export 'package:github/src/common/model/repos_hooks.dart'; +export 'package:github/src/common/model/repos_merging.dart'; +export 'package:github/src/common/model/repos_pages.dart'; +export 'package:github/src/common/model/repos_releases.dart'; +export 'package:github/src/common/model/repos_stats.dart'; +export 'package:github/src/common/model/repos_statuses.dart'; +export 'package:github/src/common/model/search.dart'; +export 'package:github/src/common/model/timeline.dart'; +export 'package:github/src/common/model/timeline_support.dart'; +export 'package:github/src/common/model/users.dart'; +export 'package:github/src/common/orgs_service.dart'; +export 'package:github/src/common/pulls_service.dart'; +export 'package:github/src/common/repos_service.dart'; +export 'package:github/src/common/search_service.dart'; +export 'package:github/src/common/url_shortener_service.dart'; +export 'package:github/src/common/users_service.dart'; +export 'package:github/src/common/util/auth.dart'; +export 'package:github/src/common/util/crawler.dart'; +export 'package:github/src/common/util/errors.dart'; +export 'package:github/src/common/util/json.dart'; +export 'package:github/src/common/util/oauth2.dart'; +export 'package:github/src/common/util/pagination.dart'; +export 'package:github/src/common/util/service.dart'; +export 'package:github/src/common/util/utils.dart'; +export 'package:github/src/const/language_color.dart'; +export 'package:github/src/const/token_env_keys.dart'; diff --git a/lib/src/common/activity_service.dart b/lib/src/common/activity_service.dart index d8c002dd..f97aefcf 100644 --- a/lib/src/common/activity_service.dart +++ b/lib/src/common/activity_service.dart @@ -1,122 +1,124 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; + +import 'package:github/src/common.dart'; +import 'package:http/http.dart' as http; /// The [ActivityService] handles communication with activity related methods /// of the GitHub API. /// /// API docs: https://developer.github.com/v3/activity/ class ActivityService extends Service { - ActivityService(GitHub github) : super(github); + ActivityService(super.github); /// Lists public events. /// /// API docs: https://developer.github.com/v3/activity/events/#list-public-events - Stream listPublicEvents({int pages: 2}) { - return new PaginationHelper(_github) - .objects("GET", "/events", Event.fromJSON, pages: pages) - as Stream; + Stream listPublicEvents({int pages = 2}) { + return PaginationHelper(github) + .objects('GET', '/events', Event.fromJson, pages: pages); } /// Lists public events for a network of repositories. /// /// API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-a-network-of-repositories Stream listRepositoryNetworkEvents(RepositorySlug slug, - {int pages: 2}) { - return new PaginationHelper(_github).objects( - "GET", "/networks/${slug.fullName}/events", Event.fromJSON, - pages: pages) as Stream; + {int pages = 2}) { + return PaginationHelper(github).objects( + 'GET', '/networks/${slug.fullName}/events', Event.fromJson, + pages: pages); } /// Returns an [EventPoller] for repository network events. /// /// API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-a-network-of-repositories EventPoller pollRepositoryNetworkEvents(RepositorySlug slug) => - new EventPoller(_github, "/networks/${slug.fullName}/events"); + EventPoller(github, '/networks/${slug.fullName}/events'); /// Returns an [EventPoller] for repository issue events. /// /// API docs: https://developer.github.com/v3/activity/events/#list-repository-events EventPoller pollRepositoryIssueEvents(RepositorySlug slug) => - new EventPoller(_github, "/repos/${slug.fullName}/issues/events"); + EventPoller(github, '/repos/${slug.fullName}/issues/events'); /// Lists repository issue events. /// /// API docs: https://developer.github.com/v3/activity/events/#list-repository-events - Stream listRepositoryIssueEvents(RepositorySlug slug, {int pages}) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/issues/events", Event.fromJSON, - pages: pages) as Stream; + Stream listRepositoryIssueEvents(RepositorySlug slug, {int? pages}) { + return PaginationHelper(github).objects( + 'GET', '/repos/${slug.fullName}/issues/events', Event.fromJson, + pages: pages); } /// Returns an [EventPoller] for public events. /// /// API docs: https://developer.github.com/v3/activity/events/#list-public-events - EventPoller pollPublicEvents() => new EventPoller(_github, "/events"); + EventPoller pollPublicEvents() => EventPoller(github, '/events'); /// Lists repository events. /// /// API docs: https://developer.github.com/v3/activity/events/#list-repository-events - Stream listRepositoryEvents(RepositorySlug slug, {int pages}) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/events", Event.fromJSON, - pages: pages) as Stream; + Stream listRepositoryEvents(RepositorySlug slug, {int? pages}) { + return PaginationHelper(github).objects( + 'GET', '/repos/${slug.fullName}/events', Event.fromJson, + pages: pages); } /// Returns an [EventPoller] for repository events. /// /// API docs: https://developer.github.com/v3/activity/events/#list-repository-events EventPoller pollRepositoryEvents(RepositorySlug slug) => - new EventPoller(_github, "/repos/${slug.fullName}/events"); + EventPoller(github, '/repos/${slug.fullName}/events'); /// Lists public events for an organization. /// /// API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-an-organization - Stream listEventsForOrganization(String name, {int pages}) { - return new PaginationHelper(_github).objects( - "GET", "/orgs/${name}/events", Event.fromJSON, pages: pages) - as Stream; + Stream listEventsForOrganization(String name, {int? pages}) { + return PaginationHelper(github) + .objects('GET', '/orgs/$name/events', Event.fromJson, pages: pages); } /// Returns an [EventPoller] for public events for an organization. /// /// API docs: https://developer.github.com/v3/activity/events/#list-public-events-for-an-organization EventPoller pollEventsForOrganization(String name) => - new EventPoller(_github, "/orgs/${name}/events"); + EventPoller(github, '/orgs/$name/events'); - /// Returns an [EventPoller] for events performed by a user. + /// Returns an [EventPoller] for events received by a user. /// /// API docs: https://developer.github.com/v3/activity/events/#list-events-that-a-user-has-received EventPoller pollEventsReceivedByUser(String user) => - new EventPoller(_github, "/users/${user}/events"); + EventPoller(github, '/users/$user/received_events'); - /// Returns an [EventPoller] for events performed by a user. + /// Returns an [EventPoller] for public events received by a user. /// /// API docs: https://developer.github.com/v3/activity/events/#list-public-events-that-a-user-has-received EventPoller pollPublicEventsReceivedByUser(String user) => - new EventPoller(_github, "/repos/${user}/events/public"); + EventPoller(github, '/users/$user/received_events/public'); /// Lists the events performed by a user. /// /// API docs: https://developer.github.com/v3/activity/events/#list-events-performed-by-a-user - Stream listEventsPerformedByUser(String username, {int pages}) { - return new PaginationHelper(_github).objects( - "GET", "/users/${username}/events", Event.fromJSON, pages: pages) - as Stream; + Stream listEventsPerformedByUser(String username, {int? pages}) { + return PaginationHelper(github).objects( + 'GET', '/users/$username/events', Event.fromJson, + pages: pages); } /// Lists the public events performed by a user. /// /// API docs: https://developer.github.com/v3/activity/events/#list-public-events-performed-by-a-user - Stream listPublicEventsPerformedByUser(String username, {int pages}) { - return new PaginationHelper(_github).objects( - "GET", "/users/${username}/events/public", Event.fromJSON, - pages: pages) as Stream; + Stream listPublicEventsPerformedByUser(String username, {int? pages}) { + return PaginationHelper(github).objects( + 'GET', '/users/$username/events/public', Event.fromJson, + pages: pages); } /// Returns an [EventPoller] for the user's organization dashboard. /// /// API docs: https://developer.github.com/v3/activity/events/#list-events-for-an-organization EventPoller pollUserEventsForOrganization(String user, String organization) => - new EventPoller(_github, "/users/${user}/events/orgs/${organization}"); + EventPoller(github, '/users/$user/events/orgs/$organization'); // TODO: Implement listFeeds: https://developer.github.com/v3/activity/feeds/#list-feeds @@ -124,36 +126,34 @@ class ActivityService extends Service { /// /// API docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications Stream listNotifications( - {bool all: false, bool participating: false}) { - return new PaginationHelper(_github).objects( - "GET", '/notifications', Notification.fromJSON, - params: {"all": all, "participating": participating}) - as Stream; + {bool all = false, bool participating = false}) { + return PaginationHelper(github).objects( + 'GET', '/notifications', Notification.fromJson, + params: {'all': all, 'participating': participating}); } /// Lists all notifications for a given repository. /// /// API docs: https://developer.github.com/v3/activity/notifications/#list-your-notifications-in-a-repository Stream listRepositoryNotifications(RepositorySlug repository, - {bool all: false, bool participating: false}) { - return new PaginationHelper(_github).objects( - "GET", - '/repos/${repository.fullName}/notifications', - Notification.fromJSON, - params: {"all": all, "participating": participating}) - as Stream; + {bool all = false, bool participating = false}) { + return PaginationHelper(github).objects('GET', + '/repos/${repository.fullName}/notifications', Notification.fromJson, + params: {'all': all, 'participating': participating}); } /// Marks all notifications up to [lastRead] as read. /// /// API docs: https://developer.github.com/v3/activity/notifications/#mark-as-read - Future markNotificationsRead({DateTime lastRead}) { - var data = {}; + Future markNotificationsRead({DateTime? lastRead}) { + final data = {}; - if (lastRead != null) data["last_read_at"] = lastRead.toIso8601String(); + if (lastRead != null) { + data['last_read_at'] = lastRead.toIso8601String(); + } - return _github - .request("PUT", "/notifications", body: JSON.encode(data)) + return github + .request('PUT', '/notifications', body: GitHubJson.encode(data)) .then((response) { return response.statusCode == 205; }); @@ -163,15 +163,19 @@ class ActivityService extends Service { /// read. /// /// API docs:https://developer.github.com/v3/activity/notifications/#mark-notifications-as-read-in-a-repository - Future markRepositoryNotificationsRead(RepositorySlug slug, - {DateTime lastRead}) { - var data = {}; - - if (lastRead != null) data["last_read_at"] = lastRead.toIso8601String(); + Future markRepositoryNotificationsRead( + RepositorySlug slug, { + DateTime? lastRead, + }) { + final data = {}; + + if (lastRead != null) { + data['last_read_at'] = lastRead.toIso8601String(); + } - return _github - .request("PUT", "/repos/${slug.fullName}/notifications", - body: JSON.encode(data)) + return github + .request('PUT', '/repos/${slug.fullName}/notifications', + body: GitHubJson.encode(data)) .then((response) { return response.statusCode == 205; }); @@ -180,13 +184,21 @@ class ActivityService extends Service { /// Fetches the specified notification thread. /// /// API docs: https://developer.github.com/v3/activity/notifications/#view-a-single-thread - Future getThread(String threadId) { - return _github.getJSON("/notification/threads/${threadId}", - statusCode: StatusCodes.OK, - convert: Notification.fromJSON) as Future; + Future getThread(String threadId) => + github.getJSON('/notification/threads/$threadId', + statusCode: StatusCodes.OK, convert: Notification.fromJson); + + /// Mark the specified notification thread as read. + /// + /// API docs: https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read + Future markThreadRead(String threadId) { + return github + .request('PATCH', '/notifications/threads/$threadId') + .then((response) { + return response.statusCode == StatusCodes.RESET_CONTENT; + }); } - // TODO: Implement markThreadRead: https://developer.github.com/v3/activity/notifications/#mark-a-thread-as-read // TODO: Implement getThreadSubscription: https://developer.github.com/v3/activity/notifications/#get-a-thread-subscription // TODO: Implement setThreadSubscription: https://developer.github.com/v3/activity/notifications/#set-a-thread-subscription // TODO: Implement deleteThreadSubscription: https://developer.github.com/v3/activity/notifications/#delete-a-thread-subscription @@ -194,35 +206,36 @@ class ActivityService extends Service { /// Lists people who have starred the specified repo. /// /// API docs: https://developer.github.com/v3/activity/starring/#list-stargazers - Stream listStargazers(RepositorySlug slug) { - return new PaginationHelper(_github) - .objects("GET", "/repos/${slug.fullName}/stargazers", User.fromJSON) - as Stream; + Stream listStargazers(RepositorySlug slug, {int perPage = 30}) { + return PaginationHelper(github).objects( + 'GET', '/repos/${slug.fullName}/stargazers', User.fromJson, + params: {'per_page': perPage}); } /// Lists all the repos starred by a user. /// /// API docs: https://developer.github.com/v3/activity/starring/#list-repositories-being-starred - Stream listStarredByUser(String user) { - return new PaginationHelper(_github) - .objects("GET", "/users/${user}/starred", Repository.fromJSON) - as Stream; + Stream listStarredByUser(String user, {int perPage = 30}) { + return PaginationHelper(github).objects( + 'GET', '/users/$user/starred', Repository.fromJson, + params: {'per_page': perPage}); } /// Lists all the repos by the current user. /// /// API docs: https://developer.github.com/v3/activity/starring/#list-repositories-being-starred - Stream listStarred() { - return new PaginationHelper(_github).objects( - "GET", "/user/starred", Repository.fromJSON) as Stream; + Stream listStarred({int perPage = 30}) { + return PaginationHelper(github).objects( + 'GET', '/user/starred', Repository.fromJson, + params: {'per_page': perPage}); } /// Checks if the currently authenticated user has starred the specified repository. /// /// API docs: https://developer.github.com/v3/activity/starring/#check-if-you-are-starring-a-repository Future isStarred(RepositorySlug slug) { - return _github - .request("GET", "/user/starred/${slug.fullName}") + return github + .request('GET', '/user/starred/${slug.fullName}') .then((response) { return response.statusCode == 204; }); @@ -232,8 +245,8 @@ class ActivityService extends Service { /// /// API docs: https://developer.github.com/v3/activity/starring/#star-a-repository Future star(RepositorySlug slug) { - return _github.request("PUT", "/user/starred/${slug.fullName}", - headers: {"Content-Length": "0"}).then((response) { + return github.request('PUT', '/user/starred/${slug.fullName}', + headers: {'Content-Length': '0'}).then((response) { return null; }); } @@ -242,8 +255,8 @@ class ActivityService extends Service { /// /// API docs: https://developer.github.com/v3/activity/starring/#unstar-a-repository Future unstar(RepositorySlug slug) { - return _github.request("DELETE", "/user/starred/${slug.fullName}", - headers: {"Content-Length": "0"}).then((response) { + return github.request('DELETE', '/user/starred/${slug.fullName}', + headers: {'Content-Length': '0'}).then((response) { return null; }); } @@ -252,56 +265,59 @@ class ActivityService extends Service { /// /// API docs: https://developer.github.com/v3/activity/watching/#list-watchers Stream listWatchers(RepositorySlug slug) { - return new PaginationHelper(_github) - .objects("GET", "/repos/${slug.fullName}/subscribers", User.fromJSON); + return PaginationHelper(github) + .objects('GET', '/repos/${slug.fullName}/subscribers', User.fromJson); } /// Lists the repositories the specified user is watching. /// /// API docs: https://developer.github.com/v3/activity/watching/#list-repositories-being-watched Stream listWatchedByUser(String user) { - return new PaginationHelper(_github) - .objects("GET", '/users/${user}/subscriptions', Repository.fromJSON); + return PaginationHelper(github) + .objects('GET', '/users/$user/subscriptions', Repository.fromJson); } /// Lists the repositories the current user is watching. /// /// API docs: https://developer.github.com/v3/activity/watching/#list-repositories-being-watched Stream listWatched() { - return new PaginationHelper(_github) - .objects("GET", '/user/subscriptions', Repository.fromJSON); + return PaginationHelper(github) + .objects('GET', '/user/subscriptions', Repository.fromJson); } /// Fetches repository subscription information. /// /// API docs: https://developer.github.com/v3/activity/watching/#get-a-repository-subscription Future getRepositorySubscription( - RepositorySlug slug) { - return _github.getJSON("/repos/${slug.fullName}/subscription", - statusCode: StatusCodes.OK, - convert: RepositorySubscription.fromJSON) - as Future; - } + RepositorySlug slug) => + github.getJSON('/repos/${slug.fullName}/subscription', + statusCode: StatusCodes.OK, convert: RepositorySubscription.fromJson); /// Sets the Repository Subscription Status /// /// API docs: https://developer.github.com/v3/activity/watching/#set-a-repository-subscription - Future setRepositorySubscription(RepositorySlug slug, - {bool subscribed, bool ignored}) { - var map = createNonNullMap({"subscribed": subscribed, "ignored": ignored}); - - return _github.postJSON("/repos/${slug.fullName}/subscription", - statusCode: StatusCodes.OK, - convert: RepositorySubscription.fromJSON, - body: map) as Future; + Future setRepositorySubscription( + RepositorySlug slug, { + bool? subscribed, + bool? ignored, + }) { + final map = + createNonNullMap({'subscribed': subscribed!, 'ignored': ignored!}); + + return github.putJSON( + '/repos/${slug.fullName}/subscription', + statusCode: StatusCodes.OK, + convert: RepositorySubscription.fromJson, + body: GitHubJson.encode(map), + ); } /// Deletes a Repository Subscription /// /// API docs: https://developer.github.com/v3/activity/watching/#delete-a-repository-subscription Future deleteRepositorySubscription(RepositorySlug slug) { - return _github.request("DELETE", "/repos/${slug.fullName}/subscription", - headers: {"Content-Length": "0"}).then((response) { + return github.request('DELETE', '/repos/${slug.fullName}/subscription', + headers: {'Content-Length': '0'}).then((response) { return null; }); } @@ -310,28 +326,28 @@ class ActivityService extends Service { class EventPoller { final GitHub github; final String path; - final List handledEvents = []; + final List handledEvents = []; - Timer _timer; - StreamController _controller; + Timer? _timer; + StreamController? _controller; - String _lastFetched; + String? _lastFetched; EventPoller(this.github, this.path); - Stream start({bool onlyNew: false, int interval, DateTime after}) { + Stream start({bool onlyNew = false, int? interval, DateTime? after}) { if (_timer != null) { - throw new Exception("Polling already started."); + throw Exception('Polling already started.'); } - if (after != null) after = after.toUtc(); + if (after != null) { + after = after.toUtc(); + } - _controller = new StreamController(); + _controller = StreamController(); void handleEvent(http.Response response) { - if (interval == null) { - interval = int.parse(response.headers['x-poll-interval']); - } + interval ??= int.parse(response.headers['x-poll-interval']!); if (response.statusCode == 304) { return; @@ -339,13 +355,15 @@ class EventPoller { _lastFetched = response.headers['ETag']; - var json = JSON.decode(response.body) as List>; + final json = List>.from(jsonDecode(response.body)); if (!(onlyNew && _timer == null)) { - for (var item in json) { - var event = Event.fromJSON(item); + for (final item in json) { + final event = Event.fromJson(item); - if (after == null ? false : event.createdAt.toUtc().isBefore(after)) { + if (after == null + ? false + : event.createdAt!.toUtc().isBefore(after)) { continue; } @@ -355,41 +373,39 @@ class EventPoller { handledEvents.add(event.id); - _controller.add(event); + _controller!.add(event); } } - if (_timer == null) { - _timer = new Timer.periodic(new Duration(seconds: interval), (timer) { - var headers = {}; + _timer ??= Timer.periodic(Duration(seconds: interval!), (timer) { + final headers = {}; - if (_lastFetched != null) { - headers['If-None-Match'] = _lastFetched; - } + if (_lastFetched != null) { + headers['If-None-Match'] = _lastFetched ?? ''; + } - github.request("GET", path, headers: headers).then(handleEvent); - }); - } + github.request('GET', path, headers: headers).then(handleEvent); + }); } - var headers = {}; + final headers = {}; if (_lastFetched != null) { - headers['If-None-Match'] = _lastFetched; + headers['If-None-Match'] = _lastFetched ?? ''; } - github.request("GET", path, headers: headers).then(handleEvent); + github.request('GET', path, headers: headers).then(handleEvent); - return _controller.stream; + return _controller!.stream; } Future stop() { if (_timer == null) { - throw new Exception("Polling not started."); + throw Exception('Polling not started.'); } - _timer.cancel(); - var future = _controller.close(); + _timer!.cancel(); + final future = _controller!.close(); _timer = null; _controller = null; diff --git a/lib/src/common/authorizations_service.dart b/lib/src/common/authorizations_service.dart index a46a6720..68b80c25 100644 --- a/lib/src/common/authorizations_service.dart +++ b/lib/src/common/authorizations_service.dart @@ -1,4 +1,6 @@ -part of github.common; +import 'dart:async'; + +import 'package:github/src/common.dart'; /// The [AuthorizationsService] handles communication with authorizations related methods /// of the GitHub API. @@ -8,25 +10,22 @@ part of github.common; /// /// API docs: https://developer.github.com/v3/oauth_authorizations/ class AuthorizationsService extends Service { - AuthorizationsService(GitHub github) : super(github); + AuthorizationsService(super.github); /// Lists all authorizations. /// /// API docs: https://developer.github.com/v3/oauth_authorizations/#list-your-authorizations Stream listAuthorizations() { - return new PaginationHelper(_github) - .objects("GET", "/authorizations", Authorization.fromJSON) - as Stream; + return PaginationHelper(github) + .objects('GET', '/authorizations', Authorization.fromJson); } /// Fetches an authorization specified by [id]. /// /// API docs: https://developer.github.com/v3/oauth_authorizations/#get-a-single-authorization - Future getAuthorization(int id) { - return _github.getJSON("/authorizations/${id}", - statusCode: 200, - convert: Authorization.fromJSON) as Future; - } + Future getAuthorization(int id) => + github.getJSON('/authorizations/$id', + statusCode: 200, convert: Authorization.fromJson); // TODO: Implement remaining API methods of authorizations: // See https://developer.github.com/v3/oauth_authorizations/ diff --git a/lib/src/common/blog_service.dart b/lib/src/common/blog_service.dart deleted file mode 100644 index a8477c46..00000000 --- a/lib/src/common/blog_service.dart +++ /dev/null @@ -1,24 +0,0 @@ -part of github.common; - -/// The [BlogService] provides methods to retrieve blog posts from GitHub. -class BlogService extends Service { - BlogService(GitHub github) : super(github); - - /// Returns a stream of blog posts for the specified [url]. - Stream listPosts([String url = "https://github.com/blog.atom"]) { - var controller = new StreamController(); - _github.client.get(url).then((response) { - var document = xml.parse(response.body); - - var entries = document.rootElement.findElements("entry"); - - for (var entry in entries) { - controller.add(BlogPost.fromXML(entry)); - } - - controller.close(); - }); - - return controller.stream; - } -} diff --git a/lib/src/common/checks_service.dart b/lib/src/common/checks_service.dart new file mode 100644 index 00000000..f3085197 --- /dev/null +++ b/lib/src/common/checks_service.dart @@ -0,0 +1,351 @@ +import 'dart:convert'; + +import 'package:github/github.dart'; + +const _previewHeader = 'application/vnd.github.antiope-preview+json'; + +/// Contains methods to interact with the Checks API. +/// +/// API docs: https://developer.github.com/v3/checks/ +class ChecksService extends Service { + /// Methods to interact with Check Runs. + /// + /// API docs: https://developer.github.com/v3/checks/runs/ + final CheckRunsService checkRuns; + + /// Methods to interact with Check Suites. + /// + /// API docs: https://developer.github.com/v3/checks/suites/ + final CheckSuitesService checkSuites; + + ChecksService(super.github) + : checkRuns = CheckRunsService._(github), + checkSuites = CheckSuitesService._(github); +} + +class CheckRunsService extends Service { + CheckRunsService._(super.github); + + /// Creates a new check run for a specific commit in a repository. + /// Your GitHub App must have the `checks:write` permission to create check runs. + /// * [name]: The name of the check. For example, "code-coverage". + /// * [headSha]: The SHA of the commit. + /// * [detailsUrl]: The URL of the integrator's site that has the full details of the check. + /// * [externalId]: A reference for the run on the integrator's system. + /// * [status]: The current status. Can be one of queued, in_progress, or completed. Default: queued. + /// * [startedAt]: The time that the check run began. + /// * [conclusion]: **Required if you provide completed_at or a status of completed.** The final conclusion of the check. + /// When the conclusion is action_required, additional details should be provided on the site specified by details_url. **Note**: Providing conclusion will automatically set the status parameter to completed. + /// * [completedAt]: The time the check completed. + /// * [output]: Check runs can accept a variety of data in the output object, including a title and summary and can optionally provide descriptive details about the run. + /// * [actions]: Displays a button on GitHub that can be clicked to alert your app to do additional tasks. + /// For example, a code linting app can display a button that automatically fixes detected errors. + /// The button created in this object is displayed after the check run completes. + /// When a user clicks the button, GitHub sends the check_run.requested_action webhook to your app. + /// A maximum of three actions are accepted. + /// + /// API docs: https://developer.github.com/v3/checks/runs/#create-a-check-run + Future createCheckRun( + RepositorySlug slug, { + required String name, + required String headSha, + String? detailsUrl, + String? externalId, + CheckRunStatus status = CheckRunStatus.queued, + DateTime? startedAt, + CheckRunConclusion? conclusion, + DateTime? completedAt, + CheckRunOutput? output, + List? actions, + }) async { + assert(conclusion != null || + (completedAt == null && status != CheckRunStatus.completed)); + assert(actions == null || actions.length <= 3); + return github.postJSON, CheckRun>( + '/repos/${slug.fullName}/check-runs', + statusCode: StatusCodes.CREATED, + preview: _previewHeader, + body: jsonEncode(createNonNullMap({ + 'name': name, + 'head_sha': headSha, + 'details_url': detailsUrl, + 'external_id': externalId, + 'status': status, + 'started_at': dateToGitHubIso8601(startedAt), + 'conclusion': conclusion, + 'completed_at': dateToGitHubIso8601(completedAt), + 'output': output, + 'actions': actions, + })), + convert: CheckRun.fromJson, + ); + } + + /// Updates a check run for a specific commit in a repository. + /// Your GitHub App must have the `checks:write` permission to edit check runs. + /// + /// * [name]: The name of the check. For example, "code-coverage". + /// * [detailsUrl]: The URL of the integrator's site that has the full details of the check. + /// * [externalId]: A reference for the run on the integrator's system. + /// * [status]: The current status. Can be one of queued, in_progress, or completed. Default: queued. + /// * [startedAt]: The time that the check run began. + /// * [conclusion]: **Required if you provide completed_at or a status of completed.** The final conclusion of the check. + /// When the conclusion is action_required, additional details should be provided on the site specified by details_url. **Note**: Providing conclusion will automatically set the status parameter to completed. + /// * [completedAt]: The time the check completed. + /// * [output]: Check runs can accept a variety of data in the output object, including a title and summary and can optionally provide descriptive details about the run. + /// * [actions]: Possible further actions the integrator can perform, which a user may trigger. Each action includes a label, identifier and description. A maximum of three actions are accepted. + /// + /// API docs: https://developer.github.com/v3/checks/runs/#update-a-check-run + Future updateCheckRun( + RepositorySlug slug, + CheckRun checkRunToUpdate, { + String? name, + String? detailsUrl, + String? externalId, + DateTime? startedAt, + CheckRunStatus status = CheckRunStatus.queued, + CheckRunConclusion? conclusion, + DateTime? completedAt, + CheckRunOutput? output, + List? actions, + }) async { + assert(conclusion != null || + (completedAt == null && status != CheckRunStatus.completed)); + assert(actions == null || actions.length <= 3); + return github.requestJson, CheckRun>( + 'PATCH', + '/repos/${slug.fullName}/check-runs/${checkRunToUpdate.id}', + statusCode: StatusCodes.OK, + preview: _previewHeader, + body: jsonEncode(createNonNullMap({ + 'name': name, + 'details_url': detailsUrl, + 'external_id': externalId, + 'started_at': dateToGitHubIso8601(startedAt), + 'status': status, + 'conclusion': conclusion, + 'completed_at': dateToGitHubIso8601(completedAt), + 'output': output, + 'actions': actions, + })), + convert: CheckRun.fromJson, + ); + } + + /// Lists check runs for a commit [ref]. + /// The `[ref]` can be a SHA, branch name, or a tag name. + /// GitHub Apps must have the `checks:read` permission on a private repository or pull access to a public repository to get check runs. + /// OAuth Apps and authenticated users must have the `repo` scope to get check runs in a private repository. + /// * [checkName]: returns check runs with the specified name. + /// * [status]: returns check runs with the specified status. + /// * [filter]: filters check runs by their completed_at timestamp. Can be one of latest (returning the most recent check runs) or all. Default: latest. + /// + /// API docs: https://developer.github.com/v3/checks/runs/#list-check-runs-for-a-specific-ref + Stream listCheckRunsForRef( + RepositorySlug slug, { + required String ref, + String? checkName, + CheckRunStatus? status, + CheckRunFilter? filter, + }) { + ArgumentError.checkNotNull(ref); + return PaginationHelper(github).objects, CheckRun>( + 'GET', + 'repos/$slug/commits/$ref/check-runs', + CheckRun.fromJson, + statusCode: StatusCodes.OK, + preview: _previewHeader, + params: createNonNullMap({ + 'check_name': checkName, + 'filter': filter, + 'status': status, + }), + arrayKey: 'check_runs', + ); + } + + /// Lists check runs for a check suite using its [checkSuiteId]. + /// GitHub Apps must have the `checks:read` permission on a private repository or pull access to a public repository to get check runs. + /// OAuth Apps and authenticated users must have the `repo` scope to get check runs in a private repository. + /// * [checkName]: returns check runs with the specified name. + /// * [status]: returns check runs with the specified status. + /// * [filter]: filters check runs by their completed_at timestamp. Can be one of latest (returning the most recent check runs) or all. Default: latest. + /// + /// API docs: https://developer.github.com/v3/checks/runs/#list-check-runs-in-a-check-suite + Stream listCheckRunsInSuite( + RepositorySlug slug, { + required int checkSuiteId, + String? checkName, + CheckRunStatus? status, + CheckRunFilter? filter, + }) { + ArgumentError.checkNotNull(checkSuiteId); + return PaginationHelper(github).objects, CheckRun>( + 'GET', + 'repos/$slug/check-suites/$checkSuiteId/check-runs', + CheckRun.fromJson, + statusCode: StatusCodes.OK, + preview: _previewHeader, + params: createNonNullMap({ + 'check_name': checkName, + 'status': status, + 'filter': filter, + }), + arrayKey: 'check_runs', + ); + } + + /// Gets a single check run using its [checkRunId]. + /// GitHub Apps must have the `checks:read` permission on a private repository or pull access to a public repository to get check runs. + /// OAuth Apps and authenticated users must have the `repo` scope to get check runs in a private repository. + /// + /// API docs: https://developer.github.com/v3/checks/runs/#get-a-single-check-run + Future getCheckRun( + RepositorySlug slug, { + required int checkRunId, + }) { + ArgumentError.checkNotNull(checkRunId); + return github.getJSON, CheckRun>( + 'repos/${slug.fullName}/check-runs/$checkRunId', + preview: _previewHeader, + statusCode: StatusCodes.OK, + convert: CheckRun.fromJson, + ); + } + + /// Lists annotations for a check run. + /// GitHub Apps must have the `checks:read` permission on a private repository or pull access to a public repository to get annotations for a check run. + /// OAuth Apps and authenticated users must have the `repo` scope to get annotations for a check run in a private repository. + /// + /// API docs: https://developer.github.com/v3/checks/runs/#list-annotations-for-a-check-run + Stream listAnnotationsInCheckRun( + RepositorySlug slug, { + required CheckRun checkRun, + }) { + return PaginationHelper(github) + .objects, CheckRunAnnotation>( + 'GET', + '/repos/${slug.fullName}/check-runs/${checkRun.id}/annotations', + CheckRunAnnotation.fromJSON, + statusCode: StatusCodes.OK, + preview: _previewHeader, + ); + } +} + +class CheckSuitesService extends Service { + CheckSuitesService._(super.github); + + /// Gets a single check suite using its `id`. + /// GitHub Apps must have the `checks:read` permission on a private repository or pull access to a public repository to get check suites. + /// OAuth Apps and authenticated users must have the `repo` scope to get check suites in a private repository. + /// + /// API docs: https://developer.github.com/v3/checks/suites/#get-a-single-check-suite + Future getCheckSuite( + RepositorySlug slug, { + required int checkSuiteId, + }) async { + ArgumentError.checkNotNull(checkSuiteId); + return github.requestJson( + 'GET', + 'repos/$slug/check-suites/$checkSuiteId', + convert: CheckSuite.fromJson, + preview: _previewHeader, + statusCode: StatusCodes.OK, + ); + } + + /// Lists check suites for a commit `[ref]`. + /// The `[ref]` can be a SHA, branch name, or a tag name. + /// GitHub Apps must have the `checks:read` permission on a private repository or pull access to a public repository to list check suites. + /// OAuth Apps and authenticated users must have the `repo` scope to get check suites in a private repository. + /// * [appId]: Filters check suites by GitHub App id. + /// * [checkName]: Filters checks suites by the name of the check run. + /// + /// API docs: https://developer.github.com/v3/checks/suites/#list-check-suites-for-a-specific-ref + Stream listCheckSuitesForRef( + RepositorySlug slug, { + required String ref, + int? appId, + String? checkName, + }) { + ArgumentError.checkNotNull(ref); + return PaginationHelper(github).objects, CheckSuite>( + 'GET', + 'repos/$slug/commits/$ref/check-suites', + CheckSuite.fromJson, + preview: _previewHeader, + params: createNonNullMap({ + 'app_id': appId, + 'check_name': checkName, + }), + statusCode: StatusCodes.OK, + arrayKey: 'check_suites', + ); + } + + /// Changes the default automatic flow when creating check suites. + /// By default, the CheckSuiteEvent is automatically created each time code is pushed to a repository. + /// When you disable the automatic creation of check suites, you can manually [Create a check suite](https://developer.github.com/v3/checks/suites/#create-a-check-suite). + /// You must have admin permissions in the repository to set preferences for check suites. + /// * [autoTriggerChecks]: Enables or disables automatic creation of CheckSuite events upon pushes to the repository. Enabled by default. + /// + /// API docs: https://developer.github.com/v3/checks/suites/#update-repository-preferences-for-check-suites + Future> updatePreferencesForCheckSuites( + RepositorySlug slug, { + required List autoTriggerChecks, + }) { + ArgumentError.checkNotNull(autoTriggerChecks); + return github.requestJson, List>( + 'PATCH', + 'repos/$slug/check-suites/preferences', + statusCode: StatusCodes.OK, + preview: _previewHeader, + body: {'auto_trigger_checks': autoTriggerChecks}, + convert: (input) => (input['preferences']['auto_trigger_checks'] as List) + .cast>() + .map(AutoTriggerChecks.fromJson) + .toList(), + ); + } + + /// By default, check suites are automatically created when you create a [check run](https://developer.github.com/v3/checks/runs/). + /// You only need to use this endpoint for manually creating check suites when you've disabled automatic creation using "[Set preferences for check suites on a repository](https://developer.github.com/v3/checks/suites/#set-preferences-for-check-suites-on-a-repository)". + /// Your GitHub App must have the `checks:write` permission to create check suites. + /// * [headSha]: The sha of the head commit. + /// + /// API docs: https://developer.github.com/v3/checks/suites/#create-a-check-suite + Future createCheckSuite( + RepositorySlug slug, { + required String headSha, + }) { + ArgumentError.checkNotNull(headSha); + return github.requestJson, CheckSuite>( + 'POST', + 'repos/$slug/check-suites', + statusCode: StatusCodes.CREATED, + preview: _previewHeader, + params: {'head_sha': headSha}, + convert: CheckSuite.fromJson, + ); + } + + /// Triggers GitHub to rerequest an existing check suite, without pushing new code to a repository. + /// This endpoint will trigger the [`check_suite` webhook](https://developer.github.com/v3/activity/events/types/#checksuiteevent) event with the action rerequested. + /// When a check suite is `rerequested`, its `status` is reset to `queued` and the `conclusion` is cleared. + /// To rerequest a check suite, your GitHub App must have the `checks:read` permission on a private repository or pull access to a public repository. + /// + /// API docs: https://developer.github.com/v3/checks/suites/#rerequest-check-suite + Future reRequestCheckSuite( + RepositorySlug slug, { + required int checkSuiteId, + }) { + ArgumentError.checkNotNull(checkSuiteId); + return github.request( + 'POST', + 'repos/$slug/check-suites/$checkSuiteId/rerequest', + statusCode: StatusCodes.CREATED, + preview: _previewHeader, + ); + } +} diff --git a/lib/src/common/explore_service.dart b/lib/src/common/explore_service.dart deleted file mode 100644 index fc015b5d..00000000 --- a/lib/src/common/explore_service.dart +++ /dev/null @@ -1,143 +0,0 @@ -part of github.common; - -/// The [ExploreService] provides methods for exploring GitHub. -/// -/// API docs: https://github.com/explore -class ExploreService extends Service { - ExploreService(GitHub github) : super(github); - - Stream listTrendingRepositories( - {String language, String since: "daily"}) { - var url = "https://github.com/trending"; - - if (language != null) url += "?l=${language}"; - - if (since != null) - url += language == null ? "?since=${since}" : "&since=${since}"; - - var controller = new StreamController(); - - _github.client.get(url).then((response) { - var doc = html_parser.parse(response.body); - var items = doc.querySelectorAll( - "li.repo-leaderboard-list-item.leaderboard-list-item"); - - for (var item in items) { - var repo = new TrendingRepository(); - repo.rank = item.querySelector("a.leaderboard-list-rank").text; - repo.titleObject = - item.querySelector("h2.repo-leaderboard-title").querySelector("a"); - var desc = item.querySelector("p.repo-leaderboard-description"); - - if (desc == null) { - repo.description = "No Description"; - } else { - repo.description = desc.text; - } - - controller.add(repo); - } - - controller.close(); - }); - - return controller.stream; - } - - Future getShowcase(ShowcaseInfo info) { - var completer = new Completer(); - - _github.client.get(info.url).then((response) { - var doc = html_parser.parse(response.body); - var showcase = new Showcase(); - - var title = doc.querySelector(".collection-header").text; - var lastUpdated = parseDateTime(doc - .querySelector(".meta-info.last-updated") - .querySelector("time") - .attributes['datetime']); - var page = doc.querySelector(".collection-page"); - - var description = page.querySelector(".collection-description"); - - // TODO: This is most likely wrong - showcase.description = description; - showcase.lastUpdated = lastUpdated; - showcase.title = title; - showcase.items = []; - - var repos = page.querySelectorAll(".collection-repo"); - - for (var repo in repos) { - var repoTitle = repo.querySelector(".collection-repo-title"); - var path = repoTitle.querySelector("a").attributes['href']; - var url = "https://githb.com${path}"; - var name = path.substring(1); - - var item = new ShowcaseItem(); - - item.name = name; - - item.url = url; - - showcase.items.add(item); - } - - completer.complete(showcase); - }); - - return completer.future; - } - - Stream listShowcases() { - var controller = new StreamController(); - - Function handleResponse; - - handleResponse = (response) { - var doc = html_parser.parse(response.body); - - var cards = doc.querySelectorAll(".collection-card"); - - for (var card in cards) { - var title = card.querySelector(".collection-card-title").text; - var description = card.querySelector(".collection-card-body").text; - var img = card.querySelector(".collection-card-image"); - var url = "https://github.com" + img.attributes['href']; - - var showcase = new ShowcaseInfo(); - - showcase - ..title = title - ..description = description - ..url = url; - - controller.add(showcase); - } - - var pag = doc.querySelector(".pagination"); - - var links = pag.querySelectorAll("a"); - - bool didFetchMore = false; - - for (var link in links) { - if (link.text.contains("Next")) { - didFetchMore = true; - - var client = new http.Client(); - - client.get(link.attributes['href']).then(handleResponse); - } - } - - if (!didFetchMore) { - controller.close(); - } - }; - - _github.client.get("https://github.com/showcases").then(handleResponse); - - return controller.stream; - } -} diff --git a/lib/src/common/gists_service.dart b/lib/src/common/gists_service.dart index 98fce6e9..05ac62bd 100644 --- a/lib/src/common/gists_service.dart +++ b/lib/src/common/gists_service.dart @@ -1,18 +1,21 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; + +import 'package:github/src/common.dart'; /// The [GistsService] handles communication with gist /// methods of the GitHub API. /// /// API docs: https://developer.github.com/v3/gists/ class GistsService extends Service { - GistsService(GitHub github) : super(github); + GistsService(super.github); /// lists gists for a user. /// /// API docs: https://developer.github.com/v3/gists/#list-gists Stream listUserGists(String username) { - return new PaginationHelper(_github).objects( - "GET", "/users/${username}/gists", Gist.fromJSON) as Stream; + return PaginationHelper(github) + .objects('GET', '/users/$username/gists', Gist.fromJson); } /// Fetches the gists for the currently authenticated user. @@ -20,66 +23,68 @@ class GistsService extends Service { /// /// API docs: https://developer.github.com/v3/gists/#list-gists Stream listCurrentUserGists() { - return new PaginationHelper(_github).objects("GET", "/gists", Gist.fromJSON) - as Stream; + return PaginationHelper(github).objects('GET', '/gists', Gist.fromJson); } /// Fetches the currently authenticated user's public gists. /// /// API docs: https://developer.github.com/v3/gists/#list-gists Stream listCurrentUserPublicGists() { - return new PaginationHelper(_github) - .objects("GET", "/gists/public", Gist.fromJSON) as Stream; + return PaginationHelper(github) + .objects('GET', '/gists/public', Gist.fromJson); } /// Fetches the currently authenticated user's starred gists. /// /// API docs: https://developer.github.com/v3/gists/#list-gists Stream listCurrentUserStarredGists() { - return new PaginationHelper(_github) - .objects("GET", "/gists/starred", Gist.fromJSON) as Stream; + return PaginationHelper(github) + .objects('GET', '/gists/starred', Gist.fromJson); } /// Fetches a Gist by the specified [id]. /// /// API docs: https://developer.github.com/v3/gists/#get-a-single-gist - Future getGist(String id) { - return _github.getJSON("/gists/${id}", - statusCode: StatusCodes.OK, convert: Gist.fromJSON) as Future; - } + Future getGist(String id) => github.getJSON('/gists/$id', + statusCode: StatusCodes.OK, convert: Gist.fromJson); /// Creates a Gist /// /// API docs: https://developer.github.com/v3/gists/#create-a-gist - Future createGist(Map files, - {String description, bool public: false}) { - var map = {"files": {}}; + Future createGist( + Map files, { + String? description, + bool public = false, + }) { + final map = {'files': {}}; if (description != null) { - map["description"] = description; + map['description'] = description; } - map["public"] = public; + map['public'] = public; - var f = {}; + final f = {}; - for (var key in files.keys) { - f[key] = {"content": files[key]}; + for (final key in files.keys) { + f[key] = {'content': files[key]}; } - map["files"] = f; + map['files'] = f; - return _github.postJSON("/gists", - statusCode: 201, - body: JSON.encode(map), - convert: Gist.fromJSON) as Future; + return github.postJSON( + '/gists', + statusCode: 201, + body: GitHubJson.encode(map), + convert: Gist.fromJson, + ); } /// Deletes the specified Gist. /// /// API docs: https://developer.github.com/v3/gists/#delete-a-gist Future deleteGist(String id) { - return _github.request("DELETE", "/gists/${id}").then((response) { + return github.request('DELETE', '/gists/$id').then((response) { return response.statusCode == 204; }); } @@ -87,26 +92,31 @@ class GistsService extends Service { /// Edits a Gist. /// /// API docs: https://developer.github.com/v3/gists/#edit-a-gist - Future editGist(String id, - {String description, Map files}) { - var map = {}; + Future editGist( + String id, { + String? description, + Map? files, + }) { + final map = {}; if (description != null) { - map["description"] = description; + map['description'] = description; } if (files != null) { - var f = {}; - for (var key in files.keys) { - f[key] = files[key] == null ? null : {"content": files[key]}; + final f = {}; + for (final key in files.keys) { + f[key] = files[key] == null ? null : {'content': files[key]}; } - map["files"] = f; + map['files'] = f; } - return _github.postJSON("/gists/${id}", - statusCode: 200, - body: JSON.encode(map), - convert: Gist.fromJSON) as Future; + return github.postJSON( + '/gists/$id', + statusCode: 200, + body: GitHubJson.encode(map), + convert: Gist.fromJson, + ); } // TODO: Implement listGistCommits: https://developer.github.com/v3/gists/#list-gist-commits @@ -115,7 +125,7 @@ class GistsService extends Service { /// /// API docs: https://developer.github.com/v3/gists/#star-a-gist Future starGist(String id) { - return _github.request("POST", "/gists/${id}/star").then((response) { + return github.request('POST', '/gists/$id/star').then((response) { return response.statusCode == 204; }); } @@ -124,7 +134,7 @@ class GistsService extends Service { /// /// API docs: https://developer.github.com/v3/gists/#star-a-gist Future unstarGist(String id) { - return _github.request("DELETE", "/gists/${id}/star").then((response) { + return github.request('DELETE', '/gists/$id/star').then((response) { return response.statusCode == 204; }); } @@ -133,7 +143,7 @@ class GistsService extends Service { /// /// API docs: https://developer.github.com/v3/gists/#check-if-a-gist-is-starred Future isGistStarred(String id) { - return _github.request("GET", "/gists/${id}/star").then((response) { + return github.request('GET', '/gists/$id/star').then((response) { return response.statusCode == 204; }); } @@ -142,10 +152,10 @@ class GistsService extends Service { /// /// API docs: https://developer.github.com/v3/gists/#check-if-a-gist-is-starred Future forkGist(String id) { - return _github - .request("POST", "/gists/${id}/forks", statusCode: 201) + return github + .request('POST', '/gists/$id/forks', statusCode: 201) .then((response) { - return Gist.fromJSON(JSON.decode(response.body) as Map); + return Gist.fromJson(jsonDecode(response.body) as Map); }); } @@ -155,9 +165,8 @@ class GistsService extends Service { /// /// API docs: https://developer.github.com/v3/gists/comments/#list-comments-on-a-gist Stream listComments(String gistId) { - return new PaginationHelper(_github) - .objects("GET", "/gists/${gistId}/comments", GistComment.fromJSON) - as Stream; + return PaginationHelper(github) + .objects('GET', '/gists/$gistId/comments', GistComment.fromJson); } // TODO: Implement getComment: https://developer.github.com/v3/gists/comments/#get-a-single-comment @@ -166,9 +175,8 @@ class GistsService extends Service { /// /// API docs: https://developer.github.com/v3/gists/comments/#create-a-comment Future createComment(String gistId, CreateGistComment request) { - return _github.postJSON("/gists/${gistId}/comments", - body: request.toJSON(), - convert: GistComment.fromJSON) as Future; + return github.postJSON('/gists/$gistId/comments', + body: GitHubJson.encode(request), convert: GistComment.fromJson); } // TODO: Implement editComment: https://developer.github.com/v3/gists/comments/#edit-a-comment diff --git a/lib/src/common/git_service.dart b/lib/src/common/git_service.dart index da42cd2c..338dbeba 100644 --- a/lib/src/common/git_service.dart +++ b/lib/src/common/git_service.dart @@ -1,48 +1,47 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; + +import 'package:github/src/common.dart'; /// The [GitService] handles communication with git related methods of the /// GitHub API. /// /// API docs: https://developer.github.com/v3/git/blobs/ class GitService extends Service { - GitService(GitHub github) : super(github); + const GitService(super.github); /// Fetches a blob from [slug] for a given [sha]. /// /// API docs: https://developer.github.com/v3/git/blobs/#get-a-blob - Future getBlob(RepositorySlug slug, String sha) { - return _github.getJSON('/repos/${slug.fullName}/git/blobs/${sha}', - convert: GitBlob.fromJSON, - statusCode: StatusCodes.OK) as Future; - } + Future getBlob(RepositorySlug slug, String? sha) => + github.getJSON('/repos/${slug.fullName}/git/blobs/$sha', + convert: GitBlob.fromJson, statusCode: StatusCodes.OK); /// Creates a blob with specified [blob] content. /// /// API docs: https://developer.github.com/v3/git/blobs/#create-a-blob Future createBlob(RepositorySlug slug, CreateGitBlob blob) { - return _github.postJSON('/repos/${slug.fullName}/git/blobs', - convert: GitBlob.fromJSON, + return github.postJSON('/repos/${slug.fullName}/git/blobs', + convert: GitBlob.fromJson, statusCode: StatusCodes.CREATED, - body: blob.toJSON()) as Future; + body: GitHubJson.encode(blob)); } /// Fetches a commit from [slug] for a given [sha]. /// /// API docs: https://developer.github.com/v3/git/commits/#get-a-commit - Future getCommit(RepositorySlug slug, String sha) { - return _github.getJSON('/repos/${slug.fullName}/git/commits/${sha}', - convert: GitCommit.fromJSON, - statusCode: StatusCodes.OK) as Future; - } + Future getCommit(RepositorySlug slug, String? sha) => + github.getJSON('/repos/${slug.fullName}/git/commits/$sha', + convert: GitCommit.fromJson, statusCode: StatusCodes.OK); /// Creates a new commit in a repository. /// /// API docs: https://developer.github.com/v3/git/commits/#create-a-commit Future createCommit(RepositorySlug slug, CreateGitCommit commit) { - return _github.postJSON('/repos/${slug.fullName}/git/commits', - convert: GitCommit.fromJSON, + return github.postJSON('/repos/${slug.fullName}/git/commits', + convert: GitCommit.fromJson, statusCode: StatusCodes.CREATED, - body: commit.toJSON()) as Future; + body: GitHubJson.encode(commit)); } /// Fetches a reference from a repository for the given [ref]. @@ -50,11 +49,9 @@ class GitService extends Service { /// Note: The [ref] in the URL must be formatted as "heads/branch", not just "branch". /// /// API docs: https://developer.github.com/v3/git/refs/#get-a-reference - Future getReference(RepositorySlug slug, String ref) { - return _github.getJSON('/repos/${slug.fullName}/git/refs/${ref}', - convert: GitReference.fromJSON, - statusCode: StatusCodes.OK) as Future; - } + Future getReference(RepositorySlug slug, String ref) => + github.getJSON('/repos/${slug.fullName}/git/refs/$ref', + convert: GitReference.fromJson, statusCode: StatusCodes.OK); /// Lists the references in a repository. /// @@ -63,14 +60,13 @@ class GitService extends Service { /// by specifying a [type], the most common being "heads" and "tags". /// /// API docs: https://developer.github.com/v3/git/refs/#get-all-references - Stream listReferences(RepositorySlug slug, {String type}) { - String path = '/repos/${slug.fullName}/git/refs'; + Stream listReferences(RepositorySlug slug, {String? type}) { + var path = '/repos/${slug.fullName}/git/refs'; if (type != null) { path += '/$type'; } - return new PaginationHelper(_github) - .objects('GET', path, GitReference.fromJSON) as Stream; + return PaginationHelper(github).objects('GET', path, GitReference.fromJson); } /// Creates a new reference in a repository. @@ -80,29 +76,32 @@ class GitService extends Service { /// /// API docs: https://developer.github.com/v3/git/refs/#create-a-reference Future createReference( - RepositorySlug slug, String ref, String sha) { - return _github.postJSON('/repos/${slug.fullName}/git/refs', - convert: GitReference.fromJSON, + RepositorySlug slug, String ref, String? sha) { + return github.postJSON('/repos/${slug.fullName}/git/refs', + convert: GitReference.fromJson, statusCode: StatusCodes.CREATED, - body: JSON.encode({'ref': ref, 'sha': sha})) as Future; + body: GitHubJson.encode({'ref': ref, 'sha': sha})); } /// Updates a reference in a repository. /// /// API docs: https://developer.github.com/v3/git/refs/#update-a-reference Future editReference( - RepositorySlug slug, String ref, String sha, - {bool force: false}) { - String body = JSON.encode({'sha': sha, 'force': force}); + RepositorySlug slug, + String ref, + String? sha, { + bool force = false, + }) { + final body = GitHubJson.encode({'sha': sha, 'force': force}); // Somehow the reference updates PATCH request needs a valid content-length. - var headers = {'content-length': body.length.toString()}; + final headers = {'content-length': body.length.toString()}; - return _github - .request('PATCH', '/repos/${slug.fullName}/git/refs/${ref}', + return github + .request('PATCH', '/repos/${slug.fullName}/git/refs/$ref', body: body, headers: headers) .then((response) { - return GitReference - .fromJSON(JSON.decode(response.body) as Map); + return GitReference.fromJson( + jsonDecode(response.body) as Map); }); } @@ -110,28 +109,26 @@ class GitService extends Service { /// /// API docs: https://developer.github.com/v3/git/refs/#delete-a-reference Future deleteReference(RepositorySlug slug, String ref) { - return _github - .request("DELETE", "/repos/${slug.fullName}/git/refs/${ref}") + return github + .request('DELETE', '/repos/${slug.fullName}/git/refs/$ref') .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } /// Fetches a tag from the repo given a SHA. /// /// API docs: https://developer.github.com/v3/git/tags/#get-a-tag - Future getTag(RepositorySlug slug, String sha) { - return _github.getJSON('/repos/${slug.fullName}/git/tags/${sha}', - convert: GitTag.fromJSON, statusCode: StatusCodes.OK) as Future; - } + Future getTag(RepositorySlug slug, String? sha) => + github.getJSON('/repos/${slug.fullName}/git/tags/$sha', + convert: GitTag.fromJson, statusCode: StatusCodes.OK); /// Creates a new tag in a repository. /// /// API docs: https://developer.github.com/v3/git/tags/#create-a-tag-object - Future createTag(RepositorySlug slug, CreateGitTag tag) { - return _github.postJSON('/repos/${slug.fullName}/git/tags', - convert: GitTag.fromJSON, - statusCode: StatusCodes.CREATED, - body: tag.toJSON()) as Future; - } + Future createTag(RepositorySlug slug, CreateGitTag tag) => + github.postJSON('/repos/${slug.fullName}/git/tags', + convert: GitTag.fromJson, + statusCode: StatusCodes.CREATED, + body: GitHubJson.encode(tag)); /// Fetches a tree from a repository for the given ref [sha]. /// @@ -139,25 +136,24 @@ class GitService extends Service { /// /// API docs: https://developer.github.com/v3/git/trees/#get-a-tree /// and https://developer.github.com/v3/git/trees/#get-a-tree-recursively - Future getTree(RepositorySlug slug, String sha, - {bool recursive: false}) { - var path = '/repos/${slug.fullName}/git/trees/${sha}'; + Future getTree(RepositorySlug slug, String? sha, + {bool recursive = false}) { + var path = '/repos/${slug.fullName}/git/trees/$sha'; if (recursive) { path += '?recursive=1'; } - return _github.getJSON(path, - convert: GitTree.fromJSON, - statusCode: StatusCodes.OK) as Future; + return github.getJSON(path, + convert: GitTree.fromJson, statusCode: StatusCodes.OK); } /// Creates a new tree in a repository. /// /// API docs: https://developer.github.com/v3/git/trees/#create-a-tree Future createTree(RepositorySlug slug, CreateGitTree tree) { - return _github.postJSON('/repos/${slug.fullName}/git/trees', - convert: GitTree.fromJSON, + return github.postJSON('/repos/${slug.fullName}/git/trees', + convert: GitTree.fromJson, statusCode: StatusCodes.CREATED, - body: tree.toJSON()) as Future; + body: GitHubJson.encode(tree)); } } diff --git a/lib/src/common/github.dart b/lib/src/common/github.dart index 37b00747..e6ba64cb 100644 --- a/lib/src/common/github.dart +++ b/lib/src/common/github.dart @@ -1,6 +1,10 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; -typedef http.Client ClientCreator(); +import 'package:github/src/common.dart'; +import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart' as http_parser; +import 'package:meta/meta.dart'; /// The Main GitHub Client /// @@ -10,44 +14,55 @@ typedef http.Client ClientCreator(); /// // Use the Client /// class GitHub { + /// Creates a new [GitHub] instance. + /// + /// [endpoint] is the api endpoint to use + /// [auth] is the authentication information + GitHub({ + this.auth = const Authentication.anonymous(), + this.endpoint = 'https://api.github.com', + this.version = '2022-11-28', + http.Client? client, + }) : client = client ?? http.Client(); + static const _ratelimitLimitHeader = 'x-ratelimit-limit'; static const _ratelimitResetHeader = 'x-ratelimit-reset'; static const _ratelimitRemainingHeader = 'x-ratelimit-remaining'; + @visibleForTesting + static const versionHeader = 'X-GitHub-Api-Version'; + /// Authentication Information Authentication auth; /// API Endpoint final String endpoint; + /// Calendar version of the GitHub API to use. + /// + /// Changing this value is unsupported. However, it may unblock you if there's + /// hotfix versions. + /// + /// See also: + /// * https://docs.github.com/en/rest/overview/api-versions?apiVersion=2022-11-28 + final String version; + /// HTTP Client final http.Client client; - ActivityService _activity; - AuthorizationsService _authorizations; - BlogService _blog; - ExploreService _explore; - GistsService _gists; - GitService _git; - IssuesService _issues; - MiscService _misc; - OrganizationsService _organizations; - PullRequestsService _pullRequests; - RepositoriesService _repositories; - SearchService _search; - UrlShortenerService _urlShortener; - UsersService _users; - - /// Creates a new [GitHub] instance. - /// - /// [endpoint] is the api endpoint to use - /// [auth] is the authentication information - GitHub( - {Authentication auth, - this.endpoint: "https://api.github.com", - http.Client client}) - : this.auth = auth == null ? new Authentication.anonymous() : auth, - this.client = client == null ? new http.Client() : client; + ActivityService? _activity; + AuthorizationsService? _authorizations; + GistsService? _gists; + GitService? _git; + IssuesService? _issues; + MiscService? _misc; + OrganizationsService? _organizations; + PullRequestsService? _pullRequests; + RepositoriesService? _repositories; + SearchService? _search; + UrlShortenerService? _urlShortener; + UsersService? _users; + ChecksService? _checks; /// The maximum number of requests that the consumer is permitted to make per /// hour. @@ -55,141 +70,75 @@ class GitHub { /// Updated with every request. /// /// Will be `null` if no requests have been made yet. - int get rateLimitLimit => _rateLimitLimit; + int? get rateLimitLimit => _rateLimitLimit; /// The number of requests remaining in the current rate limit window. /// /// Updated with every request. /// /// Will be `null` if no requests have been made yet. - int get rateLimitRemaining => _rateLimitRemaining; + int? get rateLimitRemaining => _rateLimitRemaining; /// The time at which the current rate limit window resets. /// /// Updated with every request. /// /// Will be `null` if no requests have been made yet. - DateTime get rateLimitReset => _rateLimitReset == null + DateTime? get rateLimitReset => _rateLimitReset == null ? null - : new DateTime.fromMillisecondsSinceEpoch(_rateLimitReset * 1000, + : DateTime.fromMillisecondsSinceEpoch(_rateLimitReset! * 1000, isUtc: true); - int _rateLimitReset, _rateLimitLimit, _rateLimitRemaining; + int? _rateLimitReset, _rateLimitLimit, _rateLimitRemaining; /// Service for activity related methods of the GitHub API. - ActivityService get activity { - if (_activity == null) { - _activity = new ActivityService(this); - } - return _activity; - } + ActivityService get activity => _activity ??= ActivityService(this); /// Service for autorizations related methods of the GitHub API. /// /// Note: You can only access this API via Basic Authentication using your /// username and password, not tokens. - AuthorizationsService get authorizations { - if (_authorizations == null) { - _authorizations = new AuthorizationsService(this); - } - return _authorizations; - } - - /// Service to retrieve blog posts. - BlogService get blog { - if (_blog == null) { - _blog = new BlogService(this); - } - return _blog; - } - - /// Service to explore GitHub. - ExploreService get explore { - if (_explore == null) { - _explore = new ExploreService(this); - } - return _explore; - } + AuthorizationsService get authorizations => + _authorizations ??= AuthorizationsService(this); /// Service for gist related methods of the GitHub API. - GistsService get gists { - if (_gists == null) { - _gists = new GistsService(this); - } - return _gists; - } + GistsService get gists => _gists ??= GistsService(this); /// Service for git data related methods of the GitHub API. - GitService get git { - if (_git == null) { - _git = new GitService(this); - } - return _git; - } + GitService get git => _git ??= GitService(this); /// Service for issues related methods of the GitHub API. - IssuesService get issues { - if (_issues == null) { - _issues = new IssuesService(this); - } - return _issues; - } + IssuesService get issues => _issues ??= IssuesService(this); /// Service for misc related methods of the GitHub API. - MiscService get misc { - if (_misc == null) { - _misc = new MiscService(this); - } - return _misc; - } + MiscService get misc => _misc ??= MiscService(this); /// Service for organization related methods of the GitHub API. - OrganizationsService get organizations { - if (_organizations == null) { - _organizations = new OrganizationsService(this); - } - return _organizations; - } + OrganizationsService get organizations => + _organizations ??= OrganizationsService(this); /// Service for pull requests related methods of the GitHub API. - PullRequestsService get pullRequests { - if (_pullRequests == null) { - _pullRequests = new PullRequestsService(this); - } - return _pullRequests; - } + PullRequestsService get pullRequests => + _pullRequests ??= PullRequestsService(this); /// Service for repository related methods of the GitHub API. - RepositoriesService get repositories { - if (_repositories == null) { - _repositories = new RepositoriesService(this); - } - return _repositories; - } + RepositoriesService get repositories => + _repositories ??= RepositoriesService(this); /// Service for search related methods of the GitHub API. - SearchService get search { - if (_search == null) { - _search = new SearchService(this); - } - return _search; - } + SearchService get search => _search ??= SearchService(this); /// Service to provide a handy method to access GitHub's url shortener. - UrlShortenerService get urlShortener { - if (_urlShortener == null) { - _urlShortener = new UrlShortenerService(this); - } - return _urlShortener; - } + UrlShortenerService get urlShortener => + _urlShortener ??= UrlShortenerService(this); /// Service for user related methods of the GitHub API. - UsersService get users { - if (_users == null) { - _users = new UsersService(this); - } - return _users; - } + UsersService get users => _users ??= UsersService(this); + + /// Service containing methods to interact with the Checks API. + /// + /// See https://developer.github.com/v3/checks/ + ChecksService get checks => _checks ??= ChecksService(this); /// Handles Get Requests that respond with JSON /// [path] can either be a path like '/repos' or a full url. @@ -204,38 +153,111 @@ class GitHub { /// [convert] is a simple function that is passed this [GitHub] instance and a JSON object. /// The future will pass the object returned from this function to the then method. /// The default [convert] function returns the input object. - Future getJSON(String path, - {int statusCode, - void fail(http.Response response), - Map headers, - Map params, - JSONConverter convert, - String preview}) async { - if (headers == null) headers = {}; - - if (preview != null) { - headers["Accept"] = preview; - } - - if (convert == null) { - convert = (input) => input; - } - - headers.putIfAbsent("Accept", () => "application/vnd.github.v3+json"); - - var response = await request("GET", path, - headers: headers, params: params, statusCode: statusCode, fail: fail); - - var json = JSON.decode(response.body); + Future getJSON( + String path, { + int? statusCode, + void Function(http.Response response)? fail, + Map? headers, + Map? params, + JSONConverter? convert, + String? preview, + }) => + requestJson( + 'GET', + path, + statusCode: statusCode, + fail: fail, + headers: headers, + params: params, + convert: convert, + preview: preview, + ); - if (convert == null) { - return json; - } + /// Handles Post Requests that respond with JSON + /// + /// [path] can either be a path like '/repos' or a full url. + /// [statusCode] is the expected status code. If it is null, it is ignored. + /// If the status code that the response returns is not the status code you provide + /// then the [fail] function will be called with the HTTP Response. + /// + /// If you don't throw an error or break out somehow, it will go into some error checking + /// that throws exceptions when it finds a 404 or 401. If it doesn't find a general HTTP Status Code + /// for errors, it throws an Unknown Error. + /// + /// [headers] are HTTP Headers. If it doesn't exist, the 'Accept' and 'Authorization' headers are added. + /// [params] are query string parameters. + /// [convert] is a simple function that is passed this [GitHub] instance and a JSON object. + /// + /// The future will pass the object returned from this function to the then method. + /// The default [convert] function returns the input object. + /// [body] is the data to send to the server. Pass in a `List` if you want to post binary body data. Everything else will have .toString() called on it and set as text content + /// [S] represents the input type. + /// [T] represents the type return from this function after conversion + Future postJSON( + String path, { + int? statusCode, + void Function(http.Response response)? fail, + Map? headers, + Map? params, + JSONConverter? convert, + dynamic body, + String? preview, + }) => + requestJson( + 'POST', + path, + statusCode: statusCode, + fail: fail, + headers: headers, + params: params, + convert: convert, + body: body, + preview: preview, + ); - return convert(json); - } + /// Handles PUT Requests that respond with JSON + /// + /// [path] can either be a path like '/repos' or a full url. + /// [statusCode] is the expected status code. If it is null, it is ignored. + /// If the status code that the response returns is not the status code you provide + /// then the [fail] function will be called with the HTTP Response. + /// + /// If you don't throw an error or break out somehow, it will go into some error checking + /// that throws exceptions when it finds a 404 or 401. If it doesn't find a general HTTP Status Code + /// for errors, it throws an Unknown Error. + /// + /// [headers] are HTTP Headers. If it doesn't exist, the 'Accept' and 'Authorization' headers are added. + /// [params] are query string parameters. + /// [convert] is a simple function that is passed this [GitHub] instance and a JSON object. + /// + /// The future will pass the object returned from this function to the then method. + /// The default [convert] function returns the input object. + /// [body] is the data to send to the server. Pass in a `List` if you want to post binary body data. Everything else will have .toString() called on it and set as text content + /// [S] represents the input type. + /// [T] represents the type return from this function after conversion + Future putJSON( + String path, { + int? statusCode, + void Function(http.Response response)? fail, + Map? headers, + Map? params, + JSONConverter? convert, + dynamic body, + String? preview, + }) => + requestJson( + 'PUT', + path, + statusCode: statusCode, + fail: fail, + headers: headers, + params: params, + convert: convert, + body: body, + preview: preview, + ); - /// Handles Post Requests that respond with JSO + /// Handles PATCH Requests that respond with JSON /// /// [path] can either be a path like '/repos' or a full url. /// [statusCode] is the expected status code. If it is null, it is ignored. @@ -252,80 +274,67 @@ class GitHub { /// /// The future will pass the object returned from this function to the then method. /// The default [convert] function returns the input object. - /// [body] is the data to send to the server. - Future postJSON(String path, - {int statusCode, - void fail(http.Response response), - Map headers, - Map params, - JSONConverter convert, - String body, - String preview}) async { - if (headers == null) headers = {}; + /// [body] is the data to send to the server. Pass in a `List` if you want to post binary body data. Everything else will have .toString() called on it and set as text content + /// [S] represents the input type. + /// [T] represents the type return from this function after conversion + Future patchJSON( + String path, { + int? statusCode, + void Function(http.Response response)? fail, + Map? headers, + Map? params, + JSONConverter? convert, + dynamic body, + String? preview, + }) => + requestJson( + 'PATCH', + path, + statusCode: statusCode, + fail: fail, + headers: headers, + params: params, + convert: convert, + body: body, + preview: preview, + ); + + Future requestJson( + String method, + String path, { + int? statusCode, + void Function(http.Response response)? fail, + Map? headers, + Map? params, + JSONConverter? convert, + dynamic body, + String? preview, + }) async { + convert ??= (input) => input as T?; + headers ??= {}; if (preview != null) { - headers["Accept"] = preview; + headers['Accept'] = preview; } - if (convert == null) { - convert = (input) => input; - } + headers.putIfAbsent('Accept', () => v3ApiMimeType); + headers.putIfAbsent(versionHeader, () => version); - headers.putIfAbsent("Accept", () => "application/vnd.github.v3+json"); + final response = await request( + method, + path, + headers: headers, + params: params, + body: body, + statusCode: statusCode, + fail: fail, + ); - var response = await request("POST", path, - headers: headers, - params: params, - body: body, - statusCode: statusCode, - fail: fail); - return convert(JSON.decode(response.body)); - } + final json = jsonDecode(response.body); - /// - /// Internal method to handle status codes - /// - void handleStatusCode(http.Response response) { - String message; - List> errors; - if (response.headers['content-type'].contains('application/json')) { - var json = JSON.decode(response.body); - message = json['message']; - errors = json['errors'] as List>; - } - switch (response.statusCode) { - case 404: - throw new NotFound(this, "Requested Resource was Not Found"); - break; - case 401: - throw new AccessForbidden(this); - case 400: - if (message == "Problems parsing JSON") { - throw new InvalidJSON(this, message); - } else if (message == "Body should be a JSON Hash") { - throw new InvalidJSON(this, message); - } else - throw new BadRequest(this); - break; - case 422: - var buff = new StringBuffer(); - buff.writeln(); - buff.writeln(" Message: ${message}"); - if (errors != null) { - buff.writeln(" Errors:"); - for (Map error in errors) { - var resource = error['resource']; - var field = error['field']; - var code = error['code']; - buff - ..writeln(" Resource: ${resource}") - ..writeln(" Field ${field}") - ..write(" Code: ${code}"); - } - } - throw new ValidationFailed(this, buff.toString()); - } - throw new UnknownError(this, message); + final returnValue = convert(json) as T; + _applyExpandos(returnValue, response); + return returnValue; } /// Handles Authenticated Requests in an easy to understand way. @@ -334,42 +343,52 @@ class GitHub { /// [path] can either be a path like '/repos' or a full url. /// [headers] are HTTP Headers. If it doesn't exist, the 'Accept' and 'Authorization' headers are added. /// [params] are query string parameters. - /// [body] is the body content of requests that take content. + /// [body] is the body content of requests that take content. Pass in a `List` if you want to post binary body data. Everything else will have .toString() called on it and set as text content /// - Future request(String method, String path, - {Map headers, - Map params, - String body, - int statusCode, - void fail(http.Response response), - String preview}) async { - if (headers == null) headers = {}; + Future request( + String method, + String path, { + Map? headers, + Map? params, + dynamic body, + int? statusCode, + void Function(http.Response response)? fail, + String? preview, + }) async { + if (rateLimitRemaining != null && rateLimitRemaining! <= 0) { + assert(rateLimitReset != null); + final now = DateTime.now(); + final waitTime = rateLimitReset!.difference(now); + await Future.delayed(waitTime); + } + + headers ??= {}; if (preview != null) { - headers["Accept"] = preview; + headers['Accept'] = preview; } - if (auth.isToken) { - headers.putIfAbsent("Authorization", () => "token ${auth.token}"); - } else if (auth.isBasic) { - var userAndPass = - BASE64.encode(UTF8.encode('${auth.username}:${auth.password}')); - headers.putIfAbsent("Authorization", () => "basic ${userAndPass}"); + final authHeaderValue = auth.authorizationHeaderValue(); + if (authHeaderValue != null) { + headers.putIfAbsent('Authorization', () => authHeaderValue); } - if (method == "PUT" && body == null) { - headers.putIfAbsent("Content-Length", () => "0"); + // See https://docs.github.com/en/rest/overview/resources-in-the-rest-api?apiVersion=2022-11-28#user-agent-required + headers.putIfAbsent('User-Agent', () => auth.username ?? 'github.dart'); + + if (method == 'PUT' && body == null) { + headers.putIfAbsent('Content-Length', () => '0'); } - var queryString = ""; + var queryString = ''; if (params != null) { queryString = buildQueryString(params); } - var url = new StringBuffer(); + final url = StringBuffer(); - if (path.startsWith("http://") || path.startsWith("https://")) { + if (path.startsWith('http://') || path.startsWith('https://')) { url.write(path); url.write(queryString); } else { @@ -381,41 +400,117 @@ class GitHub { url.write(queryString); } - var request = new http.Request(method, Uri.parse(url.toString())); + final request = http.Request(method, Uri.parse(url.toString())); request.headers.addAll(headers); if (body != null) { - request.body = body; + if (body is List) { + request.bodyBytes = body; + } else { + request.body = body.toString(); + } } - var streamedResponse = await client.send(request); + final streamedResponse = await client.send(request); - var response = await http.Response.fromStream(streamedResponse); + final response = await http.Response.fromStream(streamedResponse); _updateRateLimit(response.headers); if (statusCode != null && statusCode != response.statusCode) { - fail != null ? fail(response) : null; + if (fail != null) { + fail(response); + } handleStatusCode(response); - return null; - } else + } else { return response; + } + } + + /// + /// Internal method to handle status codes + /// + Never handleStatusCode(http.Response response) { + String? message = ''; + List>? errors; + if (response.headers['content-type']!.contains('application/json')) { + try { + final json = jsonDecode(response.body); + message = json['message']; + if (json['errors'] != null) { + try { + errors = List>.from(json['errors']); + } catch (_) { + errors = [ + {'code': json['errors'].toString()} + ]; + } + } + } catch (ex) { + throw UnknownError(this, ex.toString()); + } + } + switch (response.statusCode) { + case 404: + throw NotFound(this, 'Requested Resource was Not Found'); + case 401: + throw AccessForbidden(this); + case 400: + if (message == 'Problems parsing JSON') { + throw InvalidJSON(this, message); + } else if (message == 'Body should be a JSON Hash') { + throw InvalidJSON(this, message); + } else { + throw BadRequest(this); + } + case 422: + final buff = StringBuffer(); + buff.writeln(); + buff.writeln(' Message: $message'); + if (errors != null) { + buff.writeln(' Errors:'); + for (final error in errors) { + final resource = error['resource']; + final field = error['field']; + final code = error['code']; + buff + ..writeln(' Resource: $resource') + ..writeln(' Field $field') + ..write(' Code: $code'); + } + } + throw ValidationFailed(this, buff.toString()); + case 500: + case 502: + case 504: + throw ServerError(this, response.statusCode, message); + } + throw UnknownError(this, message); } /// Disposes of this GitHub Instance. /// No other methods on this instance should be called after this method is called. void dispose() { - // Destroy the Authentication Information - // This is needed for security reasons. - auth = null; - // Closes the HTTP Client client.close(); } void _updateRateLimit(Map headers) { if (headers.containsKey(_ratelimitLimitHeader)) { - _rateLimitLimit = int.parse(headers[_ratelimitLimitHeader]); - _rateLimitRemaining = int.parse(headers[_ratelimitRemainingHeader]); - _rateLimitReset = int.parse(headers[_ratelimitResetHeader]); + _rateLimitLimit = int.parse(headers[_ratelimitLimitHeader]!); + _rateLimitRemaining = int.parse(headers[_ratelimitRemainingHeader]!); + _rateLimitReset = int.parse(headers[_ratelimitResetHeader]!); } } } + +void _applyExpandos(dynamic target, http.Response response) { + _etagExpando[target] = response.headers['etag']; + if (response.headers['date'] != null) { + _dateExpando[target] = http_parser.parseHttpDate(response.headers['date']!); + } +} + +final _etagExpando = Expando('etag'); +final _dateExpando = Expando('date'); + +String? getResponseEtag(Object obj) => _etagExpando[obj]; +DateTime? getResponseDate(Object obj) => _dateExpando[obj]; diff --git a/lib/src/common/issues_service.dart b/lib/src/common/issues_service.dart index 182d0e71..ed061846 100644 --- a/lib/src/common/issues_service.dart +++ b/lib/src/common/issues_service.dart @@ -1,25 +1,28 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; + +import 'package:github/src/common.dart'; /// The [IssuesService] handles communication with issues related methods of the /// GitHub API. /// /// API docs: https://developer.github.com/v3/issues/ class IssuesService extends Service { - IssuesService(GitHub github) : super(github); + IssuesService(super.github); /// List all issues across all the authenticated user’s visible repositories /// including owned repositories, member repositories, and organization repositories /// /// API docs: https://developer.github.com/v3/issues/#list-issues Stream listAll( - {int milestoneNumber, - String state, - String direction, - String sort, - DateTime since, - int perPage, - List labels}) { - return _listIssues("/issues", milestoneNumber, state, direction, sort, + {int? milestoneNumber, + String? state, + String? direction, + String? sort, + DateTime? since, + int? perPage, + List? labels}) { + return _listIssues('/issues', milestoneNumber, state, direction, sort, since, perPage, labels); } @@ -28,14 +31,14 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/#list-issues Stream listByUser( - {int milestoneNumber, - String state, - String direction, - String sort, - DateTime since, - int perPage, - List labels}) { - return _listIssues("/user/issues", milestoneNumber, state, direction, sort, + {int? milestoneNumber, + String? state, + String? direction, + String? sort, + DateTime? since, + int? perPage, + List? labels}) { + return _listIssues('/user/issues', milestoneNumber, state, direction, sort, since, perPage, labels); } @@ -43,14 +46,14 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/#list-issues Stream listByOrg(String org, - {int milestoneNumber, - String state, - String direction, - String sort, - DateTime since, - int perPage, - List labels}) { - return _listIssues("/orgs/${org}/issues", milestoneNumber, state, direction, + {int? milestoneNumber, + String? state, + String? direction, + String? sort, + DateTime? since, + int? perPage, + List? labels}) { + return _listIssues('/orgs/$org/issues', milestoneNumber, state, direction, sort, since, perPage, labels); } @@ -60,27 +63,27 @@ class IssuesService extends Service { /// /// API docs:https://developer.github.com/v3/issues/#list-issues-for-a-repository Stream listByRepo(RepositorySlug slug, - {int milestoneNumber, - String state, - String direction, - String sort, - DateTime since, - int perPage, - List labels}) { - return _listIssues("/repos/${slug.fullName}/issues", milestoneNumber, state, + {int? milestoneNumber, + String? state, + String? direction, + String? sort, + DateTime? since, + int? perPage, + List? labels}) { + return _listIssues('/repos/${slug.fullName}/issues', milestoneNumber, state, direction, sort, since, perPage, labels); } Stream _listIssues( String pathSegment, - int milestoneNumber, - String state, - String direction, - String sort, - DateTime since, - int perPage, - List labels) { - var params = {}; + int? milestoneNumber, + String? state, + String? direction, + String? sort, + DateTime? since, + int? perPage, + List? labels) { + final params = {}; if (perPage != null) { params['per_page'] = perPage.toString(); @@ -117,46 +120,73 @@ class IssuesService extends Service { params['labels'] = labels.join(','); } - return new PaginationHelper(_github) - .objects("GET", pathSegment, Issue.fromJSON, params: params) - as Stream; + return PaginationHelper(github).objects( + 'GET', + pathSegment, + Issue.fromJson, + params: params, + ); + } + + /// Gets a stream of [Reaction]s for an issue. + /// The optional content param let's you filter the request for only reactions + /// of that type. + /// WARNING: ReactionType.plusOne and ReactionType.minusOne currently do not + /// work and will throw an exception is used. All others without + or - signs + /// work fine to filter. + /// + /// This API is currently in preview. It may break. + /// + /// See https://developer.github.com/v3/reactions/ + Stream listReactions(RepositorySlug slug, int issueNumber, + {ReactionType? content}) { + var query = content != null ? '?content=${content.content}' : ''; + return PaginationHelper(github).objects( + 'GET', + '/repos/${slug.owner}/${slug.name}/issues/$issueNumber/reactions$query', + Reaction.fromJson, + headers: { + 'Accept': 'application/vnd.github.squirrel-girl-preview+json', + }, + ); } /// Edit an issue. /// /// API docs: https://developer.github.com/v3/issues/#edit-an-issue - Future edit(RepositorySlug slug, int issueNumber, IssueRequest issue) { - return _github - .request("PATCH", '/repos/${slug.fullName}/issues/${issueNumber}', - body: issue.toJSON()) - .then((response) { - return Issue.fromJSON(JSON.decode(response.body) as Map) - as Future; + Future edit( + RepositorySlug slug, int issueNumber, IssueRequest issue) async { + return github + .request('PATCH', '/repos/${slug.fullName}/issues/$issueNumber', + body: GitHubJson.encode(issue)) + .then((response) { + return Issue.fromJson(jsonDecode(response.body) as Map); }); } /// Get an issue. /// /// API docs: https://developer.github.com/v3/issues/#get-a-single-issue - Future get(RepositorySlug slug, int issueNumber) { - return _github.getJSON("/repos/${slug.fullName}/issues/${issueNumber}", - convert: Issue.fromJSON) as Future; - } + Future get(RepositorySlug slug, int issueNumber) => + github.getJSON('/repos/${slug.fullName}/issues/$issueNumber', + convert: Issue.fromJson); /// Create an issue. /// /// API docs: https://developer.github.com/v3/issues/#create-an-issue Future create(RepositorySlug slug, IssueRequest issue) async { - var response = await _github.request( - "POST", '/repos/${slug.fullName}/issues', - body: issue.toJSON()); + final response = await github.request( + 'POST', + '/repos/${slug.fullName}/issues', + body: GitHubJson.encode(issue), + ); if (StatusCodes.isClientError(response.statusCode)) { //TODO: throw a more friendly error – better this than silent failure - throw new GitHubError(_github, response.body); + throw GitHubError(github, response.body); } - return Issue.fromJSON(JSON.decode(response.body) as Map); + return Issue.fromJson(jsonDecode(response.body) as Map); } /// Lists all available assignees (owners and collaborators) to which issues @@ -164,17 +194,16 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/assignees/#list-assignees Stream listAssignees(RepositorySlug slug) { - return new PaginationHelper(_github) - .objects("GET", "/repos/${slug.fullName}/assignees", User.fromJSON) - as Stream; + return PaginationHelper(github) + .objects('GET', '/repos/${slug.fullName}/assignees', User.fromJson); } /// Checks if a user is an assignee for the specified repository. /// /// API docs: https://developer.github.com/v3/issues/assignees/#check-assignee Future isAssignee(RepositorySlug slug, String repoName) { - return _github - .request("GET", "/repos/${slug.fullName}/assignees/${repoName}") + return github + .request('GET', '/repos/${slug.fullName}/assignees/$repoName') .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } @@ -183,51 +212,60 @@ class IssuesService extends Service { /// API docs: https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue Stream listCommentsByIssue( RepositorySlug slug, int issueNumber) { - return new PaginationHelper(_github).objects( + return PaginationHelper(github).objects( 'GET', - '/repos/${slug.fullName}/issues/${issueNumber}/comments', - IssueComment.fromJSON) as Stream; + '/repos/${slug.fullName}/issues/$issueNumber/comments', + IssueComment.fromJson); } /// Lists all comments in a repository. /// /// API docs: https://developer.github.com/v3/issues/comments/#list-comments-on-an-issue Stream listCommentsByRepo(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - 'GET', - '/repos/${slug.fullName}/issues/comments', - IssueComment.fromJSON) as Stream; + return PaginationHelper(github).objects('GET', + '/repos/${slug.fullName}/issues/comments', IssueComment.fromJson); } /// Fetches the specified issue comment. /// /// API docs: https://developer.github.com/v3/issues/comments/#get-a-single-comment - Future getComment(RepositorySlug slug, int id) { - return _github.getJSON("/repos/${slug.fullName}/issues/comments/${id}", - convert: IssueComment.fromJSON) as Future; - } + Future getComment(RepositorySlug slug, int id) => + github.getJSON('/repos/${slug.fullName}/issues/comments/$id', + convert: IssueComment.fromJson); /// Creates a new comment on the specified issue /// /// API docs: https://developer.github.com/v3/issues/comments/#create-a-comment Future createComment( RepositorySlug slug, int issueNumber, String body) { - var it = JSON.encode({"body": body}); - return _github.postJSON( - '/repos/${slug.fullName}/issues/${issueNumber}/comments', - body: it, - convert: IssueComment.fromJSON, - statusCode: StatusCodes.CREATED) as Future; + final it = GitHubJson.encode({'body': body}); + return github.postJSON( + '/repos/${slug.fullName}/issues/$issueNumber/comments', + body: it, + convert: IssueComment.fromJson, + statusCode: StatusCodes.CREATED, + ); } - // TODO: Implement editComment: https://developer.github.com/v3/issues/comments/#edit-a-comment + /// Update an issue comment. + /// + /// API docs: https://docs.github.com/en/rest/reference/issues#update-an-issue-comment + Future updateComment(RepositorySlug slug, int id, String body) { + final it = GitHubJson.encode({'body': body}); + return github.postJSON( + '/repos/${slug.fullName}/issues/comments/$id', + body: it, + convert: IssueComment.fromJson, + statusCode: StatusCodes.OK, + ); + } /// Deletes an issue comment. /// /// API docs: https://developer.github.com/v3/issues/comments/#delete-a-comment Future deleteComment(RepositorySlug slug, int id) { - return _github - .request('DELETE', '/repos/${slug.fullName}/issues/comments/${id}') + return github + .request('DELETE', '/repos/${slug.fullName}/issues/comments/$id') .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } @@ -237,45 +275,72 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository Stream listLabels(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/labels", IssueLabel.fromJSON) - as Stream; + return PaginationHelper(github) + .objects('GET', '/repos/${slug.fullName}/labels', IssueLabel.fromJson); } /// Fetches a single label. /// /// API docs: https://developer.github.com/v3/issues/labels/#get-a-single-label - Future getLabel(RepositorySlug slug, String name) { - return _github.getJSON("/repos/${slug.fullName}/labels/${name}", - convert: IssueLabel.fromJSON, - statusCode: StatusCodes.OK) as Future; - } + Future getLabel(RepositorySlug slug, String name) => + github.getJSON('/repos/${slug.fullName}/labels/$name', + convert: IssueLabel.fromJson, statusCode: StatusCodes.OK); /// Creates a new label on the specified repository. /// /// API docs: https://developer.github.com/v3/issues/labels/#create-a-label Future createLabel( - RepositorySlug slug, String name, String color) { - return _github.postJSON("/repos/${slug.fullName}/labels", - body: JSON.encode({"name": name, "color": color}), - convert: IssueLabel.fromJSON) as Future; + RepositorySlug slug, + String name, { + String? color, + String? description, + }) { + return github.postJSON( + '/repos/${slug.fullName}/labels', + body: GitHubJson.encode({ + 'name': name, + if (color != null) 'color': color, + if (description != null) 'description': description, + }), + convert: IssueLabel.fromJson, + ); } /// Edits a label. /// /// API docs: https://developer.github.com/v3/issues/labels/#update-a-label + @Deprecated('See updateLabel instead.') Future editLabel(RepositorySlug slug, String name, String color) { - return _github.postJSON("/repos/${slug.fullName}/labels/${name}", - body: JSON.encode({"name": name, "color": color}), - convert: IssueLabel.fromJSON) as Future; + return updateLabel(slug, name, color: color); + } + + /// Update a label. + /// + /// API docs: https://developer.github.com/v3/issues/labels/#update-a-label + Future updateLabel( + RepositorySlug slug, + String name, { + String? newName, + String? color, + String? description, + }) { + return github.patchJSON( + '/repos/${slug.fullName}/labels/$name', + body: GitHubJson.encode({ + if (newName != null) 'new_name': newName, + if (color != null) 'color': color, + if (description != null) 'description': description, + }), + convert: IssueLabel.fromJson, + ); } /// Deletes a label. /// /// API docs: https://developer.github.com/v3/issues/labels/#delete-a-label Future deleteLabel(RepositorySlug slug, String name) async { - var response = await _github.request( - "DELETE", "/repos/${slug.fullName}/labels/${name}"); + final response = + await github.request('DELETE', '/repos/${slug.fullName}/labels/$name'); return response.statusCode == StatusCodes.NO_CONTENT; } @@ -284,10 +349,10 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/labels/#list-all-labels-for-this-repository Stream listLabelsByIssue(RepositorySlug slug, int issueNumber) { - return new PaginationHelper(_github).objects( - "GET", - "/repos/${slug.fullName}/issues/${issueNumber}/labels", - IssueLabel.fromJSON) as Stream; + return PaginationHelper(github).objects( + 'GET', + '/repos/${slug.fullName}/issues/$issueNumber/labels', + IssueLabel.fromJson); } /// Adds labels to an issue. @@ -295,12 +360,12 @@ class IssuesService extends Service { /// API docs: https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue Future> addLabelsToIssue( RepositorySlug slug, int issueNumber, List labels) { - return _github.postJSON( - "/repos/${slug.fullName}/issues/${issueNumber}/labels", - body: JSON.encode(labels), - convert: (input) => - input.map((Map it) => IssueLabel.fromJSON(it))) - as Future>; + return github.postJSON, List>( + '/repos/${slug.fullName}/issues/$issueNumber/labels', + body: GitHubJson.encode(labels), + convert: (input) => + input.cast>().map(IssueLabel.fromJson).toList(), + ); } /// Replaces all labels for an issue. @@ -308,13 +373,11 @@ class IssuesService extends Service { /// API docs: https://developer.github.com/v3/issues/labels/#replace-all-labels-for-an-issue Future> replaceLabelsForIssue( RepositorySlug slug, int issueNumber, List labels) { - return _github - .request("PUT", "/repos/${slug.fullName}/issues/${issueNumber}/labels", - body: JSON.encode(labels)) + return github + .request('PUT', '/repos/${slug.fullName}/issues/$issueNumber/labels', + body: GitHubJson.encode(labels)) .then((response) { - return JSON - .decode(response.body) - .map((Map it) => IssueLabel.fromJSON(it)); + return jsonDecode(response.body).map(IssueLabel.fromJson); }); } @@ -323,8 +386,8 @@ class IssuesService extends Service { /// API docs: https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue Future removeLabelForIssue( RepositorySlug slug, int issueNumber, String label) async { - var response = await _github.request("DELETE", - "/repos/${slug.fullName}/issues/${issueNumber}/labels/${label}"); + final response = await github.request( + 'DELETE', '/repos/${slug.fullName}/issues/$issueNumber/labels/$label'); return response.statusCode == StatusCodes.OK; } @@ -333,9 +396,8 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/labels/#remove-all-labels-from-an-issue Future removeAllLabelsForIssue(RepositorySlug slug, int issueNumber) { - return _github - .request( - "DELETE", "/repos/${slug.fullName}/issues/${issueNumber}/labels") + return github + .request('DELETE', '/repos/${slug.fullName}/issues/$issueNumber/labels') .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } @@ -345,9 +407,8 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/milestones/#list-milestones-for-a-repository Stream listMilestones(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/milestones", Milestone.fromJSON) - as Stream; + return PaginationHelper(github).objects( + 'GET', '/repos/${slug.fullName}/milestones', Milestone.fromJson); } // TODO: Implement getMilestone: https://developer.github.com/v3/issues/milestones/#get-a-single-milestone @@ -357,9 +418,8 @@ class IssuesService extends Service { /// API docs: https://developer.github.com/v3/issues/milestones/#create-a-milestone Future createMilestone( RepositorySlug slug, CreateMilestone request) { - return _github.postJSON("/repos/${slug.fullName}/milestones", - body: JSON.encode(request.toJSON()), - convert: Milestone.fromJSON) as Future; + return github.postJSON('/repos/${slug.fullName}/milestones', + body: GitHubJson.encode(request), convert: Milestone.fromJson); } // TODO: Implement editMilestone: https://developer.github.com/v3/issues/milestones/#update-a-milestone @@ -368,8 +428,45 @@ class IssuesService extends Service { /// /// API docs: https://developer.github.com/v3/issues/milestones/#delete-a-milestone Future deleteMilestone(RepositorySlug slug, int number) { - return _github - .request("DELETE", '/repos/${slug.fullName}/milestones/${number}') + return github + .request('DELETE', '/repos/${slug.fullName}/milestones/$number') .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } + + /// Lists all timeline events for an issue. + /// + /// API docs: https://docs.github.com/en/rest/issues/timeline?apiVersion=2022-11-28 + Stream listTimeline(RepositorySlug slug, int issueNumber) { + return PaginationHelper(github).objects( + 'GET', + '/repos/${slug.fullName}/issues/$issueNumber/timeline', + TimelineEvent.fromJson, + ); + } + + /// Lock an issue. + /// + /// API docs: https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#lock-an-issue + /// + /// The `lockReason`, if specified, must be one of: `off-topic`, `too heated`, `resolved`, `spam`. + Future lock(RepositorySlug slug, int number, + {String? lockReason}) async { + String body; + if (lockReason != null) { + body = GitHubJson.encode({'lock_reason': lockReason}); + } else { + body = '{}'; + } + await github.postJSON('/repos/${slug.fullName}/issues/$number/lock', + body: body, statusCode: 204); + } + + /// Unlock an issue. + /// + /// API docs: https://docs.github.com/en/rest/issues/issues?apiVersion=2022-11-28#unlock-an-issue + Future unlock(RepositorySlug slug, int number) async { + await github.request( + 'DELETE', '/repos/${slug.fullName}/issues/$number/lock', + statusCode: 204); + } } diff --git a/lib/src/common/misc_service.dart b/lib/src/common/misc_service.dart index ab211784..30385a18 100644 --- a/lib/src/common/misc_service.dart +++ b/lib/src/common/misc_service.dart @@ -1,36 +1,41 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; +import 'package:github/src/common.dart'; /// The [MiscService] handles communication with misc related methods of the /// GitHub API. /// /// API docs: https://developer.github.com/v3/misc/ class MiscService extends Service { - MiscService(GitHub github) : super(github); + MiscService(super.github); /// Fetches all emojis available on GitHub /// Returns a map of the name to a url of the image. /// /// API docs: https://developer.github.com/v3/emojis/ Future> listEmojis() { - return _github.getJSON("/emojis", statusCode: StatusCodes.OK) - as Future>; + final r = github.getJSON>( + '/emojis', + statusCode: StatusCodes.OK, + convert: (Map json) => json.cast(), + ); + return r; } /// Lists available .gitignore template names. /// /// API docs: https://developer.github.com/v3/gitignore/#listing-available-templates Future> listGitignoreTemplates() { - return _github.getJSON("/gitignore/templates") as Future>; + return github.getJSON('/gitignore/templates') as Future>; } /// Gets a .gitignore template by [name]. /// All template names can be fetched using [listGitignoreTemplates]. /// /// API docs: https://developer.github.com/v3/gitignore/#get-a-single-template - Future getGitignoreTemplate(String name) { - return _github.getJSON("/gitignore/templates/${name}", - convert: GitignoreTemplate.fromJSON) as Future; - } + Future getGitignoreTemplate(String name) => + github.getJSON('/gitignore/templates/$name', + convert: GitignoreTemplate.fromJson); /// Renders Markdown from the [input]. /// @@ -38,12 +43,12 @@ class MiscService extends Service { /// [context] is the repository context. Only take into account when [mode] is 'gfm'. /// /// API docs: https://developer.github.com/v3/markdown/#render-an-arbitrary-markdown-document - Future renderMarkdown(String input, - {String mode: "markdown", String context}) { - return _github - .request("POST", "/markdown", - body: - JSON.encode({"text": input, "mode": mode, "context": context})) + Future renderMarkdown(String? input, + {String mode = 'markdown', String? context}) { + return github + .request('POST', '/markdown', + body: GitHubJson.encode( + {'text': input, 'mode': mode, 'context': context})) .then((response) { return response.body; }); @@ -57,60 +62,27 @@ class MiscService extends Service { /// /// API docs: https://developer.github.com/v3/rate_limit/ Future getRateLimit() { - return _github.request("GET", "/").then((response) { - return RateLimit.fromHeaders(response.headers); + return github.request('GET', '/rate_limit').then((response) { + return RateLimit.fromRateLimitResponse(jsonDecode(response.body)); }); } /// Gets the GitHub API Status. - Future getApiStatus() { - return _github.getJSON("https://status.github.com/api/status.json", - statusCode: StatusCodes.OK, - convert: APIStatus.fromJSON) as Future; - } - - /// Returns a stream of Octocats from Octodex. /// - /// See: https://octodex.github.com/ - Stream listOctodex({bool cors: false}) { - var controller = new StreamController(); - - var u = "http://feeds.feedburner.com/Octocats.xml"; - - _github.client - .get( - "${cors ? "http://whateverorigin.org/get?url=" : ""}${cors ? Uri.encodeComponent(u) : u}") - .then((response) { - var document = html_parser.parse(response.body); - document.querySelectorAll("entry").forEach((entry) { - var name = entry.querySelector("title").text; - var c = "" + - entry.querySelector("content").innerHtml + - ""; - var content = html_parser.parse(c); - var image = content.querySelector("a img").attributes['src']; - var url = entry.querySelector("link").attributes['href']; - - controller.add(new Octocat() - ..image = image - ..name = name - ..url = url); - }); - return controller.close(); - }); - - return controller.stream; - } + /// API docs: https://www.githubstatus.com/api + Future getApiStatus() => + github.getJSON('https://status.github.com/api/v2/status.json', + statusCode: StatusCodes.OK, convert: APIStatus.fromJson); /// Returns an ASCII Octocat with the specified [text]. - Future getOctocat([String text]) { - var params = {}; + Future getOctocat([String? text]) { + final params = {}; if (text != null) { - params["s"] = text; + params['s'] = text; } - return _github.request("GET", "/octocat", params: params).then((response) { + return github.request('GET', '/octocat', params: params).then((response) { return response.body; }); } @@ -119,11 +91,11 @@ class MiscService extends Service { Future getWisdom() => getOctocat(); Future getZen() => - _github.request("GET", "/zen").then((response) => response.body); + github.request('GET', '/zen').then((response) => response.body); } class Octocat { - String name; - String image; - String url; + String? name; + String? image; + String? url; } diff --git a/lib/src/common/model/activity.dart b/lib/src/common/model/activity.dart index e28667ea..cdb873ec 100644 --- a/lib/src/common/model/activity.dart +++ b/lib/src/common/model/activity.dart @@ -1,63 +1,51 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'activity.g.dart'; /// Model class for an event. +@JsonSerializable() class Event { - Repository repo; - - User actor; - - Organization org; - - @ApiName("created_at") - DateTime createdAt; - - String id; - - String type; - - Map json; - - Map payload; - - static Event fromJSON(Map input) { - if (input == null) return null; - - var event = new Event(); - - event.json = input; - - event.type = input['type']; - - event - ..repo = Repository.fromJSON(input['repo'] as Map) - ..org = Organization.fromJSON(input['org'] as Map) - ..createdAt = parseDateTime(input['created_at']) - ..id = input['id'] - ..actor = User.fromJSON(input['actor'] as Map) - ..payload = input['payload'] as Map; - - return event; - } + Event({ + this.id, + this.type, + this.repo, + this.actor, + this.org, + this.payload, + this.createdAt, + }); + String? id; + String? type; + Repository? repo; + User? actor; + Organization? org; + Map? payload; + + @JsonKey(name: 'created_at') + DateTime? createdAt; + + factory Event.fromJson(Map input) => _$EventFromJson(input); + Map toJson() => _$EventToJson(this); } /// Model class for a repository subscription. +@JsonSerializable() class RepositorySubscription { - bool subscribed; - bool ignored; - String reason; - - @ApiName("created_at") - DateTime createdAt; - - RepositorySubscription(); - - static RepositorySubscription fromJSON(Map input) { - if (input == null) return null; - - return new RepositorySubscription() - ..subscribed = input['subscribed'] - ..ignored = input['ignored'] - ..reason = input['reason'] - ..createdAt = parseDateTime(input['created_at']); - } + RepositorySubscription({ + this.subscribed, + this.ignored, + this.reason, + this.createdAt, + }); + bool? subscribed; + bool? ignored; + String? reason; + + @JsonKey(name: 'created_at') + DateTime? createdAt; + + factory RepositorySubscription.fromJson(Map input) => + _$RepositorySubscriptionFromJson(input); + Map toJson() => _$RepositorySubscriptionToJson(this); } diff --git a/lib/src/common/model/activity.g.dart b/lib/src/common/model/activity.g.dart new file mode 100644 index 00000000..9638fc87 --- /dev/null +++ b/lib/src/common/model/activity.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'activity.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Event _$EventFromJson(Map json) => Event( + id: json['id'] as String?, + type: json['type'] as String?, + repo: json['repo'] == null + ? null + : Repository.fromJson(json['repo'] as Map), + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + org: json['org'] == null + ? null + : Organization.fromJson(json['org'] as Map), + payload: json['payload'] as Map?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + ); + +Map _$EventToJson(Event instance) => { + 'id': instance.id, + 'type': instance.type, + 'repo': instance.repo, + 'actor': instance.actor, + 'org': instance.org, + 'payload': instance.payload, + 'created_at': instance.createdAt?.toIso8601String(), + }; + +RepositorySubscription _$RepositorySubscriptionFromJson( + Map json) => + RepositorySubscription( + subscribed: json['subscribed'] as bool?, + ignored: json['ignored'] as bool?, + reason: json['reason'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + ); + +Map _$RepositorySubscriptionToJson( + RepositorySubscription instance) => + { + 'subscribed': instance.subscribed, + 'ignored': instance.ignored, + 'reason': instance.reason, + 'created_at': instance.createdAt?.toIso8601String(), + }; diff --git a/lib/src/common/model/authorizations.dart b/lib/src/common/model/authorizations.dart index a81ee78d..fa80708e 100644 --- a/lib/src/common/model/authorizations.dart +++ b/lib/src/common/model/authorizations.dart @@ -1,73 +1,64 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:github/src/common/model/users.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'authorizations.g.dart'; /// Model class for an authorization. +@JsonSerializable() class Authorization { - int id; - - List scopes; - String token; - AuthorizationApplication app; - String note; - String noteUrl; - DateTime createdAt; - DateTime updatedAt; - User user; - - Map json; + Authorization( + {this.id, + this.scopes, + this.token, + this.app, + this.note, + this.noteUrl, + this.createdAt, + this.updatedAt, + this.user}); - static Authorization fromJSON(Map input) { - if (input == null) return null; + int? id; + List? scopes; + String? token; + AuthorizationApplication? app; + String? note; + String? noteUrl; + DateTime? createdAt; + DateTime? updatedAt; + User? user; - return new Authorization() - ..id = input['id'] - ..scopes = input['scopes'] as List - ..token = input['token'] - ..app = AuthorizationApplication.fromJSON(input['app'] as Map) - ..note = input['note'] - ..noteUrl = input['note_url'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..json = input - ..user = User.fromJSON(input['user'] as Map); - } + factory Authorization.fromJson(Map input) => + _$AuthorizationFromJson(input); + Map toJson() => _$AuthorizationToJson(this); } /// Model class for an application of an [Authorization]. +@JsonSerializable() class AuthorizationApplication { - String url; - String name; + AuthorizationApplication({this.url, this.name, this.clientId}); - @ApiName("client_id") - String clientID; + String? url; + String? name; + String? clientId; - AuthorizationApplication(); - - static AuthorizationApplication fromJSON(Map input) { - if (input == null) return null; - - return new AuthorizationApplication() - ..url = input['url'] - ..name = input['name'] - ..clientID = input['client_id']; - } + factory AuthorizationApplication.fromJson(Map input) => + _$AuthorizationApplicationFromJson(input); + Map toJson() => _$AuthorizationApplicationToJson(this); } +@JsonSerializable() class CreateAuthorization { - final String note; - - List scopes; - String noteUrl; - String clientID; - String clientSecret; + CreateAuthorization(this.note, + {this.scopes, this.noteUrl, this.clientId, this.clientSecret}); - CreateAuthorization(this.note); + String? note; + List? scopes; + String? noteUrl; + String? clientId; + String? clientSecret; - String toJSON() { - var map = {}; - putValue("note", note, map); - putValue("note_url", noteUrl, map); - putValue("client_id", clientID, map); - putValue("client_secret", clientSecret, map); - return JSON.encode(map); - } + factory CreateAuthorization.fromJson(Map input) => + _$CreateAuthorizationFromJson(input); + Map toJson() => _$CreateAuthorizationToJson(this); } diff --git a/lib/src/common/model/authorizations.g.dart b/lib/src/common/model/authorizations.g.dart new file mode 100644 index 00000000..918a8a47 --- /dev/null +++ b/lib/src/common/model/authorizations.g.dart @@ -0,0 +1,79 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'authorizations.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Authorization _$AuthorizationFromJson(Map json) => + Authorization( + id: (json['id'] as num?)?.toInt(), + scopes: + (json['scopes'] as List?)?.map((e) => e as String).toList(), + token: json['token'] as String?, + app: json['app'] == null + ? null + : AuthorizationApplication.fromJson( + json['app'] as Map), + note: json['note'] as String?, + noteUrl: json['note_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$AuthorizationToJson(Authorization instance) => + { + 'id': instance.id, + 'scopes': instance.scopes, + 'token': instance.token, + 'app': instance.app, + 'note': instance.note, + 'note_url': instance.noteUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'user': instance.user, + }; + +AuthorizationApplication _$AuthorizationApplicationFromJson( + Map json) => + AuthorizationApplication( + url: json['url'] as String?, + name: json['name'] as String?, + clientId: json['client_id'] as String?, + ); + +Map _$AuthorizationApplicationToJson( + AuthorizationApplication instance) => + { + 'url': instance.url, + 'name': instance.name, + 'client_id': instance.clientId, + }; + +CreateAuthorization _$CreateAuthorizationFromJson(Map json) => + CreateAuthorization( + json['note'] as String?, + scopes: + (json['scopes'] as List?)?.map((e) => e as String).toList(), + noteUrl: json['note_url'] as String?, + clientId: json['client_id'] as String?, + clientSecret: json['client_secret'] as String?, + ); + +Map _$CreateAuthorizationToJson( + CreateAuthorization instance) => + { + 'note': instance.note, + 'scopes': instance.scopes, + 'note_url': instance.noteUrl, + 'client_id': instance.clientId, + 'client_secret': instance.clientSecret, + }; diff --git a/lib/src/common/model/blog.dart b/lib/src/common/model/blog.dart deleted file mode 100644 index 0efe91bc..00000000 --- a/lib/src/common/model/blog.dart +++ /dev/null @@ -1,36 +0,0 @@ -part of github.common; - -/// Model class for a blog post. -class BlogPost { - DateTime publishedAt; - DateTime updatedAt; - - String title; - String url; - String category; - String content; - String author; - - static BlogPost fromXML(xml.XmlElement node) { - var children = node.children; - - xml.XmlElement query(String tagName) => children - .firstWhere((it) => it is xml.XmlElement && it.name.local == tagName); - - var title = query("title").text; - var content = query("content").text; - var link = query("link").getAttribute("href"); - var category = query("category").text; - var author = query("author").children[0].text; - - var post = new BlogPost(); - - post.author = author; - post.title = title; - post.content = content; - post.category = category; - post.url = link; - - return post; - } -} diff --git a/lib/src/common/model/changes.dart b/lib/src/common/model/changes.dart new file mode 100644 index 00000000..edccdbc5 --- /dev/null +++ b/lib/src/common/model/changes.dart @@ -0,0 +1,68 @@ +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'changes.g.dart'; + +@immutable +@JsonSerializable() +class Ref { + const Ref(this.from); + final String? from; + + factory Ref.fromJson(Map input) => _$RefFromJson(input); + Map toJson() => _$RefToJson(this); +} + +@immutable +@JsonSerializable() +class Sha { + const Sha(this.from); + final String? from; + + factory Sha.fromJson(Map input) => _$ShaFromJson(input); + Map toJson() => _$ShaToJson(this); +} + +@immutable +@JsonSerializable() +class Base { + const Base(this.ref, this.sha); + final Ref? ref; + final Sha? sha; + + factory Base.fromJson(Map input) => _$BaseFromJson(input); + Map toJson() => _$BaseToJson(this); +} + +@immutable +@JsonSerializable() +class Body { + const Body(this.from); + final String? from; + + factory Body.fromJson(Map input) => _$BodyFromJson(input); + Map toJson() => _$BodyToJson(this); +} + +@immutable +@JsonSerializable() +class Title { + const Title({this.from}); + final String? from; + + factory Title.fromJson(Map input) => _$TitleFromJson(input); + Map toJson() => _$TitleToJson(this); +} + +@immutable +@JsonSerializable() +class Changes { + const Changes(this.base, this.body, this.title); + final Base? base; + final Body? body; + final Title? title; + + factory Changes.fromJson(Map input) => + _$ChangesFromJson(input); + Map toJson() => _$ChangesToJson(this); +} diff --git a/lib/src/common/model/changes.g.dart b/lib/src/common/model/changes.g.dart new file mode 100644 index 00000000..13e97d0e --- /dev/null +++ b/lib/src/common/model/changes.g.dart @@ -0,0 +1,71 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'changes.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Ref _$RefFromJson(Map json) => Ref( + json['from'] as String?, + ); + +Map _$RefToJson(Ref instance) => { + 'from': instance.from, + }; + +Sha _$ShaFromJson(Map json) => Sha( + json['from'] as String?, + ); + +Map _$ShaToJson(Sha instance) => { + 'from': instance.from, + }; + +Base _$BaseFromJson(Map json) => Base( + json['ref'] == null + ? null + : Ref.fromJson(json['ref'] as Map), + json['sha'] == null + ? null + : Sha.fromJson(json['sha'] as Map), + ); + +Map _$BaseToJson(Base instance) => { + 'ref': instance.ref, + 'sha': instance.sha, + }; + +Body _$BodyFromJson(Map json) => Body( + json['from'] as String?, + ); + +Map _$BodyToJson(Body instance) => { + 'from': instance.from, + }; + +Title _$TitleFromJson(Map json) => Title( + from: json['from'] as String?, + ); + +Map _$TitleToJson(Title instance) => { + 'from': instance.from, + }; + +Changes _$ChangesFromJson(Map json) => Changes( + json['base'] == null + ? null + : Base.fromJson(json['base'] as Map), + json['body'] == null + ? null + : Body.fromJson(json['body'] as Map), + json['title'] == null + ? null + : Title.fromJson(json['title'] as Map), + ); + +Map _$ChangesToJson(Changes instance) => { + 'base': instance.base, + 'body': instance.body, + 'title': instance.title, + }; diff --git a/lib/src/common/model/checks.dart b/lib/src/common/model/checks.dart new file mode 100644 index 00000000..b7666579 --- /dev/null +++ b/lib/src/common/model/checks.dart @@ -0,0 +1,423 @@ +import 'dart:convert'; + +import 'package:github/src/common/model/pulls.dart'; +import 'package:github/src/common/util/utils.dart'; +import 'package:meta/meta.dart'; + +class CheckRunAnnotationLevel extends EnumWithValue { + static const notice = CheckRunAnnotationLevel._('notice'); + static const warning = CheckRunAnnotationLevel._('warning'); + static const failure = CheckRunAnnotationLevel._('failure'); + + const CheckRunAnnotationLevel._(String super.value); + + factory CheckRunAnnotationLevel._fromValue(String? value) { + switch (value) { + case 'notice': + return notice; + case 'warning': + return warning; + case 'failure': + return failure; + default: + throw Exception( + 'This level of check run annotation is unimplemented: $value.'); + } + } + + bool operator <(CheckRunAnnotationLevel other) { + if (this == failure) { + return false; + } + if (this == notice) { + return other != notice; + } + return other == failure; + } + + bool operator <=(CheckRunAnnotationLevel other) => + this == other || this < other; + bool operator >(CheckRunAnnotationLevel other) => !(this <= other); + bool operator >=(CheckRunAnnotationLevel other) => !(this < other); +} + +class CheckRunConclusion extends EnumWithValue { + static const success = CheckRunConclusion._('success'); + static const failure = CheckRunConclusion._('failure'); + static const neutral = CheckRunConclusion._('neutral'); + static const cancelled = CheckRunConclusion._('cancelled'); + static const timedOut = CheckRunConclusion._('timed_out'); + static const skipped = CheckRunConclusion._('skipped'); + static const actionRequired = CheckRunConclusion._('action_required'); + static const empty = CheckRunConclusion._(null); + + const CheckRunConclusion._(super.value); + + factory CheckRunConclusion._fromValue(String? value) { + if (value == null || value == 'null') { + return empty; + } + for (final level in const [ + success, + failure, + neutral, + cancelled, + timedOut, + skipped, + actionRequired + ]) { + if (level.value == value) { + return level; + } + } + throw Exception( + 'This level of check run conclusion is unimplemented: $value.'); + } +} + +class CheckRunStatus extends EnumWithValue { + static const queued = CheckRunStatus._('queued'); + static const inProgress = CheckRunStatus._('in_progress'); + static const completed = CheckRunStatus._('completed'); + const CheckRunStatus._(String super.value); +} + +class CheckRunFilter extends EnumWithValue { + static const all = CheckRunFilter._('all'); + static const latest = CheckRunFilter._('latest'); + const CheckRunFilter._(String super.value); +} + +@immutable +class CheckRun { + final String? name; + final int? id; + final String? externalId; + final String? headSha; + final CheckRunStatus? status; + final int? checkSuiteId; + final String? detailsUrl; + final DateTime startedAt; + final CheckRunConclusion conclusion; + + const CheckRun._({ + required this.id, + required this.externalId, + required this.headSha, + required this.status, + required this.checkSuiteId, + required this.name, + required this.detailsUrl, + required this.startedAt, + required this.conclusion, + }); + + factory CheckRun.fromJson(Map input) { + CheckRunStatus? status; + for (final s in const [ + CheckRunStatus.completed, + CheckRunStatus.inProgress, + CheckRunStatus.queued + ]) { + if (s.toString() == input['status']) { + status = s; + break; + } + } + return CheckRun._( + name: input['name'], + id: input['id'], + externalId: input['external_id'], + status: status, + headSha: input['head_sha'], + checkSuiteId: input['check_suite']['id'], + detailsUrl: input['details_url'], + startedAt: DateTime.parse(input['started_at']), + conclusion: CheckRunConclusion._fromValue(input['conclusion']), + ); + } + + Map toJson() { + return { + 'name': name, + 'id': id, + 'external_id': externalId, + 'status': status, + 'head_sha': externalId, + 'check_suite': { + 'id': checkSuiteId, + }, + 'details_url': detailsUrl, + 'started_at': startedAt.toIso8601String(), + 'conclusion': conclusion, + }; + } + + @override + String toString() { + return jsonEncode(toJson()); + } +} + +@immutable +class CheckRunOutput { + /// The title of the check run. + final String title; + + /// The summary of the check run. This parameter supports Markdown. + final String summary; + + /// The details of the check run. This parameter supports Markdown. + final String? text; + + /// Adds information from your analysis to specific lines of code. + /// Annotations are visible on GitHub in the Checks and Files changed tab of the pull request. + /// The Checks API limits the number of annotations to a maximum of 50 per API request. + /// To create more than 50 annotations, you have to make multiple requests to the Update a check run endpoint. + /// Each time you update the check run, annotations are appended to the list of annotations that already exist for the check run. + final List? annotations; + + /// Adds images to the output displayed in the GitHub pull request UI. + final List? images; + + const CheckRunOutput({ + required this.title, + required this.summary, + this.text, + this.annotations, + this.images, + }); + + Map toJson() { + return createNonNullMap({ + 'title': title, + 'summary': summary, + 'text': text, + 'annotations': annotations?.map((a) => a.toJson()).toList(), + 'images': images?.map((i) => i.toJson()).toList(), + }); + } +} + +@immutable +class CheckRunAnnotation { + /// The path of the file to add an annotation to. For example, assets/css/main.css. + final String path; + + /// The start line of the annotation. + final int startLine; + + /// The end line of the annotation. + final int endLine; + + /// The start column of the annotation. + /// Annotations only support start_column and end_column on the same line. + /// Omit this parameter if start_line and end_line have different values. + final int? startColumn; + + /// The end column of the annotation. + /// Annotations only support start_column and end_column on the same line. + /// Omit this parameter if start_line and end_line have different values. + final int? endColumn; + + /// The level of the annotation. + /// Can be one of notice, warning, or failure. + final CheckRunAnnotationLevel annotationLevel; + + /// A short description of the feedback for these lines of code. + /// The maximum size is 64 KB. + final String message; + + /// The title that represents the annotation. + /// The maximum size is 255 characters. + final String title; + + /// Details about this annotation. + /// The maximum size is 64 KB. + final String? rawDetails; + + const CheckRunAnnotation({ + required this.annotationLevel, + required this.endLine, + required this.message, + required this.path, + required this.startLine, + required this.title, + this.startColumn, + this.endColumn, + this.rawDetails, + }) : assert(startColumn == null || startLine == endLine, + 'Annotations only support start_column and end_column on the same line.'), + assert(endColumn == null || startLine == endLine, + 'Annotations only support start_column and end_column on the same line.'), + assert(title.length <= 255); + + @override + bool operator ==(Object other) { + if (other is CheckRunAnnotation) { + return other.annotationLevel == annotationLevel && + other.path == path && + other.startColumn == startColumn && + other.endColumn == endColumn && + other.startLine == startLine && + other.endLine == endLine && + other.title == title && + other.message == message && + other.rawDetails == rawDetails; + } + return false; + } + + @override + int get hashCode => path.hashCode; + + factory CheckRunAnnotation.fromJSON(Map input) { + return CheckRunAnnotation( + path: input['path'], + startLine: input['start_line'], + endLine: input['end_line'], + startColumn: input['start_column'], + endColumn: input['end_column'], + annotationLevel: + CheckRunAnnotationLevel._fromValue(input['annotation_level']), + title: input['title'], + message: input['message'], + rawDetails: input['raw_details'], + ); + } + + Map toJson() { + return createNonNullMap({ + 'path': path, + 'start_line': startLine, + 'end_line': endLine, + 'start_column': startColumn, + 'end_column': endColumn, + 'annotation_level': annotationLevel.toString(), + 'message': message, + 'title': title, + 'rax_details': rawDetails, + }); + } +} + +@immutable +class CheckRunImage { + /// The alternative text for the image. + final String alternativeText; + + /// The full URL of the image. + final String imageUrl; + + /// A short image description. + final String? caption; + + const CheckRunImage({ + required this.alternativeText, + required this.imageUrl, + this.caption, + }); + + Map toJson() { + return createNonNullMap({ + 'alt': alternativeText, + 'image_url': imageUrl, + 'caption': caption, + }); + } +} + +@immutable +class CheckRunAction { + /// The text to be displayed on a button in the web UI. + /// The maximum size is 20 characters. + final String label; + + /// A short explanation of what this action would do. + /// The maximum size is 40 characters. + final String description; + + /// A reference for the action on the integrator's system. + /// The maximum size is 20 characters. + final String identifier; + + const CheckRunAction({ + required this.label, + required this.description, + required this.identifier, + }) : assert(label.length <= 20), + assert(description.length <= 40), + assert(identifier.length <= 20); + + Map toJson() { + return createNonNullMap({ + 'label': label, + 'description': description, + 'identifier': identifier, + }); + } +} + +/// API docs: https://docs.github.com/en/rest/reference/checks#check-suites +@immutable +class CheckSuite { + final int? id; + final String? headBranch; + final String? headSha; + final CheckRunConclusion conclusion; + final List pullRequests; + + const CheckSuite({ + required this.conclusion, + required this.headBranch, + required this.headSha, + required this.id, + required this.pullRequests, + }); + + factory CheckSuite.fromJson(Map input) { + var pullRequestsJson = input['pull_requests'] as List; + var pullRequests = pullRequestsJson + .map((dynamic json) => + PullRequest.fromJson(json as Map)) + .toList(); + return CheckSuite( + conclusion: CheckRunConclusion._fromValue(input['conclusion']), + headBranch: input['head_branch'], + headSha: input['head_sha'], + id: input['id'], + pullRequests: pullRequests, + ); + } + + Map toJson() { + return { + 'conclusion': conclusion, + 'head_sha': headSha, + 'id': id, + }; + } +} + +@immutable +class AutoTriggerChecks { + /// The id of the GitHub App. + final int appId; + + /// Set to true to enable automatic creation of CheckSuite events upon pushes to the repository, or false to disable them. + final bool? setting; + + const AutoTriggerChecks({ + required this.appId, + this.setting = true, + }); + + factory AutoTriggerChecks.fromJson(Map input) { + return AutoTriggerChecks( + appId: input['app_id'], + setting: input['setting'], + ); + } + + Map toJson() => {'app_id': appId, 'setting': setting}; +} diff --git a/lib/src/common/model/explore.dart b/lib/src/common/model/explore.dart deleted file mode 100644 index 8fe585a8..00000000 --- a/lib/src/common/model/explore.dart +++ /dev/null @@ -1,26 +0,0 @@ -part of github.common; - -class TrendingRepository { - String rank; - html.Element titleObject; - String get title => titleObject.text; - - String get url => "https://github.com/${title}"; - String description; -} - -class ShowcaseInfo { - String title; - String description; - String url; -} - -class Showcase extends ShowcaseInfo { - DateTime lastUpdated; - List items; -} - -class ShowcaseItem { - String name; - String url; -} diff --git a/lib/src/common/model/gists.dart b/lib/src/common/model/gists.dart index 674e53b5..e5360d70 100644 --- a/lib/src/common/model/gists.dart +++ b/lib/src/common/model/gists.dart @@ -1,177 +1,160 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:github/src/common/model/users.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'gists.g.dart'; /// Model class for gists. +@JsonSerializable() class Gist { - String id; - String description; - bool public; - User owner; - User user; - List files; - - @ApiName("html_url") - String htmlUrl; - - @ApiName("comments") - int commentsCount; - - @ApiName("git_pull_url") - String gitPullUrl; - - @ApiName("git_push_url") - String gitPushUrl; - - @ApiName("created_at") - DateTime createdAt; - - @ApiName("updated_at") - DateTime updatedAt; - - static Gist fromJSON(Map input) { - if (input == null) return null; - - var gist = new Gist() - ..id = input['id'] - ..description = input['description'] - ..public = input['public'] - ..owner = User.fromJSON(input['owner'] as Map) - ..user = User.fromJSON(input['user'] as Map); - - if (input['files'] != null) { - gist.files = []; - - for (var key in input['files'].keys) { - var map = copyOf(input['files'][key]) as Map; - map['name'] = key; - gist.files.add(GistFile.fromJSON(map)); - } - } - - gist - ..htmlUrl = input['html_url'] - ..commentsCount = input['comments'] - ..gitPullUrl = input['git_pull_url'] - ..gitPushUrl = input['git_push_url'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']); - - return gist; - } + Gist({ + this.id, + this.description, + this.public, + this.owner, + this.user, + this.files, + this.htmlUrl, + this.commentsCount, + this.gitPullUrl, + this.gitPushUrl, + this.createdAt, + this.updatedAt, + }); + String? id; + String? description; + bool? public; + User? owner; + User? user; + Map? files; + + @JsonKey(name: 'html_url') + String? htmlUrl; + + @JsonKey(name: 'comments') + int? commentsCount; + + @JsonKey(name: 'git_pull_url') + String? gitPullUrl; + + @JsonKey(name: 'git_push_url') + String? gitPushUrl; + + @JsonKey(name: 'created_at') + DateTime? createdAt; + + @JsonKey(name: 'updated_at') + DateTime? updatedAt; + + factory Gist.fromJson(Map input) => _$GistFromJson(input); + Map toJson() => _$GistToJson(this); } /// Model class for a gist file. +@JsonSerializable() class GistFile { - String name; - int size; - - @ApiName("raw_url") - String rawUrl; - String type; - String language; - bool truncated; - String content; - - static GistFile fromJSON(Map input) { - if (input == null) return null; - - return new GistFile() - ..name = input['name'] - ..size = input['size'] - ..rawUrl = input['raw_url'] - ..type = input['type'] - ..language = input['language'] - ..truncated = input['truncated'] - ..content = input['content']; - } + GistFile({ + this.filename, + this.size, + this.rawUrl, + this.type, + this.language, + this.truncated, + this.content, + }); + + String? filename; + int? size; + String? rawUrl; + String? type; + String? language; + bool? truncated; + String? content; + + factory GistFile.fromJson(Map input) => + _$GistFileFromJson(input); + Map toJson() => _$GistFileToJson(this); } /// Model class for a gist fork. +@JsonSerializable() class GistFork { - User user; - int id; - - @ApiName("created_at") - DateTime createdAt; + GistFork({this.user, this.id, this.createdAt, this.updatedAt}); + User? user; + int? id; - @ApiName("updated_at") - DateTime updatedAt; + @JsonKey(name: 'created_at') + DateTime? createdAt; - static GistFork fromJSON(Map input) { - if (input == null) return null; + @JsonKey(name: 'updated_at') + DateTime? updatedAt; - return new GistFork() - ..user = User.fromJSON(input['user'] as Map) - ..id = input['id'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']); - } + factory GistFork.fromJson(Map input) => + _$GistForkFromJson(input); + Map toJson() => _$GistForkToJson(this); } /// Model class for a gits history entry. +@JsonSerializable() class GistHistoryEntry { - String version; - - User user; + GistHistoryEntry({ + this.version, + this.user, + this.deletions, + this.additions, + this.totalChanges, + this.committedAt, + }); + String? version; - @ApiName("change_status/deletions") - int deletions; + User? user; - @ApiName("change_status/additions") - int additions; + @JsonKey(name: 'change_status/deletions') + int? deletions; - @ApiName("change_status/total") - int totalChanges; + @JsonKey(name: 'change_status/additions') + int? additions; - @ApiName("committed_at") - DateTime committedAt; + @JsonKey(name: 'change_status/total') + int? totalChanges; - static GistHistoryEntry fromJSON(Map input) { - if (input == null) return null; + @JsonKey(name: 'committed_at') + DateTime? committedAt; - return new GistHistoryEntry() - ..version = input['version'] - ..user = User.fromJSON(input['user'] as Map) - ..deletions = input['change_status']['deletions'] - ..additions = input['change_status']['additions'] - ..totalChanges = input['change_status']['total'] - ..committedAt = parseDateTime(input['committed_at']); - } + factory GistHistoryEntry.fromJson(Map input) => + _$GistHistoryEntryFromJson(input); + Map toJson() => _$GistHistoryEntryToJson(this); } /// Model class for gist comments. +@JsonSerializable() class GistComment { - int id; - User user; - - @ApiName("created_at") - DateTime createdAt; - - @ApiName("updated_at") - DateTime updatedAt; - - String body; - - static GistComment fromJSON(Map input) { - if (input == null) return null; - - return new GistComment() - ..id = input['id'] - ..user = User.fromJSON(input['user'] as Map) - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..body = input['body']; - } + GistComment({ + this.id, + this.user, + this.createdAt, + this.updatedAt, + this.body, + }); + + int? id; + User? user; + DateTime? createdAt; + DateTime? updatedAt; + String? body; + + factory GistComment.fromJson(Map input) => + _$GistCommentFromJson(input); + Map toJson() => _$GistCommentToJson(this); } /// Model class for a new gist comment to be created. +@JsonSerializable() class CreateGistComment { - final String body; - CreateGistComment(this.body); + String? body; - String toJSON() { - var map = {}; - map['body'] = body; - return JSON.encode(map); - } + factory CreateGistComment.fromJson(Map input) => + _$CreateGistCommentFromJson(input); + Map toJson() => _$CreateGistCommentToJson(this); } diff --git a/lib/src/common/model/gists.g.dart b/lib/src/common/model/gists.g.dart new file mode 100644 index 00000000..3438e5ea --- /dev/null +++ b/lib/src/common/model/gists.g.dart @@ -0,0 +1,144 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'gists.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Gist _$GistFromJson(Map json) => Gist( + id: json['id'] as String?, + description: json['description'] as String?, + public: json['public'] as bool?, + owner: json['owner'] == null + ? null + : User.fromJson(json['owner'] as Map), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + files: (json['files'] as Map?)?.map( + (k, e) => MapEntry(k, GistFile.fromJson(e as Map)), + ), + htmlUrl: json['html_url'] as String?, + commentsCount: (json['comments'] as num?)?.toInt(), + gitPullUrl: json['git_pull_url'] as String?, + gitPushUrl: json['git_push_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$GistToJson(Gist instance) => { + 'id': instance.id, + 'description': instance.description, + 'public': instance.public, + 'owner': instance.owner, + 'user': instance.user, + 'files': instance.files, + 'html_url': instance.htmlUrl, + 'comments': instance.commentsCount, + 'git_pull_url': instance.gitPullUrl, + 'git_push_url': instance.gitPushUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +GistFile _$GistFileFromJson(Map json) => GistFile( + filename: json['filename'] as String?, + size: (json['size'] as num?)?.toInt(), + rawUrl: json['raw_url'] as String?, + type: json['type'] as String?, + language: json['language'] as String?, + truncated: json['truncated'] as bool?, + content: json['content'] as String?, + ); + +Map _$GistFileToJson(GistFile instance) => { + 'filename': instance.filename, + 'size': instance.size, + 'raw_url': instance.rawUrl, + 'type': instance.type, + 'language': instance.language, + 'truncated': instance.truncated, + 'content': instance.content, + }; + +GistFork _$GistForkFromJson(Map json) => GistFork( + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + id: (json['id'] as num?)?.toInt(), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$GistForkToJson(GistFork instance) => { + 'user': instance.user, + 'id': instance.id, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +GistHistoryEntry _$GistHistoryEntryFromJson(Map json) => + GistHistoryEntry( + version: json['version'] as String?, + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + deletions: (json['change_status/deletions'] as num?)?.toInt(), + additions: (json['change_status/additions'] as num?)?.toInt(), + totalChanges: (json['change_status/total'] as num?)?.toInt(), + committedAt: json['committed_at'] == null + ? null + : DateTime.parse(json['committed_at'] as String), + ); + +Map _$GistHistoryEntryToJson(GistHistoryEntry instance) => + { + 'version': instance.version, + 'user': instance.user, + 'change_status/deletions': instance.deletions, + 'change_status/additions': instance.additions, + 'change_status/total': instance.totalChanges, + 'committed_at': instance.committedAt?.toIso8601String(), + }; + +GistComment _$GistCommentFromJson(Map json) => GistComment( + id: (json['id'] as num?)?.toInt(), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + body: json['body'] as String?, + ); + +Map _$GistCommentToJson(GistComment instance) => + { + 'id': instance.id, + 'user': instance.user, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'body': instance.body, + }; + +CreateGistComment _$CreateGistCommentFromJson(Map json) => + CreateGistComment( + json['body'] as String?, + ); + +Map _$CreateGistCommentToJson(CreateGistComment instance) => + { + 'body': instance.body, + }; diff --git a/lib/src/common/model/git.dart b/lib/src/common/model/git.dart index ffd26ad1..136c01c4 100644 --- a/lib/src/common/model/git.dart +++ b/lib/src/common/model/git.dart @@ -1,334 +1,267 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'git.g.dart'; /// Model class for a blob. +@JsonSerializable() class GitBlob { - String content; - String encoding; - String url; - String sha; - int size; - - static GitBlob fromJSON(Map input) { - if (input == null) return null; - - return new GitBlob() - ..content = (input['content'] as String)?.trim() // includes newline? - ..encoding = input['encoding'] - ..url = input['url'] - ..sha = input['sha'] - ..size = input['size']; - } + GitBlob({ + this.content, + this.encoding, + this.url, + this.sha, + this.size, + }); + String? content; + String? encoding; + String? url; + String? sha; + int? size; + + factory GitBlob.fromJson(Map input) => + _$GitBlobFromJson(input); + Map toJson() => _$GitBlobToJson(this); } /// Model class for a new blob to be created. /// /// The [encoding] can be either 'utf-8' or 'base64'. +@JsonSerializable() class CreateGitBlob { - final String content; - final String encoding; - CreateGitBlob(this.content, this.encoding); - String toJSON() { - return JSON.encode({"content": content, "encoding": encoding}); - } + String? content; + String? encoding; + + factory CreateGitBlob.fromJson(Map input) => + _$CreateGitBlobFromJson(input); + Map toJson() => _$CreateGitBlobToJson(this); } /// Model class for a git commit. /// /// Note: This is the raw [GitCommit]. The [RepositoryCommit] is a repository /// commit containing GitHub-specific information. +@JsonSerializable() class GitCommit { - String sha; - String url; - GitCommitUser author; - GitCommitUser committer; - String message; - GitTree tree; - List parents; - - @ApiName('comment_count') - int commentCount; - - static GitCommit fromJSON(Map input) { - if (input == null) return null; - - var commit = new GitCommit() - ..sha = input['sha'] - ..url = input['url'] - ..message = input['message'] - ..commentCount = input['comment_count']; - - if (input['author'] != null) { - commit.author = - GitCommitUser.fromJSON(input['author'] as Map); - } - - if (input['committer'] != null) { - commit.committer = - GitCommitUser.fromJSON(input['committer'] as Map); - } - - if (input['tree'] != null) { - commit.tree = GitTree.fromJSON(input['tree'] as Map); - } - - if (input['parents'] != null) { - commit.parents = (input['parents'] as List>) - .map((Map parent) => GitCommit.fromJSON(parent)) - .toList(); - } - - return commit; - } + GitCommit({ + this.sha, + this.url, + this.author, + this.committer, + this.message, + this.tree, + this.parents, + this.commentCount, + }); + String? sha; + String? url; + GitCommitUser? author; + GitCommitUser? committer; + String? message; + GitTree? tree; + List? parents; + + @JsonKey(name: 'comment_count') + int? commentCount; + + factory GitCommit.fromJson(Map input) => + _$GitCommitFromJson(input); + Map toJson() => _$GitCommitToJson(this); } /// Model class for a new commit to be created. +@JsonSerializable() class CreateGitCommit { + CreateGitCommit(this.message, this.tree, + {this.parents, this.committer, this.author}); + /// The commit message. - final String message; + String? message; /// The SHA of the tree object this commit points to. - final String tree; + String? tree; /// The SHAs of the commits that were the parents of this commit. If omitted /// or empty, the commit will be written as a root commit. - List parents; + List? parents; /// Info about the committer. - GitCommitUser committer; + GitCommitUser? committer; /// Info about the author. - GitCommitUser author; - - CreateGitCommit(this.message, this.tree); - - String toJSON() { - var map = {}; - putValue('message', message, map); - putValue('tree', tree, map); - putValue('parents', parents, map); - - if (committer != null) { - putValue('committer', committer.toMap(), map); - } + GitCommitUser? author; - if (author != null) { - putValue('author', author.toMap(), map); - } - - return JSON.encode(map); - } + factory CreateGitCommit.fromJson(Map input) => + _$CreateGitCommitFromJson(input); + Map toJson() => _$CreateGitCommitToJson(this); } /// Model class for an author or committer of a commit. The [GitCommitUser] may /// not correspond to a GitHub [User]. +@JsonSerializable(includeIfNull: false) class GitCommitUser { - final String name; - final String email; - final DateTime date; - GitCommitUser(this.name, this.email, this.date); - static GitCommitUser fromJSON(Map input) { - if (input == null) return null; - - return new GitCommitUser( - input['name'], input['email'], parseDateTime(input['date'])); - } + String? name; + String? email; + @JsonKey(toJson: dateToGitHubIso8601) + DateTime? date; - Map toMap() { - var map = {}; + factory GitCommitUser.fromJson(Map json) => + _$GitCommitUserFromJson(json); - putValue('name', name, map); - putValue('email', email, map); - putValue('date', dateToGitHubIso8601(date), map); - - return map; - } + Map toJson() => _$GitCommitUserToJson(this); } /// Model class for a GitHub tree. +@JsonSerializable() class GitTree { - String sha; - String url; + String? sha; + String? url; /// If truncated is true, the number of items in the tree array exceeded /// GitHub's maximum limit. - bool truncated; - - @ApiName("tree") - List entries; - - static GitTree fromJSON(Map input) { - if (input == null) return null; - - var tree = new GitTree() - ..sha = input['sha'] - ..url = input['url'] - ..truncated = input['truncated']; - - // There are no tree entries if it's a tree referenced from a GitCommit. - if (input['tree'] != null) { - tree.entries = (input['tree'] as List>) - .map((Map it) => GitTreeEntry.fromJSON(it)) - .toList(growable: false); - } - return tree; - } + bool? truncated; + + @JsonKey(name: 'tree') + List? entries; + + GitTree(this.sha, this.url, this.truncated, this.entries); + + factory GitTree.fromJson(Map input) => + _$GitTreeFromJson(input); + Map toJson() => _$GitTreeToJson(this); } /// Model class for the contents of a tree structure. [GitTreeEntry] can /// represent either a blog, a commit (in the case of a submodule), or another /// tree. +@JsonSerializable() class GitTreeEntry { - String path; - String mode; - String type; - int size; - String sha; - String url; - - static GitTreeEntry fromJSON(Map input) { - if (input == null) return null; - - return new GitTreeEntry() - ..path = input['path'] - ..mode = input['mode'] - ..type = input['type'] - ..size = input['size'] - ..sha = input['sha'] - ..url = input['url']; - } + String? path; + String? mode; + String? type; + int? size; + String? sha; + String? url; + + GitTreeEntry(this.path, this.mode, this.type, this.size, this.sha, this.url); + + factory GitTreeEntry.fromJson(Map input) => + _$GitTreeEntryFromJson(input); + Map toJson() => _$GitTreeEntryToJson(this); } /// Model class for a new tree to be created. +@JsonSerializable() class CreateGitTree { + CreateGitTree(this.entries, {this.baseTree}); + /// The SHA1 of the tree you want to update with new data. /// If you don’t set this, the commit will be created on top of everything; /// however, it will only contain your change, the rest of your files will /// show up as deleted. - @ApiName("base_tree") - String baseTree; + String? baseTree; /// The Objects specifying a tree structure. - @ApiName("tree") - final List entries; - - CreateGitTree(this.entries); - - String toJSON() { - var map = {}; - - putValue('base_tree', baseTree, map); - - if (entries.isNotEmpty) { - putValue( - 'tree', entries.map((e) => e.toMap()).toList(growable: false), map); - } + @JsonKey(name: 'tree') + List? entries; - return JSON.encode(map); - } + factory CreateGitTree.fromJson(Map input) => + _$CreateGitTreeFromJson(input); + Map toJson() => _$CreateGitTreeToJson(this); } /// Model class for a new tree entry to be created. +@JsonSerializable() class CreateGitTreeEntry { - final String path; - final String mode; - final String type; - final String sha; - final String content; - /// Constructor. /// Either [sha] or [content] must be defined. - CreateGitTreeEntry(this.path, this.mode, this.type, {this.sha, this.content}); - - Map toMap() { - var map = {}; - - putValue('path', path, map); - putValue('mode', mode, map); - putValue('type', type, map); - putValue('sha', sha, map); - putValue('content', content, map); - - return map; - } + CreateGitTreeEntry( + this.path, + this.mode, + this.type, { + this.sha, + this.content, + }); + String? path; + String? mode; + String? type; + String? sha; + String? content; + + factory CreateGitTreeEntry.fromJson(Map input) => + _$CreateGitTreeEntryFromJson(input); + Map toJson() => _$CreateGitTreeEntryToJson(this); } /// Model class for a reference. +@JsonSerializable() class GitReference { - String ref; - String url; - GitObject object; - - static GitReference fromJSON(Map input) { - if (input == null) return null; - - return new GitReference() - ..ref = input['ref'] - ..url = input['url'] - ..object = GitObject.fromJSON(input['object'] as Map); - } + GitReference({ + this.ref, + this.url, + this.object, + }); + String? ref; + String? url; + GitObject? object; + + factory GitReference.fromJson(Map input) => + _$GitReferenceFromJson(input); + Map toJson() => _$GitReferenceToJson(this); } /// Model class for a tag. +@JsonSerializable() class GitTag { - String tag; - String sha; - String url; - String message; - GitCommitUser tagger; - GitObject object; - - static GitTag fromJSON(Map input) { - if (input == null) return null; - - return new GitTag() - ..tag = input['tag'] - ..sha = input['sha'] - ..url = input['url'] - ..message = input['message'] - ..tagger = GitCommitUser.fromJSON(input['tagger'] as Map) - ..object = GitObject.fromJSON(input['object'] as Map); - } + GitTag({ + this.tag, + this.sha, + this.url, + this.message, + this.tagger, + this.object, + }); + String? tag; + String? sha; + String? url; + String? message; + GitCommitUser? tagger; + GitObject? object; + + factory GitTag.fromJson(Map input) => + _$GitTagFromJson(input); + Map toJson() => _$GitTagToJson(this); } /// Model class for a new tag to be created. +@JsonSerializable() class CreateGitTag { - final String tag; - final String message; - String object; - String type; - final GitCommitUser tagger; - CreateGitTag(this.tag, this.message, this.object, this.type, this.tagger); - String toJSON() { - var map = {}; - - putValue('tag', tag, map); - putValue('message', message, map); - putValue('object', object, map); - putValue('type', type, map); - putValue('tagger', tagger.toMap(), map); + String? tag; + String? message; + String? object; + String? type; + GitCommitUser? tagger; - return JSON.encode(map); - } + factory CreateGitTag.fromJson(Map input) => + _$CreateGitTagFromJson(input); + Map toJson() => _$CreateGitTagToJson(this); } /// Model class for an object referenced by [GitReference] and [GitTag]. +@JsonSerializable() class GitObject { - String type; - String sha; - String url; - - static GitObject fromJSON(Map input) { - if (input == null) return null; - - return new GitObject() - ..type = input['type'] - ..sha = input['sha'] - ..url = input['url']; - } + GitObject(this.type, this.sha, this.url); + String? type; + String? sha; + String? url; + + factory GitObject.fromJson(Map input) => + _$GitObjectFromJson(input); + Map toJson() => _$GitObjectToJson(this); } diff --git a/lib/src/common/model/git.g.dart b/lib/src/common/model/git.g.dart new file mode 100644 index 00000000..ccbb082b --- /dev/null +++ b/lib/src/common/model/git.g.dart @@ -0,0 +1,238 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'git.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GitBlob _$GitBlobFromJson(Map json) => GitBlob( + content: json['content'] as String?, + encoding: json['encoding'] as String?, + url: json['url'] as String?, + sha: json['sha'] as String?, + size: (json['size'] as num?)?.toInt(), + ); + +Map _$GitBlobToJson(GitBlob instance) => { + 'content': instance.content, + 'encoding': instance.encoding, + 'url': instance.url, + 'sha': instance.sha, + 'size': instance.size, + }; + +CreateGitBlob _$CreateGitBlobFromJson(Map json) => + CreateGitBlob( + json['content'] as String?, + json['encoding'] as String?, + ); + +Map _$CreateGitBlobToJson(CreateGitBlob instance) => + { + 'content': instance.content, + 'encoding': instance.encoding, + }; + +GitCommit _$GitCommitFromJson(Map json) => GitCommit( + sha: json['sha'] as String?, + url: json['url'] as String?, + author: json['author'] == null + ? null + : GitCommitUser.fromJson(json['author'] as Map), + committer: json['committer'] == null + ? null + : GitCommitUser.fromJson(json['committer'] as Map), + message: json['message'] as String?, + tree: json['tree'] == null + ? null + : GitTree.fromJson(json['tree'] as Map), + parents: (json['parents'] as List?) + ?.map((e) => GitCommit.fromJson(e as Map)) + .toList(), + commentCount: (json['comment_count'] as num?)?.toInt(), + ); + +Map _$GitCommitToJson(GitCommit instance) => { + 'sha': instance.sha, + 'url': instance.url, + 'author': instance.author, + 'committer': instance.committer, + 'message': instance.message, + 'tree': instance.tree, + 'parents': instance.parents, + 'comment_count': instance.commentCount, + }; + +CreateGitCommit _$CreateGitCommitFromJson(Map json) => + CreateGitCommit( + json['message'] as String?, + json['tree'] as String?, + parents: (json['parents'] as List?) + ?.map((e) => e as String?) + .toList(), + committer: json['committer'] == null + ? null + : GitCommitUser.fromJson(json['committer'] as Map), + author: json['author'] == null + ? null + : GitCommitUser.fromJson(json['author'] as Map), + ); + +Map _$CreateGitCommitToJson(CreateGitCommit instance) => + { + 'message': instance.message, + 'tree': instance.tree, + 'parents': instance.parents, + 'committer': instance.committer, + 'author': instance.author, + }; + +GitCommitUser _$GitCommitUserFromJson(Map json) => + GitCommitUser( + json['name'] as String?, + json['email'] as String?, + json['date'] == null ? null : DateTime.parse(json['date'] as String), + ); + +Map _$GitCommitUserToJson(GitCommitUser instance) => + { + if (instance.name case final value?) 'name': value, + if (instance.email case final value?) 'email': value, + if (dateToGitHubIso8601(instance.date) case final value?) 'date': value, + }; + +GitTree _$GitTreeFromJson(Map json) => GitTree( + json['sha'] as String?, + json['url'] as String?, + json['truncated'] as bool?, + (json['tree'] as List?) + ?.map((e) => GitTreeEntry.fromJson(e as Map)) + .toList(), + ); + +Map _$GitTreeToJson(GitTree instance) => { + 'sha': instance.sha, + 'url': instance.url, + 'truncated': instance.truncated, + 'tree': instance.entries, + }; + +GitTreeEntry _$GitTreeEntryFromJson(Map json) => GitTreeEntry( + json['path'] as String?, + json['mode'] as String?, + json['type'] as String?, + (json['size'] as num?)?.toInt(), + json['sha'] as String?, + json['url'] as String?, + ); + +Map _$GitTreeEntryToJson(GitTreeEntry instance) => + { + 'path': instance.path, + 'mode': instance.mode, + 'type': instance.type, + 'size': instance.size, + 'sha': instance.sha, + 'url': instance.url, + }; + +CreateGitTree _$CreateGitTreeFromJson(Map json) => + CreateGitTree( + (json['tree'] as List?) + ?.map((e) => CreateGitTreeEntry.fromJson(e as Map)) + .toList(), + baseTree: json['base_tree'] as String?, + ); + +Map _$CreateGitTreeToJson(CreateGitTree instance) => + { + 'base_tree': instance.baseTree, + 'tree': instance.entries, + }; + +CreateGitTreeEntry _$CreateGitTreeEntryFromJson(Map json) => + CreateGitTreeEntry( + json['path'] as String?, + json['mode'] as String?, + json['type'] as String?, + sha: json['sha'] as String?, + content: json['content'] as String?, + ); + +Map _$CreateGitTreeEntryToJson(CreateGitTreeEntry instance) => + { + 'path': instance.path, + 'mode': instance.mode, + 'type': instance.type, + 'sha': instance.sha, + 'content': instance.content, + }; + +GitReference _$GitReferenceFromJson(Map json) => GitReference( + ref: json['ref'] as String?, + url: json['url'] as String?, + object: json['object'] == null + ? null + : GitObject.fromJson(json['object'] as Map), + ); + +Map _$GitReferenceToJson(GitReference instance) => + { + 'ref': instance.ref, + 'url': instance.url, + 'object': instance.object, + }; + +GitTag _$GitTagFromJson(Map json) => GitTag( + tag: json['tag'] as String?, + sha: json['sha'] as String?, + url: json['url'] as String?, + message: json['message'] as String?, + tagger: json['tagger'] == null + ? null + : GitCommitUser.fromJson(json['tagger'] as Map), + object: json['object'] == null + ? null + : GitObject.fromJson(json['object'] as Map), + ); + +Map _$GitTagToJson(GitTag instance) => { + 'tag': instance.tag, + 'sha': instance.sha, + 'url': instance.url, + 'message': instance.message, + 'tagger': instance.tagger, + 'object': instance.object, + }; + +CreateGitTag _$CreateGitTagFromJson(Map json) => CreateGitTag( + json['tag'] as String?, + json['message'] as String?, + json['object'] as String?, + json['type'] as String?, + json['tagger'] == null + ? null + : GitCommitUser.fromJson(json['tagger'] as Map), + ); + +Map _$CreateGitTagToJson(CreateGitTag instance) => + { + 'tag': instance.tag, + 'message': instance.message, + 'object': instance.object, + 'type': instance.type, + 'tagger': instance.tagger, + }; + +GitObject _$GitObjectFromJson(Map json) => GitObject( + json['type'] as String?, + json['sha'] as String?, + json['url'] as String?, + ); + +Map _$GitObjectToJson(GitObject instance) => { + 'type': instance.type, + 'sha': instance.sha, + 'url': instance.url, + }; diff --git a/lib/src/common/model/issues.dart b/lib/src/common/model/issues.dart index 0279e4c8..3d531e63 100644 --- a/lib/src/common/model/issues.dart +++ b/lib/src/common/model/issues.dart @@ -1,14 +1,60 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'issues.g.dart'; /// Model class for an issue on the tracker. +@JsonSerializable() class Issue { + Issue({ + this.id = 0, + this.url = '', + this.htmlUrl = '', + this.number = 0, + this.state = '', + this.title = '', + this.user, + List? labels, + this.assignee, + this.assignees, + this.milestone, + this.commentsCount = 0, + this.pullRequest, + this.createdAt, + this.closedAt, + this.updatedAt, + this.body = '', + this.closedBy, + + // Properties from the Timeline API + this.activeLockReason, + this.authorAssociation, + this.bodyHtml, + this.bodyText, + this.commentsUrl, + this.draft, + this.eventsUrl, + this.labelsUrl, + this.locked, + this.nodeId, + this.performedViaGithubApp, + this.reactions, + this.repository, + this.repositoryUrl, + this.stateReason, + this.timelineUrl, + }) { + if (labels != null) { + this.labels = labels; + } + } + int id; /// The api url. String url; /// Url to the Issue Page - @ApiName("html_url") String htmlUrl; /// Issue Number @@ -21,254 +67,282 @@ class Issue { String title; /// User who created the issue. - User user; + User? user; /// Issue Labels - List labels; + @JsonKey(defaultValue: []) + List labels = []; + + /// The User that the issue is assigned to + User? assignee; /// The User that the issue is assigned to - User assignee; + List? assignees; /// The Milestone - Milestone milestone; + Milestone? milestone; /// Number of Comments - @ApiName("comments") + @JsonKey(name: 'comments') int commentsCount; /// A Pull Request - @ApiName("pull_request") - IssuePullRequest pullRequest; + IssuePullRequest? pullRequest; /// Time that the issue was created at - @ApiName("created_at") - DateTime createdAt; + DateTime? createdAt; /// The time that the issue was closed at - @ApiName("closed_at") - DateTime closedAt; + DateTime? closedAt; /// The time that the issue was updated at - @ApiName("updated_at") - DateTime updatedAt; + DateTime? updatedAt; String body; /// The user who closed the issue - @ApiName("closed_by") - User closedBy; - - static Issue fromJSON(Map input) { - if (input == null) return null; - - var labels = input['labels'] as List>; - if (labels == null) labels = []; - - return new Issue() - ..id = input['id'] - ..url = input['url'] - ..htmlUrl = input['html_url'] - ..number = input['number'] - ..state = input['state'] - ..title = input['title'] - ..user = User.fromJSON(input['user'] as Map) - ..labels = labels.map(IssueLabel.fromJSON).toList(growable: false) - ..assignee = User.fromJSON(input['assignee'] as Map) - ..milestone = - Milestone.fromJSON(input['milestone'] as Map) - ..commentsCount = input['comments'] - ..pullRequest = IssuePullRequest - .fromJSON(input['pull_request'] as Map) - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..closedAt = parseDateTime(input['closed_at']) - ..closedBy = User.fromJSON(input['closed_by'] as Map) - ..body = input['body']; - } + User? closedBy; - bool get isOpen => state == "open"; - bool get isClosed => state == "closed"; -} + bool get isOpen => state.toUpperCase() == 'OPEN'; + bool get isClosed => state.toUpperCase() == 'CLOSED'; -/// Model class for a request to create/edit an issue. -class IssueRequest { - String title; - String body; - List labels; - String assignee; - String state; - int milestone; - - IssueRequest(); - - String toJSON() { - var map = {}; - putValue("title", title, map); - putValue("body", body, map); - putValue("labels", labels, map); - putValue("assignee", assignee, map); - putValue("state", state, map); - putValue("milestone", milestone, map); - return JSON.encode(map); - } -} + // The following properties were added to support the Timeline API. -/// Model class for a pull request for an issue. -class IssuePullRequest { - /// Url to the Page for this Issue Pull Request - @ApiName("html_url") - String htmlUrl; + String? activeLockReason; - /// Diff Url - @ApiName("diff_url") - String diffUrl; + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; - /// Patch Url - @ApiName("patch_url") - String patchUrl; + String? bodyHtml; - static IssuePullRequest fromJSON(Map input) { - if (input == null) return null; + String? bodyText; - return new IssuePullRequest() - ..htmlUrl = input['html_url'] - ..diffUrl = input['diff_url'] - ..patchUrl = input['patch_url']; - } -} + String? commentsUrl; -/// Model class for an issue comment. -class IssueComment { - int id; + bool? draft; - String body; + String? eventsUrl; - User user; + String? labelsUrl; - DateTime createdAt; + bool? locked; - DateTime updatedAt; + String? nodeId; - String url; + GitHubApp? performedViaGithubApp; - @ApiName("html_url") - String htmlUrl; + ReactionRollup? reactions; - @ApiName("issue_url") - String issueUrl; - - static IssueComment fromJSON(Map input) { - if (input == null) return null; - - return new IssueComment() - ..id = input['id'] - ..body = input['body'] - ..user = User.fromJSON(input['user'] as Map) - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..url = input['url'] - ..htmlUrl = input['html_url'] - ..issueUrl = input['issue_url']; - } + Repository? repository; + + String? repositoryUrl; + + /// The reason for the current state + /// + /// Example: `not_planned` + String? stateReason; + + String? timelineUrl; + + factory Issue.fromJson(Map input) => _$IssueFromJson(input); + Map toJson() => _$IssueToJson(this); +} + +/// Model class for a request to create/edit an issue. +@JsonSerializable() +class IssueRequest { + IssueRequest( + {this.title, + this.body, + this.labels, + this.assignee, + this.assignees, + this.state, + this.milestone}); + String? title; + String? body; + List? labels; + String? assignee; + List? assignees; + String? state; + int? milestone; + + Map toJson() => _$IssueRequestToJson(this); + + factory IssueRequest.fromJson(Map input) => + _$IssueRequestFromJson(input); +} + +/// Model class for a pull request for an issue. +@JsonSerializable() +class IssuePullRequest { + IssuePullRequest({ + this.htmlUrl, + this.diffUrl, + this.patchUrl, + }); + + /// Url to the Page for this Issue Pull Request + String? htmlUrl; + String? diffUrl; + String? patchUrl; + + factory IssuePullRequest.fromJson(Map input) => + _$IssuePullRequestFromJson(input); + Map toJson() => _$IssuePullRequestToJson(this); +} + +/// Model class for an issue comment. +@JsonSerializable() +class IssueComment { + IssueComment({ + this.id, + this.body, + this.user, + this.createdAt, + this.updatedAt, + this.url, + this.htmlUrl, + this.issueUrl, + this.authorAssociation, + }); + int? id; + String? body; + User? user; + DateTime? createdAt; + DateTime? updatedAt; + String? url; + String? htmlUrl; + String? issueUrl; + String? authorAssociation; + + factory IssueComment.fromJson(Map input) => + _$IssueCommentFromJson(input); + Map toJson() => _$IssueCommentToJson(this); } /// Model class for an issue label. +@JsonSerializable() class IssueLabel { - /// Label Name + IssueLabel({ + this.name = '', + this.color = '', + this.description = '', + }); + String name; - /// Label Color String color; - static IssueLabel fromJSON(Map input) { - if (input == null) return null; + String description; - assert(input['name'] != null); - assert(input['color'] != null); - - return new IssueLabel() - ..name = input['name'] - ..color = input['color']; - } + factory IssueLabel.fromJson(Map input) => + _$IssueLabelFromJson(input); + Map toJson() => _$IssueLabelToJson(this); @override String toString() => 'IssueLabel: $name'; } /// Model class for a milestone. +@JsonSerializable() class Milestone { + Milestone({ + this.id, + this.number, + this.state, + this.title, + this.description, + this.creator, + this.openIssuesCount, + this.closedIssuesCount, + this.createdAt, + this.updatedAt, + this.dueOn, + + // Properties from the Timeline API + this.closedAt, + this.htmlUrl, + this.labelsUrl, + this.nodeId, + this.url, + }); + /// Unique Identifier for Milestone - int id; + int? id; /// Milestone Number - int number; + int? number; /// Milestone State - String state; + String? state; /// Milestone Title - String title; + String? title; /// Milestone Description - String description; + String? description; /// Milestone Creator - User creator; + User? creator; /// Number of Open Issues - @ApiName("open_issues") - int openIssuesCount; + @JsonKey(name: 'open_issues') + int? openIssuesCount; /// Number of Closed Issues - @ApiName("closed_issues") - int closedIssuesCount; + @JsonKey(name: 'closed_issues') + int? closedIssuesCount; /// Time the milestone was created at - @ApiName("created_at") - DateTime createdAt; + DateTime? createdAt; /// The last time the milestone was updated at - @ApiName("updated_at") - DateTime updatedAt; + DateTime? updatedAt; /// The due date for this milestone - @ApiName("due_on") - DateTime dueOn; - - static Milestone fromJSON(Map input) { - if (input == null) return null; - - return new Milestone() - ..id = input['id'] - ..number = input['number'] - ..state = input['state'] - ..title = input['title'] - ..description = input['description'] - ..creator = User.fromJSON(input['creator'] as Map) - ..openIssuesCount = input['open_issues'] - ..closedIssuesCount = input['closed_issues'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..dueOn = parseDateTime(input['due_on']); - } -} + DateTime? dueOn; -/// Model class for a new milestone to be created. -class CreateMilestone { - final String title; + // The following properties were added to support the Timeline API. - String state; - String description; - DateTime dueOn; + DateTime? closedAt; - CreateMilestone(this.title); + /// Example: `https://github.com/octocat/Hello-World/milestones/v1.0` + String? htmlUrl; - String toJSON() { - var map = {}; - putValue("title", title, map); - putValue("state", state, map); - putValue(description, description, map); - putValue("due_on", dateToGitHubIso8601(dueOn), map); - return JSON.encode(map); - } + /// Example: `https://api.github.com/repos/octocat/Hello-World/milestones/1/labels` + String? labelsUrl; + + /// Example: `MDk6TWlsZXN0b25lMTAwMjYwNA==` + String? nodeId; + + /// Example: `https://api.github.com/repos/octocat/Hello-World/milestones/1` + String? url; + + factory Milestone.fromJson(Map input) => + _$MilestoneFromJson(input); + Map toJson() => _$MilestoneToJson(this); +} + +/// Model class for a new milestone to be created. +@JsonSerializable() +class CreateMilestone { + CreateMilestone( + this.title, { + this.state, + this.description, + this.dueOn, + }); + + String? title; + String? state; + String? description; + DateTime? dueOn; + + Map toJson() => _$CreateMilestoneToJson(this); + + factory CreateMilestone.fromJson(Map input) => + _$CreateMilestoneFromJson(input); } diff --git a/lib/src/common/model/issues.g.dart b/lib/src/common/model/issues.g.dart new file mode 100644 index 00000000..e4a80082 --- /dev/null +++ b/lib/src/common/model/issues.g.dart @@ -0,0 +1,258 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'issues.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Issue _$IssueFromJson(Map json) => Issue( + id: (json['id'] as num?)?.toInt() ?? 0, + url: json['url'] as String? ?? '', + htmlUrl: json['html_url'] as String? ?? '', + number: (json['number'] as num?)?.toInt() ?? 0, + state: json['state'] as String? ?? '', + title: json['title'] as String? ?? '', + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + labels: (json['labels'] as List?) + ?.map((e) => IssueLabel.fromJson(e as Map)) + .toList() ?? + [], + assignee: json['assignee'] == null + ? null + : User.fromJson(json['assignee'] as Map), + assignees: (json['assignees'] as List?) + ?.map((e) => User.fromJson(e as Map)) + .toList(), + milestone: json['milestone'] == null + ? null + : Milestone.fromJson(json['milestone'] as Map), + commentsCount: (json['comments'] as num?)?.toInt() ?? 0, + pullRequest: json['pull_request'] == null + ? null + : IssuePullRequest.fromJson( + json['pull_request'] as Map), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + closedAt: json['closed_at'] == null + ? null + : DateTime.parse(json['closed_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + body: json['body'] as String? ?? '', + closedBy: json['closed_by'] == null + ? null + : User.fromJson(json['closed_by'] as Map), + activeLockReason: json['active_lock_reason'] as String?, + authorAssociation: json['author_association'] as String?, + bodyHtml: json['body_html'] as String?, + bodyText: json['body_text'] as String?, + commentsUrl: json['comments_url'] as String?, + draft: json['draft'] as bool?, + eventsUrl: json['events_url'] as String?, + labelsUrl: json['labels_url'] as String?, + locked: json['locked'] as bool?, + nodeId: json['node_id'] as String?, + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + reactions: json['reactions'] == null + ? null + : ReactionRollup.fromJson(json['reactions'] as Map), + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + repositoryUrl: json['repository_url'] as String?, + stateReason: json['state_reason'] as String?, + timelineUrl: json['timeline_url'] as String?, + ); + +Map _$IssueToJson(Issue instance) => { + 'id': instance.id, + 'url': instance.url, + 'html_url': instance.htmlUrl, + 'number': instance.number, + 'state': instance.state, + 'title': instance.title, + 'user': instance.user, + 'labels': instance.labels, + 'assignee': instance.assignee, + 'assignees': instance.assignees, + 'milestone': instance.milestone, + 'comments': instance.commentsCount, + 'pull_request': instance.pullRequest, + 'created_at': instance.createdAt?.toIso8601String(), + 'closed_at': instance.closedAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'body': instance.body, + 'closed_by': instance.closedBy, + 'active_lock_reason': instance.activeLockReason, + 'author_association': instance.authorAssociation, + 'body_html': instance.bodyHtml, + 'body_text': instance.bodyText, + 'comments_url': instance.commentsUrl, + 'draft': instance.draft, + 'events_url': instance.eventsUrl, + 'labels_url': instance.labelsUrl, + 'locked': instance.locked, + 'node_id': instance.nodeId, + 'performed_via_github_app': instance.performedViaGithubApp, + 'reactions': instance.reactions, + 'repository': instance.repository, + 'repository_url': instance.repositoryUrl, + 'state_reason': instance.stateReason, + 'timeline_url': instance.timelineUrl, + }; + +IssueRequest _$IssueRequestFromJson(Map json) => IssueRequest( + title: json['title'] as String?, + body: json['body'] as String?, + labels: + (json['labels'] as List?)?.map((e) => e as String).toList(), + assignee: json['assignee'] as String?, + assignees: (json['assignees'] as List?) + ?.map((e) => e as String) + .toList(), + state: json['state'] as String?, + milestone: (json['milestone'] as num?)?.toInt(), + ); + +Map _$IssueRequestToJson(IssueRequest instance) => + { + 'title': instance.title, + 'body': instance.body, + 'labels': instance.labels, + 'assignee': instance.assignee, + 'assignees': instance.assignees, + 'state': instance.state, + 'milestone': instance.milestone, + }; + +IssuePullRequest _$IssuePullRequestFromJson(Map json) => + IssuePullRequest( + htmlUrl: json['html_url'] as String?, + diffUrl: json['diff_url'] as String?, + patchUrl: json['patch_url'] as String?, + ); + +Map _$IssuePullRequestToJson(IssuePullRequest instance) => + { + 'html_url': instance.htmlUrl, + 'diff_url': instance.diffUrl, + 'patch_url': instance.patchUrl, + }; + +IssueComment _$IssueCommentFromJson(Map json) => IssueComment( + id: (json['id'] as num?)?.toInt(), + body: json['body'] as String?, + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + url: json['url'] as String?, + htmlUrl: json['html_url'] as String?, + issueUrl: json['issue_url'] as String?, + authorAssociation: json['author_association'] as String?, + ); + +Map _$IssueCommentToJson(IssueComment instance) => + { + 'id': instance.id, + 'body': instance.body, + 'user': instance.user, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'url': instance.url, + 'html_url': instance.htmlUrl, + 'issue_url': instance.issueUrl, + 'author_association': instance.authorAssociation, + }; + +IssueLabel _$IssueLabelFromJson(Map json) => IssueLabel( + name: json['name'] as String? ?? '', + color: json['color'] as String? ?? '', + description: json['description'] as String? ?? '', + ); + +Map _$IssueLabelToJson(IssueLabel instance) => + { + 'name': instance.name, + 'color': instance.color, + 'description': instance.description, + }; + +Milestone _$MilestoneFromJson(Map json) => Milestone( + id: (json['id'] as num?)?.toInt(), + number: (json['number'] as num?)?.toInt(), + state: json['state'] as String?, + title: json['title'] as String?, + description: json['description'] as String?, + creator: json['creator'] == null + ? null + : User.fromJson(json['creator'] as Map), + openIssuesCount: (json['open_issues'] as num?)?.toInt(), + closedIssuesCount: (json['closed_issues'] as num?)?.toInt(), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + dueOn: json['due_on'] == null + ? null + : DateTime.parse(json['due_on'] as String), + closedAt: json['closed_at'] == null + ? null + : DateTime.parse(json['closed_at'] as String), + htmlUrl: json['html_url'] as String?, + labelsUrl: json['labels_url'] as String?, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + ); + +Map _$MilestoneToJson(Milestone instance) => { + 'id': instance.id, + 'number': instance.number, + 'state': instance.state, + 'title': instance.title, + 'description': instance.description, + 'creator': instance.creator, + 'open_issues': instance.openIssuesCount, + 'closed_issues': instance.closedIssuesCount, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'due_on': instance.dueOn?.toIso8601String(), + 'closed_at': instance.closedAt?.toIso8601String(), + 'html_url': instance.htmlUrl, + 'labels_url': instance.labelsUrl, + 'node_id': instance.nodeId, + 'url': instance.url, + }; + +CreateMilestone _$CreateMilestoneFromJson(Map json) => + CreateMilestone( + json['title'] as String?, + state: json['state'] as String?, + description: json['description'] as String?, + dueOn: json['due_on'] == null + ? null + : DateTime.parse(json['due_on'] as String), + ); + +Map _$CreateMilestoneToJson(CreateMilestone instance) => + { + 'title': instance.title, + 'state': instance.state, + 'description': instance.description, + 'due_on': instance.dueOn?.toIso8601String(), + }; diff --git a/lib/src/common/model/keys.dart b/lib/src/common/model/keys.dart index 1be5c935..1628a376 100644 --- a/lib/src/common/model/keys.dart +++ b/lib/src/common/model/keys.dart @@ -1,35 +1,37 @@ -part of github.common; +import 'package:json_annotation/json_annotation.dart'; + +part 'keys.g.dart'; /// Model class for a public key. /// /// Note: [PublicKey] is used both by the repositories' deploy keys and by the /// users' public keys. +@JsonSerializable() class PublicKey { - int id; - String key; - String title; - - static PublicKey fromJSON(Map input) { - if (input == null) return null; + PublicKey({ + this.id, + this.key, + this.title, + }); + final int? id; + final String? key; + final String? title; - return new PublicKey() - ..id = input['id'] - ..key = input['key'] - ..title = input['title']; - } + factory PublicKey.fromJson(Map input) => + _$PublicKeyFromJson(input); + Map toJson() => _$PublicKeyToJson(this); } /// Model class for a new public key to be created. +@JsonSerializable() class CreatePublicKey { - final String title; - final String key; - CreatePublicKey(this.title, this.key); - String toJSON() { - var map = {}; - putValue("title", title, map); - putValue("key", key, map); - return JSON.encode(map); - } + final String? title; + final String? key; + + Map toJson() => _$CreatePublicKeyToJson(this); + + factory CreatePublicKey.fromJson(Map input) => + _$CreatePublicKeyFromJson(input); } diff --git a/lib/src/common/model/keys.g.dart b/lib/src/common/model/keys.g.dart new file mode 100644 index 00000000..7cecbcf2 --- /dev/null +++ b/lib/src/common/model/keys.g.dart @@ -0,0 +1,31 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'keys.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PublicKey _$PublicKeyFromJson(Map json) => PublicKey( + id: (json['id'] as num?)?.toInt(), + key: json['key'] as String?, + title: json['title'] as String?, + ); + +Map _$PublicKeyToJson(PublicKey instance) => { + 'id': instance.id, + 'key': instance.key, + 'title': instance.title, + }; + +CreatePublicKey _$CreatePublicKeyFromJson(Map json) => + CreatePublicKey( + json['title'] as String?, + json['key'] as String?, + ); + +Map _$CreatePublicKeyToJson(CreatePublicKey instance) => + { + 'title': instance.title, + 'key': instance.key, + }; diff --git a/lib/src/common/model/misc.dart b/lib/src/common/model/misc.dart index 3c995dc2..18fc0a54 100644 --- a/lib/src/common/model/misc.dart +++ b/lib/src/common/model/misc.dart @@ -1,64 +1,124 @@ -part of github.common; +import 'package:json_annotation/json_annotation.dart'; + +part 'misc.g.dart'; /// Model class for a Gitignore Template. +@JsonSerializable() class GitignoreTemplate { + GitignoreTemplate({this.name, this.source}); + /// Template Name - String name; + final String? name; /// Template Source - String source; - - static GitignoreTemplate fromJSON(Map input) { - if (input == null) return null; + final String? source; - return new GitignoreTemplate() - ..name = input['name'] - ..source = input['source']; - } + factory GitignoreTemplate.fromJson(Map input) => + _$GitignoreTemplateFromJson(input); + Map toJson() => _$GitignoreTemplateToJson(this); } /// Model class for GitHub Rate Limit Information. +@JsonSerializable() class RateLimit { /// Maximum number of requests - final int limit; + final int? limit; /// Remaining number of requests - final int remaining; + final int? remaining; /// Time when the limit expires - final DateTime resets; + final DateTime? resets; RateLimit(this.limit, this.remaining, this.resets); - static RateLimit fromHeaders(Map headers) { - var limit = int.parse(headers['x-ratelimit-limit']); - var remaining = int.parse(headers['x-ratelimit-remaining']); - var resets = new DateTime.fromMillisecondsSinceEpoch( - int.parse(headers['x-ratelimit-reset']) * 1000); - return new RateLimit(limit, remaining, resets); + factory RateLimit.fromHeaders(Map headers) { + final limit = int.parse(headers['x-ratelimit-limit']!); + final remaining = int.parse(headers['x-ratelimit-remaining']!); + final resets = DateTime.fromMillisecondsSinceEpoch( + int.parse(headers['x-ratelimit-reset']!) * 1000); + return RateLimit(limit, remaining, resets); } + + /// Construct [RateLimit] from JSON response of /rate_limit. + /// + /// API docs: https://developer.github.com/v3/rate_limit/ + factory RateLimit.fromRateLimitResponse(Map response) { + final rateJson = response['rate'] == null + ? null + : response['rate'] as Map; + final limit = rateJson?['limit'] as int?; + final remaining = rateJson?['remaining'] as int?; + final resets = rateJson?['reset'] == null + ? null + : DateTime.fromMillisecondsSinceEpoch(rateJson?['reset']); + return RateLimit(limit, remaining, resets); + } + + factory RateLimit.fromJson(Map input) => + _$RateLimitFromJson(input); + Map toJson() => _$RateLimitToJson(this); } -/// Model class for the GitHub api status. +/// Model class for the GitHub API status. +@JsonSerializable() class APIStatus { - String status; + APIStatus({ + this.page, + this.status, + }); - @ApiName("last_updated") - DateTime lastUpdatedAt; + /// Details about where to find more information. + final APIStatusPage? page; - @ApiName("created_on") - DateTime createdOn; + /// An overview of the current status. + final APIStatusMessage? status; - @ApiName("body") - String message; + factory APIStatus.fromJson(Map input) => + _$APIStatusFromJson(input); + Map toJson() => _$APIStatusToJson(this); +} - static APIStatus fromJSON(Map input) { - if (input == null) return null; +@JsonSerializable() +class APIStatusPage { + const APIStatusPage({ + this.id, + this.name, + this.url, + this.updatedAt, + }); - return new APIStatus() - ..status = input['status'] - ..message = input['body'] - ..lastUpdatedAt = parseDateTime(input['last_updated']) - ..createdOn = parseDateTime(input['created_on']); - } + /// Unique identifier for the current status. + final String? id; + + final String? name; + + /// Where to get more detailed information. + final String? url; + + @JsonKey(name: 'updated_at') + final DateTime? updatedAt; + + factory APIStatusPage.fromJson(Map input) => + _$APIStatusPageFromJson(input); + Map toJson() => _$APIStatusPageToJson(this); +} + +/// Overview class of the GitHub API status. +@JsonSerializable() +class APIStatusMessage { + const APIStatusMessage({ + this.description, + this.indicator, + }); + + /// A human description of the blended component status. + final String? description; + + /// An indicator - one of none, minor, major, or critical. + final String? indicator; + + factory APIStatusMessage.fromJson(Map input) => + _$APIStatusMessageFromJson(input); + Map toJson() => _$APIStatusMessageToJson(this); } diff --git a/lib/src/common/model/misc.g.dart b/lib/src/common/model/misc.g.dart new file mode 100644 index 00000000..4ad9a310 --- /dev/null +++ b/lib/src/common/model/misc.g.dart @@ -0,0 +1,75 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'misc.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GitignoreTemplate _$GitignoreTemplateFromJson(Map json) => + GitignoreTemplate( + name: json['name'] as String?, + source: json['source'] as String?, + ); + +Map _$GitignoreTemplateToJson(GitignoreTemplate instance) => + { + 'name': instance.name, + 'source': instance.source, + }; + +RateLimit _$RateLimitFromJson(Map json) => RateLimit( + (json['limit'] as num?)?.toInt(), + (json['remaining'] as num?)?.toInt(), + json['resets'] == null ? null : DateTime.parse(json['resets'] as String), + ); + +Map _$RateLimitToJson(RateLimit instance) => { + 'limit': instance.limit, + 'remaining': instance.remaining, + 'resets': instance.resets?.toIso8601String(), + }; + +APIStatus _$APIStatusFromJson(Map json) => APIStatus( + page: json['page'] == null + ? null + : APIStatusPage.fromJson(json['page'] as Map), + status: json['status'] == null + ? null + : APIStatusMessage.fromJson(json['status'] as Map), + ); + +Map _$APIStatusToJson(APIStatus instance) => { + 'page': instance.page, + 'status': instance.status, + }; + +APIStatusPage _$APIStatusPageFromJson(Map json) => + APIStatusPage( + id: json['id'] as String?, + name: json['name'] as String?, + url: json['url'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$APIStatusPageToJson(APIStatusPage instance) => + { + 'id': instance.id, + 'name': instance.name, + 'url': instance.url, + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +APIStatusMessage _$APIStatusMessageFromJson(Map json) => + APIStatusMessage( + description: json['description'] as String?, + indicator: json['indicator'] as String?, + ); + +Map _$APIStatusMessageToJson(APIStatusMessage instance) => + { + 'description': instance.description, + 'indicator': instance.indicator, + }; diff --git a/lib/src/common/model/notifications.dart b/lib/src/common/model/notifications.dart index 5343bdf2..0fd0477a 100644 --- a/lib/src/common/model/notifications.dart +++ b/lib/src/common/model/notifications.dart @@ -1,45 +1,56 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'notifications.g.dart'; /// Model class for notifications. +@JsonSerializable() class Notification { - String id; - Repository repository; - NotificationSubject subject; - String reason; - bool unread; - - @ApiName("updated_at") - DateTime updatedAt; - - @ApiName("last_read_at") - DateTime lastReadAt; - - static Notification fromJSON(Map input) { - if (input == null) return null; - - return new Notification() - ..id = input['id'] - ..repository = - Repository.fromJSON(input['repository'] as Map) - ..subject = - NotificationSubject.fromJSON(input['subject'] as Map) - ..reason = input['reason'] - ..unread = input['unread'] - ..updatedAt = parseDateTime(input['updated_at']) - ..lastReadAt = parseDateTime(input['last_read_at']); - } + Notification({ + this.id, + this.repository, + this.subject, + this.reason, + this.unread, + this.updatedAt, + this.lastReadAt, + this.url, + this.subscriptionUrl, + }); + final String? id; + final Repository? repository; + final NotificationSubject? subject; + final String? reason; + final bool? unread; + + @JsonKey(name: 'updated_at') + final DateTime? updatedAt; + + @JsonKey(name: 'last_read_at') + final DateTime? lastReadAt; + + final String? url; + + @JsonKey(name: 'subscription_url') + final String? subscriptionUrl; + + factory Notification.fromJson(Map input) => + _$NotificationFromJson(input); + Map toJson() => _$NotificationToJson(this); } /// Model class for a notification subject. +@JsonSerializable() class NotificationSubject { - String title; - String type; + NotificationSubject({this.title, this.type, this.url, this.latestCommentUrl}); + final String? title; + final String? type; + final String? url; - static NotificationSubject fromJSON(Map input) { - if (input == null) return null; + @JsonKey(name: 'latest_comment_url') + final String? latestCommentUrl; - return new NotificationSubject() - ..title = input['title'] - ..type = input['type']; - } + factory NotificationSubject.fromJson(Map input) => + _$NotificationSubjectFromJson(input); + Map toJson() => _$NotificationSubjectToJson(this); } diff --git a/lib/src/common/model/notifications.g.dart b/lib/src/common/model/notifications.g.dart new file mode 100644 index 00000000..70dfde07 --- /dev/null +++ b/lib/src/common/model/notifications.g.dart @@ -0,0 +1,58 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'notifications.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Notification _$NotificationFromJson(Map json) => Notification( + id: json['id'] as String?, + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + subject: json['subject'] == null + ? null + : NotificationSubject.fromJson( + json['subject'] as Map), + reason: json['reason'] as String?, + unread: json['unread'] as bool?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + lastReadAt: json['last_read_at'] == null + ? null + : DateTime.parse(json['last_read_at'] as String), + url: json['url'] as String?, + subscriptionUrl: json['subscription_url'] as String?, + ); + +Map _$NotificationToJson(Notification instance) => + { + 'id': instance.id, + 'repository': instance.repository, + 'subject': instance.subject, + 'reason': instance.reason, + 'unread': instance.unread, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'last_read_at': instance.lastReadAt?.toIso8601String(), + 'url': instance.url, + 'subscription_url': instance.subscriptionUrl, + }; + +NotificationSubject _$NotificationSubjectFromJson(Map json) => + NotificationSubject( + title: json['title'] as String?, + type: json['type'] as String?, + url: json['url'] as String?, + latestCommentUrl: json['latest_comment_url'] as String?, + ); + +Map _$NotificationSubjectToJson( + NotificationSubject instance) => + { + 'title': instance.title, + 'type': instance.type, + 'url': instance.url, + 'latest_comment_url': instance.latestCommentUrl, + }; diff --git a/lib/src/common/model/orgs.dart b/lib/src/common/model/orgs.dart index b5e9d22d..5a26bb2c 100644 --- a/lib/src/common/model/orgs.dart +++ b/lib/src/common/model/orgs.dart @@ -1,235 +1,276 @@ -part of github.common; +import 'package:json_annotation/json_annotation.dart'; + +part 'orgs.g.dart'; /// Model class for a GitHub organization. +@JsonSerializable() class Organization { + Organization({ + this.login, + this.id, + this.htmlUrl, + this.avatarUrl, + this.name, + this.company, + this.blog, + this.location, + this.email, + this.publicReposCount, + this.publicGistsCount, + this.followersCount, + this.followingCount, + this.createdAt, + this.updatedAt, + }); + /// Organization Login - String login; + String? login; /// Organization ID - int id; + int? id; /// Url to Organization Profile - @ApiName("html_url") - String htmlUrl; + @JsonKey(name: 'html_url') + String? htmlUrl; /// Url to the Organization Avatar - @ApiName("avatar_url") - String avatarUrl; + @JsonKey(name: 'avatar_url') + String? avatarUrl; /// Organization Name - String name; + String? name; /// Organization Company - String company; + String? company; /// Organization Blog - String blog; + String? blog; /// Organization Location - String location; + String? location; /// Organization Email - String email; + String? email; /// Number of Public Repositories - @ApiName("public_repos") - int publicReposCount; + @JsonKey(name: 'public_repos') + int? publicReposCount; /// Number of Public Gists - @ApiName("public_gists") - int publicGistsCount; + @JsonKey(name: 'public_gists') + int? publicGistsCount; /// Number of Followers - @ApiName("followers") - int followersCount; + @JsonKey(name: 'followers') + int? followersCount; /// Number of People this Organization is Following - @ApiName("following") - int followingCount; + @JsonKey(name: 'following') + int? followingCount; /// Time this organization was created - @ApiName("created_at") - DateTime createdAt; + @JsonKey(name: 'created_at') + DateTime? createdAt; /// Time this organization was updated - @ApiName("updated_at") - DateTime updatedAt; - - static Organization fromJSON(Map input) { - if (input == null) return null; - - return new Organization() - ..login = input['login'] - ..id = input['id'] - ..htmlUrl = input['html_url'] - ..avatarUrl = input['avatar_url'] - ..name = input['name'] - ..company = input['company'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..publicGistsCount = input['public_gists'] - ..publicReposCount = input['public_repos'] - ..followersCount = input['followers'] - ..followingCount = input['following'] - ..email = input['email'] - ..blog = input['blog'] - ..location = input['location']; - } + @JsonKey(name: 'updated_at') + DateTime? updatedAt; + + factory Organization.fromJson(Map input) => + _$OrganizationFromJson(input); + Map toJson() => _$OrganizationToJson(this); } /// Model class for organization membership. +@JsonSerializable() class OrganizationMembership { - String state; - Organization organization; + OrganizationMembership({ + this.state, + this.organization, + }); - static OrganizationMembership fromJSON(Map input) { - if (input == null) return null; + String? state; + Organization? organization; - return new OrganizationMembership() - ..organization = - Organization.fromJSON(input['organization'] as Map) - ..state = input['state']; + factory OrganizationMembership.fromJson(Map input) { + return _$OrganizationMembershipFromJson(input); } + Map toJson() => _$OrganizationMembershipToJson(this); } /// Model class for a GitHub team. +/// +/// Different end-points populate different sets of properties. +/// +/// Groups of organization members that gives permissions on specified repositories. +@JsonSerializable() class Team { - /// Team Name - String name; + Team({ + this.description, + this.htmlUrl, + this.id, + this.ldapDn, + this.membersCount, + this.membersUrl, + this.name, + this.nodeId, + this.organization, + this.parent, + this.permission, + this.permissions, + this.privacy, + this.reposCount, + this.repositoriesUrl, + this.slug, + this.url, + }); + + /// Description of the team + /// + /// Example: `A great team.` + String? description; + + /// Format: uri + /// + /// Example: `https://github.com/orgs/rails/teams/core` + String? htmlUrl; + + /// Unique identifier of the team + /// + /// Example: `1` + int? id; + + /// Distinguished Name (DN) that team maps to within LDAP environment + /// + /// Example: `uid=example,ou=users,dc=github,dc=com` + String? ldapDn; - /// Team ID - int id; + /// Number of Members + @JsonKey(name: 'members_count') + int? membersCount; - /// Team Permission - String permission; + /// Example: `https://api.github.com/organizations/1/team/1/members{/member}` + String? membersUrl; - /// Number of Members - @ApiName("members_count") - int membersCount; + /// Name of the team + /// + /// Example: `Justice League` + String? name; - /// Number of Repositories - @ApiName("repos_count") - int reposCount; + /// Example: `MDQ6VGVhbTE=` + String? nodeId; /// Organization - Organization organization; - - static Team fromJSON(Map input) { - if (input == null) return null; - - return new Team() - ..name = input['name'] - ..id = input['id'] - ..membersCount = input['members_count'] - ..reposCount = input['repos_count'] - ..organization = - Organization.fromJSON(input['organization'] as Map); - } + Organization? organization; + + /// Team Simple + /// + /// Groups of organization members that gives permissions on specified repositories. + Team? parent; + + /// Permission that the team will have for its repositories + /// + /// Example: `admin` + String? permission; + + Permissions? permissions; + + /// The level of privacy this team should have + /// + /// Example: `closed` + String? privacy; + + /// Number of Repositories + @JsonKey(name: 'repos_count') + int? reposCount; + + /// Format: uri + /// + /// Example: `https://api.github.com/organizations/1/team/1/repos` + String? repositoriesUrl; + + /// Example: `justice-league` + String? slug; + + /// URL for the team + /// + /// Format: uri + /// + /// Example: `https://api.github.com/organizations/1/team/1` + String? url; + + Map toJson() => _$TeamToJson(this); + + factory Team.fromJson(Map input) => _$TeamFromJson(input); +} + +@JsonSerializable() +class Permissions { + Permissions({ + this.admin, + this.maintain, + this.pull, + this.push, + this.triage, + }); + + bool? admin; + bool? maintain; + bool? pull; + bool? push; + bool? triage; + + Map toJson() => _$PermissionsToJson(this); + + factory Permissions.fromJson(Map input) => + _$PermissionsFromJson(input); } /// Model class for the team membership state. class TeamMembershipState { - final String name; - TeamMembershipState(this.name); - bool get isPending => name == "pending"; - bool get isActive => name == "active"; + String? name; + + bool get isPending => name == 'pending'; + bool get isActive => name == 'active'; bool get isInactive => name == null; } /// Model class for a team member. +@JsonSerializable() class TeamMember { + TeamMember( + {this.login, + this.id, + this.avatarUrl, + this.type, + this.siteAdmin, + this.htmlUrl}); + /// Member Username - String login; + String? login; /// Member ID - int id; + int? id; /// Url to Member Avatar - @ApiName("avatar_url") - String avatarUrl; + @JsonKey(name: 'avatar_url') + String? avatarUrl; /// Member Type - String type; + String? type; /// If the member is a site administrator - @ApiName("site_admin") - bool siteAdmin; + @JsonKey(name: 'site_admin') + bool? siteAdmin; /// Profile of the Member - @ApiName("html_url") - String htmlUrl; - - static TeamMember fromJSON(Map input) { - if (input == null) return null; - - var member = new TeamMember(); - member.login = input['login']; - member.id = input['id']; - member.avatarUrl = input['avatar_url']; - member.type = input['type']; - member.siteAdmin = input['site_admin']; - member.htmlUrl = input['html_url']; - return member; - } -} - -/// Model class for a team repository. -class TeamRepository extends Repository { - /// Repository Permissions. - TeamRepositoryPermissions permissions; - - static TeamRepository fromJSON(Map input) { - if (input == null) return null; - - return new TeamRepository() - ..name = input['name'] - ..id = input['id'] - ..fullName = input['full_name'] - ..isFork = input['fork'] - ..htmlUrl = input['html_url'] - ..description = input['description'] - ..cloneUrls = CloneUrls.fromJSON(input) - ..homepage = input['homepage'] - ..size = input['size'] - ..stargazersCount = input['stargazers_count'] - ..watchersCount = input['watchers_count'] - ..language = input['language'] - ..hasIssues = input['has_issues'] - ..hasDownloads = input['has_downloads'] - ..hasWiki = input['has_wiki'] - ..defaultBranch = input['default_branch'] - ..openIssuesCount = input['open_issues_count'] - ..networkCount = input['network_count'] - ..subscribersCount = input['subscribers_count'] - ..forksCount = input['forks_count'] - ..createdAt = parseDateTime(input['created_at']) - ..pushedAt = parseDateTime(input['pushed_at']) - ..owner = UserInformation.fromJSON(input['owner'] as Map) - ..isPrivate = input['private'] - ..permissions = TeamRepositoryPermissions - .fromJSON(input['permissions'] as Map); - } -} - -/// Model class for team repository permissions. -class TeamRepositoryPermissions { - /// Administrative Access - bool admin; - - /// Push Access - bool push; - - /// Pull Access - bool pull; - - static TeamRepositoryPermissions fromJSON(Map input) { - if (input == null) return null; + @JsonKey(name: 'html_url') + String? htmlUrl; - return new TeamRepositoryPermissions() - ..admin = input['admin'] - ..push = input['push'] - ..pull = input['pull']; + factory TeamMember.fromJson(Map input) { + return _$TeamMemberFromJson(input); } + Map toJson() => _$TeamMemberToJson(this); } diff --git a/lib/src/common/model/orgs.g.dart b/lib/src/common/model/orgs.g.dart new file mode 100644 index 00000000..dc9dc349 --- /dev/null +++ b/lib/src/common/model/orgs.g.dart @@ -0,0 +1,146 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'orgs.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Organization _$OrganizationFromJson(Map json) => Organization( + login: json['login'] as String?, + id: (json['id'] as num?)?.toInt(), + htmlUrl: json['html_url'] as String?, + avatarUrl: json['avatar_url'] as String?, + name: json['name'] as String?, + company: json['company'] as String?, + blog: json['blog'] as String?, + location: json['location'] as String?, + email: json['email'] as String?, + publicReposCount: (json['public_repos'] as num?)?.toInt(), + publicGistsCount: (json['public_gists'] as num?)?.toInt(), + followersCount: (json['followers'] as num?)?.toInt(), + followingCount: (json['following'] as num?)?.toInt(), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$OrganizationToJson(Organization instance) => + { + 'login': instance.login, + 'id': instance.id, + 'html_url': instance.htmlUrl, + 'avatar_url': instance.avatarUrl, + 'name': instance.name, + 'company': instance.company, + 'blog': instance.blog, + 'location': instance.location, + 'email': instance.email, + 'public_repos': instance.publicReposCount, + 'public_gists': instance.publicGistsCount, + 'followers': instance.followersCount, + 'following': instance.followingCount, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +OrganizationMembership _$OrganizationMembershipFromJson( + Map json) => + OrganizationMembership( + state: json['state'] as String?, + organization: json['organization'] == null + ? null + : Organization.fromJson(json['organization'] as Map), + ); + +Map _$OrganizationMembershipToJson( + OrganizationMembership instance) => + { + 'state': instance.state, + 'organization': instance.organization, + }; + +Team _$TeamFromJson(Map json) => Team( + description: json['description'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + ldapDn: json['ldap_dn'] as String?, + membersCount: (json['members_count'] as num?)?.toInt(), + membersUrl: json['members_url'] as String?, + name: json['name'] as String?, + nodeId: json['node_id'] as String?, + organization: json['organization'] == null + ? null + : Organization.fromJson(json['organization'] as Map), + parent: json['parent'] == null + ? null + : Team.fromJson(json['parent'] as Map), + permission: json['permission'] as String?, + permissions: json['permissions'] == null + ? null + : Permissions.fromJson(json['permissions'] as Map), + privacy: json['privacy'] as String?, + reposCount: (json['repos_count'] as num?)?.toInt(), + repositoriesUrl: json['repositories_url'] as String?, + slug: json['slug'] as String?, + url: json['url'] as String?, + ); + +Map _$TeamToJson(Team instance) => { + 'description': instance.description, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'ldap_dn': instance.ldapDn, + 'members_count': instance.membersCount, + 'members_url': instance.membersUrl, + 'name': instance.name, + 'node_id': instance.nodeId, + 'organization': instance.organization, + 'parent': instance.parent, + 'permission': instance.permission, + 'permissions': instance.permissions, + 'privacy': instance.privacy, + 'repos_count': instance.reposCount, + 'repositories_url': instance.repositoriesUrl, + 'slug': instance.slug, + 'url': instance.url, + }; + +Permissions _$PermissionsFromJson(Map json) => Permissions( + admin: json['admin'] as bool?, + maintain: json['maintain'] as bool?, + pull: json['pull'] as bool?, + push: json['push'] as bool?, + triage: json['triage'] as bool?, + ); + +Map _$PermissionsToJson(Permissions instance) => + { + 'admin': instance.admin, + 'maintain': instance.maintain, + 'pull': instance.pull, + 'push': instance.push, + 'triage': instance.triage, + }; + +TeamMember _$TeamMemberFromJson(Map json) => TeamMember( + login: json['login'] as String?, + id: (json['id'] as num?)?.toInt(), + avatarUrl: json['avatar_url'] as String?, + type: json['type'] as String?, + siteAdmin: json['site_admin'] as bool?, + htmlUrl: json['html_url'] as String?, + ); + +Map _$TeamMemberToJson(TeamMember instance) => + { + 'login': instance.login, + 'id': instance.id, + 'avatar_url': instance.avatarUrl, + 'type': instance.type, + 'site_admin': instance.siteAdmin, + 'html_url': instance.htmlUrl, + }; diff --git a/lib/src/common/model/pulls.dart b/lib/src/common/model/pulls.dart index b8541510..2cb10a8e 100644 --- a/lib/src/common/model/pulls.dart +++ b/lib/src/common/model/pulls.dart @@ -1,315 +1,543 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'pulls.g.dart'; /// Model class for a Pull Request. -class PullRequestInformation { - /// If this is a complete pull request - final bool isCompletePullRequest; +@JsonSerializable() +class PullRequest { + PullRequest({ + this.id, + this.nodeId, + this.htmlUrl, + this.diffUrl, + this.patchUrl, + this.number, + this.state, + this.title, + this.body, + this.createdAt, + this.updatedAt, + this.closedAt, + this.mergedAt, + this.head, + this.base, + this.user, + this.draft, + this.mergeCommitSha, + this.merged, + this.mergeable, + this.mergedBy, + this.commentsCount = 0, + this.commitsCount = 0, + this.additionsCount = 0, + this.deletionsCount = 0, + this.changedFilesCount = 0, + this.labels, + this.requestedReviewers, + this.reviewCommentCount = 0, + this.milestone, + this.rebaseable = false, + this.mergeableState = '', + this.maintainerCanModify = false, + this.authorAssociation = '', + }); + + /// Pull Request ID + int? id; + + /// Unique node identification string. + String? nodeId; /// Url to the Pull Request Page - @ApiName("html_url") - String htmlUrl; + String? htmlUrl; /// Url to the diff for this Pull Request - @ApiName("diff_url") - String diffUrl; + String? diffUrl; /// Url to the patch for this Pull Request - @ApiName("patch_url") - String patchUrl; + String? patchUrl; /// Pull Request Number - int number; + int? number; /// Pull Request State - String state; + String? state; /// Pull Request Title - String title; + String? title; /// Pull Request Body - String body; + String? body; /// Time the pull request was created - @ApiName("created_at") - DateTime createdAt; + DateTime? createdAt; /// Time the pull request was updated - @ApiName("updated_at") - DateTime updatedAt; + DateTime? updatedAt; /// Time the pull request was closed - @ApiName("closed_at") - DateTime closedAt; + DateTime? closedAt; /// Time the pull request was merged - @ApiName("merged_at") - DateTime mergedAt; + DateTime? mergedAt; /// The Pull Request Head - PullRequestHead head; + PullRequestHead? head; /// Pull Request Base - PullRequestHead base; + PullRequestHead? base; /// The User who created the Pull Request - User user; - - PullRequestInformation([this.isCompletePullRequest = false]); - - static PullRequestInformation fromJSON(Map input, - [PullRequestInformation into]) { - if (input == null) return null; - - var pr = into != null ? into : new PullRequestInformation(); - pr.head = PullRequestHead.fromJSON(input['head'] as Map); - pr.base = PullRequestHead.fromJSON(input['base'] as Map); - pr.htmlUrl = input['html_url']; - pr.diffUrl = input['diff_url']; - pr.patchUrl = input['patch_url']; - pr.number = input['number']; - pr.state = input['state']; - pr.title = input['title']; - pr.body = input['body']; - pr.createdAt = parseDateTime(input['created_at']); - pr.updatedAt = parseDateTime(input['updated_at']); - pr.closedAt = parseDateTime(input['closed_at']); - pr.mergedAt = parseDateTime(input['merged_at']); - pr.user = User.fromJSON(input['user'] as Map); - return pr; - } -} + User? user; + + /// Whether or not the pull request is a draft + bool? draft; -/// Model class for a Complete Pull Request. -class PullRequest extends PullRequestInformation { - @ApiName("merge_commit_sha") - String mergeCommitSha; + String? mergeCommitSha; /// If the pull request was merged - bool merged; + bool? merged; /// If the pull request is mergeable - bool mergeable; + bool? mergeable; /// The user who merged the pull request - @ApiName("merged_by") - User mergedBy; + User? mergedBy; /// Number of comments - int commentsCount; + @JsonKey(name: 'comments') + int? commentsCount; /// Number of commits - int commitsCount; + @JsonKey(name: 'commits') + int? commitsCount; /// Number of additions - int additionsCount; + @JsonKey(name: 'additions') + int? additionsCount; /// Number of deletions - int deletionsCount; + @JsonKey(name: 'deletions') + int? deletionsCount; /// Number of changed files - int changedFilesCount; + @JsonKey(name: 'changed_files') + int? changedFilesCount; - /// Pull Request ID - int id; + /// Pull Request Labels + List? labels; - PullRequest() : super(true); - - static PullRequest fromJSON(Map input) { - if (input == null) return null; - - PullRequest pr = PullRequestInformation.fromJSON(input, new PullRequest()); - pr.mergeable = input['mergeable']; - pr.merged = input['merged']; - pr.id = input['id']; - pr.mergedBy = User.fromJSON(input['merged_by'] as Map); - pr.mergeCommitSha = input['merge_commit_sha']; - pr.commentsCount = input['comments']; - pr.commitsCount = input['commits']; - pr.additionsCount = input['additions']; - pr.deletionsCount = input['deletions']; - pr.changedFilesCount = input['changed_files']; - return pr; - } -} - -/// Model class for a pull request merge. -class PullRequestMerge { - bool merged; - String sha; - String message; + /// Reviewers requested for this Pull Request. + List? requestedReviewers; - PullRequestMerge(); + /// The number of review comments on the Pull Request. + @JsonKey(name: 'review_comments') + int? reviewCommentCount; - static PullRequestMerge fromJSON(Map input) { - if (input == null) return null; + Milestone? milestone; - return new PullRequestMerge() - ..merged = input['merged'] - ..sha = input['sha'] - ..message = input['message']; - } -} + bool? rebaseable; -/// Model class for a Pull Request Head. -class PullRequestHead { - /// Label - String label; + String? mergeableState; - /// Ref - String ref; + bool? maintainerCanModify; - /// Commit SHA - String sha; + /// Ex: CONTRIBUTOR, NONE, OWNER + String? authorAssociation; - /// User - User user; + Repository? repo; - /// Repository - Repository repo; + factory PullRequest.fromJson(Map input) => + _$PullRequestFromJson(input); + Map toJson() => _$PullRequestToJson(this); +} - static PullRequestHead fromJSON(Map input) { - if (input == null) return null; +/// Model class for a pull request merge. +@JsonSerializable() +class PullRequestMerge { + PullRequestMerge({ + this.merged, + this.sha, + this.message, + }); + + bool? merged; + String? sha; + String? message; + + factory PullRequestMerge.fromJson(Map input) => + _$PullRequestMergeFromJson(input); + Map toJson() => _$PullRequestMergeToJson(this); +} - var head = new PullRequestHead(); - head.label = input['label']; - head.ref = input['ref']; - head.sha = input['sha']; - head.user = User.fromJSON(input['user'] as Map); - head.repo = Repository.fromJSON(input['repo'] as Map); - return head; - } +/// Model class for a Pull Request Head. +@JsonSerializable() +class PullRequestHead { + PullRequestHead({ + this.label, + this.ref, + this.sha, + this.user, + this.repo, + }); + + String? label; + String? ref; + String? sha; + User? user; + Repository? repo; + + factory PullRequestHead.fromJson(Map input) => + _$PullRequestHeadFromJson(input); + Map toJson() => _$PullRequestHeadToJson(this); } /// Model class for a pull request to be created. +@JsonSerializable() class CreatePullRequest { - /// Pull Request Title - final String title; + CreatePullRequest(this.title, this.head, this.base, + {this.draft = false, this.body}); - /// Pull Request Head - final String head; + String? title; + String? head; + String? base; - /// Pull Request Base - final String base; + /// Whether a draft PR should be created. + /// + /// This is currently experimental functionality since the way draft PRs are + /// created through Github's REST API is in developer preview only - and could change at any time. + @experimental + bool? draft; - /// Pull Request Body - String body; + String? body; - CreatePullRequest(this.title, this.head, this.base, {this.body}); - - String toJSON() { - var map = {}; - putValue("title", title, map); - putValue("head", head, map); - putValue("base", base, map); - putValue("body", body, map); - return JSON.encode(map); - } + factory CreatePullRequest.fromJson(Map input) => + _$CreatePullRequestFromJson(input); + Map toJson() => _$CreatePullRequestToJson(this); } /// Model class for a pull request comment. +@JsonSerializable() class PullRequestComment { - int id; - @ApiName("diff_hunk") - String diffHunk; - String path; - int position; - - @ApiName("original_position") - int originalPosition; - - @ApiName("commit_id") - String commitID; - - @ApiName("original_commit_id") - String originalCommitID; - - User user; - String body; - - @ApiName("created_at") - DateTime createdAt; - - @ApiName("updated_at") - DateTime updatedAt; - - @ApiName("html_url") - String url; - - @ApiName("pull_request_url") - String pullRequestUrl; - - @ApiName("_links") - Links links; - - static PullRequestComment fromJSON(Map input) { - if (input == null) return null; - - return new PullRequestComment() - ..id = input['id'] - ..diffHunk = input['diff_hunk'] - ..path = input['path'] - ..position = input['position'] - ..originalPosition = input['original_position'] - ..commitID = input['commit_id'] - ..originalCommitID = input['original_commit_id'] - ..user = User.fromJSON(input['user'] as Map) - ..body = input['body'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..url = input['html_url'] - ..pullRequestUrl = input['pull_request_url'] - ..links = Links.fromJSON(input['_links'] as Map); - } + PullRequestComment({ + this.id, + this.diffHunk, + this.path, + this.position, + this.originalPosition, + this.commitId, + this.originalCommitId, + this.user, + this.body, + this.createdAt, + this.updatedAt, + this.url, + this.pullRequestUrl, + this.links, + }); + + int? id; + String? diffHunk; + String? path; + int? position; + int? originalPosition; + String? commitId; + String? originalCommitId; + User? user; + String? body; + DateTime? createdAt; + DateTime? updatedAt; + String? url; + String? pullRequestUrl; + @JsonKey(name: '_links') + Links? links; + + factory PullRequestComment.fromJson(Map input) => + _$PullRequestCommentFromJson(input); + Map toJson() => _$PullRequestCommentToJson(this); } /// Model class for a pull request comment to be created. +@JsonSerializable() class CreatePullRequestComment { - String body; + CreatePullRequestComment(this.body, this.commitId, this.path, this.position); + String? body; + String? commitId; + String? path; + int? position; + + factory CreatePullRequestComment.fromJson(Map input) => + _$CreatePullRequestCommentFromJson(input); + Map toJson() => _$CreatePullRequestCommentToJson(this); +} + +@JsonSerializable() +class PullRequestFile { + PullRequestFile({ + this.sha, + this.filename, + this.status, + this.additionsCount, + this.deletionsCount, + this.changesCount, + this.blobUrl, + this.rawUrl, + this.contentsUrl, + this.patch, + }); + + String? sha; + String? filename; + String? status; + @JsonKey(name: 'additions') + int? additionsCount; + @JsonKey(name: 'deletions') + int? deletionsCount; + @JsonKey(name: 'changes') + int? changesCount; + String? blobUrl; + String? rawUrl; + String? contentsUrl; + String? patch; + + factory PullRequestFile.fromJson(Map input) => + _$PullRequestFileFromJson(input); + Map toJson() => _$PullRequestFileToJson(this); +} - @ApiName("commit_id") - String commitId; +@JsonSerializable() +class PullRequestReview { + PullRequestReview( + {required this.id, + this.user, + this.body, + this.state, + this.htmlUrl, + this.pullRequestUrl}); - String path; + int id; + User? user; + String? body; + String? state; + String? htmlUrl; + String? pullRequestUrl; + DateTime? submittedAt; + String? authorAssociation; + String? commitId; + + factory PullRequestReview.fromJson(Map input) => + _$PullRequestReviewFromJson(input); + Map toJson() => _$PullRequestReviewToJson(this); +} - int position; +@JsonSerializable() +class CreatePullRequestReview { + CreatePullRequestReview(this.owner, this.repo, this.pullNumber, this.event, + {this.body, this.comments}); + + String owner; + String repo; + String event; + String? body; + int pullNumber; + List? comments; + + factory CreatePullRequestReview.fromJson(Map input) => + _$CreatePullRequestReviewFromJson(input); + Map toJson() => _$CreatePullRequestReviewToJson(this); +} - CreatePullRequestComment(this.body, this.commitId, this.path, this.position); +/// Pull Request Review Comment +/// +/// Pull Request Review Comments are comments on a portion of the Pull Request's +/// diff. +@JsonSerializable() +class PullRequestReviewComment { + PullRequestReviewComment({ + this.authorAssociation, + this.body, + this.bodyHtml, + this.bodyText, + this.commitId, + this.createdAt, + this.diffHunk, + this.htmlUrl, + this.id, + this.inReplyToId, + this.line, + this.links, + this.nodeId, + this.originalCommitId, + this.originalLine, + this.originalPosition, + this.originalStartLine, + this.path, + this.position, + this.pullRequestReviewId, + this.pullRequestUrl, + this.reactions, + this.side, + this.startLine, + this.startSide, + this.subjectType, + this.updatedAt, + this.url, + this.user, + }); + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + /// The text of the comment. + /// + /// Example: `We should probably include a check for null values here.` + String? body; + + /// Example: `"

comment body

"` + String? bodyHtml; + + /// Example: `"comment body"` + String? bodyText; + + /// The SHA of the commit to which the comment applies. + /// + /// Example: `6dcb09b5b57875f334f61aebed695e2e4193db5e` + String? commitId; + + DateTime? createdAt; + + /// The diff of the line that the comment refers to. + /// + /// Example: `@@ -16,33 +16,40 @@ public class Connection : IConnection...` + String? diffHunk; + + /// HTML URL for the pull request review comment. + /// + /// Example: `https://github.com/octocat/Hello-World/pull/1#discussion-diff-1` + String? htmlUrl; + + /// The ID of the pull request review comment. + int? id; + + /// The comment ID to reply to. + int? inReplyToId; + + /// The line of the blob to which the comment applies. The last line of the range + /// for a multi-line comment + int? line; + + @JsonKey(name: '_links') + ReviewLinks? links; + + /// The node ID of the pull request review comment. + /// + /// Example: `MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDEw` + String? nodeId; + + /// The SHA of the original commit to which the comment applies. + /// + /// Example: `9c48853fa3dc5c1c3d6f1f1cd1f2743e72652840` + String? originalCommitId; + + /// The line of the blob to which the comment applies. The last line of the range + /// for a multi-line comment + int? originalLine; + + /// The index of the original line in the diff to which the comment applies. This + /// field is deprecated; use `original_line` instead. + int? originalPosition; + + /// The first line of the range for a multi-line comment. + int? originalStartLine; + + /// The relative path of the file to which the comment applies. + /// + /// Example: `config/database.yaml` + String? path; + + /// The line index in the diff to which the comment applies. This field is deprecated; + /// use `line` instead. + int? position; + + /// The ID of the pull request review to which the comment belongs. + int? pullRequestReviewId; + + /// URL for the pull request that the review comment belongs to. + /// + /// Example: `https://api.github.com/repos/octocat/Hello-World/pulls/1` + String? pullRequestUrl; + + /// Reaction Rollup + ReactionRollup? reactions; - String toJSON() { - var map = {}; - putValue("body", body, map); - putValue("commit_id", commitId, map); - putValue("path", path, map); - putValue("position", position, map); - return JSON.encode(map); - } + /// The side of the diff to which the comment applies. The side of the last line + /// of the range for a multi-line comment + String? side; + + /// The first line of the range for a multi-line comment. + int? startLine; + + /// The side of the first line of the range for a multi-line comment. + String? startSide; + + /// The level at which the comment is targeted, can be a diff line or a file. + String? subjectType; + + DateTime? updatedAt; + + /// URL for the pull request review comment + /// + /// Example: `https://api.github.com/repos/octocat/Hello-World/pulls/comments/1` + String? url; + + User? user; + + Map toJson() => _$PullRequestReviewCommentToJson(this); + + factory PullRequestReviewComment.fromJson(Map input) => + _$PullRequestReviewCommentFromJson(input); } -class PullRequestFile { - String sha; - String filename; - String status; - @ApiName("additions") - int additionsCount; - @ApiName("deletions") - int deletionsCount; - @ApiName("changes") - int changesCount; - String blobUrl; - String rawUrl; - String patch; - - static PullRequestFile fromJSON(Map input) { - var file = new PullRequestFile(); - file.sha = input['sha']; - file.filename = input['filename']; - file.status = input['status']; - file.additionsCount = input['additions']; - file.deletionsCount = input['deletions']; - file.changesCount = input['changes']; - file.blobUrl = input['blob_url']; - file.rawUrl = input['raw_url']; - file.patch = input['patch']; - return file; +/// This is similar to [Links] but represents a different serialization +/// in the GitHub API. +/// +/// It is used for [PullRequestReviewComment.links] and +/// [ReviewEvent.links]. +class ReviewLinks { + ReviewLinks({ + this.html, + this.pullRequest, + this.self, + }); + + Uri? html; + Uri? pullRequest; + Uri? self; + + Map toJson() { + return { + 'html': {'href': html?.toString()}, + 'pullRequest': {'href': pullRequest?.toString()}, + 'self': {'href': self?.toString()}, + }; + } + + static Uri? _parseBlock(Map input, String key) { + if (input[key] is Map && input[key]['href'] is String) { + return Uri.parse(input[key]['href']! as String); + } + return null; + } + + factory ReviewLinks.fromJson(Map input) { + return ReviewLinks( + html: _parseBlock(input, 'html'), + pullRequest: _parseBlock(input, 'pull_request'), + self: _parseBlock(input, 'self'), + ); } } diff --git a/lib/src/common/model/pulls.g.dart b/lib/src/common/model/pulls.g.dart new file mode 100644 index 00000000..e7dea0cb --- /dev/null +++ b/lib/src/common/model/pulls.g.dart @@ -0,0 +1,384 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'pulls.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +PullRequest _$PullRequestFromJson(Map json) => PullRequest( + id: (json['id'] as num?)?.toInt(), + nodeId: json['node_id'] as String?, + htmlUrl: json['html_url'] as String?, + diffUrl: json['diff_url'] as String?, + patchUrl: json['patch_url'] as String?, + number: (json['number'] as num?)?.toInt(), + state: json['state'] as String?, + title: json['title'] as String?, + body: json['body'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + closedAt: json['closed_at'] == null + ? null + : DateTime.parse(json['closed_at'] as String), + mergedAt: json['merged_at'] == null + ? null + : DateTime.parse(json['merged_at'] as String), + head: json['head'] == null + ? null + : PullRequestHead.fromJson(json['head'] as Map), + base: json['base'] == null + ? null + : PullRequestHead.fromJson(json['base'] as Map), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + draft: json['draft'] as bool?, + mergeCommitSha: json['merge_commit_sha'] as String?, + merged: json['merged'] as bool?, + mergeable: json['mergeable'] as bool?, + mergedBy: json['merged_by'] == null + ? null + : User.fromJson(json['merged_by'] as Map), + commentsCount: (json['comments'] as num?)?.toInt() ?? 0, + commitsCount: (json['commits'] as num?)?.toInt() ?? 0, + additionsCount: (json['additions'] as num?)?.toInt() ?? 0, + deletionsCount: (json['deletions'] as num?)?.toInt() ?? 0, + changedFilesCount: (json['changed_files'] as num?)?.toInt() ?? 0, + labels: (json['labels'] as List?) + ?.map((e) => IssueLabel.fromJson(e as Map)) + .toList(), + requestedReviewers: (json['requested_reviewers'] as List?) + ?.map((e) => User.fromJson(e as Map)) + .toList(), + reviewCommentCount: (json['review_comments'] as num?)?.toInt() ?? 0, + milestone: json['milestone'] == null + ? null + : Milestone.fromJson(json['milestone'] as Map), + rebaseable: json['rebaseable'] as bool? ?? false, + mergeableState: json['mergeable_state'] as String? ?? '', + maintainerCanModify: json['maintainer_can_modify'] as bool? ?? false, + authorAssociation: json['author_association'] as String? ?? '', + )..repo = json['repo'] == null + ? null + : Repository.fromJson(json['repo'] as Map); + +Map _$PullRequestToJson(PullRequest instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'html_url': instance.htmlUrl, + 'diff_url': instance.diffUrl, + 'patch_url': instance.patchUrl, + 'number': instance.number, + 'state': instance.state, + 'title': instance.title, + 'body': instance.body, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'closed_at': instance.closedAt?.toIso8601String(), + 'merged_at': instance.mergedAt?.toIso8601String(), + 'head': instance.head, + 'base': instance.base, + 'user': instance.user, + 'draft': instance.draft, + 'merge_commit_sha': instance.mergeCommitSha, + 'merged': instance.merged, + 'mergeable': instance.mergeable, + 'merged_by': instance.mergedBy, + 'comments': instance.commentsCount, + 'commits': instance.commitsCount, + 'additions': instance.additionsCount, + 'deletions': instance.deletionsCount, + 'changed_files': instance.changedFilesCount, + 'labels': instance.labels, + 'requested_reviewers': instance.requestedReviewers, + 'review_comments': instance.reviewCommentCount, + 'milestone': instance.milestone, + 'rebaseable': instance.rebaseable, + 'mergeable_state': instance.mergeableState, + 'maintainer_can_modify': instance.maintainerCanModify, + 'author_association': instance.authorAssociation, + 'repo': instance.repo, + }; + +PullRequestMerge _$PullRequestMergeFromJson(Map json) => + PullRequestMerge( + merged: json['merged'] as bool?, + sha: json['sha'] as String?, + message: json['message'] as String?, + ); + +Map _$PullRequestMergeToJson(PullRequestMerge instance) => + { + 'merged': instance.merged, + 'sha': instance.sha, + 'message': instance.message, + }; + +PullRequestHead _$PullRequestHeadFromJson(Map json) => + PullRequestHead( + label: json['label'] as String?, + ref: json['ref'] as String?, + sha: json['sha'] as String?, + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + repo: json['repo'] == null + ? null + : Repository.fromJson(json['repo'] as Map), + ); + +Map _$PullRequestHeadToJson(PullRequestHead instance) => + { + 'label': instance.label, + 'ref': instance.ref, + 'sha': instance.sha, + 'user': instance.user, + 'repo': instance.repo, + }; + +CreatePullRequest _$CreatePullRequestFromJson(Map json) => + CreatePullRequest( + json['title'] as String?, + json['head'] as String?, + json['base'] as String?, + draft: json['draft'] as bool? ?? false, + body: json['body'] as String?, + ); + +Map _$CreatePullRequestToJson(CreatePullRequest instance) => + { + 'title': instance.title, + 'head': instance.head, + 'base': instance.base, + 'draft': instance.draft, + 'body': instance.body, + }; + +PullRequestComment _$PullRequestCommentFromJson(Map json) => + PullRequestComment( + id: (json['id'] as num?)?.toInt(), + diffHunk: json['diff_hunk'] as String?, + path: json['path'] as String?, + position: (json['position'] as num?)?.toInt(), + originalPosition: (json['original_position'] as num?)?.toInt(), + commitId: json['commit_id'] as String?, + originalCommitId: json['original_commit_id'] as String?, + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + body: json['body'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + url: json['url'] as String?, + pullRequestUrl: json['pull_request_url'] as String?, + links: json['_links'] == null + ? null + : Links.fromJson(json['_links'] as Map), + ); + +Map _$PullRequestCommentToJson(PullRequestComment instance) => + { + 'id': instance.id, + 'diff_hunk': instance.diffHunk, + 'path': instance.path, + 'position': instance.position, + 'original_position': instance.originalPosition, + 'commit_id': instance.commitId, + 'original_commit_id': instance.originalCommitId, + 'user': instance.user, + 'body': instance.body, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'url': instance.url, + 'pull_request_url': instance.pullRequestUrl, + '_links': instance.links, + }; + +CreatePullRequestComment _$CreatePullRequestCommentFromJson( + Map json) => + CreatePullRequestComment( + json['body'] as String?, + json['commit_id'] as String?, + json['path'] as String?, + (json['position'] as num?)?.toInt(), + ); + +Map _$CreatePullRequestCommentToJson( + CreatePullRequestComment instance) => + { + 'body': instance.body, + 'commit_id': instance.commitId, + 'path': instance.path, + 'position': instance.position, + }; + +PullRequestFile _$PullRequestFileFromJson(Map json) => + PullRequestFile( + sha: json['sha'] as String?, + filename: json['filename'] as String?, + status: json['status'] as String?, + additionsCount: (json['additions'] as num?)?.toInt(), + deletionsCount: (json['deletions'] as num?)?.toInt(), + changesCount: (json['changes'] as num?)?.toInt(), + blobUrl: json['blob_url'] as String?, + rawUrl: json['raw_url'] as String?, + contentsUrl: json['contents_url'] as String?, + patch: json['patch'] as String?, + ); + +Map _$PullRequestFileToJson(PullRequestFile instance) => + { + 'sha': instance.sha, + 'filename': instance.filename, + 'status': instance.status, + 'additions': instance.additionsCount, + 'deletions': instance.deletionsCount, + 'changes': instance.changesCount, + 'blob_url': instance.blobUrl, + 'raw_url': instance.rawUrl, + 'contents_url': instance.contentsUrl, + 'patch': instance.patch, + }; + +PullRequestReview _$PullRequestReviewFromJson(Map json) => + PullRequestReview( + id: (json['id'] as num).toInt(), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + body: json['body'] as String?, + state: json['state'] as String?, + htmlUrl: json['html_url'] as String?, + pullRequestUrl: json['pull_request_url'] as String?, + ) + ..submittedAt = json['submitted_at'] == null + ? null + : DateTime.parse(json['submitted_at'] as String) + ..authorAssociation = json['author_association'] as String? + ..commitId = json['commit_id'] as String?; + +Map _$PullRequestReviewToJson(PullRequestReview instance) => + { + 'id': instance.id, + 'user': instance.user, + 'body': instance.body, + 'state': instance.state, + 'html_url': instance.htmlUrl, + 'pull_request_url': instance.pullRequestUrl, + 'submitted_at': instance.submittedAt?.toIso8601String(), + 'author_association': instance.authorAssociation, + 'commit_id': instance.commitId, + }; + +CreatePullRequestReview _$CreatePullRequestReviewFromJson( + Map json) => + CreatePullRequestReview( + json['owner'] as String, + json['repo'] as String, + (json['pull_number'] as num).toInt(), + json['event'] as String, + body: json['body'] as String?, + comments: (json['comments'] as List?) + ?.map((e) => + PullRequestReviewComment.fromJson(e as Map)) + .toList(), + ); + +Map _$CreatePullRequestReviewToJson( + CreatePullRequestReview instance) => + { + 'owner': instance.owner, + 'repo': instance.repo, + 'event': instance.event, + 'body': instance.body, + 'pull_number': instance.pullNumber, + 'comments': instance.comments, + }; + +PullRequestReviewComment _$PullRequestReviewCommentFromJson( + Map json) => + PullRequestReviewComment( + authorAssociation: json['author_association'] as String?, + body: json['body'] as String?, + bodyHtml: json['body_html'] as String?, + bodyText: json['body_text'] as String?, + commitId: json['commit_id'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + diffHunk: json['diff_hunk'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + inReplyToId: (json['in_reply_to_id'] as num?)?.toInt(), + line: (json['line'] as num?)?.toInt(), + links: json['_links'] == null + ? null + : ReviewLinks.fromJson(json['_links'] as Map), + nodeId: json['node_id'] as String?, + originalCommitId: json['original_commit_id'] as String?, + originalLine: (json['original_line'] as num?)?.toInt(), + originalPosition: (json['original_position'] as num?)?.toInt(), + originalStartLine: (json['original_start_line'] as num?)?.toInt(), + path: json['path'] as String?, + position: (json['position'] as num?)?.toInt(), + pullRequestReviewId: (json['pull_request_review_id'] as num?)?.toInt(), + pullRequestUrl: json['pull_request_url'] as String?, + reactions: json['reactions'] == null + ? null + : ReactionRollup.fromJson(json['reactions'] as Map), + side: json['side'] as String?, + startLine: (json['start_line'] as num?)?.toInt(), + startSide: json['start_side'] as String?, + subjectType: json['subject_type'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + url: json['url'] as String?, + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$PullRequestReviewCommentToJson( + PullRequestReviewComment instance) => + { + 'author_association': instance.authorAssociation, + 'body': instance.body, + 'body_html': instance.bodyHtml, + 'body_text': instance.bodyText, + 'commit_id': instance.commitId, + 'created_at': instance.createdAt?.toIso8601String(), + 'diff_hunk': instance.diffHunk, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'in_reply_to_id': instance.inReplyToId, + 'line': instance.line, + '_links': instance.links, + 'node_id': instance.nodeId, + 'original_commit_id': instance.originalCommitId, + 'original_line': instance.originalLine, + 'original_position': instance.originalPosition, + 'original_start_line': instance.originalStartLine, + 'path': instance.path, + 'position': instance.position, + 'pull_request_review_id': instance.pullRequestReviewId, + 'pull_request_url': instance.pullRequestUrl, + 'reactions': instance.reactions, + 'side': instance.side, + 'start_line': instance.startLine, + 'start_side': instance.startSide, + 'subject_type': instance.subjectType, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'url': instance.url, + 'user': instance.user, + }; diff --git a/lib/src/common/model/reaction.dart b/lib/src/common/model/reaction.dart new file mode 100644 index 00000000..a9b73d7f --- /dev/null +++ b/lib/src/common/model/reaction.dart @@ -0,0 +1,108 @@ +import 'package:github/src/common.dart'; +import 'package:github/src/common/model/users.dart'; +import 'package:json_annotation/json_annotation.dart'; +import 'package:meta/meta.dart'; + +part 'reaction.g.dart'; + +/// This API is currently in preview. It may break. +/// +/// See https://developer.github.com/v3/reactions/ +@JsonSerializable() +class Reaction { + Reaction({ + this.id, + this.nodeId, + this.user, + this.content, + this.createdAt, + }); + + int? id; + String? nodeId; + User? user; + String? content; + DateTime? createdAt; + + ReactionType? get type => ReactionType.fromString(content); + + factory Reaction.fromJson(Map json) => + _$ReactionFromJson(json); + + Map toJson() => _$ReactionToJson(this); +} + +@immutable +class ReactionType { + const ReactionType._(this.content, this.emoji); + + final String content; + final String emoji; + + @override + String toString() => content; + + static const plusOne = ReactionType._('+1', '👍'); + static const minusOne = ReactionType._('-1', '👎'); + static const laugh = ReactionType._('laugh', '😄'); + static const confused = ReactionType._('confused', '😕'); + static const heart = ReactionType._('heart', '❤️'); + static const hooray = ReactionType._('hooray', '🎉'); + static const rocket = ReactionType._('rocket', '🚀'); + static const eyes = ReactionType._('eyes', '👀'); + + static final _types = { + '+1': plusOne, + '-1': minusOne, + 'laugh': laugh, + 'confused': confused, + 'heart': heart, + 'hooray': hooray, + 'rocket': rocket, + 'eyes': eyes, + ':+1:': plusOne, + ':-1:': minusOne, + ':laugh:': laugh, + ':confused:': confused, + ':heart:': heart, + ':hooray:': hooray, + ':rocket:': rocket, + ':eyes:': eyes, + }; + + static ReactionType? fromString(String? content) => _types[content!]; +} + +@JsonSerializable() +class ReactionRollup { + ReactionRollup({ + this.plusOne, + this.minusOne, + this.confused, + this.eyes, + this.heart, + this.hooray, + this.laugh, + this.rocket, + this.totalCount, + this.url, + }); + + @JsonKey(name: '+1') + int? plusOne; + @JsonKey(name: '-1') + int? minusOne; + int? confused; + int? eyes; + int? heart; + int? hooray; + int? laugh; + int? rocket; + int? totalCount; + String? url; + + Map toJson() => _$ReactionRollupToJson(this); + + factory ReactionRollup.fromJson(Map input) => + _$ReactionRollupFromJson(input); +} diff --git a/lib/src/common/model/reaction.g.dart b/lib/src/common/model/reaction.g.dart new file mode 100644 index 00000000..4647e160 --- /dev/null +++ b/lib/src/common/model/reaction.g.dart @@ -0,0 +1,55 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'reaction.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Reaction _$ReactionFromJson(Map json) => Reaction( + id: (json['id'] as num?)?.toInt(), + nodeId: json['node_id'] as String?, + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + content: json['content'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + ); + +Map _$ReactionToJson(Reaction instance) => { + 'id': instance.id, + 'node_id': instance.nodeId, + 'user': instance.user, + 'content': instance.content, + 'created_at': instance.createdAt?.toIso8601String(), + }; + +ReactionRollup _$ReactionRollupFromJson(Map json) => + ReactionRollup( + plusOne: (json['+1'] as num?)?.toInt(), + minusOne: (json['-1'] as num?)?.toInt(), + confused: (json['confused'] as num?)?.toInt(), + eyes: (json['eyes'] as num?)?.toInt(), + heart: (json['heart'] as num?)?.toInt(), + hooray: (json['hooray'] as num?)?.toInt(), + laugh: (json['laugh'] as num?)?.toInt(), + rocket: (json['rocket'] as num?)?.toInt(), + totalCount: (json['total_count'] as num?)?.toInt(), + url: json['url'] as String?, + ); + +Map _$ReactionRollupToJson(ReactionRollup instance) => + { + '+1': instance.plusOne, + '-1': instance.minusOne, + 'confused': instance.confused, + 'eyes': instance.eyes, + 'heart': instance.heart, + 'hooray': instance.hooray, + 'laugh': instance.laugh, + 'rocket': instance.rocket, + 'total_count': instance.totalCount, + 'url': instance.url, + }; diff --git a/lib/src/common/model/repos.dart b/lib/src/common/model/repos.dart index e4643301..d09c1a7e 100644 --- a/lib/src/common/model/repos.dart +++ b/lib/src/common/model/repos.dart @@ -1,7 +1,148 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'repos.g.dart'; + +@JsonSerializable() +class GitHubComparison { + GitHubComparison(this.url, this.status, this.aheadBy, this.behindBy, + this.totalCommits, this.files, this.commits); + + String? url; + String? status; + int? aheadBy; + int? behindBy; + int? totalCommits; + List? files; + List? commits; + + factory GitHubComparison.fromJson(Map json) => + _$GitHubComparisonFromJson(json); + Map toJson() => _$GitHubComparisonToJson(this); + + @override + String toString() { + switch (status) { + case 'identical': + return 'GitHubComparison: identical'; + case 'behind': + return 'GitHubComparison: behind ($behindBy)'; + case 'diverged': + return 'GitHubComparison: diverged'; + case 'ahead': + return 'GitHubComparison: ahead ($aheadBy)'; + default: + return 'Huh??? - $status'; + } + } +} /// Model class for a repository. +@JsonSerializable() class Repository { + Repository({ + this.name = '', + this.id = 0, + this.fullName = '', + this.owner, + this.htmlUrl = '', + this.description = '', + this.cloneUrl = '', + this.gitUrl = '', + this.sshUrl = '', + this.svnUrl = '', + this.defaultBranch = '', + this.createdAt, + this.isPrivate = false, + this.isFork = false, + this.stargazersCount = 0, + this.watchersCount = 0, + this.language = '', + this.hasWiki = false, + this.hasDownloads = false, + this.forksCount = 0, + this.openIssuesCount = 0, + this.subscribersCount = 0, + this.networkCount = 0, + this.hasIssues = false, + this.size = 0, + this.archived = false, + this.disabled = false, + this.homepage = '', + this.updatedAt, + this.pushedAt, + this.license, + this.hasPages = false, + this.permissions, + + // Properties from the Timeline API + this.allowAutoMerge, + this.allowForking, + this.allowMergeCommit, + this.allowRebaseMerge, + this.allowSquashMerge, + this.allowUpdateBranch, + this.anonymousAccessEnabled, + this.archiveUrl, + this.assigneesUrl, + this.blobsUrl, + this.branchesUrl, + this.collaboratorsUrl, + this.commentsUrl, + this.commitsUrl, + this.compareUrl, + this.contentsUrl, + this.contributorsUrl, + this.deleteBranchOnMerge, + this.deploymentsUrl, + this.downloadsUrl, + this.eventsUrl, + this.forks, + this.forksUrl, + this.gitCommitsUrl, + this.gitRefsUrl, + this.gitTagsUrl, + this.hasDiscussions, + this.hasProjects, + this.hooksUrl, + this.isTemplate, + this.issueCommentUrl, + this.issueEventsUrl, + this.issuesUrl, + this.keysUrl, + this.labelsUrl, + this.languagesUrl, + this.masterBranch, + this.mergeCommitMessage, + this.mergeCommitTitle, + this.mergesUrl, + this.milestonesUrl, + this.mirrorUrl, + this.nodeId, + this.notificationsUrl, + this.openIssues, + this.organization, + this.pullsUrl, + this.releasesUrl, + this.squashMergeCommitMessage, + this.squashMergeCommitTitle, + this.stargazersUrl, + this.starredAt, + this.statusesUrl, + this.subscribersUrl, + this.subscriptionUrl, + this.tagsUrl, + this.teamsUrl, + this.tempCloneToken, + this.templateRepository, + this.topics, + this.treesUrl, + this.url, + this.visibility, + this.watchers, + this.webCommitSignoffRequired, + }); + /// Repository Name String name; @@ -9,201 +150,405 @@ class Repository { int id; /// Full Repository Name - @ApiName("full_name") String fullName; /// Repository Owner - UserInformation owner; + @JsonKey(defaultValue: null) + UserInformation? owner; /// If the Repository is Private - @ApiName("private") + @JsonKey(name: 'private') bool isPrivate; /// If the Repository is a fork - @ApiName("fork") + @JsonKey(name: 'fork') bool isFork; /// Url to the GitHub Repository Page - @ApiName("html_url") String htmlUrl; /// Repository Description String description; - /// Repository Clone Urls - @ApiName("clone_urls") - CloneUrls cloneUrls; + // https clone URL + String cloneUrl; + + String sshUrl; + + String svnUrl; + + String gitUrl; /// Url to the Repository Homepage String homepage; /// Repository Size + // + /// The size of the repository. Size is calculated hourly. When a repository is + /// initially created, the size is 0. int size; /// Repository Stars - @ApiName("stargazers_count") int stargazersCount; /// Repository Watchers - @ApiName("watchers_count") int watchersCount; /// Repository Language String language; /// If the Repository has Issues Enabled - @ApiName("has_issues") bool hasIssues; /// If the Repository has the Wiki Enabled - @ApiName("has_wiki") bool hasWiki; /// If the Repository has any Downloads - @ApiName("has_downloads") bool hasDownloads; + /// If the Repository has any Github Pages + bool hasPages; + /// Number of Forks - @ApiName("forks_count") int forksCount; /// Number of Open Issues - @ApiName("open_issues_count") int openIssuesCount; /// Repository Default Branch String defaultBranch; /// Number of Subscribers - @ApiName("subscribers_count") int subscribersCount; /// Number of users in the network - @ApiName("network_count") int networkCount; /// The time the repository was created at - @ApiName("created_at") - DateTime createdAt; + DateTime? createdAt; /// The last time the repository was pushed at - @ApiName("pushed_at") - DateTime pushedAt; - - static Repository fromJSON(Map input, - [Repository instance]) { - if (input == null) return null; - - if (instance == null) instance = new Repository(); - - return instance - ..name = input['name'] - ..id = input['id'] - ..fullName = input['full_name'] - ..isFork = input['fork'] - ..htmlUrl = input['html_url'] - ..description = input['description'] - ..cloneUrls = CloneUrls.fromJSON(input) - ..homepage = input['homepage'] - ..size = input['size'] - ..stargazersCount = input['stargazers_count'] - ..watchersCount = input['watchers_count'] - ..language = input['language'] - ..hasIssues = input['has_issues'] - ..hasDownloads = input['has_downloads'] - ..hasWiki = input['has_wiki'] - ..defaultBranch = input['default_branch'] - ..openIssuesCount = input['open_issues_count'] - ..networkCount = input['network_count'] - ..subscribersCount = input['subscribers_count'] - ..forksCount = input['forks_count'] - ..createdAt = parseDateTime(input['created_at']) - ..pushedAt = parseDateTime(input['pushed_at']) - ..isPrivate = input['private'] - ..owner = - UserInformation.fromJSON(input['owner'] as Map); - } + DateTime? pushedAt; - /// Gets the Repository Slug (Full Name). - RepositorySlug slug() => new RepositorySlug(owner.login, name); + DateTime? updatedAt; - @override - String toString() => 'Repository: ${owner.login}/$name'; -} + LicenseKind? license; + + bool archived; + + bool disabled; + + RepositoryPermissions? permissions; + + // The following properties were added to support the Timeline API. + + /// Whether to allow Auto-merge to be used on pull requests. + bool? allowAutoMerge; + + /// Whether to allow forking this repo + bool? allowForking; + + /// Whether to allow merge commits for pull requests. + bool? allowMergeCommit; + + /// Whether to allow rebase merges for pull requests. + bool? allowRebaseMerge; + + /// Whether to allow squash merges for pull requests. + bool? allowSquashMerge; + + /// Whether or not a pull request head branch that is behind its base branch can + /// always be updated even if it is not required to be up to date before merging. + bool? allowUpdateBranch; + + /// Whether anonymous git access is enabled for this repository + bool? anonymousAccessEnabled; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}` + String? archiveUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/assignees{/user}` + String? assigneesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}` + String? blobsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/branches{/branch}` + String? branchesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}` + String? collaboratorsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/comments{/number}` + String? commentsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/commits{/sha}` + String? commitsUrl; -/// Repository Clone Urls -class CloneUrls { - /// Git Protocol + /// Example: `http://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}` + String? compareUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/contents/{+path}` + String? contentsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/contributors` + String? contributorsUrl; + + /// Whether to delete head branches when pull requests are merged + bool? deleteBranchOnMerge; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/deployments` + String? deploymentsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/downloads` + String? downloadsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/events` + String? eventsUrl; + + int? forks; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/forks` + String? forksUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/commits{/sha}` + String? gitCommitsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/refs{/sha}` + String? gitRefsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/tags{/sha}` + String? gitTagsUrl; + + /// Whether discussions are enabled. + bool? hasDiscussions; + + /// Whether projects are enabled. + bool? hasProjects; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/hooks` + String? hooksUrl; + + /// Whether this repository acts as a template that can be used to generate new + /// repositories. + bool? isTemplate; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/issues/comments{/number}` + String? issueCommentUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/issues/events{/number}` + String? issueEventsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/issues{/number}` + String? issuesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/keys{/key_id}` + String? keysUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/labels{/name}` + String? labelsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/languages` + String? languagesUrl; + + String? masterBranch; + + /// The default value for a merge commit message. /// - /// git://github.com/user/repo.git - String git; + /// - `PR_TITLE` - default to the pull request's title. + /// - `PR_BODY` - default to the pull request's body. + /// - `BLANK` - default to a blank commit message. + String? mergeCommitMessage; - /// SSH Protocol + /// The default value for a merge commit title. /// - /// git@github.com:user/repo.git - String ssh; + /// - `PR_TITLE` - default to the pull request's title. + /// - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., + /// Merge pull request #123 from branch-name). + String? mergeCommitTitle; - /// HTTPS Protocol + /// Format: uri /// - /// https://github.com/user/repo.git - String https; + /// Example: `http://api.github.com/repos/octocat/Hello-World/merges` + String? mergesUrl; - /// Subversion Protocol + /// Example: `http://api.github.com/repos/octocat/Hello-World/milestones{/number}` + String? milestonesUrl; + + /// Format: uri /// - /// https://github.com/user/repo - String svn; + /// Example: `git:git.example.com/octocat/Hello-World` + String? mirrorUrl; - static CloneUrls fromJSON(Map input) { - if (input == null) return null; + /// Example: `MDEwOlJlcG9zaXRvcnkxMjk2MjY5` + String? nodeId; - return new CloneUrls() - ..git = input['git_url'] - ..ssh = input['ssh_url'] - ..https = input['clone_url'] - ..svn = input['svn_url']; - } + /// Example: `http://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}` + String? notificationsUrl; + + int? openIssues; + + User? organization; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/pulls{/number}` + String? pullsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/releases{/id}` + String? releasesUrl; + + /// The default value for a squash merge commit message: + /// + /// - `PR_BODY` - default to the pull request's body. + /// - `COMMIT_MESSAGES` - default to the branch's commit messages. + /// - `BLANK` - default to a blank commit message. + String? squashMergeCommitMessage; + + /// The default value for a squash merge commit title: + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) + /// or the pull request's title (when more than one commit). + String? squashMergeCommitTitle; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/stargazers` + String? stargazersUrl; + + DateTime? starredAt; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/statuses/{sha}` + String? statusesUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/subscribers` + String? subscribersUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/subscription` + String? subscriptionUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/tags` + String? tagsUrl; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/teams` + String? teamsUrl; + + String? tempCloneToken; + + TemplateRepository? templateRepository; + + List? topics; + + /// Example: `http://api.github.com/repos/octocat/Hello-World/git/trees{/sha}` + String? treesUrl; + + /// Example: `https://api.github.com/repos/octocat/Hello-World` + String? url; + + /// The repository visibility: public, private, or internal. + String? visibility; + + int? watchers; + + /// Whether to require contributors to sign off on web-based commits + bool? webCommitSignoffRequired; + + factory Repository.fromJson(Map input) => + _$RepositoryFromJson(input); + Map toJson() => _$RepositoryToJson(this); + + /// Gets the Repository Slug (Full Name). + RepositorySlug slug() => RepositorySlug(owner?.login ?? '', name); + + @override + String toString() => 'Repository: $owner/$name'; +} + +/// Model class for repository permissions. +@JsonSerializable() +class RepositoryPermissions { + RepositoryPermissions( + {this.admin = false, this.push = false, this.pull = false}); + + /// Administrative Access + bool admin; + + /// Push Access + bool push; + + /// Pull Access + bool pull; + + factory RepositoryPermissions.fromJson(Map json) => + _$RepositoryPermissionsFromJson(json); + + Map toJson() => _$RepositoryPermissionsToJson(this); } +@JsonSerializable() class Tag { + Tag(this.name, this.commit, this.zipUrl, this.tarUrl); + String name; CommitInfo commit; + @JsonKey(name: 'zipball_url') String zipUrl; + @JsonKey(name: 'tarball_url') String tarUrl; - static Tag fromJSON(Map input) { - if (input == null) { - return null; - } - - return new Tag() - ..name = input['name'] - ..commit = CommitInfo.fromJson(input['commit'] as Map) - ..tarUrl = input['tarball_url'] - ..zipUrl = input['zipball_url']; - } + factory Tag.fromJson(Map input) => _$TagFromJson(input); + Map toJson() => _$TagToJson(this); @override String toString() => 'Tag: $name'; } +@JsonSerializable() +class CommitData { + CommitData(this.sha, this.commit, this.url, this.htmlUrl, this.commentsUrl, + this.author, this.committer, this.parents); + + String? sha; + GitCommit? commit; + String? url; + String? htmlUrl; + String? commentsUrl; + + CommitDataUser? author, committer; + List>? parents; + + factory CommitData.fromJson(Map input) => + _$CommitDataFromJson(input); + Map toJson() => _$CommitDataToJson(this); +} + +@JsonSerializable() +class CommitDataUser { + CommitDataUser(this.login, this.id, this.type); + + String? login, type; + + int? id; + + factory CommitDataUser.fromJson(Map input) => + _$CommitDataUserFromJson(input); + Map toJson() => _$CommitDataUserToJson(this); +} + +@JsonSerializable() class CommitInfo { - String sha; - GitTree tree; + CommitInfo(this.sha, this.tree); - static CommitInfo fromJson(Map input) { - if (input == null) { - return null; - } + String? sha; + GitTree? tree; - return new CommitInfo() - ..sha = input['sha'] - ..tree = - GitTree.fromJSON(input['commit']['tree'] as Map); - } + factory CommitInfo.fromJson(Map input) => + _$CommitInfoFromJson(input); + Map toJson() => _$CommitInfoToJson(this); } /// User Information +@JsonSerializable() class UserInformation { + UserInformation(this.login, this.id, this.avatarUrl, this.htmlUrl); + /// Owner Username String login; @@ -211,148 +556,135 @@ class UserInformation { int id; /// Avatar Url - @ApiName("avatar_url") String avatarUrl; /// Url to the user's GitHub Profile - @ApiName("html_url") String htmlUrl; - static UserInformation fromJSON(Map input) { - if (input == null) return null; - - return new UserInformation() - ..login = input['login'] - ..id = input['id'] - ..avatarUrl = input['avatar_url'] - ..htmlUrl = input['html_url']; - } + factory UserInformation.fromJson(Map input) => + _$UserInformationFromJson(input); + Map toJson() => _$UserInformationToJson(this); } /// A Repository Slug +@JsonSerializable() class RepositorySlug { + RepositorySlug(this.owner, this.name); + /// Repository Owner - final String owner; + String owner; /// Repository Name - final String name; - - RepositorySlug(this.owner, this.name); + String name; /// Creates a Repository Slug from a full name. factory RepositorySlug.full(String f) { - var split = f.split("/"); - var o = split[0]; - var n = (split..removeAt(0)).join("/"); - return new RepositorySlug(o, n); + final split = f.split('/'); + final o = split[0]; + final n = (split..removeAt(0)).join('/'); + return RepositorySlug(o, n); } /// The Full Name of the Repository /// /// Example: owner/name - String get fullName => "${owner}/${name}"; + String get fullName => '$owner/$name'; @override - bool operator ==(Object obj) => - obj is RepositorySlug && obj.fullName == fullName; + bool operator ==(Object other) => + other is RepositorySlug && other.fullName == fullName; @override int get hashCode => fullName.hashCode; @override - String toString() => "${owner}/${name}"; + String toString() => '$owner/$name'; + + factory RepositorySlug.fromJson(Map json) => + _$RepositorySlugFromJson(json); + Map toJson() => _$RepositorySlugToJson(this); } /// Model class for a new repository to be created. +@JsonSerializable() class CreateRepository { + CreateRepository(this.name, + {this.description, + this.homepage, + this.private, + this.hasIssues, + this.hasDownloads, + this.teamId, + this.autoInit, + this.gitignoreTemplate, + this.licenseTemplate, + this.hasWiki}); + /// Repository Name - final String name; + String? name; /// Repository Description - String description; + String? description; /// Repository Homepage - String homepage; + String? homepage; /// If the repository should be private or not. - bool private = false; + bool? private = false; /// If the repository should have issues enabled. - @ApiName("has_issues") - bool hasIssues = true; + bool? hasIssues = true; /// If the repository should have the wiki enabled. - @ApiName("has_wiki") - bool hasWiki = true; + bool? hasWiki = true; /// If the repository should have downloads enabled. - @ApiName("has_downloads") - bool hasDownloads = true; + bool? hasDownloads = true; /// The Team ID (Only for Creating a Repository for an Organization) - @OnlyWhen("Creating a repository for an organization") - @ApiName("team_id") - int teamID; + @OnlyWhen('Creating a repository for an organization') + int? teamId; /// If GitHub should auto initialize the repository. - @ApiName("auto_init") - bool autoInit = false; + bool? autoInit = false; /// .gitignore template (only when [autoInit] is true) - @OnlyWhen("autoInit is true") - String gitignoreTemplate; + @OnlyWhen('autoInit is true') + String? gitignoreTemplate; /// License template (only when [autoInit] is true) - @OnlyWhen("autoInit is true") - String licenseTemplate; - - CreateRepository(this.name); - - String toJSON() { - return JSON.encode({ - "name": name, - "description": description, - "homepage": homepage, - "private": private, - "has_issues": hasIssues, - "has_wiki": hasWiki, - "has_downloads": hasDownloads, - "team_id": teamID, - "auto_init": autoInit, - "gitignore_template": gitignoreTemplate, - "license_template": licenseTemplate - }); - } + @OnlyWhen('autoInit is true') + String? licenseTemplate; + + factory CreateRepository.fromJson(Map input) => + _$CreateRepositoryFromJson(input); + Map toJson() => _$CreateRepositoryToJson(this); } /// Model class for a branch. +@JsonSerializable() class Branch { + Branch(this.name, this.commit); + /// The name of the branch. - String name; + String? name; /// Commit Information - CommitInfo commit; - - static Branch fromJSON(Map input) { - if (input == null) return null; + CommitData? commit; - var branch = new Branch() - ..name = input['name'] - ..commit = CommitInfo.fromJson(input['commit'] as Map); - - return branch; - } + factory Branch.fromJson(Map json) => _$BranchFromJson(json); + Map toJson() => _$BranchToJson(this); } /// A Breakdown of the Languages a repository uses. class LanguageBreakdown { - final Map _data; - LanguageBreakdown(Map data) : _data = data; + final Map _data; + /// The Primary Language String get primary { - var list = mapToList(_data); + final list = mapToList(_data); list.sort((a, b) { return a.value.compareTo(b.value); }); @@ -369,8 +701,8 @@ class LanguageBreakdown { /// Creates a list of lists with a tuple of the language name and the bytes. List> toList() { - var out = >[]; - for (var key in info.keys) { + final out = >[]; + for (final key in info.keys) { out.add([key, info[key]]); } return out; @@ -378,10 +710,68 @@ class LanguageBreakdown { @override String toString() { - var buffer = new StringBuffer(); + final buffer = StringBuffer(); _data.forEach((key, value) { - buffer.writeln("${key}: ${value}"); + buffer.writeln('$key: $value'); }); return buffer.toString(); } } + +@JsonSerializable() +class LicenseDetails { + LicenseDetails( + {this.name, + this.path, + this.sha, + this.size, + this.url, + this.htmlUrl, + this.gitUrl, + this.downloadUrl, + this.type, + this.content, + this.encoding, + this.links, + this.license}); + + String? name; + String? path; + String? sha; + int? size; + Uri? url; + + Uri? htmlUrl; + Uri? gitUrl; + Uri? downloadUrl; + + String? type; + String? content; + String? encoding; + + @JsonKey(name: '_links') + Links? links; + + LicenseKind? license; + + factory LicenseDetails.fromJson(Map json) => + _$LicenseDetailsFromJson(json); + + Map toJson() => _$LicenseDetailsToJson(this); +} + +@JsonSerializable() +class LicenseKind { + LicenseKind({this.key, this.name, this.spdxId, this.url, this.nodeId}); + + String? key; + String? name; + String? spdxId; + Uri? url; + String? nodeId; + + factory LicenseKind.fromJson(Map json) => + _$LicenseKindFromJson(json); + + Map toJson() => _$LicenseKindToJson(this); +} diff --git a/lib/src/common/model/repos.g.dart b/lib/src/common/model/repos.g.dart new file mode 100644 index 00000000..fe19ea97 --- /dev/null +++ b/lib/src/common/model/repos.g.dart @@ -0,0 +1,475 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GitHubComparison _$GitHubComparisonFromJson(Map json) => + GitHubComparison( + json['url'] as String?, + json['status'] as String?, + (json['ahead_by'] as num?)?.toInt(), + (json['behind_by'] as num?)?.toInt(), + (json['total_commits'] as num?)?.toInt(), + (json['files'] as List?) + ?.map((e) => CommitFile.fromJson(e as Map)) + .toList(), + (json['commits'] as List?) + ?.map((e) => RepositoryCommit.fromJson(e as Map)) + .toList(), + ); + +Map _$GitHubComparisonToJson(GitHubComparison instance) => + { + 'url': instance.url, + 'status': instance.status, + 'ahead_by': instance.aheadBy, + 'behind_by': instance.behindBy, + 'total_commits': instance.totalCommits, + 'files': instance.files, + 'commits': instance.commits, + }; + +Repository _$RepositoryFromJson(Map json) => Repository( + name: json['name'] as String? ?? '', + id: (json['id'] as num?)?.toInt() ?? 0, + fullName: json['full_name'] as String? ?? '', + owner: json['owner'] == null + ? null + : UserInformation.fromJson(json['owner'] as Map), + htmlUrl: json['html_url'] as String? ?? '', + description: json['description'] as String? ?? '', + cloneUrl: json['clone_url'] as String? ?? '', + gitUrl: json['git_url'] as String? ?? '', + sshUrl: json['ssh_url'] as String? ?? '', + svnUrl: json['svn_url'] as String? ?? '', + defaultBranch: json['default_branch'] as String? ?? '', + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + isPrivate: json['private'] as bool? ?? false, + isFork: json['fork'] as bool? ?? false, + stargazersCount: (json['stargazers_count'] as num?)?.toInt() ?? 0, + watchersCount: (json['watchers_count'] as num?)?.toInt() ?? 0, + language: json['language'] as String? ?? '', + hasWiki: json['has_wiki'] as bool? ?? false, + hasDownloads: json['has_downloads'] as bool? ?? false, + forksCount: (json['forks_count'] as num?)?.toInt() ?? 0, + openIssuesCount: (json['open_issues_count'] as num?)?.toInt() ?? 0, + subscribersCount: (json['subscribers_count'] as num?)?.toInt() ?? 0, + networkCount: (json['network_count'] as num?)?.toInt() ?? 0, + hasIssues: json['has_issues'] as bool? ?? false, + size: (json['size'] as num?)?.toInt() ?? 0, + archived: json['archived'] as bool? ?? false, + disabled: json['disabled'] as bool? ?? false, + homepage: json['homepage'] as String? ?? '', + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + pushedAt: json['pushed_at'] == null + ? null + : DateTime.parse(json['pushed_at'] as String), + license: json['license'] == null + ? null + : LicenseKind.fromJson(json['license'] as Map), + hasPages: json['has_pages'] as bool? ?? false, + permissions: json['permissions'] == null + ? null + : RepositoryPermissions.fromJson( + json['permissions'] as Map), + allowAutoMerge: json['allow_auto_merge'] as bool?, + allowForking: json['allow_forking'] as bool?, + allowMergeCommit: json['allow_merge_commit'] as bool?, + allowRebaseMerge: json['allow_rebase_merge'] as bool?, + allowSquashMerge: json['allow_squash_merge'] as bool?, + allowUpdateBranch: json['allow_update_branch'] as bool?, + anonymousAccessEnabled: json['anonymous_access_enabled'] as bool?, + archiveUrl: json['archive_url'] as String?, + assigneesUrl: json['assignees_url'] as String?, + blobsUrl: json['blobs_url'] as String?, + branchesUrl: json['branches_url'] as String?, + collaboratorsUrl: json['collaborators_url'] as String?, + commentsUrl: json['comments_url'] as String?, + commitsUrl: json['commits_url'] as String?, + compareUrl: json['compare_url'] as String?, + contentsUrl: json['contents_url'] as String?, + contributorsUrl: json['contributors_url'] as String?, + deleteBranchOnMerge: json['delete_branch_on_merge'] as bool?, + deploymentsUrl: json['deployments_url'] as String?, + downloadsUrl: json['downloads_url'] as String?, + eventsUrl: json['events_url'] as String?, + forks: (json['forks'] as num?)?.toInt(), + forksUrl: json['forks_url'] as String?, + gitCommitsUrl: json['git_commits_url'] as String?, + gitRefsUrl: json['git_refs_url'] as String?, + gitTagsUrl: json['git_tags_url'] as String?, + hasDiscussions: json['has_discussions'] as bool?, + hasProjects: json['has_projects'] as bool?, + hooksUrl: json['hooks_url'] as String?, + isTemplate: json['is_template'] as bool?, + issueCommentUrl: json['issue_comment_url'] as String?, + issueEventsUrl: json['issue_events_url'] as String?, + issuesUrl: json['issues_url'] as String?, + keysUrl: json['keys_url'] as String?, + labelsUrl: json['labels_url'] as String?, + languagesUrl: json['languages_url'] as String?, + masterBranch: json['master_branch'] as String?, + mergeCommitMessage: json['merge_commit_message'] as String?, + mergeCommitTitle: json['merge_commit_title'] as String?, + mergesUrl: json['merges_url'] as String?, + milestonesUrl: json['milestones_url'] as String?, + mirrorUrl: json['mirror_url'] as String?, + nodeId: json['node_id'] as String?, + notificationsUrl: json['notifications_url'] as String?, + openIssues: (json['open_issues'] as num?)?.toInt(), + organization: json['organization'] == null + ? null + : User.fromJson(json['organization'] as Map), + pullsUrl: json['pulls_url'] as String?, + releasesUrl: json['releases_url'] as String?, + squashMergeCommitMessage: json['squash_merge_commit_message'] as String?, + squashMergeCommitTitle: json['squash_merge_commit_title'] as String?, + stargazersUrl: json['stargazers_url'] as String?, + starredAt: json['starred_at'] == null + ? null + : DateTime.parse(json['starred_at'] as String), + statusesUrl: json['statuses_url'] as String?, + subscribersUrl: json['subscribers_url'] as String?, + subscriptionUrl: json['subscription_url'] as String?, + tagsUrl: json['tags_url'] as String?, + teamsUrl: json['teams_url'] as String?, + tempCloneToken: json['temp_clone_token'] as String?, + templateRepository: json['template_repository'] == null + ? null + : TemplateRepository.fromJson( + json['template_repository'] as Map), + topics: + (json['topics'] as List?)?.map((e) => e as String).toList(), + treesUrl: json['trees_url'] as String?, + url: json['url'] as String?, + visibility: json['visibility'] as String?, + watchers: (json['watchers'] as num?)?.toInt(), + webCommitSignoffRequired: json['web_commit_signoff_required'] as bool?, + ); + +Map _$RepositoryToJson(Repository instance) => + { + 'name': instance.name, + 'id': instance.id, + 'full_name': instance.fullName, + 'owner': instance.owner, + 'private': instance.isPrivate, + 'fork': instance.isFork, + 'html_url': instance.htmlUrl, + 'description': instance.description, + 'clone_url': instance.cloneUrl, + 'ssh_url': instance.sshUrl, + 'svn_url': instance.svnUrl, + 'git_url': instance.gitUrl, + 'homepage': instance.homepage, + 'size': instance.size, + 'stargazers_count': instance.stargazersCount, + 'watchers_count': instance.watchersCount, + 'language': instance.language, + 'has_issues': instance.hasIssues, + 'has_wiki': instance.hasWiki, + 'has_downloads': instance.hasDownloads, + 'has_pages': instance.hasPages, + 'forks_count': instance.forksCount, + 'open_issues_count': instance.openIssuesCount, + 'default_branch': instance.defaultBranch, + 'subscribers_count': instance.subscribersCount, + 'network_count': instance.networkCount, + 'created_at': instance.createdAt?.toIso8601String(), + 'pushed_at': instance.pushedAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'license': instance.license, + 'archived': instance.archived, + 'disabled': instance.disabled, + 'permissions': instance.permissions, + 'allow_auto_merge': instance.allowAutoMerge, + 'allow_forking': instance.allowForking, + 'allow_merge_commit': instance.allowMergeCommit, + 'allow_rebase_merge': instance.allowRebaseMerge, + 'allow_squash_merge': instance.allowSquashMerge, + 'allow_update_branch': instance.allowUpdateBranch, + 'anonymous_access_enabled': instance.anonymousAccessEnabled, + 'archive_url': instance.archiveUrl, + 'assignees_url': instance.assigneesUrl, + 'blobs_url': instance.blobsUrl, + 'branches_url': instance.branchesUrl, + 'collaborators_url': instance.collaboratorsUrl, + 'comments_url': instance.commentsUrl, + 'commits_url': instance.commitsUrl, + 'compare_url': instance.compareUrl, + 'contents_url': instance.contentsUrl, + 'contributors_url': instance.contributorsUrl, + 'delete_branch_on_merge': instance.deleteBranchOnMerge, + 'deployments_url': instance.deploymentsUrl, + 'downloads_url': instance.downloadsUrl, + 'events_url': instance.eventsUrl, + 'forks': instance.forks, + 'forks_url': instance.forksUrl, + 'git_commits_url': instance.gitCommitsUrl, + 'git_refs_url': instance.gitRefsUrl, + 'git_tags_url': instance.gitTagsUrl, + 'has_discussions': instance.hasDiscussions, + 'has_projects': instance.hasProjects, + 'hooks_url': instance.hooksUrl, + 'is_template': instance.isTemplate, + 'issue_comment_url': instance.issueCommentUrl, + 'issue_events_url': instance.issueEventsUrl, + 'issues_url': instance.issuesUrl, + 'keys_url': instance.keysUrl, + 'labels_url': instance.labelsUrl, + 'languages_url': instance.languagesUrl, + 'master_branch': instance.masterBranch, + 'merge_commit_message': instance.mergeCommitMessage, + 'merge_commit_title': instance.mergeCommitTitle, + 'merges_url': instance.mergesUrl, + 'milestones_url': instance.milestonesUrl, + 'mirror_url': instance.mirrorUrl, + 'node_id': instance.nodeId, + 'notifications_url': instance.notificationsUrl, + 'open_issues': instance.openIssues, + 'organization': instance.organization, + 'pulls_url': instance.pullsUrl, + 'releases_url': instance.releasesUrl, + 'squash_merge_commit_message': instance.squashMergeCommitMessage, + 'squash_merge_commit_title': instance.squashMergeCommitTitle, + 'stargazers_url': instance.stargazersUrl, + 'starred_at': instance.starredAt?.toIso8601String(), + 'statuses_url': instance.statusesUrl, + 'subscribers_url': instance.subscribersUrl, + 'subscription_url': instance.subscriptionUrl, + 'tags_url': instance.tagsUrl, + 'teams_url': instance.teamsUrl, + 'temp_clone_token': instance.tempCloneToken, + 'template_repository': instance.templateRepository, + 'topics': instance.topics, + 'trees_url': instance.treesUrl, + 'url': instance.url, + 'visibility': instance.visibility, + 'watchers': instance.watchers, + 'web_commit_signoff_required': instance.webCommitSignoffRequired, + }; + +RepositoryPermissions _$RepositoryPermissionsFromJson( + Map json) => + RepositoryPermissions( + admin: json['admin'] as bool? ?? false, + push: json['push'] as bool? ?? false, + pull: json['pull'] as bool? ?? false, + ); + +Map _$RepositoryPermissionsToJson( + RepositoryPermissions instance) => + { + 'admin': instance.admin, + 'push': instance.push, + 'pull': instance.pull, + }; + +Tag _$TagFromJson(Map json) => Tag( + json['name'] as String, + CommitInfo.fromJson(json['commit'] as Map), + json['zipball_url'] as String, + json['tarball_url'] as String, + ); + +Map _$TagToJson(Tag instance) => { + 'name': instance.name, + 'commit': instance.commit, + 'zipball_url': instance.zipUrl, + 'tarball_url': instance.tarUrl, + }; + +CommitData _$CommitDataFromJson(Map json) => CommitData( + json['sha'] as String?, + json['commit'] == null + ? null + : GitCommit.fromJson(json['commit'] as Map), + json['url'] as String?, + json['html_url'] as String?, + json['comments_url'] as String?, + json['author'] == null + ? null + : CommitDataUser.fromJson(json['author'] as Map), + json['committer'] == null + ? null + : CommitDataUser.fromJson(json['committer'] as Map), + (json['parents'] as List?) + ?.map((e) => e as Map) + .toList(), + ); + +Map _$CommitDataToJson(CommitData instance) => + { + 'sha': instance.sha, + 'commit': instance.commit, + 'url': instance.url, + 'html_url': instance.htmlUrl, + 'comments_url': instance.commentsUrl, + 'author': instance.author, + 'committer': instance.committer, + 'parents': instance.parents, + }; + +CommitDataUser _$CommitDataUserFromJson(Map json) => + CommitDataUser( + json['login'] as String?, + (json['id'] as num?)?.toInt(), + json['type'] as String?, + ); + +Map _$CommitDataUserToJson(CommitDataUser instance) => + { + 'login': instance.login, + 'type': instance.type, + 'id': instance.id, + }; + +CommitInfo _$CommitInfoFromJson(Map json) => CommitInfo( + json['sha'] as String?, + json['tree'] == null + ? null + : GitTree.fromJson(json['tree'] as Map), + ); + +Map _$CommitInfoToJson(CommitInfo instance) => + { + 'sha': instance.sha, + 'tree': instance.tree, + }; + +UserInformation _$UserInformationFromJson(Map json) => + UserInformation( + json['login'] as String, + (json['id'] as num).toInt(), + json['avatar_url'] as String, + json['html_url'] as String, + ); + +Map _$UserInformationToJson(UserInformation instance) => + { + 'login': instance.login, + 'id': instance.id, + 'avatar_url': instance.avatarUrl, + 'html_url': instance.htmlUrl, + }; + +RepositorySlug _$RepositorySlugFromJson(Map json) => + RepositorySlug( + json['owner'] as String, + json['name'] as String, + ); + +Map _$RepositorySlugToJson(RepositorySlug instance) => + { + 'owner': instance.owner, + 'name': instance.name, + }; + +CreateRepository _$CreateRepositoryFromJson(Map json) => + CreateRepository( + json['name'] as String?, + description: json['description'] as String?, + homepage: json['homepage'] as String?, + private: json['private'] as bool?, + hasIssues: json['has_issues'] as bool?, + hasDownloads: json['has_downloads'] as bool?, + teamId: (json['team_id'] as num?)?.toInt(), + autoInit: json['auto_init'] as bool?, + gitignoreTemplate: json['gitignore_template'] as String?, + licenseTemplate: json['license_template'] as String?, + hasWiki: json['has_wiki'] as bool?, + ); + +Map _$CreateRepositoryToJson(CreateRepository instance) => + { + 'name': instance.name, + 'description': instance.description, + 'homepage': instance.homepage, + 'private': instance.private, + 'has_issues': instance.hasIssues, + 'has_wiki': instance.hasWiki, + 'has_downloads': instance.hasDownloads, + 'team_id': instance.teamId, + 'auto_init': instance.autoInit, + 'gitignore_template': instance.gitignoreTemplate, + 'license_template': instance.licenseTemplate, + }; + +Branch _$BranchFromJson(Map json) => Branch( + json['name'] as String?, + json['commit'] == null + ? null + : CommitData.fromJson(json['commit'] as Map), + ); + +Map _$BranchToJson(Branch instance) => { + 'name': instance.name, + 'commit': instance.commit, + }; + +LicenseDetails _$LicenseDetailsFromJson(Map json) => + LicenseDetails( + name: json['name'] as String?, + path: json['path'] as String?, + sha: json['sha'] as String?, + size: (json['size'] as num?)?.toInt(), + url: json['url'] == null ? null : Uri.parse(json['url'] as String), + htmlUrl: json['html_url'] == null + ? null + : Uri.parse(json['html_url'] as String), + gitUrl: + json['git_url'] == null ? null : Uri.parse(json['git_url'] as String), + downloadUrl: json['download_url'] == null + ? null + : Uri.parse(json['download_url'] as String), + type: json['type'] as String?, + content: json['content'] as String?, + encoding: json['encoding'] as String?, + links: json['_links'] == null + ? null + : Links.fromJson(json['_links'] as Map), + license: json['license'] == null + ? null + : LicenseKind.fromJson(json['license'] as Map), + ); + +Map _$LicenseDetailsToJson(LicenseDetails instance) => + { + 'name': instance.name, + 'path': instance.path, + 'sha': instance.sha, + 'size': instance.size, + 'url': instance.url?.toString(), + 'html_url': instance.htmlUrl?.toString(), + 'git_url': instance.gitUrl?.toString(), + 'download_url': instance.downloadUrl?.toString(), + 'type': instance.type, + 'content': instance.content, + 'encoding': instance.encoding, + '_links': instance.links, + 'license': instance.license, + }; + +LicenseKind _$LicenseKindFromJson(Map json) => LicenseKind( + key: json['key'] as String?, + name: json['name'] as String?, + spdxId: json['spdx_id'] as String?, + url: json['url'] == null ? null : Uri.parse(json['url'] as String), + nodeId: json['node_id'] as String?, + ); + +Map _$LicenseKindToJson(LicenseKind instance) => + { + 'key': instance.key, + 'name': instance.name, + 'spdx_id': instance.spdxId, + 'url': instance.url?.toString(), + 'node_id': instance.nodeId, + }; diff --git a/lib/src/common/model/repos_commits.dart b/lib/src/common/model/repos_commits.dart index 10dcc0e0..f79c774f 100644 --- a/lib/src/common/model/repos_commits.dart +++ b/lib/src/common/model/repos_commits.dart @@ -1,125 +1,190 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'repos_commits.g.dart'; /// Model class for a commit in a repository. /// /// Note: The [RepositoryCommit] wraps a [GitCommit], so author/committer /// information is in two places, but contain different details about them: /// in [RepositoryCommit] "github details", in [GitCommit] "git details". +@JsonSerializable() class RepositoryCommit { + RepositoryCommit({ + this.url, + this.sha, + this.htmlUrl, + this.commentsUrl, + this.commit, + this.author, + this.committer, + this.parents, + this.stats, + this.files, + }); + /// API url. - String url; + String? url; /// Commit SHA - String sha; + String? sha; /// Url to Commit Page - @ApiName("html_url") - String htmlUrl; + @JsonKey(name: 'html_url') + String? htmlUrl; /// Comments url. - @ApiName("comments_url") - String commentsUrl; + @JsonKey(name: 'comments_url') + String? commentsUrl; /// A reference to the raw [GitCommit]. - GitCommit commit; + GitCommit? commit; /// Commit Author - User author; + User? author; /// Commit Committer. - User committer; + User? committer; /// Commit parents. - List parents; + List? parents; /// Commit statistics. - CommitStats stats; + CommitStats? stats; /// The files changed in this commit. - List files; - - static RepositoryCommit fromJSON(Map input) { - if (input == null) return null; - - var commit = new RepositoryCommit() - ..url = input['url'] - ..sha = input['sha'] - ..htmlUrl = input['html_url'] - ..commentsUrl = input['comments_url'] - ..commit = GitCommit.fromJSON(input['commit'] as Map) - ..author = User.fromJSON(input['author'] as Map) - ..committer = User.fromJSON(input['committer'] as Map) - ..stats = CommitStats.fromJSON(input['stats'] as Map); - - if (input['parents'] != null) { - commit.parents = (input['parents'] as List>) - .map((parent) => GitCommit.fromJSON(parent)) - .toList(); - } - - if (input['files'] != null) { - commit.files = (input['files'] as List>) - .map((file) => CommitFile.fromJSON(file)) - .toList(); - } - - return commit; - } + List? files; + + factory RepositoryCommit.fromJson(Map input) => + _$RepositoryCommitFromJson(input); + Map toJson() => _$RepositoryCommitToJson(this); } /// Model class for commit statistics. +@JsonSerializable() class CommitStats { + CommitStats({ + this.additions, + this.deletions, + this.total, + }); + /// Number of Additions. - int additions; + int? additions; /// Number of Deletions. - int deletions; + int? deletions; /// Total changes. - int total; + int? total; - static CommitStats fromJSON(Map input) { - if (input == null) return null; - - return new CommitStats() - ..additions = input['additions'] - ..deletions = input['deletions'] - ..total = input['total']; - } + factory CommitStats.fromJson(Map input) => + _$CommitStatsFromJson(input); + Map toJson() => _$CommitStatsToJson(this); } /// Model class of a file that was changed in a commit. +@JsonSerializable() class CommitFile { - @ApiName("filename") - String name; - - int additions; - int deletions; - int changes; - String status; - - @ApiName("raw_url") - String rawUrl; - - @ApiName("blob_url") - String blobUrl; - - String patch; - - Map json; - - static CommitFile fromJSON(Map input) { - if (input == null) return null; - - return new CommitFile() - ..name = input['filename'] - ..additions = input['additions'] - ..deletions = input['deletions'] - ..changes = input['changes'] - ..status = input['status'] - ..rawUrl = input['raw_url'] - ..blobUrl = input['blob_url'] - ..patch = input['patch'] - ..json = input; - } + CommitFile({ + this.name, + this.additions, + this.deletions, + this.changes, + this.status, + this.rawUrl, + this.blobUrl, + this.patch, + }); + @JsonKey(name: 'filename') + String? name; + + int? additions; + int? deletions; + int? changes; + String? status; + + @JsonKey(name: 'raw_url') + String? rawUrl; + + @JsonKey(name: 'blob_url') + String? blobUrl; + + String? patch; + + factory CommitFile.fromJson(Map input) => + _$CommitFileFromJson(input); + Map toJson() => _$CommitFileToJson(this); +} + +/// Model class for a commit comment. +/// +/// See https://developer.github.com/v3/repos/comments +@JsonSerializable() +class CommitComment { + CommitComment({ + this.id, + this.line, + this.position, + this.path, + this.apiUrl, + this.commitId, + this.createdAt, + this.htmlUrl, + this.updatedAt, + this.body, + + // Properties from the Timeline API + this.authorAssociation, + this.nodeId, + this.reactions, + this.user, + }); + + /// Id of the comment + int? id; + + /// Relative path of the file on which the comment has been posted + String? path; + + /// Line on file + int? line; + + /// Position on the diff + int? position; + + /// SHA of the commit where the comment has been made + String? commitId; + + DateTime? createdAt; + + /// Can be equals to [createdAt] + DateTime? updatedAt; + + /// Ex: https://github.com/... + String? htmlUrl; + + /// Ex: https://api.github.com/... + @JsonKey(name: 'url') + String? apiUrl; + + /// Content of the comment + String? body; + + // The following properties were added to support the Timeline API. + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + String? nodeId; + + ReactionRollup? reactions; + + User? user; + + factory CommitComment.fromJson(Map input) => + _$CommitCommentFromJson(input); + Map toJson() => _$CommitCommentToJson(this); } diff --git a/lib/src/common/model/repos_commits.g.dart b/lib/src/common/model/repos_commits.g.dart new file mode 100644 index 00000000..a4b3a5fe --- /dev/null +++ b/lib/src/common/model/repos_commits.g.dart @@ -0,0 +1,127 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_commits.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RepositoryCommit _$RepositoryCommitFromJson(Map json) => + RepositoryCommit( + url: json['url'] as String?, + sha: json['sha'] as String?, + htmlUrl: json['html_url'] as String?, + commentsUrl: json['comments_url'] as String?, + commit: json['commit'] == null + ? null + : GitCommit.fromJson(json['commit'] as Map), + author: json['author'] == null + ? null + : User.fromJson(json['author'] as Map), + committer: json['committer'] == null + ? null + : User.fromJson(json['committer'] as Map), + parents: (json['parents'] as List?) + ?.map((e) => GitCommit.fromJson(e as Map)) + .toList(), + stats: json['stats'] == null + ? null + : CommitStats.fromJson(json['stats'] as Map), + files: (json['files'] as List?) + ?.map((e) => CommitFile.fromJson(e as Map)) + .toList(), + ); + +Map _$RepositoryCommitToJson(RepositoryCommit instance) => + { + 'url': instance.url, + 'sha': instance.sha, + 'html_url': instance.htmlUrl, + 'comments_url': instance.commentsUrl, + 'commit': instance.commit, + 'author': instance.author, + 'committer': instance.committer, + 'parents': instance.parents, + 'stats': instance.stats, + 'files': instance.files, + }; + +CommitStats _$CommitStatsFromJson(Map json) => CommitStats( + additions: (json['additions'] as num?)?.toInt(), + deletions: (json['deletions'] as num?)?.toInt(), + total: (json['total'] as num?)?.toInt(), + ); + +Map _$CommitStatsToJson(CommitStats instance) => + { + 'additions': instance.additions, + 'deletions': instance.deletions, + 'total': instance.total, + }; + +CommitFile _$CommitFileFromJson(Map json) => CommitFile( + name: json['filename'] as String?, + additions: (json['additions'] as num?)?.toInt(), + deletions: (json['deletions'] as num?)?.toInt(), + changes: (json['changes'] as num?)?.toInt(), + status: json['status'] as String?, + rawUrl: json['raw_url'] as String?, + blobUrl: json['blob_url'] as String?, + patch: json['patch'] as String?, + ); + +Map _$CommitFileToJson(CommitFile instance) => + { + 'filename': instance.name, + 'additions': instance.additions, + 'deletions': instance.deletions, + 'changes': instance.changes, + 'status': instance.status, + 'raw_url': instance.rawUrl, + 'blob_url': instance.blobUrl, + 'patch': instance.patch, + }; + +CommitComment _$CommitCommentFromJson(Map json) => + CommitComment( + id: (json['id'] as num?)?.toInt(), + line: (json['line'] as num?)?.toInt(), + position: (json['position'] as num?)?.toInt(), + path: json['path'] as String?, + apiUrl: json['url'] as String?, + commitId: json['commit_id'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + htmlUrl: json['html_url'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + body: json['body'] as String?, + authorAssociation: json['author_association'] as String?, + nodeId: json['node_id'] as String?, + reactions: json['reactions'] == null + ? null + : ReactionRollup.fromJson(json['reactions'] as Map), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$CommitCommentToJson(CommitComment instance) => + { + 'id': instance.id, + 'path': instance.path, + 'line': instance.line, + 'position': instance.position, + 'commit_id': instance.commitId, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'html_url': instance.htmlUrl, + 'url': instance.apiUrl, + 'body': instance.body, + 'author_association': instance.authorAssociation, + 'node_id': instance.nodeId, + 'reactions': instance.reactions, + 'user': instance.user, + }; diff --git a/lib/src/common/model/repos_contents.dart b/lib/src/common/model/repos_contents.dart index 35075781..53250994 100644 --- a/lib/src/common/model/repos_contents.dart +++ b/lib/src/common/model/repos_contents.dart @@ -1,157 +1,155 @@ -part of github.common; +import 'dart:convert'; +import 'package:github/github.dart'; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'repos_contents.g.dart'; /// Model class for a file on GitHub. +@JsonSerializable() class GitHubFile { + GitHubFile({ + this.type, + this.encoding, + this.size, + this.name, + this.path, + this.content, + this.sha, + this.htmlUrl, + this.gitUrl, + this.downloadUrl, + this.links, + this.sourceRepository, + }); + /// Type of File - String type; + String? type; /// File Encoding - String encoding; + String? encoding; /// File Size - int size; + int? size; /// File Name - String name; + String? name; /// File Path - String path; + String? path; - /// File Content - String content; + /// Base-64 encoded file content with newlines. + String? content; /// SHA - String sha; + String? sha; /// Url to file - @ApiName("html_url") - String htmlUrl; + @JsonKey(name: 'html_url') + String? htmlUrl; /// Git Url - @ApiName("git_url") - String gitUrl; + @JsonKey(name: 'git_url') + String? gitUrl; + + /// Download Url + @JsonKey(name: 'download_url') + String? downloadUrl; /// Links - @ApiName("_links") - Links links; + @JsonKey(name: '_links') + Links? links; - /// Text Content + /// The value in [content] Base-64 decoded. String get text { - if (_text == null) { - _text = UTF8.decode(BASE64.decode(content)); - } - return _text; + return _text ??= + utf8.decode(base64Decode(LineSplitter.split(content!).join())); } - String _text; + String? _text; /// Source Repository - RepositorySlug sourceRepository; - - static GitHubFile fromJSON(Map input, - [RepositorySlug slug]) { - if (input == null) return null; - - return new GitHubFile() - ..type = input['type'] - ..encoding = input['encoding'] - ..size = input['size'] - ..name = input['name'] - ..path = input['path'] - ..content = input['content'] - ..sha = input['sha'] - ..gitUrl = input['git_url'] - ..htmlUrl = input['html_url'] - ..links = Links.fromJSON(input['_links'] as Map) - ..sourceRepository = slug; - } + RepositorySlug? sourceRepository; + + factory GitHubFile.fromJson(Map input) => + _$GitHubFileFromJson(input); + Map toJson() => _$GitHubFileToJson(this); } -/// File links. +@JsonSerializable() class Links { - /// Git Link - @ApiName("git") - String git; - - /// Self Link - @ApiName("self") - String self; - - /// HTML Link - @ApiName("html") - String html; - - static Links fromJSON(Map input) { - if (input == null) return null; - - var links = new Links(); - links.git = input['git']; - links.self = input['self']; - links.html = input['html']; - return links; - } + final Uri? self; + final Uri? git; + final Uri? html; + + Links({this.git, this.self, this.html}); + + factory Links.fromJson(Map input) => _$LinksFromJson(input); + + Map toJson() => _$LinksToJson(this); } /// Model class for a file or directory. +@JsonSerializable() class RepositoryContents { - GitHubFile file; - List tree; + RepositoryContents({ + this.file, + this.tree, + }); + GitHubFile? file; + List? tree; bool get isFile => file != null; bool get isDirectory => tree != null; + + factory RepositoryContents.fromJson(Map json) => + _$RepositoryContentsFromJson(json); + + Map toJson() => _$RepositoryContentsToJson(this); } /// Model class for a new file to be created. + +@JsonSerializable() class CreateFile { - final String path; - final String message; - final String content; - - String branch; - CommitUser committer; - - CreateFile(this.path, this.content, this.message); - - String toJSON() { - var map = {}; - putValue("path", path, map); - putValue("message", message, map); - putValue("content", content, map); - putValue("branch", branch, map); - putValue("committer", committer != null ? committer.toMap() : null, map); - return JSON.encode(map); - } + CreateFile( + {this.path, this.content, this.message, this.branch, this.committer}); + + String? path; + String? message; + String? content; + String? branch; + CommitUser? committer; + + factory CreateFile.fromJson(Map json) => + _$CreateFileFromJson(json); + + Map toJson() => _$CreateFileToJson(this); } /// Model class for a committer of a commit. +@JsonSerializable() class CommitUser { - final String name; - final String email; - CommitUser(this.name, this.email); - Map toMap() { - var map = {}; + final String? name; + final String? email; - putValue('name', name, map); - putValue('email', email, map); + factory CommitUser.fromJson(Map input) => + _$CommitUserFromJson(input); - return map; - } + Map toJson() => _$CommitUserToJson(this); } /// Model class for the response of a content creation. +@JsonSerializable() class ContentCreation { - final RepositoryCommit commit; - final GitHubFile content; + final RepositoryCommit? commit; + final GitHubFile? content; ContentCreation(this.commit, this.content); - static ContentCreation fromJSON(Map input) { - if (input == null) return null; - - return new ContentCreation( - RepositoryCommit.fromJSON(input['commit'] as Map), - GitHubFile.fromJSON(input['content'] as Map)); - } + factory ContentCreation.fromJson(Map input) => + _$ContentCreationFromJson(input); + Map toJson() => _$ContentCreationToJson(this); } diff --git a/lib/src/common/model/repos_contents.g.dart b/lib/src/common/model/repos_contents.g.dart new file mode 100644 index 00000000..84dc677b --- /dev/null +++ b/lib/src/common/model/repos_contents.g.dart @@ -0,0 +1,117 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_contents.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GitHubFile _$GitHubFileFromJson(Map json) => GitHubFile( + type: json['type'] as String?, + encoding: json['encoding'] as String?, + size: (json['size'] as num?)?.toInt(), + name: json['name'] as String?, + path: json['path'] as String?, + content: json['content'] as String?, + sha: json['sha'] as String?, + htmlUrl: json['html_url'] as String?, + gitUrl: json['git_url'] as String?, + downloadUrl: json['download_url'] as String?, + links: json['_links'] == null + ? null + : Links.fromJson(json['_links'] as Map), + sourceRepository: json['source_repository'] == null + ? null + : RepositorySlug.fromJson( + json['source_repository'] as Map), + ); + +Map _$GitHubFileToJson(GitHubFile instance) => + { + 'type': instance.type, + 'encoding': instance.encoding, + 'size': instance.size, + 'name': instance.name, + 'path': instance.path, + 'content': instance.content, + 'sha': instance.sha, + 'html_url': instance.htmlUrl, + 'git_url': instance.gitUrl, + 'download_url': instance.downloadUrl, + '_links': instance.links, + 'source_repository': instance.sourceRepository, + }; + +Links _$LinksFromJson(Map json) => Links( + git: json['git'] == null ? null : Uri.parse(json['git'] as String), + self: json['self'] == null ? null : Uri.parse(json['self'] as String), + html: json['html'] == null ? null : Uri.parse(json['html'] as String), + ); + +Map _$LinksToJson(Links instance) => { + 'self': instance.self?.toString(), + 'git': instance.git?.toString(), + 'html': instance.html?.toString(), + }; + +RepositoryContents _$RepositoryContentsFromJson(Map json) => + RepositoryContents( + file: json['file'] == null + ? null + : GitHubFile.fromJson(json['file'] as Map), + tree: (json['tree'] as List?) + ?.map((e) => GitHubFile.fromJson(e as Map)) + .toList(), + ); + +Map _$RepositoryContentsToJson(RepositoryContents instance) => + { + 'file': instance.file, + 'tree': instance.tree, + }; + +CreateFile _$CreateFileFromJson(Map json) => CreateFile( + path: json['path'] as String?, + content: json['content'] as String?, + message: json['message'] as String?, + branch: json['branch'] as String?, + committer: json['committer'] == null + ? null + : CommitUser.fromJson(json['committer'] as Map), + ); + +Map _$CreateFileToJson(CreateFile instance) => + { + 'path': instance.path, + 'message': instance.message, + 'content': instance.content, + 'branch': instance.branch, + 'committer': instance.committer, + }; + +CommitUser _$CommitUserFromJson(Map json) => CommitUser( + json['name'] as String?, + json['email'] as String?, + ); + +Map _$CommitUserToJson(CommitUser instance) => + { + 'name': instance.name, + 'email': instance.email, + }; + +ContentCreation _$ContentCreationFromJson(Map json) => + ContentCreation( + json['commit'] == null + ? null + : RepositoryCommit.fromJson(json['commit'] as Map), + json['content'] == null + ? null + : GitHubFile.fromJson(json['content'] as Map), + ); + +Map _$ContentCreationToJson(ContentCreation instance) => + { + 'commit': instance.commit, + 'content': instance.content, + }; diff --git a/lib/src/common/model/repos_forks.dart b/lib/src/common/model/repos_forks.dart index 3dbbe6c5..cbf4b68c 100644 --- a/lib/src/common/model/repos_forks.dart +++ b/lib/src/common/model/repos_forks.dart @@ -1,14 +1,14 @@ -part of github.common; +import 'package:json_annotation/json_annotation.dart'; +part 'repos_forks.g.dart'; /// Model class for a new fork to be created. +@JsonSerializable() class CreateFork { - final String organization; - CreateFork([this.organization]); - String toJSON() { - var map = {}; - putValue("organization", organization, map); - return JSON.encode(map); - } + String? organization; + + factory CreateFork.fromJson(Map input) => + _$CreateForkFromJson(input); + Map toJson() => _$CreateForkToJson(this); } diff --git a/lib/src/common/model/repos_forks.g.dart b/lib/src/common/model/repos_forks.g.dart new file mode 100644 index 00000000..0d41f4e9 --- /dev/null +++ b/lib/src/common/model/repos_forks.g.dart @@ -0,0 +1,16 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_forks.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CreateFork _$CreateForkFromJson(Map json) => CreateFork( + json['organization'] as String?, + ); + +Map _$CreateForkToJson(CreateFork instance) => + { + 'organization': instance.organization, + }; diff --git a/lib/src/common/model/repos_hooks.dart b/lib/src/common/model/repos_hooks.dart index f5d6517b..f39972a1 100644 --- a/lib/src/common/model/repos_hooks.dart +++ b/lib/src/common/model/repos_hooks.dart @@ -1,70 +1,78 @@ -part of github.common; +import 'package:json_annotation/json_annotation.dart'; + +part 'repos_hooks.g.dart'; /// Model class for a repository hook. +@JsonSerializable() class Hook { + Hook({ + this.id, + this.name, + }); + + int? id; + String? name; + /// Events to Subscribe to - List events; + List? events; /// Content Type - @ApiName("config/content_type") - String contentType; + String? get contentType => config!.contentType; /// If the hook is active - bool active; - - /// Hook ID - int id; - - /// Hook Name - String name; + bool? active; /// The time the hook was created - @ApiName("created_at") - DateTime createdAt; + DateTime? createdAt; /// The last time the hook was updated - @ApiName("updated_at") - DateTime updatedAt; + DateTime? updatedAt; /// The Repository Name - String repoName; - - Map config; - - static Hook fromJSON(String repoName, Map input) { - if (input == null) return null; - - return new Hook() - ..events = input['events'] as List - ..active = input['active'] - ..name = input['name'] - ..id = input['id'] - ..repoName = repoName - ..updatedAt = parseDateTime(input['updated_at']) - ..createdAt = parseDateTime(input['created_at']) - ..config = input['config'] as Map; - } + String? repoName; + + HookConfig? config; + + factory Hook.fromJson(Map input) => _$HookFromJson(input); + Map toJson() => _$HookToJson(this); +} + +@JsonSerializable() +class HookConfig { + HookConfig({ + this.url, + this.contentType, + this.secret, + this.insecureSsl, + }); + String? url; + String? contentType; + String? secret; + String? insecureSsl; + factory HookConfig.fromJson(Map input) => + _$HookConfigFromJson(input); + Map toJson() => _$HookConfigToJson(this); } /// Model class for a new hook to be created. +@JsonSerializable() class CreateHook { /// Hook Name - final String name; + final String? name; /// Hook Configuration - final Map config; + final HookConfig? config; /// Events to Subscribe to - final List events; + final List? events; /// If the Hook should be active. - final bool active; + final bool? active; CreateHook(this.name, this.config, - {this.events: const ["push"], this.active: true}); + {this.events = const ['push'], this.active = true}); - String toJSON() { - return JSON.encode( - {"name": name, "config": config, "events": events, "active": active}); - } + factory CreateHook.fromJson(Map input) => + _$CreateHookFromJson(input); + Map toJson() => _$CreateHookToJson(this); } diff --git a/lib/src/common/model/repos_hooks.g.dart b/lib/src/common/model/repos_hooks.g.dart new file mode 100644 index 00000000..fa57fa4f --- /dev/null +++ b/lib/src/common/model/repos_hooks.g.dart @@ -0,0 +1,71 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_hooks.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Hook _$HookFromJson(Map json) => Hook( + id: (json['id'] as num?)?.toInt(), + name: json['name'] as String?, + ) + ..events = + (json['events'] as List?)?.map((e) => e as String).toList() + ..active = json['active'] as bool? + ..createdAt = json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String) + ..updatedAt = json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String) + ..repoName = json['repo_name'] as String? + ..config = json['config'] == null + ? null + : HookConfig.fromJson(json['config'] as Map); + +Map _$HookToJson(Hook instance) => { + 'id': instance.id, + 'name': instance.name, + 'events': instance.events, + 'active': instance.active, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'repo_name': instance.repoName, + 'config': instance.config, + }; + +HookConfig _$HookConfigFromJson(Map json) => HookConfig( + url: json['url'] as String?, + contentType: json['content_type'] as String?, + secret: json['secret'] as String?, + insecureSsl: json['insecure_ssl'] as String?, + ); + +Map _$HookConfigToJson(HookConfig instance) => + { + 'url': instance.url, + 'content_type': instance.contentType, + 'secret': instance.secret, + 'insecure_ssl': instance.insecureSsl, + }; + +CreateHook _$CreateHookFromJson(Map json) => CreateHook( + json['name'] as String?, + json['config'] == null + ? null + : HookConfig.fromJson(json['config'] as Map), + events: (json['events'] as List?) + ?.map((e) => e as String) + .toList() ?? + const ['push'], + active: json['active'] as bool? ?? true, + ); + +Map _$CreateHookToJson(CreateHook instance) => + { + 'name': instance.name, + 'config': instance.config, + 'events': instance.events, + 'active': instance.active, + }; diff --git a/lib/src/common/model/repos_merging.dart b/lib/src/common/model/repos_merging.dart index 4f720b27..7deaf0e5 100644 --- a/lib/src/common/model/repos_merging.dart +++ b/lib/src/common/model/repos_merging.dart @@ -1,20 +1,16 @@ -part of github.common; +import 'package:json_annotation/json_annotation.dart'; +part 'repos_merging.g.dart'; /// Model class for a new merge to be created. +@JsonSerializable() class CreateMerge { - final String base; - final String head; + CreateMerge(this.base, this.head, {this.commitMessage}); - @ApiName("commit_message") - String commitMessage; + final String? base; + final String? head; + String? commitMessage; - CreateMerge(this.base, this.head); - - String toJSON() { - var map = {}; - putValue("base", base, map); - putValue("head", head, map); - putValue("commit_message", commitMessage, map); - return JSON.encode(map); - } + factory CreateMerge.fromJson(Map input) => + _$CreateMergeFromJson(input); + Map toJson() => _$CreateMergeToJson(this); } diff --git a/lib/src/common/model/repos_merging.g.dart b/lib/src/common/model/repos_merging.g.dart new file mode 100644 index 00000000..be3777c0 --- /dev/null +++ b/lib/src/common/model/repos_merging.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_merging.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CreateMerge _$CreateMergeFromJson(Map json) => CreateMerge( + json['base'] as String?, + json['head'] as String?, + commitMessage: json['commit_message'] as String?, + ); + +Map _$CreateMergeToJson(CreateMerge instance) => + { + 'base': instance.base, + 'head': instance.head, + 'commit_message': instance.commitMessage, + }; diff --git a/lib/src/common/model/repos_pages.dart b/lib/src/common/model/repos_pages.dart index e1a78d30..09d0bf0b 100644 --- a/lib/src/common/model/repos_pages.dart +++ b/lib/src/common/model/repos_pages.dart @@ -1,24 +1,81 @@ -part of github.common; +import 'package:json_annotation/json_annotation.dart'; + +part 'repos_pages.g.dart'; /// GitHub Pages Information +@JsonSerializable() class RepositoryPages { - /// Pages CNAME - String cname; + RepositoryPages({ + this.cname, + this.status, + this.hasCustom404, + }); + + String? cname; + String? status; + @JsonKey(name: 'custom_404') + bool? hasCustom404; + + factory RepositoryPages.fromJson(Map input) => + _$RepositoryPagesFromJson(input); + Map toJson() => _$RepositoryPagesToJson(this); +} - /// Pages Status - String status; +@JsonSerializable() +class PageBuild { + PageBuild({ + this.url, + this.status, + this.error, + this.pusher, + this.commit, + this.duration, + this.createdAt, + this.updatedAt, + }); + String? url; + String? status; + PageBuildError? error; + PageBuildPusher? pusher; + String? commit; + int? duration; + DateTime? createdAt; + DateTime? updatedAt; - /// If the repo has a custom 404 - @ApiName("custom_404") - bool hasCustom404; + factory PageBuild.fromJson(Map input) => + _$PageBuildFromJson(input); + Map toJson() => _$PageBuildToJson(this); +} + +@JsonSerializable() +class PageBuildPusher { + PageBuildPusher({ + this.login, + this.id, + this.apiUrl, + this.htmlUrl, + this.type, + this.siteAdmin, + }); + int? id; + String? login; + @JsonKey(name: 'url') + String? apiUrl; + String? htmlUrl; + String? type; + bool? siteAdmin; + + factory PageBuildPusher.fromJson(Map input) => + _$PageBuildPusherFromJson(input); + Map toJson() => _$PageBuildPusherToJson(this); +} - static RepositoryPages fromJSON(Map input) { - if (input == null) return null; +@JsonSerializable() +class PageBuildError { + PageBuildError({this.message}); + String? message; - var pages = new RepositoryPages(); - pages.cname = input['cname']; - pages.status = input['status']; - pages.hasCustom404 = input['custom_404']; - return pages; - } + factory PageBuildError.fromJson(Map input) => + _$PageBuildErrorFromJson(input); + Map toJson() => _$PageBuildErrorToJson(this); } diff --git a/lib/src/common/model/repos_pages.g.dart b/lib/src/common/model/repos_pages.g.dart new file mode 100644 index 00000000..ead9a68f --- /dev/null +++ b/lib/src/common/model/repos_pages.g.dart @@ -0,0 +1,81 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_pages.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RepositoryPages _$RepositoryPagesFromJson(Map json) => + RepositoryPages( + cname: json['cname'] as String?, + status: json['status'] as String?, + hasCustom404: json['custom_404'] as bool?, + ); + +Map _$RepositoryPagesToJson(RepositoryPages instance) => + { + 'cname': instance.cname, + 'status': instance.status, + 'custom_404': instance.hasCustom404, + }; + +PageBuild _$PageBuildFromJson(Map json) => PageBuild( + url: json['url'] as String?, + status: json['status'] as String?, + error: json['error'] == null + ? null + : PageBuildError.fromJson(json['error'] as Map), + pusher: json['pusher'] == null + ? null + : PageBuildPusher.fromJson(json['pusher'] as Map), + commit: json['commit'] as String?, + duration: (json['duration'] as num?)?.toInt(), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$PageBuildToJson(PageBuild instance) => { + 'url': instance.url, + 'status': instance.status, + 'error': instance.error, + 'pusher': instance.pusher, + 'commit': instance.commit, + 'duration': instance.duration, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +PageBuildPusher _$PageBuildPusherFromJson(Map json) => + PageBuildPusher( + login: json['login'] as String?, + id: (json['id'] as num?)?.toInt(), + apiUrl: json['url'] as String?, + htmlUrl: json['html_url'] as String?, + type: json['type'] as String?, + siteAdmin: json['site_admin'] as bool?, + ); + +Map _$PageBuildPusherToJson(PageBuildPusher instance) => + { + 'id': instance.id, + 'login': instance.login, + 'url': instance.apiUrl, + 'html_url': instance.htmlUrl, + 'type': instance.type, + 'site_admin': instance.siteAdmin, + }; + +PageBuildError _$PageBuildErrorFromJson(Map json) => + PageBuildError( + message: json['message'] as String?, + ); + +Map _$PageBuildErrorToJson(PageBuildError instance) => + { + 'message': instance.message, + }; diff --git a/lib/src/common/model/repos_releases.dart b/lib/src/common/model/repos_releases.dart index 12bfa81b..30a80172 100644 --- a/lib/src/common/model/repos_releases.dart +++ b/lib/src/common/model/repos_releases.dart @@ -1,165 +1,277 @@ -part of github.common; +import 'dart:typed_data'; + +import 'package:github/src/common/model/users.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'repos_releases.g.dart'; /// Model class for a release. +@JsonSerializable() class Release { + Release({ + this.id, + this.url, + this.htmlUrl, + this.tarballUrl, + this.uploadUrl, + this.nodeId, + this.tagName, + this.targetCommitish, + this.name, + this.body, + this.description, + this.isDraft, + this.isPrerelease, + this.createdAt, + this.publishedAt, + this.author, + this.assets, + }); + /// Url to this Release - @ApiName("html_url") - String htmlUrl; + String? url; + + /// Url to this Release + String? htmlUrl; /// Tarball of the Repository Tree at the commit of this release. - @ApiName("tarball_url") - String tarballUrl; + String? tarballUrl; /// ZIP of the Repository Tree at the commit of this release. - @ApiName("zipball_url") - String zipballUrl; + String? zipballUrl; + + /// The endpoint for uploading release assets. + /// This key is a hypermedia resource. https://developer.github.com/v3/#hypermedia + String? uploadUrl; + + String? assetsUrl; /// Release ID - int id; + int? id; + + String? nodeId; /// Release Tag Name - @ApiName("tag_name") - String tagName; + String? tagName; /// Target Commit - @ApiName("target_commitish") - String targetCommitsh; + String? targetCommitish; /// Release Name - String name; + String? name; /// Release Notes - String body; + String? body; /// Release Description - String description; + String? description; /// If the release is a draft. - bool draft; + @JsonKey(name: 'draft') + bool? isDraft; - /// If the release is a pre release. - bool prerelease; + /// If the release is a pre-release. + @JsonKey(name: 'prerelease') + bool? isPrerelease; /// The time this release was created at. - @ApiName("created_at") - DateTime createdAt; + DateTime? createdAt; /// The time this release was published at. - @ApiName("published_at") - DateTime publishedAt; + DateTime? publishedAt; /// The author of this release. - User author; + User? author; /// Release Assets - List assets; - - static Release fromJSON(Map input) { - if (input == null) return null; - - return new Release() - ..htmlUrl = input['html_url'] - ..tarballUrl = input['tarball_url'] - ..zipballUrl = input['zipball_url'] - ..id = input['id'] - ..tagName = input['tag_name'] - ..targetCommitsh = input['target_commitish'] - ..body = input['body'] - ..description = input['description'] - ..draft = input['draft'] - ..prerelease = input['prelease'] - ..author = input['author'] - ..assets = new List.from(input['assets'] - .map((Map it) => ReleaseAsset.fromJSON(it))) - ..name = input['name'] - ..createdAt = parseDateTime(input['created_at']) - ..publishedAt = parseDateTime(input['published_at']); - } + List? assets; + + List? errors; + + factory Release.fromJson(Map input) => + _$ReleaseFromJson(input); + Map toJson() => _$ReleaseToJson(this); + + String getUploadUrlFor(String name, [String? label]) => + "${uploadUrl!.substring(0, uploadUrl!.indexOf('{'))}?name=$name${label != null ? ",$label" : ""}"; + + bool get hasErrors => errors == null ? false : errors!.isNotEmpty; } /// Model class for a release asset. +@JsonSerializable() class ReleaseAsset { + ReleaseAsset({ + this.id, + this.name, + this.label, + this.state, + this.contentType, + this.size, + this.downloadCount, + this.browserDownloadUrl, + this.createdAt, + this.updatedAt, + }); + /// Url to download the asset. - @ApiName("browser_download_url") - String browserDownloadUrl; + String? browserDownloadUrl; /// Asset ID - int id; + int? id; /// Asset Name - String name; + String? name; /// Asset Label - String label; + String? label; /// Asset State - String state; + String? state; /// Asset Content Type - @ApiName("content_type") - String contentType; + String? contentType; /// Size of Asset - int size; + int? size; /// Number of Downloads - @ApiName("download_count") - int downloadCount; + int? downloadCount; /// Time the asset was created at - @ApiName("created_at") - DateTime createdAt; + DateTime? createdAt; /// Time the asset was last updated - @ApiName("updated_at") - DateTime updatedAt; - - static ReleaseAsset fromJSON(Map input) { - if (input == null) return null; - - return new ReleaseAsset() - ..browserDownloadUrl = input['browser_download_url'] - ..name = input['name'] - ..id = input['id'] - ..label = input['label'] - ..state = input['state'] - ..contentType = input['content_type'] - ..size = input['size'] - ..downloadCount = input['download_count'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']); - } + DateTime? updatedAt; + + factory ReleaseAsset.fromJson(Map input) => + _$ReleaseAssetFromJson(input); + Map toJson() => _$ReleaseAssetToJson(this); } /// Model class for a new release to be created. +@JsonSerializable() class CreateRelease { /// Tag Name to Base off of - final String tagName; + final String? tagName; /// Commit to Target - String targetCommitish; + String? targetCommitish; /// Release Name - String name; + String? name; /// Release Body - String body; + String? body; /// If the release is a draft - bool draft; + @JsonKey(name: 'draft') + bool? isDraft; - /// If the release should actually be released. - bool release; + /// true to identify the release as a prerelease. + /// false to identify the release as a full release. Default: false + @JsonKey(name: 'prerelease') + bool? isPrerelease; + + String? discussionCategoryName; + + @JsonKey(defaultValue: false) + bool generateReleaseNotes = false; CreateRelease(this.tagName); - String toJSON() { - var map = {}; - putValue("tag_name", tagName, map); - putValue("name", name, map); - putValue("body", body, map); - putValue("draft", draft, map); - putValue("release", release, map); - return JSON.encode(map); - } + CreateRelease.from( + {required this.tagName, + required this.name, + required this.targetCommitish, + required this.isDraft, + required this.isPrerelease, + this.body, + this.discussionCategoryName, + this.generateReleaseNotes = false}); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is CreateRelease && + runtimeType == other.runtimeType && + tagName == other.tagName && + targetCommitish == other.targetCommitish && + name == other.name && + body == other.body && + isDraft == other.isDraft && + isPrerelease == other.isPrerelease && + generateReleaseNotes == other.generateReleaseNotes && + discussionCategoryName == other.discussionCategoryName; + + @override + int get hashCode => + tagName.hashCode ^ + targetCommitish.hashCode ^ + name.hashCode ^ + body.hashCode ^ + isDraft.hashCode ^ + isPrerelease.hashCode ^ + discussionCategoryName.hashCode ^ + generateReleaseNotes.hashCode; + + factory CreateRelease.fromJson(Map input) => + _$CreateReleaseFromJson(input); + Map toJson() => _$CreateReleaseToJson(this); +} + +class CreateReleaseAsset { + CreateReleaseAsset({ + required this.name, + required this.contentType, + required this.assetData, + this.label, + }); + + /// The file name of the asset. + String name; + + /// An alternate short description of the asset. Used in place of the filename. + String? label; + + /// The media type of the asset. + /// + /// For a list of media types, + /// see [Media Types](https://www.iana.org/assignments/media-types/media-types.xhtml). + /// For example: application/zip + String contentType; + + /// The raw binary data for the asset being uploaded. + /// + /// GitHub expects the asset data in its raw binary form, rather than JSON. + Uint8List assetData; +} + +/// Holds release notes information +@JsonSerializable() +class ReleaseNotes { + ReleaseNotes(this.name, this.body); + String name; + String body; + + factory ReleaseNotes.fromJson(Map input) => + _$ReleaseNotesFromJson(input); + Map toJson() => _$ReleaseNotesToJson(this); +} + +@JsonSerializable() +class CreateReleaseNotes { + CreateReleaseNotes(this.owner, this.repo, this.tagName, + {this.targetCommitish, this.previousTagName, this.configurationFilePath}); + + String owner; + String repo; + String tagName; + String? targetCommitish; + String? previousTagName; + String? configurationFilePath; + + factory CreateReleaseNotes.fromJson(Map input) => + _$CreateReleaseNotesFromJson(input); + Map toJson() => _$CreateReleaseNotesToJson(this); } diff --git a/lib/src/common/model/repos_releases.g.dart b/lib/src/common/model/repos_releases.g.dart new file mode 100644 index 00000000..e0596897 --- /dev/null +++ b/lib/src/common/model/repos_releases.g.dart @@ -0,0 +1,147 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_releases.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Release _$ReleaseFromJson(Map json) => Release( + id: (json['id'] as num?)?.toInt(), + url: json['url'] as String?, + htmlUrl: json['html_url'] as String?, + tarballUrl: json['tarball_url'] as String?, + uploadUrl: json['upload_url'] as String?, + nodeId: json['node_id'] as String?, + tagName: json['tag_name'] as String?, + targetCommitish: json['target_commitish'] as String?, + name: json['name'] as String?, + body: json['body'] as String?, + description: json['description'] as String?, + isDraft: json['draft'] as bool?, + isPrerelease: json['prerelease'] as bool?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + publishedAt: json['published_at'] == null + ? null + : DateTime.parse(json['published_at'] as String), + author: json['author'] == null + ? null + : User.fromJson(json['author'] as Map), + assets: (json['assets'] as List?) + ?.map((e) => ReleaseAsset.fromJson(e as Map)) + .toList(), + ) + ..zipballUrl = json['zipball_url'] as String? + ..assetsUrl = json['assets_url'] as String? + ..errors = json['errors'] as List?; + +Map _$ReleaseToJson(Release instance) => { + 'url': instance.url, + 'html_url': instance.htmlUrl, + 'tarball_url': instance.tarballUrl, + 'zipball_url': instance.zipballUrl, + 'upload_url': instance.uploadUrl, + 'assets_url': instance.assetsUrl, + 'id': instance.id, + 'node_id': instance.nodeId, + 'tag_name': instance.tagName, + 'target_commitish': instance.targetCommitish, + 'name': instance.name, + 'body': instance.body, + 'description': instance.description, + 'draft': instance.isDraft, + 'prerelease': instance.isPrerelease, + 'created_at': instance.createdAt?.toIso8601String(), + 'published_at': instance.publishedAt?.toIso8601String(), + 'author': instance.author, + 'assets': instance.assets, + 'errors': instance.errors, + }; + +ReleaseAsset _$ReleaseAssetFromJson(Map json) => ReleaseAsset( + id: (json['id'] as num?)?.toInt(), + name: json['name'] as String?, + label: json['label'] as String?, + state: json['state'] as String?, + contentType: json['content_type'] as String?, + size: (json['size'] as num?)?.toInt(), + downloadCount: (json['download_count'] as num?)?.toInt(), + browserDownloadUrl: json['browser_download_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$ReleaseAssetToJson(ReleaseAsset instance) => + { + 'browser_download_url': instance.browserDownloadUrl, + 'id': instance.id, + 'name': instance.name, + 'label': instance.label, + 'state': instance.state, + 'content_type': instance.contentType, + 'size': instance.size, + 'download_count': instance.downloadCount, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +CreateRelease _$CreateReleaseFromJson(Map json) => + CreateRelease( + json['tag_name'] as String?, + ) + ..targetCommitish = json['target_commitish'] as String? + ..name = json['name'] as String? + ..body = json['body'] as String? + ..isDraft = json['draft'] as bool? + ..isPrerelease = json['prerelease'] as bool? + ..discussionCategoryName = json['discussion_category_name'] as String? + ..generateReleaseNotes = json['generate_release_notes'] as bool? ?? false; + +Map _$CreateReleaseToJson(CreateRelease instance) => + { + 'tag_name': instance.tagName, + 'target_commitish': instance.targetCommitish, + 'name': instance.name, + 'body': instance.body, + 'draft': instance.isDraft, + 'prerelease': instance.isPrerelease, + 'discussion_category_name': instance.discussionCategoryName, + 'generate_release_notes': instance.generateReleaseNotes, + }; + +ReleaseNotes _$ReleaseNotesFromJson(Map json) => ReleaseNotes( + json['name'] as String, + json['body'] as String, + ); + +Map _$ReleaseNotesToJson(ReleaseNotes instance) => + { + 'name': instance.name, + 'body': instance.body, + }; + +CreateReleaseNotes _$CreateReleaseNotesFromJson(Map json) => + CreateReleaseNotes( + json['owner'] as String, + json['repo'] as String, + json['tag_name'] as String, + targetCommitish: json['target_commitish'] as String?, + previousTagName: json['previous_tag_name'] as String?, + configurationFilePath: json['configuration_file_path'] as String?, + ); + +Map _$CreateReleaseNotesToJson(CreateReleaseNotes instance) => + { + 'owner': instance.owner, + 'repo': instance.repo, + 'tag_name': instance.tagName, + 'target_commitish': instance.targetCommitish, + 'previous_tag_name': instance.previousTagName, + 'configuration_file_path': instance.configurationFilePath, + }; diff --git a/lib/src/common/model/repos_stats.dart b/lib/src/common/model/repos_stats.dart index b80c1e71..449ad36c 100644 --- a/lib/src/common/model/repos_stats.dart +++ b/lib/src/common/model/repos_stats.dart @@ -1,130 +1,143 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:github/src/common/model/users.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'repos_stats.g.dart'; /// Model class for a contributor's statistics for a repository. +@JsonSerializable() class ContributorStatistics { - /// The Author - User author; + ContributorStatistics(this.author, this.total, this.weeks); + + final User? author; /// Total Commits - int total; + final int? total; /// Weekly Statistics - List weeks; - - static ContributorStatistics fromJSON(Map input) { - if (input == null) return null; + final List? weeks; - return new ContributorStatistics() - ..author = User.fromJSON(input['author'] as Map) - ..total = input['total'] - ..weeks = (input['weeks'] as List>) - .map((it) => ContributorWeekStatistics.fromJSON(it)); - } + factory ContributorStatistics.fromJson(Map input) => + _$ContributorStatisticsFromJson(input); + Map toJson() => _$ContributorStatisticsToJson(this); } /// Model class to represent the number of additions, deletions and commits /// a contributor made in a given week. +@JsonSerializable() class ContributorWeekStatistics { + ContributorWeekStatistics( + this.start, this.additions, this.deletions, this.commits); + /// Beginning of the Week (As a Unix Timestamp) - String start; + @JsonKey(name: 'w') + final int? start; /// Number of Additions - int additions; + @JsonKey(name: 'a') + final int? additions; /// Number of Deletions - int deletions; + @JsonKey(name: 'd') + final int? deletions; /// Number of Commits - int commits; + @JsonKey(name: 'c') + final int? commits; - static ContributorWeekStatistics fromJSON(Map input) { - if (input == null) return null; + factory ContributorWeekStatistics.fromJson(Map input) => + _$ContributorWeekStatisticsFromJson(input); + Map toJson() => _$ContributorWeekStatisticsToJson(this); - return new ContributorWeekStatistics() - ..additions = input['a'] - ..deletions = input['d'] - ..commits = input['c'] - ..start = input['w']; - } + @override + String toString() => + 'ContributorWeekStatistics(start: $start, commits: $commits, additions: $additions, deletions: $deletions)'; } /// Model class for contributor participation. +@JsonSerializable() class ContributorParticipation { + ContributorParticipation({ + this.all, + this.owner, + }); + /// Commit Counts for All Users - List all; + List? all; /// Commit Counts for the Owner - List owner; - - static ContributorParticipation fromJSON(Map input) { - if (input == null) return null; + List? owner; - return new ContributorParticipation() - ..all = input['all'] as List - ..owner = input['owner'] as List; - } + factory ContributorParticipation.fromJson(Map input) => + _$ContributorParticipationFromJson(input); + Map toJson() => _$ContributorParticipationToJson(this); } /// Model class for a week in a full year commit count. +@JsonSerializable() class YearCommitCountWeek { + YearCommitCountWeek({ + this.days, + this.total, + this.timestamp, + }); + /// Commit Counts for each day (starting with Sunday) - List days; + List? days; /// Total Commit Count - int total; + int? total; /// Timestamp for Beginning of Week - int timestamp; - - static YearCommitCountWeek fromJSON(Map input) { - if (input == null) return null; + int? timestamp; - var c = new YearCommitCountWeek(); - c.days = input["days"] as List; - c.total = input["total"]; - c.timestamp = input["week"]; - return c; - } + factory YearCommitCountWeek.fromJson(Map input) => + _$YearCommitCountWeekFromJson(input); + Map toJson() => _$YearCommitCountWeekToJson(this); } /// Model class for a weekly change count. +@JsonSerializable() class WeeklyChangesCount { + WeeklyChangesCount({ + this.timestamp, + this.additions, + this.deletions, + }); + /// Timestamp for Beginning of Week - int timestamp; + int? timestamp; /// Number of Additions - int additions; + int? additions; /// Number of Deletions - int deletions; - - static WeeklyChangesCount fromJSON(Map input) { - if (input == null) return null; - var c = new WeeklyChangesCount(); - c.timestamp = input[0]; - c.additions = input[1]; - c.deletions = input[2]; - return c; - } + int? deletions; + + factory WeeklyChangesCount.fromJson(Map input) => + _$WeeklyChangesCountFromJson(input); + Map toJson() => _$WeeklyChangesCountToJson(this); } /// Model Class for a Punchcard Entry +@JsonSerializable() class PunchcardEntry { + PunchcardEntry({ + this.weekday, + this.hour, + this.commits, + }); + /// Weekday (With 0 as Sunday and 6 as Saturday) - int weekday; + int? weekday; /// Hour of Day - int hour; + int? hour; /// Number of Commits - int commits; - - static PunchcardEntry fromJSON(Map input) { - if (input == null) return null; - var c = new PunchcardEntry(); - c.weekday = input[0]; - c.hour = input[1]; - c.commits = input[2]; - return c; - } + int? commits; + + factory PunchcardEntry.fromJson(Map input) => + _$PunchcardEntryFromJson(input); + Map toJson() => _$PunchcardEntryToJson(this); } diff --git a/lib/src/common/model/repos_stats.g.dart b/lib/src/common/model/repos_stats.g.dart new file mode 100644 index 00000000..83bd1650 --- /dev/null +++ b/lib/src/common/model/repos_stats.g.dart @@ -0,0 +1,109 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_stats.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +ContributorStatistics _$ContributorStatisticsFromJson( + Map json) => + ContributorStatistics( + json['author'] == null + ? null + : User.fromJson(json['author'] as Map), + (json['total'] as num?)?.toInt(), + (json['weeks'] as List?) + ?.map((e) => + ContributorWeekStatistics.fromJson(e as Map)) + .toList(), + ); + +Map _$ContributorStatisticsToJson( + ContributorStatistics instance) => + { + 'author': instance.author, + 'total': instance.total, + 'weeks': instance.weeks, + }; + +ContributorWeekStatistics _$ContributorWeekStatisticsFromJson( + Map json) => + ContributorWeekStatistics( + (json['w'] as num?)?.toInt(), + (json['a'] as num?)?.toInt(), + (json['d'] as num?)?.toInt(), + (json['c'] as num?)?.toInt(), + ); + +Map _$ContributorWeekStatisticsToJson( + ContributorWeekStatistics instance) => + { + 'w': instance.start, + 'a': instance.additions, + 'd': instance.deletions, + 'c': instance.commits, + }; + +ContributorParticipation _$ContributorParticipationFromJson( + Map json) => + ContributorParticipation( + all: (json['all'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), + owner: (json['owner'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), + ); + +Map _$ContributorParticipationToJson( + ContributorParticipation instance) => + { + 'all': instance.all, + 'owner': instance.owner, + }; + +YearCommitCountWeek _$YearCommitCountWeekFromJson(Map json) => + YearCommitCountWeek( + days: (json['days'] as List?) + ?.map((e) => (e as num).toInt()) + .toList(), + total: (json['total'] as num?)?.toInt(), + timestamp: (json['timestamp'] as num?)?.toInt(), + ); + +Map _$YearCommitCountWeekToJson( + YearCommitCountWeek instance) => + { + 'days': instance.days, + 'total': instance.total, + 'timestamp': instance.timestamp, + }; + +WeeklyChangesCount _$WeeklyChangesCountFromJson(Map json) => + WeeklyChangesCount( + timestamp: (json['timestamp'] as num?)?.toInt(), + additions: (json['additions'] as num?)?.toInt(), + deletions: (json['deletions'] as num?)?.toInt(), + ); + +Map _$WeeklyChangesCountToJson(WeeklyChangesCount instance) => + { + 'timestamp': instance.timestamp, + 'additions': instance.additions, + 'deletions': instance.deletions, + }; + +PunchcardEntry _$PunchcardEntryFromJson(Map json) => + PunchcardEntry( + weekday: (json['weekday'] as num?)?.toInt(), + hour: (json['hour'] as num?)?.toInt(), + commits: (json['commits'] as num?)?.toInt(), + ); + +Map _$PunchcardEntryToJson(PunchcardEntry instance) => + { + 'weekday': instance.weekday, + 'hour': instance.hour, + 'commits': instance.commits, + }; diff --git a/lib/src/common/model/repos_statuses.dart b/lib/src/common/model/repos_statuses.dart index dc430b33..dd20266f 100644 --- a/lib/src/common/model/repos_statuses.dart +++ b/lib/src/common/model/repos_statuses.dart @@ -1,70 +1,64 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'repos_statuses.g.dart'; /// Model class for the combined status of a repository. +@JsonSerializable() class CombinedRepositoryStatus { - String state; - String sha; - int totalCount; - List statuses; - Repository repository; - - CombinedRepositoryStatus(); - - static CombinedRepositoryStatus fromJSON(Map input) { - if (input == null) return null; + CombinedRepositoryStatus({ + this.state, + this.sha, + this.totalCount, + this.statuses, + this.repository, + }); + String? state; + String? sha; + int? totalCount; + List? statuses; + Repository? repository; - return new CombinedRepositoryStatus() - ..state = input["state"] - ..sha = input["sha"] - ..totalCount = input["total_count"] - ..statuses = (input["statuses"] as List>) - .map((it) => RepositoryStatus.fromJSON(it)) - .toList() - ..repository = - Repository.fromJSON(input["repository"] as Map); - } + factory CombinedRepositoryStatus.fromJson(Map input) => + _$CombinedRepositoryStatusFromJson(input); + Map toJson() => _$CombinedRepositoryStatusToJson(this); } /// Model class for the status of a repository at a particular reference. +@JsonSerializable() class RepositoryStatus { - DateTime createdAt; - DateTime updatedAt; - String state; - String targetUrl; - String description; - String context; + RepositoryStatus({ + this.createdAt, + this.updatedAt, + this.state, + this.targetUrl, + this.description, + this.context, + }); + DateTime? createdAt; + DateTime? updatedAt; + String? state; + String? targetUrl; + String? description; + String? context; - static RepositoryStatus fromJSON(Map input) { - if (input == null) return null; - - return new RepositoryStatus() - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..state = input['state'] - ..targetUrl = input['target_url'] - ..description = input['description'] - ..context = input['context']; - } + factory RepositoryStatus.fromJson(Map input) => + _$RepositoryStatusFromJson(input); + Map toJson() => _$RepositoryStatusToJson(this); } /// Model class for a new repository status to be created. +@JsonSerializable() class CreateStatus { - final String state; - - @ApiName("target_url") - String targetUrl; - - String description; - String context; + CreateStatus(this.state, {this.targetUrl, this.description, this.context}); - CreateStatus(this.state); + final String? state; + String? description; + String? context; + @JsonKey(name: 'target_url') + String? targetUrl; - String toJSON() { - var map = {}; - putValue("state", state, map); - putValue("target_url", targetUrl, map); - putValue("description", description, map); - putValue("context", context, map); - return JSON.encode(map); - } + factory CreateStatus.fromJson(Map input) => + _$CreateStatusFromJson(input); + Map toJson() => _$CreateStatusToJson(this); } diff --git a/lib/src/common/model/repos_statuses.g.dart b/lib/src/common/model/repos_statuses.g.dart new file mode 100644 index 00000000..86b7b8e3 --- /dev/null +++ b/lib/src/common/model/repos_statuses.g.dart @@ -0,0 +1,70 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'repos_statuses.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CombinedRepositoryStatus _$CombinedRepositoryStatusFromJson( + Map json) => + CombinedRepositoryStatus( + state: json['state'] as String?, + sha: json['sha'] as String?, + totalCount: (json['total_count'] as num?)?.toInt(), + statuses: (json['statuses'] as List?) + ?.map((e) => RepositoryStatus.fromJson(e as Map)) + .toList(), + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + ); + +Map _$CombinedRepositoryStatusToJson( + CombinedRepositoryStatus instance) => + { + 'state': instance.state, + 'sha': instance.sha, + 'total_count': instance.totalCount, + 'statuses': instance.statuses, + 'repository': instance.repository, + }; + +RepositoryStatus _$RepositoryStatusFromJson(Map json) => + RepositoryStatus( + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + state: json['state'] as String?, + targetUrl: json['target_url'] as String?, + description: json['description'] as String?, + context: json['context'] as String?, + ); + +Map _$RepositoryStatusToJson(RepositoryStatus instance) => + { + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'state': instance.state, + 'target_url': instance.targetUrl, + 'description': instance.description, + 'context': instance.context, + }; + +CreateStatus _$CreateStatusFromJson(Map json) => CreateStatus( + json['state'] as String?, + targetUrl: json['target_url'] as String?, + description: json['description'] as String?, + context: json['context'] as String?, + ); + +Map _$CreateStatusToJson(CreateStatus instance) => + { + 'state': instance.state, + 'description': instance.description, + 'context': instance.context, + 'target_url': instance.targetUrl, + }; diff --git a/lib/src/common/model/search.dart b/lib/src/common/model/search.dart index 899e1e14..c61f39e5 100644 --- a/lib/src/common/model/search.dart +++ b/lib/src/common/model/search.dart @@ -1,42 +1,70 @@ -part of github.common; +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; -class SearchResults { - @ApiName("total_count") - int totalCount; +part 'search.g.dart'; - @ApiName("incomplete_results") - bool incompleteResults; +abstract class SearchResults { + int? totalCount; + bool? incompleteResults; + List? items; +} - List items; +@JsonSerializable() +class CodeSearchResults implements SearchResults { + @JsonKey(name: 'total_count') + @override + int? totalCount; - static SearchResults fromJSON( - Map input, JSONConverter resultConverter) { - var results = new SearchResults(); - results - ..totalCount = input['total_count'] - ..incompleteResults = input['incomplete_results']; + @JsonKey(name: 'incomplete_results') + @override + bool? incompleteResults; - var itemList = input['items']; + @JsonKey(fromJson: CodeSearchItem.fromJsonList) + @override + List? items; - results.items = []; + static CodeSearchResults fromJson(Map input) => + _$CodeSearchResultsFromJson(input); + Map toJson() => _$CodeSearchResultsToJson(this); +} - for (var item in itemList) { - results.items.add(resultConverter(item)); - } +@JsonSerializable() +class CodeSearchItem { + String? name; + String? path; + String? sha; - return results; - } -} + @JsonKey(fromJson: Uri.parse) + Uri? url; -abstract class SearchResult { - int score; -} + @JsonKey(name: 'git_url', fromJson: Uri.parse) + Uri? gitUrl; + + @JsonKey(name: 'html_url', fromJson: Uri.parse) + Uri? htmlUrl; -class RepositorySearchResult extends Repository with SearchResult { - static RepositorySearchResult fromJSON(Map input) { - var result = new RepositorySearchResult(); - Repository.fromJSON(input, result); - result.score = input['score']; + Repository? repository; + + static CodeSearchItem fromJson(Map input) { + return _$CodeSearchItemFromJson(input); + } + + static List fromJsonList(List input) { + final result = []; + for (final item in input) { + if (item is Map) { + result.add(CodeSearchItem.fromJson(item)); + } + } return result; } + + Map toJson() => _$CodeSearchItemToJson(this); } + +// TODO: Issue Search +// @JsonSerializable() +// class IssueSearchResults extends SearchResults {} + +// @JsonSerializable() +// class IssueSearchItem {} diff --git a/lib/src/common/model/search.g.dart b/lib/src/common/model/search.g.dart new file mode 100644 index 00000000..9d606865 --- /dev/null +++ b/lib/src/common/model/search.g.dart @@ -0,0 +1,43 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'search.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CodeSearchResults _$CodeSearchResultsFromJson(Map json) => + CodeSearchResults() + ..totalCount = (json['total_count'] as num?)?.toInt() + ..incompleteResults = json['incomplete_results'] as bool? + ..items = CodeSearchItem.fromJsonList(json['items'] as List); + +Map _$CodeSearchResultsToJson(CodeSearchResults instance) => + { + 'total_count': instance.totalCount, + 'incomplete_results': instance.incompleteResults, + 'items': instance.items, + }; + +CodeSearchItem _$CodeSearchItemFromJson(Map json) => + CodeSearchItem() + ..name = json['name'] as String? + ..path = json['path'] as String? + ..sha = json['sha'] as String? + ..url = Uri.parse(json['url'] as String) + ..gitUrl = Uri.parse(json['git_url'] as String) + ..htmlUrl = Uri.parse(json['html_url'] as String) + ..repository = json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map); + +Map _$CodeSearchItemToJson(CodeSearchItem instance) => + { + 'name': instance.name, + 'path': instance.path, + 'sha': instance.sha, + 'url': instance.url?.toString(), + 'git_url': instance.gitUrl?.toString(), + 'html_url': instance.htmlUrl?.toString(), + 'repository': instance.repository, + }; diff --git a/lib/src/common/model/timeline.dart b/lib/src/common/model/timeline.dart new file mode 100644 index 00000000..ae2c48ef --- /dev/null +++ b/lib/src/common/model/timeline.dart @@ -0,0 +1,582 @@ +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'timeline.g.dart'; + +// Parts of this file were originally automatically generated from the response +// schema provided in the API documentation for GitHub's "List timeline events +// for an issue" API [1], using the `tool/process_github_schema.dart` script. +// +// Unfortunately, that schema contradicts the prose documentation [2] in a great +// variety of ways (for example none of the "common properties" are actually +// common to all the event types), so this code is an attempt to find the most +// pragmatic middleground between what is documented and what actually works. +// +// [1] https://docs.github.com/en/rest/issues/timeline?apiVersion=2022-11-28 +// [2] https://docs.github.com/en/webhooks-and-events/events/issue-event-types + +/// Model class for an issue or PR timeline event. +/// +/// This is a base class for the various event types. Events that only use the +/// default fields use this class; events that have additional fields use one +/// of the subclasses. +/// +/// The [TimelineEvent.fromJson] factory selects the right subclass based on +/// the [event] field. +/// +/// If the [event] type is not known, [TimelineEvent] is used. +/// +/// See also: https://docs.github.com/en/webhooks-and-events/events/issue-event-types +@JsonSerializable() +class TimelineEvent { + TimelineEvent({ + this.id = 0, + this.nodeId, + this.url, + this.actor, + this.event = '', + this.commitId, + this.commitUrl, + this.createdAt, + this.performedViaGithubApp, + }); + + int id; + String? nodeId; + String? url; + User? actor; + String event; + String? commitId; + String? commitUrl; + DateTime? createdAt; + GitHubApp? performedViaGithubApp; + + Map toJson() => _$TimelineEventToJson(this); + + factory TimelineEvent.fromJson(Map input) { + switch (input['event']) { + case 'added_to_project': + return ProjectEvent.fromJson(input); + case 'assigned': + return AssigneeEvent.fromJson(input); + case 'commented': + return CommentEvent.fromJson(input); + case 'committed': + return TimelineCommitEvent.fromJson(input); + case 'cross-referenced': + return CrossReferenceEvent.fromJson(input); + case 'demilestoned': + return MilestoneEvent.fromJson(input); + case 'labeled': + return LabelEvent.fromJson(input); + case 'locked': + return LockEvent.fromJson(input); + case 'milestoned': + return MilestoneEvent.fromJson(input); + case 'moved_columns_in_project': + return ProjectEvent.fromJson(input); + case 'removed_from_project': + return ProjectEvent.fromJson(input); + case 'renamed': + return RenameEvent.fromJson(input); + case 'review_dismissed': + return ReviewDismissedEvent.fromJson(input); + case 'review_requested': + return ReviewRequestEvent.fromJson(input); + case 'review_request_removed': + return ReviewRequestEvent.fromJson(input); + case 'reviewed': + return ReviewEvent.fromJson(input); + case 'unassigned': + return AssigneeEvent.fromJson(input); + case 'unlabeled': + return LabelEvent.fromJson(input); + default: + return _$TimelineEventFromJson(input); + } + } +} + +/// Labeled and Unlabeled Issue Events +@JsonSerializable() +class LabelEvent extends TimelineEvent { + LabelEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', // typically 'labeled' or 'unlabeled' + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.label, + }); + + IssueLabel? label; + + @override + Map toJson() => _$LabelEventToJson(this); + + factory LabelEvent.fromJson(Map input) => + _$LabelEventFromJson(input); +} + +/// Milestoned and Demilestoned Issue Event +@JsonSerializable() +class MilestoneEvent extends TimelineEvent { + MilestoneEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', // typically 'milestoned' or 'demilestoned' + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.milestone, + }); + + Milestone? milestone; + + @override + Map toJson() => _$MilestoneEventToJson(this); + + factory MilestoneEvent.fromJson(Map input) => + _$MilestoneEventFromJson(input); +} + +/// Renamed Issue Event +@JsonSerializable() +class RenameEvent extends TimelineEvent { + RenameEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'renamed', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.rename, + }); + + Rename? rename; + + @override + Map toJson() => _$RenameEventToJson(this); + + factory RenameEvent.fromJson(Map input) => + _$RenameEventFromJson(input); +} + +/// Review Requested and Review Request Removed Issue Events +@JsonSerializable() +class ReviewRequestEvent extends TimelineEvent { + ReviewRequestEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = + '', // typically 'review_requested' or 'review_request_removed' + super.commitId, + super.commitUrl, + super.createdAt, + this.requestedReviewer, + this.requestedTeam, + this.reviewRequester, + }); + + User? requestedReviewer; + + /// Team + /// + /// Groups of organization members that gives permissions on specified repositories. + Team? requestedTeam; + + User? reviewRequester; + + @override + Map toJson() => _$ReviewRequestEventToJson(this); + + factory ReviewRequestEvent.fromJson(Map input) => + _$ReviewRequestEventFromJson(input); +} + +/// Review Dismissed Issue Event +@JsonSerializable() +class ReviewDismissedEvent extends TimelineEvent { + ReviewDismissedEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'review_dismissed', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.dismissedReview, + }); + + DismissedReview? dismissedReview; + + @override + Map toJson() => _$ReviewDismissedEventToJson(this); + + factory ReviewDismissedEvent.fromJson(Map input) => + _$ReviewDismissedEventFromJson(input); +} + +/// Locked Issue Event +@JsonSerializable() +class LockEvent extends TimelineEvent { + LockEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'locked', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.lockReason, + }); + + /// Example: `"off-topic"` + String? lockReason; + + @override + Map toJson() => _$LockEventToJson(this); + + factory LockEvent.fromJson(Map input) => + _$LockEventFromJson(input); +} + +/// Added to Project, +/// Moved Columns in Project, +/// Removed from Project, and +/// Converted Note to Issue +/// Issue Events. +@JsonSerializable() +class ProjectEvent extends TimelineEvent { + ProjectEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event, // typically one of 'added_to_project', 'moved_columns_in_project', 'removed_from_project', 'converted_note_to_issue' + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.projectCard, + }); + + ProjectCard? projectCard; + + @override + Map toJson() => _$ProjectEventToJson(this); + + factory ProjectEvent.fromJson(Map input) => + _$ProjectEventFromJson(input); +} + +/// Timeline Comment Event +@JsonSerializable() +class CommentEvent extends TimelineEvent { + CommentEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'commented', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.authorAssociation, + this.body, + this.bodyHtml, + this.bodyText, + this.htmlUrl, + this.issueUrl, + this.reactions, + this.updatedAt, + this.user, + }); + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + /// Contents of the issue comment + /// + /// Example: `What version of Safari were you using when you observed this bug?` + String? body; + + String? bodyHtml; + String? bodyText; + + String? htmlUrl; + + String? issueUrl; + + ReactionRollup? reactions; + + DateTime? updatedAt; + + User? user; + + @override + Map toJson() => _$CommentEventToJson(this); + + factory CommentEvent.fromJson(Map input) => + _$CommentEventFromJson(input); +} + +/// Timeline Cross Referenced Event +@JsonSerializable() +class CrossReferenceEvent extends TimelineEvent { + CrossReferenceEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'cross-referenced', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.source, + this.updatedAt, + }); + + Source? source; + + DateTime? updatedAt; + + @override + Map toJson() => _$CrossReferenceEventToJson(this); + + factory CrossReferenceEvent.fromJson(Map input) => + _$CrossReferenceEventFromJson(input); +} + +/// Timeline Committed Event +@JsonSerializable() +class TimelineCommitEvent extends TimelineEvent { + TimelineCommitEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'committed', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.author, + this.committer, + this.htmlUrl, + this.message, + this.parents, + this.sha, + this.tree, + this.verification, + }); + + User? author; + + /// Identifying information for the git-user + User? committer; + + /// Format: uri + String? htmlUrl; + + /// Message describing the purpose of the commit + String? message; + + List? parents; + + /// SHA for the commit + /// + /// Example: `7638417db6d59f3c431d3e1f261cc637155684cd` + String? sha; + + Tree? tree; + + Verification? verification; + + @override + Map toJson() => _$TimelineCommitEventToJson(this); + + factory TimelineCommitEvent.fromJson(Map input) => + _$TimelineCommitEventFromJson(input); +} + +/// Timeline Reviewed Event +@JsonSerializable() +class ReviewEvent extends TimelineEvent { + ReviewEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = 'reviewed', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.authorAssociation, + this.body, + this.bodyHtml, + this.bodyText, + this.htmlUrl, + this.links, + this.pullRequestUrl, + this.state, + this.submittedAt, + this.user, + }); + + /// How the author is associated with the repository. + /// + /// Example: `OWNER` + String? authorAssociation; + + /// The text of the review. + /// + /// Example: `This looks great.` + String? body; + + String? bodyHtml; + String? bodyText; + + /// Example: `https://github.com/octocat/Hello-World/pull/12#pullrequestreview-80` + String? htmlUrl; + + @JsonKey(name: '_links') + ReviewLinks? links; + + /// Example: `https://api.github.com/repos/octocat/Hello-World/pulls/12` + String? pullRequestUrl; + + /// Example: `CHANGES_REQUESTED` + String? state; + + DateTime? submittedAt; + + User? user; + + @override + Map toJson() => _$ReviewEventToJson(this); + + factory ReviewEvent.fromJson(Map input) => + _$ReviewEventFromJson(input); +} + +/// Timeline Line Commented Event +@JsonSerializable() +class TimelineLineCommentedEvent extends TimelineEvent { + TimelineLineCommentedEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.comments, + }); + + List? comments; + + @override + Map toJson() => _$TimelineLineCommentedEventToJson(this); + + factory TimelineLineCommentedEvent.fromJson(Map input) => + _$TimelineLineCommentedEventFromJson(input); +} + +/// Timeline Commit Commented Event +@JsonSerializable() +class TimelineCommitCommentedEvent extends TimelineEvent { + TimelineCommitCommentedEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.comments, + }); + + List? comments; + + @override + Map toJson() => _$TimelineCommitCommentedEventToJson(this); + + factory TimelineCommitCommentedEvent.fromJson(Map input) => + _$TimelineCommitCommentedEventFromJson(input); +} + +/// Timeline Assigned and Timeline Unassigned Issue Events +@JsonSerializable() +class AssigneeEvent extends TimelineEvent { + AssigneeEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event, // typically 'assigned' or 'unassigned' + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.assignee, + }); + + User? assignee; + + @override + Map toJson() => _$AssigneeEventToJson(this); + + factory AssigneeEvent.fromJson(Map input) => + _$AssigneeEventFromJson(input); +} + +/// State Change Issue Event +@JsonSerializable() +class StateChangeIssueEvent extends TimelineEvent { + StateChangeIssueEvent({ + super.id = 0, + super.nodeId, + super.url, + super.actor, + super.event = '', + super.commitId, + super.commitUrl, + super.createdAt, + super.performedViaGithubApp, + this.stateReason, + }); + + String? stateReason; + + @override + Map toJson() => _$StateChangeIssueEventToJson(this); + + factory StateChangeIssueEvent.fromJson(Map input) => + _$StateChangeIssueEventFromJson(input); +} diff --git a/lib/src/common/model/timeline.g.dart b/lib/src/common/model/timeline.g.dart new file mode 100644 index 00000000..be7d916d --- /dev/null +++ b/lib/src/common/model/timeline.g.dart @@ -0,0 +1,671 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'timeline.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +TimelineEvent _$TimelineEventFromJson(Map json) => + TimelineEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + ); + +Map _$TimelineEventToJson(TimelineEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + }; + +LabelEvent _$LabelEventFromJson(Map json) => LabelEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + label: json['label'] == null + ? null + : IssueLabel.fromJson(json['label'] as Map), + ); + +Map _$LabelEventToJson(LabelEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'label': instance.label, + }; + +MilestoneEvent _$MilestoneEventFromJson(Map json) => + MilestoneEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + milestone: json['milestone'] == null + ? null + : Milestone.fromJson(json['milestone'] as Map), + ); + +Map _$MilestoneEventToJson(MilestoneEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'milestone': instance.milestone, + }; + +RenameEvent _$RenameEventFromJson(Map json) => RenameEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'renamed', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + rename: json['rename'] == null + ? null + : Rename.fromJson(json['rename'] as Map), + ); + +Map _$RenameEventToJson(RenameEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'rename': instance.rename, + }; + +ReviewRequestEvent _$ReviewRequestEventFromJson(Map json) => + ReviewRequestEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + requestedReviewer: json['requested_reviewer'] == null + ? null + : User.fromJson(json['requested_reviewer'] as Map), + requestedTeam: json['requested_team'] == null + ? null + : Team.fromJson(json['requested_team'] as Map), + reviewRequester: json['review_requester'] == null + ? null + : User.fromJson(json['review_requester'] as Map), + )..performedViaGithubApp = json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map); + +Map _$ReviewRequestEventToJson(ReviewRequestEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'requested_reviewer': instance.requestedReviewer, + 'requested_team': instance.requestedTeam, + 'review_requester': instance.reviewRequester, + }; + +ReviewDismissedEvent _$ReviewDismissedEventFromJson( + Map json) => + ReviewDismissedEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'review_dismissed', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + dismissedReview: json['dismissed_review'] == null + ? null + : DismissedReview.fromJson( + json['dismissed_review'] as Map), + ); + +Map _$ReviewDismissedEventToJson( + ReviewDismissedEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'dismissed_review': instance.dismissedReview, + }; + +LockEvent _$LockEventFromJson(Map json) => LockEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'locked', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + lockReason: json['lock_reason'] as String?, + ); + +Map _$LockEventToJson(LockEvent instance) => { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'lock_reason': instance.lockReason, + }; + +ProjectEvent _$ProjectEventFromJson(Map json) => ProjectEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + projectCard: json['project_card'] == null + ? null + : ProjectCard.fromJson(json['project_card'] as Map), + ); + +Map _$ProjectEventToJson(ProjectEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'project_card': instance.projectCard, + }; + +CommentEvent _$CommentEventFromJson(Map json) => CommentEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'commented', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + authorAssociation: json['author_association'] as String?, + body: json['body'] as String?, + bodyHtml: json['body_html'] as String?, + bodyText: json['body_text'] as String?, + htmlUrl: json['html_url'] as String?, + issueUrl: json['issue_url'] as String?, + reactions: json['reactions'] == null + ? null + : ReactionRollup.fromJson(json['reactions'] as Map), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$CommentEventToJson(CommentEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'author_association': instance.authorAssociation, + 'body': instance.body, + 'body_html': instance.bodyHtml, + 'body_text': instance.bodyText, + 'html_url': instance.htmlUrl, + 'issue_url': instance.issueUrl, + 'reactions': instance.reactions, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'user': instance.user, + }; + +CrossReferenceEvent _$CrossReferenceEventFromJson(Map json) => + CrossReferenceEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'cross-referenced', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + source: json['source'] == null + ? null + : Source.fromJson(json['source'] as Map), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + ); + +Map _$CrossReferenceEventToJson( + CrossReferenceEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'source': instance.source, + 'updated_at': instance.updatedAt?.toIso8601String(), + }; + +TimelineCommitEvent _$TimelineCommitEventFromJson(Map json) => + TimelineCommitEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'committed', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + author: json['author'] == null + ? null + : User.fromJson(json['author'] as Map), + committer: json['committer'] == null + ? null + : User.fromJson(json['committer'] as Map), + htmlUrl: json['html_url'] as String?, + message: json['message'] as String?, + parents: (json['parents'] as List?) + ?.map((e) => Tree.fromJson(e as Map)) + .toList(), + sha: json['sha'] as String?, + tree: json['tree'] == null + ? null + : Tree.fromJson(json['tree'] as Map), + verification: json['verification'] == null + ? null + : Verification.fromJson(json['verification'] as Map), + ); + +Map _$TimelineCommitEventToJson( + TimelineCommitEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'author': instance.author, + 'committer': instance.committer, + 'html_url': instance.htmlUrl, + 'message': instance.message, + 'parents': instance.parents, + 'sha': instance.sha, + 'tree': instance.tree, + 'verification': instance.verification, + }; + +ReviewEvent _$ReviewEventFromJson(Map json) => ReviewEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? 'reviewed', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + authorAssociation: json['author_association'] as String?, + body: json['body'] as String?, + bodyHtml: json['body_html'] as String?, + bodyText: json['body_text'] as String?, + htmlUrl: json['html_url'] as String?, + links: json['_links'] == null + ? null + : ReviewLinks.fromJson(json['_links'] as Map), + pullRequestUrl: json['pull_request_url'] as String?, + state: json['state'] as String?, + submittedAt: json['submitted_at'] == null + ? null + : DateTime.parse(json['submitted_at'] as String), + user: json['user'] == null + ? null + : User.fromJson(json['user'] as Map), + ); + +Map _$ReviewEventToJson(ReviewEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'author_association': instance.authorAssociation, + 'body': instance.body, + 'body_html': instance.bodyHtml, + 'body_text': instance.bodyText, + 'html_url': instance.htmlUrl, + '_links': instance.links, + 'pull_request_url': instance.pullRequestUrl, + 'state': instance.state, + 'submitted_at': instance.submittedAt?.toIso8601String(), + 'user': instance.user, + }; + +TimelineLineCommentedEvent _$TimelineLineCommentedEventFromJson( + Map json) => + TimelineLineCommentedEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + comments: (json['comments'] as List?) + ?.map((e) => + PullRequestReviewComment.fromJson(e as Map)) + .toList(), + ); + +Map _$TimelineLineCommentedEventToJson( + TimelineLineCommentedEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'comments': instance.comments, + }; + +TimelineCommitCommentedEvent _$TimelineCommitCommentedEventFromJson( + Map json) => + TimelineCommitCommentedEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + comments: (json['comments'] as List?) + ?.map((e) => CommitComment.fromJson(e as Map)) + .toList(), + ); + +Map _$TimelineCommitCommentedEventToJson( + TimelineCommitCommentedEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'comments': instance.comments, + }; + +AssigneeEvent _$AssigneeEventFromJson(Map json) => + AssigneeEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + assignee: json['assignee'] == null + ? null + : User.fromJson(json['assignee'] as Map), + ); + +Map _$AssigneeEventToJson(AssigneeEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'assignee': instance.assignee, + }; + +StateChangeIssueEvent _$StateChangeIssueEventFromJson( + Map json) => + StateChangeIssueEvent( + id: (json['id'] as num?)?.toInt() ?? 0, + nodeId: json['node_id'] as String?, + url: json['url'] as String?, + actor: json['actor'] == null + ? null + : User.fromJson(json['actor'] as Map), + event: json['event'] as String? ?? '', + commitId: json['commit_id'] as String?, + commitUrl: json['commit_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + performedViaGithubApp: json['performed_via_github_app'] == null + ? null + : GitHubApp.fromJson( + json['performed_via_github_app'] as Map), + stateReason: json['state_reason'] as String?, + ); + +Map _$StateChangeIssueEventToJson( + StateChangeIssueEvent instance) => + { + 'id': instance.id, + 'node_id': instance.nodeId, + 'url': instance.url, + 'actor': instance.actor, + 'event': instance.event, + 'commit_id': instance.commitId, + 'commit_url': instance.commitUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'performed_via_github_app': instance.performedViaGithubApp, + 'state_reason': instance.stateReason, + }; diff --git a/lib/src/common/model/timeline_support.dart b/lib/src/common/model/timeline_support.dart new file mode 100644 index 00000000..f2b03c62 --- /dev/null +++ b/lib/src/common/model/timeline_support.dart @@ -0,0 +1,562 @@ +import 'package:github/src/common.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'timeline_support.g.dart'; + +/// GitHub app +/// +/// GitHub apps are a new way to extend GitHub. They can be installed directly +/// on organizations and user accounts and granted access to specific repositories. +/// They come with granular permissions and built-in webhooks. GitHub apps are +/// first class actors within GitHub. +@JsonSerializable() +class GitHubApp { + GitHubApp({ + this.clientId, + this.clientSecret, + this.createdAt, + this.description, + this.events, + this.externalUrl, + this.htmlUrl, + this.id, + this.installationsCount, + this.name, + this.nodeId, + this.owner, + this.pem, + this.permissions, + this.slug, + this.updatedAt, + this.webhookSecret, + }); + + /// Example: `"Iv1.25b5d1e65ffc4022"` + final String? clientId; + + /// Example: `"1d4b2097ac622ba702d19de498f005747a8b21d3"` + final String? clientSecret; + + final DateTime? createdAt; + + final String? description; + + /// The list of events for the GitHub app + /// + /// Example: `label` + /// + /// Example: `deployment` + final List? events; + + /// Example: `https://example.com` + final String? externalUrl; + + /// Example: `https://github.com/apps/super-ci` + final String? htmlUrl; + + /// Unique identifier of the GitHub app + final int? id; + + /// The number of installations associated with the GitHub app + final int? installationsCount; + + /// The name of the GitHub app + /// + /// Example: `Probot Owners` + final String? name; + + /// Example: `MDExOkludGVncmF0aW9uMQ==` + final String? nodeId; + + final User? owner; + + /// Example: + /// + /// ``` + /// -----BEGIN RSA PRIVATE KEY----- + /// MIIEogIBAAKCAQEArYxrNYD/iT5CZVpRJu4rBKmmze3PVmT/gCo2ATUvDvZTPTey + /// xcGJ3vvrJXazKk06pN05TN29o98jrYz4cengG3YGsXPNEpKsIrEl8NhbnxapEnM9 + /// JCMRe0P5JcPsfZlX6hmiT7136GRWiGOUba2X9+HKh8QJVLG5rM007TBER9/z9mWm + /// rJuNh+m5l320oBQY/Qq3A7wzdEfZw8qm/mIN0FCeoXH1L6B8xXWaAYBwhTEh6SSn + /// ZHlO1Xu1JWDmAvBCi0RO5aRSKM8q9QEkvvHP4yweAtK3N8+aAbZ7ovaDhyGz8r6r + /// zhU1b8Uo0Z2ysf503WqzQgIajr7Fry7/kUwpgQIDAQABAoIBADwJp80Ko1xHPZDy + /// fcCKBDfIuPvkmSW6KumbsLMaQv1aGdHDwwTGv3t0ixSay8CGlxMRtRDyZPib6SvQ + /// 6OH/lpfpbMdW2ErkksgtoIKBVrDilfrcAvrNZu7NxRNbhCSvN8q0s4ICecjbbVQh + /// nueSdlA6vGXbW58BHMq68uRbHkP+k+mM9U0mDJ1HMch67wlg5GbayVRt63H7R2+r + /// Vxcna7B80J/lCEjIYZznawgiTvp3MSanTglqAYi+m1EcSsP14bJIB9vgaxS79kTu + /// oiSo93leJbBvuGo8QEiUqTwMw4tDksmkLsoqNKQ1q9P7LZ9DGcujtPy4EZsamSJT + /// y8OJt0ECgYEA2lxOxJsQk2kI325JgKFjo92mQeUObIvPfSNWUIZQDTjniOI6Gv63 + /// GLWVFrZcvQBWjMEQraJA9xjPbblV8PtfO87MiJGLWCHFxmPz2dzoedN+2Coxom8m + /// V95CLz8QUShuao6u/RYcvUaZEoYs5bHcTmy5sBK80JyEmafJPtCQVxMCgYEAy3ar + /// Zr3yv4xRPEPMat4rseswmuMooSaK3SKub19WFI5IAtB/e7qR1Rj9JhOGcZz+OQrl + /// T78O2OFYlgOIkJPvRMrPpK5V9lslc7tz1FSh3BZMRGq5jSyD7ETSOQ0c8T2O/s7v + /// beEPbVbDe4mwvM24XByH0GnWveVxaDl51ABD65sCgYB3ZAspUkOA5egVCh8kNpnd + /// Sd6SnuQBE3ySRlT2WEnCwP9Ph6oPgn+oAfiPX4xbRqkL8q/k0BdHQ4h+zNwhk7+h + /// WtPYRAP1Xxnc/F+jGjb+DVaIaKGU18MWPg7f+FI6nampl3Q0KvfxwX0GdNhtio8T + /// Tj1E+SnFwh56SRQuxSh2gwKBgHKjlIO5NtNSflsUYFM+hyQiPiqnHzddfhSG+/3o + /// m5nNaSmczJesUYreH5San7/YEy2UxAugvP7aSY2MxB+iGsiJ9WD2kZzTUlDZJ7RV + /// UzWsoqBR+eZfVJ2FUWWvy8TpSG6trh4dFxImNtKejCR1TREpSiTV3Zb1dmahK9GV + /// rK9NAoGAbBxRLoC01xfxCTgt5BDiBcFVh4fp5yYKwavJPLzHSpuDOrrI9jDn1oKN + /// onq5sDU1i391zfQvdrbX4Ova48BN+B7p63FocP/MK5tyyBoT8zQEk2+vWDOw7H/Z + /// u5dTCPxTIsoIwUw1I+7yIxqJzLPFgR2gVBwY1ra/8iAqCj+zeBw= + /// -----END RSA PRIVATE KEY----- + /// ``` + final String? pem; + + /// The set of permissions for the GitHub app + final Permissions? permissions; + + /// The slug name of the GitHub app + /// + /// Example: `probot-owners` + final String? slug; + + final DateTime? updatedAt; + + /// Example: `"6fba8f2fc8a7e8f2cca5577eddd82ca7586b3b6b"` + final String? webhookSecret; + + Map toJson() => _$GitHubAppToJson(this); + + factory GitHubApp.fromJson(Map input) => + _$GitHubAppFromJson(input); +} + +@JsonSerializable() +class Rename { + Rename({ + this.from, + this.to, + }); + + final String? from; + final String? to; + + Map toJson() => _$RenameToJson(this); + + factory Rename.fromJson(Map input) => + _$RenameFromJson(input); +} + +@JsonSerializable() +class DismissedReview { + DismissedReview({ + this.dismissalCommitId, + this.dismissalMessage, + this.reviewId, + this.state, + }); + + final String? dismissalCommitId; + final String? dismissalMessage; + final int? reviewId; + final String? state; + + Map toJson() => _$DismissedReviewToJson(this); + + factory DismissedReview.fromJson(Map input) => + _$DismissedReviewFromJson(input); +} + +@JsonSerializable() +class ProjectCard { + ProjectCard({ + this.columnName, + this.id, + this.previousColumnName, + this.projectId, + this.projectUrl, + this.url, + }); + + final String? columnName; + final int? id; + final String? previousColumnName; + final int? projectId; + final String? projectUrl; + final String? url; + + Map toJson() => _$ProjectCardToJson(this); + + factory ProjectCard.fromJson(Map input) => + _$ProjectCardFromJson(input); +} + +@JsonSerializable() +class Source { + Source({ + this.issue, + this.type, + }); + + final Issue? issue; + final String? type; + + Map toJson() => _$SourceToJson(this); + + factory Source.fromJson(Map input) => + _$SourceFromJson(input); +} + +/// License +@JsonSerializable() +class License { + License({ + this.htmlUrl, + this.key, + this.name, + this.nodeId, + this.spdxId, + this.url, + }); + + final String? htmlUrl; + + /// Example: `mit` + final String? key; + + /// Example: `MIT License` + final String? name; + + /// Example: `MDc6TGljZW5zZW1pdA==` + final String? nodeId; + + /// Example: `MIT` + final String? spdxId; + + /// Example: `https://api.github.com/licenses/mit` + final String? url; + + Map toJson() => _$LicenseToJson(this); + + factory License.fromJson(Map input) => + _$LicenseFromJson(input); +} + +@JsonSerializable() +class TemplateRepository { + TemplateRepository({ + this.allowAutoMerge, + this.allowMergeCommit, + this.allowRebaseMerge, + this.allowSquashMerge, + this.allowUpdateBranch, + this.archiveUrl, + this.archived, + this.assigneesUrl, + this.blobsUrl, + this.branchesUrl, + this.cloneUrl, + this.collaboratorsUrl, + this.commentsUrl, + this.commitsUrl, + this.compareUrl, + this.contentsUrl, + this.contributorsUrl, + this.createdAt, + this.defaultBranch, + this.deleteBranchOnMerge, + this.deploymentsUrl, + this.description, + this.disabled, + this.downloadsUrl, + this.eventsUrl, + this.fork, + this.forksCount, + this.forksUrl, + this.fullName, + this.gitCommitsUrl, + this.gitRefsUrl, + this.gitTagsUrl, + this.gitUrl, + this.hasDownloads, + this.hasIssues, + this.hasPages, + this.hasProjects, + this.hasWiki, + this.homepage, + this.hooksUrl, + this.htmlUrl, + this.id, + this.isTemplate, + this.issueCommentUrl, + this.issueEventsUrl, + this.issuesUrl, + this.keysUrl, + this.labelsUrl, + this.language, + this.languagesUrl, + this.mergeCommitMessage, + this.mergeCommitTitle, + this.mergesUrl, + this.milestonesUrl, + this.mirrorUrl, + this.name, + this.networkCount, + this.nodeId, + this.notificationsUrl, + this.openIssuesCount, + this.owner, + this.permissions, + this.private, + this.pullsUrl, + this.pushedAt, + this.releasesUrl, + this.size, + this.squashMergeCommitMessage, + this.squashMergeCommitTitle, + this.sshUrl, + this.stargazersCount, + this.stargazersUrl, + this.statusesUrl, + this.subscribersCount, + this.subscribersUrl, + this.subscriptionUrl, + this.svnUrl, + this.tagsUrl, + this.teamsUrl, + this.tempCloneToken, + this.topics, + this.treesUrl, + this.updatedAt, + this.url, + this.visibility, + this.watchersCount, + }); + + final bool? allowAutoMerge; + final bool? allowMergeCommit; + final bool? allowRebaseMerge; + final bool? allowSquashMerge; + final bool? allowUpdateBranch; + final String? archiveUrl; + final bool? archived; + final String? assigneesUrl; + final String? blobsUrl; + final String? branchesUrl; + final String? cloneUrl; + final String? collaboratorsUrl; + final String? commentsUrl; + final String? commitsUrl; + final String? compareUrl; + final String? contentsUrl; + final String? contributorsUrl; + final DateTime? createdAt; + final String? defaultBranch; + final bool? deleteBranchOnMerge; + final String? deploymentsUrl; + final String? description; + final bool? disabled; + final String? downloadsUrl; + final String? eventsUrl; + final bool? fork; + final int? forksCount; + final String? forksUrl; + final String? fullName; + final String? gitCommitsUrl; + final String? gitRefsUrl; + final String? gitTagsUrl; + final String? gitUrl; + final bool? hasDownloads; + final bool? hasIssues; + final bool? hasPages; + final bool? hasProjects; + final bool? hasWiki; + final String? homepage; + final String? hooksUrl; + final String? htmlUrl; + final int? id; + final bool? isTemplate; + final String? issueCommentUrl; + final String? issueEventsUrl; + final String? issuesUrl; + final String? keysUrl; + final String? labelsUrl; + final String? language; + final String? languagesUrl; + + /// The default value for a merge commit message. + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `PR_BODY` - default to the pull request's body. + /// - `BLANK` - default to a blank commit message. + final String? mergeCommitMessage; + + /// The default value for a merge commit title. + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `MERGE_MESSAGE` - default to the classic title for a merge message (e.g., + /// Merge pull request #123 from branch-name). + final String? mergeCommitTitle; + + final String? mergesUrl; + final String? milestonesUrl; + final String? mirrorUrl; + final String? name; + final int? networkCount; + final String? nodeId; + final String? notificationsUrl; + final int? openIssuesCount; + final Owner? owner; + final Permissions? permissions; + final bool? private; + final String? pullsUrl; + final DateTime? pushedAt; + final String? releasesUrl; + final int? size; + + /// The default value for a squash merge commit message: + /// + /// - `PR_BODY` - default to the pull request's body. + /// - `COMMIT_MESSAGES` - default to the branch's commit messages. + /// - `BLANK` - default to a blank commit message. + final String? squashMergeCommitMessage; + + /// The default value for a squash merge commit title: + /// + /// - `PR_TITLE` - default to the pull request's title. + /// - `COMMIT_OR_PR_TITLE` - default to the commit's title (if only one commit) + /// or the pull request's title (when more than one commit). + final String? squashMergeCommitTitle; + + final String? sshUrl; + final int? stargazersCount; + final String? stargazersUrl; + final String? statusesUrl; + final int? subscribersCount; + final String? subscribersUrl; + final String? subscriptionUrl; + final String? svnUrl; + final String? tagsUrl; + final String? teamsUrl; + final String? tempCloneToken; + final List? topics; + final String? treesUrl; + final DateTime? updatedAt; + final String? url; + final String? visibility; + final int? watchersCount; + + Map toJson() => _$TemplateRepositoryToJson(this); + + factory TemplateRepository.fromJson(Map input) => + _$TemplateRepositoryFromJson(input); +} + +@JsonSerializable() +class Owner { + Owner({ + this.avatarUrl, + this.eventsUrl, + this.followersUrl, + this.followingUrl, + this.gistsUrl, + this.gravatarId, + this.htmlUrl, + this.id, + this.login, + this.nodeId, + this.organizationsUrl, + this.receivedEventsUrl, + this.reposUrl, + this.siteAdmin, + this.starredUrl, + this.subscriptionsUrl, + this.type, + this.url, + }); + + final String? avatarUrl; + final String? eventsUrl; + final String? followersUrl; + final String? followingUrl; + final String? gistsUrl; + final String? gravatarId; + final String? htmlUrl; + final int? id; + final String? login; + final String? nodeId; + final String? organizationsUrl; + final String? receivedEventsUrl; + final String? reposUrl; + final bool? siteAdmin; + final String? starredUrl; + final String? subscriptionsUrl; + final String? type; + final String? url; + + Map toJson() => _$OwnerToJson(this); + + factory Owner.fromJson(Map input) => _$OwnerFromJson(input); +} + +@JsonSerializable() +class Tree { + Tree({ + this.sha, + this.url, + this.htmlUrl, + }); + + /// SHA for the commit + /// + /// Example: `7638417db6d59f3c431d3e1f261cc637155684cd` + final String? sha; + + final String? url; + + final String? htmlUrl; + + Map toJson() => _$TreeToJson(this); + + factory Tree.fromJson(Map input) => _$TreeFromJson(input); +} + +@JsonSerializable() +class Verification { + Verification({ + this.payload, + this.reason, + this.signature, + this.verified, + }); + + final String? payload; + final String? reason; + final String? signature; + final bool? verified; + + Map toJson() => _$VerificationToJson(this); + + factory Verification.fromJson(Map input) => + _$VerificationFromJson(input); +} + +@JsonSerializable() +class HtmlLink { + HtmlLink({ + this.href, + }); + + final String? href; + + Map toJson() => _$HtmlLinkToJson(this); + + factory HtmlLink.fromJson(Map input) => + _$HtmlLinkFromJson(input); +} + +@JsonSerializable() +class PullRequestLink { + PullRequestLink({ + this.href, + }); + + /// Example: `https://api.github.com/repos/octocat/Hello-World/pulls/1` + final String? href; + + Map toJson() => _$PullRequestLinkToJson(this); + + factory PullRequestLink.fromJson(Map input) => + _$PullRequestLinkFromJson(input); +} diff --git a/lib/src/common/model/timeline_support.g.dart b/lib/src/common/model/timeline_support.g.dart new file mode 100644 index 00000000..83adf60a --- /dev/null +++ b/lib/src/common/model/timeline_support.g.dart @@ -0,0 +1,409 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'timeline_support.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +GitHubApp _$GitHubAppFromJson(Map json) => GitHubApp( + clientId: json['client_id'] as String?, + clientSecret: json['client_secret'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + description: json['description'] as String?, + events: + (json['events'] as List?)?.map((e) => e as String).toList(), + externalUrl: json['external_url'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + installationsCount: (json['installations_count'] as num?)?.toInt(), + name: json['name'] as String?, + nodeId: json['node_id'] as String?, + owner: json['owner'] == null + ? null + : User.fromJson(json['owner'] as Map), + pem: json['pem'] as String?, + permissions: json['permissions'] == null + ? null + : Permissions.fromJson(json['permissions'] as Map), + slug: json['slug'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + webhookSecret: json['webhook_secret'] as String?, + ); + +Map _$GitHubAppToJson(GitHubApp instance) => { + 'client_id': instance.clientId, + 'client_secret': instance.clientSecret, + 'created_at': instance.createdAt?.toIso8601String(), + 'description': instance.description, + 'events': instance.events, + 'external_url': instance.externalUrl, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'installations_count': instance.installationsCount, + 'name': instance.name, + 'node_id': instance.nodeId, + 'owner': instance.owner, + 'pem': instance.pem, + 'permissions': instance.permissions, + 'slug': instance.slug, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'webhook_secret': instance.webhookSecret, + }; + +Rename _$RenameFromJson(Map json) => Rename( + from: json['from'] as String?, + to: json['to'] as String?, + ); + +Map _$RenameToJson(Rename instance) => { + 'from': instance.from, + 'to': instance.to, + }; + +DismissedReview _$DismissedReviewFromJson(Map json) => + DismissedReview( + dismissalCommitId: json['dismissal_commit_id'] as String?, + dismissalMessage: json['dismissal_message'] as String?, + reviewId: (json['review_id'] as num?)?.toInt(), + state: json['state'] as String?, + ); + +Map _$DismissedReviewToJson(DismissedReview instance) => + { + 'dismissal_commit_id': instance.dismissalCommitId, + 'dismissal_message': instance.dismissalMessage, + 'review_id': instance.reviewId, + 'state': instance.state, + }; + +ProjectCard _$ProjectCardFromJson(Map json) => ProjectCard( + columnName: json['column_name'] as String?, + id: (json['id'] as num?)?.toInt(), + previousColumnName: json['previous_column_name'] as String?, + projectId: (json['project_id'] as num?)?.toInt(), + projectUrl: json['project_url'] as String?, + url: json['url'] as String?, + ); + +Map _$ProjectCardToJson(ProjectCard instance) => + { + 'column_name': instance.columnName, + 'id': instance.id, + 'previous_column_name': instance.previousColumnName, + 'project_id': instance.projectId, + 'project_url': instance.projectUrl, + 'url': instance.url, + }; + +Source _$SourceFromJson(Map json) => Source( + issue: json['issue'] == null + ? null + : Issue.fromJson(json['issue'] as Map), + type: json['type'] as String?, + ); + +Map _$SourceToJson(Source instance) => { + 'issue': instance.issue, + 'type': instance.type, + }; + +License _$LicenseFromJson(Map json) => License( + htmlUrl: json['html_url'] as String?, + key: json['key'] as String?, + name: json['name'] as String?, + nodeId: json['node_id'] as String?, + spdxId: json['spdx_id'] as String?, + url: json['url'] as String?, + ); + +Map _$LicenseToJson(License instance) => { + 'html_url': instance.htmlUrl, + 'key': instance.key, + 'name': instance.name, + 'node_id': instance.nodeId, + 'spdx_id': instance.spdxId, + 'url': instance.url, + }; + +TemplateRepository _$TemplateRepositoryFromJson(Map json) => + TemplateRepository( + allowAutoMerge: json['allow_auto_merge'] as bool?, + allowMergeCommit: json['allow_merge_commit'] as bool?, + allowRebaseMerge: json['allow_rebase_merge'] as bool?, + allowSquashMerge: json['allow_squash_merge'] as bool?, + allowUpdateBranch: json['allow_update_branch'] as bool?, + archiveUrl: json['archive_url'] as String?, + archived: json['archived'] as bool?, + assigneesUrl: json['assignees_url'] as String?, + blobsUrl: json['blobs_url'] as String?, + branchesUrl: json['branches_url'] as String?, + cloneUrl: json['clone_url'] as String?, + collaboratorsUrl: json['collaborators_url'] as String?, + commentsUrl: json['comments_url'] as String?, + commitsUrl: json['commits_url'] as String?, + compareUrl: json['compare_url'] as String?, + contentsUrl: json['contents_url'] as String?, + contributorsUrl: json['contributors_url'] as String?, + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + defaultBranch: json['default_branch'] as String?, + deleteBranchOnMerge: json['delete_branch_on_merge'] as bool?, + deploymentsUrl: json['deployments_url'] as String?, + description: json['description'] as String?, + disabled: json['disabled'] as bool?, + downloadsUrl: json['downloads_url'] as String?, + eventsUrl: json['events_url'] as String?, + fork: json['fork'] as bool?, + forksCount: (json['forks_count'] as num?)?.toInt(), + forksUrl: json['forks_url'] as String?, + fullName: json['full_name'] as String?, + gitCommitsUrl: json['git_commits_url'] as String?, + gitRefsUrl: json['git_refs_url'] as String?, + gitTagsUrl: json['git_tags_url'] as String?, + gitUrl: json['git_url'] as String?, + hasDownloads: json['has_downloads'] as bool?, + hasIssues: json['has_issues'] as bool?, + hasPages: json['has_pages'] as bool?, + hasProjects: json['has_projects'] as bool?, + hasWiki: json['has_wiki'] as bool?, + homepage: json['homepage'] as String?, + hooksUrl: json['hooks_url'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + isTemplate: json['is_template'] as bool?, + issueCommentUrl: json['issue_comment_url'] as String?, + issueEventsUrl: json['issue_events_url'] as String?, + issuesUrl: json['issues_url'] as String?, + keysUrl: json['keys_url'] as String?, + labelsUrl: json['labels_url'] as String?, + language: json['language'] as String?, + languagesUrl: json['languages_url'] as String?, + mergeCommitMessage: json['merge_commit_message'] as String?, + mergeCommitTitle: json['merge_commit_title'] as String?, + mergesUrl: json['merges_url'] as String?, + milestonesUrl: json['milestones_url'] as String?, + mirrorUrl: json['mirror_url'] as String?, + name: json['name'] as String?, + networkCount: (json['network_count'] as num?)?.toInt(), + nodeId: json['node_id'] as String?, + notificationsUrl: json['notifications_url'] as String?, + openIssuesCount: (json['open_issues_count'] as num?)?.toInt(), + owner: json['owner'] == null + ? null + : Owner.fromJson(json['owner'] as Map), + permissions: json['permissions'] == null + ? null + : Permissions.fromJson(json['permissions'] as Map), + private: json['private'] as bool?, + pullsUrl: json['pulls_url'] as String?, + pushedAt: json['pushed_at'] == null + ? null + : DateTime.parse(json['pushed_at'] as String), + releasesUrl: json['releases_url'] as String?, + size: (json['size'] as num?)?.toInt(), + squashMergeCommitMessage: json['squash_merge_commit_message'] as String?, + squashMergeCommitTitle: json['squash_merge_commit_title'] as String?, + sshUrl: json['ssh_url'] as String?, + stargazersCount: (json['stargazers_count'] as num?)?.toInt(), + stargazersUrl: json['stargazers_url'] as String?, + statusesUrl: json['statuses_url'] as String?, + subscribersCount: (json['subscribers_count'] as num?)?.toInt(), + subscribersUrl: json['subscribers_url'] as String?, + subscriptionUrl: json['subscription_url'] as String?, + svnUrl: json['svn_url'] as String?, + tagsUrl: json['tags_url'] as String?, + teamsUrl: json['teams_url'] as String?, + tempCloneToken: json['temp_clone_token'] as String?, + topics: + (json['topics'] as List?)?.map((e) => e as String).toList(), + treesUrl: json['trees_url'] as String?, + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + url: json['url'] as String?, + visibility: json['visibility'] as String?, + watchersCount: (json['watchers_count'] as num?)?.toInt(), + ); + +Map _$TemplateRepositoryToJson(TemplateRepository instance) => + { + 'allow_auto_merge': instance.allowAutoMerge, + 'allow_merge_commit': instance.allowMergeCommit, + 'allow_rebase_merge': instance.allowRebaseMerge, + 'allow_squash_merge': instance.allowSquashMerge, + 'allow_update_branch': instance.allowUpdateBranch, + 'archive_url': instance.archiveUrl, + 'archived': instance.archived, + 'assignees_url': instance.assigneesUrl, + 'blobs_url': instance.blobsUrl, + 'branches_url': instance.branchesUrl, + 'clone_url': instance.cloneUrl, + 'collaborators_url': instance.collaboratorsUrl, + 'comments_url': instance.commentsUrl, + 'commits_url': instance.commitsUrl, + 'compare_url': instance.compareUrl, + 'contents_url': instance.contentsUrl, + 'contributors_url': instance.contributorsUrl, + 'created_at': instance.createdAt?.toIso8601String(), + 'default_branch': instance.defaultBranch, + 'delete_branch_on_merge': instance.deleteBranchOnMerge, + 'deployments_url': instance.deploymentsUrl, + 'description': instance.description, + 'disabled': instance.disabled, + 'downloads_url': instance.downloadsUrl, + 'events_url': instance.eventsUrl, + 'fork': instance.fork, + 'forks_count': instance.forksCount, + 'forks_url': instance.forksUrl, + 'full_name': instance.fullName, + 'git_commits_url': instance.gitCommitsUrl, + 'git_refs_url': instance.gitRefsUrl, + 'git_tags_url': instance.gitTagsUrl, + 'git_url': instance.gitUrl, + 'has_downloads': instance.hasDownloads, + 'has_issues': instance.hasIssues, + 'has_pages': instance.hasPages, + 'has_projects': instance.hasProjects, + 'has_wiki': instance.hasWiki, + 'homepage': instance.homepage, + 'hooks_url': instance.hooksUrl, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'is_template': instance.isTemplate, + 'issue_comment_url': instance.issueCommentUrl, + 'issue_events_url': instance.issueEventsUrl, + 'issues_url': instance.issuesUrl, + 'keys_url': instance.keysUrl, + 'labels_url': instance.labelsUrl, + 'language': instance.language, + 'languages_url': instance.languagesUrl, + 'merge_commit_message': instance.mergeCommitMessage, + 'merge_commit_title': instance.mergeCommitTitle, + 'merges_url': instance.mergesUrl, + 'milestones_url': instance.milestonesUrl, + 'mirror_url': instance.mirrorUrl, + 'name': instance.name, + 'network_count': instance.networkCount, + 'node_id': instance.nodeId, + 'notifications_url': instance.notificationsUrl, + 'open_issues_count': instance.openIssuesCount, + 'owner': instance.owner, + 'permissions': instance.permissions, + 'private': instance.private, + 'pulls_url': instance.pullsUrl, + 'pushed_at': instance.pushedAt?.toIso8601String(), + 'releases_url': instance.releasesUrl, + 'size': instance.size, + 'squash_merge_commit_message': instance.squashMergeCommitMessage, + 'squash_merge_commit_title': instance.squashMergeCommitTitle, + 'ssh_url': instance.sshUrl, + 'stargazers_count': instance.stargazersCount, + 'stargazers_url': instance.stargazersUrl, + 'statuses_url': instance.statusesUrl, + 'subscribers_count': instance.subscribersCount, + 'subscribers_url': instance.subscribersUrl, + 'subscription_url': instance.subscriptionUrl, + 'svn_url': instance.svnUrl, + 'tags_url': instance.tagsUrl, + 'teams_url': instance.teamsUrl, + 'temp_clone_token': instance.tempCloneToken, + 'topics': instance.topics, + 'trees_url': instance.treesUrl, + 'updated_at': instance.updatedAt?.toIso8601String(), + 'url': instance.url, + 'visibility': instance.visibility, + 'watchers_count': instance.watchersCount, + }; + +Owner _$OwnerFromJson(Map json) => Owner( + avatarUrl: json['avatar_url'] as String?, + eventsUrl: json['events_url'] as String?, + followersUrl: json['followers_url'] as String?, + followingUrl: json['following_url'] as String?, + gistsUrl: json['gists_url'] as String?, + gravatarId: json['gravatar_id'] as String?, + htmlUrl: json['html_url'] as String?, + id: (json['id'] as num?)?.toInt(), + login: json['login'] as String?, + nodeId: json['node_id'] as String?, + organizationsUrl: json['organizations_url'] as String?, + receivedEventsUrl: json['received_events_url'] as String?, + reposUrl: json['repos_url'] as String?, + siteAdmin: json['site_admin'] as bool?, + starredUrl: json['starred_url'] as String?, + subscriptionsUrl: json['subscriptions_url'] as String?, + type: json['type'] as String?, + url: json['url'] as String?, + ); + +Map _$OwnerToJson(Owner instance) => { + 'avatar_url': instance.avatarUrl, + 'events_url': instance.eventsUrl, + 'followers_url': instance.followersUrl, + 'following_url': instance.followingUrl, + 'gists_url': instance.gistsUrl, + 'gravatar_id': instance.gravatarId, + 'html_url': instance.htmlUrl, + 'id': instance.id, + 'login': instance.login, + 'node_id': instance.nodeId, + 'organizations_url': instance.organizationsUrl, + 'received_events_url': instance.receivedEventsUrl, + 'repos_url': instance.reposUrl, + 'site_admin': instance.siteAdmin, + 'starred_url': instance.starredUrl, + 'subscriptions_url': instance.subscriptionsUrl, + 'type': instance.type, + 'url': instance.url, + }; + +Tree _$TreeFromJson(Map json) => Tree( + sha: json['sha'] as String?, + url: json['url'] as String?, + htmlUrl: json['html_url'] as String?, + ); + +Map _$TreeToJson(Tree instance) => { + 'sha': instance.sha, + 'url': instance.url, + 'html_url': instance.htmlUrl, + }; + +Verification _$VerificationFromJson(Map json) => Verification( + payload: json['payload'] as String?, + reason: json['reason'] as String?, + signature: json['signature'] as String?, + verified: json['verified'] as bool?, + ); + +Map _$VerificationToJson(Verification instance) => + { + 'payload': instance.payload, + 'reason': instance.reason, + 'signature': instance.signature, + 'verified': instance.verified, + }; + +HtmlLink _$HtmlLinkFromJson(Map json) => HtmlLink( + href: json['href'] as String?, + ); + +Map _$HtmlLinkToJson(HtmlLink instance) => { + 'href': instance.href, + }; + +PullRequestLink _$PullRequestLinkFromJson(Map json) => + PullRequestLink( + href: json['href'] as String?, + ); + +Map _$PullRequestLinkToJson(PullRequestLink instance) => + { + 'href': instance.href, + }; diff --git a/lib/src/common/model/users.dart b/lib/src/common/model/users.dart index 7be7dece..f66fd395 100644 --- a/lib/src/common/model/users.dart +++ b/lib/src/common/model/users.dart @@ -1,187 +1,286 @@ -part of github.common; +import 'package:json_annotation/json_annotation.dart'; + +part 'users.g.dart'; /// Model class for a user. +@JsonSerializable() class User { - Map json; + User({ + this.id, + this.login, + this.avatarUrl, + this.htmlUrl, + this.siteAdmin, + this.name, + this.company, + this.blog, + this.location, + this.email, + this.hirable, + this.bio, + this.publicReposCount, + this.publicGistsCount, + this.followersCount, + this.followingCount, + this.createdAt, + this.updatedAt, + + // Properties from the Timeline API + this.eventsUrl, + this.followersUrl, + this.followingUrl, + this.gistsUrl, + this.gravatarId, + this.nodeId, + this.organizationsUrl, + this.receivedEventsUrl, + this.reposUrl, + this.starredAt, + this.starredUrl, + this.subscriptionsUrl, + this.type, + this.url, + }); + + @JsonKey(includeToJson: false, includeFromJson: false) + Map? json; // TODO remove /// User's Username - String login; + String? login; /// User ID - int id; + int? id; /// Avatar URL - @ApiName("avatar_url") - String avatarUrl; + String? avatarUrl; /// Url to this user's profile. - @ApiName("html_url") - String htmlUrl; + String? htmlUrl; /// If the user is a site administrator - @ApiName("site_admin") - bool siteAdmin; + bool? siteAdmin; /// User's Name - String name; + String? name; /// Name of User's Company - String company; + String? company; /// Link to User's Blog - String blog; + String? blog; /// User's Location - String location; + String? location; /// User's Email - String email; + String? email; /// If this user is hirable - bool hirable; + bool? hirable; /// The User's Biography - String bio; + String? bio; /// Number of public repositories that this user has - @ApiName("public_repos") - int publicReposCount; + @JsonKey(name: 'public_repos') + int? publicReposCount; /// Number of public gists that this user has - @ApiName("public_gists") - int publicGistsCount; + @JsonKey(name: 'public_gists') + int? publicGistsCount; /// Number of followers that this user has - @ApiName("followers") - int followersCount; + @JsonKey(name: 'followers') + int? followersCount; /// Number of Users that this user follows - @ApiName("following") - int followingCount; + @JsonKey(name: 'following') + int? followingCount; /// The time this [User] was created. - @ApiName("created_at") - DateTime createdAt; + DateTime? createdAt; /// Last time this [User] was updated. - @ApiName("updated_at") - DateTime updatedAt; - - static User fromJSON(Map input) { - if (input == null) return null; - - if (input['avatar_url'] == null) { - print(input); - return null; - } - - return new User() - ..login = input['login'] - ..id = input['id'] - ..avatarUrl = input['avatar_url'] - ..htmlUrl = input['html_url'] - ..bio = input['bio'] - ..name = input['name'] - ..siteAdmin = input['site_admin'] - ..company = input['company'] - ..blog = input['blog'] - ..location = input['location'] - ..email = input['email'] - ..hirable = input['hirable'] - ..publicGistsCount = input['public_gists'] - ..publicReposCount = input['public_repos'] - ..followersCount = input['followers'] - ..followingCount = input['following'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..json = input; - } + DateTime? updatedAt; + + /// The username of the twitter account (without leading @) + String? twitterUsername; + + // The following properties were added to support the Timeline API. + + /// Example: `https://api.github.com/users/octocat/events{/privacy}` + String? eventsUrl; + + /// Example: `https://api.github.com/users/octocat/followers` + String? followersUrl; + + /// Example: `https://api.github.com/users/octocat/following{/other_user}` + String? followingUrl; + + /// Example: `https://api.github.com/users/octocat/gists{/gist_id}` + String? gistsUrl; + + /// Example: `41d064eb2195891e12d0413f63227ea7` + String? gravatarId; + + /// Example: `MDQ6VXNlcjE=` + String? nodeId; + + /// Example: `https://api.github.com/users/octocat/orgs` + String? organizationsUrl; + + /// Example: `https://api.github.com/users/octocat/received_events` + String? receivedEventsUrl; + + /// Example: `https://api.github.com/users/octocat/repos` + String? reposUrl; + + DateTime? starredAt; + + /// Example: `https://api.github.com/users/octocat/starred{/owner}{/repo}` + String? starredUrl; + + /// Example: `https://api.github.com/users/octocat/subscriptions` + String? subscriptionsUrl; + + /// Example: `User` + String? type; + + /// Example: `https://api.github.com/users/octocat` + String? url; + + factory User.fromJson(Map input) => _$UserFromJson(input); + Map toJson() => _$UserToJson(this); +} + +/// The response from listing collaborators on a repo. +// https://developer.github.com/v3/repos/collaborators/#response +@JsonSerializable() +class Collaborator { + Collaborator( + this.login, + this.id, + this.htmlUrl, + this.type, + this.siteAdmin, + this.permissions, + ); + + String? login; + int? id; + String? htmlUrl; + String? type; + bool? siteAdmin; + Map? permissions; + + factory Collaborator.fromJson(Map json) => + _$CollaboratorFromJson(json); + Map toJson() => _$CollaboratorToJson(this); +} + +/// The response from listing contributors on a repo. +/// +/// https://developer.github.com/v3/repos/#response-if-repository-contains-content +@JsonSerializable() +class Contributor { + Contributor({ + this.id, + this.login, + this.avatarUrl, + this.htmlUrl, + this.type, + this.siteAdmin, + this.contributions, + }); + + /// User's Username + String? login; + + /// User ID + int? id; + + /// Avatar URL + String? avatarUrl; + + /// Url to this user's profile. + String? htmlUrl; + + String? type; + + /// If the user is a site administrator + bool? siteAdmin; + + /// Contributions count + int? contributions; + + factory Contributor.fromJson(Map input) => + _$ContributorFromJson(input); + Map toJson() => _$ContributorToJson(this); } /// The Currently Authenticated User +@JsonSerializable() class CurrentUser extends User { + CurrentUser(); + /// Number of Private Repositories - @ApiName("total_private_repos") - int privateReposCount; + @JsonKey(name: 'total_private_repos') + int? privateReposCount; /// Number of Owned Private Repositories that the user owns - @ApiName("owned_private_repos") - int ownedPrivateReposCount; + @JsonKey(name: 'owned_private_repos') + int? ownedPrivateReposCount; /// The User's Disk Usage - @ApiName("disk_usage") - int diskUsage; + @JsonKey(name: 'disk_usage') + int? diskUsage; /// The User's GitHub Plan - UserPlan plan; - - static CurrentUser fromJSON(Map input) { - if (input == null) return null; - - return new CurrentUser() - ..login = input['login'] - ..id = input['id'] - ..avatarUrl = input['avatar_url'] - ..htmlUrl = input['html_url'] - ..bio = input['bio'] - ..name = input['name'] - ..siteAdmin = input['site_admin'] - ..company = input['company'] - ..blog = input['blog'] - ..location = input['location'] - ..email = input['email'] - ..hirable = input['hirable'] - ..publicGistsCount = input['public_gists'] - ..publicReposCount = input['public_repos'] - ..followersCount = input['followers'] - ..followingCount = input['following'] - ..createdAt = parseDateTime(input['created_at']) - ..updatedAt = parseDateTime(input['updated_at']) - ..privateReposCount = input['total_private_repos'] - ..ownedPrivateReposCount = input['owned_private_repos'] - ..plan = UserPlan.fromJSON(input['plan'] as Map) - ..json = input; - } + UserPlan? plan; + + factory CurrentUser.fromJson(Map input) => + _$CurrentUserFromJson(input); + @override + Map toJson() => _$CurrentUserToJson(this); } /// A Users GitHub Plan +@JsonSerializable() class UserPlan { + UserPlan(); + // Plan Name - String name; + String? name; // Plan Space - int space; + int? space; // Number of Private Repositories - @ApiName("private_repos") - int privateReposCount; + @JsonKey(name: 'private_repos') + int? privateReposCount; // Number of Collaborators - @ApiName("collaborators") - int collaboratorsCount; - - static UserPlan fromJSON(Map input) { - if (input == null) return null; - return new UserPlan() - ..name = input['name'] - ..space = input['space'] - ..privateReposCount = input['private_repos'] - ..collaboratorsCount = input['collaborators']; - } + @JsonKey(name: 'collaborators') + int? collaboratorsCount; + + factory UserPlan.fromJson(Map input) => + _$UserPlanFromJson(input); + Map toJson() => _$UserPlanToJson(this); } /// Model class for a user's email address. +@JsonSerializable() class UserEmail { - String email; - bool verified; - bool primary; - - static UserEmail fromJSON(Map input) { - if (input == null) return null; - - return new UserEmail() - ..email = input['email'] - ..primary = input['primary'] - ..verified = input['verified']; - } + UserEmail({ + this.email, + this.verified, + this.primary, + }); + String? email; + bool? verified; + bool? primary; + + factory UserEmail.fromJson(Map input) => + _$UserEmailFromJson(input); + Map toJson() => _$UserEmailToJson(this); } diff --git a/lib/src/common/model/users.g.dart b/lib/src/common/model/users.g.dart new file mode 100644 index 00000000..1e61153c --- /dev/null +++ b/lib/src/common/model/users.g.dart @@ -0,0 +1,239 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'users.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +User _$UserFromJson(Map json) => User( + id: (json['id'] as num?)?.toInt(), + login: json['login'] as String?, + avatarUrl: json['avatar_url'] as String?, + htmlUrl: json['html_url'] as String?, + siteAdmin: json['site_admin'] as bool?, + name: json['name'] as String?, + company: json['company'] as String?, + blog: json['blog'] as String?, + location: json['location'] as String?, + email: json['email'] as String?, + hirable: json['hirable'] as bool?, + bio: json['bio'] as String?, + publicReposCount: (json['public_repos'] as num?)?.toInt(), + publicGistsCount: (json['public_gists'] as num?)?.toInt(), + followersCount: (json['followers'] as num?)?.toInt(), + followingCount: (json['following'] as num?)?.toInt(), + createdAt: json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String), + updatedAt: json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String), + eventsUrl: json['events_url'] as String?, + followersUrl: json['followers_url'] as String?, + followingUrl: json['following_url'] as String?, + gistsUrl: json['gists_url'] as String?, + gravatarId: json['gravatar_id'] as String?, + nodeId: json['node_id'] as String?, + organizationsUrl: json['organizations_url'] as String?, + receivedEventsUrl: json['received_events_url'] as String?, + reposUrl: json['repos_url'] as String?, + starredAt: json['starred_at'] == null + ? null + : DateTime.parse(json['starred_at'] as String), + starredUrl: json['starred_url'] as String?, + subscriptionsUrl: json['subscriptions_url'] as String?, + type: json['type'] as String?, + url: json['url'] as String?, + )..twitterUsername = json['twitter_username'] as String?; + +Map _$UserToJson(User instance) => { + 'login': instance.login, + 'id': instance.id, + 'avatar_url': instance.avatarUrl, + 'html_url': instance.htmlUrl, + 'site_admin': instance.siteAdmin, + 'name': instance.name, + 'company': instance.company, + 'blog': instance.blog, + 'location': instance.location, + 'email': instance.email, + 'hirable': instance.hirable, + 'bio': instance.bio, + 'public_repos': instance.publicReposCount, + 'public_gists': instance.publicGistsCount, + 'followers': instance.followersCount, + 'following': instance.followingCount, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'twitter_username': instance.twitterUsername, + 'events_url': instance.eventsUrl, + 'followers_url': instance.followersUrl, + 'following_url': instance.followingUrl, + 'gists_url': instance.gistsUrl, + 'gravatar_id': instance.gravatarId, + 'node_id': instance.nodeId, + 'organizations_url': instance.organizationsUrl, + 'received_events_url': instance.receivedEventsUrl, + 'repos_url': instance.reposUrl, + 'starred_at': instance.starredAt?.toIso8601String(), + 'starred_url': instance.starredUrl, + 'subscriptions_url': instance.subscriptionsUrl, + 'type': instance.type, + 'url': instance.url, + }; + +Collaborator _$CollaboratorFromJson(Map json) => Collaborator( + json['login'] as String?, + (json['id'] as num?)?.toInt(), + json['html_url'] as String?, + json['type'] as String?, + json['site_admin'] as bool?, + (json['permissions'] as Map?)?.map( + (k, e) => MapEntry(k, e as bool), + ), + ); + +Map _$CollaboratorToJson(Collaborator instance) => + { + 'login': instance.login, + 'id': instance.id, + 'html_url': instance.htmlUrl, + 'type': instance.type, + 'site_admin': instance.siteAdmin, + 'permissions': instance.permissions, + }; + +Contributor _$ContributorFromJson(Map json) => Contributor( + id: (json['id'] as num?)?.toInt(), + login: json['login'] as String?, + avatarUrl: json['avatar_url'] as String?, + htmlUrl: json['html_url'] as String?, + type: json['type'] as String?, + siteAdmin: json['site_admin'] as bool?, + contributions: (json['contributions'] as num?)?.toInt(), + ); + +Map _$ContributorToJson(Contributor instance) => + { + 'login': instance.login, + 'id': instance.id, + 'avatar_url': instance.avatarUrl, + 'html_url': instance.htmlUrl, + 'type': instance.type, + 'site_admin': instance.siteAdmin, + 'contributions': instance.contributions, + }; + +CurrentUser _$CurrentUserFromJson(Map json) => CurrentUser() + ..login = json['login'] as String? + ..id = (json['id'] as num?)?.toInt() + ..avatarUrl = json['avatar_url'] as String? + ..htmlUrl = json['html_url'] as String? + ..siteAdmin = json['site_admin'] as bool? + ..name = json['name'] as String? + ..company = json['company'] as String? + ..blog = json['blog'] as String? + ..location = json['location'] as String? + ..email = json['email'] as String? + ..hirable = json['hirable'] as bool? + ..bio = json['bio'] as String? + ..publicReposCount = (json['public_repos'] as num?)?.toInt() + ..publicGistsCount = (json['public_gists'] as num?)?.toInt() + ..followersCount = (json['followers'] as num?)?.toInt() + ..followingCount = (json['following'] as num?)?.toInt() + ..createdAt = json['created_at'] == null + ? null + : DateTime.parse(json['created_at'] as String) + ..updatedAt = json['updated_at'] == null + ? null + : DateTime.parse(json['updated_at'] as String) + ..twitterUsername = json['twitter_username'] as String? + ..eventsUrl = json['events_url'] as String? + ..followersUrl = json['followers_url'] as String? + ..followingUrl = json['following_url'] as String? + ..gistsUrl = json['gists_url'] as String? + ..gravatarId = json['gravatar_id'] as String? + ..nodeId = json['node_id'] as String? + ..organizationsUrl = json['organizations_url'] as String? + ..receivedEventsUrl = json['received_events_url'] as String? + ..reposUrl = json['repos_url'] as String? + ..starredAt = json['starred_at'] == null + ? null + : DateTime.parse(json['starred_at'] as String) + ..starredUrl = json['starred_url'] as String? + ..subscriptionsUrl = json['subscriptions_url'] as String? + ..type = json['type'] as String? + ..url = json['url'] as String? + ..privateReposCount = (json['total_private_repos'] as num?)?.toInt() + ..ownedPrivateReposCount = (json['owned_private_repos'] as num?)?.toInt() + ..diskUsage = (json['disk_usage'] as num?)?.toInt() + ..plan = json['plan'] == null + ? null + : UserPlan.fromJson(json['plan'] as Map); + +Map _$CurrentUserToJson(CurrentUser instance) => + { + 'login': instance.login, + 'id': instance.id, + 'avatar_url': instance.avatarUrl, + 'html_url': instance.htmlUrl, + 'site_admin': instance.siteAdmin, + 'name': instance.name, + 'company': instance.company, + 'blog': instance.blog, + 'location': instance.location, + 'email': instance.email, + 'hirable': instance.hirable, + 'bio': instance.bio, + 'public_repos': instance.publicReposCount, + 'public_gists': instance.publicGistsCount, + 'followers': instance.followersCount, + 'following': instance.followingCount, + 'created_at': instance.createdAt?.toIso8601String(), + 'updated_at': instance.updatedAt?.toIso8601String(), + 'twitter_username': instance.twitterUsername, + 'events_url': instance.eventsUrl, + 'followers_url': instance.followersUrl, + 'following_url': instance.followingUrl, + 'gists_url': instance.gistsUrl, + 'gravatar_id': instance.gravatarId, + 'node_id': instance.nodeId, + 'organizations_url': instance.organizationsUrl, + 'received_events_url': instance.receivedEventsUrl, + 'repos_url': instance.reposUrl, + 'starred_at': instance.starredAt?.toIso8601String(), + 'starred_url': instance.starredUrl, + 'subscriptions_url': instance.subscriptionsUrl, + 'type': instance.type, + 'url': instance.url, + 'total_private_repos': instance.privateReposCount, + 'owned_private_repos': instance.ownedPrivateReposCount, + 'disk_usage': instance.diskUsage, + 'plan': instance.plan, + }; + +UserPlan _$UserPlanFromJson(Map json) => UserPlan() + ..name = json['name'] as String? + ..space = (json['space'] as num?)?.toInt() + ..privateReposCount = (json['private_repos'] as num?)?.toInt() + ..collaboratorsCount = (json['collaborators'] as num?)?.toInt(); + +Map _$UserPlanToJson(UserPlan instance) => { + 'name': instance.name, + 'space': instance.space, + 'private_repos': instance.privateReposCount, + 'collaborators': instance.collaboratorsCount, + }; + +UserEmail _$UserEmailFromJson(Map json) => UserEmail( + email: json['email'] as String?, + verified: json['verified'] as bool?, + primary: json['primary'] as bool?, + ); + +Map _$UserEmailToJson(UserEmail instance) => { + 'email': instance.email, + 'verified': instance.verified, + 'primary': instance.primary, + }; diff --git a/lib/src/common/orgs_service.dart b/lib/src/common/orgs_service.dart index 6fd185aa..b2ca7b26 100644 --- a/lib/src/common/orgs_service.dart +++ b/lib/src/common/orgs_service.dart @@ -1,140 +1,165 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; + +import 'package:github/src/common.dart'; +import 'package:http/http.dart' as http; /// The [OrganizationsService] handles communication with organization /// methods of the GitHub API. /// /// API docs: https://developer.github.com/v3/orgs/ class OrganizationsService extends Service { - OrganizationsService(GitHub github) : super(github); + OrganizationsService(super.github); /// Lists all of the memberships in organizations for the given [userName]. /// If [userName] is not specified we list the memberships in organizations /// for the authenticated user. /// /// API docs: : https://developer.github.com/v3/orgs/#list-user-organizations - Stream list([String userName]) { - String requestPath = "/users/$userName/orgs"; + Stream list([String? userName]) { + var requestPath = '/users/$userName/orgs'; if (userName == null) { - requestPath = "/user/orgs"; + requestPath = '/user/orgs'; } - return new PaginationHelper(_github).objects( - "GET", requestPath, Organization.fromJSON) as Stream; + return PaginationHelper(github).objects( + 'GET', + requestPath, + Organization.fromJson, + ); } /// Fetches the organization specified by [name]. /// /// API docs: https://developer.github.com/v3/orgs/#get-an-organization - Future get(String name) { - return _github.getJSON("/orgs/${name}", - convert: Organization.fromJSON, - statusCode: StatusCodes.OK, fail: (http.Response response) { - if (response.statusCode == 404) { - throw new OrganizationNotFound(_github, name); - } - }) as Future; - } + Future get(String? name) => github.getJSON('/orgs/$name', + convert: Organization.fromJson, + statusCode: StatusCodes.OK, fail: (http.Response response) { + if (response.statusCode == StatusCodes.NOT_FOUND) { + throw OrganizationNotFound(github, name); + } + }); /// Fetches the organizations specified by [names]. - Stream getMulti(List names) { - var controller = new StreamController(); - - var group = new FutureGroup(); - - for (var name in names) { - group.add(get(name).then((org) { - controller.add(org); - })); + Stream getMulti(List names) async* { + for (final name in names) { + final org = await get(name); + yield org; } - - group.future.then((_) { - controller.close(); - }); - - return controller.stream; } /// Edits an Organization /// /// API docs: https://developer.github.com/v3/orgs/#edit-an-organization - Future edit(String org, - {String billingEmail, - String company, - String email, - String location, - String name, - String description}) { - var map = createNonNullMap({ - "billing_email": billingEmail, - "company": company, - "email": email, - "location": location, - "name": name, - "description": description + Future edit( + String org, { + String? billingEmail, + String? company, + String? email, + String? location, + String? name, + String? description, + }) { + final map = createNonNullMap({ + 'billing_email': billingEmail, + 'company': company, + 'email': email, + 'location': location, + 'name': name, + 'description': description }); - return _github.postJSON("/orgs/${org}", - statusCode: 200, - convert: Organization.fromJSON, - // TODO: This is probably wrong. Map needs to be json encoded? - body: map) as Future; + return github.postJSON('/orgs/$org', + statusCode: StatusCodes.OK, + convert: Organization.fromJson, + body: GitHubJson.encode(map)); } /// Lists all of the teams for the specified organization. /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-teams Stream listTeams(String orgName) { - return new PaginationHelper(_github).objects( - "GET", "/orgs/${orgName}/teams", Team.fromJSON) as Stream; + return PaginationHelper(github).objects( + 'GET', + '/orgs/$orgName/teams', + Team.fromJson, + ); } /// Gets the team specified by the [teamId]. /// /// API docs: https://developer.github.com/v3/orgs/teams/#get-team Future getTeam(int teamId) { - return _github.getJSON("/teams/${teamId}", - convert: Organization.fromJSON, statusCode: 200) as Future; + return github.getJSON('/teams/$teamId', + convert: Organization.fromJson, + statusCode: StatusCodes.OK) as Future; + } + + /// Gets the team specified by its [teamName]. + /// + /// https://docs.github.com/en/rest/teams/teams?apiVersion=2022-11-28#get-a-team-by-name + Future getTeamByName( + String orgName, + String teamName, + ) { + return github.getJSON( + 'orgs/$orgName/teams/$teamName', + convert: Team.fromJson, + statusCode: StatusCodes.OK, + ); } /// Creates a Team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#create-team - Future createTeam(String org, String name, - {String description, List repos, String permission}) { - var map = createNonNullMap({ - "name": name, - "description": description, - "repo_names": repos, - "permission": permission + Future createTeam( + String org, + String name, { + String? description, + List? repos, + String? permission, + }) { + final map = createNonNullMap({ + 'name': name, + 'description': description, + 'repo_names': repos, + 'permission': permission }); - return _github.postJSON("/orgs/${org}/teams", - statusCode: 201, - convert: Team.fromJSON, - // TODO: This is probably wrong, map needs to be json encoded? - body: map) as Future; + return github.postJSON('/orgs/$org/teams', + statusCode: StatusCodes.CREATED, + convert: Team.fromJson, + body: GitHubJson.encode(map)); } /// Edits a Team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#edit-team - Future editTeam(int teamId, String name, - {String description, String permission}) { - var map = createNonNullMap( - {"name": name, "description": description, "permission": permission}); - - return _github.postJSON("/teams/${teamId}", - statusCode: 200, - convert: Team.fromJSON, - // TODO: This is probably wrong, map needs to be json encoded? - body: map) as Future; + Future editTeam( + int teamId, + String name, { + String? description, + String? permission, + }) { + final map = createNonNullMap({ + 'name': name, + 'description': description, + 'permission': permission, + }); + + return github.postJSON( + '/teams/$teamId', + statusCode: StatusCodes.OK, + convert: Team.fromJson, + body: GitHubJson.encode(map), + ); } /// Deletes the team specified by the [teamId] /// /// API docs: https://developer.github.com/v3/orgs/teams/#delete-team Future deleteTeam(int teamId) { - return _github.request("DELETE", "/teams/${teamId}").then((response) { - return response.statusCode == 204; + return github.request('DELETE', '/teams/$teamId').then((response) { + return response.statusCode == StatusCodes.NO_CONTENT; }); } @@ -142,128 +167,142 @@ class OrganizationsService extends Service { /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-team-members Stream listTeamMembers(int teamId) { - return new PaginationHelper(_github) - .objects("GET", "/teams/${teamId}/members", TeamMember.fromJSON) - as Stream; + return PaginationHelper(github).objects( + 'GET', + '/teams/$teamId/members', + TeamMember.fromJson, + ); } Future getTeamMemberStatus(int teamId, String user) { - return _github.getJSON("/teams/${teamId}/memberships/${user}").then((json) { - return json["state"]; + return github.getJSON('/teams/$teamId/memberships/$user').then((json) { + return json['state']; }); } - /// Adds a user to the team. + /// Returns the membership status for a [user] in a team with [teamId]. /// - /// API docs: https://developer.github.com/v3/orgs/teams/#add-team-member - @deprecated - Future addTeamMember(int teamId, String user) { - return _github - .request("PUT", "/teams/${teamId}/members/${user}") - .then((response) { - return response.statusCode == 204; - }); - } - - /// Removes a user from the team. - /// - /// API docs: https://developer.github.com/v3/orgs/teams/#remove-team-member - @deprecated - Future removeMember(int teamId, String user) { - return _github - .request("DELETE", "/teams/${teamId}/members/${user}") - .then((response) { - return response.statusCode == 204; - }); + /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership + Future getTeamMembership( + int teamId, + String user, + ) { + return github.getJSON( + '/teams/$teamId/memberships/$user', + statusCode: StatusCodes.OK, + convert: (dynamic json) => TeamMembershipState( + json['state'], + ), + ); } - /// Returns the membership status for a user in a team. + /// Returns the membership status for [user] in [teamName] given the [orgName]. /// - /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership - Future getTeamMembership(int teamId, String user) { - var completer = new Completer(); - - _github.getJSON("/teams/${teamId}/memberships/${user}", statusCode: 200, - fail: (http.Response response) { - if (response.statusCode == 404) { - completer.complete(new TeamMembershipState(null)); - } else { - _github.handleStatusCode(response); - } - }, convert: (json) => new TeamMembershipState(json['state'])).then( - completer.complete); - - return completer.future; + /// Note that this will throw on NotFound if the user is not a member of the + /// team. Adding a fail function to set the value does not help unless you + /// throw out of the fail function. + Future getTeamMembershipByName( + String orgName, + String teamName, + String user, + ) { + return github.getJSON( + '/orgs/$orgName/teams/$teamName/memberships/$user', + statusCode: StatusCodes.OK, + convert: (dynamic json) => TeamMembershipState( + json['state'], + ), + ); } /// Invites a user to the specified team. /// - /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership - Future addTeamMembership(int teamId, String user) { - var completer = new Completer(); - - _github.request("POST", "/teams/${teamId}/memberships/${user}", - statusCode: 200, fail: (http.Response response) { - if (response.statusCode == 404) { - completer.complete(new TeamMembershipState(null)); - } else { - _github.handleStatusCode(response); - } - }).then((response) { - return new TeamMembershipState(JSON.decode(response.body)["state"]); - // TODO: Not sure what should go here. - }).then(completer.complete); - - return completer.future; + /// API docs: https://developer.github.com/v3/teams/members/#add-or-update-team-membership + Future addTeamMembership( + int teamId, + String user, + ) async { + final response = await github.request( + 'PUT', + '/teams/$teamId/memberships/$user', + statusCode: StatusCodes.OK, + ); + return TeamMembershipState(jsonDecode(response.body)['state']); } /// Removes a user from the specified team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-membership - Future removeTeamMembership(int teamId, String user) { - return _github.request("DELETE", "/teams/${teamId}/memberships/${user}", - statusCode: 204); + Future removeTeamMembership( + int teamId, + String user, + ) { + return github.request( + 'DELETE', + '/teams/$teamId/memberships/$user', + statusCode: StatusCodes.NO_CONTENT, + ); } /// Lists the repositories that the specified team has access to. /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-team-repos Stream listTeamRepositories(int teamId) { - return new PaginationHelper(_github) - .objects("GET", "/teams/${teamId}/repos", Repository.fromJSON) - as Stream; + return PaginationHelper(github).objects( + 'GET', + '/teams/$teamId/repos', + Repository.fromJson, + ); } /// Checks if a team manages the specified repository. /// /// API docs: https://developer.github.com/v3/orgs/teams/#get-team-repo - Future isTeamRepository(int teamId, RepositorySlug slug) { - return _github - .request("GET", "/teams/${teamId}/repos/${slug.fullName}") + Future isTeamRepository( + int teamId, + RepositorySlug slug, + ) { + return github + .request( + 'GET', + '/teams/$teamId/repos/${slug.fullName}', + ) .then((response) { - return response.statusCode == 204; + return response.statusCode == StatusCodes.NO_CONTENT; }); } /// Adds a repository to be managed by the specified team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#add-team-repo - Future addTeamRepository(int teamId, RepositorySlug slug) { - return _github - .request("PUT", "/teams/${teamId}/repos/${slug.fullName}") + Future addTeamRepository( + int teamId, + RepositorySlug slug, + ) { + return github + .request( + 'PUT', + '/teams/$teamId/repos/${slug.fullName}', + ) .then((response) { - return response.statusCode == 204; + return response.statusCode == StatusCodes.NO_CONTENT; }); } /// Removes a repository from being managed by the specified team. /// /// API docs: https://developer.github.com/v3/orgs/teams/#remove-team-repo - Future removeTeamRepository(int teamId, RepositorySlug slug) { - return _github - .request("DELETE", "/teams/${teamId}/repos/${slug.fullName}") + Future removeTeamRepository( + int teamId, + RepositorySlug slug, + ) { + return github + .request( + 'DELETE', + '/teams/$teamId/repos/${slug.fullName}', + ) .then((response) { - return response.statusCode == 204; + return response.statusCode == StatusCodes.NO_CONTENT; }); } @@ -271,35 +310,52 @@ class OrganizationsService extends Service { /// /// API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams Stream listUserTeams() { - return new PaginationHelper(_github) - .objects("GET", "/user/teams", Team.fromJSON) as Stream; + return PaginationHelper(github).objects( + 'GET', + '/user/teams', + Team.fromJson, + ); + } + + /// Lists all of the users in an organization + /// + /// API docs: https://developer.github.com/v3/orgs/teams/#list-user-teams + Stream listUsers(String org) { + return PaginationHelper(github).objects( + 'GET', + '/orgs/$org/members', + User.fromJson, + ); } /// Lists the hooks for the specified organization. /// /// API docs: https://developer.github.com/v3/orgs/hooks/#list-hooks Stream listHooks(String org) { - return new PaginationHelper(_github).objects("GET", "/orgs/${org}/hooks", - (Map input) => Hook.fromJSON(org, input)) - as Stream; + return PaginationHelper(github).objects('GET', '/orgs/$org/hooks', + (dynamic i) => Hook.fromJson(i)..repoName = org); } /// Fetches a single hook by [id]. /// /// API docs: https://developer.github.com/v3/orgs/hooks/#get-single-hook - Future getHook(String org, int id) { - return _github.getJSON("/orgs/${org}/hooks/${id}", - convert: (Map i) => Hook.fromJSON(org, i)) - as Future; - } + Future getHook( + String org, + int id, + ) => + github.getJSON('/orgs/$org/hooks/$id', + convert: (dynamic i) => Hook.fromJson(i)..repoName = org); /// Creates an organization hook based on the specified [hook]. /// /// API docs: https://developer.github.com/v3/orgs/hooks/#create-a-hook - Future createHook(String org, CreateHook hook) { - return _github.postJSON("/orgs/${org}/hooks", - convert: (Map i) => Hook.fromJSON(org, i), - body: hook.toJSON()) as Future; + Future createHook( + String org, + CreateHook hook, + ) { + return github.postJSON('/orgs/$org/hooks', + convert: (Map i) => Hook.fromJson(i)..repoName = org, + body: GitHubJson.encode(hook)); } // TODO: Implement editHook: https://developer.github.com/v3/orgs/hooks/#edit-a-hook @@ -307,18 +363,27 @@ class OrganizationsService extends Service { /// Pings the organization hook. /// /// API docs: https://developer.github.com/v3/orgs/hooks/#ping-a-hook - Future pingHook(String org, int id) { - return _github - .request("POST", "/orgs/${org}/hooks/${id}/pings") - .then((response) => response.statusCode == 204); + Future pingHook( + String org, + int id, + ) { + return github + .request( + 'POST', + '/orgs/$org/hooks/$id/pings', + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } /// Deletes the specified hook. Future deleteHook(String org, int id) { - return _github - .request("DELETE", "/orgs/${org}/hooks/${id}") + return github + .request( + 'DELETE', + '/orgs/$org/hooks/$id', + ) .then((response) { - return response.statusCode == 204; + return response.statusCode == StatusCodes.NO_CONTENT; }); } } diff --git a/lib/src/common/pulls_service.dart b/lib/src/common/pulls_service.dart index 5e633a64..7fee2bf9 100644 --- a/lib/src/common/pulls_service.dart +++ b/lib/src/common/pulls_service.dart @@ -1,53 +1,77 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; + +import 'package:github/src/common.dart'; /// The [PullRequestsService] handles communication with pull request /// methods of the GitHub API. /// /// API docs: https://developer.github.com/v3/pulls/ class PullRequestsService extends Service { - PullRequestsService(GitHub github) : super(github); + PullRequestsService(super.github); - Stream list(RepositorySlug slug, {int pages}) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/pulls", PullRequest.fromJSON, - pages: pages) as Stream; + /// Fetches several pull requests. + /// + /// API docs: https://developer.github.com/v3/pulls/#list-pull-requests + Stream list( + RepositorySlug slug, { + int? pages, + String? base, + String direction = 'desc', + String? head, + String sort = 'created', + String state = 'open', + }) { + final params = {}; + putValue('base', base, params); + putValue('direction', direction, params); + putValue('head', head, params); + putValue('sort', sort, params); + putValue('state', state, params); + + return PaginationHelper(github).objects( + 'GET', '/repos/${slug.fullName}/pulls', PullRequest.fromJson, + pages: pages, params: params); } /// Fetches a single pull request. /// /// API docs: https://developer.github.com/v3/pulls/#get-a-single-pull-request - Future get(RepositorySlug slug, int number) { - return _github.getJSON("/repos/${slug.fullName}/pulls/${number}", - convert: PullRequest.fromJSON, - statusCode: StatusCodes.OK) as Future; - } + Future get(RepositorySlug slug, int number) => + github.getJSON('/repos/${slug.fullName}/pulls/$number', + convert: PullRequest.fromJson, statusCode: StatusCodes.OK); /// Creates a Pull Request based on the given [request]. /// /// API docs: https://developer.github.com/v3/pulls/#create-a-pull-request - Future create( - RepositorySlug slug, CreateRelease request) { - return _github.postJSON("/repos/${slug.fullName}/pulls", - convert: PullRequestInformation.fromJSON, - body: request.toJSON()) as Future; + Future create(RepositorySlug slug, CreatePullRequest request) { + return github.postJSON( + '/repos/${slug.fullName}/pulls', + convert: PullRequest.fromJson, + body: GitHubJson.encode(request), + preview: request.draft! + ? 'application/vnd.github.shadow-cat-preview+json' + : null, + ); } /// Edit a pull request. /// /// API docs: https://developer.github.com/v3/pulls/#update-a-pull-request Future edit(RepositorySlug slug, int number, - {String title, String body, String state}) { - var map = {}; - putValue("title", title, map); - putValue("body", body, map); - putValue("state", state, map); - - return _github - .request("POST", '/repos/${slug.fullName}/pulls/${number}', - body: JSON.encode(map)) + {String? title, String? body, String? state, String? base}) { + final map = {}; + putValue('title', title, map); + putValue('body', body, map); + putValue('state', state, map); + putValue('base', base, map); + + return github + .request('POST', '/repos/${slug.fullName}/pulls/$number', + body: GitHubJson.encode(map)) .then((response) { - return PullRequest - .fromJSON(JSON.decode(response.body) as Map); + return PullRequest.fromJson( + jsonDecode(response.body) as Map); }); } @@ -55,22 +79,35 @@ class PullRequestsService extends Service { /// /// API docs: https://developer.github.com/v3/pulls/#list-commits-on-a-pull-request Stream listCommits(RepositorySlug slug, int number) { - return new PaginationHelper(_github).objects( - "GET", - '/repos/${slug.fullName}/pulls/${number}/commits', - RepositoryCommit.fromJSON) as Stream; + return PaginationHelper(github).objects( + 'GET', + '/repos/${slug.fullName}/pulls/$number/commits', + RepositoryCommit.fromJson); } - Stream listFiles(RepositorySlug slug, int number) { - return new PaginationHelper(_github).objects( - "GET", - '/repos/${slug.fullName}/pulls/${number}/files', - PullRequestFile.fromJSON) as Stream; + /// Lists the files in a pull request. + /// + /// API docs: https://developer.github.com/v3/pulls/#list-pull-requests-files + Stream listFiles(RepositorySlug slug, int number) { + return PaginationHelper(github).objects( + 'GET', + '/repos/${slug.fullName}/pulls/$number/files', + PullRequestFile.fromJson); + } + + /// Lists the reviews for a pull request. + /// + /// API docs: https://docs.github.com/en/rest/reference/pulls#list-reviews-for-a-pull-request + Stream listReviews(RepositorySlug slug, int number) { + return PaginationHelper(github).objects( + 'GET', + '/repos/${slug.fullName}/pulls/$number/reviews', + PullRequestReview.fromJson); } Future isMerged(RepositorySlug slug, int number) { - return _github - .request("GET", "/repos/${slug.fullName}/pulls/${number}/merge") + return github + .request('GET', '/repos/${slug.fullName}/pulls/$number/merge') .then((response) { return response.statusCode == 204; }); @@ -79,20 +116,34 @@ class PullRequestsService extends Service { /// Merge a pull request (Merge Button). /// /// API docs: https://developer.github.com/v3/pulls/#merge-a-pull-request-merge-button - Future merge(RepositorySlug slug, int number, - {String message}) { - var json = {}; + Future merge( + RepositorySlug slug, + int number, { + String? message, + MergeMethod mergeMethod = MergeMethod.merge, + String? requestSha, + }) { + final json = {}; if (message != null) { json['commit_message'] = message; } + if (requestSha != null) { + json['sha'] = requestSha; + } + + json['merge_method'] = mergeMethod.name; - return _github - .request("PUT", "/repos/${slug.fullName}/pulls/${number}/merge", - body: JSON.encode(json)) + // Recommended Accept header when making a merge request. + Map? headers = {}; + headers['Accept'] = 'application/vnd.github+json'; + + return github + .request('PUT', '/repos/${slug.fullName}/pulls/$number/merge', + headers: headers, body: GitHubJson.encode(json)) .then((response) { - return PullRequestMerge - .fromJSON(JSON.decode(response.body) as Map); + return PullRequestMerge.fromJson( + jsonDecode(response.body) as Map); }); } @@ -101,33 +152,49 @@ class PullRequestsService extends Service { /// API docs: https://developer.github.com/v3/pulls/comments/#list-comments-on-a-pull-request Stream listCommentsByPullRequest( RepositorySlug slug, int number) { - return new PaginationHelper(_github).objects( - "GET", - "/repos/${slug.fullName}/pulls/${number}/comments", - PullRequestComment.fromJSON) as Stream; + return PaginationHelper(github).objects( + 'GET', + '/repos/${slug.fullName}/pulls/$number/comments', + PullRequestComment.fromJson); } /// Lists all comments on all pull requests for the repository. /// /// API docs: https://developer.github.com/v3/pulls/comments/#list-comments-in-a-repository Stream listComments(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", - "/repos/${slug.fullName}/pulls/comments", - PullRequestComment.fromJSON) as Stream; + return PaginationHelper(github).objects('GET', + '/repos/${slug.fullName}/pulls/comments', PullRequestComment.fromJson); } /// Creates a new pull request comment. /// /// API docs: https://developer.github.com/v3/pulls/comments/#create-a-comment - Future createComment( + Future createComment( RepositorySlug slug, int number, CreatePullRequestComment comment) { - return _github.postJSON('/repos/${slug.fullName}/pulls/${number}/comments', - body: comment.toJSON(), - convert: PullRequestComment.fromJSON, - statusCode: 201) as Future; + return github.postJSON('/repos/${slug.fullName}/pulls/$number/comments', + body: GitHubJson.encode(comment.toJson()), + convert: PullRequestComment.fromJson, + statusCode: 201); } // TODO: Implement editComment: https://developer.github.com/v3/pulls/comments/#edit-a-comment // TODO: Implement deleteComment: https://developer.github.com/v3/pulls/comments/#delete-a-comment + + /// Creates a new pull request comment. + /// + /// API docs: https://developer.github.com/v3/pulls/comments/#create-a-comment + Future createReview( + RepositorySlug slug, CreatePullRequestReview review) { + return github.postJSON( + '/repos/${slug.fullName}/pulls/${review.pullNumber}/reviews', + body: GitHubJson.encode(review), + convert: PullRequestReview.fromJson, + ); + } +} + +enum MergeMethod { + merge, + squash, + rebase, } diff --git a/lib/src/common/repos_service.dart b/lib/src/common/repos_service.dart index e93be18c..83fa5ef0 100644 --- a/lib/src/common/repos_service.dart +++ b/lib/src/common/repos_service.dart @@ -1,52 +1,73 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; + +import 'package:github/src/common.dart'; +import 'package:http/http.dart' as http; /// The [RepositoriesService] handles communication with repository related /// methods of the GitHub API. /// /// API docs: https://developer.github.com/v3/repos/ class RepositoriesService extends Service { - RepositoriesService(GitHub github) : super(github); + RepositoriesService(super.github); /// Lists the repositories of the currently authenticated user. /// /// API docs: https://developer.github.com/v3/repos/#list-your-repositories Stream listRepositories( - {String type: "owner", - String sort: "full_name", - String direction: "asc"}) { - var params = {"type": type, "sort": sort, "direction": direction}; + {String type = 'owner', + String sort = 'full_name', + String direction = 'asc'}) { + final params = { + 'type': type, + 'sort': sort, + 'direction': direction, + }; - return new PaginationHelper(_github) - .objects("GET", "/user/repos", Repository.fromJSON, params: params) - as Stream; + return PaginationHelper(github).objects, Repository>( + 'GET', + '/user/repos', + Repository.fromJson, + params: params, + ); } /// Lists the repositories of the user specified by [user] in a streamed fashion. /// - /// API docs: https://developer.github.com/v3/repos/#list-user-repositories + /// API docs: https://developer.github.com/v3/repos/#list-repositories-for-a-user Stream listUserRepositories(String user, - {String type: "owner", - String sort: "full_name", - String direction: "asc"}) { - var params = {"type": type, "sort": sort, "direction": direction}; + {String type = 'owner', + String sort = 'full_name', + String direction = 'asc'}) { + ArgumentError.checkNotNull(user); + final params = { + 'type': type, + 'sort': sort, + 'direction': direction + }; - return new PaginationHelper(_github).objects( - "GET", "/users/${user}/repos", Repository.fromJSON, params: params) - as Stream; + return PaginationHelper(github).objects, Repository>( + 'GET', + '/users/$user/repos', + Repository.fromJson, + params: params, + ); } /// List repositories for the specified [org]. /// - /// API docs: https://developer.github.com/v3/repos/#list-user-repositories + /// API docs: https://developer.github.com/v3/repos/#list-organization-repositories Stream listOrganizationRepositories(String org, - {String type: "all"}) { - var params = { - "type": type, - }; - - return new PaginationHelper(_github).objects( - "GET", "/orgs/${org}/repos", Repository.fromJSON, params: params) - as Stream; + {String type = 'all'}) { + ArgumentError.checkNotNull(org); + final params = {'type': type}; + + return PaginationHelper(github).objects, Repository>( + 'GET', + '/orgs/$org/repos', + Repository.fromJson, + params: params, + ); } /// Lists all the public repositories on GitHub, in the order that they were @@ -56,28 +77,22 @@ class RepositoriesService extends Service { /// If [limit] is null, it will fetch ALL the repositories on GitHub. /// /// API docs: https://developer.github.com/v3/repos/#list-all-public-repositories - Stream listPublicRepositories({int limit: 50, DateTime since}) { - var params = {}; + Stream listPublicRepositories({int limit = 50, DateTime? since}) { + final params = {}; if (since != null) { params['since'] = since.toIso8601String(); } - var pages = limit != null ? (limit / 30).ceil() : null; + final pages = (limit / 30).ceil(); - // TODO: Close this, but where? - var controller = new StreamController.broadcast(); + return PaginationHelper(github) + .fetchStreamed('GET', '/repositories', pages: pages, params: params) + .expand((http.Response response) { + final list = jsonDecode(response.body) as List>; - new PaginationHelper(_github) - .fetchStreamed("GET", "/repositories", pages: pages, params: params) - .listen((http.Response response) { - var list = JSON.decode(response.body); - var repos = new List.from( - list.map((Map it) => Repository.fromJSON(it))); - for (var repo in repos) controller.add(repo); + return list.map(Repository.fromJson); }); - - return controller.stream.take(limit); } /// Creates a repository with [repository]. If an [org] is specified, the new @@ -86,75 +101,83 @@ class RepositoriesService extends Service { /// /// API docs: https://developer.github.com/v3/repos/#create Future createRepository(CreateRepository repository, - {String org}) { + {String? org}) async { + ArgumentError.checkNotNull(repository); if (org != null) { - return _github.postJSON('/orgs/${org}/repos', - body: repository.toJSON(), - convert: TeamRepository.fromJSON) as Future; + return github.postJSON, Repository>( + '/orgs/$org/repos', + body: GitHubJson.encode(repository), + convert: Repository.fromJson, + ); } else { - return _github.postJSON('/user/repos', - body: repository.toJSON(), - convert: Repository.fromJSON) as Future; + return github.postJSON, Repository>( + '/user/repos', + body: GitHubJson.encode(repository), + convert: Repository.fromJson, + ); } } + Future getLicense(RepositorySlug slug) async { + ArgumentError.checkNotNull(slug); + return github.getJSON, LicenseDetails>( + '/repos/${slug.owner}/${slug.name}/license', + convert: LicenseDetails.fromJson, + ); + } + /// Fetches the repository specified by the [slug]. /// /// API docs: https://developer.github.com/v3/repos/#get - Future getRepository(RepositorySlug slug) { - return _github.getJSON("/repos/${slug.owner}/${slug.name}", - convert: Repository.fromJSON, - statusCode: StatusCodes.OK, fail: (http.Response response) { - if (response.statusCode == 404) { - throw new RepositoryNotFound(_github, slug.fullName); - } - }) as Future; + Future getRepository(RepositorySlug slug) async { + ArgumentError.checkNotNull(slug); + return github.getJSON, Repository>( + '/repos/${slug.owner}/${slug.name}', + convert: Repository.fromJson, + statusCode: StatusCodes.OK, + fail: (http.Response response) { + if (response.statusCode == 404) { + throw RepositoryNotFound(github, slug.fullName); + } + }, + ); } /// Fetches a list of repositories specified by [slugs]. - Stream getRepositories(List slugs) { - var controller = new StreamController(); - - var group = new FutureGroup(); - - for (var slug in slugs) { - group.add(getRepository(slug).then((repo) { - controller.add(repo); - })); + Stream getRepositories(List slugs) async* { + for (final slug in slugs) { + final repo = await getRepository(slug); + yield repo; } - - group.future.then((_) { - controller.close(); - }); - - return controller.stream; } /// Edit a Repository. /// /// API docs: https://developer.github.com/v3/repos/#edit - Future editRepository(RepositorySlug repo, - {String name, - String description, - String homepage, - bool private, - bool hasIssues, - bool hasWiki, - bool hasDownloads}) { - var data = createNonNullMap({ - "name": name, - "description": description, - "homepage": homepage, - "private": private, - "has_issues": hasIssues, - "has_wiki": hasWiki, - "has_downloads": hasDownloads, - "default_branch": "defaultBranch" + Future editRepository(RepositorySlug slug, + {String? name, + String? description, + String? homepage, + bool? private, + bool? hasIssues, + bool? hasWiki, + bool? hasDownloads}) async { + ArgumentError.checkNotNull(slug); + final data = createNonNullMap({ + 'name': name!, + 'description': description!, + 'homepage': homepage!, + 'private': private!, + 'has_issues': hasIssues!, + 'has_wiki': hasWiki!, + 'has_downloads': hasDownloads!, + 'default_branch': 'defaultBranch' }); - return _github.postJSON("/repos/${repo.fullName}", - // TODO: data probably needs to be json encoded? - body: data, - statusCode: 200) as Future; + return github.postJSON( + '/repos/${slug.fullName}', + body: GitHubJson.encode(data), + statusCode: 200, + ); } /// Deletes a repository. @@ -162,121 +185,352 @@ class RepositoriesService extends Service { /// Returns true if it was successfully deleted. /// /// API docs: https://developer.github.com/v3/repos/#delete-a-repository - Future deleteRepository(RepositorySlug slug) { - return _github - .request('DELETE', '/repos/${slug.fullName}') + Future deleteRepository(RepositorySlug slug) async { + ArgumentError.checkNotNull(slug); + return github + .request( + 'DELETE', + '/repos/${slug.fullName}', + statusCode: StatusCodes.NO_CONTENT, + ) .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } /// Lists the contributors of the specified repository. /// /// API docs: https://developer.github.com/v3/repos/#list-contributors - Stream listContributors(RepositorySlug slug, {bool anon: false}) { - return new PaginationHelper(_github).objects( - 'GET', '/repos/${slug.fullName}/contributors', User.fromJSON, - params: {"anon": anon.toString()}) as Stream; + Stream listContributors(RepositorySlug slug, + {bool anon = false}) { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(anon); + return PaginationHelper(github).objects, Contributor>( + 'GET', + '/repos/${slug.fullName}/contributors', + Contributor.fromJson, + params: {'anon': anon.toString()}, + ); } /// Lists the teams of the specified repository. /// /// API docs: https://developer.github.com/v3/repos/#list-teams Stream listTeams(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - 'GET', '/repos/${slug.fullName}/teams', Team.fromJSON) as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, Team>( + 'GET', + '/repos/${slug.fullName}/teams', + Team.fromJson, + ); } /// Gets a language breakdown for the specified repository. /// /// API docs: https://developer.github.com/v3/repos/#list-languages - Future listLanguages(RepositorySlug slug) => - _github.getJSON("/repos/${slug.fullName}/languages", - statusCode: StatusCodes.OK, - convert: (Map input) => new LanguageBreakdown(input)) - as Future; + Future listLanguages(RepositorySlug slug) async { + ArgumentError.checkNotNull(slug); + return github.getJSON, LanguageBreakdown>( + '/repos/${slug.fullName}/languages', + statusCode: StatusCodes.OK, + convert: (input) => LanguageBreakdown(input.cast()), + ); + } /// Lists the tags of the specified repository. /// /// API docs: https://developer.github.com/v3/repos/#list-tags - Stream listTags(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - 'GET', '/repos/${slug.fullName}/tags', Tag.fromJSON) as Stream; + Stream listTags(RepositorySlug slug, + {int page = 1, int? pages, int perPage = 30}) { + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, Tag>( + 'GET', '/repos/${slug.fullName}/tags', Tag.fromJson, + pages: pages, params: {'page': page, 'per_page': perPage}); } /// Lists the branches of the specified repository. /// /// API docs: https://developer.github.com/v3/repos/#list-branches Stream listBranches(RepositorySlug slug) { - return new PaginationHelper(_github) - .objects('GET', '/repos/${slug.fullName}/branches', Branch.fromJSON) - as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, Branch>( + 'GET', + '/repos/${slug.fullName}/branches', + Branch.fromJson, + ); } /// Fetches the specified branch. /// /// API docs: https://developer.github.com/v3/repos/#get-branch - Future getBranch(RepositorySlug slug, String branch) { - return _github.getJSON("/repos/${slug.fullName}/branches/${branch}", - convert: Branch.fromJSON) as Future; + Future getBranch(RepositorySlug slug, String branch) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(branch); + return github.getJSON, Branch>( + '/repos/${slug.fullName}/branches/$branch', + convert: Branch.fromJson, + ); } /// Lists the users that have access to the repository identified by [slug]. /// /// API docs: https://developer.github.com/v3/repos/collaborators/#list - Stream listCollaborators(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/collaborators", User.fromJSON) - as Stream; + Stream listCollaborators(RepositorySlug slug) { + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, Collaborator>( + 'GET', + '/repos/${slug.fullName}/collaborators', + Collaborator.fromJson, + ); } - Future isCollaborator(RepositorySlug slug, String user) { - return _github - .request("GET", "/repos/${slug.fullName}/collaborators/${user}") - .then((response) { - return response.statusCode == 204; - }); + Future isCollaborator(RepositorySlug slug, String user) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(user); + var catchError = false; + http.Response response; + try { + response = await github.request( + 'GET', + '/repos/${slug.fullName}/collaborators/$user', + statusCode: StatusCodes.NO_CONTENT, + fail: (response) { + if (response.statusCode == StatusCodes.NOT_FOUND) { + catchError = true; + } + }, + ); + if (response.statusCode == StatusCodes.NO_CONTENT) { + return true; + } + } catch (e) { + if (!catchError) { + rethrow; + } + } + return false; } - Future addCollaborator(RepositorySlug slug, String user) { - return _github - .request("PUT", "/repos/${slug.fullName}/collaborators/${user}") - .then((response) { - return response.statusCode == 204; - }); + Future addCollaborator(RepositorySlug slug, String user) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(user); + return github + .request( + 'PUT', + '/repos/${slug.fullName}/collaborators/$user', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); + } + + Future removeCollaborator(RepositorySlug slug, String user) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(user); + return github + .request( + 'DELETE', + '/repos/${slug.fullName}/collaborators/$user', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); + } + + /// Returns a list of all comments for a specific commit. + /// + /// https://developer.github.com/v3/repos/comments/#list-comments-for-a-single-commit + Stream listSingleCommitComments( + RepositorySlug slug, + RepositoryCommit commit, + ) { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(commit); + return PaginationHelper(github) + .objects, CommitComment>( + 'GET', + '/repos/${slug.fullName}/commits/${commit.sha}/comments', + CommitComment.fromJson, + statusCode: StatusCodes.OK, + ); + } + + /// Returns a list of all commit comments in a repository. + /// + /// https://developer.github.com/v3/repos/comments/#list-commit-comments-for-a-repository + Stream listCommitComments(RepositorySlug slug) { + ArgumentError.checkNotNull(slug); + return PaginationHelper(github) + .objects, CommitComment>( + 'GET', + 'repos/${slug.fullName}/comments', + CommitComment.fromJson, + statusCode: StatusCodes.OK, + ); } - Future removeCollaborator(RepositorySlug slug, String user) { - return _github - .request("DELETE", "/repos/${slug.fullName}/collaborators/${user}") - .then((response) { - return response.statusCode == 204; + /// Create a comment for a commit using its sha. + /// * [body]: The contents of the comment. + /// * [path]: Relative path of the file to comment on. + /// * [position]: Line index in the diff to comment on. + /// * [line]: **Deprecated**. Use position parameter instead. Line number in the file to comment on. + /// + /// https://developer.github.com/v3/repos/comments/#create-a-commit-comment + Future createCommitComment( + RepositorySlug slug, + RepositoryCommit commit, { + required String body, + String? path, + int? position, + @Deprecated('Use position parameter instead') int? line, + }) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(commit); + final data = createNonNullMap({ + 'body': body, + 'path': path!, + 'position': position!, + 'line': line!, }); + return github.postJSON, CommitComment>( + '/repos/${slug.fullName}/commits/${commit.sha}/comments', + body: GitHubJson.encode(data), + statusCode: StatusCodes.CREATED, + convert: CommitComment.fromJson, + ); + } + + /// Retrieve a commit comment by its id. + /// + /// https://developer.github.com/v3/repos/comments/#get-a-single-commit-comment + Future getCommitComment(RepositorySlug slug, + {required int id}) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(id); + return github.getJSON, CommitComment>( + '/repos/${slug.fullName}/comments/$id', + statusCode: StatusCodes.OK, + convert: CommitComment.fromJson, + ); } - // TODO: Implement listComments: https://developer.github.com/v3/repos/comments/#list-commit-comments-for-a-repository - // TODO: Implement listCommitComments: https://developer.github.com/v3/repos/comments/#list-comments-for-a-single-commit - // TODO: Implement createComment: https://developer.github.com/v3/repos/comments/#create-a-commit-comment - // TODO: Implement getComment: https://developer.github.com/v3/repos/comments/#get-a-single-commit-comment - // TODO: Implement updateComment: https://developer.github.com/v3/repos/comments/#update-a-commit-comment - // TODO: Implement deleteComment: https://developer.github.com/v3/repos/comments/#delete-a-commit-comment + /// Update a commit comment + /// * [id]: id of the comment to update. + /// * [body]: new body of the comment. + /// + /// Returns the updated commit comment. + /// + /// https://developer.github.com/v3/repos/comments/#update-a-commit-comment + Future updateCommitComment(RepositorySlug slug, + {required int id, required String body}) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(id); + ArgumentError.checkNotNull(body); + return github.postJSON, CommitComment>( + '/repos/${slug.fullName}/comments/$id', + body: GitHubJson.encode(createNonNullMap({'body': body})), + statusCode: StatusCodes.OK, + convert: CommitComment.fromJson, + ); + } + + /// Delete a commit comment. + /// *[id]: id of the comment to delete. + /// + /// https://developer.github.com/v3/repos/comments/#delete-a-commit-comment + Future deleteCommitComment(RepositorySlug slug, + {required int id}) async { + ArgumentError.checkNotNull(slug); + return github + .request( + 'DELETE', + '/repos/${slug.fullName}/comments/$id', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); + } /// Lists the commits of the provided repository [slug]. /// + /// [sha] is the SHA or branch to start listing commits from. Default: the + /// repository’s default branch (usually main). + /// + /// [path] will only show commits that changed that file path. + /// + /// [author] and [committer] are the GitHub username to filter commits for. + /// + /// [since] shows commit after this time, and [until] shows commits before + /// this time. + /// /// API docs: https://developer.github.com/v3/repos/commits/#list-commits-on-a-repository - Stream listCommits(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/commits", RepositoryCommit.fromJSON) - as Stream; + Stream listCommits( + RepositorySlug slug, { + String? sha, + String? path, + String? author, + String? committer, + DateTime? since, + DateTime? until, + }) { + ArgumentError.checkNotNull(slug); + final params = { + if (author != null) 'author': author, + if (committer != null) 'committer': committer, + if (sha != null) 'sha': sha, + if (path != null) 'path': path, + if (since != null) 'since': since.toIso8601String(), + if (until != null) 'until': until.toIso8601String(), + }; + return PaginationHelper(github) + .objects, RepositoryCommit>( + 'GET', + '/repos/${slug.fullName}/commits', + RepositoryCommit.fromJson, + params: params, + ); } /// Fetches the specified commit. /// /// API docs: https://developer.github.com/v3/repos/commits/#get-a-single-commit - Future getCommit(RepositorySlug slug, String sha) { - return _github.getJSON("/repos/${slug.fullName}/commits/${sha}", - convert: RepositoryCommit.fromJSON) as Future; + Future getCommit(RepositorySlug slug, String sha) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(sha); + return github.getJSON, RepositoryCommit>( + '/repos/${slug.fullName}/commits/$sha', + convert: RepositoryCommit.fromJson, + statusCode: StatusCodes.OK, + ); + } + + Future getCommitDiff(RepositorySlug slug, String sha) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(sha); + return github + .request( + 'GET', + '/repos/${slug.fullName}/commits/$sha', + headers: { + 'Accept': 'application/vnd.github.VERSION.diff' + }, + statusCode: StatusCodes.OK, + ) + .then((r) => r.body); } - // TODO: Implement compareCommits: https://developer.github.com/v3/repos/commits/#compare-two-commits + /// [refBase] and [refHead] can be the same value for a branch, commit, or ref + /// in [slug] or specify other repositories by using `repo:ref` syntax. + /// + /// API docs: https://developer.github.com/v3/repos/commits/#compare-two-commits + Future compareCommits( + RepositorySlug slug, + String refBase, + String refHead, + ) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(refBase); + ArgumentError.checkNotNull(refHead); + return github.getJSON, GitHubComparison>( + '/repos/${slug.fullName}/compare/$refBase...$refHead', + convert: GitHubComparison.fromJson, + ); + } /// Fetches the readme file for a repository. /// @@ -284,23 +538,26 @@ class RepositoriesService extends Service { /// is defined, the repository's default branch is used (usually master). /// /// API docs: https://developer.github.com/v3/repos/contents/#get-the-readme - Future getReadme(RepositorySlug slug, {String ref}) { - var headers = {}; + Future getReadme(RepositorySlug slug, {String? ref}) async { + ArgumentError.checkNotNull(slug); + final headers = {}; - String url = "/repos/${slug.fullName}/readme"; + var url = '/repos/${slug.fullName}/readme'; if (ref != null) { url += '?ref=$ref'; } - return _github.getJSON(url, headers: headers, statusCode: StatusCodes.OK, + return github.getJSON(url, headers: headers, statusCode: StatusCodes.OK, fail: (http.Response response) { - if (response.statusCode == 404) { - throw new NotFound(_github, response.body); + if (response.statusCode == StatusCodes.NOT_FOUND) { + throw NotFound(github, response.body); } - }, - convert: (Map input) => - GitHubFile.fromJSON(input, slug)) as Future; + }, convert: (Map input) { + var file = GitHubFile.fromJson(input); + file.sourceRepository = slug; + return file; + }); } /// Fetches content in a repository at the specified [path]. @@ -319,37 +576,53 @@ class RepositoriesService extends Service { /// /// API docs: https://developer.github.com/v3/repos/contents/#get-contents Future getContents(RepositorySlug slug, String path, - {String ref}) { - String url = "/repos/${slug.fullName}/contents/${path}"; + {String? ref}) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(path); + var url = '/repos/${slug.fullName}/contents/$path'; if (ref != null) { url += '?ref=$ref'; } - return _github.getJSON(url, convert: (input) { - var contents = new RepositoryContents(); - if (input is Map) { - contents.file = GitHubFile.fromJSON(input as Map); - } else { - contents.tree = (input as List>) - .map((Map it) => GitHubFile.fromJSON(it)) - .toList(); - } - return contents; - }) as Future; + return github.getJSON( + url, + convert: (dynamic input) { + final contents = RepositoryContents(); + if (input is Map) { + // Weird one-off. If the content of `input` is JSON w/ a message + // it was likely a 404 – but we don't have the status code here + // But we can guess an the JSON content + if (input.containsKey('message')) { + throw GitHubError(github, input['message'], + apiUrl: input['documentation_url']); + } + contents.file = GitHubFile.fromJson(input as Map); + } else { + contents.tree = (input as List) + .cast>() + .map(GitHubFile.fromJson) + .toList(); + } + return contents; + }, + ); } /// Creates a new file in a repository. /// /// API docs: https://developer.github.com/v3/repos/contents/#create-a-file - Future createFile(RepositorySlug slug, CreateFile file) { - return _github - .request("PUT", "/repos/${slug.fullName}/contents/${file.path}", - body: file.toJSON()) - .then((response) { - return ContentCreation - .fromJSON(JSON.decode(response.body) as Map); - }); + Future createFile( + RepositorySlug slug, CreateFile file) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(file); + final response = await github.request( + 'PUT', + '/repos/${slug.fullName}/contents/${file.path}', + body: GitHubJson.encode(file), + ); + return ContentCreation.fromJson( + jsonDecode(response.body) as Map); } /// Updates the specified file. @@ -357,121 +630,213 @@ class RepositoriesService extends Service { /// API docs: https://developer.github.com/v3/repos/contents/#update-a-file Future updateFile(RepositorySlug slug, String path, String message, String content, String sha, - {String branch}) { - var map = createNonNullMap( - {"message": message, "content": content, "sha": sha, "branch": branch}); - - return _github.postJSON("/repos/${slug.fullName}/contents/${path}", - // TODO: map probably needs to be json encoded - body: map, - statusCode: 200, - convert: ContentCreation.fromJSON) as Future; + {String? branch}) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(path); + final map = createNonNullMap({ + 'message': message, + 'content': content, + 'sha': sha, + 'branch': branch!, + }); + final response = await github.request( + 'PUT', + '/repos/${slug.fullName}/contents/$path', + body: GitHubJson.encode(map), + ); + return ContentCreation.fromJson( + jsonDecode(response.body) as Map); } /// Deletes the specified file. /// /// API docs: https://developer.github.com/v3/repos/contents/#delete-a-file Future deleteFile(RepositorySlug slug, String path, - String message, String sha, String branch) { - var map = - createNonNullMap({"message": message, "sha": sha, "branch": branch}); - - return _github - .request("DELETE", "/repos/${slug.fullName}/contents/${path}", - body: JSON.encode(map), statusCode: 200) - .then((response) { - return ContentCreation - .fromJSON(JSON.decode(response.body) as Map); - }); + String message, String sha, String branch) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(path); + final map = + createNonNullMap({'message': message, 'sha': sha, 'branch': branch}); + final response = await github.request( + 'DELETE', + '/repos/${slug.fullName}/contents/$path', + body: GitHubJson.encode(map), + statusCode: StatusCodes.OK, + ); + return ContentCreation.fromJson( + jsonDecode(response.body) as Map); } /// Gets an archive link for the specified repository and reference. /// /// API docs: https://developer.github.com/v3/repos/contents/#get-archive-link - Future getArchiveLink(RepositorySlug slug, String ref, - {String format: "tarball"}) { - return _github - .request("GET", "/repos/${slug.fullName}/${format}/${ref}", - statusCode: 302) - .then((response) { - return response.headers["Location"]; - }); + Future getArchiveLink(RepositorySlug slug, String ref, + {String format = 'tarball'}) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(ref); + ArgumentError.checkNotNull(format); + final response = await github.request( + 'GET', + '/repos/${slug.fullName}/$format/$ref', + statusCode: StatusCodes.FOUND, + ); + return response.headers['Location']; } /// Lists the forks of the specified repository. /// /// API docs: https://developer.github.com/v3/repos/forks/#list-forks Stream listForks(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/forks", Repository.fromJSON) - as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, Repository>( + 'GET', + '/repos/${slug.fullName}/forks', + Repository.fromJson, + ); } /// Creates a fork for the authenticated user. /// /// API docs: https://developer.github.com/v3/repos/forks/#create-a-fork - Future createFork(RepositorySlug slug, [CreateFork fork]) { - if (fork == null) fork = new CreateFork(); - return _github.postJSON("/repos/${slug.fullName}/forks", - body: fork.toJSON(), - convert: Repository.fromJSON) as Future; + Future createFork(RepositorySlug slug, [CreateFork? fork]) async { + ArgumentError.checkNotNull(slug); + fork ??= CreateFork(); + return github.postJSON, Repository>( + '/repos/${slug.fullName}/forks', + body: GitHubJson.encode(fork), + convert: Repository.fromJson, + ); } /// Lists the hooks of the specified repository. /// /// API docs: https://developer.github.com/v3/repos/hooks/#list-hooks Stream listHooks(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", - "/repos/${slug.fullName}/hooks", - (Map input) => Hook.fromJSON(slug.fullName, input)) - as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, Hook>( + 'GET', + '/repos/${slug.fullName}/hooks', + (i) => Hook.fromJson(i)..repoName = slug.fullName, + ); } /// Fetches a single hook by [id]. /// /// API docs: https://developer.github.com/v3/repos/hooks/#get-single-hook - Future getHook(RepositorySlug slug, int id) { - return _github.getJSON("/repos/${slug.fullName}/hooks/${id}", - convert: (Map i) => - Hook.fromJSON(slug.fullName, i)) as Future; + Future getHook(RepositorySlug slug, int id) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(id); + return github.getJSON, Hook>( + '/repos/${slug.fullName}/hooks/$id', + convert: (i) => Hook.fromJson(i)..repoName = slug.fullName, + ); } /// Creates a repository hook based on the specified [hook]. /// /// API docs: https://developer.github.com/v3/repos/hooks/#create-a-hook - Future createHook(RepositorySlug slug, CreateHook hook) { - return _github.postJSON("/repos/${slug.fullName}/hooks", - convert: (Map i) => Hook.fromJSON(slug.fullName, i), - body: hook.toJSON()) as Future; + Future createHook(RepositorySlug slug, CreateHook hook) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(hook); + return github.postJSON, Hook>( + '/repos/${slug.fullName}/hooks', + convert: (i) => Hook.fromJson(i)..repoName = slug.fullName, + body: GitHubJson.encode(hook), + ); } - // TODO: Implement editHook: https://developer.github.com/v3/repos/hooks/#edit-a-hook + /// Edits a hook. + /// * [configUrl]: The URL to which the payloads will be delivered. + /// * [configContentType]: The media type used to serialize the payloads. Supported values include json and form. The default is form. + /// * [configSecret]: If provided, the secret will be used as the key to generate the HMAC hex digest value in the X-Hub-Signature header. + /// * [configInsecureSsl]: Determines whether the SSL certificate of the host for url will be verified when delivering payloads. We strongly recommend not setting this to true as you are subject to man-in-the-middle and other attacks. + /// * [events]: Determines what events the hook is triggered for. This replaces the entire array of events. Default: ['push']. + /// * [addEvents]: Determines a list of events to be added to the list of events that the Hook triggers for. + /// * [removeEvents]: Determines a list of events to be removed from the list of events that the Hook triggers for. + /// * [active]: Determines if notifications are sent when the webhook is triggered. Set to true to send notifications. + /// + /// Leave blank the unedited fields. + /// Returns the edited hook. + /// + /// https://developer.github.com/v3/repos/hooks/#edit-a-hook + Future editHook( + RepositorySlug slug, + Hook hookToEdit, { + String? configUrl, + String? configContentType, + String? configSecret, + bool? configInsecureSsl, + List? events, + List? addEvents, + List? removeEvents, + bool? active, + }) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(hookToEdit); + ArgumentError.checkNotNull(configUrl ?? hookToEdit.config!.url); + if (configContentType != 'json' && configContentType != 'form') { + throw ArgumentError.value(configContentType, 'configContentType'); + } + return github.postJSON, Hook>( + '/repos/${slug.fullName}/hooks/${hookToEdit.id.toString()}', + statusCode: StatusCodes.OK, + convert: (i) => Hook.fromJson(i)..repoName = slug.fullName, + body: GitHubJson.encode(createNonNullMap({ + 'active': active ?? hookToEdit.active, + 'events': events ?? hookToEdit.events, + 'add_events': addEvents, + 'remove_events': removeEvents, + 'config': { + 'url': configUrl ?? hookToEdit.config!.url, + 'content_type': configContentType ?? hookToEdit.config!.contentType, + 'secret': configSecret ?? hookToEdit.config!.secret, + 'insecure_ssl': + configInsecureSsl == null || !configInsecureSsl ? '0' : '1', + }, + })), + ); + } /// Triggers a hook with the latest push. /// /// API docs: https://developer.github.com/v3/repos/hooks/#test-a-push-hook - Future testPushHook(RepositorySlug slug, int id) { - return _github - .request("POST", "/repos/${slug.fullName}/hooks/${id}/tests") - .then((response) => response.statusCode == 204); + Future testPushHook(RepositorySlug slug, int id) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(id); + return github + .request( + 'POST', + '/repos/${slug.fullName}/hooks/$id/tests', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } /// Pings the hook. /// /// API docs: https://developer.github.com/v3/repos/hooks/#ping-a-hook - Future pingHook(RepositorySlug slug, int id) { - return _github - .request("POST", "/repos/${slug.fullName}/hooks/${id}/pings") - .then((response) => response.statusCode == 204); + Future pingHook(RepositorySlug slug, int id) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(id); + return github + .request( + 'POST', + '/repos/${slug.fullName}/hooks/$id/pings', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } - Future deleteHook(RepositorySlug slug, int id) { - return _github - .request("DELETE", "/repos/${slug.fullName}/hooks/${id}") - .then((response) { - return response.statusCode == 204; - }); + Future deleteHook(RepositorySlug slug, int id) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(id); + return github + .request( + 'DELETE', + '/repos/${slug.fullName}/hooks/$id', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } // TODO: Implement other hook methods: https://developer.github.com/v3/repos/hooks/ @@ -480,143 +845,422 @@ class RepositoriesService extends Service { /// /// API docs: https://developer.github.com/v3/repos/keys/#list Stream listDeployKeys(RepositorySlug slug) { - return new PaginationHelper(_github) - .objects("GET", "/repos/${slug.fullName}/keys", PublicKey.fromJSON) - as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, PublicKey>( + 'GET', + '/repos/${slug.fullName}/keys', + PublicKey.fromJson, + ); } - // TODO: Implement getDeployKey: https://developer.github.com/v3/repos/keys/#get + /// Get a deploy key. + /// * [id]: id of the key to retrieve. + /// + /// https://developer.github.com/v3/repos/keys/#get + Future getDeployKey(RepositorySlug slug, {required int id}) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(id); + return github.getJSON, PublicKey>( + '/repos/${slug.fullName}/keys/$id', + statusCode: StatusCodes.OK, + convert: PublicKey.fromJson, + ); + } /// Adds a deploy key for a repository. /// /// API docs: https://developer.github.com/v3/repos/keys/#create - Future createDeployKey(RepositorySlug slug, CreatePublicKey key) { - return _github.postJSON("/repos/${slug.fullName}/keys", body: key.toJSON()) - as Future; + Future createDeployKey( + RepositorySlug slug, CreatePublicKey key) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(key); + return github.postJSON, PublicKey>( + '/repos/${slug.fullName}/keys', + body: GitHubJson.encode(key), + statusCode: StatusCodes.CREATED, + convert: PublicKey.fromJson, + ); } - // TODO: Implement editDeployKey: https://developer.github.com/v3/repos/keys/#edit - // TODO: Implement deleteDeployKey: https://developer.github.com/v3/repos/keys/#delete + /// Delete a deploy key. + /// + /// https://developer.github.com/v3/repos/keys/#delete + Future deleteDeployKey( + {required RepositorySlug slug, required PublicKey key}) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(key); + return github + .request( + 'DELETE', + '/repos/${slug.fullName}/keys/${key.id}', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); + } /// Merges a branch in the specified repository. /// /// API docs: https://developer.github.com/v3/repos/merging/#perform-a-merge - Future merge(RepositorySlug slug, CreateMerge merge) { - return _github.postJSON("/repos/${slug.fullName}/merges", - body: merge.toJSON(), - convert: RepositoryCommit.fromJSON, - statusCode: 201) as Future; + Future merge(RepositorySlug slug, CreateMerge merge) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(merge); + return github.postJSON, RepositoryCommit>( + '/repos/${slug.fullName}/merges', + body: GitHubJson.encode(merge), + convert: RepositoryCommit.fromJson, + statusCode: StatusCodes.CREATED, + ); } /// Fetches the GitHub pages information for the specified repository. /// /// API docs: https://developer.github.com/v3/repos/pages/#get-information-about-a-pages-site - Future getPagesInfo(RepositorySlug slug) { - return _github.getJSON("/repos/${slug.fullName}/pages", - statusCode: 200, - convert: RepositoryPages.fromJSON) as Future; + Future getPagesInfo(RepositorySlug slug) async { + ArgumentError.checkNotNull(slug); + return github.getJSON, RepositoryPages>( + '/repos/${slug.fullName}/pages', + statusCode: StatusCodes.OK, + convert: RepositoryPages.fromJson, + ); + } + + /// List Pages builds. + /// + /// API docs: https://developer.github.com/v3/repos/pages/#list-pages-builds + Stream listPagesBuilds(RepositorySlug slug) { + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, PageBuild>( + 'GET', + '/repos/${slug.fullName}/pages/builds', + PageBuild.fromJson, + statusCode: StatusCodes.OK, + ); + } + + /// Get latest Pages build. + /// + /// API docs: https://developer.github.com/v3/repos/pages/#list-latest-pages-build + Future getLatestPagesBuild(RepositorySlug slug) async { + ArgumentError.checkNotNull(slug); + return github.getJSON( + '/repos/${slug.fullName}/pages/builds/latest', + convert: PageBuild.fromJson, + statusCode: StatusCodes.OK, + ); } - // TODO: Implement listPagesBuilds: https://developer.github.com/v3/repos/pages/#list-pages-builds - // TODO: Implement getLatestPagesBuild: https://developer.github.com/v3/repos/pages/#list-latest-pages-build + // Releases /// Lists releases for the specified repository. /// /// API docs: https://developer.github.com/v3/repos/releases/#list-releases-for-a-repository Stream listReleases(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", "/repos/${slug.fullName}/releases", Release.fromJSON) - as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github).objects, Release>( + 'GET', + '/repos/${slug.fullName}/releases', + Release.fromJson, + ); } - /// Fetches a single release. + /// Lists the latest release for the specified repository. + /// + /// API docs: https://developer.github.com/v3/repos/releases/#get-the-latest-release + Future getLatestRelease(RepositorySlug slug) { + ArgumentError.checkNotNull(slug); + return github.getJSON, Release>( + '/repos/${slug.fullName}/releases/latest', + convert: Release.fromJson, + statusCode: StatusCodes.OK, + ); + } + + /// Fetches a single release by the release ID. /// /// API docs: https://developer.github.com/v3/repos/releases/#get-a-single-release - Future getRelease(RepositorySlug slug, int id) { - return _github.getJSON("/repos/${slug.fullName}/releases/${id}", - convert: Release.fromJSON) as Future; + Future getReleaseById(RepositorySlug slug, int id) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(id); + return github.getJSON, Release>( + '/repos/${slug.fullName}/releases/$id', + convert: Release.fromJson, + ); } - /// Creates a Release based on the specified [release]. + /// Fetches a single release by the release tag name. + /// + /// Throws a [ReleaseNotFound] exception if the release + /// doesn't exist. /// + /// API docs: https://developer.github.com/v3/repos/releases/#get-a-release-by-tag-name + Future getReleaseByTagName( + RepositorySlug slug, String? tagName) async { + return github.getJSON( + '/repos/${slug.fullName}/releases/tags/$tagName', + convert: Release.fromJson, + statusCode: StatusCodes.OK, + fail: (http.Response response) { + if (response.statusCode == 404) { + throw ReleaseNotFound.fromTagName(github, tagName); + } + }, + ); + } + + /// Creates a Release based on the specified [createRelease]. + /// + /// If [getIfExists] is true, this returns an already existing release instead of an error. + /// Defaults to true. /// API docs: https://developer.github.com/v3/repos/releases/#create-a-release - Future createRelease(RepositorySlug slug, CreateRelease release) { - return _github.postJSON("/repos/${slug.fullName}/releases", - convert: Release.fromJSON, body: release.toJSON()) as Future; + Future createRelease( + RepositorySlug slug, + CreateRelease createRelease, { + bool getIfExists = true, + }) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(createRelease); + final release = await github.postJSON, Release>( + '/repos/${slug.fullName}/releases', + convert: Release.fromJson, + body: GitHubJson.encode(createRelease.toJson()), + statusCode: StatusCodes.CREATED); + if (release.hasErrors) { + final alreadyExistsErrorCode = release.errors!.firstWhere( + (error) => error['code'] == 'already_exists', + orElse: () => null, + ); + if (alreadyExistsErrorCode != null) { + final field = alreadyExistsErrorCode['field']; + if (field == 'tag_name') { + if (getIfExists) { + return getReleaseByTagName(slug, createRelease.tagName); + } else { + throw Exception( + 'Tag / Release already exists ${createRelease.tagName}'); + } + } + } else { + print( + 'Unexpected response from the API. Returning response. \n Errors: ${release.errors}'); + } + } + return release; + } + + /// Edits the given release with new fields. + /// * [tagName]: The name of the tag. + /// * [targetCommitish]: Specifies the commitish value that determines where the Git tag is created from. Can be any branch or commit SHA. Unused if the Git tag already exists. Default: the repository's default branch (usually master). + /// * [name]: The name of the release. + /// * [body]: Text describing the contents of the tag. + /// * [draft]: true makes the release a draft, and false publishes the release. + /// * [preRelease]: true to identify the release as a prerelease, false to identify the release as a full release. + /// + /// Leave blank the fields you don't want to edit. + /// + /// API docs: https://developer.github.com/v3/repos/releases/#edit-a-release + Future editRelease( + RepositorySlug slug, + Release releaseToEdit, { + String? tagName, + String? targetCommitish, + String? name, + String? body, + bool? draft, + bool? preRelease, + }) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(releaseToEdit); + return github.postJSON, Release>( + '/repos/${slug.fullName}/releases/${releaseToEdit.id.toString()}', + body: GitHubJson.encode(createNonNullMap({ + 'tag_name': tagName ?? releaseToEdit.tagName, + 'target_commitish': targetCommitish ?? releaseToEdit.targetCommitish, + 'name': name ?? releaseToEdit.name, + 'body': body ?? releaseToEdit.body, + 'draft': draft ?? releaseToEdit.isDraft, + 'prerelease': preRelease ?? releaseToEdit.isPrerelease, + })), + statusCode: StatusCodes.OK, + convert: Release.fromJson, + ); + } + + /// Delete the release. + /// + /// API docs: https://developer.github.com/v3/repos/releases/#delete-a-release + Future deleteRelease(RepositorySlug slug, Release release) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(release); + return github + .request( + 'DELETE', + '/repos/${slug.fullName}/releases/${release.id}', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); + } + + /// Lists assets for a release. + /// + /// API docs: https://developer.github.com/v3/repos/releases/#list-assets-for-a-release + Stream listReleaseAssets(RepositorySlug slug, Release release) { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(release); + return PaginationHelper(github).objects, ReleaseAsset>( + 'GET', + '/repos/${slug.fullName}/releases/${release.id}/assets', + ReleaseAsset.fromJson, + statusCode: StatusCodes.OK, + ); + } + + /// Get a single release asset. + /// + /// API docs: https://developer.github.com/v3/repos/releases/#get-a-single-release-asset + // TODO: implement a way to retrieve the asset's binary content + Future getReleaseAsset(RepositorySlug slug, Release release, + {required int assetId}) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(release); + return github.postJSON, ReleaseAsset>( + '/repos/${slug.fullName}/releases/assets/$assetId', + statusCode: StatusCodes.OK, + convert: ReleaseAsset.fromJson, + ); + } + + /// Edits a release asset. + /// + /// API docs: https://developer.github.com/v3/repos/releases/#edit-a-release-asset + Future editReleaseAsset( + RepositorySlug slug, + ReleaseAsset assetToEdit, { + String? name, + String? label, + }) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(assetToEdit); + return github.postJSON, ReleaseAsset>( + '/repos/${slug.fullName}/releases/assets/${assetToEdit.id}', + statusCode: StatusCodes.OK, + convert: ReleaseAsset.fromJson, + body: GitHubJson.encode(createNonNullMap({ + 'name': name ?? assetToEdit.name, + 'label': label ?? assetToEdit.label, + })), + ); + } + + /// Delete a release asset. + /// + /// API docs: https://developer.github.com/v3/repos/releases/#delete-a-release-asset + Future deleteReleaseAsset( + RepositorySlug slug, ReleaseAsset asset) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(asset); + return github + .request( + 'DELETE', + '/repos/${slug.fullName}/releases/assets/${asset.id}', + statusCode: StatusCodes.NO_CONTENT, + ) + .then((response) => response.statusCode == StatusCodes.NO_CONTENT); } - // TODO: Implement editRelease: https://developer.github.com/v3/repos/releases/#edit-a-release - // TODO: Implement deleteRelease: https://developer.github.com/v3/repos/releases/#delete-a-release - // TODO: Implement listReleaseAssets: https://developer.github.com/v3/repos/releases/#list-assets-for-a-release - // TODO: Implement getReleaseAssets: https://developer.github.com/v3/repos/releases/#get-a-single-release-asset - // TODO: Implement editReleaseAssets: https://developer.github.com/v3/repos/releases/#edit-a-release-asset - // TODO: Implement deleteReleaseAssets: https://developer.github.com/v3/repos/releases/#delete-a-release-asset - // TODO: Implement uploadReleaseAsset: https://developer.github.com/v3/repos/releases/#upload-a-release-asset + Future> uploadReleaseAssets( + Release release, + Iterable createReleaseAssets, + ) async { + final releaseAssets = []; + for (final createReleaseAsset in createReleaseAssets) { + final headers = {'Content-Type': createReleaseAsset.contentType}; + final releaseAsset = await github.postJSON( + release.getUploadUrlFor( + createReleaseAsset.name, + createReleaseAsset.label, + ), + headers: headers, + body: createReleaseAsset.assetData, + convert: ReleaseAsset.fromJson); + releaseAssets.add(releaseAsset); + } + return releaseAssets; + } /// Lists repository contributor statistics. /// + /// It's possible that this API will throw [NotReady] in which case you should + /// try the call again later. + /// /// API docs: https://developer.github.com/v3/repos/statistics/#contributors - Future> listContributorStats(RepositorySlug slug, - {int limit: 30}) { - var completer = new Completer>(); - var path = "/repos/${slug.fullName}/stats/contributors"; - var handle; - handle = (json) { - if (json is Map) { - new Future.delayed(new Duration(milliseconds: 200), () { - _github.getJSON(path, - statusCode: 200, - convert: handle, - params: {"per_page": limit.toString()}); - }); - return null; - } else { - completer.complete(json.map( - (Map it) => ContributorStatistics.fromJSON(it))); - } - }; - _github - .getJSON(path, convert: handle, params: {"per_page": limit.toString()}); - return completer.future; + Future> listContributorStats( + RepositorySlug slug, + ) async { + ArgumentError.checkNotNull(slug); + final path = '/repos/${slug.fullName}/stats/contributors'; + final response = + await github.request('GET', path, headers: {'Accept': v3ApiMimeType}); + + if (response.statusCode == StatusCodes.OK) { + return (jsonDecode(response.body) as List) + .cast>() + .map(ContributorStatistics.fromJson) + .toList(); + } else if (response.statusCode == StatusCodes.ACCEPTED) { + throw NotReady(github, path); + } + github.handleStatusCode(response); } /// Fetches commit counts for the past year. /// /// API docs: https://developer.github.com/v3/repos/statistics/#commit-activity Stream listCommitActivity(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", - "/repos/${slug.fullName}/stats/commit_activity", - YearCommitCountWeek.fromJSON) as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github) + .objects, YearCommitCountWeek>( + 'GET', + '/repos/${slug.fullName}/stats/commit_activity', + YearCommitCountWeek.fromJson, + ); } /// Fetches weekly addition and deletion counts. /// /// API docs: https://developer.github.com/v3/repos/statistics/#code-frequency Stream listCodeFrequency(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", - "/repos/${slug.fullName}/stats/code_frequency", - WeeklyChangesCount.fromJSON) as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github) + .objects, WeeklyChangesCount>( + 'GET', + '/repos/${slug.fullName}/stats/code_frequency', + WeeklyChangesCount.fromJson, + ); } /// Fetches Participation Breakdowns. /// /// API docs: https://developer.github.com/v3/repos/statistics/#participation - Future getParticipation(RepositorySlug slug) { - return _github.getJSON("/repos/${slug.fullName}/stats/participation", - statusCode: 200, convert: ContributorParticipation.fromJSON) - as Future; + Future getParticipation(RepositorySlug slug) async { + ArgumentError.checkNotNull(slug); + return github.getJSON( + '/repos/${slug.fullName}/stats/participation', + statusCode: StatusCodes.OK, + convert: ContributorParticipation.fromJson, + ); } /// Fetches Punchcard. /// /// API docs: https://developer.github.com/v3/repos/statistics/#punch-card Stream listPunchcard(RepositorySlug slug) { - return new PaginationHelper(_github).objects( - "GET", - "/repos/${slug.fullName}/stats/punchcard", - PunchcardEntry.fromJSON) as Stream; + ArgumentError.checkNotNull(slug); + return PaginationHelper(github) + .objects, PunchcardEntry>( + 'GET', + '/repos/${slug.fullName}/stats/punchcard', + PunchcardEntry.fromJson, + ); } /// Lists the statuses of a repository at the specified reference. @@ -624,10 +1268,14 @@ class RepositoriesService extends Service { /// /// API docs: https://developer.github.com/v3/repos/statuses/#list-statuses-for-a-specific-ref Stream listStatuses(RepositorySlug slug, String ref) { - return new PaginationHelper(_github).objects( - "GET", - "/repos/${slug.fullName}/commits/${ref}/statuses", - RepositoryStatus.fromJSON) as Stream; + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(ref); + return PaginationHelper(github) + .objects, RepositoryStatus>( + 'GET', + '/repos/${slug.fullName}/commits/$ref/statuses', + RepositoryStatus.fromJson, + ); } /// Creates a new status for a repository at the specified reference. @@ -635,19 +1283,44 @@ class RepositoriesService extends Service { /// /// API docs: https://developer.github.com/v3/repos/statuses/#create-a-status Future createStatus( - RepositorySlug slug, String ref, CreateStatus request) { - return _github.postJSON("/repos/${slug.fullName}/statuses/${ref}", - body: request.toJSON(), - convert: RepositoryStatus.fromJSON) as Future; + RepositorySlug slug, String ref, CreateStatus request) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(ref); + ArgumentError.checkNotNull(request); + return github.postJSON, RepositoryStatus>( + '/repos/${slug.fullName}/statuses/$ref', + body: GitHubJson.encode(request), + convert: RepositoryStatus.fromJson, + ); } /// Gets a Combined Status for the specified repository and ref. /// /// API docs: https://developer.github.com/v3/repos/statuses/#get-the-combined-status-for-a-specific-ref Future getCombinedStatus( - RepositorySlug slug, String ref) { - return _github.getJSON("/repos/${slug.fullName}/commits/${ref}/status", - convert: CombinedRepositoryStatus.fromJSON, - statusCode: 200) as Future; + RepositorySlug slug, String ref) async { + ArgumentError.checkNotNull(slug); + ArgumentError.checkNotNull(ref); + return github.getJSON, CombinedRepositoryStatus>( + '/repos/${slug.fullName}/commits/$ref/status', + convert: CombinedRepositoryStatus.fromJson, + statusCode: StatusCodes.OK, + ); + } + + /// Generate a name and body describing a release. The body content will be + /// markdown formatted and contain information like the changes since last + /// release and users who contributed. The generated release notes are not + /// saved anywhere. They are intended to be generated and used when + /// creating a new release. + /// + /// API docs: https://docs.github.com/en/rest/reference/repos#generate-release-notes-content-for-a-release + Future generateReleaseNotes(CreateReleaseNotes crn) async { + return github.postJSON, ReleaseNotes>( + '/repos/${crn.owner}/${crn.repo}/releases/generate-notes', + body: GitHubJson.encode(crn), + statusCode: StatusCodes.OK, + convert: ReleaseNotes.fromJson, + ); } } diff --git a/lib/src/common/search_service.dart b/lib/src/common/search_service.dart index 3d7ebefe..27cf6eef 100644 --- a/lib/src/common/search_service.dart +++ b/lib/src/common/search_service.dart @@ -1,96 +1,220 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; + +import 'package:github/src/common.dart'; /// The [SearchService] handles communication with search related methods of /// the GitHub API. /// /// API docs: https://developer.github.com/v3/search/ class SearchService extends Service { - SearchService(GitHub github) : super(github); + SearchService(super.github); /// Search for repositories using [query]. /// Since the Search Rate Limit is small, this is a best effort implementation. /// /// API docs: https://developer.github.com/v3/search/#search-repositories - Stream repositories(String query, {String sort, int pages: 2}) { - var params = {"q": query}; + Stream repositories(String query, {String? sort, int pages = 2}) { + final params = {'q': query}; if (sort != null) { - params["sort"] = sort; + params['sort'] = sort; } - var controller = new StreamController(); + final controller = StreamController(); var isFirst = true; - new PaginationHelper(_github) - .fetchStreamed("GET", "/search/repositories", + PaginationHelper(github) + .fetchStreamed('GET', '/search/repositories', params: params, pages: pages) .listen((response) { if (response.statusCode == 403 && - response.body.contains("rate limit") && + response.body.contains('rate limit') && isFirst) { - throw new RateLimitHit(_github); + throw RateLimitHit(github); } isFirst = false; - var input = JSON.decode(response.body); + final input = jsonDecode(response.body); if (input['items'] == null) { return; } - List items = input['items']; + final items = input['items'] as List; items - .map((Map item) => Repository.fromJSON(item)) + .cast>() + .map(Repository.fromJson) .forEach(controller.add); }).onDone(controller.close); return controller.stream; } - // TODO: Implement code: https://developer.github.com/v3/search/#search-code - // TODO: Implement issues: https://developer.github.com/v3/search/#search-issues + /// Search through code for a given [query]. + /// By default you will get all search results if you consume all + /// events on the returned stream. To limit results, set the + /// [pages] and [perPage] parameters. + /// + /// You can include any github qualifiers in the query directly + /// or you can set some of the optional params to set the qualifiers + /// For example, these do the same thing: + /// code('awesome language:dart') and + /// code('awesome', language: 'dart') + /// + /// https://developer.github.com/v3/search/#search-code + Stream code( + String query, { + int? pages, + int? perPage, + String? language, + String? filename, + String? extension, + String? user, + String? org, + String? repo, + String? fork, + String? path, + String? size, + bool inFile = true, + bool inPath = false, + }) { + // Add qualifiers to the query + // Known Issue: If a query already has a qualifier and the same + // qualifier parameter is passed in, it will be duplicated. + // Example: code('example repo:ex', repo: 'ex') will result in + // a query of 'example repo:ex repo:ex' + query += _searchQualifier('language', language); + query += _searchQualifier('filename', filename); + query += _searchQualifier('extension', extension); + query += _searchQualifier('user', user); + query += _searchQualifier('org', org); + query += _searchQualifier('repo', repo); + query += _searchQualifier('fork', fork); + query += _searchQualifier('path', path); + query += _searchQualifier('size', size); + + // build up the in: qualifier based on the 2 booleans + var inValue = ''; + if (inFile) { + inValue = 'file'; + } + if (inPath) { + if (inValue.isEmpty) { + inValue = 'path'; + } else { + inValue = 'file,path'; + } + } + if (inValue.isNotEmpty) { + query += ' in:$inValue'; + } + + final params = {}; + params['q'] = query; + if (perPage != null) { + params['per_page'] = perPage.toString(); + } + + return PaginationHelper(github) + .fetchStreamed('GET', '/search/code', params: params, pages: pages) + .map((r) => CodeSearchResults.fromJson(json.decode(r.body))); + } + + String _searchQualifier(String key, String? value) { + if (value != null && value.isNotEmpty) { + return ' $key:$value'; + } + return ''; + } + + /// Search for issues and pull-requests using [query]. + /// Since the Search Rate Limit is small, this is a best effort implementation. + /// API docs: https://developer.github.com/v3/search/#search-issues + Stream issues(String query, {String? sort, int pages = 2}) { + final params = {'q': query}; + if (sort != null) { + params['sort'] = sort; + } + + final controller = StreamController(); + + var isFirst = true; + + PaginationHelper(github) + .fetchStreamed('GET', '/search/issues', params: params, pages: pages) + .listen((response) { + if (response.statusCode == 403 && + response.body.contains('rate limit') && + isFirst) { + throw RateLimitHit(github); + } + + isFirst = false; + + final input = jsonDecode(response.body); + + if (input['items'] == null) { + return; + } + + final items = input['items'] as List; + + items + .cast>() + .map(Issue.fromJson) + .forEach(controller.add); + }).onDone(controller.close); + + return controller.stream; + } /// Search for users using [query]. /// Since the Search Rate Limit is small, this is a best effort implementation. /// /// API docs: https://developer.github.com/v3/search/#search-users - Stream users(String query, - {String sort, int pages: 2, int perPage: 30}) { - var params = {"q": query}; + Stream users( + String query, { + String? sort, + int pages = 2, + int perPage = 30, + }) { + final params = {'q': query}; if (sort != null) { - params["sort"] = sort; + params['sort'] = sort; } - params["per_page"] = perPage.toString(); + params['per_page'] = perPage.toString(); - var controller = new StreamController(); + final controller = StreamController(); var isFirst = true; - new PaginationHelper(_github) - .fetchStreamed("GET", "/search/users", params: params, pages: pages) + PaginationHelper(github) + .fetchStreamed('GET', '/search/users', params: params, pages: pages) .listen((response) { if (response.statusCode == 403 && - response.body.contains("rate limit") && + response.body.contains('rate limit') && isFirst) { - throw new RateLimitHit(_github); + throw RateLimitHit(github); } isFirst = false; - var input = JSON.decode(response.body); + final input = jsonDecode(response.body); if (input['items'] == null) { return; } - List items = input['items']; + final items = input['items'] as List; items - .map((Map item) => User.fromJSON(item)) + .cast>() + .map(User.fromJson) .forEach(controller.add); }).onDone(controller.close); diff --git a/lib/src/common/url_shortener_service.dart b/lib/src/common/url_shortener_service.dart index 7af62807..60db2958 100644 --- a/lib/src/common/url_shortener_service.dart +++ b/lib/src/common/url_shortener_service.dart @@ -1,16 +1,17 @@ -part of github.common; +import 'dart:async'; +import 'package:github/src/common.dart'; /// The [UrlShortenerService] provides a handy method to access GitHub's /// url shortener. /// /// API docs: https://github.com/blog/985-git-io-github-url-shortener class UrlShortenerService extends Service { - UrlShortenerService(GitHub github) : super(github); + UrlShortenerService(super.github); /// Shortens the provided [url]. An optional [code] can be provided to create /// your own vanity URL. - Future shortenUrl(String url, {String code}) { - var params = {}; + Future shortenUrl(String url, {String? code}) { + final params = {}; params['url'] = url; @@ -18,14 +19,14 @@ class UrlShortenerService extends Service { params['code'] = code; } - return _github - .request("POST", "http://git.io/", params: params) + return github + .request('POST', 'http://git.io/', params: params) .then((response) { if (response.statusCode != StatusCodes.CREATED) { - throw new GitHubError(_github, "Failed to create shortened url!"); + throw GitHubError(github, 'Failed to create shortened url!'); } - return response.headers["Location"].split("/").last; + return response.headers['Location']!.split('/').last; }); } } diff --git a/lib/src/common/users_service.dart b/lib/src/common/users_service.dart index ee1fb649..6485f4b0 100644 --- a/lib/src/common/users_service.dart +++ b/lib/src/common/users_service.dart @@ -1,63 +1,56 @@ -part of github.common; +import 'dart:async'; + +import 'package:github/src/common.dart'; +import 'package:http/http.dart' as http; /// The [UsersService] handles communication with user related methods of the /// GitHub API. /// /// API docs: https://developer.github.com/v3/users/ class UsersService extends Service { - UsersService(GitHub github) : super(github); + UsersService(super.github); /// Fetches the user specified by [name]. /// /// API docs: https://developer.github.com/v3/users/#get-a-single-user - Future getUser(String name) => - _github.getJSON("/users/${name}", convert: User.fromJSON) as Future; + Future getUser(String? name) => + github.getJSON('/users/$name', convert: User.fromJson); /// Updates the Current User. /// /// API docs: https://developer.github.com/v3/users/#update-the-authenticated-user Future editCurrentUser( - {String name, - String email, - String blog, - String company, - String location, - bool hireable, - String bio}) { - var map = createNonNullMap({ - "name": name, - "email": email, - "blog": blog, - "company": company, - "location": location, - "hireable": hireable, - "bio": bio + {String? name, + String? email, + String? blog, + String? company, + String? location, + bool? hireable, + String? bio}) { + final map = createNonNullMap({ + 'name': name, + 'email': email, + 'blog': blog, + 'company': company, + 'location': location, + 'hireable': hireable, + 'bio': bio }); - return _github.postJSON("/user", - // TODO: map probably needs to be JSON encoded. - body: map, - statusCode: 200, - convert: CurrentUser.fromJSON) as Future; + return github.postJSON( + '/user', + body: GitHubJson.encode(map), + statusCode: 200, + convert: CurrentUser.fromJson, + ); } /// Fetches a list of users specified by [names]. - Stream getUsers(List names, {int pages}) { - var controller = new StreamController(); - - var group = new FutureGroup(); - - for (var name in names) { - group.add(getUser(name).then((user) { - controller.add(user); - })); + Stream getUsers(List names, {int? pages}) async* { + for (final name in names) { + final user = await getUser(name); + yield user; } - - group.future.then((_) { - controller.close(); - }); - - return controller.stream; } /// Fetches the currently authenticated user. @@ -65,18 +58,17 @@ class UsersService extends Service { /// Throws [AccessForbidden] if we are not authenticated. /// /// API docs: https://developer.github.com/v3/users/#get-the-authenticated-user - Future getCurrentUser() { - return _github.getJSON("/user", statusCode: StatusCodes.OK, - fail: (http.Response response) { - if (response.statusCode == StatusCodes.FORBIDDEN) { - throw new AccessForbidden(_github); - } - }, convert: CurrentUser.fromJSON) as Future; - } + Future getCurrentUser() => + github.getJSON('/user', statusCode: StatusCodes.OK, + fail: (http.Response response) { + if (response.statusCode == StatusCodes.FORBIDDEN) { + throw AccessForbidden(github); + } + }, convert: CurrentUser.fromJson); /// Checks if a user exists. - Future isUser(String name) => _github - .request("GET", "/users/${name}") + Future isUser(String name) => github + .request('GET', '/users/$name') .then((resp) => resp.statusCode == StatusCodes.OK); // TODO: Implement editUser: https://developer.github.com/v3/users/#update-the-authenticated-user @@ -84,55 +76,55 @@ class UsersService extends Service { /// Lists all users. /// /// API docs: https://developer.github.com/v3/users/#get-all-users - Stream listUsers({int pages, int since}) => - new PaginationHelper(_github).objects("GET", "/users", User.fromJSON, - pages: pages, params: {"since": since}) as Stream; + Stream listUsers({int? pages, int? since}) => + PaginationHelper(github).objects('GET', '/users', User.fromJson, + pages: pages, params: {'since': since}); /// Lists all email addresses for the currently authenticated user. /// /// API docs: https://developer.github.com/v3/users/emails/#list-email-addresses-for-a-user - Stream listEmails() => new PaginationHelper(_github) - .objects("GET", "/user/emails", UserEmail.fromJSON) as Stream; + Stream listEmails() => PaginationHelper(github) + .objects('GET', '/user/emails', UserEmail.fromJson); /// Add Emails /// /// API docs: https://developer.github.com/v3/users/emails/#add-email-addresses - Stream addEmails(List emails) => - new PaginationHelper(_github).objects( - "POST", "/user/emails", UserEmail.fromJSON, - statusCode: 201, body: JSON.encode(emails)) as Stream; + Stream addEmails(List emails) => PaginationHelper(github) + .objects('POST', '/user/emails', UserEmail.fromJson, + statusCode: 201, body: GitHubJson.encode(emails)); /// Delete Emails /// /// API docs: https://developer.github.com/v3/users/emails/#delete-email-addresses - Future deleteEmails(List emails) => _github - .request("DELETE", "/user/emails", - body: JSON.encode(emails), statusCode: 204) + Future deleteEmails(List emails) => github + .request('DELETE', '/user/emails', + body: GitHubJson.encode(emails), statusCode: 204) .then((x) => x.statusCode == 204); /// List user followers. /// /// API docs: https://developer.github.com/v3/users/followers/#list-followers-of-a-user - Stream listUserFollowers(String user) => new PaginationHelper(_github) - .objects("GET", "/users/${user}/followers", User.fromJSON, - statusCode: 200) as Stream; + Stream listUserFollowers(String user) => PaginationHelper(github) + .objects('GET', '/users/$user/followers', User.fromJson, statusCode: 200); /// Check if the current user is following the specified user. Future isFollowingUser(String user) => - _github.request("GET", "/user/following/${user}").then((response) { + github.request('GET', '/user/following/$user').then((response) { return response.statusCode == 204; }); /// Check if the specified user is following target. Future isUserFollowing(String user, String target) => - _github.request("GET", "/users/${user}/following/${target}").then((x) { + github.request('GET', '/users/$user/following/$target').then((x) { return x.statusCode == 204; }); /// Follows a user. + /// + /// https://developer.github.com/v3/users/followers/#follow-a-user Future followUser(String user) { - return _github - .request("POST", "/user/following/${user}", statusCode: 204) + return github + .request('PUT', '/user/following/$user', statusCode: 204) .then((response) { return response.statusCode == 204; }); @@ -140,8 +132,8 @@ class UsersService extends Service { /// Unfollows a user. Future unfollowUser(String user) { - return _github - .request("DELETE", "/user/following/${user}", statusCode: 204) + return github + .request('DELETE', '/user/following/$user', statusCode: 204) .then((response) { return response.statusCode == 204; }); @@ -150,19 +142,23 @@ class UsersService extends Service { /// List current user followers. /// /// API docs: https://developer.github.com/v3/users/followers/#list-followers-of-a-user - Stream listCurrentUserFollowers() => new PaginationHelper(_github) - .objects("GET", "/user/followers", User.fromJSON, statusCode: 200) - as Stream; + Stream listCurrentUserFollowers() => PaginationHelper(github) + .objects('GET', '/user/followers', User.fromJson, statusCode: 200); + + /// List current user following + /// + /// API docs: https://developer.github.com/v3/users/followers/#list-users-followed-by-the-authenticated-user + Stream listCurrentUserFollowing() => PaginationHelper(github) + .objects('GET', '/user/following', User.fromJson, statusCode: 200); /// Lists the verified public keys for a [userLogin]. If no [userLogin] is specified, /// the public keys for the authenticated user are fetched. /// /// API docs: https://developer.github.com/v3/users/keys/#list-public-keys-for-a-user /// and https://developer.github.com/v3/users/keys/#list-your-public-keys - Stream listPublicKeys([String userLogin]) { - var path = userLogin == null ? "/user/keys" : "/users/${userLogin}/keys"; - return new PaginationHelper(_github) - .objects("GET", path, PublicKey.fromJSON) as Stream; + Stream listPublicKeys([String? userLogin]) { + final path = userLogin == null ? '/user/keys' : '/users/$userLogin/keys'; + return PaginationHelper(github).objects('GET', path, PublicKey.fromJson); } // TODO: Implement getPublicKey: https://developer.github.com/v3/users/keys/#get-a-single-public-key @@ -171,7 +167,7 @@ class UsersService extends Service { /// /// API docs: https://developer.github.com/v3/users/keys/#create-a-public-key Future createPublicKey(CreatePublicKey key) { - return _github.postJSON("/user/keys", body: key.toJSON()) + return github.postJSON('/user/keys', body: GitHubJson.encode(key)) as Future; } diff --git a/lib/src/common/util/auth.dart b/lib/src/common/util/auth.dart index 3282fbfb..1c4d6798 100644 --- a/lib/src/common/util/auth.dart +++ b/lib/src/common/util/auth.dart @@ -1,36 +1,75 @@ -part of github.common; +import 'dart:convert'; /// Authentication information. class Authentication { /// OAuth2 Token - final String token; + final String? token; /// GitHub Username - final String username; + final String? username; /// GitHub Password - final String password; + final String? password; + final String? bearerToken; + + // TODO: mark the pram as `String` to REQUIRE a non-null value. + // NEXT major version /// Creates an [Authentication] instance that uses the specified OAuth2 [token]. - Authentication.withToken(this.token) + const Authentication.withToken(this.token) + : username = null, + password = null, + bearerToken = null; + + /// Creates an [Authentication] instance that uses the specified + /// [bearerToken]. + const Authentication.bearerToken(String this.bearerToken) : username = null, - password = null; + password = null, + token = null; /// Creates an [Authentication] instance that has no authentication. - Authentication.anonymous() + const Authentication.anonymous() : token = null, username = null, - password = null; + password = null, + bearerToken = null; + // TODO: mark the `username` and `password` params as `String` to REQUIRE + // non-null values. - NEXT major version /// Creates an [Authentication] instance that uses a username and password. - Authentication.basic(this.username, this.password) : token = null; + const Authentication.basic(this.username, this.password) + : token = null, + bearerToken = null; /// Anonymous Authentication Flag - bool get isAnonymous => !isBasic && !isToken; + bool get isAnonymous => !isBasic && !isToken && !isBearer; /// Basic Authentication Flag bool get isBasic => username != null; /// Token Authentication Flag bool get isToken => token != null; + + // This instance represents a authentication with a "Bearer" token. + bool get isBearer => bearerToken != null; + + /// Returns a value for the `Authorization` HTTP request header or `null` + /// if [isAnonymous] is `true`. + String? authorizationHeaderValue() { + if (isToken) { + return 'token $token'; + } + + if (isBasic) { + final userAndPass = base64Encode(utf8.encode('$username:$password')); + return 'basic $userAndPass'; + } + + if (isBearer) { + return 'Bearer $bearerToken'; + } + + return null; + } } diff --git a/lib/src/common/util/crawler.dart b/lib/src/common/util/crawler.dart index 0ce469c3..5b5a7f7e 100644 --- a/lib/src/common/util/crawler.dart +++ b/lib/src/common/util/crawler.dart @@ -1,4 +1,5 @@ -part of github.common; +import 'dart:async'; +import 'package:github/src/common.dart'; // Crawls a Repository to Fetch All Files class RepositoryCrawler { @@ -7,29 +8,19 @@ class RepositoryCrawler { RepositoryCrawler(this.github, this.slug); - Stream crawl() { - var controller = new StreamController(); - - var group = new FutureGroup(); - - void scan(String path) { - group.add(github.repositories.getContents(slug, path).then((contents) { - contents.tree.where((it) => it.type != "dir").forEach(controller.add); - - contents.tree.where((it) { - return it.type == "dir"; - }).forEach((file) { - scan(file.path); - }); - })); + Stream crawl() async* { + Stream scan(String path) async* { + final contents = await github.repositories.getContents(slug, path); + + for (final content in contents.tree!) { + if (content.type == 'dir') { + yield* scan(content.path!); + } else { + yield content; + } + } } - group.future.then((_) { - return controller.close(); - }); - - scan("/"); - - return controller.stream; + yield* scan('/'); } } diff --git a/lib/src/common/util/errors.dart b/lib/src/common/util/errors.dart index 29983caf..14625a6c 100644 --- a/lib/src/common/util/errors.dart +++ b/lib/src/common/util/errors.dart @@ -1,77 +1,102 @@ -part of github.common; +import 'package:github/src/common.dart'; /// Error Generated by [GitHub] class GitHubError implements Exception { - final String message; - final String apiUrl; + final String? message; + final String? apiUrl; final GitHub github; - final Object source; + final Object? source; - GitHubError(this.github, this.message, {this.apiUrl, this.source}); + const GitHubError(this.github, this.message, {this.apiUrl, this.source}); @override - String toString() => "GitHub Error: ${message}"; + String toString() => 'GitHub Error: $message'; +} + +class NotReady extends GitHubError { + const NotReady(GitHub github, String path) + : super( + github, + 'Not ready. Try again later', + apiUrl: path, + ); } /// GitHub Entity was not found class NotFound extends GitHubError { - NotFound(GitHub github, String msg) : super(github, msg); + const NotFound( + super.github, + String super.msg, + ); } class BadRequest extends GitHubError { - BadRequest(GitHub github, [String msg = 'Not Found']) : super(github, msg); + const BadRequest(super.github, [super.msg = 'Not Found']); } /// GitHub Repository was not found class RepositoryNotFound extends NotFound { - RepositoryNotFound(GitHub github, String repo) - : super(github, "Repository Not Found: ${repo}"); + const RepositoryNotFound(GitHub github, String repo) + : super(github, 'Repository Not Found: $repo'); +} + +/// Release not found +class ReleaseNotFound extends NotFound { + const ReleaseNotFound.fromTagName(GitHub github, String? tagName) + : super(github, 'Release for tagName $tagName Not Found.'); } /// GitHub User was not found class UserNotFound extends NotFound { - UserNotFound(GitHub github, String user) - : super(github, "User Not Found: ${user}"); + const UserNotFound(GitHub github, String user) + : super(github, 'User Not Found: $user'); } /// GitHub Organization was not found class OrganizationNotFound extends NotFound { - OrganizationNotFound(GitHub github, String organization) - : super(github, "Organization Not Found: ${organization}"); + const OrganizationNotFound(GitHub github, String? organization) + : super(github, 'Organization Not Found: $organization'); } /// GitHub Team was not found class TeamNotFound extends NotFound { - TeamNotFound(GitHub github, int id) : super(github, "Team Not Found: ${id}"); + const TeamNotFound(GitHub github, int id) + : super(github, 'Team Not Found: $id'); } /// Access was forbidden to a resource class AccessForbidden extends GitHubError { - AccessForbidden(GitHub github) : super(github, "Access Forbidden"); + const AccessForbidden(GitHub github) : super(github, 'Access Forbidden'); } /// Client hit the rate limit. class RateLimitHit extends GitHubError { - RateLimitHit(GitHub github) : super(github, "Rate Limit Hit"); + const RateLimitHit(GitHub github) : super(github, 'Rate Limit Hit'); +} + +/// A GitHub Server Error +class ServerError extends GitHubError { + ServerError(GitHub github, int statusCode, String? message) + : super(github, '${message ?? 'Server Error'} ($statusCode)'); } /// An Unknown Error class UnknownError extends GitHubError { - UnknownError(GitHub github, [String message]) - : super(github, message != null ? message : "Unknown Error"); + const UnknownError(GitHub github, [String? message]) + : super(github, message ?? 'Unknown Error'); } /// GitHub Client was not authenticated class NotAuthenticated extends GitHubError { - NotAuthenticated(GitHub github) : super(github, "Client not Authenticated"); + const NotAuthenticated(GitHub github) + : super(github, 'Client not Authenticated'); } class InvalidJSON extends BadRequest { - InvalidJSON(GitHub github, [String message = "Invalid JSON"]) - : super(github, message); + const InvalidJSON(super.github, [super.message = 'Invalid JSON']); } class ValidationFailed extends GitHubError { - ValidationFailed(GitHub github, [String message = "Validation Failed"]) - : super(github, message); + const ValidationFailed(super.github, + [String super.message = 'Validation Failed']); } diff --git a/lib/src/common/util/json.dart b/lib/src/common/util/json.dart index 8abe399c..ff922b0b 100644 --- a/lib/src/common/util/json.dart +++ b/lib/src/common/util/json.dart @@ -1,4 +1,59 @@ -part of github.common; +import 'dart:convert'; + +import 'package:github/src/common/util/utils.dart'; /// Creates a Model Object from the JSON [input] -typedef T JSONConverter(dynamic input); +typedef JSONConverter = T Function(S input); + +/// Internal class for Json encoding +/// that should be used instead of `dart:convert`. +/// +/// It contains methods that ensures that converted Json +/// will work with the GitHub API. +class GitHubJson { + const GitHubJson._(); + + /// Called only if an object is of a non primitive type. + /// + /// If [object] is a [DateTime], it converts it to a String whose format is compatible with the API. + /// Else, it uses the default behavior of [JsonEncoder] which is to call `toJson` method onto [object]. + /// + /// If [object] is not a [DateTime] and don't have a `toJson` method, an exception will be thrown + /// but handled by [JsonEncoder]. + /// Do not catch it. + static dynamic _toEncodable(dynamic object) { + if (object is DateTime) { + return dateToGitHubIso8601(object); + } + // `toJson` could return a [Map] or a [List], + // so we have to delete null values in them. + return _checkObject(object.toJson()); + } + + /// Encodes [object] to a Json String compatible with the GitHub API. + /// It should be used instead of `jsonEncode`. + /// + /// Equivalent to `jsonEncode` except that + /// it converts [DateTime] to a proper String for the GitHub API, + /// and it also deletes keys associated with null values in maps before converting them. + /// + /// The obtained String can be decoded using `jsonDecode`. + static String encode(Object object, {String? indent}) { + final encoder = JsonEncoder.withIndent(indent, _toEncodable); + return encoder.convert(_checkObject(object)); + } + + /// Deletes keys associated with null values + /// in every map contained in [object]. + static dynamic _checkObject(dynamic object) { + if (object is Map) { + return Map.fromEntries(object.entries + .where((e) => e.value != null) + .map((e) => MapEntry(e.key, _checkObject(e.value)))); + } + if (object is List) { + return object.map(_checkObject).toList(); + } + return object; + } +} diff --git a/lib/src/common/util/oauth2.dart b/lib/src/common/util/oauth2.dart index 3ed2b6db..9333d606 100644 --- a/lib/src/common/util/oauth2.dart +++ b/lib/src/common/util/oauth2.dart @@ -1,11 +1,14 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert'; +import 'package:github/src/common.dart'; +import 'package:http/http.dart' as http; /// OAuth2 Flow Helper /// /// **Example**: /// -/// var flow = new OAuth2Flow("ClientID", "ClientSecret"); -/// var authUrl = flow.createAuthorizationURL(); +/// var flow = new OAuth2Flow('ClientID', 'ClientSecret'); +/// var authUrl = flow.createAuthorizeUrl(); /// // Display to the User and handle the redirect URI, and also get the code. /// flow.exchange(code).then((response) { /// var github = new GitHub(auth: new Authentication.withToken(response.token)); @@ -21,10 +24,10 @@ class OAuth2Flow { final List scopes; /// Redirect URI - final String redirectUri; + final String? redirectUri; /// State - final String state; + final String? state; /// Client Secret final String clientSecret; @@ -32,71 +35,69 @@ class OAuth2Flow { /// OAuth2 Base URL final String baseUrl; - GitHub github; + GitHub? github; OAuth2Flow(this.clientId, this.clientSecret, - {String redirectUri, - this.scopes: const [], + {String? redirectUri, + this.scopes = const [], this.state, this.github, - this.baseUrl: "https://github.com/login/oauth"}) - : this.redirectUri = + this.baseUrl = 'https://github.com/login/oauth'}) + : redirectUri = redirectUri == null ? null : _checkRedirectUri(redirectUri); static String _checkRedirectUri(String uri) { - return uri.contains("?") ? uri.substring(0, uri.indexOf("?")) : uri; + return uri.contains('?') ? uri.substring(0, uri.indexOf('?')) : uri; } /// Generates an Authorization URL /// /// This should be displayed to the user. String createAuthorizeUrl() { - return baseUrl + - "/authorize" + - buildQueryString({ - "client_id": clientId, - "scope": scopes.join(","), - "redirect_uri": redirectUri, - "state": state - }); + return '$baseUrl/authorize${buildQueryString({ + 'client_id': clientId, + 'scope': scopes.join(','), + 'redirect_uri': redirectUri, + 'state': state + })}'; } /// Exchanges the given [code] for a token. - Future exchange(String code, [String origin]) { - var headers = { - "Accept": "application/json", - "content-type": "application/json" + Future exchange(String code, [String? origin]) { + final headers = { + 'Accept': 'application/json', + 'content-type': 'application/json' }; if (origin != null) { headers['Origin'] = origin; } - var body = JSON.encode({ - "client_id": clientId, - "client_secret": clientSecret, - "code": code, - "redirect_uri": redirectUri + final body = GitHubJson.encode({ + 'client_id': clientId, + 'client_secret': clientSecret, + 'code': code, + 'redirect_uri': redirectUri }); - return (github == null ? new http.Client() : github.client) - .post("${baseUrl}/access_token", body: body, headers: headers) + return (github == null ? http.Client() : github!.client) + .post(Uri.parse('$baseUrl/access_token'), body: body, headers: headers) .then((response) { - var json = JSON.decode(response.body) as Map; + final json = jsonDecode(response.body) as Map; if (json['error'] != null) { - throw json; + throw Exception(json['error']); } - return new ExchangeResponse(json['access_token'], json['token_type'], - (json['scope'] as String).split(",")); + return ExchangeResponse(json['access_token'], json['token_type'], + (json['scope'] as String).split(',')); }); } } /// Represents a response for exchanging a code for a token. class ExchangeResponse { - final String token; + final String? token; final List scopes; - final String tokenType; + final String? tokenType; ExchangeResponse(this.token, this.tokenType, this.scopes); } diff --git a/lib/src/common/util/pagination.dart b/lib/src/common/util/pagination.dart index fd905f55..93f3a0d7 100644 --- a/lib/src/common/util/pagination.dart +++ b/lib/src/common/util/pagination.dart @@ -1,29 +1,49 @@ -part of github.common; +import 'dart:async'; +import 'dart:convert' show jsonDecode; + +import 'package:http/http.dart' as http; + +import '../../common.dart'; /// Internal Helper for dealing with GitHub Pagination. -class PaginationHelper { +class PaginationHelper { final GitHub github; PaginationHelper(this.github); Stream fetchStreamed(String method, String path, - {int pages, - Map headers, - Map params, - String body, - int statusCode: 200}) async* { - int count = 0; + {int? pages, + Map? headers, + Map? params, + String? body, + int statusCode = 200}) async* { + var count = 0; + const serverErrorBackOff = Duration(seconds: 10); + const maxServerErrors = 10; + var serverErrors = 0; if (params == null) { params = {}; } else { - params = new Map.from(params); + params = Map.from(params); } - assert(!params.containsKey('page')); - do { - var response = await github.request(method, path, - headers: headers, params: params, body: body, statusCode: statusCode); + while (true) { + http.Response response; + try { + response = await github.request(method, path, + headers: headers, + params: params, + body: body, + statusCode: statusCode); + } on ServerError { + serverErrors += 1; + if (serverErrors >= maxServerErrors) { + break; + } + await Future.delayed(serverErrorBackOff); + continue; + } yield response; @@ -33,71 +53,103 @@ class PaginationHelper { break; } - var link = response.headers['link']; + final link = response.headers['link']; if (link == null) { break; } - var info = parseLinkHeader(link); - if (info == null) { - break; - } + final info = parseLinkHeader(link); - var next = info['next']; + final next = info['next']; if (next == null) { break; } - var nextUrl = Uri.parse(next); - var nextPageArg = nextUrl.queryParameters['page']; - assert(nextPageArg != null); - params['page'] = nextPageArg; - } while (true); + path = next; + params = null; + } } - Stream jsonObjects(String method, String path, - {int pages, - Map headers, - Map params, - String body, - int statusCode: 200, - String preview}) async* { - if (headers == null) headers = {}; + Stream jsonObjects( + String method, + String path, { + int? pages, + Map? headers, + Map? params, + String? body, + int statusCode = 200, + String? preview, + String? arrayKey, + }) async* { + headers ??= {}; if (preview != null) { - headers["Accept"] = preview; + headers['Accept'] = preview; } - headers.putIfAbsent("Accept", () => "application/vnd.github.v3+json"); - - await for (var response in fetchStreamed(method, path, - pages: pages, - headers: headers, - params: params, - body: body, - statusCode: statusCode)) { - var json = JSON.decode(response.body) as List; - - for (var item in json) { - yield item; + headers.putIfAbsent('Accept', () => v3ApiMimeType); + + await for (final response in fetchStreamed( + method, + path, + pages: pages, + headers: headers, + params: params, + body: body, + statusCode: statusCode, + )) { + final json = arrayKey == null + ? jsonDecode(response.body) as List? + : (jsonDecode(response.body) as Map)[arrayKey]; + + for (final item in json) { + yield (item as T?)!; } } } - Stream objects(String method, String path, JSONConverter converter, - {int pages, - Map headers, - Map params, - String body, - int statusCode: 200, - String preview}) { - return jsonObjects(method, path, - pages: pages, - headers: headers, - params: params, - body: body, - statusCode: statusCode, - preview: preview) - .map(converter); + /// If the response body is a JSONObject (and not a JSONArray), + /// use [arrayKey] to specify the key under which the array is stored. + Stream objects( + String method, + String path, + JSONConverter converter, { + int? pages, + Map? headers, + Map? params, + String? body, + int statusCode = 200, + String? preview, + String? arrayKey, + }) { + return jsonObjects( + method, + path, + pages: pages, + headers: headers, + params: params, + body: body, + statusCode: statusCode, + preview: preview, + arrayKey: arrayKey, + ).map(converter); + } +} + +//TODO(kevmoo): use regex here. +Map parseLinkHeader(String input) { + final out = {}; + final parts = input.split(', '); + for (final part in parts) { + if (part[0] != '<') { + throw const FormatException('Invalid Link Header'); + } + final kv = part.split('; '); + var url = kv[0].substring(1); + url = url.substring(0, url.length - 1); + var key = kv[1]; + key = key.replaceAll('"', '').substring(4); + out[key] = url; } + return out; } diff --git a/lib/src/common/util/service.dart b/lib/src/common/util/service.dart index a70a9bcc..3e0b5d75 100644 --- a/lib/src/common/util/service.dart +++ b/lib/src/common/util/service.dart @@ -1,8 +1,8 @@ -part of github.common; +import 'package:github/src/common.dart'; /// Superclass for all services. abstract class Service { - final GitHub _github; + final GitHub github; - Service(this._github); + const Service(this.github); } diff --git a/lib/src/common/util/utils.dart b/lib/src/common/util/utils.dart index 01eb08e8..5c690774 100644 --- a/lib/src/common/util/utils.dart +++ b/lib/src/common/util/utils.dart @@ -1,4 +1,32 @@ -part of github.common; +// ignore_for_file: constant_identifier_names + +import 'package:github/src/common.dart'; +import 'package:meta/meta.dart'; + +/// A Json encodable class that mimics an enum, +/// but with a String value that is used for serialization. +@immutable +abstract class EnumWithValue { + final String? value; + + /// The value will be used when [toJson] or [toString] will be called. + /// It will also be used to check if two [EnumWithValue] are equal. + const EnumWithValue(this.value); + + @override + String toString() => value ?? 'null'; + + /// Returns the String value of this. + String toJson() => value ?? 'null'; + + /// True iff [other] is an [EnumWithValue] with the same value as this object. + @override + bool operator ==(Object other) => + other is EnumWithValue && value == other.value; + + @override + int get hashCode => value.hashCode; +} /// Marks something as not being ready or complete. class NotReadyYet { @@ -8,14 +36,6 @@ class NotReadyYet { const NotReadyYet(this.message); } -/// Specifies the original API Field Name -class ApiName { - /// Original API Field Name - final String name; - - const ApiName(this.name); -} - /// Specifies that something should be only used when the specified condition is met. class OnlyWhen { /// Condition @@ -27,18 +47,22 @@ class OnlyWhen { /// Converts the [date] to GitHub's ISO-8601 format: /// /// The format is "YYYY-MM-DDTHH:mm:ssZ" -String dateToGitHubIso8601(DateTime date) { +String? dateToGitHubIso8601(DateTime? date) { + if (date == null) { + return null; + } // Regex removes the milliseconds. return date.toUtc().toIso8601String().replaceAll(githubDateRemoveRegExp, ''); } RepositorySlug slugFromAPIUrl(String url) { - var split = url.split("/"); - var i = split.indexOf("repos") + 1; - var parts = split.sublist(i, i + 2); - return new RepositorySlug(parts[0], parts[1]); + final split = url.split('/'); + final i = split.indexOf('repos') + 1; + final parts = split.sublist(i, i + 2); + return RepositorySlug(parts[0], parts[1]); } +// ignore: avoid_classes_with_only_static_members abstract class StatusCodes { static const int OK = 200; static const int CREATED = 201; @@ -70,3 +94,90 @@ abstract class StatusCodes { static bool isClientError(int code) => code > 400 && code < 500; } + +final RegExp githubDateRemoveRegExp = RegExp(r'\.\d*'); + +const v3ApiMimeType = 'application/vnd.github.v3+json'; + +String buildQueryString(Map params) { + final queryString = StringBuffer(); + + if (params.isNotEmpty && !params.values.every((value) => value == null)) { + queryString.write('?'); + } + + var i = 0; + for (final key in params.keys) { + i++; + if (params[key] == null) { + continue; + } + queryString.write('$key=${Uri.encodeComponent(params[key].toString())}'); + if (i != params.keys.length) { + queryString.write('&'); + } + } + return queryString.toString(); +} + +dynamic copyOf(dynamic input) { + if (input is Iterable) { + return List.from(input); + } else if (input is Map) { + return Map.from(input); + } else { + throw Exception('type could not be copied'); + } +} + +/// Puts a [name] and [value] into the [map] if [value] is not null. If [value] +/// is null, nothing is added. +void putValue(String name, dynamic value, Map map) { + if (value != null) { + map[name] = value; + } +} + +List> mapToList(Map input) { + final out = >[]; + for (final key in input.keys) { + out.add(MapEntry(key, input[key])); + } + return out; +} + +/// Returns a new map containing only the entries of [input] whose value is not null. +/// +/// If [recursive] is true, nested maps are also filtered. +Map createNonNullMap(Map input, {bool recursive = true}) { + final map = {}; + for (final entry in input.entries) { + if (entry.value != null) { + map[entry.key] = recursive && entry.value is Map + ? createNonNullMap(entry.value as Map, recursive: recursive) as V? + : entry.value; + } + } + return map; +} + +// TODO: only used in test – delete? +int parseFancyNumber(String input) { + input = input.trim(); + if (input.contains(',')) { + input = input.replaceAll(',', ''); + } + + const multipliers = {'h': 100, 'k': 1000, 'ht': 100000, 'm': 1000000}; + int value; + + if (!multipliers.keys.any((m) => input.endsWith(m))) { + value = int.parse(input); + } else { + final m = multipliers.keys.firstWhere((m) => input.endsWith(m)); + input = input.substring(0, input.length - m.length); + value = num.parse(input) * multipliers[m]! as int; + } + + return value; +} diff --git a/lib/src/common/xplat_common.dart b/lib/src/common/xplat_common.dart new file mode 100644 index 00000000..1c60f906 --- /dev/null +++ b/lib/src/common/xplat_common.dart @@ -0,0 +1,30 @@ +import 'package:github/src/common.dart'; + +/// Looks for GitHub Authentication information from the current environment. +/// +/// If in a browser context, it will look through query string parameters first +/// and then sessionStorage. +/// +/// If in a server, command line or Flutter context it will use the system environment variables. +/// +/// In both contexts it delegates to [findAuthenticationInMap] to find the +/// github token or username and password. +Authentication findAuthenticationFromEnvironment() => + const Authentication.anonymous(); + +/// Checks the passed in map for keys in [COMMON_GITHUB_TOKEN_ENV_KEYS]. +/// The first one that exists is used as the github token to call [Authentication.withToken] with. +/// If the above fails, the GITHUB_USERNAME and GITHUB_PASSWORD keys will be checked. +/// If those keys both exist, then [Authentication.basic] will be used. +Authentication? findAuthenticationInMap(Map map) { + for (final key in COMMON_GITHUB_TOKEN_ENV_KEYS) { + if (map.containsKey(key)) { + return Authentication.withToken(map[key]); + } + if (map['GITHUB_USERNAME'] is String && map['GITHUB_PASSWORD'] is String) { + return Authentication.basic( + map['GITHUB_USERNAME'], map['GITHUB_PASSWORD']); + } + } + return null; +} diff --git a/lib/src/const/language_color.dart b/lib/src/const/language_color.dart new file mode 100644 index 00000000..95bfc175 --- /dev/null +++ b/lib/src/const/language_color.dart @@ -0,0 +1,636 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND +// VERSION OF 2022-03-13T22:39:42.882755 + +const languageColors = { + '1C Enterprise': '#814CCC', + '2-Dimensional Array': '#38761D', + '4D': '#004289', + 'ABAP': '#E8274B', + 'ABAP CDS': '#555E25', + 'ABNF': '#EDEDED', + 'AGS Script': '#B9D9FF', + 'AIDL': '#34EB6B', + 'AL': '#3AA2B5', + 'AMPL': '#E6EFBB', + 'ANTLR': '#9DC3FF', + 'API Blueprint': '#2ACCA8', + 'APL': '#5A8164', + 'ASL': '#EDEDED', + 'ASN.1': '#EDEDED', + 'ASP.NET': '#9400FF', + 'ATS': '#1AC620', + 'ActionScript': '#882B0F', + 'Ada': '#02F88C', + 'Adobe Font Metrics': '#FA0F00', + 'Agda': '#315665', + 'Alloy': '#64C800', + 'Alpine Abuild': '#0D597F', + 'Altium Designer': '#A89663', + 'AngelScript': '#C7D7DC', + 'Ant Build System': '#A9157E', + 'ApacheConf': '#D12127', + 'Apex': '#1797C0', + 'Apollo Guidance Computer': '#0B3D91', + 'AppleScript': '#101F1F', + 'Arc': '#AA2AFE', + 'AsciiDoc': '#73A0C5', + 'AspectJ': '#A957B0', + 'Assembly': '#6E4C13', + 'Astro': '#FF5A03', + 'Asymptote': '#FF0000', + 'Augeas': '#9CC134', + 'AutoHotkey': '#6594B9', + 'AutoIt': '#1C3552', + 'Avro IDL': '#0040FF', + 'Awk': '#C30E9B', + 'BASIC': '#FF0000', + 'Ballerina': '#FF5000', + 'Batchfile': '#C1F12E', + 'Beef': '#A52F4E', + 'Befunge': '#EDEDED', + 'BibTeX': '#778899', + 'Bicep': '#519ABA', + 'Bison': '#6A463F', + 'BitBake': '#00BCE4', + 'Blade': '#F7523F', + 'BlitzBasic': '#00FFAE', + 'BlitzMax': '#CD6400', + 'Bluespec': '#12223C', + 'Boo': '#D4BEC1', + 'Boogie': '#C80FA0', + 'Brainfuck': '#2F2530', + 'Brightscript': '#662D91', + 'Browserslist': '#FFD539', + 'C': '#555555', + 'C#': '#178600', + 'C++': '#F34B7D', + 'C-ObjDump': '#EDEDED', + 'C2hs Haskell': '#EDEDED', + 'CIL': '#EDEDED', + 'CLIPS': '#00A300', + 'CMake': '#DA3434', + 'COBOL': '#EDEDED', + 'CODEOWNERS': '#EDEDED', + 'COLLADA': '#F1A42B', + 'CSON': '#244776', + 'CSS': '#563D7C', + 'CSV': '#237346', + 'CUE': '#5886E1', + 'CWeb': '#00007A', + 'Cabal Config': '#483465', + 'Cairo': '#FF4A48', + 'Cap\'n Proto': '#C42727', + 'CartoCSS': '#EDEDED', + 'Ceylon': '#DFA535', + 'Chapel': '#8DC63F', + 'Charity': '#EDEDED', + 'ChucK': '#3F8000', + 'Cirru': '#CCCCFF', + 'Clarion': '#DB901E', + 'Clarity': '#5546FF', + 'Classic ASP': '#6A40FD', + 'Clean': '#3F85AF', + 'Click': '#E4E6F3', + 'Clojure': '#DB5855', + 'Closure Templates': '#0D948F', + 'Cloud Firestore Security Rules': '#FFA000', + 'CoNLL-U': '#EDEDED', + 'CodeQL': '#140F46', + 'CoffeeScript': '#244776', + 'ColdFusion': '#ED2CD6', + 'ColdFusion CFC': '#ED2CD6', + 'Common Lisp': '#3FB68B', + 'Common Workflow Language': '#B5314C', + 'Component Pascal': '#B0CE4E', + 'Cool': '#EDEDED', + 'Coq': '#D0B68C', + 'Cpp-ObjDump': '#EDEDED', + 'Creole': '#EDEDED', + 'Crystal': '#000100', + 'Csound': '#1A1A1A', + 'Csound Document': '#1A1A1A', + 'Csound Score': '#1A1A1A', + 'Cuda': '#3A4E3A', + 'Cue Sheet': '#EDEDED', + 'Curry': '#531242', + 'Cycript': '#EDEDED', + 'Cython': '#FEDF5B', + 'D': '#BA595E', + 'D-ObjDump': '#EDEDED', + 'DIGITAL Command Language': '#EDEDED', + 'DM': '#447265', + 'DNS Zone': '#EDEDED', + 'DTrace': '#EDEDED', + 'Dafny': '#FFEC25', + 'Darcs Patch': '#8EFF23', + 'Dart': '#00B4AB', + 'DataWeave': '#003A52', + 'Debian Package Control File': '#D70751', + 'DenizenScript': '#FBEE96', + 'Dhall': '#DFAFFF', + 'Diff': '#EDEDED', + 'DirectX 3D File': '#AACE60', + 'Dockerfile': '#384D54', + 'Dogescript': '#CCA760', + 'Dylan': '#6C616E', + 'E': '#CCCE35', + 'E-mail': '#EDEDED', + 'EBNF': '#EDEDED', + 'ECL': '#8A1267', + 'ECLiPSe': '#001D9D', + 'EJS': '#A91E50', + 'EQ': '#A78649', + 'Eagle': '#EDEDED', + 'Earthly': '#2AF0FF', + 'Easybuild': '#069406', + 'Ecere Projects': '#913960', + 'EditorConfig': '#FFF1F2', + 'Edje Data Collection': '#EDEDED', + 'Eiffel': '#4D6977', + 'Elixir': '#6E4A7E', + 'Elm': '#60B5CC', + 'Emacs Lisp': '#C065DB', + 'EmberScript': '#FFF4F3', + 'Erlang': '#B83998', + 'Euphoria': '#FF790B', + 'F#': '#B845FC', + 'F*': '#572E30', + 'FIGlet Font': '#FFDDBB', + 'FLUX': '#88CCFF', + 'Factor': '#636746', + 'Fancy': '#7B9DB4', + 'Fantom': '#14253C', + 'Faust': '#C37240', + 'Fennel': '#FFF3D7', + 'Filebench WML': '#F6B900', + 'Filterscript': '#EDEDED', + 'Fluent': '#FFCC33', + 'Formatted': '#EDEDED', + 'Forth': '#341708', + 'Fortran': '#4D41B1', + 'Fortran Free Form': '#4D41B1', + 'FreeBasic': '#867DB1', + 'FreeMarker': '#0050B2', + 'Frege': '#00CAFE', + 'Futhark': '#5F021F', + 'G-code': '#D08CF2', + 'GAML': '#FFC766', + 'GAMS': '#F49A22', + 'GAP': '#0000CC', + 'GCC Machine Description': '#FFCFAB', + 'GDB': '#EDEDED', + 'GDScript': '#355570', + 'GEDCOM': '#003058', + 'GLSL': '#5686A5', + 'GN': '#EDEDED', + 'GSC': '#FF6800', + 'Game Maker Language': '#71B417', + 'Gemfile.lock': '#701516', + 'Genie': '#FB855D', + 'Genshi': '#951531', + 'Gentoo Ebuild': '#9400FF', + 'Gentoo Eclass': '#9400FF', + 'Gerber Image': '#D20B00', + 'Gettext Catalog': '#EDEDED', + 'Gherkin': '#5B2063', + 'Git Attributes': '#F44D27', + 'Git Config': '#F44D27', + 'Gleam': '#FFAFF3', + 'Glyph': '#C1AC7F', + 'Glyph Bitmap Distribution Format': '#EDEDED', + 'Gnuplot': '#F0A9F0', + 'Go': '#00ADD8', + 'Go Checksums': '#00ADD8', + 'Go Module': '#00ADD8', + 'Golo': '#88562A', + 'Gosu': '#82937F', + 'Grace': '#615F8B', + 'Gradle': '#02303A', + 'Grammatical Framework': '#FF0000', + 'Graph Modeling Language': '#EDEDED', + 'GraphQL': '#E10098', + 'Graphviz (DOT)': '#2596BE', + 'Groovy': '#4298B8', + 'Groovy Server Pages': '#4298B8', + 'HAProxy': '#106DA9', + 'HCL': '#EDEDED', + 'HLSL': '#AACE60', + 'HTML': '#E34C26', + 'HTML+ECR': '#2E1052', + 'HTML+EEX': '#6E4A7E', + 'HTML+ERB': '#701516', + 'HTML+PHP': '#4F5D95', + 'HTML+Razor': '#512BE4', + 'HTTP': '#005C9C', + 'HXML': '#F68712', + 'Hack': '#878787', + 'Haml': '#ECE2A9', + 'Handlebars': '#F7931E', + 'Harbour': '#0E60E3', + 'Haskell': '#5E5086', + 'Haxe': '#DF7900', + 'HiveQL': '#DCE200', + 'HolyC': '#FFEFAF', + 'Hy': '#7790B2', + 'HyPhy': '#EDEDED', + 'IDL': '#A3522F', + 'IGOR Pro': '#0000CC', + 'INI': '#D1DBE0', + 'IRC log': '#EDEDED', + 'Idris': '#B30000', + 'Ignore List': '#000000', + 'ImageJ Macro': '#99AAFF', + 'Inform 7': '#EDEDED', + 'Inno Setup': '#264B99', + 'Io': '#A9188D', + 'Ioke': '#078193', + 'Isabelle': '#FEFE00', + 'Isabelle ROOT': '#FEFE00', + 'J': '#9EEDFF', + 'JAR Manifest': '#B07219', + 'JFlex': '#DBCA00', + 'JSON': '#292929', + 'JSON with Comments': '#292929', + 'JSON5': '#267CB9', + 'JSONLD': '#0C479C', + 'JSONiq': '#40D47E', + 'Janet': '#0886A5', + 'Jasmin': '#D03600', + 'Java': '#B07219', + 'Java Properties': '#2A6277', + 'Java Server Pages': '#2A6277', + 'JavaScript': '#F1E05A', + 'JavaScript+ERB': '#F1E05A', + 'Jest Snapshot': '#15C213', + 'Jinja': '#A52A22', + 'Jison': '#56B3CB', + 'Jison Lex': '#56B3CB', + 'Jolie': '#843179', + 'Jsonnet': '#0064BD', + 'Julia': '#A270BA', + 'Jupyter Notebook': '#DA5B0B', + 'KRL': '#28430A', + 'Kaitai Struct': '#773B37', + 'KakouneScript': '#6F8042', + 'KiCad Layout': '#2F4AAB', + 'KiCad Legacy Layout': '#2F4AAB', + 'KiCad Schematic': '#2F4AAB', + 'Kit': '#EDEDED', + 'Kotlin': '#A97BFF', + 'Kusto': '#EDEDED', + 'LFE': '#4C3023', + 'LLVM': '#185619', + 'LOLCODE': '#CC9900', + 'LSL': '#3D9970', + 'LTspice Symbol': '#EDEDED', + 'LabVIEW': '#FEDE06', + 'Lark': '#2980B9', + 'Lasso': '#999999', + 'Latte': '#F2A542', + 'Lean': '#EDEDED', + 'Less': '#1D365D', + 'Lex': '#DBCA00', + 'LilyPond': '#9CCC7C', + 'Limbo': '#EDEDED', + 'Linker Script': '#EDEDED', + 'Linux Kernel Module': '#EDEDED', + 'Liquid': '#67B8DE', + 'Literate Agda': '#315665', + 'Literate CoffeeScript': '#244776', + 'Literate Haskell': '#5E5086', + 'LiveScript': '#499886', + 'Logos': '#EDEDED', + 'Logtalk': '#295B9A', + 'LookML': '#652B81', + 'LoomScript': '#EDEDED', + 'Lua': '#000080', + 'M': '#EDEDED', + 'M4': '#EDEDED', + 'M4Sugar': '#EDEDED', + 'MATLAB': '#E16737', + 'MAXScript': '#00A6A6', + 'MLIR': '#5EC8DB', + 'MQL4': '#62A8D6', + 'MQL5': '#4A76B8', + 'MTML': '#B7E1F4', + 'MUF': '#EDEDED', + 'Macaulay2': '#D8FFFF', + 'Makefile': '#427819', + 'Mako': '#7E858D', + 'Markdown': '#083FA1', + 'Marko': '#42BFF2', + 'Mask': '#F97732', + 'Mathematica': '#DD1100', + 'Maven POM': '#EDEDED', + 'Max': '#C4A79C', + 'Mercury': '#FF2B2B', + 'Meson': '#007800', + 'Metal': '#8F14E9', + 'Microsoft Developer Studio Project': '#EDEDED', + 'Microsoft Visual Studio Solution': '#EDEDED', + 'MiniD': '#EDEDED', + 'MiniYAML': '#FF1111', + 'Mint': '#02B046', + 'Mirah': '#C7A938', + 'Modelica': '#DE1D31', + 'Modula-2': '#10253F', + 'Modula-3': '#223388', + 'Module Management System': '#EDEDED', + 'Monkey': '#EDEDED', + 'Moocode': '#EDEDED', + 'MoonScript': '#FF4585', + 'Motoko': '#FBB03B', + 'Motorola 68K Assembly': '#005DAA', + 'Muse': '#EDEDED', + 'Mustache': '#724B3B', + 'Myghty': '#EDEDED', + 'NASL': '#EDEDED', + 'NCL': '#28431F', + 'NEON': '#EDEDED', + 'NL': '#EDEDED', + 'NPM Config': '#CB3837', + 'NSIS': '#EDEDED', + 'NWScript': '#111522', + 'Nearley': '#990000', + 'Nemerle': '#3D3C6E', + 'NetLinx': '#0AA0FF', + 'NetLinx+ERB': '#747FAA', + 'NetLogo': '#FF6375', + 'NewLisp': '#87AED7', + 'Nextflow': '#3AC486', + 'Nginx': '#009639', + 'Nim': '#FFC200', + 'Ninja': '#EDEDED', + 'Nit': '#009917', + 'Nix': '#7E7EFF', + 'Nu': '#C9DF40', + 'NumPy': '#9C8AF9', + 'Nunjucks': '#3D8137', + 'OCaml': '#3BE133', + 'ObjDump': '#EDEDED', + 'Object Data Instance Notation': '#EDEDED', + 'ObjectScript': '#424893', + 'Objective-C': '#438EFF', + 'Objective-C++': '#6866FB', + 'Objective-J': '#FF0C5A', + 'Odin': '#60AFFE', + 'Omgrofl': '#CABBFF', + 'Opa': '#EDEDED', + 'Opal': '#F7EDE0', + 'Open Policy Agent': '#7D9199', + 'OpenCL': '#ED2E2D', + 'OpenEdge ABL': '#5CE600', + 'OpenQASM': '#AA70FF', + 'OpenRC runscript': '#EDEDED', + 'OpenSCAD': '#E5CD45', + 'OpenStep Property List': '#EDEDED', + 'OpenType Feature File': '#EDEDED', + 'Org': '#77AA99', + 'Ox': '#EDEDED', + 'Oxygene': '#CDD0E3', + 'Oz': '#FAB738', + 'P4': '#7055B5', + 'PEG.js': '#234D6B', + 'PHP': '#4F5D95', + 'PLSQL': '#DAD8D8', + 'PLpgSQL': '#336790', + 'POV-Ray SDL': '#6BAC65', + 'Pan': '#CC0000', + 'Papyrus': '#6600CC', + 'Parrot': '#F3CA0A', + 'Parrot Assembly': '#EDEDED', + 'Parrot Internal Representation': '#EDEDED', + 'Pascal': '#E3F171', + 'Pawn': '#DBB284', + 'Pep8': '#C76F5B', + 'Perl': '#0298C3', + 'Pic': '#EDEDED', + 'Pickle': '#EDEDED', + 'PicoLisp': '#6067AF', + 'PigLatin': '#FCD7DE', + 'Pike': '#005390', + 'PlantUML': '#EDEDED', + 'Pod': '#EDEDED', + 'Pod 6': '#EDEDED', + 'PogoScript': '#D80074', + 'Pony': '#EDEDED', + 'PostCSS': '#DC3A0C', + 'PostScript': '#DA291C', + 'PowerBuilder': '#8F0F8D', + 'PowerShell': '#012456', + 'Prisma': '#0C344B', + 'Processing': '#0096D8', + 'Procfile': '#3B2F63', + 'Proguard': '#EDEDED', + 'Prolog': '#74283C', + 'Promela': '#DE0000', + 'Propeller Spin': '#7FA2A7', + 'Protocol Buffer': '#EDEDED', + 'Protocol Buffer Text Format': '#EDEDED', + 'Public Key': '#EDEDED', + 'Pug': '#A86454', + 'Puppet': '#302B6D', + 'Pure Data': '#EDEDED', + 'PureBasic': '#5A6986', + 'PureScript': '#1D222D', + 'Python': '#3572A5', + 'Python console': '#3572A5', + 'Python traceback': '#3572A5', + 'Q#': '#FED659', + 'QML': '#44A51C', + 'QMake': '#EDEDED', + 'Qt Script': '#00B841', + 'Quake': '#882233', + 'R': '#198CE7', + 'RAML': '#77D9FB', + 'RDoc': '#701516', + 'REALbasic': '#EDEDED', + 'REXX': '#D90E09', + 'RMarkdown': '#198CE7', + 'RPC': '#EDEDED', + 'RPGLE': '#2BDE21', + 'RPM Spec': '#EDEDED', + 'RUNOFF': '#665A4E', + 'Racket': '#3C5CAA', + 'Ragel': '#9D5200', + 'Raku': '#0000FB', + 'Rascal': '#FFFAA0', + 'Raw token data': '#EDEDED', + 'ReScript': '#ED5051', + 'Readline Config': '#EDEDED', + 'Reason': '#FF5847', + 'Rebol': '#358A5B', + 'Record Jar': '#0673BA', + 'Red': '#F50000', + 'Redcode': '#EDEDED', + 'Redirect Rules': '#EDEDED', + 'Regular Expression': '#009A00', + 'Ren\'Py': '#FF7F7F', + 'RenderScript': '#EDEDED', + 'Rich Text Format': '#EDEDED', + 'Ring': '#2D54CB', + 'Riot': '#A71E49', + 'RobotFramework': '#00C0B5', + 'Roff': '#ECDEBE', + 'Roff Manpage': '#ECDEBE', + 'Rouge': '#CC0088', + 'Ruby': '#701516', + 'Rust': '#DEA584', + 'SAS': '#B34936', + 'SCSS': '#C6538C', + 'SELinux Policy': '#EDEDED', + 'SMT': '#EDEDED', + 'SPARQL': '#0C4597', + 'SQF': '#3F3F3F', + 'SQL': '#E38C00', + 'SQLPL': '#E38C00', + 'SRecode Template': '#348A34', + 'SSH Config': '#EDEDED', + 'STON': '#EDEDED', + 'SVG': '#FF9900', + 'SWIG': '#EDEDED', + 'Sage': '#EDEDED', + 'SaltStack': '#646464', + 'Sass': '#A53B70', + 'Scala': '#C22D40', + 'Scaml': '#BD181A', + 'Scheme': '#1E4AEC', + 'Scilab': '#CA0F21', + 'Self': '#0579AA', + 'ShaderLab': '#222C37', + 'Shell': '#89E051', + 'ShellCheck Config': '#CECFCB', + 'ShellSession': '#EDEDED', + 'Shen': '#120F14', + 'Sieve': '#EDEDED', + 'Singularity': '#64E6AD', + 'Slash': '#007EFF', + 'Slice': '#003FA2', + 'Slim': '#2B2B2B', + 'SmPL': '#C94949', + 'Smali': '#EDEDED', + 'Smalltalk': '#596706', + 'Smarty': '#F0C040', + 'Solidity': '#AA6746', + 'Soong': '#EDEDED', + 'SourcePawn': '#F69E1D', + 'Spline Font Database': '#EDEDED', + 'Squirrel': '#800000', + 'Stan': '#B2011D', + 'Standard ML': '#DC566D', + 'Starlark': '#76D275', + 'Stata': '#1A5F91', + 'StringTemplate': '#3FB34F', + 'Stylus': '#FF6347', + 'SubRip Text': '#9E0101', + 'SugarSS': '#2FCC9F', + 'SuperCollider': '#46390B', + 'Svelte': '#FF3E00', + 'Swift': '#F05138', + 'SystemVerilog': '#DAE1C2', + 'TI Program': '#A0AA87', + 'TLA': '#4B0079', + 'TOML': '#9C4221', + 'TSQL': '#E38C00', + 'TSV': '#237346', + 'TSX': '#2B7489', + 'TXL': '#0178B8', + 'Tcl': '#E4CC98', + 'Tcsh': '#EDEDED', + 'TeX': '#3D6117', + 'Tea': '#EDEDED', + 'Terra': '#00004C', + 'Texinfo': '#EDEDED', + 'Text': '#EDEDED', + 'TextMate Properties': '#DF66E4', + 'Textile': '#FFE7AC', + 'Thrift': '#D12127', + 'Turing': '#CF142B', + 'Turtle': '#EDEDED', + 'Twig': '#C1D026', + 'Type Language': '#EDEDED', + 'TypeScript': '#2B7489', + 'Unified Parallel C': '#4E3617', + 'Unity3D Asset': '#222C37', + 'Unix Assembly': '#EDEDED', + 'Uno': '#9933CC', + 'UnrealScript': '#A54C4D', + 'UrWeb': '#CCCCEE', + 'V': '#4F87C4', + 'VBA': '#867DB1', + 'VBScript': '#15DCDC', + 'VCL': '#148AA8', + 'VHDL': '#ADB2CB', + 'Vala': '#FBE5CD', + 'Valve Data Format': '#F26025', + 'Verilog': '#B2B7F8', + 'Vim Help File': '#199F4B', + 'Vim Script': '#199F4B', + 'Vim Snippet': '#199F4B', + 'Visual Basic .NET': '#945DB7', + 'Volt': '#1F1F1F', + 'Vue': '#41B883', + 'Vyper': '#2980B9', + 'Wavefront Material': '#EDEDED', + 'Wavefront Object': '#EDEDED', + 'Web Ontology Language': '#5B70BD', + 'WebAssembly': '#04133B', + 'WebIDL': '#EDEDED', + 'WebVTT': '#EDEDED', + 'Wget Config': '#EDEDED', + 'Wikitext': '#FC5757', + 'Windows Registry Entries': '#52D5FF', + 'Witcher Script': '#FF0000', + 'Wollok': '#A23738', + 'World of Warcraft Addon Data': '#F7E43F', + 'X BitMap': '#EDEDED', + 'X Font Directory Index': '#EDEDED', + 'X PixMap': '#EDEDED', + 'X10': '#4B6BEF', + 'XC': '#99DA07', + 'XCompose': '#EDEDED', + 'XML': '#0060AC', + 'XML Property List': '#0060AC', + 'XPages': '#EDEDED', + 'XProc': '#EDEDED', + 'XQuery': '#5232E7', + 'XS': '#EDEDED', + 'XSLT': '#EB8CEB', + 'Xojo': '#81BD41', + 'Xonsh': '#285EEF', + 'Xtend': '#24255D', + 'YAML': '#CB171E', + 'YANG': '#EDEDED', + 'YARA': '#220000', + 'YASnippet': '#32AB90', + 'Yacc': '#4B6C4B', + 'ZAP': '#0D665E', + 'ZIL': '#DC75E5', + 'Zeek': '#EDEDED', + 'ZenScript': '#00BCD1', + 'Zephir': '#118F9E', + 'Zig': '#EC915C', + 'Zimpl': '#D67711', + 'cURL Config': '#EDEDED', + 'desktop': '#EDEDED', + 'dircolors': '#EDEDED', + 'eC': '#913960', + 'edn': '#EDEDED', + 'fish': '#4AAE47', + 'hoon': '#00B171', + 'jq': '#C7254E', + 'kvlang': '#1DA6E0', + 'mIRC Script': '#3D57C3', + 'mcfunction': '#E22837', + 'mupad': '#244963', + 'nanorc': '#2D004D', + 'nesC': '#94B0C7', + 'ooc': '#B0B77E', + 'q': '#0040CD', + 'reStructuredText': '#141414', + 'robots.txt': '#EDEDED', + 'sed': '#64B970', + 'wdl': '#42F1F4', + 'wisp': '#7582D1', + 'xBase': '#403A40', +}; diff --git a/lib/src/const/token_env_keys.dart b/lib/src/const/token_env_keys.dart new file mode 100644 index 00000000..7a65804e --- /dev/null +++ b/lib/src/const/token_env_keys.dart @@ -0,0 +1,9 @@ +// ignore: constant_identifier_names +const List COMMON_GITHUB_TOKEN_ENV_KEYS = [ + 'GITHUB_ADMIN_TOKEN', + 'GITHUB_DART_TOKEN', + 'GITHUB_API_TOKEN', + 'GITHUB_TOKEN', + 'HOMEBREW_GITHUB_API_TOKEN', + 'MACHINE_GITHUB_API_TOKEN' +]; diff --git a/lib/src/server/hooks.dart b/lib/src/server/hooks.dart index 96972575..ae3fa0bf 100644 --- a/lib/src/server/hooks.dart +++ b/lib/src/server/hooks.dart @@ -1,36 +1,41 @@ -import "dart:async"; -import "dart:convert"; -import "dart:io"; +import 'dart:async'; +import 'dart:convert'; +import 'dart:io'; -import "../common.dart"; +import 'package:json_annotation/json_annotation.dart'; + +import '../common.dart'; +import '../common/model/changes.dart'; + +part 'hooks.g.dart'; class HookMiddleware { // TODO: Close this, but where? final StreamController _eventController = - new StreamController(); + StreamController(); Stream get onEvent => _eventController.stream; void handleHookRequest(HttpRequest request) { - if (request.method != "POST") { + if (request.method != 'POST') { request.response - ..write("Only POST is Supported") + ..write('Only POST is Supported') ..close(); return; } - if (request.headers.value("X-GitHub-Event") == null) { + if (request.headers.value('X-GitHub-Event') == null) { request.response - ..write("X-GitHub-Event must be specified.") + ..write('X-GitHub-Event must be specified.') ..close(); return; } - request.transform(const Utf8Decoder()).join().then((content) { - _eventController.add(new HookEvent.fromJSON( - request.headers.value("X-GitHub-Event"), - JSON.decode(content) as Map)); + const Utf8Decoder().bind(request).join().then((content) { + _eventController.add(HookEvent.fromJson( + request.headers.value('X-GitHub-Event'), + jsonDecode(content) as Map?)); request.response - ..write(JSON.encode({"handled": _eventController.hasListener})) + ..write(GitHubJson.encode({'handled': _eventController.hasListener})) ..close(); }); } @@ -40,20 +45,20 @@ class HookServer extends HookMiddleware { final String host; final int port; - HttpServer _server; + late HttpServer _server; - HookServer(this.port, [this.host = "0.0.0.0"]); + HookServer(this.port, [this.host = '0.0.0.0']); void start() { HttpServer.bind(host, port).then((HttpServer server) { _server = server; server.listen((request) { - if (request.uri.path == "/hook") { + if (request.uri.path == '/hook') { handleHookRequest(request); } else { request.response ..statusCode = 404 - ..write("404 - Not Found") + ..write('404 - Not Found') ..close(); } }); @@ -66,101 +71,172 @@ class HookServer extends HookMiddleware { class HookEvent { HookEvent(); - factory HookEvent.fromJSON(String event, Map json) { - if (event == "pull_request") { - return PullRequestEvent.fromJSON(json); - } else if (event == "issues") { - return IssueEvent.fromJSON(json); - } else if (event == "issue_comment") { - return IssueCommentEvent.fromJSON(json); - } else if (event == "repository") { - return RepositoryEvent.fromJSON(json); + factory HookEvent.fromJson(String? event, Map? json) { + if (event == 'pull_request') { + return PullRequestEvent.fromJson(json!); + } else if (event == 'issues') { + return IssueEvent.fromJson(json!); + } else if (event == 'issue_comment') { + return IssueCommentEvent.fromJson(json!); + } else if (event == 'repository') { + return RepositoryEvent.fromJson(json!); } - return new UnknownHookEvent(event, json); + return UnknownHookEvent(event, json); } } class UnknownHookEvent extends HookEvent { - final String event; - final Map data; + final String? event; + final Map? data; UnknownHookEvent(this.event, this.data); } +@JsonSerializable() +class CheckRunEvent extends HookEvent { + CheckRunEvent({ + this.action, + this.checkRun, + this.sender, + this.repository, + }); + + factory CheckRunEvent.fromJson(Map input) => + _$CheckRunEventFromJson(input); + CheckRun? checkRun; + String? action; + User? sender; + Repository? repository; + + Map toJson() => _$CheckRunEventToJson(this); +} + +@JsonSerializable() +class CheckSuiteEvent extends HookEvent { + CheckSuiteEvent({ + this.action, + this.checkSuite, + this.repository, + this.sender, + }); + + String? action; + CheckSuite? checkSuite; + Repository? repository; + User? sender; + + factory CheckSuiteEvent.fromJson(Map input) => + _$CheckSuiteEventFromJson(input); + Map toJson() => _$CheckSuiteEventToJson(this); +} + +@JsonSerializable() class RepositoryEvent extends HookEvent { - String action; - Repository repository; - User sender; - - static RepositoryEvent fromJSON(Map json) { - return new RepositoryEvent() - ..action = json["action"] - ..repository = - Repository.fromJSON(json["repository"] as Map) - ..sender = User.fromJSON(json["sender"] as Map); - } + RepositoryEvent({ + this.action, + this.repository, + this.sender, + }); + String? action; + Repository? repository; + User? sender; + + factory RepositoryEvent.fromJson(Map input) => + _$RepositoryEventFromJson(input); + Map toJson() => _$RepositoryEventToJson(this); } +@JsonSerializable() class IssueCommentEvent extends HookEvent { - String action; - Issue issue; - IssueComment comment; - - static IssueCommentEvent fromJSON(Map json) { - return new IssueCommentEvent() - ..action = json["action"] - ..issue = Issue.fromJSON(json["issue"] as Map) - ..comment = - IssueComment.fromJSON(json["comment"] as Map); - } + IssueCommentEvent({ + this.action, + this.issue, + this.comment, + }); + String? action; + Issue? issue; + IssueComment? comment; + + factory IssueCommentEvent.fromJson(Map input) => + _$IssueCommentEventFromJson(input); + Map toJson() => _$IssueCommentEventToJson(this); } +@JsonSerializable() class ForkEvent extends HookEvent { - Repository forkee; - User sender; - - static ForkEvent fromJSON(Map json) { - return new ForkEvent() - ..forkee = Repository.fromJSON(json["forkee"] as Map) - ..sender = User.fromJSON(json["sender"] as Map); - } + ForkEvent({ + this.forkee, + this.sender, + }); + Repository? forkee; + User? sender; + + factory ForkEvent.fromJson(Map input) => + _$ForkEventFromJson(input); + Map toJson() => _$ForkEventToJson(this); } +@JsonSerializable() class IssueEvent extends HookEvent { - String action; - User assignee; - IssueLabel label; - Issue issue; - User sender; - Repository repository; - - static IssueEvent fromJSON(Map json) { - return new IssueEvent() - ..action = json["action"] - ..assignee = User.fromJSON(json["assignee"] as Map) - ..label = IssueLabel.fromJSON(json["label"] as Map) - ..issue = Issue.fromJSON(json["issue"] as Map) - ..repository = - Repository.fromJSON(json["repository"] as Map) - ..sender = User.fromJSON(json["sender"] as Map); - } + IssueEvent({ + this.action, + this.assignee, + this.label, + this.issue, + this.sender, + this.repository, + }); + String? action; + User? assignee; + IssueLabel? label; + Issue? issue; + User? sender; + Repository? repository; + + factory IssueEvent.fromJson(Map input) => + _$IssueEventFromJson(input); + Map toJson() => _$IssueEventToJson(this); } +@JsonSerializable() class PullRequestEvent extends HookEvent { - String action; - int number; - PullRequest pullRequest; - User sender; - Repository repository; - - static PullRequestEvent fromJSON(Map json) { - return new PullRequestEvent() - ..action = json["action"] - ..number = json["number"] - ..repository = - Repository.fromJSON(json["repository"] as Map) - ..pullRequest = - PullRequest.fromJSON(json["pull_request"] as Map) - ..sender = User.fromJSON(json["sender"] as Map); - } + PullRequestEvent({ + this.action, + this.number, + this.pullRequest, + this.sender, + this.repository, + this.changes, + }); + String? action; + int? number; + PullRequest? pullRequest; + User? sender; + Repository? repository; + Changes? changes; + + factory PullRequestEvent.fromJson(Map input) => + _$PullRequestEventFromJson(input); + Map toJson() => _$PullRequestEventToJson(this); +} + +@JsonSerializable(fieldRename: FieldRename.snake) +class CreateEvent extends HookEvent { + CreateEvent({ + this.ref, + this.refType, + this.pusherType, + this.repository, + this.sender, + }); + + factory CreateEvent.fromJson(Map input) => + _$CreateEventFromJson(input); + String? ref; + String? refType; + String? pusherType; + Repository? repository; + User? sender; + + Map toJson() => _$CreateEventToJson(this); } diff --git a/lib/src/server/hooks.g.dart b/lib/src/server/hooks.g.dart new file mode 100644 index 00000000..81cbb790 --- /dev/null +++ b/lib/src/server/hooks.g.dart @@ -0,0 +1,179 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'hooks.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +CheckRunEvent _$CheckRunEventFromJson(Map json) => + CheckRunEvent( + action: json['action'] as String?, + checkRun: json['check_run'] == null + ? null + : CheckRun.fromJson(json['check_run'] as Map), + sender: json['sender'] == null + ? null + : User.fromJson(json['sender'] as Map), + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + ); + +Map _$CheckRunEventToJson(CheckRunEvent instance) => + { + 'check_run': instance.checkRun, + 'action': instance.action, + 'sender': instance.sender, + 'repository': instance.repository, + }; + +CheckSuiteEvent _$CheckSuiteEventFromJson(Map json) => + CheckSuiteEvent( + action: json['action'] as String?, + checkSuite: json['check_suite'] == null + ? null + : CheckSuite.fromJson(json['check_suite'] as Map), + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + sender: json['sender'] == null + ? null + : User.fromJson(json['sender'] as Map), + ); + +Map _$CheckSuiteEventToJson(CheckSuiteEvent instance) => + { + 'action': instance.action, + 'check_suite': instance.checkSuite, + 'repository': instance.repository, + 'sender': instance.sender, + }; + +RepositoryEvent _$RepositoryEventFromJson(Map json) => + RepositoryEvent( + action: json['action'] as String?, + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + sender: json['sender'] == null + ? null + : User.fromJson(json['sender'] as Map), + ); + +Map _$RepositoryEventToJson(RepositoryEvent instance) => + { + 'action': instance.action, + 'repository': instance.repository, + 'sender': instance.sender, + }; + +IssueCommentEvent _$IssueCommentEventFromJson(Map json) => + IssueCommentEvent( + action: json['action'] as String?, + issue: json['issue'] == null + ? null + : Issue.fromJson(json['issue'] as Map), + comment: json['comment'] == null + ? null + : IssueComment.fromJson(json['comment'] as Map), + ); + +Map _$IssueCommentEventToJson(IssueCommentEvent instance) => + { + 'action': instance.action, + 'issue': instance.issue, + 'comment': instance.comment, + }; + +ForkEvent _$ForkEventFromJson(Map json) => ForkEvent( + forkee: json['forkee'] == null + ? null + : Repository.fromJson(json['forkee'] as Map), + sender: json['sender'] == null + ? null + : User.fromJson(json['sender'] as Map), + ); + +Map _$ForkEventToJson(ForkEvent instance) => { + 'forkee': instance.forkee, + 'sender': instance.sender, + }; + +IssueEvent _$IssueEventFromJson(Map json) => IssueEvent( + action: json['action'] as String?, + assignee: json['assignee'] == null + ? null + : User.fromJson(json['assignee'] as Map), + label: json['label'] == null + ? null + : IssueLabel.fromJson(json['label'] as Map), + issue: json['issue'] == null + ? null + : Issue.fromJson(json['issue'] as Map), + sender: json['sender'] == null + ? null + : User.fromJson(json['sender'] as Map), + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + ); + +Map _$IssueEventToJson(IssueEvent instance) => + { + 'action': instance.action, + 'assignee': instance.assignee, + 'label': instance.label, + 'issue': instance.issue, + 'sender': instance.sender, + 'repository': instance.repository, + }; + +PullRequestEvent _$PullRequestEventFromJson(Map json) => + PullRequestEvent( + action: json['action'] as String?, + number: (json['number'] as num?)?.toInt(), + pullRequest: json['pull_request'] == null + ? null + : PullRequest.fromJson(json['pull_request'] as Map), + sender: json['sender'] == null + ? null + : User.fromJson(json['sender'] as Map), + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + changes: json['changes'] == null + ? null + : Changes.fromJson(json['changes'] as Map), + ); + +Map _$PullRequestEventToJson(PullRequestEvent instance) => + { + 'action': instance.action, + 'number': instance.number, + 'pull_request': instance.pullRequest, + 'sender': instance.sender, + 'repository': instance.repository, + 'changes': instance.changes, + }; + +CreateEvent _$CreateEventFromJson(Map json) => CreateEvent( + ref: json['ref'] as String?, + refType: json['ref_type'] as String?, + pusherType: json['pusher_type'] as String?, + repository: json['repository'] == null + ? null + : Repository.fromJson(json['repository'] as Map), + sender: json['sender'] == null + ? null + : User.fromJson(json['sender'] as Map), + ); + +Map _$CreateEventToJson(CreateEvent instance) => + { + 'ref': instance.ref, + 'ref_type': instance.refType, + 'pusher_type': instance.pusherType, + 'repository': instance.repository, + 'sender': instance.sender, + }; diff --git a/lib/src/server/xplat_server.dart b/lib/src/server/xplat_server.dart new file mode 100644 index 00000000..a6c85d02 --- /dev/null +++ b/lib/src/server/xplat_server.dart @@ -0,0 +1,31 @@ +import 'dart:io'; +import 'package:github/src/common.dart'; +import 'package:github/src/common/xplat_common.dart' + show findAuthenticationInMap; + +export 'hooks.dart'; + +/// Looks for GitHub Authentication Information in the current process environment. +/// +/// Checks all the environment variables in [COMMON_GITHUB_TOKEN_ENV_KEYS] for tokens. +/// If the above fails, the GITHUB_USERNAME and GITHUB_PASSWORD keys will be checked. +Authentication findAuthenticationFromEnvironment() { + if (Platform.isMacOS) { + final result = Process.runSync( + 'security', const ['find-internet-password', '-g', '-s', 'github.com']); + + if (result.exitCode == 0) { + final out = result.stdout.toString(); + + var username = out.split('"acct"="')[1]; + username = username.substring(0, username.indexOf('\n')); + username = username.substring(0, username.length - 1); + var password = result.stderr.toString().split('password:')[1].trim(); + password = password.substring(1, password.length - 1); + return Authentication.basic(username.trim(), password.trim()); + } + } + + return findAuthenticationInMap(Platform.environment) ?? + const Authentication.anonymous(); +} diff --git a/lib/src/util.dart b/lib/src/util.dart deleted file mode 100644 index d711659c..00000000 --- a/lib/src/util.dart +++ /dev/null @@ -1,111 +0,0 @@ -final RegExp githubDateRemoveRegExp = new RegExp(r'\.\d*'); - -String buildQueryString(Map params) { - var queryString = new StringBuffer(); - - if (params.isNotEmpty && !params.values.every((value) => value == null)) { - queryString.write("?"); - } - - var i = 0; - for (var key in params.keys) { - i++; - if (params[key] == null) { - continue; - } - queryString.write("${key}=${Uri.encodeComponent(params[key].toString())}"); - if (i != params.keys.length) { - queryString.write("&"); - } - } - return queryString.toString(); -} - -dynamic copyOf(dynamic input) { - if (input is Iterable) { - return new List.from(input); - } else if (input is Map) { - return new Map.from(input); - } else { - throw "type could not be copied"; - } -} - -/// Puts a [name] and [value] into the [map] if [value] is not null. If [value] -/// is null, nothing is added. -void putValue(String name, dynamic value, Map map) { - if (value != null) { - map[name] = value; - } -} - -//TODO(kevmoo): use regex here. -Map parseLinkHeader(String input) { - var out = {}; - var parts = input.split(", "); - for (var part in parts) { - if (part[0] != "<") { - throw new FormatException("Invalid Link Header"); - } - var kv = part.split("; "); - var url = kv[0].substring(1); - url = url.substring(0, url.length - 1); - var key = kv[1]; - key = key.replaceAll('"', "").substring(4); - out[key] = url; - } - return out; -} - -List> mapToList(Map input) { - var out = > []; - for (var key in input.keys) { - out.add(new MapEntry(key, input[key])); - } - return out; -} - -class MapEntry { - final K key; - final V value; - - MapEntry(this.key, this.value); -} - -/// Internal method to handle null for parsing dates. -DateTime parseDateTime(String input) { - if (input == null) { - return null; - } - - return DateTime.parse(input); -} - -Map createNonNullMap(Map input) { - var map = {}; - for (var key in input.keys) { - if (input[key] != null) { - map[key] = input[key]; - } - } - return map; -} - -// TODO: only used in test – delete? -int parseFancyNumber(String input) { - input = input.trim(); - if (input.contains(",")) input = input.replaceAll(",", ""); - - var multipliers = {"h": 100, "k": 1000, "ht": 100000, "m": 1000000}; - int value; - - if (!multipliers.keys.any((m) => input.endsWith(m))) { - value = int.parse(input); - } else { - var m = multipliers.keys.firstWhere((m) => input.endsWith(m)); - input = input.substring(0, input.length - m.length); - value = num.parse(input) * multipliers[m]; - } - - return value; -} diff --git a/pubspec.yaml b/pubspec.yaml index 2267e255..eed1fec4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,17 +1,27 @@ name: github -version: 3.0.0 -author: Kenneth Endfinger -description: GitHub API Client Library -homepage: https://github.com/DirectMyFile/github.dart +version: 9.25.0 +description: A high-level GitHub API Client Library that uses Github's v3 API +homepage: https://github.com/SpinlockLabs/github.dart + environment: - sdk: '>=1.13.0 <2.0.0' + sdk: ^3.5.0 + dependencies: - html: '>=0.12.0 <0.14.0' - http: '^0.11.3' - quiver: '>=0.20.0 <0.25.0' - xml: '^2.0.0' - markdown: '^0.11.1' + http: ^1.0.0 + http_parser: ^4.0.0 + json_annotation: ^4.9.0 + meta: ^1.7.0 + dev_dependencies: - browser: '^0.10.0+2' - test: '^0.12.0' - mockito: '^1.0.1' + build_runner: ^2.2.1 + build_test: ^2.1.2 + build_web_compilers: '>=3.2.6 < 5.0.0' + collection: ^1.15.0 + dependency_validator: ^3.0.0 + json_serializable: ^6.6.1 + lints: ^5.0.0 + nock: ^1.1.3 + pub_semver: ^2.0.0 + test: ^1.21.6 + yaml: ^3.1.0 + yaml_edit: ^2.2.0 diff --git a/test/README.md b/test/README.md index 8c75b601..c05bf247 100644 --- a/test/README.md +++ b/test/README.md @@ -1,12 +1,12 @@ # Integration Tests -The integration tests will run against the live GitHub API. These tests will +The integration tests will run against the live GitHub API. These tests will verify that the library is properly coded against the actual behavior of the GitHub API. To run these tests a GitHub repository and OAuth token will need to be defined -in the `config/config.dart` file. +in the `config/config.dart` file. -**Warning:** The test will delete and recreate the specified repository to -start with a clean repo. It is highly recommended that a dedicated test account -is used! \ No newline at end of file +**Warning:** The test will delete and recreate the specified repository to +start with a clean repo. It is highly recommended that a dedicated test account +is used! diff --git a/test/assets/responses/create_release.dart b/test/assets/responses/create_release.dart new file mode 100644 index 00000000..65bdbd16 --- /dev/null +++ b/test/assets/responses/create_release.dart @@ -0,0 +1,8 @@ +const Map createReleasePayload = { + 'tag_name': 'v1.0.0', + 'target_commitish': 'master', + 'name': 'v1.0.0', + 'body': 'Description of the release', + 'draft': false, + 'prerelease': false +}; diff --git a/test/assets/responses/nocked_responses.dart b/test/assets/responses/nocked_responses.dart new file mode 100644 index 00000000..bf0e50f4 --- /dev/null +++ b/test/assets/responses/nocked_responses.dart @@ -0,0 +1,1480 @@ +String getBlob = ''' +{ + "content": "Q29udGVudCBvZiB0aGUgYmxvYg==", + "encoding": "base64", + "url": "https://api.github.com/repos/octocat/example/git/blobs/3a0f86fb8db8eea7ccbb9a95f325ddbedfb25e15", + "sha": "3a0f86fb8db8eea7ccbb9a95f325ddbedfb25e15", + "size": 19, + "node_id": "Q29udGVudCBvZiB0aGUgYmxvYg==" +}'''; + +String createBlob = ''' +{ + "url": "https://api.github.com/repos/octocat/example/git/blobs/3a0f86fb8db8eea7ccbb9a95f325ddbedfb25e15", + "sha": "3a0f86fb8db8eea7ccbb9a95f325ddbedfb25e15", + "content": "bbb", + "encoding": "utf-8" +}'''; + +String getCommit = ''' +{ + "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", + "node_id": "MDY6Q29tbWl0NmRjYjA5YjViNTc4NzVmMzM0ZjYxYWViZWQ2OTVlMmU0MTkzZGI1ZQ==", + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", + "html_url": "https://github.com/octocat/Hello-World/commit/7638417db6d59f3c431d3e1f261cc637155684cd", + "author": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "committer": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "message": "added readme, because im a good github citizen", + "tree": { + "url": "https://api.github.com/repos/octocat/Hello-World/git/trees/691272480426f78a0138979dd3ce63b77f706feb", + "sha": "691272480426f78a0138979dd3ce63b77f706feb" + }, + "parents": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/1acc419d4d6a9ce985db7be48c6349a0475975b5", + "sha": "1acc419d4d6a9ce985db7be48c6349a0475975b5", + "html_url": "https://github.com/octocat/Hello-World/commit/7638417db6d59f3c431d3e1f261cc637155684cd" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } +}'''; + +String createCommit = ''' +{ + "sha": "7638417db6d59f3c431d3e1f261cc637155684cd", + "node_id": "MDY6Q29tbWl0NzYzODQxN2RiNmQ1OWYzYzQzMWQzZTFmMjYxY2M2MzcxNTU2ODRjZA==", + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/7638417db6d59f3c431d3e1f261cc637155684cd", + "author": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "committer": { + "date": "2014-11-07T22:01:45Z", + "name": "Monalisa Octocat", + "email": "octocat@github.com" + }, + "message": "aMessage", + "tree": { + "url": "https://api.github.com/repos/octocat/Hello-World/git/trees/827efc6d56897b048c772eb4087f854f46256132", + "sha": "aTreeSha" + }, + "parents": [ + { + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/7d1b31e74ee336d15cbd21741bc88a537ed063a0", + "sha": "7d1b31e74ee336d15cbd21741bc88a537ed063a0", + "html_url": "https://github.com/octocat/Hello-World/commit/7d1b31e74ee336d15cbd21741bc88a537ed063a0" + } + ], + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + }, + "html_url": "https://github.com/octocat/Hello-World/commit/7638417db6d59f3c431d3e1f261cc637155684cd" +}'''; + +String getReference = '''{ + "ref": "refs/heads/b", + "node_id": "MDM6UmVmcmVmcy9oZWFkcy9mZWF0dXJlQQ==", + "url": "https://api.github.com/repos/octocat/Hello-World/git/refs/heads/featureA", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } +}'''; + +String createReference = '''{ + "ref": "refs/heads/b", + "node_id": "MDM6UmVmcmVmcy9oZWFkcy9mZWF0dXJlQQ==", + "url": "https://api.github.com/repos/octocat/Hello-World/git/refs/heads/featureA", + "object": { + "type": "commit", + "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd", + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd" + } +}'''; + +String getTag = '''{ + "node_id": "MDM6VGFnOTQwYmQzMzYyNDhlZmFlMGY5ZWU1YmM3YjJkNWM5ODU4ODdiMTZhYw==", + "tag": "v0.0.1", + "sha": "940bd336248efae0f9ee5bc7b2d5c985887b16ac", + "url": "https://api.github.com/repos/octocat/Hello-World/git/tags/940bd336248efae0f9ee5bc7b2d5c985887b16ac", + "message": "initial version", + "tagger": { + "name": "Monalisa Octocat", + "email": "octocat@github.com", + "date": "2014-11-07T22:01:45Z" + }, + "object": { + "type": "commit", + "sha": "c3d0be41ecbe669545ee3e94d31ed9a4bc91ee3c", + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/c3d0be41ecbe669545ee3e94d31ed9a4bc91ee3c" + }, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } +}'''; + +String createTag = '''{ + "node_id": "MDM6VGFnOTQwYmQzMzYyNDhlZmFlMGY5ZWU1YmM3YjJkNWM5ODU4ODdiMTZhYw==", + "tag": "v0.0.1", + "sha": "940bd336248efae0f9ee5bc7b2d5c985887b16ac", + "url": "https://api.github.com/repos/octocat/Hello-World/git/tags/940bd336248efae0f9ee5bc7b2d5c985887b16ac", + "message": "initial version", + "tagger": { + "name": "Monalisa Octocat", + "email": "octocat@github.com", + "date": "2014-11-07T22:01:45Z" + }, + "object": { + "type": "commit", + "sha": "c3d0be41ecbe669545ee3e94d31ed9a4bc91ee3c", + "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/c3d0be41ecbe669545ee3e94d31ed9a4bc91ee3c" + }, + "verification": { + "verified": false, + "reason": "unsigned", + "signature": null, + "payload": null + } +}'''; + +String createTree = '''{ + "sha": "44b4fc6d56897b048c772eb4087f854f46256132", + "url": "https://api.github.com/repos/octocat/Hello-World/trees/44b4fc6d56897b048c772eb4087f854f46256132", + "tree": [ + { + "path": "file.rb", + "mode": "100644", + "type": "blob", + "size": 132, + "sha": "44b4fc6d56897b048c772eb4087f854f46256132", + "url": "https://api.github.com/repos/octocat/Hello-World/git/blobs/44b4fc6d56897b048c772eb4087f854f46256132" + } + ], + "truncated": true +}'''; + +String searchResults = '''{ + "total_count": 17, + "incomplete_results": false, + "items": [ + { + "name": "search.dart", + "path": "lib/src/common/model/search.dart", + "sha": "c61f39e54eeef0b20d132d2c3ea48bcd3b0d34a9", + "url": "https://api.github.com/repositories/22344823/contents/lib/src/common/model/search.dart?ref=27929ddc731393422327dddee0aaa56d0164c775", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/c61f39e54eeef0b20d132d2c3ea48bcd3b0d34a9", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/27929ddc731393422327dddee0aaa56d0164c775/lib/src/common/model/search.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "search_service.dart", + "path": "lib/src/common/search_service.dart", + "sha": "e98344a6f07d4d9ba1d5b1045354c6a8d3a5323f", + "url": "https://api.github.com/repositories/22344823/contents/lib/src/common/search_service.dart?ref=11a83b4fc9558b7f8c47fdced01c7a8dac3c65b2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/e98344a6f07d4d9ba1d5b1045354c6a8d3a5323f", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/11a83b4fc9558b7f8c47fdced01c7a8dac3c65b2/lib/src/common/search_service.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "search.dart", + "path": "example/search.dart", + "sha": "9ca8b0ac2bfe0ce4afe2d49713ba639146f1ee1a", + "url": "https://api.github.com/repositories/22344823/contents/example/search.dart?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/9ca8b0ac2bfe0ce4afe2d49713ba639146f1ee1a", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/example/search.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "emoji.dart", + "path": "example/emoji.dart", + "sha": "7604d4619400b6b2a19ab11baa60dfa6fa08843e", + "url": "https://api.github.com/repositories/22344823/contents/example/emoji.dart?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/7604d4619400b6b2a19ab11baa60dfa6fa08843e", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/example/emoji.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "search.html", + "path": "example/search.html", + "sha": "16f41b72e4e6135c63aaf923be50a6c87ec80126", + "url": "https://api.github.com/repositories/22344823/contents/example/search.html?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/16f41b72e4e6135c63aaf923be50a6c87ec80126", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/example/search.html", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "mocks.mocks.dart", + "path": "test/src/mocks.mocks.dart", + "sha": "c381f58a8641b8814afd65b6b7a39384035e2ae3", + "url": "https://api.github.com/repositories/22344823/contents/test/src/mocks.mocks.dart?ref=7056f9c2bf17c5f437e6cb32012a7d16f9ed3278", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/c381f58a8641b8814afd65b6b7a39384035e2ae3", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/7056f9c2bf17c5f437e6cb32012a7d16f9ed3278/test/src/mocks.mocks.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "emoji.html", + "path": "example/emoji.html", + "sha": "bdafb143dd918a4872e982b3f876c32aaf9877b2", + "url": "https://api.github.com/repositories/22344823/contents/example/emoji.html?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/bdafb143dd918a4872e982b3f876c32aaf9877b2", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/example/emoji.html", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "index.html", + "path": "example/index.html", + "sha": "81e054e8a613cb2932f905e42c3dc2e294e551ac", + "url": "https://api.github.com/repositories/22344823/contents/example/index.html?ref=c72b46031fcd326820cbc5bb0c3b4b1c15e075e4", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/81e054e8a613cb2932f905e42c3dc2e294e551ac", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/c72b46031fcd326820cbc5bb0c3b4b1c15e075e4/example/index.html", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "readme.md", + "path": "example/readme.md", + "sha": "0781c2ba3898ee16123d9b63a7685d588f3f7511", + "url": "https://api.github.com/repositories/22344823/contents/example/readme.md?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/0781c2ba3898ee16123d9b63a7685d588f3f7511", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/example/readme.md", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "CHANGELOG.md", + "path": "CHANGELOG.md", + "sha": "5de4e987d591c1f71b8f94311671fa8edb38ca6b", + "url": "https://api.github.com/repositories/22344823/contents/CHANGELOG.md?ref=f90446459e2723baecc16f8ac725b5147bd915ab", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/5de4e987d591c1f71b8f94311671fa8edb38ca6b", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/f90446459e2723baecc16f8ac725b5147bd915ab/CHANGELOG.md", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "github.dart", + "path": "lib/src/common/github.dart", + "sha": "e715c2a9b4439c24f00b00071cc86db63c426f1e", + "url": "https://api.github.com/repositories/22344823/contents/lib/src/common/github.dart?ref=921269ba8f803ba47470c624460c23c289b3291b", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/e715c2a9b4439c24f00b00071cc86db63c426f1e", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/921269ba8f803ba47470c624460c23c289b3291b/lib/src/common/github.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "common.dart", + "path": "lib/src/common.dart", + "sha": "f8be345ced35b7320355e04663f7504cb0a502b6", + "url": "https://api.github.com/repositories/22344823/contents/lib/src/common.dart?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/f8be345ced35b7320355e04663f7504cb0a502b6", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/lib/src/common.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "search.dart", + "path": "test/experiment/search.dart", + "sha": "1fffe4dd40ca49cff3f96d248a1740ac67e6a602", + "url": "https://api.github.com/repositories/22344823/contents/test/experiment/search.dart?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/1fffe4dd40ca49cff3f96d248a1740ac67e6a602", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/test/experiment/search.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "code_search_test.dart", + "path": "test/code_search_test.dart", + "sha": "3d60cf6329a298d99d8b074009c2a8fd3c39a470", + "url": "https://api.github.com/repositories/22344823/contents/test/code_search_test.dart?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/3d60cf6329a298d99d8b074009c2a8fd3c39a470", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/test/code_search_test.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "xplat_browser.dart", + "path": "lib/src/browser/xplat_browser.dart", + "sha": "79aeeb174148ae38f6a26d2297356b160e504b6b", + "url": "https://api.github.com/repositories/22344823/contents/lib/src/browser/xplat_browser.dart?ref=4875e4b34ade7f5e36443cd5a2716fe83d9360a2", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/79aeeb174148ae38f6a26d2297356b160e504b6b", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/4875e4b34ade7f5e36443cd5a2716fe83d9360a2/lib/src/browser/xplat_browser.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "release_notes.dart", + "path": "example/release_notes.dart", + "sha": "867474ee071b0b026fcf64cd2409830279c8b2db", + "url": "https://api.github.com/repositories/22344823/contents/example/release_notes.dart?ref=921269ba8f803ba47470c624460c23c289b3291b", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/867474ee071b0b026fcf64cd2409830279c8b2db", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/921269ba8f803ba47470c624460c23c289b3291b/example/release_notes.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + }, + { + "name": "release_unreleased_prs.dart", + "path": "tool/release_unreleased_prs.dart", + "sha": "aca8dae11ec0a26804761ea302934829bf70b4c9", + "url": "https://api.github.com/repositories/22344823/contents/tool/release_unreleased_prs.dart?ref=7056f9c2bf17c5f437e6cb32012a7d16f9ed3278", + "git_url": "https://api.github.com/repositories/22344823/git/blobs/aca8dae11ec0a26804761ea302934829bf70b4c9", + "html_url": "https://github.com/SpinlockLabs/github.dart/blob/7056f9c2bf17c5f437e6cb32012a7d16f9ed3278/tool/release_unreleased_prs.dart", + "repository": { + "id": 22344823, + "node_id": "MDEwOlJlcG9zaXRvcnkyMjM0NDgyMw==", + "name": "github.dart", + "full_name": "SpinlockLabs/github.dart", + "private": false, + "owner": { + "login": "SpinlockLabs", + "id": 26679435, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjI2Njc5NDM1", + "avatar_url": "https://avatars.githubusercontent.com/u/26679435?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/SpinlockLabs", + "html_url": "https://github.com/SpinlockLabs", + "followers_url": "https://api.github.com/users/SpinlockLabs/followers", + "following_url": "https://api.github.com/users/SpinlockLabs/following{/other_user}", + "gists_url": "https://api.github.com/users/SpinlockLabs/gists{/gist_id}", + "starred_url": "https://api.github.com/users/SpinlockLabs/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/SpinlockLabs/subscriptions", + "organizations_url": "https://api.github.com/users/SpinlockLabs/orgs", + "repos_url": "https://api.github.com/users/SpinlockLabs/repos", + "events_url": "https://api.github.com/users/SpinlockLabs/events{/privacy}", + "received_events_url": "https://api.github.com/users/SpinlockLabs/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/SpinlockLabs/github.dart", + "description": "GitHub Client Library for Dart", + "fork": false, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart", + "forks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/forks", + "keys_url": "https://api.github.com/repos/SpinlockLabs/github.dart/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/SpinlockLabs/github.dart/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/SpinlockLabs/github.dart/teams", + "hooks_url": "https://api.github.com/repos/SpinlockLabs/github.dart/hooks", + "issue_events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/events{/number}", + "events_url": "https://api.github.com/repos/SpinlockLabs/github.dart/events", + "assignees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/assignees{/user}", + "branches_url": "https://api.github.com/repos/SpinlockLabs/github.dart/branches{/branch}", + "tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/tags", + "blobs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/SpinlockLabs/github.dart/statuses/{sha}", + "languages_url": "https://api.github.com/repos/SpinlockLabs/github.dart/languages", + "stargazers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/stargazers", + "contributors_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contributors", + "subscribers_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscribers", + "subscription_url": "https://api.github.com/repos/SpinlockLabs/github.dart/subscription", + "commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/SpinlockLabs/github.dart/contents/{+path}", + "compare_url": "https://api.github.com/repos/SpinlockLabs/github.dart/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/SpinlockLabs/github.dart/merges", + "archive_url": "https://api.github.com/repos/SpinlockLabs/github.dart/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/SpinlockLabs/github.dart/downloads", + "issues_url": "https://api.github.com/repos/SpinlockLabs/github.dart/issues{/number}", + "pulls_url": "https://api.github.com/repos/SpinlockLabs/github.dart/pulls{/number}", + "milestones_url": "https://api.github.com/repos/SpinlockLabs/github.dart/milestones{/number}", + "notifications_url": "https://api.github.com/repos/SpinlockLabs/github.dart/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/SpinlockLabs/github.dart/labels{/name}", + "releases_url": "https://api.github.com/repos/SpinlockLabs/github.dart/releases{/id}", + "deployments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/deployments" + }, + "score": 1.0 + } + ] +}'''; + +String mergedPR1 = '''{ + "sha": "someSHA", + "merged": true, + "message": "Pull Request successfully merged" +}'''; diff --git a/test/assets/responses/release.dart b/test/assets/responses/release.dart new file mode 100644 index 00000000..dabcc399 --- /dev/null +++ b/test/assets/responses/release.dart @@ -0,0 +1,88 @@ +/// https://developer.github.com/v3/repos/releases/#get-the-latest-release +const Map releasePayload = { + 'url': 'https://api.github.com/repos/octocat/Hello-World/releases/1', + 'html_url': 'https://github.com/octocat/Hello-World/releases/v1.0.0', + 'assets_url': + 'https://api.github.com/repos/octocat/Hello-World/releases/1/assets', + 'upload_url': + 'https://uploads.github.com/repos/octocat/Hello-World/releases/1/assets{?name,label}', + 'tarball_url': + 'https://api.github.com/repos/octocat/Hello-World/tarball/v1.0.0', + 'zipball_url': + 'https://api.github.com/repos/octocat/Hello-World/zipball/v1.0.0', + 'id': 1, + 'node_id': 'MDc6UmVsZWFzZTE=', + 'tag_name': 'v1.0.0', + 'target_commitish': 'master', + 'name': 'v1.0.0', + 'body': 'Description of the release', + 'draft': false, + 'prerelease': false, + 'created_at': '2013-02-27T19:35:32Z', + 'published_at': '2013-02-27T19:35:32Z', + 'author': { + 'login': 'octocat', + 'id': 1, + 'node_id': 'MDQ6VXNlcjE=', + 'avatar_url': 'https://github.com/images/error/octocat_happy.gif', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/octocat', + 'html_url': 'https://github.com/octocat', + 'followers_url': 'https://api.github.com/users/octocat/followers', + 'following_url': + 'https://api.github.com/users/octocat/following{/other_user}', + 'gists_url': 'https://api.github.com/users/octocat/gists{/gist_id}', + 'starred_url': + 'https://api.github.com/users/octocat/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/octocat/subscriptions', + 'organizations_url': 'https://api.github.com/users/octocat/orgs', + 'repos_url': 'https://api.github.com/users/octocat/repos', + 'events_url': 'https://api.github.com/users/octocat/events{/privacy}', + 'received_events_url': + 'https://api.github.com/users/octocat/received_events', + 'type': 'User', + 'site_admin': false + }, + 'assets': [ + { + 'url': + 'https://api.github.com/repos/octocat/Hello-World/releases/assets/1', + 'browser_download_url': + 'https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip', + 'id': 1, + 'node_id': 'MDEyOlJlbGVhc2VBc3NldDE=', + 'name': 'example.zip', + 'label': 'short description', + 'state': 'uploaded', + 'content_type': 'application/zip', + 'size': 1024, + 'download_count': 42, + 'created_at': '2013-02-27T19:35:32Z', + 'updated_at': '2013-02-27T19:35:32Z', + 'uploader': { + 'login': 'octocat', + 'id': 1, + 'node_id': 'MDQ6VXNlcjE=', + 'avatar_url': 'https://github.com/images/error/octocat_happy.gif', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/octocat', + 'html_url': 'https://github.com/octocat', + 'followers_url': 'https://api.github.com/users/octocat/followers', + 'following_url': + 'https://api.github.com/users/octocat/following{/other_user}', + 'gists_url': 'https://api.github.com/users/octocat/gists{/gist_id}', + 'starred_url': + 'https://api.github.com/users/octocat/starred{/owner}{/repo}', + 'subscriptions_url': + 'https://api.github.com/users/octocat/subscriptions', + 'organizations_url': 'https://api.github.com/users/octocat/orgs', + 'repos_url': 'https://api.github.com/users/octocat/repos', + 'events_url': 'https://api.github.com/users/octocat/events{/privacy}', + 'received_events_url': + 'https://api.github.com/users/octocat/received_events', + 'type': 'User', + 'site_admin': false + } + } + ] +}; diff --git a/test/assets/responses/release_asset.dart b/test/assets/responses/release_asset.dart new file mode 100644 index 00000000..2d553105 --- /dev/null +++ b/test/assets/responses/release_asset.dart @@ -0,0 +1,38 @@ +const Map releaseAssetPayload = { + 'url': 'https://api.github.com/repos/octocat/Hello-World/releases/assets/1', + 'browser_download_url': + 'https://github.com/octocat/Hello-World/releases/download/v1.0.0/example.zip', + 'id': 1, + 'node_id': 'MDEyOlJlbGVhc2VBc3NldDE=', + 'name': 'example.zip', + 'label': 'short description', + 'state': 'uploaded', + 'content_type': 'application/zip', + 'size': 1024, + 'download_count': 42, + 'created_at': '2013-02-27T19:35:32Z', + 'updated_at': '2013-02-27T19:35:32Z', + 'uploader': { + 'login': 'octocat', + 'id': 1, + 'node_id': 'MDQ6VXNlcjE=', + 'avatar_url': 'https://github.com/images/error/octocat_happy.gif', + 'gravatar_id': '', + 'url': 'https://api.github.com/users/octocat', + 'html_url': 'https://github.com/octocat', + 'followers_url': 'https://api.github.com/users/octocat/followers', + 'following_url': + 'https://api.github.com/users/octocat/following{/other_user}', + 'gists_url': 'https://api.github.com/users/octocat/gists{/gist_id}', + 'starred_url': + 'https://api.github.com/users/octocat/starred{/owner}{/repo}', + 'subscriptions_url': 'https://api.github.com/users/octocat/subscriptions', + 'organizations_url': 'https://api.github.com/users/octocat/orgs', + 'repos_url': 'https://api.github.com/users/octocat/repos', + 'events_url': 'https://api.github.com/users/octocat/events{/privacy}', + 'received_events_url': + 'https://api.github.com/users/octocat/received_events', + 'type': 'User', + 'site_admin': false + } +}; diff --git a/test/assets/responses/repository.json b/test/assets/responses/repository.json index bfa6b24d..431c6b32 100644 --- a/test/assets/responses/repository.json +++ b/test/assets/responses/repository.json @@ -1,10 +1,10 @@ { "headers": {}, "body": { - "full_name": "DirectMyFile/github.dart", + "full_name": "SpinlockLabs/github.dart", "name": "github.dart", "owner": { - "login": "DirectMyFile" + "login": "SpinlockLabs" }, "default_branch": "master", "id": 0 diff --git a/test/common/data/repos_json.dart b/test/common/data/repos_json.dart new file mode 100644 index 00000000..f5087cf7 --- /dev/null +++ b/test/common/data/repos_json.dart @@ -0,0 +1,81 @@ +const String listCommits = ''' +[ + { + "sha": "3771b3c9ab1912d32a0d0baffaf489d561caf558", + "node_id": "C_kwDOAVT0d9oAKDM3NzFiM2M5YWIxOTEyZDMyYTBkMGJhZmZhZjQ4OWQ1NjFjYWY1NTg", + "commit": { + "author": { + "name": "Rob Becker", + "email": "rob.becker@workiva.com", + "date": "2023-04-17T14:55:14Z" + }, + "committer": { + "name": "Rob Becker", + "email": "rob.becker@workiva.com", + "date": "2023-04-17T14:55:14Z" + }, + "message": "prep 9.12.0", + "tree": { + "sha": "282532b41e8fead81ec6d68e7e603139e7dd7581", + "url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/trees/282532b41e8fead81ec6d68e7e603139e7dd7581" + }, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart/git/commits/3771b3c9ab1912d32a0d0baffaf489d561caf558", + "comment_count": 0, + "verification": { + "verified": true, + "reason": "valid" + } + }, + "url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits/3771b3c9ab1912d32a0d0baffaf489d561caf558", + "html_url": "https://github.com/SpinlockLabs/github.dart/commit/3771b3c9ab1912d32a0d0baffaf489d561caf558", + "comments_url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits/3771b3c9ab1912d32a0d0baffaf489d561caf558/comments", + "author": { + "login": "robbecker-wf", + "id": 6053699, + "node_id": "MDQ6VXNlcjYwNTM2OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/6053699?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/robbecker-wf", + "html_url": "https://github.com/robbecker-wf", + "followers_url": "https://api.github.com/users/robbecker-wf/followers", + "following_url": "https://api.github.com/users/robbecker-wf/following{/other_user}", + "gists_url": "https://api.github.com/users/robbecker-wf/gists{/gist_id}", + "starred_url": "https://api.github.com/users/robbecker-wf/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/robbecker-wf/subscriptions", + "organizations_url": "https://api.github.com/users/robbecker-wf/orgs", + "repos_url": "https://api.github.com/users/robbecker-wf/repos", + "events_url": "https://api.github.com/users/robbecker-wf/events{/privacy}", + "received_events_url": "https://api.github.com/users/robbecker-wf/received_events", + "type": "User", + "site_admin": false + }, + "committer": { + "login": "robbecker-wf", + "id": 6053699, + "node_id": "MDQ6VXNlcjYwNTM2OTk=", + "avatar_url": "https://avatars.githubusercontent.com/u/6053699?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/robbecker-wf", + "html_url": "https://github.com/robbecker-wf", + "followers_url": "https://api.github.com/users/robbecker-wf/followers", + "following_url": "https://api.github.com/users/robbecker-wf/following{/other_user}", + "gists_url": "https://api.github.com/users/robbecker-wf/gists{/gist_id}", + "starred_url": "https://api.github.com/users/robbecker-wf/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/robbecker-wf/subscriptions", + "organizations_url": "https://api.github.com/users/robbecker-wf/orgs", + "repos_url": "https://api.github.com/users/robbecker-wf/repos", + "events_url": "https://api.github.com/users/robbecker-wf/events{/privacy}", + "received_events_url": "https://api.github.com/users/robbecker-wf/received_events", + "type": "User", + "site_admin": false + }, + "parents": [ + { + "sha": "a3081681da68383d9a5f18ef3502f47f7144e7d8", + "url": "https://api.github.com/repos/SpinlockLabs/github.dart/commits/a3081681da68383d9a5f18ef3502f47f7144e7d8", + "html_url": "https://github.com/SpinlockLabs/github.dart/commit/a3081681da68383d9a5f18ef3502f47f7144e7d8" + } + ] + } +] +'''; diff --git a/test/common/github_test.dart b/test/common/github_test.dart new file mode 100644 index 00000000..97ce2930 --- /dev/null +++ b/test/common/github_test.dart @@ -0,0 +1,59 @@ +import 'dart:io'; + +import 'package:github/src/common.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +void main() { + group(GitHub, () { + test('passes calendar version header', () async { + Request? request; + final client = MockClient((r) async { + request = r; + return Response('{}', HttpStatus.ok); + }); + + final github = GitHub(client: client); + await github.getJSON(''); // Make HTTP request + + expect(request, isNotNull); + expect(request!.headers.containsKey(GitHub.versionHeader), isTrue); + final version = request!.headers[GitHub.versionHeader]; + expect(version, github.version); + }); + + test('passes required user-agent header', () async { + Request? request; + final client = MockClient((r) async { + request = r; + return Response('{}', HttpStatus.ok); + }); + + final github = GitHub(client: client); + await github.getJSON(''); // Make HTTP request + + expect(request, isNotNull); + expect(request!.headers.containsKey('User-Agent'), isTrue); + final userAgent = request!.headers['User-Agent']; + expect(userAgent, 'github.dart'); + }); + + test('anonymous auth passes no authorization header', () async { + Request? request; + final client = MockClient((r) async { + request = r; + return Response('{}', HttpStatus.ok); + }); + + final github = GitHub( + client: client, + auth: const Authentication.anonymous(), + ); + await github.getJSON(''); // Make HTTP request + + expect(request, isNotNull); + expect(request!.headers.containsKey('Authorization'), isFalse); + }); + }); +} diff --git a/test/common/misc_service_test.dart b/test/common/misc_service_test.dart new file mode 100644 index 00000000..2066e271 --- /dev/null +++ b/test/common/misc_service_test.dart @@ -0,0 +1,37 @@ +import 'dart:io'; + +import 'package:github/src/common.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +void main() { + MiscService create(Future Function(Request) f) { + final client = MockClient(f); + final github = GitHub(client: client); + return MiscService(github); + } + + test('api status', () async { + final miscService = create( + (_) async => Response(''' +{ + "page":{ + "id":"kctbh9vrtdwd", + "name":"GitHub", + "url":"https://www.githubstatus.com", + "updated_at": "2023-11-29T08:03:04Z" + }, + "status": { + "description": "Partial System Outage", + "indicator": "major" + } +}''', HttpStatus.ok), + ); + final status = await miscService.getApiStatus(); + expect(status.page, isNotNull); + expect(status.page?.id, 'kctbh9vrtdwd'); + expect(status.status, isNotNull); + expect(status.status?.indicator, 'major'); + }); +} diff --git a/test/common/repos_service_test.dart b/test/common/repos_service_test.dart new file mode 100644 index 00000000..2f3f9120 --- /dev/null +++ b/test/common/repos_service_test.dart @@ -0,0 +1,49 @@ +import 'package:github/src/common.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +import 'data/repos_json.dart'; + +void main() { + final slug = RepositorySlug('SpinlockLabs', 'github.dart'); + RepositoriesService create(Future Function(Request) f) { + final client = MockClient(f); + final github = GitHub(client: client); + return RepositoriesService(github); + } + + test('listCommits', () async { + final repositories = create((request) async { + expect(request.url.path, '/repos/${slug.fullName}/commits'); + expect(request.url.query, isEmpty); + + return Response(listCommits, StatusCodes.OK); + }); + final commits = await repositories.listCommits(slug).toList(); + expect(commits, hasLength(1)); + }); + + test('listCommits with query params', () async { + final repositories = create((request) async { + expect(request.url.path, '/repos/${slug.fullName}/commits'); + expect( + request.url.query, + 'author=octocat&committer=octodog&sha=abc&path=%2Fpath&since=2022-02-22T00%3A00%3A00.000&until=2023-02-22T00%3A00%3A00.000', + ); + return Response(listCommits, StatusCodes.OK); + }); + final commits = await repositories + .listCommits( + slug, + sha: 'abc', + path: '/path', + author: 'octocat', + committer: 'octodog', + since: DateTime(2022, 2, 22), + until: DateTime(2023, 2, 22), + ) + .toList(); + expect(commits, hasLength(1)); + }); +} diff --git a/test/data_object_test.dart b/test/data_object_test.dart new file mode 100644 index 00000000..f1d1d5db --- /dev/null +++ b/test/data_object_test.dart @@ -0,0 +1,44 @@ +import 'dart:convert'; + +import 'package:github/github.dart'; +import 'package:test/test.dart'; + +const _licenseJson = r''' { + "name": "LICENSE", + "path": "LICENSE", + "sha": "68bcabfb39b7af5a1f5efbb8f651d51e16d60398", + "size": 2500, + "url": "https://api.github.com/repos/dart-lang/sdk/contents/LICENSE?ref=master", + "html_url": "https://github.com/dart-lang/sdk/blob/master/LICENSE", + "git_url": "https://api.github.com/repos/dart-lang/sdk/git/blobs/68bcabfb39b7af5a1f5efbb8f651d51e16d60398", + "download_url": "https://raw.githubusercontent.com/dart-lang/sdk/master/LICENSE", + "type": "file", + "content": "VGhpcyBsaWNlbnNlIGFwcGxpZXMgdG8gYWxsIHBhcnRzIG9mIERhcnQgdGhh\ndCBhcmUgbm90IGV4dGVybmFsbHkKbWFpbnRhaW5lZCBsaWJyYXJpZXMuIFRo\nZSBleHRlcm5hbCBtYWludGFpbmVkIGxpYnJhcmllcyB1c2VkIGJ5CkRhcnQg\nYXJlOgoKNy1aaXAgLSBpbiB0aGlyZF9wYXJ0eS83emlwCkpTQ1JFIC0gaW4g\ncnVudGltZS90aGlyZF9wYXJ0eS9qc2NyZQpBbnQgLSBpbiB0aGlyZF9wYXJ0\neS9hcGFjaGVfYW50CmFyZ3M0aiAtIGluIHRoaXJkX3BhcnR5L2FyZ3M0agpi\nemlwMiAtIGluIHRoaXJkX3BhcnR5L2J6aXAyCkNvbW1vbnMgSU8gLSBpbiB0\naGlyZF9wYXJ0eS9jb21tb25zLWlvCkNvbW1vbnMgTGFuZyBpbiB0aGlyZF9w\nYXJ0eS9jb21tb25zLWxhbmcKRWNsaXBzZSAtIGluIHRoaXJkX3BhcnR5L2Vj\nbGlwc2UKZ3N1dGlsIC0gaW4gdGhpcmRfcGFydHkvZ3N1dGlsCkd1YXZhIC0g\naW4gdGhpcmRfcGFydHkvZ3VhdmEKaGFtY3Jlc3QgLSBpbiB0aGlyZF9wYXJ0\neS9oYW1jcmVzdApIdHRwbGliMiAtIGluIHNhbXBsZXMvdGhpcmRfcGFydHkv\naHR0cGxpYjIKSlNPTiAtIGluIHRoaXJkX3BhcnR5L2pzb24KSlVuaXQgLSBp\nbiB0aGlyZF9wYXJ0eS9qdW5pdApOU1MgLSBpbiB0aGlyZF9wYXJ0eS9uc3Mg\nYW5kIHRoaXJkX3BhcnR5L25ldF9uc3MKT2F1dGggLSBpbiBzYW1wbGVzL3Ro\naXJkX3BhcnR5L29hdXRoMmNsaWVudApTUUxpdGUgLSBpbiB0aGlyZF9wYXJ0\neS9zcWxpdGUKd2ViZXJrbmVjaHQgLSBpbiB0aGlyZF9wYXJ0eS93ZWJlcmtu\nZWNodAp6bGliIC0gaW4gdGhpcmRfcGFydHkvemxpYgpmZXN0IC0gaW4gdGhp\ncmRfcGFydHkvZmVzdAptb2NraXRvIC0gaW4gdGhpcmRfcGFydHkvbW9ja2l0\nbwoKVGhlIGxpYnJhcmllcyBtYXkgaGF2ZSB0aGVpciBvd24gbGljZW5zZXM7\nIHdlIHJlY29tbWVuZCB5b3UgcmVhZCB0aGVtLAphcyB0aGVpciB0ZXJtcyBt\nYXkgZGlmZmVyIGZyb20gdGhlIHRlcm1zIGJlbG93LgoKQ29weXJpZ2h0IDIw\nMTIsIHRoZSBEYXJ0IHByb2plY3QgYXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNl\ncnZlZC4KUmVkaXN0cmlidXRpb24gYW5kIHVzZSBpbiBzb3VyY2UgYW5kIGJp\nbmFyeSBmb3Jtcywgd2l0aCBvciB3aXRob3V0Cm1vZGlmaWNhdGlvbiwgYXJl\nIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0\naW9ucyBhcmUKbWV0OgogICAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNl\nIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodAogICAgICBu\nb3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93\naW5nIGRpc2NsYWltZXIuCiAgICAqIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5h\ncnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmUKICAgICAgY29weXJp\nZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBm\nb2xsb3dpbmcKICAgICAgZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlv\nbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkCiAgICAgIHdpdGgg\ndGhlIGRpc3RyaWJ1dGlvbi4KICAgICogTmVpdGhlciB0aGUgbmFtZSBvZiBH\nb29nbGUgSW5jLiBub3IgdGhlIG5hbWVzIG9mIGl0cwogICAgICBjb250cmli\ndXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1\nY3RzIGRlcml2ZWQKICAgICAgZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQg\nc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLgpUSElTIFNPRlRX\nQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQg\nQ09OVFJJQlVUT1JTCiJBUyBJUyIgQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJ\nRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQgTk9UCkxJTUlURUQgVE8s\nIFRIRSBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFO\nRCBGSVRORVNTIEZPUgpBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xB\nSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVApPV05FUiBP\nUiBDT05UUklCVVRPUlMgQkUgTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJ\nUkVDVCwgSU5DSURFTlRBTCwKU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05T\nRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVApMSU1JVEVE\nIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJ\nQ0VTOyBMT1NTIE9GIFVTRSwKREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5F\nU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZClRI\nRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklD\nVCBMSUFCSUxJVFksIE9SIFRPUlQKKElOQ0xVRElORyBORUdMSUdFTkNFIE9S\nIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNF\nCk9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9T\nU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuCg==\n", + "encoding": "base64", + "_links": { + "self": "https://api.github.com/repos/dart-lang/sdk/contents/LICENSE?ref=master", + "git": "https://api.github.com/repos/dart-lang/sdk/git/blobs/68bcabfb39b7af5a1f5efbb8f651d51e16d60398", + "html": "https://github.com/dart-lang/sdk/blob/master/LICENSE" + }, + "license": { + "key": "other", + "name": "Other", + "spdx_id": null, + "url": null, + "node_id": "MDc6TGljZW5zZTA=" + } +}'''; + +void main() { + test('License round-trip', () { + final licenseJson = jsonDecode(_licenseJson) as Map; + + final instance = LicenseDetails.fromJson(licenseJson); + + final toJson = instance.toJson(); + + expect(_prettyEncode(toJson), _prettyEncode(licenseJson)); + }); +} + +String _prettyEncode(obj) => GitHubJson.encode(obj, indent: ' '); diff --git a/test/experiment/api_urls.dart b/test/experiment/api_urls.dart index 94840e9e..6a7a50a5 100644 --- a/test/experiment/api_urls.dart +++ b/test/experiment/api_urls.dart @@ -1,10 +1,10 @@ -import "package:github/src/common.dart"; +import 'package:github/src/common.dart'; void main() { - print(slugFromAPIUrl("https://api.github.com/repos/DirectMyFile/irc.dart")); - print(slugFromAPIUrl("https://api.github.com/repos/DirectMyFile/irc.dart/")); + print(slugFromAPIUrl('https://api.github.com/repos/SpinlockLabs/irc.dart')); + print(slugFromAPIUrl('https://api.github.com/repos/SpinlockLabs/irc.dart/')); print(slugFromAPIUrl( - "https://api.github.com/repos/DirectMyFile/irc.dart/issues")); + 'https://api.github.com/repos/SpinlockLabs/irc.dart/issues')); print(slugFromAPIUrl( - "https://api.github.com/repos/DirectMyFile/irc.dart/issues/1")); + 'https://api.github.com/repos/SpinlockLabs/irc.dart/issues/1')); } diff --git a/test/experiment/blog.dart b/test/experiment/blog.dart deleted file mode 100755 index 82f03b41..00000000 --- a/test/experiment/blog.dart +++ /dev/null @@ -1,9 +0,0 @@ -import "package:github/server.dart"; - -void main() { - var github = createGitHubClient(); - - github.blog.listPosts().listen((post) { - print(post.title); - }).onDone(() => github.dispose()); -} diff --git a/test/experiment/crawler.dart b/test/experiment/crawler.dart index a52454ab..7dd8e737 100644 --- a/test/experiment/crawler.dart +++ b/test/experiment/crawler.dart @@ -1,10 +1,12 @@ -import "package:github/server.dart"; +import 'package:github/github.dart'; void main() { - var github = new GitHub(auth: new Authentication.anonymous()); + final github = GitHub(auth: const Authentication.anonymous()); - var crawler = new RepositoryCrawler( - github, new RepositorySlug.full("DirectMyFile/github.dart")); + final crawler = RepositoryCrawler( + github, + RepositorySlug.full('SpinlockLabs/github.dart'), + ); crawler.crawl().listen((file) { print(file.path); diff --git a/test/experiment/directcode_keys.dart b/test/experiment/directcode_keys.dart deleted file mode 100755 index 43d721f4..00000000 --- a/test/experiment/directcode_keys.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'dart:async'; -import "package:github/server.dart"; - -import "package:quiver/async.dart"; - -Future main() async { - var github = createGitHubClient(); - - github.organizations.get("DirectMyFile").then((organization) { - return github.organizations.listTeams(organization.name).toList(); - }).then((teams) { - var group = new FutureGroup(); - teams.forEach((team) { - group.add(github.organizations.listTeamMembers(team.id).toList()); - }); - return group.future; - }).then((mems) { - return mems.reduce((value, e) { - return new Set()..addAll(value)..addAll(e); - }); - }).then((members) { - var group = new FutureGroup(); - for (TeamMember member in members) { - group.add(github.users.listPublicKeys(member.login).toList().then((keys) { - print("${member.login}:"); - keys.forEach((key) { - print("- ${key.key}"); - }); - })); - } - return group.future; - }).then((_) => github.dispose()); -} diff --git a/test/experiment/error_handling.dart b/test/experiment/error_handling.dart index cc58cb3c..e76b7e9d 100644 --- a/test/experiment/error_handling.dart +++ b/test/experiment/error_handling.dart @@ -1,21 +1,25 @@ -import 'package:github/server.dart'; +import 'dart:io'; -import '../helper.dart'; +import 'package:github/github.dart'; -import 'dart:io'; -import 'dart:convert'; +import '../helper/http.dart'; void main() { - var github = createGitHubClient(); - var response = new MockResponse( - JSON.encode({ - "message": "Invalid Entity", - "errors": [ - {"resource": "Issue", "field": "body", "code": "not_found"} - ] - }), - {}, - 422); + final github = GitHub(); + final response = MockResponse( + GitHubJson.encode({ + 'message': 'Invalid Entity', + 'errors': [ + { + 'resource': 'Issue', + 'field': 'body', + 'code': 'not_found', + } + ] + }), + {}, + 422, + ); try { github.handleStatusCode(response); @@ -23,8 +27,4 @@ void main() { print(e); exit(0); } - - print("Invalid Entity Error Handling Failed"); - - exit(1); } diff --git a/test/experiment/fancy_numbers.dart b/test/experiment/fancy_numbers.dart index f7d5bee8..693936eb 100644 --- a/test/experiment/fancy_numbers.dart +++ b/test/experiment/fancy_numbers.dart @@ -1,20 +1,20 @@ -import "package:github/src/util.dart"; +import 'package:github/src/common/util/utils.dart'; void main() { - test("1k", 1000); - test("2k", 2000); - test("2.2k", 2200); - test("2.34k", 2340); - test("1ht", 100000); - test("1m", 1000000); - test("3,000", 3000); + test('1k', 1000); + test('2k', 2000); + test('2.2k', 2200); + test('2.34k', 2340); + test('1ht', 100000); + test('1m', 1000000); + test('3,000', 3000); } void test(String input, int expect) { - var out = parseFancyNumber(input); + final out = parseFancyNumber(input); if (out != expect) { - print("ERROR: ${input} was parsed as ${out} but we expected ${expect}"); + print('ERROR: $input was parsed as $out but we expected $expect'); } else { - print("${input} => ${expect}"); + print('$input => $expect'); } } diff --git a/test/experiment/files.dart b/test/experiment/files.dart index 31fc0ca4..37efa7b0 100755 --- a/test/experiment/files.dart +++ b/test/experiment/files.dart @@ -1,12 +1,14 @@ -import "package:github/server.dart"; +import 'package:github/github.dart'; void main() { - var github = new GitHub(); + final github = GitHub(); github.repositories .getContents( - new RepositorySlug("DirectMyFile", "github.dart"), "pubspec.yaml") + RepositorySlug('SpinlockLabs', 'github.dart'), + 'pubspec.yaml', + ) .then((contents) => contents.file) - .then((file) => print(file.text)) + .then((file) => print(file?.text)) .then((_) => github.dispose()); } diff --git a/test/experiment/generate_release_notes.dart b/test/experiment/generate_release_notes.dart new file mode 100755 index 00000000..8d0e7a1e --- /dev/null +++ b/test/experiment/generate_release_notes.dart @@ -0,0 +1,11 @@ +import 'package:github/github.dart'; + +Future main() async { + final github = GitHub(auth: findAuthenticationFromEnvironment()); + + var notes = await github.repositories.generateReleaseNotes(CreateReleaseNotes( + 'Spinlocklabs', 'github.dart', '1.0.1', + targetCommitish: '1.0.1', previousTagName: '1.0.0')); + print(notes.name); + print(notes.body); +} diff --git a/test/experiment/limit_pager.dart b/test/experiment/limit_pager.dart index c0f983d7..05654bfa 100755 --- a/test/experiment/limit_pager.dart +++ b/test/experiment/limit_pager.dart @@ -5,28 +5,27 @@ void main() { print(solve(201)); } -const int MAX_PER_PAGE = 100; -const int ACCURACY_RANGE = 5; +const int maxPerPage = 100; +const int accuracyRange = 5; /// Solves the most efficient way to fetch the number of objects [limit] with the least requests. PaginationInformation solve(int limit) { if (limit < 0) { - throw new RangeError("limit cannot be less than zero (was ${limit})"); + throw RangeError('limit cannot be less than zero (was $limit)'); } - if (limit < MAX_PER_PAGE) { - return new PaginationInformation(limit, 1, limit); + if (limit < maxPerPage) { + return PaginationInformation(limit, 1, limit); } - if ((limit % MAX_PER_PAGE) == 0) { - return new PaginationInformation( - limit, limit ~/ MAX_PER_PAGE, MAX_PER_PAGE); + if ((limit % maxPerPage) == 0) { + return PaginationInformation(limit, limit ~/ maxPerPage, maxPerPage); } - int itemsPerPage = 100; - int pages = (limit / itemsPerPage).ceil(); + const itemsPerPage = 100; + final pages = (limit / itemsPerPage).ceil(); - return new PaginationInformation(limit, pages, itemsPerPage); + return PaginationInformation(limit, pages, itemsPerPage); } class PaginationInformation { @@ -37,6 +36,5 @@ class PaginationInformation { PaginationInformation(this.limit, this.pages, this.itemsPerPage); @override - String toString() => - "limit: ${limit}, pages: ${pages}, per page: ${itemsPerPage}"; + String toString() => 'limit: $limit, pages: $pages, per page: $itemsPerPage'; } diff --git a/test/experiment/link_header.dart b/test/experiment/link_header.dart index 3d750174..dd0230a0 100644 --- a/test/experiment/link_header.dart +++ b/test/experiment/link_header.dart @@ -1,7 +1,7 @@ -import 'package:github/src/util.dart'; +import 'package:github/src/common/util/pagination.dart'; void main() { - var it = parseLinkHeader( + final it = parseLinkHeader( '; rel="next", ; rel="last"'); print(it); } diff --git a/test/experiment/org_hooks.dart b/test/experiment/org_hooks.dart index 5c85638b..9e0d79f8 100644 --- a/test/experiment/org_hooks.dart +++ b/test/experiment/org_hooks.dart @@ -1,12 +1,12 @@ -import "../helper.dart"; import 'dart:async'; +import '../helper.dart'; Future main() async { - var org = "IOT-DSA"; + const org = 'IOT-DSA'; - var hooks = await github.organizations.listHooks(org).toList(); + final hooks = await github.organizations.listHooks(org).toList(); - for (var hook in hooks) { + for (final hook in hooks) { print(hook.config); } diff --git a/test/experiment/orglist.dart b/test/experiment/orglist.dart index bb8f4565..01aa4c6c 100644 --- a/test/experiment/orglist.dart +++ b/test/experiment/orglist.dart @@ -1,10 +1,10 @@ import 'dart:async'; -import "package:github/server.dart"; +import 'package:github/github.dart'; Future main() async { - var github = createGitHubClient(); - var repos = - await github.repositories.listUserRepositories("dart-lang").toList(); + final github = GitHub(); + final repos = + await github.repositories.listUserRepositories('dart-lang').toList(); github.dispose(); print(repos); } diff --git a/test/experiment/polling.dart b/test/experiment/polling.dart index e3f13fa5..b12b2ef6 100755 --- a/test/experiment/polling.dart +++ b/test/experiment/polling.dart @@ -1,12 +1,12 @@ -import "package:github/server.dart"; +import 'package:github/github.dart'; void main() { - var github = createGitHubClient(); + final github = GitHub(); - EventPoller poller = github.activity.pollPublicEvents(); + final poller = github.activity.pollPublicEvents(); poller.start().listen((event) { - print("New Event:"); - print("- Payload: ${event.payload}"); - }).onDone(() => github.dispose()); + print('New Event:'); + print('- Payload: ${event.payload}'); + }).onDone(github.dispose); } diff --git a/test/experiment/public_repos.dart b/test/experiment/public_repos.dart index 5ff4fa79..ed8bb86b 100755 --- a/test/experiment/public_repos.dart +++ b/test/experiment/public_repos.dart @@ -1,9 +1,9 @@ -import "package:github/server.dart"; +import 'package:github/github.dart'; void main() { - var github = createGitHubClient(); + final github = GitHub(); github.repositories.listPublicRepositories(limit: 50).listen((repo) { - print("-> ${repo.fullName}"); - }).onDone(() => github.dispose()); + print('-> ${repo.fullName}'); + }).onDone(github.dispose); } diff --git a/test/experiment/reactions.dart b/test/experiment/reactions.dart new file mode 100644 index 00000000..76f2a8b4 --- /dev/null +++ b/test/experiment/reactions.dart @@ -0,0 +1,11 @@ +import 'package:github/github.dart'; + +void main() { + final github = GitHub(auth: findAuthenticationFromEnvironment()); + github.issues + .listReactions(RepositorySlug('SpinlockLabs', 'github.dart'), 177, + content: ReactionType.plusOne) + .listen((Reaction r) { + print(ReactionType.fromString(r.content)!.emoji); + }); +} diff --git a/test/experiment/readme.dart b/test/experiment/readme.dart index e08b1521..b75e25df 100755 --- a/test/experiment/readme.dart +++ b/test/experiment/readme.dart @@ -1,11 +1,9 @@ -import "package:github/server.dart"; +import 'package:github/github.dart'; -void main() { - var github = createGitHubClient(); - - github.repositories - .getReadme(new RepositorySlug("DirectMyFile", "github.dart")) - .then((file) => github.misc.renderMarkdown(file.text)) - .then((html) => print(html)) - .then((_) => github.dispose()); +Future main() async { + final github = GitHub(); + final file = await github.repositories + .getReadme(RepositorySlug('SpinlockLabs', 'github.dart')); + print(await github.misc.renderMarkdown(file.text)); + github.dispose(); } diff --git a/test/experiment/search.dart b/test/experiment/search.dart index 84b5f366..1fffe4dd 100755 --- a/test/experiment/search.dart +++ b/test/experiment/search.dart @@ -1,10 +1,9 @@ -import "package:github/server.dart"; +import 'package:github/github.dart'; void main() { - var github = createGitHubClient(); + final github = GitHub(); - github.search.repositories("github").listen((repo) { - print( - "${repo.fullName}: ${repo.description.isNotEmpty ? repo.description : "No Description"}"); - }).onDone(() => github.dispose()); + github.search.repositories('github').listen((repo) { + print('${repo.fullName}: ${repo.description}'); + }).onDone(github.dispose); } diff --git a/test/experiment/showcases.dart b/test/experiment/showcases.dart deleted file mode 100755 index 76aca0c4..00000000 --- a/test/experiment/showcases.dart +++ /dev/null @@ -1,9 +0,0 @@ -import "package:github/server.dart"; - -void main() { - var github = createGitHubClient(); - - github.explore.listShowcases().listen((info) { - print("- ${info.title}"); - }).onDone(() => github.dispose()); -} diff --git a/test/experiment/trending.dart b/test/experiment/trending.dart deleted file mode 100644 index ff14fd58..00000000 --- a/test/experiment/trending.dart +++ /dev/null @@ -1,9 +0,0 @@ -import "package:github/server.dart"; - -void main() { - var github = createGitHubClient(); - - github.explore - .listTrendingRepositories(language: "Dart", since: "month") - .listen((repo) => print("${repo.title}: ${repo.description}")); -} diff --git a/test/experiment/wisdom.dart b/test/experiment/wisdom.dart index 2a5738a5..2f5cec3f 100755 --- a/test/experiment/wisdom.dart +++ b/test/experiment/wisdom.dart @@ -1,9 +1,8 @@ -import "package:github/server.dart"; +import 'package:github/github.dart'; -void main() { - var github = createGitHubClient(); - - github.misc.getWisdom().then((value) { - print(value); - }).then((_) => github.dispose()); +Future main() async { + final github = GitHub(); + final wisdom = await github.misc.getWisdom(); + print(wisdom); + github.dispose(); } diff --git a/test/git_integration_test.dart b/test/git_integration_test.dart deleted file mode 100644 index 2fed2541..00000000 --- a/test/git_integration_test.dart +++ /dev/null @@ -1,163 +0,0 @@ -library github.test.integration.git_integration_test; - -import 'dart:convert'; - -import 'dart:io'; -import 'package:github/server.dart'; - -import 'package:test/test.dart'; - -void main() { - String firstCommitSha; - String firstCommitTreeSha; - - String createdTreeSha; - String createdCommitSha; - - GitHub github; - RepositorySlug slug; - - setUpAll(() { - var authToken = Platform.environment['GITHUB_API_TOKEN']; - var repoOwner = Platform.environment['GITHUB_DART_TEST_REPO_OWNER']; - var repoName = Platform.environment['GITHUB_DART_TEST_REPO_NAME']; - - github = createGitHubClient(auth: new Authentication.withToken(authToken)); - slug = new RepositorySlug(repoOwner, repoName); - }); - - tearDownAll(() { - github.dispose(); - }); - - // Test definitions. - test('get last commit of master', () async { - var branch = await github.repositories.getBranch(slug, 'master'); - firstCommitSha = branch.commit.sha; - firstCommitTreeSha = branch.commit.tree.sha; - }); - - test('create and get a new blob', () async { - var newBlob = new CreateGitBlob('bbb', 'utf-8'); - - // createBlob() - var createdBlob = await github.git.createBlob(slug, newBlob); - var createdBlobSha = createdBlob.sha; - - var fetchedBlob = await github.git.getBlob(slug, createdBlobSha); - - var base64Decoded = BASE64.decode(fetchedBlob.content); - - expect(UTF8.decode(base64Decoded), equals('bbb')); - expect(fetchedBlob.encoding, equals('base64')); - expect( - fetchedBlob.url, - equals( - 'https://api.github.com/repos/${slug.fullName}/git/blobs/${createdBlobSha}')); - expect(fetchedBlob.sha, equals(createdBlobSha)); - expect(fetchedBlob.size, equals(3)); - }); - - test('create and get a new tree', () async { - var entry1 = new CreateGitTreeEntry('README.md', '100644', 'blob', - content: 'This is a repository for integration tests.'); - var entry2 = new CreateGitTreeEntry('subdir/asdf.txt', '100644', 'blob', - content: 'Some file in a folder.'); - - var newTree = new CreateGitTree([entry1, entry2]) - ..baseTree = firstCommitTreeSha; - - // createTree() - var createdTree = await github.git.createTree(slug, newTree); - createdTreeSha = createdTree.sha; - - // getTree() - var fetchedTree = await github.git.getTree(slug, createdTreeSha); - - expect(fetchedTree.sha, equals(createdTreeSha)); - expect(fetchedTree.entries.length, equals(2)); - }); - - test('create and get a new commit', () async { - var newCommit = new CreateGitCommit('My test commit', createdTreeSha) - ..parents = [firstCommitSha]; - - // createCommit() - var createdCommit = await github.git.createCommit(slug, newCommit); - createdCommitSha = createdCommit.sha; - - // getCommit() - var fetchedCommit = await github.git.getCommit(slug, createdCommitSha); - expect(fetchedCommit.sha, equals(createdCommitSha)); - expect(fetchedCommit.message, equals('My test commit')); - expect(fetchedCommit.tree.sha, equals(createdTreeSha)); - expect(fetchedCommit.parents.first.sha, equals(firstCommitSha)); - }); - - test('update heads/master reference to new commit', () { - return github.git.editReference(slug, 'heads/master', createdCommitSha); - }); - - test('create and get a new reference (branch)', () async { - var branchName = _randomGitName(); - - await github.git - .createReference(slug, 'refs/heads/$branchName', createdCommitSha); - - var fetchedRef = await github.git.getReference(slug, 'heads/$branchName'); - expect(fetchedRef.ref, equals('refs/heads/$branchName')); - expect(fetchedRef.object.type, equals('commit')); - expect(fetchedRef.object.sha, equals(createdCommitSha)); - }); - - test('create and get a new tag', () async { - var tagName = 'v${_randomGitName()}'; - - var newTag = new CreateGitTag(tagName, 'Version 0.0.1', createdCommitSha, - 'commit', new GitCommitUser('aName', 'aEmail', new DateTime.now())); - - // createTag() - var createdTag = await github.git.createTag(slug, newTag); - var createdTagSha = createdTag.sha; - - // getTag() - var fetchedTag = await github.git.getTag(slug, createdTagSha); - expect(fetchedTag.tag, equals(tagName)); - expect(fetchedTag.sha, equals(createdTagSha)); - expect(fetchedTag.message, equals('Version 0.0.1')); - expect(fetchedTag.tagger.name, equals('aName')); - expect(fetchedTag.object.sha, equals(createdCommitSha)); - - // Create a reference for the tag. - await github.git.createReference(slug, 'refs/tags/$tagName', createdTagSha); - }); - - group('create and query issues', () { - test('query issues', () async { - var issues = await github.issues.listByRepo(slug).toList(); - - var count = issues.length; - - var issueRequest = new IssueRequest() - ..title = 'new issue - ${_randomGitName()}'; - - await github.issues.create(slug, issueRequest); - - issues = await github.issues - .listByRepo(slug, sort: 'updated', direction: 'desc') - .toList(); - - expect(issues, hasLength(count + 1)); - - var issue = issues.first; - - expect(issue.title, issueRequest.title); - }); - }); -} - -String _randomGitName() { - var now = new DateTime.now().toIso8601String().replaceAll(':', '_'); - - return now.toString(); -} diff --git a/test/git_test.dart b/test/git_test.dart index ce179561..e95ef5d6 100644 --- a/test/git_test.dart +++ b/test/git_test.dart @@ -1,301 +1,263 @@ -library github.test.git_test; +import 'package:github/github.dart'; +import 'package:nock/nock.dart'; +import 'package:test/test.dart'; -import 'dart:async'; -import 'dart:convert' show JSON; +import 'assets/responses/nocked_responses.dart' as nocked; -import 'package:github/src/common.dart'; -import 'package:github/src/util.dart'; -import "package:http/http.dart" as http; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; +const fakeApiUrl = 'http://fake.api.github.com'; +const date = '2014-10-02T15:21:29Z'; -class MockGitHub extends Mock implements GitHub {} +GitHub createGithub() { + return GitHub( + endpoint: fakeApiUrl, + auth: const Authentication.withToken( + '0000000000000000000000000000000000000001')); +} void main() { - MockGitHub github; - GitService git; - RepositorySlug repo; - var someSha = 'someSHA'; + late GitHub github; + late GitService git; + late RepositorySlug repo; + const someSha = 'someSHA'; + + setUpAll(nock.init); setUp(() { - github = new MockGitHub(); - git = new GitService(github); - repo = new RepositorySlug('o', 'n'); + nock.cleanAll(); + github = createGithub(); + git = GitService(github); + repo = RepositorySlug('o', 'n'); }); + tearDown(nock.cleanAll); - group('getBlob()', () { - test('constructs correct path', () { - git.getBlob(repo, 'sh'); - - verify(github.getJSON('/repos/o/n/git/blobs/sh', - convert: GitBlob.fromJSON, statusCode: StatusCodes.OK)); - }); + test('getBlob()', () async { + nock(fakeApiUrl).get('/repos/o/n/git/blobs/sh').reply(200, nocked.getBlob); + final blob = await git.getBlob(repo, 'sh'); + expect(blob.sha, '3a0f86fb8db8eea7ccbb9a95f325ddbedfb25e15'); + expect(nock.pendingMocks.isEmpty, true); }); - group('createBlob()', () { - test('constructs correct path', () { - CreateGitBlob blob = new CreateGitBlob('bbb', 'utf-8'); - git.createBlob(repo, blob); - - verify(github.postJSON('/repos/o/n/git/blobs', - convert: GitBlob.fromJSON, - statusCode: StatusCodes.CREATED, - body: blob.toJSON())); - }); - - test('creates valid JSON body', () { - CreateGitBlob blob = new CreateGitBlob('bbb', 'utf-8'); - - git.createBlob(repo, blob); - var body = captureSentBody(github); - expect(body['content'], equals('bbb')); - expect(body['encoding'], equals('utf-8')); - }); + test('createBlob()', () async { + nock(fakeApiUrl) + .post('/repos/o/n/git/blobs', '{"content":"bbb","encoding":"utf-8"}') + .reply(201, nocked.createBlob); + var blob = await git.createBlob(repo, CreateGitBlob('bbb', 'utf-8')); + expect(blob.content, 'bbb'); + expect(blob.encoding, 'utf-8'); }); - group('getCommit()', () { - test('constructs correct path', () { - git.getCommit(repo, 'sh'); - - verify(github.getJSON('/repos/o/n/git/commits/sh', - convert: GitCommit.fromJSON, statusCode: StatusCodes.OK)); - }); + test('getCommit()', () async { + nock(fakeApiUrl) + .get('/repos/o/n/git/commits/sh') + .reply(200, nocked.getCommit); + var commit = await git.getCommit(repo, 'sh'); + expect(commit.sha, '7638417db6d59f3c431d3e1f261cc637155684cd'); }); - group('createCommit()', () { - test('constructs correct path', () { - CreateGitCommit commit = new CreateGitCommit('aMessage', 'aTreeSha'); - git.createCommit(repo, commit); - - verify(github.postJSON('/repos/o/n/git/commits', - convert: GitCommit.fromJSON, - statusCode: StatusCodes.CREATED, - body: commit.toJSON())); - }); - - test('creates valid JSON body', () { - // given - String date = '2014-10-02T15:21:29Z'; - - CreateGitCommit commit = new CreateGitCommit('aMessage', 'aTreeSha') - ..parents = ['parentSha1', 'parentSha2'] - ..committer = new GitCommitUser('cName', 'cEmail', parseDateTime(date)) - ..author = new GitCommitUser('aName', 'aEmail', parseDateTime(date)); - - // when - git.createCommit(repo, commit); - - // then - var body = captureSentBody(github); - expect(body['message'], equals('aMessage')); - expect(body['tree'], equals('aTreeSha')); - expect(body['parents'], equals(['parentSha1', 'parentSha2'])); - expect(body['committer']['name'], equals('cName')); - expect(body['committer']['email'], equals('cEmail')); - expect(body['committer']['date'], equals(date)); - expect(body['author']['name'], equals('aName')); - expect(body['author']['email'], equals('aEmail')); - expect(body['author']['date'], equals(date)); - }); + test('createCommit()', () async { + nock(fakeApiUrl) + .post('/repos/o/n/git/commits', + '{"message":"aMessage","tree":"aTreeSha","parents":["parentSha1","parentSha2"],"committer":{"name":"cName","email":"cEmail","date":"2014-10-02T15:21:29Z"},"author":{"name":"aName","email":"aEmail","date":"2014-10-02T15:21:29Z"}}') + .reply(201, nocked.createCommit); + + var commit = await git.createCommit( + repo, + CreateGitCommit('aMessage', 'aTreeSha') + ..parents = ['parentSha1', 'parentSha2'] + ..committer = GitCommitUser('cName', 'cEmail', DateTime.parse(date)) + ..author = GitCommitUser('aName', 'aEmail', DateTime.parse(date))); + expect(commit.message, 'aMessage'); + expect(commit.tree!.sha, 'aTreeSha'); }); - group('getReference()', () { - test('constructs correct path', () { - git.getReference(repo, 'heads/b'); - - verify(github.getJSON('/repos/o/n/git/refs/heads/b', - convert: GitReference.fromJSON, statusCode: StatusCodes.OK)); - }); + test('getReference()', () async { + nock(fakeApiUrl) + .get('/repos/o/n/git/refs/heads/b') + .reply(200, nocked.getReference); + var ref = await git.getReference(repo, 'heads/b'); + expect(ref.ref, 'refs/heads/b'); }); - group('createReference()', () { - var someRef = 'refs/heads/b'; - test('constructs correct path', () { - git.createReference(repo, someRef, someSha); - - verify(github.postJSON('/repos/o/n/git/refs', - convert: GitReference.fromJSON, - statusCode: StatusCodes.CREATED, - body: JSON.encode({'ref': someRef, 'sha': someSha}))); - }); - - test('creates valid JSON body', () { - git.createReference(repo, someRef, someSha); - - var body = captureSentBody(github); - expect(body['ref'], equals(someRef)); - expect(body['sha'], equals(someSha)); - }); + test('createReference()', () async { + const someRef = 'refs/heads/b'; + nock(fakeApiUrl) + .post('/repos/o/n/git/refs', '{"ref":"refs/heads/b","sha":"someSHA"}') + .reply(201, nocked.createReference); + var ref = await git.createReference(repo, someRef, someSha); + expect(ref.ref, someRef); }); - group('editReference()', () { - test('constructs correct path', () { - // given - http.Response expectedResponse = new http.Response('{}', 200); - - when(github.request(any, any, - body: any, headers: typed(any, named: 'headers'))) - .thenReturn(new Future.value(expectedResponse)); - - // when - git.editReference(repo, 'heads/b', someSha); + test('editReference()', () async { + nock(fakeApiUrl) + .patch('/repos/o/n/git/refs/heads/b', '{"sha":"someSHA","force":true}') + .reply(200, '{}'); - // then - verify(github.request('PATCH', '/repos/o/n/git/refs/heads/b', - headers: typed(any, named: 'headers'), body: any)); - }); - - test('creates valid JSON body', () { - // given - http.Response expectedResponse = new http.Response('{}', 200); - when(github.request(any, any, - body: any, headers: typed(any, named: 'headers'))) - .thenReturn(new Future.value(expectedResponse)); - - // when - git.editReference(repo, 'heads/b', someSha, force: true); - - // then - var captured = verify(github.request(any, any, - body: captureAny, headers: typed(captureAny, named: 'headers'))) - .captured; - - var body = JSON.decode(captured[0]); - var headers = captured[1]; - - expect(body['sha'], equals(someSha)); - expect(body['force'], equals(true)); - expect(headers['content-length'], equals('30')); - }); + await git.editReference(repo, 'heads/b', someSha, force: true); }); - group('deleteReference()', () { - test('constructs correct path', () { - // given - http.Response expectedResponse = new http.Response('{}', 200); - when(github.request(any, any)) - .thenReturn(new Future.value(expectedResponse)); - - // when - git.deleteReference(repo, 'heads/b'); + test('deleteReference()', () async { + nock(fakeApiUrl).delete('/repos/o/n/git/refs/heads/b').reply(200, '{}'); + await git.deleteReference(repo, 'heads/b'); + }); - // then - verify(github.request('DELETE', '/repos/o/n/git/refs/heads/b')); - }); + test('getTag()', () async { + nock(fakeApiUrl) + .get('/repos/o/n/git/tags/someSHA') + .reply(200, nocked.getTag); + await git.getTag(repo, someSha); }); - group('getTag()', () { - test('constructs correct path', () { - git.getTag(repo, someSha); + test('createTag()', () async { + nock(fakeApiUrl) + .post('/repos/o/n/git/tags', + '{"tag":"v0.0.1","message":"initial version","object":"someSHA","type":"commit","tagger":{"name":"Monalisa Octocat","email":"octocat@github.com","date":"$date"}}') + .reply(201, nocked.createTag); + + final createGitTag = CreateGitTag( + 'v0.0.1', + 'initial version', + someSha, + 'commit', + GitCommitUser( + 'Monalisa Octocat', 'octocat@github.com', DateTime.parse(date))); + + var tag = await git.createTag(repo, createGitTag); + + expect(tag.tag, 'v0.0.1'); + expect(tag.message, 'initial version'); + expect(tag.tagger?.name, 'Monalisa Octocat'); + }); - verify(github.getJSON('/repos/o/n/git/tags/someSHA', - convert: GitTag.fromJSON, statusCode: StatusCodes.OK)); - }); + test('getTree()', () async { + nock(fakeApiUrl) + .get('/repos/o/n/git/trees/sh?recursive=1') + .reply(200, '{}'); + await git.getTree(repo, 'sh', recursive: true); }); - group('createTag()', () { - var createGitTag = new CreateGitTag('v0.0.1', 'a message', someSha, - 'commit', new GitCommitUser('aName', 'aEmail', new DateTime.now())); + test('createTree()', () async { + nock(fakeApiUrl) + .post('/repos/o/n/git/trees', + '{"tree":[{"path":"file.rb","mode":"100644","type":"blob","sha":"44b4fc6d56897b048c772eb4087f854f46256132"}]}') + .reply(201, nocked.createTree); + + var createTree = CreateGitTree([ + CreateGitTreeEntry('file.rb', '100644', 'blob', + sha: '44b4fc6d56897b048c772eb4087f854f46256132') + ]); + + var tree = await git.createTree(repo, createTree); + var entry = tree.entries?.first; + expect(entry?.path, 'file.rb'); + expect(entry?.mode, '100644'); + expect(entry?.type, 'blob'); + expect(entry?.sha, '44b4fc6d56897b048c772eb4087f854f46256132'); + + nock(fakeApiUrl) + .post('/repos/o/n/git/trees', + '{"tree":[{"path":"file.rb","mode":"100644","type":"blob","content":"content"}]}') + .reply(201, nocked.createTree); + + createTree = CreateGitTree( + [CreateGitTreeEntry('file.rb', '100644', 'blob', content: 'content')]); + + tree = await git.createTree(repo, createTree); + entry = tree.entries?.first; + expect(entry?.path, 'file.rb'); + expect(entry?.mode, '100644'); + expect(entry?.type, 'blob'); + expect(entry?.sha, '44b4fc6d56897b048c772eb4087f854f46256132'); + }); - test('constructs correct path', () { - git.createTag(repo, createGitTag); + test('code search', () async { + nock(fakeApiUrl) + .get( + '/search/code?q=search%20repo%3ASpinlockLabs%2Fgithub.dart%20in%3Afile&per_page=20') + .reply(200, nocked.searchResults); + + final results = (await github.search + .code( + 'search', + repo: 'SpinlockLabs/github.dart', + perPage: 20, + pages: 1, + ) + .toList()) + .first; + expect(results.totalCount, 17); + expect(results.items?.length, 17); + }); - verify(github.postJSON('/repos/o/n/git/tags', - convert: GitTag.fromJSON, - statusCode: StatusCodes.CREATED, - body: createGitTag.toJSON())); - }); + group('Merge', () { + test('Merge() normal', () async { + nock(fakeApiUrl) + .put('/repos/o/n/pulls/1/merge', '{"merge_method":"merge"}') + .reply(201, nocked.mergedPR1); - test('creates valid JSON body', () { - git.createTag(repo, createGitTag); + var pullRequestMerge = await github.pullRequests.merge(repo, 1); - var body = captureSentBody(github); - expect(body['tag'], equals('v0.0.1')); - expect(body['message'], equals('a message')); - expect(body['object'], equals(someSha)); - expect(body['type'], equals('commit')); - expect(body['tagger']['name'], equals('aName')); + expect(pullRequestMerge.merged, true); + expect(pullRequestMerge.message, 'Pull Request successfully merged'); + expect(pullRequestMerge.sha, someSha); }); - }); - group('getTree()', () { - test('constructs correct path', () { - git.getTree(repo, 'sh'); - - verify(github.getJSON('/repos/o/n/git/trees/sh', - convert: GitTree.fromJSON, statusCode: StatusCodes.OK)); - }); - }); + test('Merge() with squash', () async { + nock(fakeApiUrl) + .put('/repos/o/n/pulls/1/merge', '{"merge_method":"squash"}') + .reply(201, nocked.mergedPR1); - group('getTree(recursive: true)', () { - test('constructs correct path', () { - git.getTree(repo, 'sh', recursive: true); + var pullRequestMerge = await github.pullRequests + .merge(repo, 1, mergeMethod: MergeMethod.squash); - verify(github.getJSON('/repos/o/n/git/trees/sh?recursive=1', - convert: GitTree.fromJSON, statusCode: StatusCodes.OK)); + expect(pullRequestMerge.merged, true); + expect(pullRequestMerge.message, 'Pull Request successfully merged'); + expect(pullRequestMerge.sha, someSha); }); - }); - group('createTree()', () { - test('constructs correct path', () { - var createGitTree = new CreateGitTree([]); - git.createTree(repo, createGitTree); + test('Merge() with rebase', () async { + nock(fakeApiUrl) + .put('/repos/o/n/pulls/1/merge', '{"merge_method":"rebase"}') + .reply(201, nocked.mergedPR1); - verify(github.postJSON('/repos/o/n/git/trees', - convert: GitTree.fromJSON, - statusCode: StatusCodes.CREATED, - body: createGitTree.toJSON())); - }); + var pullRequestMerge = await github.pullRequests + .merge(repo, 1, mergeMethod: MergeMethod.rebase); - test('with sha creates valid JSON body', () { - // given - var treeEntry = new CreateGitTreeEntry('file.rb', '100644', 'blob', - sha: '44b4fc6d56897b048c772eb4087f854f46256132'); - - var tree = new CreateGitTree([treeEntry]); - - // when - git.createTree(repo, tree); - - // then - var body = captureSentBody(github); - expect(body['tree'], isNotNull); - expect(body['tree'][0]['path'], equals('file.rb')); - expect(body['tree'][0]['mode'], equals('100644')); - expect(body['tree'][0]['type'], equals('blob')); - expect(body['tree'][0]['sha'], - equals('44b4fc6d56897b048c772eb4087f854f46256132')); - expect(body['tree'][0]['content'], isNull); + expect(pullRequestMerge.merged, true); + expect(pullRequestMerge.message, 'Pull Request successfully merged'); + expect(pullRequestMerge.sha, someSha); }); - test('with content creates valid JSON body', () { - // given - var treeEntry = new CreateGitTreeEntry('file.rb', '100644', 'blob', - content: 'some file content'); + test('Merge() with commitMessage', () async { + const commitMessage = 'Some message'; + nock(fakeApiUrl) + .put('/repos/o/n/pulls/1/merge', + '{"commit_message":"$commitMessage","merge_method":"squash"}') + .reply(201, nocked.mergedPR1); - var tree = new CreateGitTree([treeEntry]); + var pullRequestMerge = await github.pullRequests.merge(repo, 1, + message: commitMessage, mergeMethod: MergeMethod.squash); - // when - git.createTree(repo, tree); + expect(pullRequestMerge.merged, true); + expect(pullRequestMerge.message, 'Pull Request successfully merged'); + expect(pullRequestMerge.sha, someSha); + }); - // then - var body = captureSentBody(github); - expect(body['tree'], isNotNull); - expect(body['tree'][0]['path'], equals('file.rb')); - expect(body['tree'][0]['mode'], equals('100644')); - expect(body['tree'][0]['type'], equals('blob')); - expect(body['tree'][0]['sha'], isNull); - expect(body['tree'][0]['content'], equals('some file content')); + test('Merge() with commitMessage, with sha', () async { + const commitMessage = 'Some message'; + const commitSha = 'commitSha'; + nock(fakeApiUrl) + .put('/repos/o/n/pulls/1/merge', + '{"commit_message":"$commitMessage","sha":"$commitSha","merge_method":"squash"}') + .reply(201, nocked.mergedPR1); + + var pullRequestMerge = await github.pullRequests.merge(repo, 1, + message: commitMessage, + mergeMethod: MergeMethod.squash, + requestSha: commitSha); + + expect(pullRequestMerge.merged, true); + expect(pullRequestMerge.message, 'Pull Request successfully merged'); + expect(pullRequestMerge.sha, someSha); }); }); } - -Map captureSentBody(MockGitHub github) { - var bodyString = verify( - github.postJSON(any, convert: any, statusCode: any, body: captureAny)) - .captured - .single; - - var body = JSON.decode(bodyString) as Map; - return body; -} diff --git a/test/helper.dart b/test/helper.dart index da83b0a0..eaf8ed8d 100644 --- a/test/helper.dart +++ b/test/helper.dart @@ -1,28 +1,16 @@ -library github.test.helper; - -import 'dart:async'; -import 'dart:convert'; import 'dart:io'; - -import "package:http/http.dart" as http; -import 'package:github/server.dart'; -import 'package:test/test.dart'; - -part 'helper/assets.dart'; -part 'helper/expect.dart'; -part 'helper/http.dart'; +import 'package:github/github.dart'; GitHub github = _makeGitHubClient(); GitHub _makeGitHubClient() { GitHub g; - if (Platform.environment.containsKey("GITHUB_TOKEN")) { - g = createGitHubClient( - auth: - new Authentication.withToken(Platform.environment["GITHUB_TOKEN"])); + if (Platform.environment.containsKey('GITHUB_TOKEN')) { + g = GitHub( + auth: Authentication.withToken(Platform.environment['GITHUB_TOKEN'])); } else { - g = createGitHubClient(); + g = GitHub(); } return g; diff --git a/test/helper/assets.dart b/test/helper/assets.dart index bc28aee2..e0cf6f56 100644 --- a/test/helper/assets.dart +++ b/test/helper/assets.dart @@ -1,3 +1,3 @@ -part of github.test.helper; +import 'dart:io'; -File asset(String id) => new File("test/assets/${id}"); +File asset(String id) => File('test/assets/$id'); diff --git a/test/helper/expect.dart b/test/helper/expect.dart index 4fc21f07..2053021c 100644 --- a/test/helper/expect.dart +++ b/test/helper/expect.dart @@ -1,5 +1,6 @@ -part of github.test.helper; +import 'package:github/src/common/model/repos.dart'; +import 'package:test/test.dart'; void expectSlug(RepositorySlug slug, String user, String repo) { - expect(slug.fullName, equals("${user}/${repo}")); + expect(slug.fullName, equals('$user/$repo')); } diff --git a/test/helper/http.dart b/test/helper/http.dart index d5f32203..ffed40d4 100644 --- a/test/helper/http.dart +++ b/test/helper/http.dart @@ -1,45 +1,47 @@ -part of github.test.helper; +import 'dart:convert'; +import 'package:collection/collection.dart' show IterableExtension; +import 'package:http/http.dart' as http; +import 'assets.dart'; -final MockHTTPClient httpClient = new MockHTTPClient(); +final MockHTTPClient httpClient = MockHTTPClient(); -typedef http.Response ResponseCreator(http.BaseRequest request); +typedef ResponseCreator = http.StreamedResponse Function( + http.BaseRequest request); class MockHTTPClient extends http.BaseClient { final Map responses = {}; @override - Future send(http.BaseRequest request) { - var matchingUrlCreatorKey = responses.keys.firstWhere( - (it) => it.allMatches(request.url.toString()).isNotEmpty, - orElse: () => null); - var creator = responses[matchingUrlCreatorKey]; + Future send(http.BaseRequest request) async { + final matchingUrlCreatorKey = responses.keys.firstWhereOrNull( + (it) => it.allMatches(request.url.toString()).isNotEmpty); + final creator = responses[matchingUrlCreatorKey!]; if (creator == null) { - throw new Exception("No Response Configured"); + throw Exception('No Response Configured'); } - return new Future.value(creator(request)); + return creator(request); } } class MockResponse extends http.Response { - MockResponse(String body, Map headers, int statusCode) - : super(body, statusCode, headers: headers); + MockResponse(super.body, Map headers, super.statusCode) + : super(headers: headers); factory MockResponse.fromAsset(String name) { - Map responseData = - JSON.decode(asset("responses/${name}.json").readAsStringSync()) - as Map; - Map headers = - responseData['headers'] as Map; - dynamic body = responseData['body']; - int statusCode = responseData['statusCode']; - String actualBody; + final responseData = + jsonDecode(asset('responses/$name.json').readAsStringSync()) + as Map; + final headers = responseData['headers'] as Map; + final dynamic body = responseData['body']; + final int statusCode = responseData['statusCode']; + String? actualBody; if (body is Map || body is List) { - actualBody = JSON.decode(body); + actualBody = jsonDecode(body); } else { actualBody = body.toString(); } - return new MockResponse(actualBody, headers, statusCode); + return MockResponse(actualBody!, headers, statusCode); } } diff --git a/test/scenarios_test.dart b/test/scenarios_test.dart new file mode 100644 index 00000000..fb453845 --- /dev/null +++ b/test/scenarios_test.dart @@ -0,0 +1,143 @@ +// ignore_for_file: unused_local_variable + +@Tags(['scenarios']) +@TestOn('vm') +library; + +import 'dart:convert'; + +import 'package:github/github.dart'; +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; + +final defaultFixtureServerUri = Uri.parse('http://localhost:3000/fixtures'); + +/// All folder names at @octokit/fixtures/scenarios/api.github.com +/// are valid values for [scenario]. +Future createGithubWithScenario(String scenario, + {Uri? fixtureServerUri}) async { + fixtureServerUri ??= defaultFixtureServerUri; + + // send a request to the fixtures server to get load a fixture + var resp = await http.post(fixtureServerUri, + headers: {'Content-Type': 'application/json'}, + body: '{"scenario": "$scenario"}'); + if (resp.statusCode < 200 || resp.statusCode >= 300) { + throw Exception( + 'Error loading scenario: $scenario\n${resp.statusCode}\n${resp.body}'); + } + var j = json.decode(resp.body); + return GitHub( + endpoint: j['url'], + auth: const Authentication.withToken( + '0000000000000000000000000000000000000001')); +} + +/// Run scenario tests against ockokits fixtures-server +/// https://github.com/octokit/fixtures-server +/// +/// These tests are a port of the rest.js ocktokit tests from +/// https://github.com/octokit/rest.js/tree/master/test/scenarios +/// +/// The fixture server must be running before running these tests +/// The easiest way is to install node and then run +/// npx octokit-fixtures-server +/// +/// TODO(robrbecker) Implement a fixture-server "light" in Dart +/// directly using nock so we can remove the dependency on node +/// and running a server in order to run tests +void main() { + test('add-and-remove-repository-collaborator', () async { + var gh = await createGithubWithScenario( + 'add-and-remove-repository-collaborator'); + // todo do test + }, skip: true); + test('add-labels-to-issue', () async { + var gh = await createGithubWithScenario('add-labels-to-issue'); + // todo do test + }, skip: true); + test('branch-protection', () async { + var gh = await createGithubWithScenario('branch-protection'); + // todo do test + }, skip: true); + test('create-file', () async { + var gh = await createGithubWithScenario('create-file'); + // todo do test + }, skip: true); + + test('create-status', () async { + var gh = await createGithubWithScenario('create-status'); + // todo do test + }, skip: true); + test('errors', () async { + var gh = await createGithubWithScenario('errors'); + // todo do test + }, skip: true); + test('get-archive', () async { + var gh = await createGithubWithScenario('get-archive'); + // todo do test + }, skip: true); + test('get-content', () async { + var gh = await createGithubWithScenario('get-content'); + // todo do test + }, skip: true); + + test('get-organization', () async { + var gh = await createGithubWithScenario('get-organization'); + var org = await gh.organizations.get('octokit-fixture-org'); + expect(org.login, 'octokit-fixture-org'); + }); + + test('get-repository', () async { + var gh = await createGithubWithScenario('get-repository'); + // todo do test + }, skip: true); + test('get-root', () async { + var gh = await createGithubWithScenario('get-root'); + // todo do test + }, skip: true); + test('git-refs', () async { + var gh = await createGithubWithScenario('git-refs'); + // todo do test + }, skip: true); + test('labels', () async { + var gh = await createGithubWithScenario('labels'); + // todo do test + }, skip: true); + test('lock-issue', () async { + var gh = await createGithubWithScenario('lock-issue'); + // todo do test + }, skip: true); + test('mark-notifications-as-read', () async { + var gh = await createGithubWithScenario('mark-notifications-as-read'); + // todo do test + }, skip: true); + test('markdown', () async { + var gh = await createGithubWithScenario('markdown'); + // todo do test + }, skip: true); + test('paginate-issues', () async { + var gh = await createGithubWithScenario('paginate-issues'); + // todo do test + }, skip: true); + test('project-cards', () async { + var gh = await createGithubWithScenario('project-cards'); + // todo do test + }, skip: true); + test('release-assets-conflict', () async { + var gh = await createGithubWithScenario('release-assets-conflict'); + // todo do test + }, skip: true); + test('release-assets', () async { + var gh = await createGithubWithScenario('release-assets'); + // todo do test + }, skip: true); + test('rename-repository', () async { + var gh = await createGithubWithScenario('rename-repository'); + // todo do test + }, skip: true); + test('search-issues', () async { + var gh = await createGithubWithScenario('search-issues'); + // todo do test + }, skip: true); +} diff --git a/test/server/hooks_test.dart b/test/server/hooks_test.dart new file mode 100644 index 00000000..d00cb3de --- /dev/null +++ b/test/server/hooks_test.dart @@ -0,0 +1,85 @@ +import 'dart:convert'; +import 'package:github/github.dart'; +import 'package:github/hooks.dart'; +import 'package:test/test.dart'; + +import 'hooks_test_data.dart'; + +void main() { + group('CheckSuiteEvent', () { + test('deserialize', () async { + final checkSuiteEvent = CheckSuiteEvent.fromJson( + json.decode(checkSuiteString) as Map); + // Top level properties. + expect(checkSuiteEvent.action, 'requested'); + expect(checkSuiteEvent.checkSuite, isA()); + // CheckSuite properties. + final suite = checkSuiteEvent.checkSuite!; + expect(suite.headSha, 'ec26c3e57ca3a959ca5aad62de7213c562f8c821'); + expect(suite.id, 118578147); + expect(suite.conclusion, CheckRunConclusion.success); + }); + }); + group('CheckRunEvent', () { + test('deserialize', () async { + final checkRunEvent = CheckRunEvent.fromJson( + json.decode(checkRunString) as Map); + // Top level properties. + expect(checkRunEvent.action, 'created'); + expect(checkRunEvent.checkRun, isA()); + // CheckSuite properties. + final checkRun = checkRunEvent.checkRun!; + expect(checkRun.headSha, 'ec26c3e57ca3a959ca5aad62de7213c562f8c821'); + expect(checkRun.checkSuiteId, 118578147); + expect(checkRun.detailsUrl, 'https://octocoders.io'); + expect(checkRun.externalId, ''); + expect(checkRun.id, 128620228); + expect(checkRun.name, 'Octocoders-linter'); + expect(checkRun.startedAt, DateTime.utc(2019, 05, 15, 15, 21, 12)); + expect(checkRun.status, CheckRunStatus.queued); + }); + }); + + group('CreateEvent', () { + test('deserialize', () async { + final createEvent = CreateEvent.fromJson( + json.decode(createString) as Map); + expect(createEvent.ref, 'simple-branch'); + expect(createEvent.refType, 'branch'); + expect(createEvent.pusherType, 'user'); + + final repo = createEvent.repository!; + expect(repo.slug().fullName, 'Codertocat/Hello-World'); + expect(repo.id, 186853002); + + final sender = createEvent.sender!; + expect(sender.login, "Codertocat"); + expect(sender.htmlUrl, "https://github.com/Codertocat"); + }); + }); + + group('EditedPullRequest', () { + test('deserialize with body edit', () { + final pullRequestEditedEvent = PullRequestEvent.fromJson( + jsonDecode(prBodyEditedEvent) as Map); + final changes = pullRequestEditedEvent.changes; + expect(changes, isNotNull); + expect(changes!.body!.from, isNotNull); + assert(changes.body!.from == + '**This should not land until https://github.com/flutter/buildroot/pull/790'); + }); + + test('deserialize with base edit', () { + final pullRequestEditedEvent = PullRequestEvent.fromJson( + jsonDecode(prBaseEditedEvent) as Map); + final changes = pullRequestEditedEvent.changes; + expect(changes, isNotNull); + expect(changes!.body, isNull); + expect(changes.base, isNotNull); + expect(changes.base!.ref, isNotNull); + assert(changes.base!.ref!.from == 'main'); + assert(changes.base!.sha!.from == + 'b3af5d64d3e6e2110b07d71909fc432537339659'); + }); + }); +} diff --git a/test/server/hooks_test_data.dart b/test/server/hooks_test_data.dart new file mode 100644 index 00000000..ad02cc10 --- /dev/null +++ b/test/server/hooks_test_data.dart @@ -0,0 +1,1818 @@ +/// Json messages as dart string used for checks model tests. +library; + +String checkSuiteString = checkSuiteTemplate('requested'); + +String checkSuiteTemplate(String action) => '''\ +{ + "action": "$action", + "check_suite": { + "id": 118578147, + "node_id": "MDEwOkNoZWNrU3VpdGUxMTg1NzgxNDc=", + "head_branch": "changes", + "head_sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "status": "completed", + "conclusion": "success", + "url": "https://api.github.com/repos/Codertocat/Hello-World/check-suites/118578147", + "before": "6113728f27ae82c7b1a177c8d03f9e96e0adf246", + "after": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "pull_requests": [ + { + "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2", + "id": 279147437, + "number": 2, + "head": { + "ref": "changes", + "sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + }, + "base": { + "ref": "master", + "sha": "f95f852bd8fca8fcc58a9a2d6c842781e32a215e", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + } + } + ], + "app": { + "id": 29310, + "node_id": "MDM6QXBwMjkzMTA=", + "owner": { + "login": "Octocoders", + "id": 38302899, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjM4MzAyODk5", + "avatar_url": "https://avatars1.githubusercontent.com/u/38302899?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Octocoders", + "html_url": "https://github.com/Octocoders", + "followers_url": "https://api.github.com/users/Octocoders/followers", + "following_url": "https://api.github.com/users/Octocoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Octocoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Octocoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Octocoders/subscriptions", + "organizations_url": "https://api.github.com/users/Octocoders/orgs", + "repos_url": "https://api.github.com/users/Octocoders/repos", + "events_url": "https://api.github.com/users/Octocoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Octocoders/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "octocoders-linter", + "description": "", + "external_url": "https://octocoders.io", + "html_url": "https://github.com/apps/octocoders-linter", + "created_at": "2019-04-19T19:36:24Z", + "updated_at": "2019-04-19T19:36:56Z", + "permissions": { + "administration": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "issues": "write", + "members": "write", + "metadata": "read", + "organization_administration": "write", + "organization_hooks": "write", + "organization_plan": "read", + "organization_projects": "write", + "organization_user_blocking": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "statuses": "write", + "team_discussions": "write", + "vulnerability_alerts": "read" + }, + "events": [] + }, + "created_at": "2019-05-15T15:20:31Z", + "updated_at": "2019-05-15T15:21:14Z", + "latest_check_runs_count": 1, + "check_runs_url": "https://api.github.com/repos/Codertocat/Hello-World/check-suites/118578147/check-runs", + "head_commit": { + "id": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "tree_id": "31b122c26a97cf9af023e9ddab94a82c6e77b0ea", + "message": "Update README.md", + "timestamp": "2019-05-15T15:20:30Z", + "author": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com" + }, + "committer": { + "name": "Codertocat", + "email": "21031067+Codertocat@users.noreply.github.com" + } + } + }, + "repository": { + "id": 186853002, + "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "private": false, + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2019-05-15T15:19:25Z", + "updated_at": "2019-05-15T15:21:14Z", + "pushed_at": "2019-05-15T15:20:57Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Ruby", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 2, + "license": null, + "forks": 0, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } +} +'''; + +const String checkRunString = ''' +{ + "action": "created", + "check_run": { + "id": 128620228, + "node_id": "MDg6Q2hlY2tSdW4xMjg2MjAyMjg=", + "head_sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "external_id": "", + "url": "https://api.github.com/repos/Codertocat/Hello-World/check-runs/128620228", + "html_url": "https://github.com/Codertocat/Hello-World/runs/128620228", + "details_url": "https://octocoders.io", + "status": "queued", + "conclusion": null, + "started_at": "2019-05-15T15:21:12Z", + "completed_at": null, + "output": { + "title": null, + "summary": null, + "text": null, + "annotations_count": 0, + "annotations_url": "https://api.github.com/repos/Codertocat/Hello-World/check-runs/128620228/annotations" + }, + "name": "Octocoders-linter", + "check_suite": { + "id": 118578147, + "node_id": "MDEwOkNoZWNrU3VpdGUxMTg1NzgxNDc=", + "head_branch": "changes", + "head_sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "status": "queued", + "conclusion": null, + "url": "https://api.github.com/repos/Codertocat/Hello-World/check-suites/118578147", + "before": "6113728f27ae82c7b1a177c8d03f9e96e0adf246", + "after": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "pull_requests": [ + { + "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2", + "id": 279147437, + "number": 2, + "head": { + "ref": "changes", + "sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + }, + "base": { + "ref": "master", + "sha": "f95f852bd8fca8fcc58a9a2d6c842781e32a215e", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + } + } + ], + "app": { + "id": 29310, + "node_id": "MDM6QXBwMjkzMTA=", + "owner": { + "login": "Octocoders", + "id": 38302899, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjM4MzAyODk5", + "avatar_url": "https://avatars1.githubusercontent.com/u/38302899?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Octocoders", + "html_url": "https://github.com/Octocoders", + "followers_url": "https://api.github.com/users/Octocoders/followers", + "following_url": "https://api.github.com/users/Octocoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Octocoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Octocoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Octocoders/subscriptions", + "organizations_url": "https://api.github.com/users/Octocoders/orgs", + "repos_url": "https://api.github.com/users/Octocoders/repos", + "events_url": "https://api.github.com/users/Octocoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Octocoders/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "octocoders-linter", + "description": "", + "external_url": "https://octocoders.io", + "html_url": "https://github.com/apps/octocoders-linter", + "created_at": "2019-04-19T19:36:24Z", + "updated_at": "2019-04-19T19:36:56Z", + "permissions": { + "administration": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "issues": "write", + "members": "write", + "metadata": "read", + "organization_administration": "write", + "organization_hooks": "write", + "organization_plan": "read", + "organization_projects": "write", + "organization_user_blocking": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "statuses": "write", + "team_discussions": "write", + "vulnerability_alerts": "read" + }, + "events": [] + }, + "created_at": "2019-05-15T15:20:31Z", + "updated_at": "2019-05-15T15:20:31Z" + }, + "app": { + "id": 29310, + "node_id": "MDM6QXBwMjkzMTA=", + "owner": { + "login": "Octocoders", + "id": 38302899, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjM4MzAyODk5", + "avatar_url": "https://avatars1.githubusercontent.com/u/38302899?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Octocoders", + "html_url": "https://github.com/Octocoders", + "followers_url": "https://api.github.com/users/Octocoders/followers", + "following_url": "https://api.github.com/users/Octocoders/following{/other_user}", + "gists_url": "https://api.github.com/users/Octocoders/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Octocoders/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Octocoders/subscriptions", + "organizations_url": "https://api.github.com/users/Octocoders/orgs", + "repos_url": "https://api.github.com/users/Octocoders/repos", + "events_url": "https://api.github.com/users/Octocoders/events{/privacy}", + "received_events_url": "https://api.github.com/users/Octocoders/received_events", + "type": "Organization", + "site_admin": false + }, + "name": "octocoders-linter", + "description": "", + "external_url": "https://octocoders.io", + "html_url": "https://github.com/apps/octocoders-linter", + "created_at": "2019-04-19T19:36:24Z", + "updated_at": "2019-04-19T19:36:56Z", + "permissions": { + "administration": "write", + "checks": "write", + "contents": "write", + "deployments": "write", + "issues": "write", + "members": "write", + "metadata": "read", + "organization_administration": "write", + "organization_hooks": "write", + "organization_plan": "read", + "organization_projects": "write", + "organization_user_blocking": "write", + "pages": "write", + "pull_requests": "write", + "repository_hooks": "write", + "repository_projects": "write", + "statuses": "write", + "team_discussions": "write", + "vulnerability_alerts": "read" + }, + "events": [] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/Codertocat/Hello-World/pulls/2", + "id": 279147437, + "number": 2, + "head": { + "ref": "changes", + "sha": "ec26c3e57ca3a959ca5aad62de7213c562f8c821", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + }, + "base": { + "ref": "master", + "sha": "f95f852bd8fca8fcc58a9a2d6c842781e32a215e", + "repo": { + "id": 186853002, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "name": "Hello-World" + } + } + } + ], + "deployment": { + "url": "https://api.github.com/repos/Codertocat/Hello-World/deployments/326191728", + "id": 326191728, + "node_id": "MDEwOkRlcGxveW1lbnQzMjYxOTE3Mjg=", + "task": "deploy", + "original_environment": "lab", + "environment": "lab", + "description": null, + "created_at": "2021-02-18T08:22:48Z", + "updated_at": "2021-02-18T09:47:16Z", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments/326191728/statuses", + "repository_url": "https://api.github.com/repos/Codertocat/Hello-World" + } + }, + "repository": { + "id": 186853002, + "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "private": false, + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2019-05-15T15:19:25Z", + "updated_at": "2019-05-15T15:21:03Z", + "pushed_at": "2019-05-15T15:20:57Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Ruby", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 2, + "license": null, + "forks": 1, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } +} +'''; + +const String createString = ''' +{ + "ref": "simple-branch", + "ref_type": "branch", + "master_branch": "master", + "description": null, + "pusher_type": "user", + "repository": { + "id": 186853002, + "node_id": "MDEwOlJlcG9zaXRvcnkxODY4NTMwMDI=", + "name": "Hello-World", + "full_name": "Codertocat/Hello-World", + "private": false, + "owner": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/Codertocat/Hello-World", + "description": null, + "fork": false, + "url": "https://api.github.com/repos/Codertocat/Hello-World", + "forks_url": "https://api.github.com/repos/Codertocat/Hello-World/forks", + "keys_url": "https://api.github.com/repos/Codertocat/Hello-World/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/Codertocat/Hello-World/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/Codertocat/Hello-World/teams", + "hooks_url": "https://api.github.com/repos/Codertocat/Hello-World/hooks", + "issue_events_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/events{/number}", + "events_url": "https://api.github.com/repos/Codertocat/Hello-World/events", + "assignees_url": "https://api.github.com/repos/Codertocat/Hello-World/assignees{/user}", + "branches_url": "https://api.github.com/repos/Codertocat/Hello-World/branches{/branch}", + "tags_url": "https://api.github.com/repos/Codertocat/Hello-World/tags", + "blobs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/Codertocat/Hello-World/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/Codertocat/Hello-World/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/Codertocat/Hello-World/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/Codertocat/Hello-World/statuses/{sha}", + "languages_url": "https://api.github.com/repos/Codertocat/Hello-World/languages", + "stargazers_url": "https://api.github.com/repos/Codertocat/Hello-World/stargazers", + "contributors_url": "https://api.github.com/repos/Codertocat/Hello-World/contributors", + "subscribers_url": "https://api.github.com/repos/Codertocat/Hello-World/subscribers", + "subscription_url": "https://api.github.com/repos/Codertocat/Hello-World/subscription", + "commits_url": "https://api.github.com/repos/Codertocat/Hello-World/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/Codertocat/Hello-World/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/Codertocat/Hello-World/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/Codertocat/Hello-World/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/Codertocat/Hello-World/contents/{+path}", + "compare_url": "https://api.github.com/repos/Codertocat/Hello-World/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/Codertocat/Hello-World/merges", + "archive_url": "https://api.github.com/repos/Codertocat/Hello-World/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/Codertocat/Hello-World/downloads", + "issues_url": "https://api.github.com/repos/Codertocat/Hello-World/issues{/number}", + "pulls_url": "https://api.github.com/repos/Codertocat/Hello-World/pulls{/number}", + "milestones_url": "https://api.github.com/repos/Codertocat/Hello-World/milestones{/number}", + "notifications_url": "https://api.github.com/repos/Codertocat/Hello-World/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/Codertocat/Hello-World/labels{/name}", + "releases_url": "https://api.github.com/repos/Codertocat/Hello-World/releases{/id}", + "deployments_url": "https://api.github.com/repos/Codertocat/Hello-World/deployments", + "created_at": "2019-05-15T15:19:25Z", + "updated_at": "2019-05-15T15:20:41Z", + "pushed_at": "2019-05-15T15:20:56Z", + "git_url": "git://github.com/Codertocat/Hello-World.git", + "ssh_url": "git@github.com:Codertocat/Hello-World.git", + "clone_url": "https://github.com/Codertocat/Hello-World.git", + "svn_url": "https://github.com/Codertocat/Hello-World", + "homepage": null, + "size": 0, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Ruby", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 1, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 2, + "license": null, + "forks": 1, + "open_issues": 2, + "watchers": 0, + "default_branch": "master" + }, + "sender": { + "login": "Codertocat", + "id": 21031067, + "node_id": "MDQ6VXNlcjIxMDMxMDY3", + "avatar_url": "https://avatars1.githubusercontent.com/u/21031067?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/Codertocat", + "html_url": "https://github.com/Codertocat", + "followers_url": "https://api.github.com/users/Codertocat/followers", + "following_url": "https://api.github.com/users/Codertocat/following{/other_user}", + "gists_url": "https://api.github.com/users/Codertocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/Codertocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/Codertocat/subscriptions", + "organizations_url": "https://api.github.com/users/Codertocat/orgs", + "repos_url": "https://api.github.com/users/Codertocat/repos", + "events_url": "https://api.github.com/users/Codertocat/events{/privacy}", + "received_events_url": "https://api.github.com/users/Codertocat/received_events", + "type": "User", + "site_admin": false + } +} +'''; + +const String prBodyEditedEvent = ''' +{ + "action": "edited", + "number": 47609, + "pull_request": { + "url": "https://api.github.com/repos/flutter/engine/pulls/47609", + "id": 1584723957, + "node_id": "PR_kwDOAlZRSc5edPf1", + "html_url": "https://github.com/flutter/engine/pull/47609", + "diff_url": "https://github.com/flutter/engine/pull/47609.diff", + "patch_url": "https://github.com/flutter/engine/pull/47609.patch", + "issue_url": "https://api.github.com/repos/flutter/engine/issues/47609", + "number": 47609, + "state": "open", + "locked": false, + "title": "Upgrade Android SDK to 34 UpsideDownCake", + "user": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "body": "~**This should not land until https://github.com/flutter/buildroot/pull/790 (re)lands, and I swap the buildroot url back to the latest commit.**~ Reland of PR to update buildroot at https://github.com/flutter/buildroot/pull/792. Upgrades to android api 34 Also: 1. Upgrades to java 17 in DEPS/ci", + "created_at": "2023-11-02T17:09:59Z", + "updated_at": "2023-11-08T21:00:47Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "8e5e3a59a5cba4239c542ed9a914899a246640b7", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + { + "id": 246348935, + "node_id": "MDU6TGFiZWwyNDYzNDg5MzU=", + "url": "https://api.github.com/repos/flutter/engine/labels/platform-android", + "name": "platform-android", + "color": "A4C639", + "default": false, + "description": null + } + ], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/flutter/engine/pulls/47609/commits", + "review_comments_url": "https://api.github.com/repos/flutter/engine/pulls/47609/comments", + "review_comment_url": "https://api.github.com/repos/flutter/engine/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/flutter/engine/issues/47609/comments", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/a6765b4c309aa082bbebade68e0c7ec308a1cc6c", + "head": { + "label": "gmackall:upgrade_to_android14", + "ref": "upgrade_to_android14", + "sha": "a6765b4c309aa082bbebade68e0c7ec308a1cc6c", + "user": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 547558963, + "node_id": "R_kgDOIKMWMw", + "name": "engine", + "full_name": "gmackall/engine", + "private": false, + "owner": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/gmackall/engine", + "description": "The Flutter engine", + "fork": true, + "url": "https://api.github.com/repos/gmackall/engine", + "forks_url": "https://api.github.com/repos/gmackall/engine/forks", + "keys_url": "https://api.github.com/repos/gmackall/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/gmackall/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/gmackall/engine/teams", + "hooks_url": "https://api.github.com/repos/gmackall/engine/hooks", + "issue_events_url": "https://api.github.com/repos/gmackall/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/gmackall/engine/events", + "assignees_url": "https://api.github.com/repos/gmackall/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/gmackall/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/gmackall/engine/tags", + "blobs_url": "https://api.github.com/repos/gmackall/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/gmackall/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/gmackall/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/gmackall/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/gmackall/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/gmackall/engine/languages", + "stargazers_url": "https://api.github.com/repos/gmackall/engine/stargazers", + "contributors_url": "https://api.github.com/repos/gmackall/engine/contributors", + "subscribers_url": "https://api.github.com/repos/gmackall/engine/subscribers", + "subscription_url": "https://api.github.com/repos/gmackall/engine/subscription", + "commits_url": "https://api.github.com/repos/gmackall/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/gmackall/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/gmackall/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/gmackall/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/gmackall/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/gmackall/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/gmackall/engine/merges", + "archive_url": "https://api.github.com/repos/gmackall/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/gmackall/engine/downloads", + "issues_url": "https://api.github.com/repos/gmackall/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/gmackall/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/gmackall/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/gmackall/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/gmackall/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/gmackall/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/gmackall/engine/deployments", + "created_at": "2022-10-07T22:25:57Z", + "updated_at": "2023-02-02T18:38:07Z", + "pushed_at": "2023-11-08T20:57:02Z", + "git_url": "git://github.com/gmackall/engine.git", + "ssh_url": "git@github.com:gmackall/engine.git", + "clone_url": "https://github.com/gmackall/engine.git", + "svn_url": "https://github.com/gmackall/engine", + "homepage": "https://flutter.dev", + "size": 466778, + "stargazers_count": 0, + "watchers_count": 0, + "language": "C++", + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "base": { + "label": "flutter:main", + "ref": "main", + "sha": "941e246d4851f652cf13312180174ebc9395fac4", + "user": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 39211337, + "node_id": "MDEwOlJlcG9zaXRvcnkzOTIxMTMzNw==", + "name": "engine", + "full_name": "flutter/engine", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/engine", + "description": "The Flutter engine", + "fork": false, + "url": "https://api.github.com/repos/flutter/engine", + "forks_url": "https://api.github.com/repos/flutter/engine/forks", + "keys_url": "https://api.github.com/repos/flutter/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/flutter/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/flutter/engine/teams", + "hooks_url": "https://api.github.com/repos/flutter/engine/hooks", + "issue_events_url": "https://api.github.com/repos/flutter/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/flutter/engine/events", + "assignees_url": "https://api.github.com/repos/flutter/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/flutter/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/flutter/engine/tags", + "blobs_url": "https://api.github.com/repos/flutter/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/flutter/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/flutter/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/flutter/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/flutter/engine/languages", + "stargazers_url": "https://api.github.com/repos/flutter/engine/stargazers", + "contributors_url": "https://api.github.com/repos/flutter/engine/contributors", + "subscribers_url": "https://api.github.com/repos/flutter/engine/subscribers", + "subscription_url": "https://api.github.com/repos/flutter/engine/subscription", + "commits_url": "https://api.github.com/repos/flutter/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/flutter/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/flutter/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/flutter/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/flutter/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/flutter/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/flutter/engine/merges", + "archive_url": "https://api.github.com/repos/flutter/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/flutter/engine/downloads", + "issues_url": "https://api.github.com/repos/flutter/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/flutter/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/flutter/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/flutter/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/flutter/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/flutter/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/flutter/engine/deployments", + "created_at": "2015-07-16T17:39:56Z", + "updated_at": "2023-11-08T07:16:25Z", + "pushed_at": "2023-11-08T21:00:38Z", + "git_url": "git://github.com/flutter/engine.git", + "ssh_url": "git@github.com:flutter/engine.git", + "clone_url": "https://github.com/flutter/engine.git", + "svn_url": "https://github.com/flutter/engine", + "homepage": "https://flutter.dev", + "size": 704170, + "stargazers_count": 6848, + "watchers_count": 6848, + "language": "C++", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 5600, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 99, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "c-plus-plus" + ], + "visibility": "public", + "forks": 5600, + "open_issues": 99, + "watchers": 6848, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": false, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": true, + "squash_merge_commit_message": "PR_BODY", + "squash_merge_commit_title": "PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609" + }, + "html": { + "href": "https://github.com/flutter/engine/pull/47609" + }, + "issue": { + "href": "https://api.github.com/repos/flutter/engine/issues/47609" + }, + "comments": { + "href": "https://api.github.com/repos/flutter/engine/issues/47609/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/flutter/engine/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/flutter/engine/statuses/a6765b4c309aa082bbebade68e0c7ec308a1cc6c" + } + }, + "author_association": "MEMBER", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": null, + "comments": 4, + "review_comments": 4, + "maintainer_can_modify": true, + "commits": 32, + "additions": 83, + "deletions": 77, + "changed_files": 18 + }, + "changes": { + "body": { + "from": "**This should not land until https://github.com/flutter/buildroot/pull/790" + } + }, + "repository": { + "id": 39211337, + "node_id": "MDEwOlJlcG9zaXRvcnkzOTIxMTMzNw==", + "name": "engine", + "full_name": "flutter/engine", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/engine", + "description": "The Flutter engine", + "fork": false, + "url": "https://api.github.com/repos/flutter/engine", + "forks_url": "https://api.github.com/repos/flutter/engine/forks", + "keys_url": "https://api.github.com/repos/flutter/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/flutter/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/flutter/engine/teams", + "hooks_url": "https://api.github.com/repos/flutter/engine/hooks", + "issue_events_url": "https://api.github.com/repos/flutter/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/flutter/engine/events", + "assignees_url": "https://api.github.com/repos/flutter/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/flutter/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/flutter/engine/tags", + "blobs_url": "https://api.github.com/repos/flutter/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/flutter/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/flutter/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/flutter/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/flutter/engine/languages", + "stargazers_url": "https://api.github.com/repos/flutter/engine/stargazers", + "contributors_url": "https://api.github.com/repos/flutter/engine/contributors", + "subscribers_url": "https://api.github.com/repos/flutter/engine/subscribers", + "subscription_url": "https://api.github.com/repos/flutter/engine/subscription", + "commits_url": "https://api.github.com/repos/flutter/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/flutter/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/flutter/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/flutter/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/flutter/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/flutter/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/flutter/engine/merges", + "archive_url": "https://api.github.com/repos/flutter/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/flutter/engine/downloads", + "issues_url": "https://api.github.com/repos/flutter/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/flutter/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/flutter/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/flutter/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/flutter/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/flutter/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/flutter/engine/deployments", + "created_at": "2015-07-16T17:39:56Z", + "updated_at": "2023-11-08T07:16:25Z", + "pushed_at": "2023-11-08T21:00:38Z", + "git_url": "git://github.com/flutter/engine.git", + "ssh_url": "git@github.com:flutter/engine.git", + "clone_url": "https://github.com/flutter/engine.git", + "svn_url": "https://github.com/flutter/engine", + "homepage": "https://flutter.dev", + "size": 704170, + "stargazers_count": 6848, + "watchers_count": 6848, + "language": "C++", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 5600, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 99, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "c-plus-plus" + ], + "visibility": "public", + "forks": 5600, + "open_issues": 99, + "watchers": 6848, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "url": "https://api.github.com/orgs/flutter", + "repos_url": "https://api.github.com/orgs/flutter/repos", + "events_url": "https://api.github.com/orgs/flutter/events", + "hooks_url": "https://api.github.com/orgs/flutter/hooks", + "issues_url": "https://api.github.com/orgs/flutter/issues", + "members_url": "https://api.github.com/orgs/flutter/members{/member}", + "public_members_url": "https://api.github.com/orgs/flutter/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "description": "Flutter is Google's UI toolkit for building beautiful, natively compiled applications for mobile, web, desktop, and embedded devices from a single codebase." + }, + "enterprise": { + "id": 1732, + "slug": "alphabet", + "name": "Alphabet", + "node_id": "MDEwOkVudGVycHJpc2UxNzMy", + "avatar_url": "https://avatars.githubusercontent.com/b/1732?v=4", + "description": "", + "website_url": "https://abc.xyz/", + "html_url": "https://github.com/enterprises/alphabet", + "created_at": "2019-12-19T00:30:52Z", + "updated_at": "2023-01-20T00:41:48Z" + }, + "sender": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 10381585, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTAzODE1ODU=" + } +} +'''; + +const String prBaseEditedEvent = ''' +{ + "action": "edited", + "number": 47609, + "pull_request": { + "url": "https://api.github.com/repos/flutter/engine/pulls/47609", + "id": 1584723957, + "node_id": "PR_kwDOAlZRSc5edPf1", + "html_url": "https://github.com/flutter/engine/pull/47609", + "diff_url": "https://github.com/flutter/engine/pull/47609.diff", + "patch_url": "https://github.com/flutter/engine/pull/47609.patch", + "issue_url": "https://api.github.com/repos/flutter/engine/issues/47609", + "number": 47609, + "state": "open", + "locked": false, + "title": "Upgrade Android SDK to 34 UpsideDownCake", + "user": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "body": "~**This should not land until https://github.com/flutter/buildroot/pull/790 (re)lands, and I swap the buildroot url back to the latest commit.**~ Reland of PR to update buildroot at https://github.com/flutter/buildroot/pull/792. Upgrades to android api 34 Also: 1. Upgrades to java 17 in DEPS/ci", + "created_at": "2023-11-02T17:09:59Z", + "updated_at": "2023-11-08T21:00:47Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "8e5e3a59a5cba4239c542ed9a914899a246640b7", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + { + "id": 246348935, + "node_id": "MDU6TGFiZWwyNDYzNDg5MzU=", + "url": "https://api.github.com/repos/flutter/engine/labels/platform-android", + "name": "platform-android", + "color": "A4C639", + "default": false, + "description": null + } + ], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/flutter/engine/pulls/47609/commits", + "review_comments_url": "https://api.github.com/repos/flutter/engine/pulls/47609/comments", + "review_comment_url": "https://api.github.com/repos/flutter/engine/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/flutter/engine/issues/47609/comments", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/a6765b4c309aa082bbebade68e0c7ec308a1cc6c", + "head": { + "label": "gmackall:upgrade_to_android14", + "ref": "upgrade_to_android14", + "sha": "a6765b4c309aa082bbebade68e0c7ec308a1cc6c", + "user": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 547558963, + "node_id": "R_kgDOIKMWMw", + "name": "engine", + "full_name": "gmackall/engine", + "private": false, + "owner": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "html_url": "https://github.com/gmackall/engine", + "description": "The Flutter engine", + "fork": true, + "url": "https://api.github.com/repos/gmackall/engine", + "forks_url": "https://api.github.com/repos/gmackall/engine/forks", + "keys_url": "https://api.github.com/repos/gmackall/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/gmackall/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/gmackall/engine/teams", + "hooks_url": "https://api.github.com/repos/gmackall/engine/hooks", + "issue_events_url": "https://api.github.com/repos/gmackall/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/gmackall/engine/events", + "assignees_url": "https://api.github.com/repos/gmackall/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/gmackall/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/gmackall/engine/tags", + "blobs_url": "https://api.github.com/repos/gmackall/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/gmackall/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/gmackall/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/gmackall/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/gmackall/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/gmackall/engine/languages", + "stargazers_url": "https://api.github.com/repos/gmackall/engine/stargazers", + "contributors_url": "https://api.github.com/repos/gmackall/engine/contributors", + "subscribers_url": "https://api.github.com/repos/gmackall/engine/subscribers", + "subscription_url": "https://api.github.com/repos/gmackall/engine/subscription", + "commits_url": "https://api.github.com/repos/gmackall/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/gmackall/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/gmackall/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/gmackall/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/gmackall/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/gmackall/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/gmackall/engine/merges", + "archive_url": "https://api.github.com/repos/gmackall/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/gmackall/engine/downloads", + "issues_url": "https://api.github.com/repos/gmackall/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/gmackall/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/gmackall/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/gmackall/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/gmackall/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/gmackall/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/gmackall/engine/deployments", + "created_at": "2022-10-07T22:25:57Z", + "updated_at": "2023-02-02T18:38:07Z", + "pushed_at": "2023-11-08T20:57:02Z", + "git_url": "git://github.com/gmackall/engine.git", + "ssh_url": "git@github.com:gmackall/engine.git", + "clone_url": "https://github.com/gmackall/engine.git", + "svn_url": "https://github.com/gmackall/engine", + "homepage": "https://flutter.dev", + "size": 466778, + "stargazers_count": 0, + "watchers_count": 0, + "language": "C++", + "has_issues": false, + "has_projects": true, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 0, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 0, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": true, + "allow_rebase_merge": true, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": false, + "squash_merge_commit_message": "COMMIT_MESSAGES", + "squash_merge_commit_title": "COMMIT_OR_PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "base": { + "label": "flutter:main", + "ref": "main", + "sha": "941e246d4851f652cf13312180174ebc9395fac4", + "user": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 39211337, + "node_id": "MDEwOlJlcG9zaXRvcnkzOTIxMTMzNw==", + "name": "engine", + "full_name": "flutter/engine", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/engine", + "description": "The Flutter engine", + "fork": false, + "url": "https://api.github.com/repos/flutter/engine", + "forks_url": "https://api.github.com/repos/flutter/engine/forks", + "keys_url": "https://api.github.com/repos/flutter/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/flutter/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/flutter/engine/teams", + "hooks_url": "https://api.github.com/repos/flutter/engine/hooks", + "issue_events_url": "https://api.github.com/repos/flutter/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/flutter/engine/events", + "assignees_url": "https://api.github.com/repos/flutter/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/flutter/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/flutter/engine/tags", + "blobs_url": "https://api.github.com/repos/flutter/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/flutter/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/flutter/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/flutter/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/flutter/engine/languages", + "stargazers_url": "https://api.github.com/repos/flutter/engine/stargazers", + "contributors_url": "https://api.github.com/repos/flutter/engine/contributors", + "subscribers_url": "https://api.github.com/repos/flutter/engine/subscribers", + "subscription_url": "https://api.github.com/repos/flutter/engine/subscription", + "commits_url": "https://api.github.com/repos/flutter/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/flutter/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/flutter/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/flutter/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/flutter/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/flutter/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/flutter/engine/merges", + "archive_url": "https://api.github.com/repos/flutter/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/flutter/engine/downloads", + "issues_url": "https://api.github.com/repos/flutter/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/flutter/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/flutter/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/flutter/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/flutter/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/flutter/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/flutter/engine/deployments", + "created_at": "2015-07-16T17:39:56Z", + "updated_at": "2023-11-08T07:16:25Z", + "pushed_at": "2023-11-08T21:00:38Z", + "git_url": "git://github.com/flutter/engine.git", + "ssh_url": "git@github.com:flutter/engine.git", + "clone_url": "https://github.com/flutter/engine.git", + "svn_url": "https://github.com/flutter/engine", + "homepage": "https://flutter.dev", + "size": 704170, + "stargazers_count": 6848, + "watchers_count": 6848, + "language": "C++", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 5600, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 99, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "c-plus-plus" + ], + "visibility": "public", + "forks": 5600, + "open_issues": 99, + "watchers": 6848, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": false, + "delete_branch_on_merge": true, + "allow_update_branch": true, + "use_squash_pr_title_as_default": true, + "squash_merge_commit_message": "PR_BODY", + "squash_merge_commit_title": "PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609" + }, + "html": { + "href": "https://github.com/flutter/engine/pull/47609" + }, + "issue": { + "href": "https://api.github.com/repos/flutter/engine/issues/47609" + }, + "comments": { + "href": "https://api.github.com/repos/flutter/engine/issues/47609/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/flutter/engine/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/flutter/engine/pulls/47609/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/flutter/engine/statuses/a6765b4c309aa082bbebade68e0c7ec308a1cc6c" + } + }, + "author_association": "MEMBER", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "clean", + "merged_by": null, + "comments": 4, + "review_comments": 4, + "maintainer_can_modify": true, + "commits": 32, + "additions": 83, + "deletions": 77, + "changed_files": 18 + }, + "changes": { + "base": { + "ref": { + "from": "main" + }, + "sha": { + "from": "b3af5d64d3e6e2110b07d71909fc432537339659" + } + } + }, + "repository": { + "id": 39211337, + "node_id": "MDEwOlJlcG9zaXRvcnkzOTIxMTMzNw==", + "name": "engine", + "full_name": "flutter/engine", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "followers_url": "https://api.github.com/users/flutter/followers", + "following_url": "https://api.github.com/users/flutter/following{/other_user}", + "gists_url": "https://api.github.com/users/flutter/gists{/gist_id}", + "starred_url": "https://api.github.com/users/flutter/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/flutter/subscriptions", + "organizations_url": "https://api.github.com/users/flutter/orgs", + "repos_url": "https://api.github.com/users/flutter/repos", + "events_url": "https://api.github.com/users/flutter/events{/privacy}", + "received_events_url": "https://api.github.com/users/flutter/received_events", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/engine", + "description": "The Flutter engine", + "fork": false, + "url": "https://api.github.com/repos/flutter/engine", + "forks_url": "https://api.github.com/repos/flutter/engine/forks", + "keys_url": "https://api.github.com/repos/flutter/engine/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/flutter/engine/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/flutter/engine/teams", + "hooks_url": "https://api.github.com/repos/flutter/engine/hooks", + "issue_events_url": "https://api.github.com/repos/flutter/engine/issues/events{/number}", + "events_url": "https://api.github.com/repos/flutter/engine/events", + "assignees_url": "https://api.github.com/repos/flutter/engine/assignees{/user}", + "branches_url": "https://api.github.com/repos/flutter/engine/branches{/branch}", + "tags_url": "https://api.github.com/repos/flutter/engine/tags", + "blobs_url": "https://api.github.com/repos/flutter/engine/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/flutter/engine/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/flutter/engine/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/flutter/engine/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/flutter/engine/statuses/{sha}", + "languages_url": "https://api.github.com/repos/flutter/engine/languages", + "stargazers_url": "https://api.github.com/repos/flutter/engine/stargazers", + "contributors_url": "https://api.github.com/repos/flutter/engine/contributors", + "subscribers_url": "https://api.github.com/repos/flutter/engine/subscribers", + "subscription_url": "https://api.github.com/repos/flutter/engine/subscription", + "commits_url": "https://api.github.com/repos/flutter/engine/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/flutter/engine/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/flutter/engine/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/flutter/engine/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/flutter/engine/contents/{+path}", + "compare_url": "https://api.github.com/repos/flutter/engine/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/flutter/engine/merges", + "archive_url": "https://api.github.com/repos/flutter/engine/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/flutter/engine/downloads", + "issues_url": "https://api.github.com/repos/flutter/engine/issues{/number}", + "pulls_url": "https://api.github.com/repos/flutter/engine/pulls{/number}", + "milestones_url": "https://api.github.com/repos/flutter/engine/milestones{/number}", + "notifications_url": "https://api.github.com/repos/flutter/engine/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/flutter/engine/labels{/name}", + "releases_url": "https://api.github.com/repos/flutter/engine/releases{/id}", + "deployments_url": "https://api.github.com/repos/flutter/engine/deployments", + "created_at": "2015-07-16T17:39:56Z", + "updated_at": "2023-11-08T07:16:25Z", + "pushed_at": "2023-11-08T21:00:38Z", + "git_url": "git://github.com/flutter/engine.git", + "ssh_url": "git@github.com:flutter/engine.git", + "clone_url": "https://github.com/flutter/engine.git", + "svn_url": "https://github.com/flutter/engine", + "homepage": "https://flutter.dev", + "size": 704170, + "stargazers_count": 6848, + "watchers_count": 6848, + "language": "C++", + "has_issues": false, + "has_projects": false, + "has_downloads": true, + "has_wiki": false, + "has_pages": false, + "has_discussions": false, + "forks_count": 5600, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 99, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + "c-plus-plus" + ], + "visibility": "public", + "forks": 5600, + "open_issues": 99, + "watchers": 6848, + "default_branch": "main", + "custom_properties": { + + } + }, + "organization": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "url": "https://api.github.com/orgs/flutter", + "repos_url": "https://api.github.com/orgs/flutter/repos", + "events_url": "https://api.github.com/orgs/flutter/events", + "hooks_url": "https://api.github.com/orgs/flutter/hooks", + "issues_url": "https://api.github.com/orgs/flutter/issues", + "members_url": "https://api.github.com/orgs/flutter/members{/member}", + "public_members_url": "https://api.github.com/orgs/flutter/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "description": "Flutter is Google's UI toolkit for building beautiful, natively compiled applications for mobile, web, desktop, and embedded devices from a single codebase." + }, + "enterprise": { + "id": 1732, + "slug": "alphabet", + "name": "Alphabet", + "node_id": "MDEwOkVudGVycHJpc2UxNzMy", + "avatar_url": "https://avatars.githubusercontent.com/b/1732?v=4", + "description": "", + "website_url": "https://abc.xyz/", + "html_url": "https://github.com/enterprises/alphabet", + "created_at": "2019-12-19T00:30:52Z", + "updated_at": "2023-01-20T00:41:48Z" + }, + "sender": { + "login": "gmackall", + "id": 34871572, + "node_id": "MDQ6VXNlcjM0ODcxNTcy", + "avatar_url": "https://avatars.githubusercontent.com/u/34871572?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/gmackall", + "html_url": "https://github.com/gmackall", + "followers_url": "https://api.github.com/users/gmackall/followers", + "following_url": "https://api.github.com/users/gmackall/following{/other_user}", + "gists_url": "https://api.github.com/users/gmackall/gists{/gist_id}", + "starred_url": "https://api.github.com/users/gmackall/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/gmackall/subscriptions", + "organizations_url": "https://api.github.com/users/gmackall/orgs", + "repos_url": "https://api.github.com/users/gmackall/repos", + "events_url": "https://api.github.com/users/gmackall/events{/privacy}", + "received_events_url": "https://api.github.com/users/gmackall/received_events", + "type": "User", + "site_admin": false + }, + "installation": { + "id": 10381585, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTAzODE1ODU=" + } +} +'''; diff --git a/test/unit/checks_test.dart b/test/unit/checks_test.dart new file mode 100644 index 00000000..2eb7e6c4 --- /dev/null +++ b/test/unit/checks_test.dart @@ -0,0 +1,234 @@ +import 'dart:convert'; + +import 'package:github/src/common/model/checks.dart'; +import 'package:test/test.dart'; + +/// The checkRun Json is the official Github values +/// +/// Github api url: https://docs.github.com/en/rest/reference/checks#get-a-check-run +const checkRunJson = '''{ + "id": 4, + "head_sha": "ce587453ced02b1526dfb4cb910479d431683101", + "node_id": "MDg6Q2hlY2tSdW40", + "external_id": "", + "url": "https://api.github.com/repos/github/hello-world/check-runs/4", + "html_url": "https://github.com/github/hello-world/runs/4", + "details_url": "https://example.com", + "status": "completed", + "conclusion": "neutral", + "started_at": "2018-05-04T01:14:52Z", + "completed_at": "2018-05-04T01:14:52Z", + "output": { + "title": "Mighty Readme report", + "summary": "There are 0 failures, 2 warnings, and 1 notice.", + "text": "You may have some misspelled words on lines 2 and 4. You also may want to add a section in your README about how to install your app.", + "annotations_count": 2, + "annotations_url": "https://api.github.com/repos/github/hello-world/check-runs/4/annotations" + }, + "name": "mighty_readme", + "check_suite": { + "id": 5 + }, + "app": { + "id": 1, + "slug": "octoapp", + "node_id": "MDExOkludGVncmF0aW9uMQ==", + "owner": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": true + }, + "name": "Octocat App", + "description": "", + "external_url": "https://example.com", + "html_url": "https://github.com/apps/octoapp", + "created_at": "2017-07-08T16:18:44-04:00", + "updated_at": "2017-07-08T16:18:44-04:00", + "permissions": { + "metadata": "read", + "contents": "read", + "issues": "write", + "single_file": "write" + }, + "events": [ + "push", + "pull_request" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/github/hello-world/pulls/1", + "id": 1934, + "number": 3956, + "head": { + "ref": "say-hello", + "sha": "3dca65fa3e8d4b3da3f3d056c59aee1c50f41390", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + }, + "base": { + "ref": "master", + "sha": "e7fdf7640066d71ad16a86fbcbb9c6a10a18af4f", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + } + } + ] +}'''; + +const String expectedToString = + '{"name":"mighty_readme","id":4,"external_id":"","status":"completed","head_sha":"","check_suite":{"id":5},"details_url":"https://example.com","started_at":"2018-05-04T01:14:52.000Z","conclusion":"neutral"}'; + +const String newCheckRun = + '{"name":"New CheckRun","id":12345,"external_id":"","status":"queued","head_sha":"","check_suite":{"id":123456},"details_url":"https://example.com","started_at":"2024-12-05T01:05:24.000Z","conclusion":"null"}'; + +void main() { + group('Check run', () { + test('CheckRun fromJson', () { + final checkRun = CheckRun.fromJson(jsonDecode(checkRunJson)); + + expect(checkRun.id, 4); + expect(checkRun.name, 'mighty_readme'); + expect(checkRun.conclusion, CheckRunConclusion.neutral); + }); + + test('CheckRun from freshly created and encoded', () { + final checkRun = CheckRun.fromJson(jsonDecode(newCheckRun)); + + expect(checkRun.id, 12345); + expect(checkRun.name, 'New CheckRun'); + expect(checkRun.conclusion, CheckRunConclusion.empty); + }); + + test('CheckRun fromJson for skipped conclusion', () { + /// The checkRun Json is the official Github values + /// + /// Github api url: https://docs.github.com/en/rest/reference/checks#get-a-check-run + const checkRunJson = '''{ + "id": 10, + "head_sha": "ce587453ced02b1526dfb4cb910479d431683101", + "node_id": "MDg6Q2hlY2tSdW40", + "external_id": "", + "url": "https://api.github.com/repos/github/hello-world/check-runs/4", + "html_url": "https://github.com/github/hello-world/runs/4", + "details_url": "https://example.com", + "status": "completed", + "conclusion": "skipped", + "started_at": "2018-05-04T01:14:52Z", + "completed_at": "2018-05-04T01:14:52Z", + "output": { + "title": "Mighty Readme report", + "summary": "There are 0 failures, 2 warnings, and 1 notice.", + "text": "You may have some misspelled words on lines 2 and 4. You also may want to add a section in your README about how to install your app.", + "annotations_count": 2, + "annotations_url": "https://api.github.com/repos/github/hello-world/check-runs/4/annotations" + }, + "name": "mighty_readme", + "check_suite": { + "id": 5 + }, + "app": { + "id": 1, + "slug": "octoapp", + "node_id": "MDExOkludGVncmF0aW9uMQ==", + "owner": { + "login": "github", + "id": 1, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE=", + "url": "https://api.github.com/orgs/github", + "repos_url": "https://api.github.com/orgs/github/repos", + "events_url": "https://api.github.com/orgs/github/events", + "avatar_url": "https://github.com/images/error/octocat_happy.gif", + "gravatar_id": "", + "html_url": "https://github.com/octocat", + "followers_url": "https://api.github.com/users/octocat/followers", + "following_url": "https://api.github.com/users/octocat/following{/other_user}", + "gists_url": "https://api.github.com/users/octocat/gists{/gist_id}", + "starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/octocat/subscriptions", + "organizations_url": "https://api.github.com/users/octocat/orgs", + "received_events_url": "https://api.github.com/users/octocat/received_events", + "type": "User", + "site_admin": true + }, + "name": "Octocat App", + "description": "", + "external_url": "https://example.com", + "html_url": "https://github.com/apps/octoapp", + "created_at": "2017-07-08T16:18:44-04:00", + "updated_at": "2017-07-08T16:18:44-04:00", + "permissions": { + "metadata": "read", + "contents": "read", + "issues": "write", + "single_file": "write" + }, + "events": [ + "push", + "pull_request" + ] + }, + "pull_requests": [ + { + "url": "https://api.github.com/repos/github/hello-world/pulls/1", + "id": 1934, + "number": 3956, + "head": { + "ref": "say-hello", + "sha": "3dca65fa3e8d4b3da3f3d056c59aee1c50f41390", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + }, + "base": { + "ref": "master", + "sha": "e7fdf7640066d71ad16a86fbcbb9c6a10a18af4f", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + } + } + ] + }'''; + final checkRun = CheckRun.fromJson(jsonDecode(checkRunJson)); + + expect(checkRun.id, 10); + expect(checkRun.name, 'mighty_readme'); + expect(checkRun.conclusion, CheckRunConclusion.skipped); + }); + + test('CheckRun toString', () { + // indirectly tests the toJson method as well. + final checkRun = CheckRun.fromJson(jsonDecode(checkRunJson)); + expect(checkRun, isNotNull); + final checkRunString = checkRun.toString(); + expect(checkRunString, isNotNull); + expect(checkRunString == expectedToString, isTrue); + }); + }); +} diff --git a/test/unit/checksuite_test.dart b/test/unit/checksuite_test.dart new file mode 100644 index 00000000..d9571bc9 --- /dev/null +++ b/test/unit/checksuite_test.dart @@ -0,0 +1,121 @@ +import 'dart:convert'; + +import 'package:github/src/common/model/checks.dart'; +import 'package:test/test.dart'; + +/// The checkSuite Json is composed from multiple GitHub examples +/// +/// See https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28 +/// See https://docs.github.com/en/rest/checks/suites?apiVersion=2022-11-28 +const checkSuiteJson = '''{ + "id": 5, + "head_branch": "main", + "head_sha": "d6fde92930d4715a2b49857d24b940956b26d2d3", + "conclusion": "neutral", + "pull_requests": [ + { + "url": "https://api.github.com/repos/github/hello-world/pulls/1", + "id": 1934, + "number": 3956, + "head": { + "ref": "say-hello", + "sha": "3dca65fa3e8d4b3da3f3d056c59aee1c50f41390", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + }, + "base": { + "ref": "master", + "sha": "e7fdf7640066d71ad16a86fbcbb9c6a10a18af4f", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + } + } + ] +}'''; + +const String expectedToString = + '{"name":"mighty_readme","id":4,"external_id":"","status":"completed","head_sha":"","check_suite":{"id":5},"details_url":"https://example.com","started_at":"2018-05-04T01:14:52.000Z","conclusion":"neutral"}'; + +void main() { + group('Check suite', () { + test('CheckSuite fromJson', () { + final checkSuite = CheckSuite.fromJson(jsonDecode(checkSuiteJson)); + + expect(checkSuite.id, 5); + expect(checkSuite.headBranch, 'main'); + expect(checkSuite.headSha, 'd6fde92930d4715a2b49857d24b940956b26d2d3'); + expect(checkSuite.conclusion, CheckRunConclusion.neutral); + expect(checkSuite.pullRequests.isNotEmpty, true); + }); + + test('CheckSuite fromJson for skipped conclusion', () { + /// The checkSuite Json is composed from multiple GitHub examples + /// + /// See https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28 + /// See https://docs.github.com/en/rest/checks/suites?apiVersion=2022-11-28 + const checkSuiteJson = '''{ + "id": 10, + "head_branch": "master", + "head_sha": "ce587453ced02b1526dfb4cb910479d431683101", + "conclusion": "skipped", + "pull_requests": [ + { + "url": "https://api.github.com/repos/github/hello-world/pulls/1", + "id": 1934, + "number": 3956, + "head": { + "ref": "say-hello", + "sha": "3dca65fa3e8d4b3da3f3d056c59aee1c50f41390", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + }, + "base": { + "ref": "master", + "sha": "e7fdf7640066d71ad16a86fbcbb9c6a10a18af4f", + "repo": { + "id": 526, + "url": "https://api.github.com/repos/github/hello-world", + "name": "hello-world" + } + } + } + ] + }'''; + final checkSuite = CheckSuite.fromJson(jsonDecode(checkSuiteJson)); + + expect(checkSuite.id, 10); + expect(checkSuite.headBranch, 'master'); + expect(checkSuite.headSha, 'ce587453ced02b1526dfb4cb910479d431683101'); + expect(checkSuite.conclusion, CheckRunConclusion.skipped); + expect(checkSuite.pullRequests.isNotEmpty, true); + }); + + test('CheckSuite fromJson for forked repository', () { + /// The checkSuite Json is composed from multiple GitHub examples + /// + /// See https://docs.github.com/en/rest/checks/runs?apiVersion=2022-11-28 + /// See https://docs.github.com/en/rest/checks/suites?apiVersion=2022-11-28 + const checkSuiteJson = '''{ + "id": 10, + "head_branch": null, + "head_sha": "ce587453ced02b1526dfb4cb910479d431683101", + "conclusion": "skipped", + "pull_requests": [] + }'''; + final checkSuite = CheckSuite.fromJson(jsonDecode(checkSuiteJson)); + + expect(checkSuite.id, 10); + expect(checkSuite.headBranch, null); + expect(checkSuite.pullRequests.isEmpty, true); + }); + }); +} diff --git a/test/unit/common/model/misc_test.dart b/test/unit/common/model/misc_test.dart new file mode 100644 index 00000000..a7cbf3c2 --- /dev/null +++ b/test/unit/common/model/misc_test.dart @@ -0,0 +1,58 @@ +import 'dart:convert'; + +import 'package:github/src/common/model/misc.dart'; +import 'package:test/test.dart'; + +void main() { + group('RateLimit', () { + test('fromRateLimitResponse', () { + const rateLimitJson = ''' +{ + "resources": { + "core": { + "limit": 5000, + "remaining": 4999, + "reset": 1372700873, + "used": 1 + }, + "search": { + "limit": 30, + "remaining": 18, + "reset": 1372697452, + "used": 12 + }, + "graphql": { + "limit": 5000, + "remaining": 4993, + "reset": 1372700389, + "used": 7 + }, + "integration_manifest": { + "limit": 5000, + "remaining": 4999, + "reset": 1551806725, + "used": 1 + }, + "code_scanning_upload": { + "limit": 500, + "remaining": 499, + "reset": 1551806725, + "used": 1 + } + }, + "rate": { + "limit": 5000, + "remaining": 4999, + "reset": 1372700873, + "used": 1 + } +}'''; + final rateLimit = + RateLimit.fromRateLimitResponse(jsonDecode(rateLimitJson)); + + expect(rateLimit.limit, 5000); + expect(rateLimit.remaining, 4999); + expect(rateLimit.resets, DateTime.fromMillisecondsSinceEpoch(1372700873)); + }); + }); +} diff --git a/test/unit/common/model/pulls_test.dart b/test/unit/common/model/pulls_test.dart new file mode 100644 index 00000000..f4d5fe65 --- /dev/null +++ b/test/unit/common/model/pulls_test.dart @@ -0,0 +1,246 @@ +import 'dart:convert'; + +import 'package:github/src/common/model/pulls.dart'; +import 'package:test/test.dart'; + +const String samplePullRequest = ''' + { + "url": "https://api.github.com/repos/flutter/cocoon/pulls/2703", + "id": 1344460863, + "node_id": "PR_kwDOA8VHis5QItg_", + "html_url": "https://github.com/flutter/cocoon/pull/2703", + "diff_url": "https://github.com/flutter/cocoon/pull/2703.diff", + "patch_url": "https://github.com/flutter/cocoon/pull/2703.patch", + "issue_url": "https://api.github.com/repos/flutter/cocoon/issues/2703", + "number": 2703, + "state": "open", + "locked": false, + "title": "Bump url_launcher from 6.1.10 to 6.1.11 in /dashboard", + "user": { + "login": "dependabot[bot]", + "id": 49699333, + "node_id": "MDM6Qm90NDk2OTkzMzM=", + "avatar_url": "https://avatars.githubusercontent.com/in/29110?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/dependabot%5Bbot%5D", + "html_url": "https://github.com/apps/dependabot", + "type": "Bot", + "site_admin": false + }, + "body": "Bumps [url_launcher](https://github.com/flutter/packages/tree/main/packages/url_launcher) from 6.1.10 to 6.1.11.", + "created_at": "2023-05-09T22:23:34Z", + "updated_at": "2023-05-09T22:23:35Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": "252a1a4370e30631b090eeeda182879985cc8f08", + "assignee": null, + "assignees": [ + + ], + "requested_reviewers": [ + + ], + "requested_teams": [ + + ], + "labels": [ + { + "id": 3960015931, + "node_id": "LA_kwDOA8VHis7sCQw7", + "url": "https://api.github.com/repos/flutter/cocoon/labels/autosubmit", + "name": "autosubmit", + "color": "0E8A16", + "default": false, + "description": "Merge PR when tree becomes green via auto submit App" + } + ], + "milestone": null, + "draft": false, + "commits_url": "https://api.github.com/repos/flutter/cocoon/pulls/2703/commits", + "review_comments_url": "https://api.github.com/repos/flutter/cocoon/pulls/2703/comments", + "review_comment_url": "https://api.github.com/repos/flutter/cocoon/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/flutter/cocoon/issues/2703/comments", + "statuses_url": "https://api.github.com/repos/flutter/cocoon/statuses/57ec5a040c8a631e39b3f3dee82a77fdf79b6e19", + "head": { + "label": "flutter:dependabot/pub/dashboard/url_launcher-6.1.11", + "ref": "dependabot/pub/dashboard/url_launcher-6.1.11", + "sha": "57ec5a040c8a631e39b3f3dee82a77fdf79b6e19", + "user": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 63260554, + "node_id": "MDEwOlJlcG9zaXRvcnk2MzI2MDU1NA==", + "name": "cocoon", + "full_name": "flutter/cocoon", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/cocoon", + "description": "Flutter's build coordinator and aggregator", + "fork": false, + "url": "https://api.github.com/repos/flutter/cocoon", + "forks_url": "https://api.github.com/repos/flutter/cocoon/forks", + "created_at": "2016-07-13T16:04:04Z", + "updated_at": "2023-04-12T16:34:46Z", + "pushed_at": "2023-05-09T22:23:35Z", + "git_url": "git://github.com/flutter/cocoon.git", + "ssh_url": "git@github.com:flutter/cocoon.git", + "clone_url": "https://github.com/flutter/cocoon.git", + "svn_url": "https://github.com/flutter/cocoon", + "homepage": null, + "size": 13247, + "stargazers_count": 171, + "watchers_count": 171, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 91, + "open_issues": 2, + "watchers": 171, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": true, + "squash_merge_commit_message": "PR_BODY", + "squash_merge_commit_title": "PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "base": { + "label": "flutter:main", + "ref": "main", + "sha": "152dd99368b8417b2ede8ed49d5923e594a3b0f2", + "user": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 63260554, + "node_id": "MDEwOlJlcG9zaXRvcnk2MzI2MDU1NA==", + "name": "cocoon", + "full_name": "flutter/cocoon", + "private": false, + "owner": { + "login": "flutter", + "id": 14101776, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjE0MTAxNzc2", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/flutter", + "html_url": "https://github.com/flutter", + "type": "Organization", + "site_admin": false + }, + "html_url": "https://github.com/flutter/cocoon", + "description": "Flutter's build coordinator and aggregator", + "fork": false, + "url": "https://api.github.com/repos/flutter/cocoon", + "forks_url": "https://api.github.com/repos/flutter/cocoon/forks", + "created_at": "2016-07-13T16:04:04Z", + "updated_at": "2023-04-12T16:34:46Z", + "pushed_at": "2023-05-09T22:23:35Z", + "git_url": "git://github.com/flutter/cocoon.git", + "ssh_url": "git@github.com:flutter/cocoon.git", + "clone_url": "https://github.com/flutter/cocoon.git", + "svn_url": "https://github.com/flutter/cocoon", + "homepage": null, + "size": 13247, + "license": { + "key": "bsd-3-clause", + "name": "BSD 3-Clause New or Revised License", + "spdx_id": "BSD-3-Clause", + "url": "https://api.github.com/licenses/bsd-3-clause", + "node_id": "MDc6TGljZW5zZTU=" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [ + + ], + "visibility": "public", + "forks": 91, + "open_issues": 2, + "watchers": 171, + "default_branch": "main", + "allow_squash_merge": true, + "allow_merge_commit": false, + "allow_rebase_merge": false, + "allow_auto_merge": false, + "delete_branch_on_merge": false, + "allow_update_branch": false, + "use_squash_pr_title_as_default": true, + "squash_merge_commit_message": "PR_BODY", + "squash_merge_commit_title": "PR_TITLE", + "merge_commit_message": "PR_TITLE", + "merge_commit_title": "MERGE_MESSAGE" + } + }, + "author_association": "CONTRIBUTOR", + "auto_merge": null, + "active_lock_reason": null, + "merged": false, + "mergeable": true, + "rebaseable": true, + "mergeable_state": "unstable", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "maintainer_can_modify": false, + "commits": 1, + "additions": 119, + "deletions": 202, + "changed_files": 2 + } +'''; + +void main() { + group('Pull Request fromJson', () { + test('Node ID is collected', () { + final pullRequest = PullRequest.fromJson(jsonDecode(samplePullRequest)); + expect(pullRequest, isNotNull); + expect(pullRequest.nodeId, "PR_kwDOA8VHis5QItg_"); + }); + }); +} diff --git a/test/unit/common/model/repos_releases_test.dart b/test/unit/common/model/repos_releases_test.dart new file mode 100644 index 00000000..bcea4137 --- /dev/null +++ b/test/unit/common/model/repos_releases_test.dart @@ -0,0 +1,54 @@ +import 'package:github/src/common/model/repos_releases.dart'; +import 'package:test/test.dart'; + +import '../../../assets/responses/create_release.dart'; +import '../../../assets/responses/release.dart'; +import '../../../assets/responses/release_asset.dart'; + +void main() { + group('Release', () { + test('fromJson', () { + expect(() => Release.fromJson(releasePayload), returnsNormally); + }); + + test('toJson', () { + final release = Release.fromJson(releasePayload); + expect(release.toJson, returnsNormally); + }); + }); + + group('ReleaseAsset', () { + test('fromJson', () { + expect(() => ReleaseAsset.fromJson(releaseAssetPayload), returnsNormally); + }); + + test('toJson', () { + final releaseAsset = ReleaseAsset.fromJson(releaseAssetPayload); + expect(releaseAsset.toJson, returnsNormally); + }); + }); + + group('CreateRelease', () { + test('fromJson', () { + expect( + () => CreateRelease.fromJson(createReleasePayload), returnsNormally); + }); + + test('toJson', () { + final createRelease = CreateRelease.fromJson(createReleasePayload); + expect(createRelease.toJson, returnsNormally); + }); + + test('toJson reserializes back to the same type of object', () { + final createRelease = CreateRelease.from( + tagName: 'v1.0.0', + name: 'Initial Release', + targetCommitish: 'master', + isDraft: false, + isPrerelease: true); + final json = createRelease.toJson(); + + expect(CreateRelease.fromJson(json), createRelease); + }); + }); +} diff --git a/test/unit/issues_test.dart b/test/unit/issues_test.dart new file mode 100644 index 00000000..512d83a3 --- /dev/null +++ b/test/unit/issues_test.dart @@ -0,0 +1,63 @@ +import 'dart:convert'; +import 'package:github/src/common/model/issues.dart'; + +import 'package:test/test.dart'; + +const String testIssueCommentJson = ''' + { + "url": "https://api.github.com/repos/flutter/cocoon/issues/comments/1352355796", + "html_url": "https://github.com/flutter/cocoon/pull/2356#issuecomment-1352355796", + "issue_url": "https://api.github.com/repos/flutter/cocoon/issues/2356", + "id": 1352355796, + "node_id": "IC_kwDOA8VHis5Qm0_U", + "user": { + "login": "CaseyHillers", + "id": 2148558, + "node_id": "MDQ6VXNlcjIxNDg1NTg=", + "avatar_url": "https://avatars.githubusercontent.com/u/2148558?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/CaseyHillers", + "html_url": "https://github.com/CaseyHillers", + "followers_url": "https://api.github.com/users/CaseyHillers/followers", + "following_url": "https://api.github.com/users/CaseyHillers/following{/other_user}", + "gists_url": "https://api.github.com/users/CaseyHillers/gists{/gist_id}", + "starred_url": "https://api.github.com/users/CaseyHillers/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/CaseyHillers/subscriptions", + "organizations_url": "https://api.github.com/users/CaseyHillers/orgs", + "repos_url": "https://api.github.com/users/CaseyHillers/repos", + "events_url": "https://api.github.com/users/CaseyHillers/events{/privacy}", + "received_events_url": "https://api.github.com/users/CaseyHillers/received_events", + "type": "User", + "site_admin": false + }, + "created_at": "2022-12-14T23:26:32Z", + "updated_at": "2022-12-14T23:26:32Z", + "author_association": "MEMBER", + "body": "FYI you need to run https://github.com/flutter/cocoon/blob/main/format.sh for formatting Cocoon code", + "reactions": { + "url": "https://api.github.com/repos/flutter/cocoon/issues/comments/1352355796/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "performed_via_github_app": null + } +'''; + +void main() { + group('Issue Comments', () { + test('IssueComment from Json', () { + final issueComment = + IssueComment.fromJson(jsonDecode(testIssueCommentJson)); + expect(1352355796, issueComment.id); + expect('MEMBER', issueComment.authorAssociation); + expect('CaseyHillers', issueComment.user!.login); + }); + }); +} diff --git a/test/unit/orgs_service_test.dart b/test/unit/orgs_service_test.dart new file mode 100644 index 00000000..9ba4ff3f --- /dev/null +++ b/test/unit/orgs_service_test.dart @@ -0,0 +1,174 @@ +import 'dart:io'; + +import 'package:github/github.dart'; +import 'package:http/http.dart'; +import 'package:http/testing.dart'; +import 'package:test/test.dart'; + +void main() { + const teamResponse = ''' + { + "name": "flutter-hackers", + "id": 1753404, + "slug": "flutter-hackers", + "permission": "pull", + "members_count": 255, + "repos_count": 34, + "organization": { + "login": "flutter", + "id": 14101776, + "url": "https://api.github.com/orgs/flutter", + "avatar_url": "https://avatars.githubusercontent.com/u/14101776?v=4", + "name": "Flutter", + "company": null, + "blog": "https://flutter.dev", + "location": null, + "email": null, + "public_repos": 30, + "public_gists": 0, + "followers": 6642, + "following": 0, + "html_url": "https://github.com/flutter", + "created_at": "2015-09-03T00:37:37Z", + "updated_at": "2022-03-17T17:35:40Z" + } + } +'''; + + const teamNotFoundResponse = ''' + { + "message": "Not Found", + "documentation_url": "https://docs.github.com/rest/reference/teams#list-teams" + } + '''; + + const activeMemberResponse = ''' + { + "state": "active", + "role": "member", + "url": "https://api.github.com/organizations/14101776/team/1753404/memberships/ricardoamador" + } + '''; + + const pendingMemberResponse = ''' + { + "state": "pending", + "role": "member", + "url": "https://api.github.com/organizations/14101776/team/1753404/memberships/ricardoamador" + } + '''; + + group(GitHub, () { + test('getTeamByName is successful', () async { + Request? request; + + final client = MockClient((r) async { + request = r; + return Response(teamResponse, HttpStatus.ok); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + final team = await organizationsService.getTeamByName( + 'flutter', 'flutter-hackers'); + expect(team.name, 'flutter-hackers'); + expect(team.id, 1753404); + expect(team.organization!.login, 'flutter'); + expect(request, isNotNull); + }); + + test('getTeamByName not found', () async { + Request? request; + + final headers = {}; + headers['content-type'] = 'application/json'; + + final client = MockClient((r) async { + request = r; + return Response(teamNotFoundResponse, HttpStatus.notFound, + headers: headers); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + expect( + () async => organizationsService.getTeamByName( + 'flutter', 'flutter-programmers'), + throwsException); + expect(request, isNull); + }); + + test('getTeamMembership using string name, active', () async { + Request? request; + + final client = MockClient((r) async { + request = r; + return Response(activeMemberResponse, HttpStatus.ok); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + final teamMembershipState = + await organizationsService.getTeamMembershipByName( + 'flutter', + 'flutter-hackers', + 'ricardoamador', + ); + expect(teamMembershipState.isActive, isTrue); + expect(request, isNotNull); + }); + + test('getTeamMembership using string name, pending', () async { + Request? request; + + final client = MockClient((r) async { + request = r; + return Response(pendingMemberResponse, HttpStatus.ok); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + final teamMembershipState = + await organizationsService.getTeamMembershipByName( + 'flutter', + 'flutter-hackers', + 'ricardoamador', + ); + expect(teamMembershipState.isActive, isFalse); + expect(teamMembershipState.isPending, isTrue); + expect(request, isNotNull); + }); + + test('getTeamMembership using string name, null', () async { + Request? request; + + final headers = {}; + headers['content-type'] = 'application/json'; + + final client = MockClient((r) async { + request = r; + return Response( + teamNotFoundResponse, + HttpStatus.notFound, + headers: headers, + ); + }); + + final github = GitHub(client: client); + final organizationsService = OrganizationsService(github); + + expect( + () async => organizationsService.getTeamMembershipByName( + 'flutter', + 'flutter-hackers', + 'garfield', + ), + throwsException); + expect(request, isNull); + }); + }); +} diff --git a/test/util_test.dart b/test/util_test.dart index 0671262d..c8fdfad2 100644 --- a/test/util_test.dart +++ b/test/util_test.dart @@ -1,18 +1,15 @@ -library github.test.util_test; - -import "helper.dart"; - -import "package:github/src/common.dart"; -import "package:test/test.dart"; +import 'package:github/src/common.dart'; +import 'package:test/test.dart'; +import 'helper/expect.dart'; void main() { - group("slugFromAPIUrl()", () { - test("https://api.github.com/repos/DirectMyFile/irc.dart slug is correct", + group('slugFromAPIUrl()', () { + test('https://api.github.com/repos/SpinlockLabs/irc.dart slug is correct', () { expectSlug( - slugFromAPIUrl("https://api.github.com/repos/DirectMyFile/irc.dart"), - "DirectMyFile", - "irc.dart"); + slugFromAPIUrl('https://api.github.com/repos/SpinlockLabs/irc.dart'), + 'SpinlockLabs', + 'irc.dart'); }); }); } diff --git a/tool/build.dart b/tool/build.dart deleted file mode 100755 index e389c0b5..00000000 --- a/tool/build.dart +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env dart -import "dart:async"; -import "dart:io"; - -Directory packagesDirectory = new Directory("packages/"); - -void main(List args) { - var future = new Future.value(null); - - if (!packagesDirectory.existsSync()) { - future = execute("pub get"); - } - - future.then((_) { - var argz = (args.length > 0 ? " " : "") + args.join(" "); - return execute("dart --checked tool/hop_runner.dart --color${argz}"); - }); -} - -dynamic execute(String cmdline) { - var split = cmdline.split(" "); - var command = split[0]; - split.remove(command); - var args = split; - return Process.start(command, args).then((Process process) { - stdout.addStream(process.stdout); - stderr.addStream(process.stderr); - return process.exitCode; - }).then((int exitCode) { - if (exitCode != 0) { - exit(exitCode); - } - }); -} diff --git a/tool/build.yaml b/tool/build.yaml deleted file mode 100644 index 9081b2a6..00000000 --- a/tool/build.yaml +++ /dev/null @@ -1,17 +0,0 @@ -analyzer.files: - - lib/common.dart - - lib/server.dart - - lib/browser.dart - - example/repos.dart - - example/organization.dart - - example/users.dart - - example/user_info.dart - - example/languages.dart - - example/oauth2.dart - - example/releases.dart - - example/common.dart -check.tasks: - - analyze - - test -docs.output: out/docs -test.file: test/all_tests.dart diff --git a/tool/ci/retry.sh b/tool/ci/retry.sh deleted file mode 100755 index b784c8d8..00000000 --- a/tool/ci/retry.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -n=0 -LAST_EXIT=0 -until [ $n -ge 5 ] -do - echo "$ ${@}" - ${@} - LAST_EXIT=${?} - [ ${LAST_EXIT} == 0 ] && break - n=$[$n+1] - sleep 2 -done - -exit ${LAST_EXIT} \ No newline at end of file diff --git a/tool/config.dart b/tool/config.dart deleted file mode 100644 index e5e3e90f..00000000 --- a/tool/config.dart +++ /dev/null @@ -1,52 +0,0 @@ -part of hop_runner; - -Map config; - -Directory get tool_dir => new File.fromUri(Platform.script).parent.absolute; -Directory get root_dir => tool_dir.parent; - -Map load_config() { - var it = loadYaml(new File("${tool_dir.path}/build.yaml").readAsStringSync()); - if (it.containsKey("variables")) { - variables.addAll(it["variables"]); - } - return it; -} - -Map variables = { - "tool_dir": tool_dir.path, - "root_dir": root_dir.path -}; - -String parse_config_value(String input) { - var out = input; - for (var variable in variables.keys) { - out = out.replaceAll("{${variable}}", variables[variable]); - } - return out; -} - -dynamic getvar(String path, [dynamic defaultValue = false]) { - var current = config; - - if (current.containsKey(path)) { - return current[path]; - } - - var parts = path.split(r"\."); - for (var part in parts) { - if (current == null) { - return null; - } - current = current[part]; - } - if (current is String) { - current = parse_config_value(current); - } - return current; -} - -void init() { - Directory.current = root_dir; - config = load_config(); -} diff --git a/tool/language_color_generator.dart b/tool/language_color_generator.dart new file mode 100644 index 00000000..2bc948f1 --- /dev/null +++ b/tool/language_color_generator.dart @@ -0,0 +1,41 @@ +import 'dart:io'; + +import 'package:http/http.dart' as http; +import 'package:yaml/yaml.dart'; + +const _indent = ' '; +const _path = './lib/src/const/language_color.dart'; +const _url = 'https://raw.githubusercontent.com/' + 'github/linguist/master/lib/linguist/languages.yml'; + +Future main() async { + final response = await http.Client().get(Uri.parse(_url)); + + final yaml = loadYaml(response.body) as YamlMap; + + final stringBuffer = StringBuffer() + ..writeln('// GENERATED CODE - DO NOT MODIFY BY HAND') + ..writeln('// VERSION OF ${DateTime.now().toIso8601String()}') + ..writeln() + ..writeln('const languageColors = {'); + + final map = yaml.value as YamlMap; + + final languages = map.keys.cast().toList(growable: false)..sort(); + + for (var language in languages) { + final color = map[language]['color']?.toString().toUpperCase() ?? '#EDEDED'; + + language = language.replaceAll("'", "\\'"); + + stringBuffer.writeln("$_indent'$language': '$color',"); + } + + stringBuffer.writeln('};'); + + File(_path) + ..createSync() + ..writeAsStringSync(stringBuffer.toString()); + + print('File created with success'); +} diff --git a/tool/process_github_schema.dart b/tool/process_github_schema.dart new file mode 100644 index 00000000..14994289 --- /dev/null +++ b/tool/process_github_schema.dart @@ -0,0 +1,621 @@ +import 'dart:convert'; +import 'dart:io'; + +const int width = 72; + +List wordWrap(String body) { + var result = []; + var start = 0; + for (var index = 0; index < body.length; index += 1) { + if ((index == body.length - 1) || + (body[index] == '\n') || + ((body[index] == ' ') && (index - start > width))) { + result.add(body.substring(start, index + 1).trimRight()); + start = index + 1; + } + } + assert(start == body.length); + return result; +} + +typedef GenTypeVisitor = void Function(GenType type); + +abstract class GenType implements Comparable { + GenType(); + + String get name; + String get comment => ''; + + String get signature; + + void cleanup() {} + + String generateDeclaration(); + + void visit(GenTypeVisitor visitor) { + visitor(this); + } + + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(signature == other.signature, + 'cannot merge types with different signatures'); + throw StateError( + 'not sure how to merge $runtimeType with ${other.runtimeType}'); + } + + @override + int compareTo(GenType other) { + return signature.compareTo(other.signature); + } + + @override + String toString() => '$runtimeType($name)'; +} + +class GenPrimitive extends GenType { + GenPrimitive(this.type, this.comment); + + @override + String get name => type.toString(); + + @override + String get signature => name; + + @override + String generateDeclaration() => ''; + + @override + final String comment; + + final Type type; + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(superclass == null); + if (other is GenPrimitive) { + assert(type == other.type); + if (comment != other.comment) { + return GenPrimitive( + type, + '$comment\n\n${other.comment}', + ); + } + return this; + } + return super.mergeWith(other, superclass); + } +} + +class GenUnion extends GenType { + GenUnion(this.subtypes); + + @override + String get name => 'Object'; + + @override + String get comment { + var result = StringBuffer(); + result.writeln('One of the following:'); + for (final subtype in subtypes) { + if (subtype.comment.isNotEmpty) { + result.writeln( + ' * [${subtype.name}]: ${subtype.comment.split('\n').first}'); + } else { + result.writeln(' * [${subtype.name}]'); + } + } + return result.toString(); + } + + @override + String get signature { + var subsignatures = + subtypes.map((GenType type) => type.signature).toList() + ..sort() + ..join(','); + return 'Union<$subsignatures>'; + } + + final List subtypes; + + @override + String generateDeclaration() => ''; + + @override + void visit(GenTypeVisitor visitor) { + super.visit(visitor); + for (final subtype in subtypes) { + subtype.visit(visitor); + } + } + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(superclass == null); + if (other is GenUnion) { + assert(subtypes.length == other.subtypes.length); + var subtypesA = subtypes..sort(); + var subtypesB = other.subtypes..sort(); + var subtypesC = []; + for (var index = 0; index < subtypesA.length; index += 1) { + subtypesC.add(subtypesA[index].mergeWith(subtypesB[index], null)); + } + return GenUnion(subtypesC); + } + return super.mergeWith(other, superclass); + } +} + +class GenList extends GenType { + GenList(this.members, this.comment); + + @override + String get name => 'List<${members.name}>'; + + @override + final String comment; + + final GenType members; + + @override + String get signature { + return 'List<${members.signature}>'; + } + + @override + String generateDeclaration() => ''; + + @override + void visit(GenTypeVisitor visitor) { + super.visit(visitor); + members.visit(visitor); + } + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(superclass == null); + if (other is GenList) { + var newComment = + comment != other.comment ? '$comment\n\n${other.comment}' : comment; + var newMembers = members.mergeWith(other.members, null); + return GenList(newMembers, newComment); + } + return super.mergeWith(other, superclass); + } +} + +class GenAbstractClass extends GenType { + GenAbstractClass(this.name, this.comment, {Map? properties}) + : properties = properties ?? {}; + + @override + final String name; + + @override + final String comment; + + final List subclasses = []; + final Map properties; + + @override + String get signature { + var propertySignatures = properties.keys + .map((String propertyName) => + '$propertyName:${properties[propertyName]!.signature}') + .toList() + ..sort() + ..join(','); + return 'abstract class $name { $propertySignatures }'; + } + + @override + void cleanup() { + if (subclasses.length > 1) { + var names = subclasses.first.properties.keys.toSet(); + properties: + for (final name in names) { + var signature = subclasses.first.properties[name]!.signature; + for (final subclass in subclasses.skip(1)) { + if (!subclass.properties.containsKey(name) || + subclass.properties[name]!.signature != signature) { + continue properties; + } + } + var property = subclasses.first.properties[name]!; + for (final subclass in subclasses.skip(1)) { + property = property.mergeWith(subclass.properties[name]!, null); + } + properties[name] = property; + for (final subclass in subclasses) { + subclass.properties.remove(name); + } + } + } + } + + @override + String generateDeclaration() { + var output = StringBuffer(); + if (comment.isNotEmpty) { + for (final line in wordWrap(comment)) { + output.writeln('/// $line'); + } + } + output.writeln('@JsonSerializable()'); + output.writeln('abstract class $name {'); + output.write(' $name('); + if (properties.isNotEmpty) { + output.writeln('{'); + for (final propertyName in properties.keys.toList()..sort()) { + output.writeln(' this.$propertyName,'); + } + output.write(' }'); + } + output.writeln(');'); + output.writeln(''); + var lastLineWasBlank = true; + for (final propertyName in properties.keys.toList()..sort()) { + if (properties[propertyName]!.comment.isNotEmpty) { + if (!lastLineWasBlank) { + output.writeln(''); + lastLineWasBlank = true; + } + for (final line in wordWrap(properties[propertyName]!.comment)) { + output.writeln(' /// $line'); + } + } else { + lastLineWasBlank = false; + } + output.writeln(' ${properties[propertyName]!.name}? $propertyName;'); + if (lastLineWasBlank) { + output.writeln(''); + lastLineWasBlank = true; + } + } + output.writeln('}'); + return output.toString(); + } + + @override + void visit(GenTypeVisitor visitor) { + super.visit(visitor); + for (final subclass in subclasses) { + subclass.visit(visitor); + } + } + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert(superclass == null); + if (other is GenAbstractClass) { + assert(name == other.name); + assert(properties.length == other.properties.length); + var newComment = + comment != other.comment ? '$comment\n\n${other.comment}' : comment; + var newProperties = {}; + for (final propertyName in properties.keys) { + newProperties[propertyName] = properties[propertyName]! + .mergeWith(other.properties[propertyName]!, null); + } + var result = + GenAbstractClass(name, newComment, properties: newProperties); + var subclassesA = subclasses..sort(); + var subclassesB = other.subclasses..sort(); + for (var index = 0; index < subclassesA.length; index += 1) { + subclassesA[index].mergeWith(subclassesB[index], result); + } + assert(result.subclasses.length == subclasses.length); + assert(result.subclasses.length == other.subclasses.length); + return result; + } + return super.mergeWith(other, superclass); + } +} + +class GenClass extends GenType { + GenClass(this.name, this.comment, this.superclass, this.properties) { + if (superclass != null) { + superclass!.subclasses.add(this); + } + } + + @override + final String name; + + @override + final String comment; + + final GenAbstractClass? superclass; + final Map properties; + + @override + String get signature { + var propertySignatures = properties.keys + .map((String propertyName) => + '$propertyName:${properties[propertyName]!.signature}') + .toList() + ..sort() + ..join(','); + return 'class $name extends { ${superclass?.signature} } with { $propertySignatures }'; + } + + @override + String generateDeclaration() { + var output = StringBuffer(); + if (comment.isNotEmpty) { + for (final line in wordWrap(comment)) { + output.writeln('/// $line'); + } + } + output.writeln('@JsonSerializable()'); + output.write('class $name '); + if (superclass != null) { + output.write('extends ${superclass!.name} '); + } + output.writeln('{'); + output.writeln(' $name({'); + if (superclass != null) { + for (final propertyName in superclass!.properties.keys.toList()..sort()) { + output.writeln(' super.$propertyName,'); + } + } + for (final propertyName in properties.keys.toList()..sort()) { + output.writeln(' this.$propertyName,'); + } + output.writeln(' });'); + output.writeln(''); + var lastLineWasBlank = true; + for (final propertyName in properties.keys.toList()..sort()) { + if (properties[propertyName]!.comment.isNotEmpty) { + if (!lastLineWasBlank) { + output.writeln(''); + lastLineWasBlank = true; + } + for (final line in wordWrap(properties[propertyName]!.comment)) { + output.writeln(' /// $line'); + } + } else { + lastLineWasBlank = false; + } + output.writeln(' ${properties[propertyName]!.name}? $propertyName;'); + if (lastLineWasBlank) { + output.writeln(''); + lastLineWasBlank = true; + } + } + if (!lastLineWasBlank) { + output.writeln(''); + } + output + .writeln(' Map toJson() => _\$${name}ToJson(this);'); + output.writeln(''); + output.writeln(' factory $name.fromJson(Map input) =>'); + output.writeln(' _\$${name}FromJson(input);'); + output.writeln('}'); + return output.toString(); + } + + @override + void visit(GenTypeVisitor visitor) { + super.visit(visitor); + for (final property in properties.values) { + property.visit(visitor); + } + } + + @override + GenType mergeWith(GenType other, GenAbstractClass? superclass) { + assert((superclass == null) == (this.superclass == null)); + if (other is GenClass) { + assert((other.superclass == null) == (this.superclass == null)); + assert(name == other.name); + assert(properties.length == other.properties.length); + var newComment = + comment != other.comment ? '$comment\n\n${other.comment}' : comment; + var newProperties = {}; + for (final propertyName in properties.keys) { + newProperties[propertyName] = properties[propertyName]! + .mergeWith(other.properties[propertyName]!, null); + } + return GenClass(name, newComment, superclass, newProperties); + } + return super.mergeWith(other, superclass); + } +} + +void assure(bool condition, String Function() callback) { + if (!condition) { + print(callback()); + exit(1); + } +} + +String? camelCase(String? text, {bool uppercase = false}) { + if (text == null) { + return null; + } + var bits = text.split(RegExp('[- _]')); + var result = StringBuffer(); + for (final bit in bits) { + if (bit.isNotEmpty) { + if (result.isNotEmpty || uppercase) { + result.write(String.fromCharCode(bit.runes.first).toUpperCase()); + result.write(String.fromCharCodes(bit.runes.skip(1))); + } else { + result.write(bit); + } + } + } + return result.toString(); +} + +String buildComment(Map schema) { + var description = StringBuffer(); + if (schema['title'] != null) { + description.writeln(schema['title']); + } + if (schema['description'] != null && + schema['description'] != schema['title']) { + if (description.isNotEmpty) { + description.writeln(''); + } + description.writeln(schema['description']); + } + if (schema['format'] != null) { + if (description.isNotEmpty) { + description.writeln(''); + } + description.write('Format: '); + description.writeln(schema['format']); + } + if (schema['examples'] != null) { + assure(schema['examples'] is List, + () => 'examples should be a list, not as in $schema'); + for (final example in schema['examples'] as List) { + if (description.isNotEmpty) { + description.writeln(''); + } + description.writeln('Example: `$example`'); + } + } + return description.toString().trimRight(); +} + +GenType process(Map schema, {String? defaultName}) { + final comment = buildComment(schema); + String type; + if (schema['type'] is List) { + var types = schema['type'] as List; + if (types.length == 2) { + if (types[0] == 'null' && types[1] is String) { + type = types[1] as String; + } else if (types[1] == 'null' && types[0] is String) { + type = types[0] as String; + } else { + print('Arbitrary union types not supported: $types'); + exit(1); + } + } else { + print('Arbitrary union types not supported: $types'); + exit(1); + } + } else if (schema['type'] is String) { + type = schema['type'] as String; + } else { + var anyOf = schema['anyOf'] ?? schema['oneOf']; + if (anyOf != null) { + assure(comment.isEmpty, () => 'lost comment to anyOf/oneOf: $comment'); + assure( + anyOf is List, () => 'anyOf/oneOf key is not a JSON list'); + var subtypes = []; + for (final subtype in anyOf as List) { + assure(subtype is Map, + () => 'type in anyOf/oneOf is not a JSON object'); + subtypes.add(process(subtype as Map)); + } + if (subtypes.length == 2) { + if (subtypes[0] is GenPrimitive && + (subtypes[0] as GenPrimitive).type == Null) { + return subtypes[1]; + } + if (subtypes[1] is GenPrimitive && + (subtypes[1] as GenPrimitive).type == Null) { + return subtypes[0]; + } + } + return GenUnion(subtypes); + } + if (schema['type'] == null) { + print('missing type: $schema'); + exit(1); + } + print('unknown type ${schema['type']}'); + exit(1); + } + if (type == 'array') { + assure(schema['items'] is Map, + () => 'array items are not a JSON object'); + return GenList(process(schema['items'] as Map), comment); + } + if (type == 'object') { + var anyOf = schema['anyOf']; + if (anyOf != null) { + assure(anyOf is List, () => 'anyOf key is not a JSON list'); + var result = GenAbstractClass( + camelCase(schema['title'] as String?) ?? '##unnamed##', + comment, + ); + for (final subschema in anyOf as List) { + assure(subschema is Map, + () => 'anyOf value is not a JSON object'); + var subclass = processObject(subschema as Map, + superclass: result); + assert(result.subclasses.last == subclass); + } + return result; + } + return processObject(schema, defaultName: defaultName); + } + if (type == 'null') { + return GenPrimitive(Null, comment); + } + if (type == 'boolean') { + return GenPrimitive(bool, comment); + } + if (type == 'integer') { + return GenPrimitive(int, comment); + } + if (type == 'string') { + return GenPrimitive(String, comment); + } + print('unknown type $type'); + exit(1); +} + +GenClass processObject(Map schema, + {GenAbstractClass? superclass, String? comment, String? defaultName}) { + assert(schema['anyOf'] == null); + comment ??= buildComment(schema); + var properties = {}; + var propertiesData = schema['properties']; + assure(propertiesData is Map, + () => 'properties key is not a JSON map'); + for (final propertyName in (propertiesData as Map).keys) { + var propertyData = propertiesData[propertyName]; + assure(propertyData is Map, + () => 'property $propertyName is not a JSON object'); + properties[camelCase(propertyName)!] = process( + propertyData as Map, + defaultName: camelCase(propertyName, uppercase: true)); + } + return GenClass( + camelCase(schema['title'] as String?) ?? defaultName ?? '##unnamed##', + comment, + superclass, + properties, + ); +} + +void main(List arguments) { + if (arguments.length != 1) { + print( + 'Command must be run with one argument, the file name of the schema to process.'); + exit(1); + } + Object schema = json.decode(File(arguments.single).readAsStringSync()); + assure(schema is Map, () => 'schema is not a JSON object'); + var rootType = process(schema as Map); + rootType.visit((GenType type) { + type.cleanup(); + }); + var declarations = {}; + rootType.visit((GenType type) { + var declaration = type.generateDeclaration().trimRight(); + declarations.add(declaration); + }); + for (final declaration in declarations) { + print(declaration); + print(''); + } + print('// root type is: ${rootType.name}'); +} diff --git a/tool/publish.sh b/tool/publish.sh deleted file mode 100755 index 836d0ae3..00000000 --- a/tool/publish.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env bash -# Publishes a GitHub.dart release -./tool/build.dart publish ${@} -VERSION=`grep 'version:' pubspec.yaml | sed 's/version: //'` -echo Releasing ${VERSION} -git add . -git tag v${VERSION} -git commit -m "v${VERSION}" -git push --tags origin master diff --git a/tool/serve.sh b/tool/serve.sh deleted file mode 100755 index 1b0f658c..00000000 --- a/tool/serve.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -pub serve example/ test/ --hostname 0.0.0.0 --port 8080 diff --git a/tool/update-demos.sh b/tool/update-demos.sh deleted file mode 100755 index 27462b73..00000000 --- a/tool/update-demos.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash - -set -e - -if [ -z ${1} ] -then - echo "Usage: tool/update-demos.sh path/to/demos" - exit 1 -fi - -rm -rf build -rm -rf ${1} - -pub build example --mode=debug -cp -R build/example ${1} - -cd ${1} -git add . -git commit -m "Updating Demos" -git push pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy