├── .github └── workflows │ └── build.yaml ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── append-block-example.png ├── example-database.png ├── internal-integration-token.png ├── page-in-page-example.png └── public-integration-details.png ├── examples └── version1 │ ├── README.md │ ├── append-block-children-example.go │ ├── create-page-example.go │ ├── go.mod │ ├── go.sum │ ├── list-databases-example.go │ ├── list-users-example.go │ ├── oauth2-flow-part1-example.go │ ├── oauth2-flow-part2-example.go │ ├── query-database-example.go │ ├── retrieve-block-children-example.go │ ├── retrieve-database-example.go │ ├── retrieve-page-example.go │ ├── retrieve-user-example.go │ ├── search-example.go │ ├── testing.go │ └── update-page-properties-example.go ├── go.mod ├── go.sum └── notion └── version1 ├── auth.go ├── auth_test.go ├── blocks.go ├── blocks_test.go ├── common.go ├── databases.go ├── databases_test.go ├── errors.go ├── go.mod ├── go.sum ├── notion.go ├── notion_test.go ├── pages.go ├── pages_test.go ├── search.go ├── search_test.go ├── users.go └── users_test.go /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | build: 11 | name: Build 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Set up Go 1.x 15 | uses: actions/setup-go@v2 16 | with: 17 | go-version: ^1.13 18 | id: go 19 | 20 | - name: Check out code into the Go module directory 21 | uses: actions/checkout@v2 22 | 23 | - name: Get dependencies 24 | run: | 25 | go get -v -t -d ./notion/verson1/... 26 | if [ -f Gopkg.toml ]; then 27 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 28 | dep ensure 29 | fi 30 | 31 | - name: Test 32 | run: cd notion/version1 && go test -v -race -coverprofile=coverage.txt -covermode=atomic . -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/go,intellij+all 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=go,intellij+all 4 | 5 | ### Go ### 6 | # Binaries for programs and plugins 7 | *.exe 8 | *.exe~ 9 | *.dll 10 | *.so 11 | *.dylib 12 | 13 | # Test binary, built with `go test -c` 14 | *.test 15 | 16 | # Output of the go coverage tool, specifically when used with LiteIDE 17 | *.out 18 | 19 | # Dependency directories (remove the comment below to include it) 20 | # vendor/ 21 | 22 | ### Go Patch ### 23 | /vendor/ 24 | /Godeps/ 25 | 26 | ### Intellij+all ### 27 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider 28 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 29 | 30 | # User-specific stuff 31 | .idea/**/workspace.xml 32 | .idea/**/tasks.xml 33 | .idea/**/usage.statistics.xml 34 | .idea/**/dictionaries 35 | .idea/**/shelf 36 | 37 | # Generated files 38 | .idea/**/contentModel.xml 39 | 40 | # Sensitive or high-churn files 41 | .idea/**/dataSources/ 42 | .idea/**/dataSources.ids 43 | .idea/**/dataSources.local.xml 44 | .idea/**/sqlDataSources.xml 45 | .idea/**/dynamic.xml 46 | .idea/**/uiDesigner.xml 47 | .idea/**/dbnavigator.xml 48 | 49 | # Gradle 50 | .idea/**/gradle.xml 51 | .idea/**/libraries 52 | 53 | # Gradle and Maven with auto-import 54 | # When using Gradle or Maven with auto-import, you should exclude module files, 55 | # since they will be recreated, and may cause churn. Uncomment if using 56 | # auto-import. 57 | # .idea/artifacts 58 | # .idea/compiler.xml 59 | # .idea/jarRepositories.xml 60 | # .idea/modules.xml 61 | # .idea/*.iml 62 | # .idea/modules 63 | # *.iml 64 | # *.ipr 65 | 66 | # CMake 67 | cmake-build-*/ 68 | 69 | # Mongo Explorer plugin 70 | .idea/**/mongoSettings.xml 71 | 72 | # File-based project format 73 | *.iws 74 | 75 | # IntelliJ 76 | out/ 77 | 78 | # mpeltonen/sbt-idea plugin 79 | .idea_modules/ 80 | 81 | # JIRA plugin 82 | atlassian-ide-plugin.xml 83 | 84 | # Cursive Clojure plugin 85 | .idea/replstate.xml 86 | 87 | # Crashlytics plugin (for Android Studio and IntelliJ) 88 | com_crashlytics_export_strings.xml 89 | crashlytics.properties 90 | crashlytics-build.properties 91 | fabric.properties 92 | 93 | # Editor-based Rest Client 94 | .idea/httpRequests 95 | 96 | # Android studio 3.1+ serialized cache file 97 | .idea/caches/build_file_checksums.ser 98 | 99 | ### Intellij+all Patch ### 100 | # Ignores the whole .idea folder and all .iml files 101 | # See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360 102 | 103 | .idea/ 104 | 105 | # Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023 106 | 107 | *.iml 108 | modules.xml 109 | .idea/misc.xml 110 | *.ipr 111 | 112 | # Sonarlint plugin 113 | .idea/sonarlint 114 | 115 | # End of https://www.toptal.com/developers/gitignore/api/go,intellij+all 116 | 117 | # General 118 | .DS_Store 119 | .AppleDouble 120 | .LSOverride 121 | 122 | examples/version1/testing.go -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-notion (beta) [![Build status](https://github.com/oyekanmiayo/go-notion/workflows/Build/badge.svg)](https://github.com/oyekanmiayo/go-notion/actions?query=workflow%3ABuild) 2 | 3 | go-notion is a minimal Go client library for [Notion's v1 API](https://developers.notion.com/). Check the [usage](#usage) or 4 | [examples](examples/version1) to see how to access Notion's v1 API. 5 | 6 | ***NB**: Notion's v1 API is still in beta; this integration will change as they update it.* 7 | 8 | ## Table of Contents 9 | 10 | * [Installation](#installation) 11 | * [Authentication](#authentication) 12 | * [Internal Integration](#internal-integration) 13 | * [Public Integration](#public-integration) 14 | * [Usage](#usage) 15 | * [Databases](#databases) 16 | * [Pages](#pages) 17 | * [Blocks](#blocks) 18 | * [Users](#users) 19 | * [Search](#search) 20 | * [Contributing](#contributing) 21 | * [License](#license) 22 | * [Authors](#authors) 23 | 24 | ## Installation 25 | 26 | ``` 27 | go get github.com/oyekanmiayo/go-notion/notion/version1 28 | ``` 29 | 30 | ## Authentication 31 | 32 | HTTP requests to Notion's API must contain a bearer token in the `Authorization` header to be successful. Currently, 33 | Notion allows two types of integrations: Internal and Public. Getting the bearer token is different for each case. 34 | 35 | ### Internal Integration 36 | 37 | When you create an internal integration on Notion's developer portal, you are given something called an "Internal 38 | Integration Token" - this is the bearer token. All you need to do is simply copy it out and store it as an environment 39 | variable (preferred) or use it directly in your code. 40 | 41 | internal integration token 42 | 43 | NB: Internal Integration Token, Bearer Token and Access Token mean the same things here. 44 | 45 | ```go 46 | import ( 47 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 48 | "net/http" 49 | "os" 50 | ) 51 | 52 | func main() { 53 | accessToken := os.Getenv("NOTION_BEARER_TOKEN") 54 | 55 | // Notion Client 56 | client := notion.NewClient(http.DefaultClient, accessToken) 57 | } 58 | ``` 59 | 60 | ### Public Integration 61 | 62 | This integration receives bearer tokens each time a user completes the [OAuth flow](https://developers.notion.com/docs/authorization#authorizing-public-integrations). 63 | Basically, this is in 3 steps: 64 | 1. **The client** requests authorization from a user 65 | 2. **Notion** calls a callback url (a.k.a `redirect_uri`) with `state` and `code` as query params 66 | 3. **The client** exchanges the code for a token 67 | 68 | ```go 69 | import ( 70 | "flag" 71 | "fmt" 72 | "golang.org/x/oauth2" 73 | "log" 74 | "os" 75 | "net/http" 76 | ) 77 | 78 | func main() { 79 | 80 | flags := flag.NewFlagSet("notion-example", flag.ExitOnError) 81 | clientID := flags.String("client-id", "", "Client ID") 82 | redirectURL := flags.String("url", "", "Redirect url") 83 | err := flags.Parse(os.Args[1:]) 84 | if err != nil { 85 | log.Fatalf("error: %v", err) 86 | } 87 | 88 | c := oauth2.Config{ 89 | ClientID: *clientID, 90 | ClientSecret: "", 91 | Endpoint: oauth2.Endpoint{ 92 | AuthURL: "https://api.notion.com/v1/oauth/authorize", 93 | TokenURL: "https://api.notion.com/v1/oauth/token", 94 | }, 95 | RedirectURL: *redirectURL, 96 | } 97 | 98 | client := notion.AuthClient(http.DefaultClient) 99 | 100 | // Generate the authorization url 101 | authURL := client.Auth.AuthorizationURL(&c, "") 102 | fmt.Println(authURL) 103 | 104 | // The authorization url should be sent back to the client as a redirect 105 | // The user will see an option to give the integration access to their workspace 106 | // If the user approves the request, notion will call the call the callback url (a.k.a `redirect_uri`) with `state` and `code` as query params 107 | 108 | // Use the code here to get an access token that can now be used with go-notion's client 109 | resp, _ := client.Auth.AccessToken(&c, *code) 110 | fmt.Println(resp) 111 | 112 | client := notion.NewClient(http.DefaultClient, resp.AcessToken) 113 | } 114 | ``` 115 | 116 | The `clientID` and `clientSecret` in the snippet above can be found on the developer portal: 117 | 118 | public integration details 119 | 120 | ## Usage 121 | 122 | ### Databases 123 | 124 | Read more about the Database endpoints [here](https://developers.notion.com/reference/database). 125 | 126 | #### Retrieve a database 127 | 128 | This retrieves a Notion database based on a specified ID. Read more [here](https://developers.notion.com/reference/get-database). 129 | 130 | ```go 131 | client := notion.NewClient(http.DefaultClient, *accessToken) 132 | 133 | // Retrieve DB 134 | db, _, err := client.Databases.RetrieveDatabase(databaseID) 135 | if err != nil { 136 | fmt.Printf("Err %v\n", err) 137 | } 138 | ``` 139 | 140 | See full code example [here](examples/version1/retrieve-database-example.go). 141 | 142 | #### Query a database 143 | 144 | This gets a list of [Pages](https://developers.notion.com/reference/page) contained in a database, filtered and ordered according to the filter conditions and sort 145 | criteria provided in the request. Filters can single filters or compound filters. Read more [here](https://developers.notion.com/reference/post-database-query). 146 | 147 | ```go 148 | client := notion.NewClient(http.DefaultClient, *accessToken) 149 | 150 | // Query DB with SingleFilter 151 | params := ¬ion.QueryDatabaseBodyParams{ 152 | Filter: ¬ion.SingleFilter{ 153 | Property: "Tags", 154 | MultiSelect: ¬ion.MultiSelectCondition{ 155 | Contains: "Tag1", 156 | }, 157 | }, 158 | } 159 | resp, _, err := client.Databases.QueryDatabase(*databaseID, params) 160 | if err != nil { 161 | fmt.Printf("Err %v\n", err) 162 | } 163 | ``` 164 | 165 | See full code example [here](examples/version1/query-database-example.go) - it also contains a compound filter example :) 166 | 167 | #### List databases 168 | 169 | More than one database can be shared with a Notion integration. This endpoint lists all the databases shared with an 170 | authenticated integration. Read more [here](https://developers.notion.com/reference/get-databases). 171 | 172 | ```go 173 | client := notion.NewClient(http.DefaultClient, *accessToken) 174 | 175 | // List all DBs in a workspace 176 | params := ¬ion.ListDatabasesQueryParams{ 177 | PageSize: 20, 178 | } 179 | resp, _, err := client.Databases.ListDatabases(params) 180 | if err != nil { 181 | fmt.Printf("Err %v\n", err) 182 | } 183 | ``` 184 | 185 | See full code example [here](examples/version1/list-databases-example.go). 186 | 187 | ### Pages 188 | 189 | Read more about Page endpoints [here](https://developers.notion.com/reference/page). 190 | 191 | #### Retrieve a page 192 | 193 | This retrieves a Notion page based on a specified ID. Read more [here](https://developers.notion.com/reference/get-page). 194 | 195 | ```go 196 | client := notion.NewClient(http.DefaultClient, *accessToken) 197 | 198 | // Retrieve a page using its pageID 199 | db, _, err := client.Pages.RetrievePage(*pageID) 200 | if err != nil { 201 | fmt.Printf("Err %v\n", err) 202 | } 203 | ``` 204 | 205 | See full code example [here](examples/version1/retrieve-page-example.go). 206 | 207 | #### Create a page 208 | 209 | Pages in Notion can be created within a database or within another page. This endpoint creates a page as a child of the 210 | parent (database or page) specified. Read more [here](https://developers.notion.com/reference/post-page). 211 | 212 | ```go 213 | client := notion.NewClient(http.DefaultClient, *accessToken) 214 | 215 | params := ¬ion.CreatePageBodyParams{ 216 | Parent: ¬ion.DatabaseParent{ 217 | DatabaseID: *databaseID, 218 | }, 219 | Properties: map[string]notion.PageProperty{ 220 | "Name": { 221 | Title: []notion.RichText{ 222 | { 223 | Text: ¬ion.Text{ 224 | Content: "Creating Page Sample", 225 | }, 226 | }, 227 | }, 228 | }, 229 | "Tags": { 230 | MultiSelect: []notion.MultiSelectPropertyOpts{ 231 | { 232 | Name: "Tag1", 233 | }, 234 | { 235 | Name: "Tag3", 236 | }, 237 | }, 238 | }, 239 | "Recommended": { 240 | Checkbox: true, 241 | }, 242 | }, 243 | } 244 | resp, _, err := client.Pages.CreatePage(params) 245 | if err != nil { 246 | fmt.Printf("Err %v\n", err) 247 | } 248 | ``` 249 | 250 | The example above creates a page within a database. The body of the request is determined by the structure of the 251 | database. See full code example [here](examples/version1/create-page-example.go) along with an example to create a page within another page. 252 | 253 | #### Update page properties 254 | 255 | Updates page property values for the specified page. Properties that are not set via the "properties" parameter will 256 | remain unchanged. Read more [here](https://developers.notion.com/reference/patch-page). 257 | 258 | ```go 259 | client := notion.NewClient(http.DefaultClient, *accessToken) 260 | 261 | // Update the title of a page 262 | params := ¬ion.UpdatePagePropertiesBodyParams{ 263 | // first keys are the names or ids of the properties :) 264 | // id for title is "title" 265 | // See examples here: https://developers.notion.com/reference/page#page-property-value 266 | Properties: map[string]notion.PageProperty{ 267 | "Name": { 268 | Title: []notion.RichText{ 269 | { 270 | Type: "text", 271 | Text: ¬ion.Text{ 272 | Content: "Jamaican Cuisines III", 273 | }, 274 | }, 275 | }, 276 | }, 277 | "Recommended": { 278 | Checkbox: true, 279 | }, 280 | }, 281 | } 282 | resp, _, err := client.Pages.UpdatePageProperties(*pageID, params) 283 | if err != nil { 284 | fmt.Printf("Err %v\n", err) 285 | } 286 | ``` 287 | 288 | See full code example [here](examples/version1/update-page-properties-example.go). 289 | 290 | ### Blocks 291 | 292 | A block object represents content within Notion. Blocks can be text, lists, media, and more. A page is a type of block, 293 | too! Read more about Block endpoints [here](https://developers.notion.com/reference/block). 294 | 295 | #### Retrieve block children 296 | 297 | Returns a paginated array of child block objects contained in the block using the ID specified. In order to receive a 298 | complete representation of a block, you may need to recursively retrieve the block children of child blocks. Read more [here](https://developers.notion.com/reference/get-block-children). 299 | 300 | ```go 301 | client := notion.NewClient(http.DefaultClient, *accessToken) 302 | 303 | // Retrieve the block children for a block 304 | params := ¬ion.RetrieveBlockChildrenParams{} 305 | db, _, err := client.Blocks.RetrieveBlockChildren(*blockID, params) 306 | if err != nil { 307 | fmt.Printf("Err %v\n", err) 308 | } 309 | ``` 310 | 311 | See full code example [here](examples/version1/retrieve-block-children-example.go). 312 | 313 | #### Append block children 314 | 315 | Creates and appends new children blocks to the block using the ID specified. Returns the Block object which contains the 316 | new children. Read more [here](https://developers.notion.com/reference/patch-block-children). 317 | 318 | ```go 319 | client := notion.NewClient(http.DefaultClient, *accessToken) 320 | 321 | // Append block children (Header Two & Paragraph) to a block 322 | params := ¬ion.AppendBlockChildrenBodyParams{ 323 | Children: []notion.Block{ 324 | { 325 | Object: "block", 326 | Type: "heading_2", 327 | HeadingTwo: ¬ion.HeadingTwo{ 328 | Text: []notion.RichText{ 329 | { 330 | Type: "text", 331 | Text: ¬ion.Text{ 332 | Content: "Header Two Test", 333 | }, 334 | }, 335 | }, 336 | }, 337 | }, 338 | { 339 | Object: "block", 340 | Type: "paragraph", 341 | Paragraph: ¬ion.Paragraph{ 342 | Text: []notion.RichText{ 343 | { 344 | Type: "text", 345 | Text: ¬ion.Text{ 346 | Content: "Paragraph Test", 347 | }, 348 | }, 349 | }, 350 | }, 351 | }, 352 | }, 353 | } 354 | 355 | db, _, err := client.Blocks.AppendBlockChildren(*blockID, params) 356 | if err != nil { 357 | fmt.Printf("Err %v\n", err) 358 | } 359 | ``` 360 | 361 | See full code example [here](examples/version1/append-block-children-example.go). 362 | 363 | ### Users 364 | 365 | The User object represents a user in a Notion workspace. Users include guests, full workspace members, and bots. Read 366 | more about User endpoints [here](https://developers.notion.com/reference/user). 367 | 368 | #### Retrieve a user 369 | 370 | This retrieves a Notion user based on a specified ID. Read more [here](https://developers.notion.com/reference/get-user). 371 | 372 | ```go 373 | client := notion.NewClient(http.DefaultClient, *accessToken) 374 | 375 | // Retrieve a user by userID 376 | db, _, err := client.Users.RetrieveUser(*userID) 377 | if err != nil { 378 | fmt.Printf("Err %v\n", err) 379 | } 380 | ``` 381 | 382 | See full code example [here](examples/version1/retrieve-user-example.go). 383 | 384 | #### List all users 385 | 386 | Returns a paginated list of users for the workspace. Read more [here](https://developers.notion.com/reference/get-users). 387 | 388 | ```go 389 | client := notion.NewClient(http.DefaultClient, *accessToken) 390 | 391 | // List all users in workspace 392 | params := ¬ion.ListUsersQueryParams{ 393 | PageSize: 20, 394 | } 395 | db, _, err := client.Users.ListUsers(params) 396 | if err != nil { 397 | fmt.Printf("Err %v\n", err) 398 | } 399 | ``` 400 | 401 | See full code example [here](examples/version1/list-users-example.go). 402 | 403 | ### Search 404 | 405 | Searches all pages and child pages that are shared with the integration and returns a list of databases and pages which 406 | have titles that contain the `query` parameter. Other parameters like `sort` and `filter` also affect the output. Read 407 | more about it [here](https://developers.notion.com/reference/post-search). 408 | 409 | ```go 410 | client := notion.NewClient(http.DefaultClient, *accessToken) 411 | 412 | // Search the workspace and return pages that have titles containing "Yurts" 413 | // The result should be sorted in descending order of "last_edited_time" 414 | params := ¬ion.SearchBodyParams{ 415 | Query: "Yurts", 416 | Sort: ¬ion.Sort{ 417 | Direction: "descending", 418 | Timestamp: "last_edited_time", 419 | }, 420 | Filter: ¬ion.SearchFilter{ 421 | Value: "page", 422 | Property: "object", 423 | }, 424 | } 425 | db, _, err := client.Search.Search(params) 426 | if err != nil { 427 | fmt.Printf("Err %v\n", err) 428 | } 429 | ``` 430 | 431 | See full code example [here](examples/version1/search-example.go). 432 | 433 | ### Contributing 434 | 435 | * Code Contributions won't be accepted until Notion's v1 API is out of beta 436 | * Open an issue if you find a bug or missing integration 437 | 438 | ### License 439 | 440 | [Apache 2.0 License](LICENSE) 441 | 442 | ### Authors 443 | * [Ayomide Oyekanmi](https://twitter.com/_alternatewolf) 444 | -------------------------------------------------------------------------------- /assets/append-block-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyekanmiayo/go-notion/f800055d9888d516d72a67ea926e6d4c9bc1513d/assets/append-block-example.png -------------------------------------------------------------------------------- /assets/example-database.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyekanmiayo/go-notion/f800055d9888d516d72a67ea926e6d4c9bc1513d/assets/example-database.png -------------------------------------------------------------------------------- /assets/internal-integration-token.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyekanmiayo/go-notion/f800055d9888d516d72a67ea926e6d4c9bc1513d/assets/internal-integration-token.png -------------------------------------------------------------------------------- /assets/page-in-page-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyekanmiayo/go-notion/f800055d9888d516d72a67ea926e6d4c9bc1513d/assets/page-in-page-example.png -------------------------------------------------------------------------------- /assets/public-integration-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oyekanmiayo/go-notion/f800055d9888d516d72a67ea926e6d4c9bc1513d/assets/public-integration-details.png -------------------------------------------------------------------------------- /examples/version1/README.md: -------------------------------------------------------------------------------- 1 | # go-notion examples 2 | 3 | This README helps to provide useful information to help people understand the examples in this directory. 4 | 5 | ## Examples 6 | 7 | * [Databases](#databases) 8 | * [Pages](#pages) 9 | * [Blocks](#blocks) 10 | * [Users](#users) 11 | * [Search](#search) 12 | 13 | ### Databases 14 | 15 | The image below represents the structure of the database that was used in the examples. 16 | 17 | Example DB 18 | 19 | #### Examples 20 | 21 | * [Retrieve a database](retrieve-database-example.go) 22 | * [Query a database](query-database-example.go) 23 | * [List databases](list-databases-example.go) 24 | 25 | ### Pages 26 | 27 | Pages can be added within databases or within other pages. If a page's parent is a database, it is added as an entry to 28 | that database. The rows in the "Example DB" image above represents pages within the DB. If a page's parent is another 29 | page, then it looks like the image below: 30 | 31 | Page within a page 32 | 33 | #### Examples 34 | 35 | * [Retrieve a page](retrieve-page-example.go) 36 | * [Create a page](create-page-example.go) 37 | * [Update page properties](update-page-properties-example.go) 38 | 39 | ### Blocks 40 | 41 | Read more about blocks [here](../../README.md#blocks). In the append-block example, I demo-ed adding a header and 42 | paragraph to a page. It'll look like the image below. 43 | 44 | Append block children 45 | 46 | #### Examples 47 | 48 | * [Retrieve block children](retrieve-block-children-example.go) 49 | * [Append block children](append-block-children-example.go) 50 | 51 | ### Users 52 | 53 | #### Examples 54 | 55 | * [Retrieve a user](retrieve-user-example.go) 56 | * [List all users](list-users-example.go) 57 | 58 | ### Search 59 | 60 | I couldn't figure out how to combine the two calls below into one call. Ideally, we should be able to make a call to the 61 | Notion server that can contains an array of pages and databases. See the model below 62 | 63 | ``` 64 | type SearchResponse struct { 65 | Object string `json:"object,omitempty"` 66 | Results []interface{} `json:"results,omitempty"` 67 | NextCursor string `json:"next_cursor,omitempty"` 68 | HasMore bool `json:"has_more,omitempty"` 69 | } 70 | ``` 71 | 72 | The `Results` field cannot be `[]Page`, neither can it be `[]Database` because the results can be an array of both 73 | models. So, for now, the calls are split (with its own model). I'll add the things I've tried later, but if you think of 74 | something feel free to add as an issue to the repo :). 75 | 76 | #### Examples 77 | 78 | * [Search a page](search-example.go) 79 | * [Search a database](search-example.go) -------------------------------------------------------------------------------- /examples/version1/append-block-children-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 12 | ) 13 | 14 | func main() { 15 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 16 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 17 | blockID := flags.String("block-id", "", "Page ID") 18 | err := flags.Parse(os.Args[1:]) 19 | if err != nil { 20 | log.Fatalf("error: %v", err) 21 | } 22 | 23 | client := notion.NewClient(http.DefaultClient, *accessToken) 24 | 25 | // Append block children to a block 26 | // Sample command: go run append-block-children-example.go --access-token= --block-id= 27 | params := ¬ion.AppendBlockChildrenBodyParams{ 28 | Children: []notion.Block{ 29 | { 30 | Object: "block", 31 | Type: "heading_2", 32 | HeadingTwo: ¬ion.HeadingTwoBlock{ 33 | Text: []notion.RichText{ 34 | { 35 | Type: "text", 36 | Text: ¬ion.Text{ 37 | Content: "Header Two Test", 38 | }, 39 | }, 40 | }, 41 | }, 42 | }, 43 | { 44 | Object: "block", 45 | Type: "paragraph", 46 | Paragraph: ¬ion.ParagraphBlock{ 47 | Text: []notion.RichText{ 48 | { 49 | Type: "text", 50 | Text: ¬ion.Text{ 51 | Content: "Paragraph Test", 52 | }, 53 | }, 54 | }, 55 | }, 56 | }, 57 | }, 58 | } 59 | 60 | 61 | res, _, err := client.Blocks.AppendBlockChildren(*blockID, params) 62 | if err != nil { 63 | fmt.Printf("Err %v\n", err) 64 | } 65 | 66 | jsonBody, _ := json.Marshal(res) 67 | fmt.Println() 68 | fmt.Println(string(jsonBody)) 69 | } 70 | -------------------------------------------------------------------------------- /examples/version1/create-page-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 15 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 16 | pageID := flags.String("page-id", "", "Page ID") 17 | databaseID := flags.String("db-id", "", "DB ID") 18 | err := flags.Parse(os.Args[1:]) 19 | if err != nil { 20 | log.Fatalf("error: %v", err) 21 | } 22 | 23 | client := notion.NewClient(http.DefaultClient, *accessToken) 24 | 25 | // Create a page in a database 26 | // Sample command: go run create-page-example.go --access-token= --page-id= --db-id= 27 | params := ¬ion.CreatePageBodyParams{ 28 | Parent: ¬ion.DatabaseParent{ 29 | DatabaseID: *databaseID, 30 | }, 31 | Properties: map[string]notion.PageProperty{ 32 | "Name": { 33 | Title: []notion.RichText{ 34 | { 35 | Text: ¬ion.Text{ 36 | Content: "Creating Page Sample", 37 | }, 38 | }, 39 | }, 40 | }, 41 | "Tags": { 42 | MultiSelect: []notion.MultiSelectPropertyOpts{ 43 | { 44 | Name: "Tag1", 45 | }, 46 | { 47 | Name: "Tag3", 48 | }, 49 | }, 50 | }, 51 | "Recommended": { 52 | Checkbox: true, 53 | }, 54 | }, 55 | } 56 | resp, _, err := client.Pages.CreatePage(params) 57 | if err != nil { 58 | fmt.Printf("Err %v\n", err) 59 | } 60 | 61 | jsonBody, _ := json.Marshal(resp) 62 | fmt.Println(string(jsonBody)) 63 | 64 | // Create a page within a page 65 | params = ¬ion.CreatePageBodyParams{ 66 | Parent: ¬ion.PageParent{ 67 | PageID: *pageID, 68 | }, 69 | Properties: ¬ion.PageProperty{ 70 | Title: []notion.RichText{ 71 | { 72 | Text: ¬ion.Text{ 73 | Content: "Creating Page Within A Page Sample", 74 | }, 75 | }, 76 | }, 77 | }, 78 | } 79 | 80 | resp, _, err = client.Pages.CreatePage(params) 81 | if err != nil { 82 | fmt.Printf("Err %v\n", err) 83 | } 84 | 85 | jsonBody, _ = json.Marshal(resp) 86 | fmt.Println(string(jsonBody)) 87 | } 88 | -------------------------------------------------------------------------------- /examples/version1/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oyekanmiayo/go-notion/examples/version1 2 | 3 | go 1.15 4 | 5 | replace github.com/oyekanmiayo/go-notion/notion/version1 => ../../notion/version1 6 | 7 | require ( 8 | github.com/dghubble/sling v1.3.0 // indirect 9 | github.com/oyekanmiayo/go-notion/notion/version1 v0.0.0-00010101000000-000000000000 10 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c 11 | ) 12 | -------------------------------------------------------------------------------- /examples/version1/go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 37 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 38 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 39 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 40 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 41 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 42 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 43 | github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU= 44 | github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY= 45 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 46 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 47 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 48 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 49 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 50 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 51 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 52 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 53 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 54 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 55 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 56 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 57 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 58 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 59 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 60 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 61 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 62 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 63 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 64 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 65 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 66 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 67 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 68 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 69 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 70 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 71 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 72 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 73 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 74 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 75 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 76 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 77 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 78 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 79 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 80 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 81 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 82 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 83 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 84 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 85 | github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= 86 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 87 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 88 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 89 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 90 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 91 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 92 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 93 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 94 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 95 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 96 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 97 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 98 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 99 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 100 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 101 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 102 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 103 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 104 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 105 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 106 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 107 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 108 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 109 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 110 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 111 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 112 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 113 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 114 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 115 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 116 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 117 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 118 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 119 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 120 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 121 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 122 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 123 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 124 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 125 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 126 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 127 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 128 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 129 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 130 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 131 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 132 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 133 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 134 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 135 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 136 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 137 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 138 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 139 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 140 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 141 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 142 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 143 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 144 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 145 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 146 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 147 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 148 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 149 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 150 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 151 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 152 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 153 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 154 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 155 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 156 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 157 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 158 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 159 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 160 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 161 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 162 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 163 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 164 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 165 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 166 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 167 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 168 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 169 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 170 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 171 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 172 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 173 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 174 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 175 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 176 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 177 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 178 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 179 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 180 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 181 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 182 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 183 | golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= 184 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 185 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 186 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 187 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 188 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 189 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 190 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI= 191 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 192 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 193 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 194 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 195 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 196 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 197 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 198 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 199 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 200 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 201 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 202 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 203 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 204 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 206 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 207 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 208 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 209 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 210 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 211 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 212 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 213 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 214 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 215 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 216 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 217 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 218 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 219 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 220 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 221 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 222 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 223 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 224 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 225 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 226 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 227 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 228 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 229 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 230 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 231 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 232 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 233 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 234 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 235 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 236 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 237 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 238 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 239 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 240 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 241 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 242 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 243 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 244 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 245 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 246 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 247 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 248 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 249 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 250 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 251 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 252 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 253 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 254 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 255 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 256 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 257 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 258 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 259 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 260 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 261 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 262 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 263 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 264 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 265 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 266 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 267 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 268 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 269 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 270 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 271 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 272 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 273 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 274 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 275 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 276 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 277 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 278 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 279 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 280 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 281 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 282 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 283 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 284 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 285 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 286 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 287 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 288 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 289 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 290 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 291 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 292 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 293 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 294 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 295 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 296 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 297 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 298 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 299 | google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc= 300 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 301 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 302 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 303 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 304 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 305 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 306 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 307 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 308 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 309 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 310 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 311 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 312 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 313 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 314 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 315 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 316 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 317 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 318 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 319 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 320 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 321 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 322 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 323 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 324 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 325 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 326 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 327 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 328 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 329 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 330 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 331 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 332 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 333 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 334 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 335 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 336 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 337 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 338 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 339 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 340 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 341 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 342 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 343 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 344 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 345 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 346 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 347 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 348 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 349 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 350 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 351 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 352 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 353 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 354 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 355 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 356 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 357 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 358 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 359 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 360 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 361 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 362 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 363 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 364 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 365 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 366 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 367 | -------------------------------------------------------------------------------- /examples/version1/list-databases-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 12 | ) 13 | 14 | func main() { 15 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 16 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 17 | err := flags.Parse(os.Args[1:]) 18 | if err != nil { 19 | log.Fatalf("error: %v", err) 20 | } 21 | 22 | client := notion.NewClient(http.DefaultClient, *accessToken) 23 | 24 | // List all DBs in a workspace 25 | // Sample command: go run list-databases-example.go --access-token= 26 | params := ¬ion.ListDatabasesQueryParams{ 27 | PageSize: 20, 28 | } 29 | resp, _, err := client.Databases.ListDatabases(params) 30 | if err != nil { 31 | fmt.Printf("Err %v\n", err) 32 | } 33 | 34 | jsonBody, _ := json.Marshal(resp) 35 | fmt.Println(string(jsonBody)) 36 | } 37 | -------------------------------------------------------------------------------- /examples/version1/list-users-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 12 | ) 13 | 14 | func main() { 15 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 16 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 17 | err := flags.Parse(os.Args[1:]) 18 | if err != nil { 19 | log.Fatalf("error: %v", err) 20 | } 21 | 22 | client := notion.NewClient(http.DefaultClient, *accessToken) 23 | 24 | // List all users in workspace 25 | // Sample command: go run list-users-example.go --access-token= --db-id= 26 | params := ¬ion.ListUsersQueryParams{ 27 | PageSize: 20, 28 | } 29 | db, _, err := client.Users.ListUsers(params) 30 | if err != nil { 31 | fmt.Printf("Err %v\n", err) 32 | } 33 | 34 | jsonBody, _ := json.Marshal(db) 35 | fmt.Println(string(jsonBody)) 36 | } 37 | -------------------------------------------------------------------------------- /examples/version1/oauth2-flow-part1-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 7 | "golang.org/x/oauth2" 8 | "log" 9 | "os" 10 | ) 11 | 12 | // go run oauth2-flow-part1-example.go --client-id= --url= 13 | func main() { 14 | 15 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 16 | clientID := flags.String("client-id", "", "Client ID") 17 | redirectURL := flags.String("url", "", "Redirect url") 18 | err := flags.Parse(os.Args[1:]) 19 | if err != nil { 20 | log.Fatalf("error: %v", err) 21 | } 22 | 23 | c := oauth2.Config{ 24 | ClientID: *clientID, 25 | ClientSecret: "", 26 | Endpoint: oauth2.Endpoint{ 27 | AuthURL: "https://api.notion.com/v1/oauth/authorize", 28 | TokenURL: "https://api.notion.com/v1/oauth/token", 29 | }, 30 | RedirectURL: *redirectURL, 31 | } 32 | 33 | client := notion.AuthClient(nil) 34 | 35 | authURL := client.Auth.AuthorizationURL(&c, "") 36 | fmt.Println(authURL) 37 | } 38 | -------------------------------------------------------------------------------- /examples/version1/oauth2-flow-part2-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 7 | "golang.org/x/oauth2" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | // go run oauth2-flow-part2-example.go --client-id= --client-secret= --code= --url= 14 | func main() { 15 | 16 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 17 | clientID := flags.String("client-id", "", "Client ID") 18 | clientSecret := flags.String("client-secret", "", "Client ID") 19 | code := flags.String("code", "", "Auth Code from Notion") 20 | redirectURL := flags.String("url", "", "Redirect url") 21 | err := flags.Parse(os.Args[1:]) 22 | if err != nil { 23 | log.Fatalf("error: %v", err) 24 | } 25 | 26 | c := oauth2.Config{ 27 | ClientID: *clientID, 28 | ClientSecret: *clientSecret, 29 | Endpoint: oauth2.Endpoint{ 30 | AuthURL: "https://api.notion.com/v1/oauth/authorize", 31 | TokenURL: "https://api.notion.com/v1/oauth/token", 32 | }, 33 | RedirectURL: *redirectURL, 34 | } 35 | 36 | fmt.Println("Hello") 37 | client := notion.AuthClient(http.DefaultClient) 38 | 39 | // This code is sent in as a query param to the redirect_uri after the user has authorized notion 40 | //?code=&state= 41 | resp, _, _ := client.Auth.AccessToken(&c, *code) 42 | fmt.Println(resp) 43 | } 44 | -------------------------------------------------------------------------------- /examples/version1/query-database-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 12 | ) 13 | 14 | func main() { 15 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 16 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 17 | databaseID := flags.String("db-id", "", "Notion Database ID") 18 | err := flags.Parse(os.Args[1:]) 19 | if err != nil { 20 | log.Fatalf("error: %v", err) 21 | } 22 | 23 | client := notion.NewClient(http.DefaultClient, *accessToken) 24 | 25 | // Query DB with SingleFilter 26 | // Sample command: go run query-database-example.go --access-token= --db-id= 27 | params := ¬ion.QueryDatabaseBodyParams{ 28 | Filter: ¬ion.SingleFilter{ 29 | Property: "Tags", 30 | MultiSelect: ¬ion.MultiSelectCondition{ 31 | Contains: "Tag1", 32 | }, 33 | }, 34 | } 35 | resp, _, err := client.Databases.QueryDatabase(*databaseID, params) 36 | if err != nil { 37 | fmt.Printf("Err %v\n", err) 38 | } 39 | 40 | jsonBody, _ := json.Marshal(resp) 41 | fmt.Println(string(jsonBody)) 42 | 43 | fmt.Println() 44 | 45 | // Query DB with CompoundFilter 46 | params = ¬ion.QueryDatabaseBodyParams{ 47 | Filter: ¬ion.CompoundFilter{ 48 | AND: []notion.SingleFilter{ 49 | { 50 | Property: "Tags", 51 | MultiSelect: ¬ion.MultiSelectCondition{ 52 | Contains: "Tag1", 53 | }, 54 | }, 55 | { 56 | Property: "Recommended", 57 | Checkbox: ¬ion.CheckboxCondition{ 58 | Equals: true, 59 | }, 60 | }, 61 | }, 62 | }, 63 | } 64 | resp, _, err = client.Databases.QueryDatabase(*databaseID, params) 65 | if err != nil { 66 | fmt.Printf("Err %v\n", err) 67 | } 68 | 69 | jsonBody, _ = json.Marshal(resp) 70 | fmt.Println(string(jsonBody)) 71 | } 72 | -------------------------------------------------------------------------------- /examples/version1/retrieve-block-children-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 12 | ) 13 | 14 | func main() { 15 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 16 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 17 | blockID := flags.String("block-id", "", "Page ID") 18 | err := flags.Parse(os.Args[1:]) 19 | if err != nil { 20 | log.Fatalf("error: %v", err) 21 | } 22 | 23 | client := notion.NewClient(http.DefaultClient, *accessToken) 24 | 25 | // Retrieve the block children for a block 26 | // Sample command: go run retrieve-block-children-example.go --access-token= --block-id= 27 | params := ¬ion.RetrieveBlockChildrenParams{} 28 | db, _, err := client.Blocks.RetrieveBlockChildren(*blockID, params) 29 | if err != nil { 30 | fmt.Printf("Err %v\n", err) 31 | } 32 | 33 | jsonBody, _ := json.Marshal(db) 34 | fmt.Println(string(jsonBody)) 35 | } 36 | -------------------------------------------------------------------------------- /examples/version1/retrieve-database-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 12 | ) 13 | 14 | func main() { 15 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 16 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 17 | databaseID := flags.String("db-id", "", "Notion Database ID") 18 | err := flags.Parse(os.Args[1:]) 19 | if err != nil { 20 | log.Fatalf("error: %v", err) 21 | } 22 | 23 | client := notion.NewClient(http.DefaultClient, *accessToken) 24 | 25 | // Retrieve DB 26 | // Sample command: go run retrieve-database-example.go --access-token= --db-id= 27 | db, _, err := client.Databases.RetrieveDatabase(*databaseID) 28 | if err != nil { 29 | fmt.Printf("Err %v\n", err) 30 | } 31 | 32 | // Print column names and type for DB table 33 | for k, v := range db.Properties { 34 | fmt.Printf("%v, %v", k, v.Type) 35 | fmt.Println() 36 | } 37 | 38 | // View response in json 39 | jsonBody, _ := json.Marshal(db) 40 | fmt.Println(string(jsonBody)) 41 | } 42 | -------------------------------------------------------------------------------- /examples/version1/retrieve-page-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 15 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 16 | pageID := flags.String("page-id", "", "Page ID") 17 | err := flags.Parse(os.Args[1:]) 18 | if err != nil { 19 | log.Fatalf("error: %v", err) 20 | } 21 | 22 | client := notion.NewClient(http.DefaultClient, *accessToken) 23 | 24 | // Retrieve a page using its pageID 25 | // Sample command: go run retrieve-page-example.go --access-token= --page-id= 26 | page, _, err := client.Pages.RetrievePage(*pageID) 27 | if err != nil { 28 | fmt.Printf("Err %v\n", err) 29 | } 30 | 31 | // Print page response as json 32 | jsonBody, _ := json.Marshal(page) 33 | fmt.Println(string(jsonBody)) 34 | } 35 | -------------------------------------------------------------------------------- /examples/version1/retrieve-user-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net/http" 9 | "os" 10 | 11 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 12 | ) 13 | 14 | func main() { 15 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 16 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 17 | userID := flags.String("user-id", "", "User ID") 18 | err := flags.Parse(os.Args[1:]) 19 | if err != nil { 20 | log.Fatalf("error: %v", err) 21 | } 22 | 23 | client := notion.NewClient(http.DefaultClient, *accessToken) 24 | 25 | // Retrieve a user by userID 26 | // Sample command: go run retrieve-user-example.go --access-token= --user-id= 27 | db, _, err := client.Users.RetrieveUser(*userID) 28 | if err != nil { 29 | fmt.Printf("Err %v\n", err) 30 | } 31 | 32 | jsonBody, _ := json.Marshal(db) 33 | fmt.Println(string(jsonBody)) 34 | } 35 | -------------------------------------------------------------------------------- /examples/version1/search-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 15 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 16 | err := flags.Parse(os.Args[1:]) 17 | if err != nil { 18 | log.Fatalf("error: %v", err) 19 | } 20 | 21 | client := notion.NewClient(http.DefaultClient, *accessToken) 22 | 23 | // Search the workspace for pages with titles that contain this 24 | // Sample command: go run search-example.go --access-token= 25 | params := ¬ion.SearchBodyParams{ 26 | Query: "Yurts", 27 | Sort: ¬ion.Sort{ 28 | Direction: "descending", 29 | Timestamp: "last_edited_time", 30 | }, 31 | } 32 | res, _, err := client.Search.SearchPage(params) 33 | if err != nil { 34 | fmt.Printf("Err %v\n", err) 35 | } 36 | 37 | jsonBody, _ := json.Marshal(res) 38 | fmt.Println(string(jsonBody)) 39 | 40 | fmt.Println("<==============================>") 41 | 42 | // Search the workspace for databases with titles that contain this 43 | paramsII := ¬ion.SearchBodyParams{ 44 | Query: "Yurts", 45 | Sort: ¬ion.Sort{ 46 | Direction: "descending", 47 | Timestamp: "last_edited_time", 48 | }, 49 | } 50 | resII, _, err := client.Search.SearchDatabase(paramsII) 51 | if err != nil { 52 | fmt.Printf("Err %v\n", err) 53 | } 54 | 55 | jsonBodyII, _ := json.Marshal(resII) 56 | fmt.Println(string(jsonBodyII)) 57 | } 58 | -------------------------------------------------------------------------------- /examples/version1/testing.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 7 | ) 8 | 9 | func main() { 10 | 11 | params := ¬ion.SearchDatabaseResponse{ 12 | Object: "list", 13 | Results: []notion.Database{ 14 | { 15 | Object: "database", 16 | ID: "123", 17 | CreatedTime: "2021-05-23T07:41:16.751Z", 18 | LastEditedTime: "2021-05-23T07:41:00.000Z", 19 | Title: []notion.RichText{ 20 | { 21 | Type: "text", 22 | Text: ¬ion.Text{ 23 | Content: "Jamboree", 24 | }, 25 | }, 26 | }, 27 | Properties: map[string]notion.PropertyObj{ 28 | "Name": { 29 | ID: "title", 30 | Type: "title", 31 | Title: map[string]interface{}{}, 32 | }, 33 | }, 34 | }, 35 | }, 36 | } 37 | 38 | res, _ := json.Marshal(params) 39 | fmt.Println(string(res)) 40 | } 41 | -------------------------------------------------------------------------------- /examples/version1/update-page-properties-example.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "flag" 6 | "fmt" 7 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 8 | "log" 9 | "net/http" 10 | "os" 11 | ) 12 | 13 | func main() { 14 | flags := flag.NewFlagSet("notion-databases-example", flag.ExitOnError) 15 | accessToken := flags.String("access-token", "", "Notion API Key / Notion Access Token") 16 | pageID := flags.String("page-id", "", "Page ID") 17 | err := flags.Parse(os.Args[1:]) 18 | if err != nil { 19 | log.Fatalf("error: %v", err) 20 | } 21 | 22 | client := notion.NewClient(http.DefaultClient, *accessToken) 23 | 24 | // Sample command: go run update-page-properties-example.go --access-token= --page-id= 25 | params := ¬ion.UpdatePagePropertiesBodyParams{ 26 | // first keys are the names or ids of the properties :) 27 | // id for title is "title" 28 | // See examples here: https://developers.notion.com/reference/page#page-property-value 29 | Properties: map[string]notion.PageProperty{ 30 | "Name": { 31 | Title: []notion.RichText{ 32 | { 33 | Type: "text", 34 | Text: ¬ion.Text{ 35 | Content: "Jamaican Cuisines III", 36 | }, 37 | }, 38 | }, 39 | }, 40 | "Recommended": { 41 | Checkbox: true, 42 | }, 43 | }, 44 | } 45 | resp, _, err := client.Pages.UpdatePageProperties(*pageID, params) 46 | if err != nil { 47 | fmt.Printf("Err %v\n", err) 48 | } 49 | 50 | jsonBody, _ := json.Marshal(resp) 51 | fmt.Println(string(jsonBody)) 52 | } 53 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oyekanmiayo/go-notion 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/dghubble/sling v1.3.0 // indirect 7 | github.com/stretchr/testify v1.7.0 // indirect 8 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c // indirect 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 35 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 36 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 37 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 38 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 39 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 40 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 41 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 42 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 43 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 44 | github.com/dghubble/sling v1.3.0 h1:pZHjCJq4zJvc6qVQ5wN1jo5oNZlNE0+8T/h0XeXBUKU= 45 | github.com/dghubble/sling v1.3.0/go.mod h1:XXShWaBWKzNLhu2OxikSNFrlsvowtz4kyRuXUG7oQKY= 46 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 47 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 48 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 49 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 50 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 51 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 52 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 53 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 54 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 55 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 56 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 57 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 58 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 59 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 60 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 61 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 62 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 63 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 64 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 65 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 66 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 67 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 68 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 69 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 70 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 71 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 72 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 73 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 74 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 75 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 76 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 77 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 78 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 79 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 80 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 81 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 82 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 83 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 84 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 85 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 86 | github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= 87 | github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= 88 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 89 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 90 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 91 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 92 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 93 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 94 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 95 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 96 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 97 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 98 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 99 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 100 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 101 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 102 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 103 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 104 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 105 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 106 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 107 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 108 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 109 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 110 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 111 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 112 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 113 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 114 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 115 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 116 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 117 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 118 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 119 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 120 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 121 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 122 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 123 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 124 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 125 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 126 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 127 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 128 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 129 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 130 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 131 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 132 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 133 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 134 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 135 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 136 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 137 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 138 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 139 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 140 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 141 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 142 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 143 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 144 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 145 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 146 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 147 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 148 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 149 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 150 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 151 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 152 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 153 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 154 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 155 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 156 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 157 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 158 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 159 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 160 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 161 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 162 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 163 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 164 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 165 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 166 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 167 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 168 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 169 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 170 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 171 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 172 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 173 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 174 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 175 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 176 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 177 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 178 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 179 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 180 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 181 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 182 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 183 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 184 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 185 | golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA= 186 | golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 187 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 188 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 189 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 190 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 191 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 192 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c h1:pkQiBZBvdos9qq4wBAHqlzuZHEXo07pqV06ef90u1WI= 193 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= 194 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 195 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 196 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 197 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 198 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 199 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 200 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 201 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 202 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 203 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 204 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 205 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 206 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 207 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 208 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 209 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 210 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 211 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 212 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 213 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 214 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 215 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 216 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 217 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 218 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 219 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 220 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 221 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 222 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 223 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 224 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 225 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 226 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 227 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 228 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 229 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 230 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 231 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 232 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 233 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 234 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 235 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 236 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 237 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 238 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 239 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 240 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 241 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 242 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 243 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 244 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 245 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 246 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 247 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 248 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 249 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 250 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 251 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 252 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 253 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 254 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 255 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 256 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 257 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 258 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 259 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 260 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 261 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 262 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 263 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 264 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 265 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 266 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 267 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 268 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 269 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 270 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 271 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 272 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 273 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 274 | golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 275 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 276 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 277 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 278 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 279 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 280 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 281 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 282 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 283 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 284 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 285 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 286 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 287 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 288 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 289 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 290 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 291 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 292 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 293 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 294 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 295 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 296 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 297 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 298 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 299 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 300 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 301 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 302 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 303 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 304 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 305 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 306 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 307 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 308 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 309 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 310 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 311 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 312 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 313 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 314 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 315 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 316 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 317 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 318 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 319 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 320 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 321 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 322 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 323 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 324 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 325 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 326 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 327 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 328 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 329 | google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 330 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 331 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 332 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 333 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 334 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 335 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 336 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 337 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 338 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 339 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 340 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 341 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 342 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 343 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 344 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 345 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 346 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 347 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 348 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 349 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 350 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 351 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 352 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 353 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 354 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 355 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 356 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 357 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 358 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 359 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 360 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 361 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 362 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 363 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 364 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 365 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 366 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 367 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 368 | -------------------------------------------------------------------------------- /notion/version1/auth.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | import ( 4 | "encoding/base64" 5 | "github.com/dghubble/sling" 6 | "golang.org/x/oauth2" 7 | "net/http" 8 | ) 9 | 10 | type AuthService struct { 11 | sling *sling.Sling 12 | } 13 | 14 | // newAuthService returns a new AuthService. 15 | func newAuthService(sling *sling.Sling) *AuthService { 16 | return &AuthService{ 17 | sling: sling.Path("oauth/"), 18 | } 19 | } 20 | 21 | func (a *AuthService) AuthorizationURL(c *oauth2.Config, state string) string { 22 | return c.AuthCodeURL("") 23 | } 24 | 25 | type AccessTokenRequest struct { 26 | GrantType string `json:"grant_type"` 27 | Code string `json:"code"` 28 | RedirectURI string `json:"redirect_uri"` 29 | } 30 | 31 | type AccessTokenResponse struct { 32 | AccessToken string `json:"access_token"` 33 | WorkspaceName string `json:"workspace_name"` 34 | WorkspaceIcon string `json:"workspace_icon"` 35 | BotID string `json:"bot_id"` 36 | } 37 | 38 | func (a *AuthService) AccessToken(c *oauth2.Config, authCode string) (*AccessTokenResponse, *http.Response, error) { 39 | 40 | tokenResponse := new(AccessTokenResponse) 41 | apiError := new(APIError) 42 | 43 | tokenRequest := AccessTokenRequest{ 44 | GrantType: "authorization_code", 45 | Code: authCode, 46 | RedirectURI: c.RedirectURL, 47 | } 48 | 49 | resp, err := a.sling.New().Post("token"). 50 | BodyJSON(tokenRequest).Add("Content-Type", "application/json"). 51 | Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(c.ClientID+":"+c.ClientSecret))). 52 | Receive(tokenResponse, apiError) 53 | 54 | return tokenResponse, resp, relevantError(err, *apiError) 55 | } 56 | -------------------------------------------------------------------------------- /notion/version1/auth_test.go: -------------------------------------------------------------------------------- 1 | package version1_test 2 | 3 | import ( 4 | "fmt" 5 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 6 | "github.com/stretchr/testify/assert" 7 | "golang.org/x/oauth2" 8 | "net/http" 9 | "testing" 10 | ) 11 | 12 | var ( 13 | testAuthorizationURL = "https://api.notion.com/v1/oauth/authorize?client_id=client_id&redirect_uri=http%3A%2F%2Flocalhost%3A8081&response_type=code" 14 | testAccessTokenReqJSON = `{"grant_type":"authorization_code","code":"1234","redirect_uri":"http://localhost:8081"}` + "\n" 15 | testAccessTokenResJSON = `{"access_token": "abc123def", "workspace_name": "workspace", "workspace_icon": "icon", "bot_id": "987"}` 16 | testAccessTokenRes = notion.AccessTokenResponse{ 17 | AccessToken: "abc123def", 18 | WorkspaceName: "workspace", 19 | WorkspaceIcon: "icon", 20 | BotID: "987", 21 | } 22 | ) 23 | 24 | func TestAuthService_AuthorizationURL(t *testing.T) { 25 | config := oauth2.Config{ 26 | ClientID: "client_id", 27 | ClientSecret: "", 28 | Endpoint: oauth2.Endpoint{ 29 | AuthURL: "https://api.notion.com/v1/oauth/authorize", 30 | TokenURL: "https://api.notion.com/v1/oauth/token", 31 | }, 32 | RedirectURL: "http://localhost:8081", 33 | } 34 | client := notion.AuthClient(nil) 35 | authURL := client.Auth.AuthorizationURL(&config, "") 36 | assert.Equal(t, testAuthorizationURL, authURL) 37 | } 38 | 39 | func TestAuthService_AccessToken(t *testing.T) { 40 | httpClient, mux, server := testServer() 41 | defer server.Close() 42 | 43 | mux.HandleFunc("/v1/oauth/token", func(w http.ResponseWriter, r *http.Request) { 44 | assertMethod(t, "POST", r) 45 | assertPostJSON(t, testAccessTokenReqJSON, r) 46 | w.Header().Set("Content-Type", "application/json") 47 | fmt.Fprintf(w, testAccessTokenResJSON) 48 | }) 49 | 50 | config := oauth2.Config{ 51 | ClientID: "client_id", 52 | ClientSecret: "client_sec", 53 | Endpoint: oauth2.Endpoint{ 54 | AuthURL: "https://api.notion.com/v1/oauth/authorize", 55 | TokenURL: "https://api.notion.com/v1/oauth/token", 56 | }, 57 | RedirectURL: "http://localhost:8081", 58 | } 59 | 60 | client := notion.AuthClient(httpClient) 61 | resp, _, err := client.Auth.AccessToken(&config, "1234") 62 | assert.Nil(t, err) 63 | assert.Equal(t, &testAccessTokenRes, resp) 64 | } 65 | -------------------------------------------------------------------------------- /notion/version1/blocks.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | import ( 4 | "github.com/dghubble/sling" 5 | "net/http" 6 | ) 7 | 8 | type BlockService struct { 9 | sling *sling.Sling 10 | } 11 | 12 | // newBlockService returns a new BlockService. 13 | func newBlockService(sling *sling.Sling) *BlockService { 14 | return &BlockService{ 15 | sling: sling.Path("blocks/"), 16 | } 17 | } 18 | 19 | // https://developers.notion.com/reference/block 20 | // Object is always "block" 21 | // Type must be one of "paragraph", "heading_1", "heading_2", "heading_3", "bulleted_list_item", 22 | // "numbered_list_item", "to_do", "toggle", "child_page", and "unsupported" 23 | type Block struct { 24 | Object string `json:"object,omitempty"` 25 | ID string `json:"id,omitempty"` 26 | Type string `json:"type,omitempty"` 27 | CreatedTime string `json:"created_time,omitempty"` 28 | LastEditedTime string `json:"last_edited_time,omitempty"` 29 | HasChildren bool `json:"has_children,omitempty"` 30 | Paragraph *ParagraphBlock `json:"paragraph,omitempty"` 31 | HeadingOne *HeadingOneBlock `json:"heading_1,omitempty"` 32 | HeadingTwo *HeadingTwoBlock `json:"heading_2,omitempty"` 33 | HeadingThree *HeadingThreeBlock `json:"heading_3,omitempty"` 34 | BulletedListItem *BulletedListItemBlock `json:"bulleted_list_item,omitempty"` 35 | NumberedListItem *NumberedListItemBlock `json:"numbered_list_item,omitempty"` 36 | ToDo *ToDoBlock `json:"to_do,omitempty"` 37 | Toggle *ToggleBlock `json:"toggle,omitempty"` 38 | ChildPage *ChildPageBlock `json:"child_page,omitempty"` 39 | } 40 | 41 | // https://developers.notion.com/reference/block#paragraph-blocks 42 | type ParagraphBlock struct { 43 | Text []RichText `json:"text,omitempty"` 44 | Children []Block `json:"children,omitempty"` 45 | } 46 | 47 | // https://developers.notion.com/reference/block#heading-one-blocks 48 | type HeadingOneBlock struct { 49 | Text []RichText `json:"text,omitempty"` 50 | } 51 | 52 | // https://developers.notion.com/reference/block#heading-two-blocks 53 | type HeadingTwoBlock struct { 54 | Text []RichText `json:"text,omitempty"` 55 | } 56 | 57 | // https://developers.notion.com/reference/block#heading-three-blocks 58 | type HeadingThreeBlock struct { 59 | Text []RichText `json:"text,omitempty"` 60 | } 61 | 62 | // https://developers.notion.com/reference/block#bulleted-list-item-blocks 63 | type BulletedListItemBlock struct { 64 | Text []RichText `json:"text,omitempty"` 65 | Children []Block `json:"children,omitempty"` 66 | } 67 | 68 | // https://developers.notion.com/reference/block#numbered-list-item-blocks 69 | type NumberedListItemBlock struct { 70 | Text []RichText `json:"text,omitempty"` 71 | Children []Block `json:"children,omitempty"` 72 | } 73 | 74 | // https://developers.notion.com/reference/block#to-do-blocks 75 | type ToDoBlock struct { 76 | Text []RichText `json:"text,omitempty"` 77 | Checked bool `json:"checked,omitempty"` 78 | Children []Block `json:"children,omitempty"` 79 | } 80 | 81 | // https://developers.notion.com/reference/block#toggle-blocks 82 | type ToggleBlock struct { 83 | Text []RichText `json:"text,omitempty"` 84 | Children []Block `json:"children,omitempty"` 85 | } 86 | 87 | // https://developers.notion.com/reference/block#child-page-blocks 88 | type ChildPageBlock struct { 89 | Title string `json:"title,omitempty"` 90 | } 91 | 92 | type RetrieveBlockChildrenParams struct { 93 | StartCursor string `url:"start_cursor,omitempty"` 94 | PageSize int32 `url:"page_size,omitempty"` 95 | } 96 | 97 | type RetrieveBlockChildrenResponse struct { 98 | Object string `json:"object,omitempty"` 99 | Results []Block `json:"results,omitempty"` 100 | NextCursor string `json:"next_cursor,omitempty"` 101 | HasMore bool `json:"has_more,omitempty"` 102 | } 103 | 104 | // https://developers.notion.com/reference/get-block-children 105 | func (b *BlockService) RetrieveBlockChildren(blockID string, 106 | params *RetrieveBlockChildrenParams) (*RetrieveBlockChildrenResponse, *http.Response, error) { 107 | response := new(RetrieveBlockChildrenResponse) 108 | apiError := new(APIError) 109 | resp, err := b.sling.New().Get(blockID+"/children").QueryStruct(params).Receive(response, apiError) 110 | 111 | return response, resp, relevantError(err, *apiError) 112 | } 113 | 114 | type AppendBlockChildrenBodyParams struct { 115 | Children []Block `json:"children"` 116 | } 117 | 118 | // https://developers.notion.com/reference/patch-block-children 119 | // NB: Blocks cannot be modified currently. Once a block is appended as a child of another block, 120 | // it cannot be updated or deleted. 121 | func (b *BlockService) AppendBlockChildren(blockID string, params *AppendBlockChildrenBodyParams) (*Block, *http.Response, error) { 122 | block := new(Block) 123 | apiError := new(APIError) 124 | resp, err := b.sling.New().Patch(blockID+"/children").BodyJSON(params).Receive(block, apiError) 125 | 126 | return block, resp, relevantError(err, *apiError) 127 | } 128 | -------------------------------------------------------------------------------- /notion/version1/blocks_test.go: -------------------------------------------------------------------------------- 1 | package version1_test 2 | 3 | import ( 4 | "fmt" 5 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | testRetrieveBlockChildrenResJSON = `{"object":"list","results":[{"object":"block","type":"to_do","to_do":{"text":[{"plain_text":"Sample Task","type":""}]}}]}` 13 | testRetrieveBlockChildrenRes = ¬ion.RetrieveBlockChildrenResponse{ 14 | Object: "list", 15 | Results: []notion.Block{ 16 | { 17 | Object: "block", 18 | Type: "to_do", 19 | ToDo: ¬ion.ToDoBlock{ 20 | Text: []notion.RichText{ 21 | { 22 | PlainText: "Sample Task", 23 | }, 24 | }, 25 | }, 26 | }, 27 | }, 28 | } 29 | 30 | testAppendBlockChildrenBodyParamsJSON = `{"children":[{"object":"block","type":"heading_2","heading_2":{"text":[{"type":"text","text":{"content":"Header Two Test"}}]}}]}` + "\n" 31 | testAppendBlockChildrenBodyParams = ¬ion.AppendBlockChildrenBodyParams{ 32 | Children: []notion.Block{ 33 | { 34 | Object: "block", 35 | Type: "heading_2", 36 | HeadingTwo: ¬ion.HeadingTwoBlock{ 37 | Text: []notion.RichText{ 38 | { 39 | Type: "text", 40 | Text: ¬ion.Text{ 41 | Content: "Header Two Test", 42 | }, 43 | }, 44 | }, 45 | }, 46 | }, 47 | }, 48 | } 49 | testAppendBlockChildrenResJSON = ` 50 | { 51 | "object": "block", 52 | "id": "123", 53 | "type": "child_page", 54 | "created_time": "2021-05-16T14:35:46.713Z", 55 | "last_edited_time": "2021-05-23T07:09:07.935Z", 56 | "has_children": true, 57 | "child_page": { 58 | "title": "Yurts in Big Surr, California 2 " 59 | } 60 | }` 61 | testAppendBlockChildrenRes = ¬ion.Block{ 62 | Object: "block", 63 | ID: "123", 64 | Type: "child_page", 65 | CreatedTime: "2021-05-16T14:35:46.713Z", 66 | LastEditedTime: "2021-05-23T07:09:07.935Z", 67 | HasChildren: true, 68 | ChildPage: ¬ion.ChildPageBlock{ 69 | Title: "Yurts in Big Surr, California 2 ", 70 | }, 71 | } 72 | ) 73 | 74 | func TestBlockService_RetrieveBlockChildren(t *testing.T) { 75 | httpClient, mux, server := testServer() 76 | defer server.Close() 77 | 78 | mux.HandleFunc("/v1/blocks/123/children", func(w http.ResponseWriter, r *http.Request) { 79 | assertMethod(t, "GET", r) 80 | w.Header().Set("Content-Type", "application/json") 81 | fmt.Fprintf(w, testRetrieveBlockChildrenResJSON) 82 | }) 83 | 84 | client := notion.NewClient(httpClient, "0000") 85 | params := notion.RetrieveBlockChildrenParams{} 86 | resp, _, err := client.Blocks.RetrieveBlockChildren("123", ¶ms) 87 | assert.Nil(t, err) 88 | assert.Equal(t, testRetrieveBlockChildrenRes, resp) 89 | } 90 | 91 | func TestBlockService_AppendBlockChildren(t *testing.T) { 92 | httpClient, mux, server := testServer() 93 | defer server.Close() 94 | 95 | mux.HandleFunc("/v1/blocks/123/children", func(w http.ResponseWriter, r *http.Request) { 96 | assertMethod(t, "PATCH", r) 97 | assertPostJSON(t, testAppendBlockChildrenBodyParamsJSON, r) 98 | w.Header().Set("Content-Type", "application/json") 99 | fmt.Fprintf(w, testAppendBlockChildrenResJSON) 100 | }) 101 | 102 | client := notion.NewClient(httpClient, "0000") 103 | resp, _, err := client.Blocks.AppendBlockChildren("123", testAppendBlockChildrenBodyParams) 104 | assert.Nil(t, err) 105 | assert.Equal(t, testAppendBlockChildrenRes, resp) 106 | } 107 | -------------------------------------------------------------------------------- /notion/version1/common.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | // Type must be either "text", "mention" or "equation" 4 | // Use Mention for Mention 5 | type RichText struct { 6 | PlainText string `json:"plain_text,omitempty"` 7 | Href string `json:"href,omitempty"` 8 | Annotations *Annotations `json:"annotations,omitempty"` 9 | Type string `json:"type,omitempty"` 10 | Text *Text `json:"text,omitempty"` 11 | Mention *Mention `json:"mention,omitempty"` 12 | Equation *Equation `json:"equation,omitempty"` 13 | } 14 | 15 | // Color must be either "default", "gray", "brown", "orange", "yellow", "green", "blue", "purple", "pink", "red", 16 | // "gray_background", "brown_background", "orange_background", "yellow_background", "green_background", 17 | // "blue_background", "purple_background", "pink_background", or "red_background" 18 | type Annotations struct { 19 | Bold bool `json:"bold,omitempty"` 20 | Italic bool `json:"italic,omitempty"` 21 | Strikethrough bool `json:"strikethrough,omitempty"` 22 | Underline bool `json:"underline,omitempty"` 23 | Code bool `json:"code,omitempty"` 24 | Color string `json:"color,omitempty"` 25 | } 26 | 27 | type Text struct { 28 | Content string `json:"content,omitempty"` 29 | Link *Link `json:"link,omitempty"` 30 | } 31 | 32 | // If, Link != nil, Type is always "url" and URL is always a web address. 33 | // https://developers.notion.com/reference/rich-text#link-objects 34 | type Link struct { 35 | Type string `json:"type,omitempty"` 36 | URL string `json:"url,omitempty"` 37 | } 38 | 39 | // Use PageMention for PageMention 40 | // Use DatabaseMention for DatabaseMention 41 | // Use Date for Date 42 | type Mention struct { 43 | Type string `json:"type"` 44 | UserMention *User `json:"user,omitempty"` 45 | PageMention *PageMention `json:"page,omitempty"` 46 | DatabaseMention *DatabaseMention `json:"database,omitempty"` 47 | Date *DateProperty `json:"date,omitempty"` 48 | } 49 | 50 | type PageMention struct { 51 | ID string `json:"id,omitempty"` 52 | } 53 | 54 | type DatabaseMention struct { 55 | ID string `json:"id,omitempty"` 56 | } 57 | 58 | type Equation struct { 59 | Expression string `json:"expression,omitempty"` 60 | } 61 | -------------------------------------------------------------------------------- /notion/version1/databases.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | import ( 4 | "github.com/dghubble/sling" 5 | "net/http" 6 | ) 7 | 8 | type DatabaseService struct { 9 | sling *sling.Sling 10 | } 11 | 12 | // newDatabaseService returns a new DatabaseService. 13 | func newDatabaseService(sling *sling.Sling) *DatabaseService { 14 | return &DatabaseService{ 15 | sling: sling.Path("databases/"), 16 | } 17 | } 18 | 19 | type Database struct { 20 | Object string `json:"object,omitempty"` 21 | ID string `json:"id,omitempty"` 22 | CreatedTime string `json:"created_time,omitempty"` 23 | LastEditedTime string `json:"last_edited_time,omitempty"` 24 | Title []RichText `json:"title,omitempty"` 25 | Properties map[string]PropertyObj `json:"properties,omitempty"` 26 | } 27 | 28 | // Type can only be one of "title", "rich_text", "number", "select", "multi_select", "date", "people", "file", 29 | // "checkbox", "url", "email", "phone_number", "formula", "relation", "rollup", "created_time", "created_by", 30 | // "last_edited_time", "last_edited_by". 31 | type PropertyObj struct { 32 | ID string `json:"id,omitempty"` 33 | Type string `json:"type,omitempty"` 34 | Title interface{} `json:"title,omitempty"` 35 | Text interface{} `json:"text,omitempty"` 36 | RichText interface{} `json:"rich_text,omitempty"` 37 | Number *NumberConfig `json:"number,omitempty"` 38 | Select *SelectConfig `json:"select,omitempty"` 39 | MultiSelect *MultiSelectConfig `json:"multi_select,omitempty"` 40 | Date interface{} `json:"date,omitempty"` 41 | People interface{} `json:"people,omitempty"` 42 | File interface{} `json:"file,omitempty"` 43 | Checkbox interface{} `json:"checkbox,omitempty"` 44 | URL interface{} `json:"url,omitempty"` 45 | Email interface{} `json:"email,omitempty"` 46 | PhoneNumber interface{} `json:"phone_number,omitempty"` 47 | Formula *FormulaConfig `json:"formula,omitempty"` 48 | Relation *RelationConfig `json:"relation,omitempty"` 49 | Rollup *RollupConfig `json:"rollup,omitempty"` 50 | CreatedTime interface{} `json:"created_time,omitempty"` 51 | CreatedBy interface{} `json:"created_by,omitempty"` 52 | LastEditedTime interface{} `json:"last_edited_time,omitempty"` 53 | LastEditedBy interface{} `json:"last_edited_by,omitempty"` 54 | } 55 | 56 | // Format can only be one of number, number_with_commas, percent, dollar, euro, pound, yen, ruble, rupee, won, yuan. 57 | type NumberConfig struct { 58 | Format string `json:"format,omitempty"` 59 | } 60 | 61 | type SelectConfig struct { 62 | Options []SelectOption `json:"options,omitempty"` 63 | } 64 | 65 | // Color can only be one of default, gray, brown, orange, yellow, green, blue, purple, pink, red. 66 | type SelectOption struct { 67 | Name string `json:"name,omitempty"` 68 | ID string `json:"id,omitempty"` 69 | Color string `json:"color,omitempty"` 70 | } 71 | 72 | type MultiSelectConfig struct { 73 | Options []MultiSelectOption `json:"options,omitempty"` 74 | } 75 | 76 | // Color can only be one of default, gray, brown, orange, yellow, green, blue, purple, pink, red. 77 | type MultiSelectOption struct { 78 | Name string `json:"name,omitempty"` 79 | ID string `json:"id,omitempty"` 80 | Color string `json:"color,omitempty"` 81 | } 82 | 83 | type FormulaConfig struct { 84 | Expression string `json:"expression,omitempty"` 85 | } 86 | 87 | type RelationConfig struct { 88 | DatabaseID string `json:"database_id,omitempty"` 89 | SyncedPropertyName string `json:"synced_property_name,omitempty"` 90 | SyncedPropertyID string `json:"synced_property_id,omitempty"` 91 | } 92 | 93 | // Function can be one of count_all, count_values, count_unique_values, count_empty, count_not_empty, percent_empty, 94 | // percent_not_empty, sum, average, median, min, max, range 95 | type RollupConfig struct { 96 | RelationPropertyName string `json:"relation_property_name,omitempty"` 97 | RelationPropertyID string `json:"relation_property_id,omitempty"` 98 | RollupPropertyName string `json:"rollup_property_name,omitempty"` 99 | RollupPropertyID string `json:"rollup_property_id,omitempty"` 100 | Function string `json:"function,omitempty"` 101 | } 102 | 103 | // https://developers.notion.com/reference/get-database 104 | func (d *DatabaseService) RetrieveDatabase(databaseID string) (*Database, *http.Response, error) { 105 | database := new(Database) 106 | apiError := new(APIError) 107 | resp, err := d.sling.New().Get(databaseID).Receive(database, apiError) 108 | 109 | return database, resp, relevantError(err, *apiError) 110 | } 111 | 112 | // Filter can be either SingleFilter or CompoundFilter 113 | type QueryDatabaseBodyParams struct { 114 | Filter interface{} `json:"filter,omitempty"` 115 | Sorts []SortCriteria `json:"sorts,omitempty"` 116 | StartCursor string `json:"start_cursor,omitempty"` 117 | PageSize int32 `json:"page_size,omitempty"` 118 | } 119 | 120 | // Timestamp must either be "created_time" or "last_edited_time" 121 | // Direction must either be "ascending" or "descending" 122 | type SortCriteria struct { 123 | Property string `json:"property,omitempty"` 124 | Timestamp string `json:"timestamp,omitempty"` 125 | Direction string `json:"direction,omitempty"` 126 | } 127 | 128 | // OR and AND can either be []SingleFilter or []CompoundFilter (this is only valid once). 129 | type CompoundFilter struct { 130 | OR interface{} `json:"or,omitempty"` 131 | AND interface{} `json:"and,omitempty"` 132 | } 133 | 134 | type SingleFilter struct { 135 | Property string `json:"property,omitempty"` 136 | Title *TextCondition `json:"title,omitempty"` 137 | RichText *TextCondition `json:"rich_text,omitempty"` 138 | URL *TextCondition `json:"url,omitempty"` 139 | Email *TextCondition `json:"email,omitempty"` 140 | Phone *TextCondition `json:"phone,omitempty"` 141 | Number *NumberCondition `json:"number,omitempty"` 142 | Checkbox *CheckboxCondition `json:"checkbox,omitempty"` 143 | Select *SelectCondition `json:"select,omitempty"` 144 | MultiSelect *MultiSelectCondition `json:"multi_select,omitempty"` 145 | CreatedTime *DateCondition `json:"created_time,omitempty"` 146 | LastEditedTime *DateCondition `json:"last_edited_time,omitempty"` 147 | Date *DateCondition `json:"date,omitempty"` 148 | CreatedBy *PeopleCondition `json:"created_by,omitempty"` 149 | LastEditedBy *PeopleCondition `json:"last_edited_by,omitempty"` 150 | Files *FileCondition `json:"files,omitempty"` 151 | Relation *RelationCondition `json:"relation,omitempty"` 152 | Formula *FormulaCondition `json:"formula,omitempty"` 153 | } 154 | 155 | type TextCondition struct { 156 | Equals string `json:"equals,omitempty"` 157 | DoesNotEqual string `json:"does_not_equal,omitempty"` 158 | Contains string `json:"contains,omitempty"` 159 | DoesNotContain string `json:"does_not_contain,omitempty"` 160 | StartsWith string `json:"starts_with,omitempty"` 161 | EndsWith string `json:"ends_with,omitempty"` 162 | IsEmpty bool `json:"is_empty,omitempty"` 163 | isNotEmpty bool `json:"is_not_empty,omitempty"` 164 | } 165 | 166 | type NumberCondition struct { 167 | Equals int32 `json:"equals,omitempty"` 168 | DoesNotEqual int32 `json:"does_not_equal,omitempty"` 169 | GreaterThan int32 `json:greater_than, omitempty` 170 | LessThan int32 `json:"less_than,omitempty"` 171 | GreaterThanOrEqualTo int32 `json:"greater_than_or_equal_to,omitempty"` 172 | LessThanOrEqualTo int32 `json:"less_than_or_equal_to,omitempty"` 173 | IsEmpty bool `json:"is_empty,omitempty"` 174 | isNotEmpty bool `json:"is_not_empty,omitempty"` 175 | } 176 | 177 | type CheckboxCondition struct { 178 | Equals bool `json:"equals,omitempty"` 179 | DoesNotEqual bool `json:"does_not_equal,omitempty"` 180 | } 181 | 182 | type SelectCondition struct { 183 | Equals string `json:"equals,omitempty"` 184 | DoesNotEqual string `json:"does_not_equal,omitempty"` 185 | IsEmpty bool `json:"is_empty,omitempty"` 186 | isNotEmpty bool `json:"is_not_empty,omitempty"` 187 | } 188 | 189 | type MultiSelectCondition struct { 190 | Contains string `json:"contains,omitempty"` 191 | DoesNotContain string `json:"does_not_contain,omitempty"` 192 | IsEmpty bool `json:"is_empty,omitempty"` 193 | isNotEmpty bool `json:"is_not_empty,omitempty"` 194 | } 195 | 196 | type DateCondition struct { 197 | Equals string `json:"equals,omitempty"` 198 | Before string `json:"before,omitempty"` 199 | After string `json:"after,omitempty"` 200 | OnOrBefore string `json:"on_or_before,omitempty"` 201 | IsEmpty bool `json:"is_empty,omitempty"` 202 | IsNotEmpty bool `json:"is_not_empty,omitempty"` 203 | OnOrAfter string `json:"on_or_after,omitempty"` 204 | PastWeek interface{} `json:"past_week,omitempty"` 205 | PastMonth interface{} `json:"past_month,omitempty"` 206 | PastYear interface{} `json:"past_year,omitempty"` 207 | NextWeek interface{} `json:"next_week,omitempty"` 208 | NextMonth interface{} `json:"next_month,omitempty"` 209 | NextYear interface{} `json:"next_year,omitempty"` 210 | } 211 | 212 | type PeopleCondition struct { 213 | Contains string `json:"contains,omitempty"` 214 | DoesNotContain string `json:"does_not_contain,omitempty"` 215 | IsEmpty bool `json:"is_empty,omitempty"` 216 | IsNotEmpty bool `json:"is_not_empty,omitempty"` 217 | } 218 | 219 | type FileCondition struct { 220 | IsEmpty bool `json:"is_empty,omitempty"` 221 | IsNotEmpty bool `json:"is_not_empty,omitempty"` 222 | } 223 | 224 | type RelationCondition struct { 225 | Contains string `json:"contains,omitempty"` 226 | DoesNotContains string `json:"does_not_contains,omitempty"` 227 | IsEmpty string `json:"is_empty,omitempty"` 228 | IsNotEmpty string `json:"is_not_empty,omitempty"` 229 | } 230 | 231 | type FormulaCondition struct { 232 | Text *TextCondition `json:"text,omitempty"` 233 | Checkbox *CheckboxCondition `json:"checkbox,omitempty"` 234 | Number *NumberCondition `json:"number,omitempty"` 235 | Date *DateCondition `json:"date,omitempty"` 236 | } 237 | 238 | type QueryDatabaseResponse struct { 239 | Object string `json:"object,omitempty"` 240 | Results []Page `json:"results,omitempty"` 241 | NextCursor string `json:"next_cursor,omitempty"` 242 | HasMore bool `json:"has_more,omitempty"` 243 | } 244 | 245 | // https://developers.notion.com/reference/post-database-query 246 | func (d *DatabaseService) QueryDatabase(databaseID string, 247 | params *QueryDatabaseBodyParams) (*QueryDatabaseResponse, *http.Response, error) { 248 | 249 | response := new(QueryDatabaseResponse) 250 | apiError := new(APIError) 251 | resp, err := d.sling.New().Post(databaseID+"/query").BodyJSON(params).Receive(response, apiError) 252 | 253 | return response, resp, relevantError(err, *apiError) 254 | } 255 | 256 | type ListDatabasesQueryParams struct { 257 | StartCursor string `url:"start_cursor,omitempty"` 258 | PageSize int32 `url:"page_size,omitempty"` 259 | } 260 | 261 | type ListDatabasesResponse struct { 262 | Results []Database `json:"results,omitempty"` 263 | NextCursor string `json:"next_cursor,omitempty"` 264 | HasMore bool `json:"has_more,omitempty"` 265 | } 266 | 267 | // https://developers.notion.com/reference/get-databases 268 | func (d *DatabaseService) ListDatabases(params *ListDatabasesQueryParams) (*ListDatabasesResponse, *http.Response, error) { 269 | response := new(ListDatabasesResponse) 270 | apiError := new(APIError) 271 | resp, err := d.sling.New().Get("").QueryStruct(params).Receive(response, apiError) 272 | 273 | return response, resp, relevantError(err, *apiError) 274 | } 275 | -------------------------------------------------------------------------------- /notion/version1/databases_test.go: -------------------------------------------------------------------------------- 1 | package version1_test 2 | 3 | import ( 4 | "fmt" 5 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | testDB = ¬ion.Database{ 13 | Object: "database", 14 | ID: "123", 15 | CreatedTime: "2021-05-23T07:41:16.751Z", 16 | LastEditedTime: "2021-05-23T07:41:00.000Z", 17 | Title: []notion.RichText{ 18 | { 19 | PlainText: "TestDB", 20 | Annotations: ¬ion.Annotations{ 21 | Color: "default", 22 | }, 23 | Type: "text", 24 | Text: ¬ion.Text{ 25 | Content: "TestDB", 26 | }, 27 | }, 28 | }, 29 | Properties: map[string]notion.PropertyObj{ 30 | "Name": { 31 | ID: "title", 32 | Type: "title", 33 | Title: map[string]interface{}{}, 34 | }, 35 | }, 36 | } 37 | 38 | testDatabaseResJSON = `{"object":"database","id":"123","created_time":"2021-05-23T07:41:16.751Z","last_edited_time":"2021-05-23T07:41:00.000Z","title":[{"plain_text":"TestDB","annotations":{"color":"default"},"type":"text","text":{"content":"TestDB"}}],"properties":{"Name":{"id":"title","type":"title","title":{}}}}` + "\n" 39 | testDatabaseRes = testDB 40 | 41 | testSingleFilterParamsJSON = `{"filter":{"property":"Tags","multi_select":{"contains":"TagTest"}}}` + "\n" 42 | testSingleFilterParams = ¬ion.QueryDatabaseBodyParams{ 43 | Filter: ¬ion.SingleFilter{ 44 | Property: "Tags", 45 | MultiSelect: ¬ion.MultiSelectCondition{ 46 | Contains: "TagTest", 47 | }, 48 | }, 49 | } 50 | testFilterResJSON = `{"object":"list","results":[{"object":"page","id":"5678","created_time":"2021-05-14T01:06:32.845Z","last_edited_time":"2021-05-23T08:02:00.000Z","parent":{"database_id":"38923","type":"database_id"},"properties":{"Name":{"id":"title","type":"title","title":[{"plain_text":"Jamboree"}]},"Recommended":{"id":"EZMA","type":"checkbox","checkbox":true},"Tags":{"id":"VSvn","type":"multi_select","multi_select":[{"id":"44645","name":"TagTest","color":"purple"}]}}}]}` + "\n" 51 | testFilterRes = ¬ion.QueryDatabaseResponse{ 52 | Object: "list", 53 | Results: []notion.Page{ 54 | { 55 | Object: "page", 56 | ID: "5678", 57 | CreatedTime: "2021-05-14T01:06:32.845Z", 58 | LastEditedTime: "2021-05-23T08:02:00.000Z", 59 | // Technically this is &DatabaseParent but Go will see type as map[string]interface{} 60 | // Haven't found a workaround yet :) 61 | Parent: map[string]interface{}{ 62 | "database_id": "38923", 63 | "type": "database_id", 64 | }, 65 | Properties: map[string]notion.PageProperty{ 66 | "Name": { 67 | ID: "title", 68 | Type: "title", 69 | Title: []notion.RichText{ 70 | { 71 | PlainText: "Jamboree", 72 | }, 73 | }, 74 | }, 75 | "Recommended": { 76 | ID: "EZMA", 77 | Type: "checkbox", 78 | Checkbox: true, 79 | }, 80 | "Tags": { 81 | ID: "VSvn", 82 | Type: "multi_select", 83 | MultiSelect: []notion.MultiSelectPropertyOpts{ 84 | { 85 | ID: "44645", 86 | Name: "TagTest", 87 | Color: "purple", 88 | }, 89 | }, 90 | }, 91 | }, 92 | }, 93 | }, 94 | } 95 | 96 | testCompFilterParamsJSON = `{"filter":{"and":[{"property":"Tags","multi_select":{"contains":"TagTest"}},{"property":"Name","title":{"contains":"Jamboree"}}]}}` + "\n" 97 | testCompFilterParams = ¬ion.QueryDatabaseBodyParams{ 98 | Filter: ¬ion.CompoundFilter{ 99 | AND: []notion.SingleFilter{ 100 | { 101 | Property: "Tags", 102 | MultiSelect: ¬ion.MultiSelectCondition{ 103 | Contains: "TagTest", 104 | }, 105 | }, 106 | { 107 | Property: "Name", 108 | Title: ¬ion.TextCondition{ 109 | Contains: "Jamboree", 110 | }, 111 | }, 112 | }, 113 | }, 114 | } 115 | 116 | testListDBResJSON = `{"results":[{"object":"database","id":"f170fb7c-af53-4173-a0b2-c4cfc8da7789","created_time":"2021-05-23T07:41:16.751Z","last_edited_time":"2021-05-23T08:04:00.000Z","title":[{"plain_text":"DB for Testing"}],"properties":{"Name":{"id":"title","type":"title","title":{}},"Tags":{"id":"M}hZ","type":"multi_select","multi_select":{"options":[{"name":"TagTest","id":"f3bbc99e-f70b-4585-a1a4-f3f895e01104","color":"gray"}]}}}}]}` + "\n" 117 | testListDBRes = ¬ion.ListDatabasesResponse{ 118 | Results: []notion.Database{ 119 | { 120 | Object: "database", 121 | ID: "f170fb7c-af53-4173-a0b2-c4cfc8da7789", 122 | CreatedTime: "2021-05-23T07:41:16.751Z", 123 | LastEditedTime: "2021-05-23T08:04:00.000Z", 124 | Title: []notion.RichText{ 125 | { 126 | PlainText: "DB for Testing", 127 | }, 128 | }, 129 | Properties: map[string]notion.PropertyObj{ 130 | "Name": { 131 | ID: "title", 132 | Type: "title", 133 | Title: map[string]interface{}{}, 134 | }, 135 | "Tags": { 136 | ID: "M}hZ", 137 | Type: "multi_select", 138 | MultiSelect: ¬ion.MultiSelectConfig{ 139 | Options: []notion.MultiSelectOption{ 140 | { 141 | Name: "TagTest", 142 | ID: "f3bbc99e-f70b-4585-a1a4-f3f895e01104", 143 | Color: "gray", 144 | }, 145 | }, 146 | }, 147 | }, 148 | }, 149 | }, 150 | }, 151 | } 152 | ) 153 | 154 | func TestDatabaseService_RetrieveDatabase(t *testing.T) { 155 | httpClient, mux, server := testServer() 156 | defer server.Close() 157 | 158 | mux.HandleFunc("/v1/databases/123", func(w http.ResponseWriter, r *http.Request) { 159 | assertMethod(t, "GET", r) 160 | w.Header().Set("Content-Type", "application/json") 161 | fmt.Fprintf(w, testDatabaseResJSON) 162 | }) 163 | 164 | client := notion.NewClient(httpClient, "0000") 165 | resp, _, err := client.Databases.RetrieveDatabase("123") 166 | assert.Nil(t, err) 167 | assert.Equal(t, testDatabaseRes, resp) 168 | } 169 | 170 | func TestDatabaseService_QueryDatabase_SingleFilter(t *testing.T) { 171 | httpClient, mux, server := testServer() 172 | defer server.Close() 173 | 174 | mux.HandleFunc("/v1/databases/123/query", func(w http.ResponseWriter, r *http.Request) { 175 | assertMethod(t, "POST", r) 176 | assertPostJSON(t, testSingleFilterParamsJSON, r) 177 | w.Header().Set("Content-Type", "application/json") 178 | fmt.Fprintf(w, testFilterResJSON) 179 | }) 180 | 181 | client := notion.NewClient(httpClient, "0000") 182 | resp, _, err := client.Databases.QueryDatabase("123", testSingleFilterParams) 183 | assert.Nil(t, err) 184 | assert.Equal(t, testFilterRes, resp) 185 | 186 | } 187 | 188 | func TestDatabaseService_QueryDatabase_CompoundFilter(t *testing.T) { 189 | httpClient, mux, server := testServer() 190 | defer server.Close() 191 | 192 | mux.HandleFunc("/v1/databases/123/query", func(w http.ResponseWriter, r *http.Request) { 193 | assertMethod(t, "POST", r) 194 | assertPostJSON(t, testCompFilterParamsJSON, r) 195 | w.Header().Set("Content-Type", "application/json") 196 | fmt.Fprintf(w, testFilterResJSON) 197 | }) 198 | 199 | client := notion.NewClient(httpClient, "0000") 200 | resp, _, err := client.Databases.QueryDatabase("123", testCompFilterParams) 201 | assert.Nil(t, err) 202 | assert.Equal(t, testFilterRes, resp) 203 | 204 | } 205 | 206 | func TestDatabaseService_ListDatabases(t *testing.T) { 207 | httpClient, mux, server := testServer() 208 | defer server.Close() 209 | 210 | mux.HandleFunc("/v1/databases/", func(w http.ResponseWriter, r *http.Request) { 211 | assertMethod(t, "GET", r) 212 | w.Header().Set("Content-Type", "application/json") 213 | fmt.Fprintf(w, testListDBResJSON) 214 | }) 215 | 216 | client := notion.NewClient(httpClient, "0000") 217 | resp, _, err := client.Databases.ListDatabases(¬ion.ListDatabasesQueryParams{}) 218 | assert.Nil(t, err) 219 | assert.Equal(t, testListDBRes, resp) 220 | } 221 | -------------------------------------------------------------------------------- /notion/version1/errors.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | import "fmt" 4 | 5 | // APIError represents a Notion API response 6 | // https://developers.notion.com/reference/errors 7 | // Object is always "error" 8 | type APIError struct { 9 | Object string `json:"object,omitempty"` 10 | Status int32 `json:"status,omitempty"` 11 | Code string `json:"code,omitempty"` 12 | Message string `json:"message,omitempty"` 13 | } 14 | 15 | func (e APIError) Error() string { 16 | return fmt.Sprintf("code: %v, message: %v", e.Code, e.Message) 17 | } 18 | 19 | // relevantError returns any http-related error if it exists 20 | // if http-related errors don't exist, it returns apiError if it exists 21 | // else it returns nil 22 | func relevantError(httpError error, apiError APIError) error { 23 | if httpError != nil { 24 | return httpError 25 | } 26 | 27 | if (APIError{}) == apiError { 28 | return nil 29 | } 30 | 31 | return apiError 32 | } 33 | -------------------------------------------------------------------------------- /notion/version1/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/oyekanmiayo/go-notion/notion/version1 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/dghubble/sling v1.3.0 7 | github.com/stretchr/testify v1.4.0 8 | golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c 9 | ) 10 | -------------------------------------------------------------------------------- /notion/version1/notion.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | import ( 4 | "github.com/dghubble/sling" 5 | "net/http" 6 | ) 7 | 8 | const notionAPI = "https://api.notion.com/v1/" 9 | const notionVersion = "2021-05-13" 10 | 11 | type Client struct { 12 | sling *sling.Sling 13 | 14 | // Notion API Services 15 | Users *UserService 16 | Databases *DatabaseService 17 | Pages *PageService 18 | Blocks *BlockService 19 | Search *SearchService 20 | Auth *AuthService 21 | } 22 | 23 | func AuthClient(client *http.Client) *Client { 24 | base := sling.New().Client(client).Base(notionAPI) 25 | 26 | return &Client{ 27 | Auth: newAuthService(base.New()), 28 | } 29 | } 30 | 31 | func NewClient(client *http.Client, accessToken string) *Client { 32 | base := sling.New().Client(client).Base(notionAPI) 33 | base.Add("Authorization", "Bearer "+accessToken) 34 | base.Add("Notion-Version", notionVersion) 35 | 36 | return &Client{ 37 | sling: base, 38 | Users: newUserService(base.New()), 39 | Databases: newDatabaseService(base.New()), 40 | Pages: newPageService(base.New()), 41 | Blocks: newBlockService(base.New()), 42 | Search: newSearchService(base.New()), 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /notion/version1/notion_test.go: -------------------------------------------------------------------------------- 1 | package version1_test 2 | 3 | import ( 4 | "github.com/stretchr/testify/assert" 5 | "io/ioutil" 6 | "net/http" 7 | "net/http/httptest" 8 | "net/url" 9 | "testing" 10 | ) 11 | 12 | // testServer returns an http Client, ServeMux, and Server. The client proxies 13 | // requests to the server and handlers can be registered on the mux to handle 14 | // requests. The caller must close the test server. 15 | func testServer() (*http.Client, *http.ServeMux, *httptest.Server) { 16 | mux := http.NewServeMux() 17 | server := httptest.NewServer(mux) 18 | transport := &RewriteTransport{&http.Transport{ 19 | Proxy: func(req *http.Request) (*url.URL, error) { 20 | return url.Parse(server.URL) 21 | }, 22 | }} 23 | client := &http.Client{Transport: transport} 24 | return client, mux, server 25 | } 26 | 27 | // RewriteTransport rewrites https requests to http to avoid TLS cert issues 28 | // during testing. 29 | type RewriteTransport struct { 30 | Transport http.RoundTripper 31 | } 32 | 33 | // RoundTrip rewrites the request scheme to http and calls through to the 34 | // composed RoundTripper or if it is nil, to the http.DefaultTransport. 35 | func (t *RewriteTransport) RoundTrip(req *http.Request) (*http.Response, error) { 36 | req.URL.Scheme = "http" 37 | if t.Transport == nil { 38 | return http.DefaultTransport.RoundTrip(req) 39 | } 40 | return t.Transport.RoundTrip(req) 41 | } 42 | 43 | func assertMethod(t *testing.T, expectedMethod string, req *http.Request) { 44 | assert.Equal(t, expectedMethod, req.Method) 45 | } 46 | 47 | // assertPostJSON tests that the Request has the expected JSON body. 48 | func assertPostJSON(t *testing.T, expected string, req *http.Request) { 49 | data, err := ioutil.ReadAll(req.Body) 50 | assert.Nil(t, err) 51 | assert.Equal(t, expected, string(data)) 52 | } 53 | -------------------------------------------------------------------------------- /notion/version1/pages.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | import ( 4 | "github.com/dghubble/sling" 5 | "net/http" 6 | ) 7 | 8 | type PageService struct { 9 | sling *sling.Sling 10 | } 11 | 12 | // newPageService returns a new PageService. 13 | func newPageService(sling *sling.Sling) *PageService { 14 | return &PageService{ 15 | sling: sling.Path("pages/"), 16 | } 17 | } 18 | 19 | // https://developers.notion.com/reference/page#all-pages 20 | // Object must always be "page" 21 | // For Properties, 22 | // - If parent.type is "page_id" or "workspace", then the only valid key is title. 23 | // - If parent.type is "database_id", then the keys and values of this field are determined by the properties 24 | // of the database this page belongs to. 25 | // Page can be *DatabaseParent, *PageParent or *WorkspaceParent 26 | type Page struct { 27 | Object string `json:"object,omitempty"` 28 | ID string `json:"id,omitempty"` 29 | CreatedTime string `json:"created_time,omitempty"` 30 | LastEditedTime string `json:"last_edited_time,omitempty"` 31 | Parent interface{} `json:"parent,omitempty"` 32 | Archived bool `json:"archived,omitempty"` 33 | Properties map[string]PageProperty `json:"properties,omitempty"` 34 | } 35 | 36 | // Type is always "database_id" for Database Parent 37 | type DatabaseParent struct { 38 | Type string `json:"type,omitempty"` 39 | DatabaseID string `json:"database_id,omitempty"` 40 | } 41 | 42 | // Type is always "page_id 43 | type PageParent struct { 44 | Type string `json:"type,omitempty"` 45 | PageID string `json:"page_id,omitempty"` 46 | } 47 | 48 | // Type is always "workspace" 49 | type WorkspaceParent struct { 50 | Type string `json:"type,omitempty"` 51 | } 52 | 53 | // Type can be only one of "rich_text", "number", "select", "multi_select", "date", "formula", 54 | // "relation", "rollup", "title", "people", "files", "checkbox", "url", "email", "phone_number", 55 | // "created_time", "created_by", "last_edited_time", and "last_edited_by". 56 | // https://developers.notion.com/reference/page#all-property-values 57 | type PageProperty struct { 58 | ID string `json:"id,omitempty"` 59 | Type string `json:"type,omitempty"` 60 | Title []RichText `json:"title,omitempty"` 61 | RichText []RichText `json:"rich_text,omitempty"` 62 | Number int64 `json:"number,omitempty"` 63 | Select *SelectProperty `json:"select,omitempty"` 64 | URL string `json:"url,omitempty"` 65 | Email string `json:"email,omitempty"` 66 | Phone interface{} `json:"phone,omitempty"` 67 | Checkbox bool `json:"checkbox,omitempty"` 68 | MultiSelect []MultiSelectPropertyOpts `json:"multi_select,omitempty"` 69 | CreatedTime string `json:"created_time,omitempty"` 70 | LastEditedTime string `json:"last_edited_time,omitempty"` 71 | Date *DateProperty `json:"date,omitempty"` 72 | CreatedBy *User `json:"created_by,omitempty"` 73 | LastEditedBy *User `json:"last_edited_by,omitempty"` 74 | Files []FileReferenceProperty `json:"files,omitempty"` 75 | Relation []PageReferenceProperty `json:"relation,omitempty"` 76 | Formula *FormulaProperty `json:"formula,omitempty"` 77 | Rollup *RollupProperty `json:"rollup,omitempty"` 78 | People []User `json:"people,omitempty"` 79 | PhoneNumber string `json:"phone_number,omitempty"` 80 | } 81 | 82 | type NumberProperty struct { 83 | Number int32 `json:"number,omitempty"` 84 | } 85 | 86 | // Color can only be "default", "gray", "brown", "red", "orange", "yellow", "green", 87 | // "blue", "purple" or "pink" 88 | type SelectProperty struct { 89 | ID string `json:"id,omitempty"` 90 | Name string `json:"name,omitempty"` 91 | Color string `json:"color,omitempty"` 92 | } 93 | 94 | // When updating a multi-select property, you can use either name or id. 95 | // Setting a multi-select option by name will only update the page if the multi-select database 96 | // property has an option by that name. 97 | // Color can only be "default", "gray", "brown", "red", "orange", "yellow", "green", 98 | // "blue", "purple" or "pink" 99 | // https://developers.notion.com/reference/page#multi-select-property-values 100 | type MultiSelectPropertyOpts struct { 101 | ID string `json:"id,omitempty"` 102 | Name string `json:"name,omitempty"` 103 | Color string `json:"color,omitempty"` 104 | } 105 | 106 | // If End is not defined, this Date struct isn't considered a range 107 | // https://developers.notion.com/reference/page#date-property-values 108 | type DateProperty struct { 109 | Start string `json:"start,omitempty"` 110 | End string `json:"end,omitempty"` 111 | } 112 | 113 | // Type must be one of "string", "number", "boolean", and "date". 114 | // https://developers.notion.com/reference/page#formula-property-values 115 | // Use *Date for Date 116 | type FormulaProperty struct { 117 | Type string `json:"type"` 118 | String string `json:"string,omitempty"` 119 | Number int32 `json:"number,omitempty"` 120 | Boolean bool `json:"boolean,omitempty"` 121 | Date *DateProperty `json:"date,omitempty"` 122 | } 123 | 124 | // https://developers.notion.com/reference/page#relation-property-values 125 | type PageReferenceProperty struct { 126 | ID string `json:"id,omitempty"` 127 | } 128 | 129 | // https://developers.notion.com/reference/page#rollup-property-values 130 | // Use *DateProperty for Date 131 | // Use for Array 132 | type RollupProperty struct { 133 | Type string `json:"type,omitempty"` 134 | Number int32 `json:"number,omitempty"` 135 | Date *DateProperty `json:"date,omitempty"` 136 | Array []RollupPropertyElement `json:"array,omitempty"` 137 | } 138 | 139 | // https://developers.notion.com/reference/page#rollup-property-value-element 140 | // Type is one of rich_text", "number", "select", "multi_select", "date", "formula", "relation", 141 | // "rollup", "title", "people", "files", "checkbox", "url", "email", "phone_number", "created_time", 142 | // "created_by", "last_edited_time", or "last_edited_by" 143 | type RollupPropertyElement struct { 144 | Type string `json:"type"` 145 | Title []RichText `json:"title,omitempty"` 146 | RichText []RichText `json:"rich_text,omitempty"` 147 | Number int64 `json:"number,omitempty"` 148 | Select *SelectProperty `json:"select,omitempty"` 149 | URL string `json:"url,omitempty"` 150 | Email string `json:"email,omitempty"` 151 | Phone interface{} `json:"phone,omitempty"` 152 | Checkbox bool `json:"checkbox,omitempty"` 153 | MultiSelect []MultiSelectPropertyOpts `json:"multi_select,omitempty"` 154 | CreatedTime string `json:"created_time,omitempty"` 155 | LastEditedTime string `json:"last_edited_time,omitempty"` 156 | Date *DateProperty `json:"date,omitempty"` 157 | CreatedBy *User `json:"created_by,omitempty"` 158 | LastEditedBy *User `json:"last_edited_by,omitempty"` 159 | Files []FileReferenceProperty `json:"files,omitempty"` 160 | Relation interface{} `json:"relation,omitempty"` 161 | Formula *FormulaProperty `json:"formula,omitempty"` 162 | Rollup interface{} `json:"rollup,omitempty"` 163 | People []User `json:"people,omitempty"` 164 | PhoneNumber string `json:"phone_number,omitempty"` 165 | } 166 | 167 | // https://developers.notion.com/reference/page#files-property-values 168 | type FileReferenceProperty struct { 169 | Name string `json:"name,omitempty"` 170 | } 171 | 172 | // https://developers.notion.com/reference/get-page 173 | func (p *PageService) RetrievePage(pageID string) (*Page, *http.Response, error) { 174 | page := new(Page) 175 | apiError := new(APIError) 176 | resp, err := p.sling.New().Get(pageID).Receive(page, apiError) 177 | 178 | return page, resp, relevantError(err, *apiError) 179 | } 180 | 181 | // Use *DatabaseParent or *PageParent for Parent 182 | // Read https://developers.notion.com/reference/post-page before using this 183 | type CreatePageBodyParams struct { 184 | Parent interface{} `json:"parent,omitempty"` 185 | Properties interface{} `json:"properties,omitempty"` 186 | Children []Block `json:"children,omitempty"` 187 | } 188 | 189 | // https://developers.notion.com/reference/post-page 190 | func (p *PageService) CreatePage(params *CreatePageBodyParams) (*Page, *http.Response, error) { 191 | page := new(Page) 192 | apiError := new(APIError) 193 | resp, err := p.sling.New().Post("").BodyJSON(params).Receive(page, apiError) 194 | 195 | return page, resp, relevantError(err, *apiError) 196 | } 197 | 198 | type UpdatePagePropertiesBodyParams struct { 199 | Properties map[string]PageProperty `json:"properties,omitempty"` 200 | } 201 | 202 | // https://developers.notion.com/reference/patch-page 203 | func (p *PageService) UpdatePageProperties(pageID string, 204 | params *UpdatePagePropertiesBodyParams) (*Page, *http.Response, error) { 205 | page := new(Page) 206 | apiError := new(APIError) 207 | resp, err := p.sling.New().Patch(pageID).BodyJSON(params).Receive(page, apiError) 208 | 209 | return page, resp, relevantError(err, *apiError) 210 | } 211 | -------------------------------------------------------------------------------- /notion/version1/pages_test.go: -------------------------------------------------------------------------------- 1 | package version1_test 2 | 3 | import ( 4 | "fmt" 5 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "testing" 9 | ) 10 | 11 | //TODO: Clean up tests. Extract reused names. 12 | var ( 13 | testPageJSON = `{"object":"page","id":"5678","created_time":"2021-05-14T01:06:32.845Z","last_edited_time":"2021-05-23T08:02:00.000Z","parent":{"database_id":"38923","type":"database_id"},"properties":{"Name":{"id":"title","type":"title","title":[{"plain_text":"Jamboree"}]},"Recommended":{"id":"EZMA","type":"checkbox","checkbox":true},"Tags":{"id":"VSvn","type":"multi_select","multi_select":[{"id":"44645","name":"TagTest","color":"purple"}]}}}` + "\n" 14 | testPage = ¬ion.Page{ 15 | Object: "page", 16 | ID: "5678", 17 | CreatedTime: "2021-05-14T01:06:32.845Z", 18 | LastEditedTime: "2021-05-23T08:02:00.000Z", 19 | // Technically this is &DatabaseParent but Go will see type as map[string]interface{} 20 | // Haven't found a workaround yet :) 21 | Parent: map[string]interface{}{ 22 | "database_id": "38923", 23 | "type": "database_id", 24 | }, 25 | Properties: map[string]notion.PageProperty{ 26 | "Name": { 27 | ID: "title", 28 | Type: "title", 29 | Title: []notion.RichText{ 30 | { 31 | PlainText: "Jamboree", 32 | }, 33 | }, 34 | }, 35 | "Recommended": { 36 | ID: "EZMA", 37 | Type: "checkbox", 38 | Checkbox: true, 39 | }, 40 | "Tags": { 41 | ID: "VSvn", 42 | Type: "multi_select", 43 | MultiSelect: []notion.MultiSelectPropertyOpts{ 44 | { 45 | ID: "44645", 46 | Name: "TagTest", 47 | Color: "purple", 48 | }, 49 | }, 50 | }, 51 | }, 52 | } 53 | testRetrievePageResJSON = testPageJSON 54 | testRetrievePageRes = testPage 55 | 56 | testPageProperty = map[string]notion.PageProperty{ 57 | "Name": { 58 | Title: []notion.RichText{ 59 | { 60 | Text: ¬ion.Text{ 61 | Content: "Jamboree", 62 | }, 63 | }, 64 | }, 65 | }, 66 | "Tags": { 67 | MultiSelect: []notion.MultiSelectPropertyOpts{ 68 | { 69 | Name: "TagTest", 70 | }, 71 | }, 72 | }, 73 | "Recommended": { 74 | Checkbox: true, 75 | }, 76 | } 77 | 78 | testCreatePageBodyJSON = `{"parent":{"database_id":"7d6410f1-0c2d-4c75-8199-3fd7d90ff4ff"},"properties":{"Name":{"title":[{"text":{"content":"Jamboree"}}]},"Recommended":{"checkbox":true},"Tags":{"multi_select":[{"name":"TagTest"}]}}}` + "\n" 79 | testCreatePageBody = ¬ion.CreatePageBodyParams{ 80 | Parent: ¬ion.DatabaseParent{ 81 | DatabaseID: "7d6410f1-0c2d-4c75-8199-3fd7d90ff4ff", 82 | }, 83 | Properties: testPageProperty, 84 | } 85 | testCreatePageResJSON = testPageJSON 86 | testCreatePageRes = testPage 87 | 88 | testUpdatePropertiesBodyJSON = `{"properties":{"Name":{"title":[{"text":{"content":"Jamboree"}}]},"Recommended":{"checkbox":true},"Tags":{"multi_select":[{"name":"TagTest"}]}}}` + "\n" 89 | testUpdatePropertiesBody = ¬ion.UpdatePagePropertiesBodyParams{ 90 | Properties: testPageProperty, 91 | } 92 | testUpdatePropertiesResJSON = testPageJSON 93 | testUpdatePropertiesRes = testPage 94 | ) 95 | 96 | func TestPageService_RetrievePage(t *testing.T) { 97 | httpClient, mux, server := testServer() 98 | defer server.Close() 99 | 100 | mux.HandleFunc("/v1/pages/123", func(w http.ResponseWriter, r *http.Request) { 101 | assertMethod(t, "GET", r) 102 | w.Header().Set("Content-Type", "application/json") 103 | fmt.Fprintf(w, testRetrievePageResJSON) 104 | }) 105 | 106 | client := notion.NewClient(httpClient, "0000") 107 | resp, _, err := client.Pages.RetrievePage("123") 108 | assert.Nil(t, err) 109 | assert.Equal(t, testRetrievePageRes, resp) 110 | } 111 | 112 | func TestPageService_CreatePage(t *testing.T) { 113 | httpClient, mux, server := testServer() 114 | defer server.Close() 115 | 116 | mux.HandleFunc("/v1/pages/", func(w http.ResponseWriter, r *http.Request) { 117 | assertMethod(t, "POST", r) 118 | assertPostJSON(t, testCreatePageBodyJSON, r) 119 | w.Header().Set("Content-Type", "application/json") 120 | fmt.Fprintf(w, testCreatePageResJSON) 121 | }) 122 | 123 | client := notion.NewClient(httpClient, "0000") 124 | resp, _, err := client.Pages.CreatePage(testCreatePageBody) 125 | assert.Nil(t, err) 126 | assert.Equal(t, testCreatePageRes, resp) 127 | } 128 | 129 | func TestPageService_UpdatePageProperties(t *testing.T) { 130 | httpClient, mux, server := testServer() 131 | defer server.Close() 132 | 133 | mux.HandleFunc("/v1/pages/123", func(w http.ResponseWriter, r *http.Request) { 134 | assertMethod(t, "PATCH", r) 135 | assertPostJSON(t, testUpdatePropertiesBodyJSON, r) 136 | w.Header().Set("Content-Type", "application/json") 137 | fmt.Fprintf(w, testUpdatePropertiesResJSON) 138 | }) 139 | 140 | client := notion.NewClient(httpClient, "0000") 141 | resp, _, err := client.Pages.UpdatePageProperties("123", testUpdatePropertiesBody) 142 | assert.Nil(t, err) 143 | assert.Equal(t, testUpdatePropertiesRes, resp) 144 | } 145 | -------------------------------------------------------------------------------- /notion/version1/search.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | import ( 4 | "github.com/dghubble/sling" 5 | "net/http" 6 | ) 7 | 8 | type SearchService struct { 9 | sling *sling.Sling 10 | } 11 | 12 | // newSearchService returns a new SearchService. 13 | func newSearchService(sling *sling.Sling) *SearchService { 14 | return &SearchService{ 15 | sling: sling.Path("search/"), 16 | } 17 | } 18 | 19 | // From my tests it looks like the api checks if page titles contain the query 20 | type SearchBodyParams struct { 21 | Query string `json:"query,omitempty"` 22 | Sort *Sort `json:"sort,omitempty"` 23 | Filter *SearchFilter `json:"filter,omitempty"` 24 | StartCursor string `json:"start_cursor,omitempty"` 25 | PageSize int32 `json:"page_size,omitempty"` 26 | } 27 | 28 | // Direction is either "ascending" or "descending" 29 | // Timestamp is always "last_edited_time" 30 | type Sort struct { 31 | Direction string `json:"direction,omitempty"` 32 | Timestamp string `json:"timestamp,omitempty"` 33 | } 34 | 35 | // Value is either "page" or "database" 36 | // Property can only be "object" 37 | type SearchFilter struct { 38 | Value string `json:"value,omitempty"` 39 | Property string `json:"property,omitempty"` 40 | } 41 | 42 | // TODO: Find a way to unify responses and make them accessible. 43 | // Use json.RawMessage & https://play.golang.org/p/e6kvxtOeTCc 44 | // One approach: Unmarshal into a random struct to get the type of 45 | // each entry, and use that to Unnmarshal into the right struct. 46 | 47 | type SearchPageResponse struct { 48 | Object string `json:"object,omitempty"` 49 | Results []Page `json:"results,omitempty"` 50 | NextCursor string `json:"next_cursor,omitempty"` 51 | HasMore bool `json:"has_more,omitempty"` 52 | } 53 | 54 | func (s *SearchService) SearchPage(params *SearchBodyParams) (*SearchPageResponse, *http.Response, error) { 55 | sResponse := new(SearchPageResponse) 56 | apiError := new(APIError) 57 | 58 | params.Filter = &SearchFilter{ 59 | Property: "object", 60 | Value: "page", 61 | } 62 | 63 | httpResponse, httpError := s.sling.New().Post("").BodyJSON(params).Receive(sResponse, apiError) 64 | 65 | return sResponse, httpResponse, relevantError(httpError, *apiError) 66 | } 67 | 68 | type SearchDatabaseResponse struct { 69 | Object string `json:"object,omitempty"` 70 | Results []Database `json:"results,omitempty"` 71 | NextCursor string `json:"next_cursor,omitempty"` 72 | HasMore bool `json:"has_more,omitempty"` 73 | } 74 | 75 | func (s *SearchService) SearchDatabase(params *SearchBodyParams) (*SearchDatabaseResponse, *http.Response, error) { 76 | sResponse := new(SearchDatabaseResponse) 77 | apiError := new(APIError) 78 | 79 | params.Filter = &SearchFilter{ 80 | Property: "object", 81 | Value: "database", 82 | } 83 | httpResponse, httpError := s.sling.New().Post("").BodyJSON(params).Receive(sResponse, apiError) 84 | 85 | return sResponse, httpResponse, relevantError(httpError, *apiError) 86 | } 87 | -------------------------------------------------------------------------------- /notion/version1/search_test.go: -------------------------------------------------------------------------------- 1 | package version1_test 2 | 3 | import ( 4 | "fmt" 5 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | testSearchPageBodyJSON = `{"query":"Jamboree","sort":{"direction":"descending","timestamp":"last_edited_time"},"filter":{"value":"page","property":"object"}}` + "\n" 13 | testSearchBody = ¬ion.SearchBodyParams{ 14 | Query: "Jamboree", 15 | Sort: ¬ion.Sort{ 16 | Direction: "descending", 17 | Timestamp: "last_edited_time", 18 | }, 19 | } 20 | testSearchPageResJSON = `{"object":"list","results":[{"object":"page","id":"5678","created_time":"2021-05-14T01:06:32.845Z","last_edited_time":"2021-05-23T08:02:00.000Z","parent":{"database_id":"38923","type":"database_id"},"properties":{"Name":{"id":"title","type":"title","title":[{"plain_text":"Jamboree"}]},"Recommended":{"id":"EZMA","type":"checkbox","checkbox":true},"Tags":{"id":"VSvn","type":"multi_select","multi_select":[{"id":"44645","name":"TagTest","color":"purple"}]}}}]}` + "\n" 21 | testSearchPageRes = ¬ion.SearchPageResponse{ 22 | Object: "list", 23 | Results: []notion.Page{ 24 | *testPage, 25 | }, 26 | } 27 | 28 | testSearchDBBodyJSON = `{"query":"Jamboree","sort":{"direction":"descending","timestamp":"last_edited_time"},"filter":{"value":"database","property":"object"}}` + "\n" 29 | testSearchDBResJSON = `{"object":"list","results":[{"object":"database","id":"123","created_time":"2021-05-23T07:41:16.751Z","last_edited_time":"2021-05-23T07:41:00.000Z","title":[{"type":"text","text":{"content":"Jamboree"}}],"properties":{"Name":{"id":"title","type":"title","title":{}}}}]}` + "\n" 30 | testSearchDBRes = ¬ion.SearchDatabaseResponse{ 31 | Object: "list", 32 | Results: []notion.Database{ 33 | { 34 | Object: "database", 35 | ID: "123", 36 | CreatedTime: "2021-05-23T07:41:16.751Z", 37 | LastEditedTime: "2021-05-23T07:41:00.000Z", 38 | Title: []notion.RichText{ 39 | { 40 | Type: "text", 41 | Text: ¬ion.Text{ 42 | Content: "Jamboree", 43 | }, 44 | }, 45 | }, 46 | Properties: map[string]notion.PropertyObj{ 47 | "Name": { 48 | ID: "title", 49 | Type: "title", 50 | Title: map[string]interface{}{}, 51 | }, 52 | }, 53 | }, 54 | }, 55 | } 56 | ) 57 | 58 | func TestSearchService_SearchPage(t *testing.T) { 59 | httpClient, mux, server := testServer() 60 | defer server.Close() 61 | 62 | mux.HandleFunc("/v1/search/", func(w http.ResponseWriter, r *http.Request) { 63 | assertMethod(t, "POST", r) 64 | assertPostJSON(t, testSearchPageBodyJSON, r) 65 | 66 | w.Header().Set("Content-Type", "application/json") 67 | fmt.Fprintf(w, testSearchPageResJSON) 68 | }) 69 | 70 | client := notion.NewClient(httpClient, "0000") 71 | resp, _, err := client.Search.SearchPage(testSearchBody) 72 | assert.Nil(t, err) 73 | assert.Equal(t, testSearchPageRes, resp) 74 | } 75 | 76 | func TestSearchService_SearchDatabase(t *testing.T) { 77 | httpClient, mux, server := testServer() 78 | defer server.Close() 79 | 80 | mux.HandleFunc("/v1/search/", func(w http.ResponseWriter, r *http.Request) { 81 | assertMethod(t, "POST", r) 82 | assertPostJSON(t, testSearchDBBodyJSON, r) 83 | 84 | w.Header().Set("Content-Type", "application/json") 85 | fmt.Fprintf(w, testSearchDBResJSON) 86 | }) 87 | 88 | client := notion.NewClient(httpClient, "0000") 89 | resp, _, err := client.Search.SearchDatabase(testSearchBody) 90 | assert.Nil(t, err) 91 | assert.Equal(t, testSearchDBRes, resp) 92 | } 93 | -------------------------------------------------------------------------------- /notion/version1/users.go: -------------------------------------------------------------------------------- 1 | package version1 2 | 3 | import ( 4 | "github.com/dghubble/sling" 5 | "net/http" 6 | ) 7 | 8 | type UserService struct { 9 | sling *sling.Sling 10 | } 11 | 12 | // newUserService returns a new UserService. 13 | func newUserService(sling *sling.Sling) *UserService { 14 | return &UserService{ 15 | sling: sling.Path("users/"), 16 | } 17 | } 18 | 19 | // https://developers.notion.com/reference/user 20 | // Updateable properties: ID 21 | // Display-only properties: Object, Type, Name, AvatarURL 22 | // Object is always "user" and it 23 | // Type is either "person" or "bot" 24 | type User struct { 25 | Object string `json:"object,omitempty"` 26 | ID string `json:"id,omitempty"` 27 | Type string `json:"type,omitempty"` 28 | Name string `json:"name,omitempty"` 29 | AvatarURL string `json:"avatar_url,omitempty"` 30 | Person *Person `json:"person,omitempty"` 31 | Bot interface{} `json:"bot,omitempty"` 32 | } 33 | 34 | // https://developers.notion.com/reference/user#people 35 | // Display-only properties: Person, PersonEmail 36 | type Person struct { 37 | PersonEmail string `json:"email,omitempty"` 38 | } 39 | 40 | func (u *UserService) RetrieveUser(userID string) (*User, *http.Response, error) { 41 | user := new(User) 42 | apiError := new(APIError) 43 | resp, err := u.sling.New().Get(userID).Receive(user, apiError) 44 | 45 | return user, resp, relevantError(err, *apiError) 46 | } 47 | 48 | type ListUsersQueryParams struct { 49 | StartCursor string `url:"start_cursor,omitempty"` 50 | PageSize int32 `url:"page_size,omitempty"` 51 | } 52 | 53 | type ListUsersResponse struct { 54 | Object string `json:"object,omitempty"` 55 | Results []User `json:"results"` 56 | NextCursor string `json:"next_cursor"` 57 | HasMore bool `json:"has_more"` 58 | } 59 | 60 | // https://developers.notion.com/reference/get-users 61 | // See https://developers.notion.com/reference/pagination to understand 62 | // how to iterate through paginated responses 63 | func (u *UserService) ListUsers(params *ListUsersQueryParams) (*ListUsersResponse, *http.Response, error) { 64 | response := new(ListUsersResponse) 65 | apiError := new(APIError) 66 | resp, err := u.sling.New().Get("").QueryStruct(params).Receive(response, apiError) 67 | 68 | return response, resp, relevantError(err, *apiError) 69 | } 70 | -------------------------------------------------------------------------------- /notion/version1/users_test.go: -------------------------------------------------------------------------------- 1 | package version1_test 2 | 3 | import ( 4 | "fmt" 5 | notion "github.com/oyekanmiayo/go-notion/notion/version1" 6 | "github.com/stretchr/testify/assert" 7 | "net/http" 8 | "testing" 9 | ) 10 | 11 | var ( 12 | testUserJSON = `{"object":"user","id":"123abc","type":"person","name":"John Doe","avatar_url":"https://test.com/test","person":{"email":"test@gmail.com"}}` + "\n" 13 | testUser = ¬ion.User{ 14 | Object: "user", 15 | ID: "123abc", 16 | Type: "person", 17 | Name: "John Doe", 18 | AvatarURL: "https://test.com/test", 19 | Person: ¬ion.Person{ 20 | PersonEmail: "test@gmail.com", 21 | }, 22 | } 23 | 24 | testBot = ¬ion.User{ 25 | Object: "user", 26 | ID: "456def", 27 | Type: "bot", 28 | Name: "Test Integration", 29 | Bot: map[string]interface{}{}, 30 | } 31 | 32 | testListUsersJSON = `{"object":"list","results":[{"object":"user","id":"123abc","type":"person","name":"John Doe","avatar_url":"https://test.com/test","person":{"email":"test@gmail.com"}},{"object":"user","id":"456def","type":"bot","name":"Test Integration","bot":{}}]}` + "\n" 33 | testListUsers = ¬ion.ListUsersResponse{ 34 | Object: "list", 35 | Results: []notion.User{ 36 | *testUser, 37 | *testBot, 38 | }, 39 | } 40 | ) 41 | 42 | func TestUserService_RetrieveUser(t *testing.T) { 43 | httpClient, mux, server := testServer() 44 | defer server.Close() 45 | 46 | mux.HandleFunc("/v1/users/123", func(w http.ResponseWriter, r *http.Request) { 47 | assertMethod(t, "GET", r) 48 | w.Header().Set("Content-Type", "application/json") 49 | fmt.Fprintf(w, testUserJSON) 50 | }) 51 | 52 | client := notion.NewClient(httpClient, "0000") 53 | resp, _, err := client.Users.RetrieveUser("123") 54 | assert.Nil(t, err) 55 | assert.Equal(t, testUser, resp) 56 | } 57 | 58 | func TestUserService_ListUsers(t *testing.T) { 59 | httpClient, mux, server := testServer() 60 | defer server.Close() 61 | 62 | mux.HandleFunc("/v1/users/", func(w http.ResponseWriter, r *http.Request) { 63 | assertMethod(t, "GET", r) 64 | w.Header().Set("Content-Type", "application/json") 65 | fmt.Fprintf(w, testListUsersJSON) 66 | }) 67 | 68 | client := notion.NewClient(httpClient, "0000") 69 | resp, _, err := client.Users.ListUsers(¬ion.ListUsersQueryParams{}) 70 | assert.Nil(t, err) 71 | assert.Equal(t, testListUsers, resp) 72 | } 73 | --------------------------------------------------------------------------------