diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..331a95d5 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file + +version: 2 +updates: + - package-ecosystem: "nuget" # See documentation for possible values + directory: "/" # Location of package manifests + schedule: + interval: "weekly" + open-pull-requests-limit: 10 + commit-message: + prefix: "deps" diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..f42ffd37 --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,16 @@ +categories: + - title: '🚀 Features' + labels: + - 'feature' + - 'enhancement' + - title: '🐛 Bug Fixes' + labels: + - 'fix' + - 'bugfix' + - 'bug' + - title: '🧰 Maintenance' + label: 'chore' +template: | + ## Changes + + $CHANGES \ No newline at end of file diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 00000000..55a63c2c --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,23 @@ +name: Greetings + +on: [pull_request_target, issues] + +jobs: + greet-first-time-contributors: + name: Greet First-Time Contributors + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + + steps: + - name: Send greeting to first-time contributors + uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: > + 👋 Hi there! Thanks for opening your first issue in this repository. + We appreciate your contribution. A maintainer will take a look soon. + pr-message: > + 🎉 Thanks for your first pull request! + The team will review it shortly. We’re excited to have you contribute! diff --git a/.github/workflows/publish-code.yml b/.github/workflows/publish-code.yml index ef76a197..2073a733 100644 --- a/.github/workflows/publish-code.yml +++ b/.github/workflows/publish-code.yml @@ -36,7 +36,7 @@ jobs: dotnet pack --no-restore --no-build -o PackOutputs -c Release -p:Version=${{ github.event.release.tag_name }} -p:PackageReleaseNotes="See https://github.com/notion-dotnet/notion-sdk-net/releases/tag/${{ github.event.release.tag_name }}" -p:PackageReadmeFile=README.md - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Notion.Net path: PackOutputs/* @@ -45,7 +45,7 @@ jobs: run: dotnet nuget push PackOutputs/*.nupkg --api-key ${{ secrets.NUGET_API_KEY }} - name: Upload Nuget packages as release artifacts - uses: actions/github-script@v2 + uses: actions/github-script@v7 with: github-token: ${{secrets.GITHUB_TOKEN}} script: | diff --git a/.github/workflows/release-drafter.yml b/.github/workflows/release-drafter.yml new file mode 100644 index 00000000..4eed5a56 --- /dev/null +++ b/.github/workflows/release-drafter.yml @@ -0,0 +1,41 @@ +name: Release Drafter + +on: + push: + # branches to consider in the event; optional, defaults to all + branches: + - main + # pull_request event is required only for autolabeler + pull_request: + # Only following types are handled by the action, but one can default to all as well + types: [opened, reopened, synchronize] + # pull_request_target event is required for autolabeler to support PRs from forks + pull_request_target: + types: [opened, reopened, synchronize] + +permissions: + contents: read + +jobs: + update_release_draft: + permissions: + # write permission is required to create a github release + contents: write + # write permission is required for autolabeler + # otherwise, read permission is required at least + pull-requests: write + runs-on: ubuntu-latest + steps: + # (Optional) GitHub Enterprise requires GHE_HOST variable set + #- name: Set GHE_HOST + # run: | + # echo "GHE_HOST=${GITHUB_SERVER_URL##https:\/\/}" >> $GITHUB_ENV + + # Drafts your next Release notes as Pull Requests are merged into "master" + - uses: release-drafter/release-drafter@v5.21.1 + # (Optional) specify config name to use, relative to .github/. Default: release-drafter.yml + # with: + # config-name: my-config.yml + # disable-autolabeler: true + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/test-publish-code.yml b/.github/workflows/test-publish-code.yml index e90afd73..ce6974e6 100644 --- a/.github/workflows/test-publish-code.yml +++ b/.github/workflows/test-publish-code.yml @@ -45,7 +45,7 @@ jobs: ls -l PackOutputs/ - name: Upload artifacts - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: Notion.Net path: PackOutputs/* diff --git a/.gitignore b/.gitignore index c6d337c2..5072687a 100644 --- a/.gitignore +++ b/.gitignore @@ -354,3 +354,5 @@ MigrationBackup/ # Rider .idea/ + +.DS_Store diff --git a/README.md b/README.md index 5ab47338..d74a4cc6 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,6 @@ [![Publish Code](https://github.com/notion-dotnet/notion-sdk-net/actions/workflows/publish-code.yml/badge.svg)](https://github.com/notion-dotnet/notion-sdk-net/actions/workflows/publish-code.yml) [![CodeQL](https://github.com/notion-dotnet/notion-sdk-net/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/notion-dotnet/notion-sdk-net/actions/workflows/codeql-analysis.yml) -[![LGTM Alerts](https://img.shields.io/lgtm/alerts/github/notion-dotnet/notion-sdk-net)](https://lgtm.com/projects/g/notion-dotnet/notion-sdk-net/alerts/?mode=list) -[![LGTM Grade](https://img.shields.io/lgtm/grade/csharp/github/notion-dotnet/notion-sdk-net)](https://lgtm.com/projects/g/notion-dotnet/notion-sdk-net/alerts/?mode=list) - [![GitHub last commit](https://img.shields.io/github/last-commit/notion-dotnet/notion-sdk-net)]() [![GitHub commit activity](https://img.shields.io/github/commit-activity/w/notion-dotnet/notion-sdk-net)]() [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/notion-dotnet/notion-sdk-net)]() diff --git a/Src/Notion.Client/Api/ApiEndpoints.cs b/Src/Notion.Client/Api/ApiEndpoints.cs index a6434683..86d703b3 100644 --- a/Src/Notion.Client/Api/ApiEndpoints.cs +++ b/Src/Notion.Client/Api/ApiEndpoints.cs @@ -131,5 +131,11 @@ public static string Create() return "/v1/comments"; } } + + public static class AuthenticationUrls + { + public static string CreateToken() => "/v1/oauth/token"; + public static string RevokeToken() => "/v1/oauth/revoke"; + } } } diff --git a/Src/Notion.Client/Api/Authentication/AuthenticationClient.cs b/Src/Notion.Client/Api/Authentication/AuthenticationClient.cs new file mode 100644 index 00000000..ab141200 --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/AuthenticationClient.cs @@ -0,0 +1,12 @@ +namespace Notion.Client +{ + public sealed partial class AuthenticationClient : IAuthenticationClient + { + private readonly IRestClient _client; + + public AuthenticationClient(IRestClient restClient) + { + _client = restClient; + } + } +} diff --git a/Src/Notion.Client/Api/Authentication/CreateToken/AuthenticationClient.cs b/Src/Notion.Client/Api/Authentication/CreateToken/AuthenticationClient.cs new file mode 100644 index 00000000..23632632 --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/CreateToken/AuthenticationClient.cs @@ -0,0 +1,21 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Notion.Client +{ + public sealed partial class AuthenticationClient + { + public async Task CreateTokenAsync( + CreateTokenRequest createTokenRequest, + CancellationToken cancellationToken = default) + { + var body = (ICreateTokenBodyParameters)createTokenRequest; + + return await _client.PostAsync( + ApiEndpoints.AuthenticationUrls.CreateToken(), + body, + cancellationToken: cancellationToken + ); + } + } +} diff --git a/Src/Notion.Client/Api/Authentication/CreateToken/Request/CreateTokenRequest.cs b/Src/Notion.Client/Api/Authentication/CreateToken/Request/CreateTokenRequest.cs new file mode 100644 index 00000000..3fe8ffde --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/CreateToken/Request/CreateTokenRequest.cs @@ -0,0 +1,13 @@ +namespace Notion.Client +{ + public class CreateTokenRequest : ICreateTokenBodyParameters + { + public string GrantType => "authorization_code"; + + public string Code { get; set; } + + public string RedirectUri { get; set; } + + public ExternalAccount ExternalAccount { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Authentication/CreateToken/Request/ExternalAccount.cs b/Src/Notion.Client/Api/Authentication/CreateToken/Request/ExternalAccount.cs new file mode 100644 index 00000000..432d709f --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/CreateToken/Request/ExternalAccount.cs @@ -0,0 +1,22 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + /// + /// External account info + /// + public class ExternalAccount + { + /// + /// External account key + /// + [JsonProperty("key")] + public string Key { get; set; } + + /// + /// External account name + /// + [JsonProperty("name")] + public string Name { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Authentication/CreateToken/Request/ICreateTokenBodyParameters.cs b/Src/Notion.Client/Api/Authentication/CreateToken/Request/ICreateTokenBodyParameters.cs new file mode 100644 index 00000000..e896e71a --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/CreateToken/Request/ICreateTokenBodyParameters.cs @@ -0,0 +1,35 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public interface ICreateTokenBodyParameters + { + /// + /// A constant string: "authorization_code". + /// + [JsonProperty("grant_type")] + string GrantType { get; } + + /// + /// A unique random code that Notion generates to authenticate with your service, + /// generated when a user initiates the OAuth flow. + /// + [JsonProperty("code")] + string Code { get; set; } + + /// + /// The "redirect_uri" that was provided in the OAuth Domain & URI section + /// of the integration's Authorization settings. Do not include this field if a + /// "redirect_uri" query param was not included in the Authorization URL + /// provided to users. In most cases, this field is required. + /// + [JsonProperty("redirect_uri")] + string RedirectUri { get; set; } + + /// + /// External account details + /// + [JsonProperty("external_account")] + ExternalAccount ExternalAccount { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Authentication/CreateToken/Response/CreateTokenResponse.cs b/Src/Notion.Client/Api/Authentication/CreateToken/Response/CreateTokenResponse.cs new file mode 100644 index 00000000..fbbe7a7a --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/CreateToken/Response/CreateTokenResponse.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class CreateTokenResponse + { + [JsonProperty("access_token")] + public string AccessToken { get; set; } + + [JsonProperty("token_type")] + public string TokenType { get; set; } = "bearer"; + + [JsonProperty("bot_id")] + public string BotId { get; set; } + + [JsonProperty("duplicated_template_id")] + public string DuplicatedTemplateId { get; set; } + + [JsonProperty("owner")] + public IBotOwner Owner { get; set; } + + [JsonProperty("workspace_icon")] + public string WorkspaceIcon { get; set; } + + [JsonProperty("workspace_id")] + public string WorkspaceId { get; set; } + + [JsonProperty("workspace_name")] + public string WorkspaceName { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Authentication/IAuthenticationClient.cs b/Src/Notion.Client/Api/Authentication/IAuthenticationClient.cs new file mode 100644 index 00000000..1973638f --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/IAuthenticationClient.cs @@ -0,0 +1,33 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Notion.Client +{ + /// + /// Authentication client + /// + public interface IAuthenticationClient + { + /// + /// Creates an access token that a third-party service can use to authenticate with Notion. + /// + /// + /// + /// + Task CreateTokenAsync( + CreateTokenRequest createTokenRequest, + CancellationToken cancellationToken = default + ); + + /// + /// Revokes an access token. + /// + /// + /// + /// + Task RevokeTokenAsync( + RevokeTokenRequest revokeTokenRequest, + CancellationToken cancellationToken = default + ); + } +} diff --git a/Src/Notion.Client/Api/Authentication/RevokeToken/AuthenticationClient.cs b/Src/Notion.Client/Api/Authentication/RevokeToken/AuthenticationClient.cs new file mode 100644 index 00000000..c1f3ab9d --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/RevokeToken/AuthenticationClient.cs @@ -0,0 +1,21 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Notion.Client +{ + public sealed partial class AuthenticationClient + { + public async Task RevokeTokenAsync( + RevokeTokenRequest revokeTokenRequest, + CancellationToken cancellationToken = default) + { + var body = (IRevokeTokenBodyParameters)revokeTokenRequest; + + await _client.PostAsync( + ApiEndpoints.AuthenticationUrls.RevokeToken(), + body, + cancellationToken: cancellationToken + ); + } + } +} diff --git a/Src/Notion.Client/Api/Authentication/RevokeToken/Request/IRevokeTokenBodyParameters.cs b/Src/Notion.Client/Api/Authentication/RevokeToken/Request/IRevokeTokenBodyParameters.cs new file mode 100644 index 00000000..0a194516 --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/RevokeToken/Request/IRevokeTokenBodyParameters.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public interface IRevokeTokenBodyParameters + { + /// + /// The token to be revoked. + /// + [JsonProperty("token")] + string Token { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Authentication/RevokeToken/Request/RevokeTokenRequest.cs b/Src/Notion.Client/Api/Authentication/RevokeToken/Request/RevokeTokenRequest.cs new file mode 100644 index 00000000..7e92ca01 --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/RevokeToken/Request/RevokeTokenRequest.cs @@ -0,0 +1,7 @@ +namespace Notion.Client +{ + public class RevokeTokenRequest : IRevokeTokenBodyParameters + { + public string Token { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Authentication/RevokeToken/Response/RevokeTokenResponse.cs b/Src/Notion.Client/Api/Authentication/RevokeToken/Response/RevokeTokenResponse.cs new file mode 100644 index 00000000..428dd287 --- /dev/null +++ b/Src/Notion.Client/Api/Authentication/RevokeToken/Response/RevokeTokenResponse.cs @@ -0,0 +1,6 @@ +namespace Notion.Client +{ + internal class RevokeTokenResponse + { + } +} diff --git a/Src/Notion.Client/Api/Blocks/AppendChildren/BlocksClient.cs b/Src/Notion.Client/Api/Blocks/AppendChildren/BlocksClient.cs new file mode 100644 index 00000000..50ce3c61 --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/AppendChildren/BlocksClient.cs @@ -0,0 +1,25 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Notion.Client +{ + public sealed partial class BlocksClient + { + public async Task AppendChildrenAsync( + BlockAppendChildrenRequest request, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(request.BlockId)) + { + throw new ArgumentNullException(nameof(request.BlockId)); + } + + var url = ApiEndpoints.BlocksApiUrls.AppendChildren(request.BlockId); + + var body = new BlockAppendChildrenBodyParameters(request); + + return await _client.PatchAsync(url, body, cancellationToken: cancellationToken); + } + } +} diff --git a/Src/Notion.Client/Api/Blocks/AppendChildren/Request/BlockAppendChildrenRequest.cs b/Src/Notion.Client/Api/Blocks/AppendChildren/Request/BlockAppendChildrenRequest.cs new file mode 100644 index 00000000..8a5a9cde --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/AppendChildren/Request/BlockAppendChildrenRequest.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace Notion.Client +{ + public class BlockAppendChildrenRequest : IBlockAppendChildrenBodyParameters, IBlockAppendChildrenPathParameters + { + public IEnumerable Children { get; set; } + + public string After { get; set; } + + public string BlockId { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Blocks/AppendChildren/Request/IBlockAppendChildrenBodyParameters.cs b/Src/Notion.Client/Api/Blocks/AppendChildren/Request/IBlockAppendChildrenBodyParameters.cs new file mode 100644 index 00000000..ea6cef37 --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/AppendChildren/Request/IBlockAppendChildrenBodyParameters.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public interface IBlockAppendChildrenBodyParameters + { + [JsonProperty("children")] + IEnumerable Children { get; set; } + + /// + /// The ID of the existing block that the new block should be appended after. + /// + [JsonProperty("after")] + public string After { get; set; } + } + + internal class BlockAppendChildrenBodyParameters : IBlockAppendChildrenBodyParameters + { + public IEnumerable Children { get; set; } + + public string After { get; set; } + + public BlockAppendChildrenBodyParameters(BlockAppendChildrenRequest request) + { + Children = request.Children; + After = request.After; + } + } +} diff --git a/Src/Notion.Client/Api/Blocks/AppendChildren/Request/IBlockAppendChildrenPathParameters.cs b/Src/Notion.Client/Api/Blocks/AppendChildren/Request/IBlockAppendChildrenPathParameters.cs new file mode 100644 index 00000000..350382f6 --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/AppendChildren/Request/IBlockAppendChildrenPathParameters.cs @@ -0,0 +1,7 @@ +namespace Notion.Client +{ + public interface IBlockAppendChildrenPathParameters + { + public string BlockId { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Blocks/AppendChildren/Response/AppendChildrenResponse.cs b/Src/Notion.Client/Api/Blocks/AppendChildren/Response/AppendChildrenResponse.cs new file mode 100644 index 00000000..88e04ad5 --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/AppendChildren/Response/AppendChildrenResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class AppendChildrenResponse : PaginatedList + { + [JsonProperty("block")] + public Dictionary Block { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Blocks/BlocksClient.cs b/Src/Notion.Client/Api/Blocks/BlocksClient.cs index 1282a346..92713a78 100644 --- a/Src/Notion.Client/Api/Blocks/BlocksClient.cs +++ b/Src/Notion.Client/Api/Blocks/BlocksClient.cs @@ -1,11 +1,12 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using static Notion.Client.ApiEndpoints; namespace Notion.Client { - public class BlocksClient : IBlocksClient + public sealed partial class BlocksClient : IBlocksClient { private readonly IRestClient _client; @@ -14,45 +15,7 @@ public BlocksClient(IRestClient client) _client = client; } - public async Task> RetrieveChildrenAsync( - string blockId, - BlocksRetrieveChildrenParameters parameters = null) - { - if (string.IsNullOrWhiteSpace(blockId)) - { - throw new ArgumentNullException(nameof(blockId)); - } - - var url = BlocksApiUrls.RetrieveChildren(blockId); - - var queryParameters = (IBlocksRetrieveChildrenQueryParameters)parameters; - - var queryParams = new Dictionary - { - { "start_cursor", queryParameters?.StartCursor }, - { "page_size", queryParameters?.PageSize?.ToString() } - }; - - return await _client.GetAsync>(url, queryParams); - } - - public async Task> AppendChildrenAsync( - string blockId, - BlocksAppendChildrenParameters parameters = null) - { - if (string.IsNullOrWhiteSpace(blockId)) - { - throw new ArgumentNullException(nameof(blockId)); - } - - var url = BlocksApiUrls.AppendChildren(blockId); - - var body = (IBlocksAppendChildrenBodyParameters)parameters; - - return await _client.PatchAsync>(url, body); - } - - public async Task RetrieveAsync(string blockId) + public async Task RetrieveAsync(string blockId, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(blockId)) { @@ -61,10 +24,10 @@ public async Task RetrieveAsync(string blockId) var url = BlocksApiUrls.Retrieve(blockId); - return await _client.GetAsync(url); + return await _client.GetAsync(url, cancellationToken: cancellationToken); } - public async Task UpdateAsync(string blockId, IUpdateBlock updateBlock) + public async Task UpdateAsync(string blockId, IUpdateBlock updateBlock, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(blockId)) { @@ -73,10 +36,10 @@ public async Task UpdateAsync(string blockId, IUpdateBlock updateBlock) var url = BlocksApiUrls.Update(blockId); - return await _client.PatchAsync(url, updateBlock); + return await _client.PatchAsync(url, updateBlock, cancellationToken: cancellationToken); } - public async Task DeleteAsync(string blockId) + public async Task DeleteAsync(string blockId, CancellationToken cancellationToken = default) { if (string.IsNullOrWhiteSpace(blockId)) { @@ -85,7 +48,7 @@ public async Task DeleteAsync(string blockId) var url = BlocksApiUrls.Delete(blockId); - await _client.DeleteAsync(url); + await _client.DeleteAsync(url, cancellationToken: cancellationToken); } } } diff --git a/Src/Notion.Client/Api/Blocks/IBlocksClient.cs b/Src/Notion.Client/Api/Blocks/IBlocksClient.cs index f45611b7..c5a64e59 100644 --- a/Src/Notion.Client/Api/Blocks/IBlocksClient.cs +++ b/Src/Notion.Client/Api/Blocks/IBlocksClient.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Notion.Client { @@ -9,7 +10,7 @@ public interface IBlocksClient /// /// /// Block - Task RetrieveAsync(string blockId); + Task RetrieveAsync(string blockId, CancellationToken cancellationToken = default); /// /// Updates the content for the specified block_id based on the block type. @@ -17,26 +18,38 @@ public interface IBlocksClient /// /// /// Block - Task UpdateAsync(string blockId, IUpdateBlock updateBlock); + Task UpdateAsync(string blockId, IUpdateBlock updateBlock, + CancellationToken cancellationToken = default); - Task> RetrieveChildrenAsync( - string blockId, - BlocksRetrieveChildrenParameters parameters = null); + /// + /// Returns a paginated array of child block objects contained in the block using the ID specified. + ///
+ /// In order to receive a complete representation of a block, you may need to recursively retrieve the + /// block children of child blocks. + ///
+ /// + /// + /// + Task RetrieveChildrenAsync( + BlockRetrieveChildrenRequest request, + CancellationToken cancellationToken = default + ); /// /// Creates and appends new children blocks to the parent block_id specified. /// - /// Identifier for a block - /// + /// + /// /// A paginated list of newly created first level children block objects. - Task> AppendChildrenAsync( - string blockId, - BlocksAppendChildrenParameters parameters = null); + Task AppendChildrenAsync( + BlockAppendChildrenRequest request, + CancellationToken cancellationToken = default + ); /// - /// Sets a Block object, including page blocks, to archived: true using the ID specified. + /// Moves a Block object, including page blocks, to the trash: true using the ID specified. /// /// Identifier for a Notion block - Task DeleteAsync(string blockId); + Task DeleteAsync(string blockId, CancellationToken cancellationToken = default); } } diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksAppendChildrenParameters.cs b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksAppendChildrenParameters.cs deleted file mode 100644 index e78899bb..00000000 --- a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksAppendChildrenParameters.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace Notion.Client -{ - public class BlocksAppendChildrenParameters : IBlocksAppendChildrenBodyParameters - { - public IEnumerable Children { get; set; } - } -} diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksRetrieveChildrenParameters.cs b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksRetrieveChildrenParameters.cs deleted file mode 100644 index 30f9c17d..00000000 --- a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksRetrieveChildrenParameters.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace Notion.Client -{ - public class BlocksRetrieveChildrenParameters : IBlocksRetrieveChildrenQueryParameters - { - public string StartCursor { get; set; } - - public int? PageSize { get; set; } - } -} diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/BookmarkUpdateBlock.cs b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/BookmarkUpdateBlock.cs index 612b6dda..b147c587 100644 --- a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/BookmarkUpdateBlock.cs +++ b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/BookmarkUpdateBlock.cs @@ -8,7 +8,7 @@ public class BookmarkUpdateBlock : IUpdateBlock [JsonProperty("bookmark")] public Info Bookmark { get; set; } - public bool Archived { get; set; } + public bool InTrash { get; set; } public class Info { diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/BreadcrumbUpdateBlock.cs b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/BreadcrumbUpdateBlock.cs index a90f6966..260eca82 100644 --- a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/BreadcrumbUpdateBlock.cs +++ b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/BreadcrumbUpdateBlock.cs @@ -12,7 +12,7 @@ public BreadcrumbUpdateBlock() [JsonProperty("breadcrumb")] public Info Breadcrumb { get; set; } - public bool Archived { get; set; } + public bool InTrash { get; set; } public class Info { diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/DividerUpdateBlock.cs b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/DividerUpdateBlock.cs index 79f332f0..62b83451 100644 --- a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/DividerUpdateBlock.cs +++ b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/DividerUpdateBlock.cs @@ -12,7 +12,7 @@ public DividerUpdateBlock() [JsonProperty("divider")] public Info Divider { get; set; } - public bool Archived { get; set; } + public bool InTrash { get; set; } public class Info { diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/IUpdateBlock.cs b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/IUpdateBlock.cs index ba93fe29..5b1de4be 100644 --- a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/IUpdateBlock.cs +++ b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/IUpdateBlock.cs @@ -4,7 +4,7 @@ namespace Notion.Client { public interface IUpdateBlock { - [JsonProperty("archived")] - bool Archived { get; set; } + [JsonProperty("in_trash")] + bool InTrash { get; set; } } } diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/TableOfContentsUpdateBlock.cs b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/TableOfContentsUpdateBlock.cs index bef1f278..2e4adff9 100644 --- a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/TableOfContentsUpdateBlock.cs +++ b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/TableOfContentsUpdateBlock.cs @@ -12,7 +12,7 @@ public TableOfContentsUpdateBlock() [JsonProperty("table_of_contents")] public Info TableOfContents { get; set; } - public bool Archived { get; set; } + public bool InTrash { get; set; } public class Info { diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/UpdateBlock.cs b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/UpdateBlock.cs index d0321e52..14704cb2 100644 --- a/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/UpdateBlock.cs +++ b/Src/Notion.Client/Api/Blocks/RequestParams/BlocksUpdateParameters/UpdateBlocks/UpdateBlock.cs @@ -2,6 +2,6 @@ { public abstract class UpdateBlock : IUpdateBlock { - public bool Archived { get; set; } + public bool InTrash { get; set; } } } diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/IBlocksAppendChildrenBodyParameters.cs b/Src/Notion.Client/Api/Blocks/RequestParams/IBlocksAppendChildrenBodyParameters.cs deleted file mode 100644 index bdd4d324..00000000 --- a/Src/Notion.Client/Api/Blocks/RequestParams/IBlocksAppendChildrenBodyParameters.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace Notion.Client -{ - // TODO: need an input version of Block - public interface IBlocksAppendChildrenBodyParameters - { - [JsonProperty("children")] - IEnumerable Children { get; set; } - } -} diff --git a/Src/Notion.Client/Api/Blocks/RequestParams/IBlocksRetrieveChildrenQueryParameters.cs b/Src/Notion.Client/Api/Blocks/RequestParams/IBlocksRetrieveChildrenQueryParameters.cs deleted file mode 100644 index 5c85e06a..00000000 --- a/Src/Notion.Client/Api/Blocks/RequestParams/IBlocksRetrieveChildrenQueryParameters.cs +++ /dev/null @@ -1,6 +0,0 @@ -namespace Notion.Client -{ - public interface IBlocksRetrieveChildrenQueryParameters : IPaginationParameters - { - } -} diff --git a/Src/Notion.Client/Api/Blocks/RetrieveChildren/BlocksClient.cs b/Src/Notion.Client/Api/Blocks/RetrieveChildren/BlocksClient.cs new file mode 100644 index 00000000..c91f5c6d --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/RetrieveChildren/BlocksClient.cs @@ -0,0 +1,36 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace Notion.Client +{ + public sealed partial class BlocksClient + { + public async Task RetrieveChildrenAsync( + BlockRetrieveChildrenRequest request, + CancellationToken cancellationToken = default) + { + if (string.IsNullOrWhiteSpace(request.BlockId)) + { + throw new ArgumentNullException(nameof(request.BlockId)); + } + + var url = ApiEndpoints.BlocksApiUrls.RetrieveChildren(request.BlockId); + + var queryParameters = (IBlockRetrieveChildrenQueryParameters)request; + + var queryParams = new Dictionary + { + { "start_cursor", queryParameters?.StartCursor }, + { "page_size", queryParameters?.PageSize?.ToString() } + }; + + return await _client.GetAsync( + url, + queryParams, + cancellationToken: cancellationToken + ); + } + } +} diff --git a/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/BlockRetrieveChildrenRequest.cs b/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/BlockRetrieveChildrenRequest.cs new file mode 100644 index 00000000..c0e87f3e --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/BlockRetrieveChildrenRequest.cs @@ -0,0 +1,13 @@ +namespace Notion.Client +{ + public class BlockRetrieveChildrenRequest : + IBlockRetrieveChildrenQueryParameters, + IBlockRetrieveChildrenPathParameters + { + public string StartCursor { get; set; } + + public int? PageSize { get; set; } + + public string BlockId { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/IBlockRetrieveChildrenPathParameters.cs b/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/IBlockRetrieveChildrenPathParameters.cs new file mode 100644 index 00000000..c23c2e90 --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/IBlockRetrieveChildrenPathParameters.cs @@ -0,0 +1,7 @@ +namespace Notion.Client +{ + public interface IBlockRetrieveChildrenPathParameters + { + public string BlockId { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/IBlockRetrieveChildrenQueryParameters.cs b/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/IBlockRetrieveChildrenQueryParameters.cs new file mode 100644 index 00000000..68c7de32 --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/RetrieveChildren/Request/IBlockRetrieveChildrenQueryParameters.cs @@ -0,0 +1,6 @@ +namespace Notion.Client +{ + public interface IBlockRetrieveChildrenQueryParameters : IPaginationParameters + { + } +} diff --git a/Src/Notion.Client/Api/Blocks/RetrieveChildren/Response/RetrieveChildrenResponse.cs b/Src/Notion.Client/Api/Blocks/RetrieveChildren/Response/RetrieveChildrenResponse.cs new file mode 100644 index 00000000..0b34619f --- /dev/null +++ b/Src/Notion.Client/Api/Blocks/RetrieveChildren/Response/RetrieveChildrenResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class RetrieveChildrenResponse : PaginatedList + { + [JsonProperty("block")] + public Dictionary Block { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Comments/Create/CommentsClient.cs b/Src/Notion.Client/Api/Comments/Create/CommentsClient.cs index cb3b9bd1..ac2244ea 100644 --- a/Src/Notion.Client/Api/Comments/Create/CommentsClient.cs +++ b/Src/Notion.Client/Api/Comments/Create/CommentsClient.cs @@ -1,16 +1,18 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Notion.Client { public partial class CommentsClient { - public async Task CreateAsync(CreateCommentParameters parameters) + public async Task CreateAsync(CreateCommentParameters parameters, CancellationToken cancellationToken = default) { var body = (ICreateCommentsBodyParameters)parameters; return await _client.PostAsync( ApiEndpoints.CommentsApiUrls.Create(), - body + body, + cancellationToken: cancellationToken ); } } diff --git a/Src/Notion.Client/Api/Comments/ICommentsClient.cs b/Src/Notion.Client/Api/Comments/ICommentsClient.cs index 98ec3faf..7b7af943 100644 --- a/Src/Notion.Client/Api/Comments/ICommentsClient.cs +++ b/Src/Notion.Client/Api/Comments/ICommentsClient.cs @@ -1,11 +1,12 @@ +using System.Threading; using System.Threading.Tasks; namespace Notion.Client { public interface ICommentsClient { - Task CreateAsync(CreateCommentParameters createCommentParameters); + Task CreateAsync(CreateCommentParameters createCommentParameters, CancellationToken cancellationToken = default); - Task RetrieveAsync(RetrieveCommentsParameters parameters); + Task RetrieveAsync(RetrieveCommentsParameters parameters, CancellationToken cancellationToken = default); } } diff --git a/Src/Notion.Client/Api/Comments/Retrieve/CommentsClient.cs b/Src/Notion.Client/Api/Comments/Retrieve/CommentsClient.cs index 22964c5f..45c64f00 100644 --- a/Src/Notion.Client/Api/Comments/Retrieve/CommentsClient.cs +++ b/Src/Notion.Client/Api/Comments/Retrieve/CommentsClient.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; namespace Notion.Client @@ -7,7 +8,7 @@ namespace Notion.Client public partial class CommentsClient { [SuppressMessage("ReSharper", "UnusedMember.Global")] - public async Task RetrieveAsync(RetrieveCommentsParameters parameters) + public async Task RetrieveAsync(RetrieveCommentsParameters parameters, CancellationToken cancellationToken = default) { var qp = (IRetrieveCommentsQueryParameters)parameters; @@ -20,7 +21,8 @@ public async Task RetrieveAsync(RetrieveCommentsParame return await _client.GetAsync( ApiEndpoints.CommentsApiUrls.Retrieve(), - queryParams + queryParams, + cancellationToken: cancellationToken ); } } diff --git a/Src/Notion.Client/Api/Comments/Retrieve/Response/Comments.cs b/Src/Notion.Client/Api/Comments/Retrieve/Response/Comments.cs index c89182a5..8b3b43b3 100644 --- a/Src/Notion.Client/Api/Comments/Retrieve/Response/Comments.cs +++ b/Src/Notion.Client/Api/Comments/Retrieve/Response/Comments.cs @@ -5,9 +5,6 @@ namespace Notion.Client { public class RetrieveCommentsResponse : PaginatedList { - [JsonProperty("type")] - public string Type { get; set; } - [JsonProperty("comment")] public Dictionary Comment { get; set; } } diff --git a/Src/Notion.Client/Api/Databases/DatabasesClient.cs b/Src/Notion.Client/Api/Databases/DatabasesClient.cs index 5157dee9..a3dc9a8c 100644 --- a/Src/Notion.Client/Api/Databases/DatabasesClient.cs +++ b/Src/Notion.Client/Api/Databases/DatabasesClient.cs @@ -1,9 +1,11 @@ -using System.Threading.Tasks; +using System; +using System.Threading; +using System.Threading.Tasks; using static Notion.Client.ApiEndpoints; namespace Notion.Client { - public class DatabasesClient : IDatabasesClient + public sealed partial class DatabasesClient : IDatabasesClient { private readonly IRestClient _client; @@ -12,32 +14,28 @@ public DatabasesClient(IRestClient client) _client = client; } - public async Task RetrieveAsync(string databaseId) + public async Task RetrieveAsync(string databaseId, CancellationToken cancellationToken = default) { - return await _client.GetAsync(DatabasesApiUrls.Retrieve(databaseId)); - } - - public async Task> QueryAsync( - string databaseId, - DatabasesQueryParameters databasesQueryParameters) - { - var body = (IDatabaseQueryBodyParameters)databasesQueryParameters; + if (string.IsNullOrWhiteSpace(databaseId)) + { + throw new ArgumentNullException(nameof(databaseId)); + } - return await _client.PostAsync>(DatabasesApiUrls.Query(databaseId), body); + return await _client.GetAsync(DatabasesApiUrls.Retrieve(databaseId), cancellationToken: cancellationToken); } - public async Task CreateAsync(DatabasesCreateParameters databasesCreateParameters) + public async Task CreateAsync(DatabasesCreateParameters databasesCreateParameters, CancellationToken cancellationToken = default) { var body = (IDatabasesCreateBodyParameters)databasesCreateParameters; - return await _client.PostAsync(DatabasesApiUrls.Create, body); + return await _client.PostAsync(DatabasesApiUrls.Create, body, cancellationToken: cancellationToken); } - public async Task UpdateAsync(string databaseId, DatabasesUpdateParameters databasesUpdateParameters) + public async Task UpdateAsync(string databaseId, DatabasesUpdateParameters databasesUpdateParameters, CancellationToken cancellationToken = default) { var body = (IDatabasesUpdateBodyParameters)databasesUpdateParameters; - return await _client.PatchAsync(DatabasesApiUrls.Update(databaseId), body); + return await _client.PatchAsync(DatabasesApiUrls.Update(databaseId), body, cancellationToken: cancellationToken); } } } diff --git a/Src/Notion.Client/Api/Databases/IDatabasesClient.cs b/Src/Notion.Client/Api/Databases/IDatabasesClient.cs index 9c245ce3..98df1273 100644 --- a/Src/Notion.Client/Api/Databases/IDatabasesClient.cs +++ b/Src/Notion.Client/Api/Databases/IDatabasesClient.cs @@ -1,4 +1,5 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; namespace Notion.Client { @@ -11,7 +12,7 @@ public interface IDatabasesClient /// /// /// - Task RetrieveAsync(string databaseId); + Task RetrieveAsync(string databaseId, CancellationToken cancellationToken = default); /// /// Gets a list of Pages contained in the database, filtered and ordered according to the @@ -23,7 +24,7 @@ public interface IDatabasesClient /// /// /// - Task> QueryAsync(string databaseId, DatabasesQueryParameters databasesQueryParameters); + Task QueryAsync(string databaseId, DatabasesQueryParameters databasesQueryParameters, CancellationToken cancellationToken = default); /// /// Creates a database as a subpage in the specified parent page, with the specified properties schema. @@ -32,7 +33,7 @@ public interface IDatabasesClient /// /// /// - Task CreateAsync(DatabasesCreateParameters databasesCreateParameters); + Task CreateAsync(DatabasesCreateParameters databasesCreateParameters, CancellationToken cancellationToken = default); /// /// Updates an existing database as specified by the parameters. @@ -42,6 +43,6 @@ public interface IDatabasesClient /// /// /// - Task UpdateAsync(string databaseId, DatabasesUpdateParameters databasesUpdateParameters); + Task UpdateAsync(string databaseId, DatabasesUpdateParameters databasesUpdateParameters, CancellationToken cancellationToken = default); } } diff --git a/Src/Notion.Client/Api/Databases/Query/DatabasesClient.cs b/Src/Notion.Client/Api/Databases/Query/DatabasesClient.cs new file mode 100644 index 00000000..2d0c62be --- /dev/null +++ b/Src/Notion.Client/Api/Databases/Query/DatabasesClient.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; + +namespace Notion.Client +{ + public sealed partial class DatabasesClient + { + public async Task QueryAsync( + string databaseId, + DatabasesQueryParameters databasesQueryParameters, CancellationToken cancellationToken = default) + { + var body = (IDatabaseQueryBodyParameters)databasesQueryParameters; + var queryParameters = (IDatabaseQueryQueryParameters)databasesQueryParameters; + + var queryParams = queryParameters.FilterProperties? + .Select(x => new KeyValuePair("filter_properties", x)); + + return await _client.PostAsync( + ApiEndpoints.DatabasesApiUrls.Query(databaseId), + body, + queryParams, + cancellationToken: cancellationToken + ); + } + } +} diff --git a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesQueryParameters.cs b/Src/Notion.Client/Api/Databases/Query/Request/DatabasesQueryParameters.cs similarity index 76% rename from Src/Notion.Client/Api/Databases/RequestParams/DatabasesQueryParameters.cs rename to Src/Notion.Client/Api/Databases/Query/Request/DatabasesQueryParameters.cs index 51bd8a06..2c2bb030 100644 --- a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesQueryParameters.cs +++ b/Src/Notion.Client/Api/Databases/Query/Request/DatabasesQueryParameters.cs @@ -2,7 +2,7 @@ namespace Notion.Client { - public class DatabasesQueryParameters : IDatabaseQueryBodyParameters + public class DatabasesQueryParameters : IDatabaseQueryBodyParameters, IDatabaseQueryQueryParameters { public Filter Filter { get; set; } @@ -11,5 +11,7 @@ public class DatabasesQueryParameters : IDatabaseQueryBodyParameters public string StartCursor { get; set; } public int? PageSize { get; set; } + + public List FilterProperties { get; set; } } } diff --git a/Src/Notion.Client/Api/Databases/RequestParams/IDatabaseQueryBodyParameters.cs b/Src/Notion.Client/Api/Databases/Query/Request/IDatabaseQueryBodyParameters.cs similarity index 100% rename from Src/Notion.Client/Api/Databases/RequestParams/IDatabaseQueryBodyParameters.cs rename to Src/Notion.Client/Api/Databases/Query/Request/IDatabaseQueryBodyParameters.cs diff --git a/Src/Notion.Client/Api/Databases/Query/Request/IDatabaseQueryQueryParameters.cs b/Src/Notion.Client/Api/Databases/Query/Request/IDatabaseQueryQueryParameters.cs new file mode 100644 index 00000000..f8ba1f41 --- /dev/null +++ b/Src/Notion.Client/Api/Databases/Query/Request/IDatabaseQueryQueryParameters.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public interface IDatabaseQueryQueryParameters + { + [JsonProperty("filter_properties")] + List FilterProperties { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Databases/Query/Response/DatabaseQueryResponse.cs b/Src/Notion.Client/Api/Databases/Query/Response/DatabaseQueryResponse.cs new file mode 100644 index 00000000..5b7c9ec6 --- /dev/null +++ b/Src/Notion.Client/Api/Databases/Query/Response/DatabaseQueryResponse.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + // ReSharper disable once ClassNeverInstantiated.Global + public class DatabaseQueryResponse : PaginatedList + { + [JsonProperty("database")] + public Dictionary Database { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesCreateParameters/MentionInput.cs b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesCreateParameters/MentionInput.cs index 83d1e699..48b34948 100644 --- a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesCreateParameters/MentionInput.cs +++ b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesCreateParameters/MentionInput.cs @@ -17,6 +17,6 @@ public class MentionInput public ObjectId Database { get; set; } [JsonProperty("date")] - public DatePropertyValue Date { get; set; } + public Date Date { get; set; } } } diff --git a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesCreateParameters/PropertySchema/RelationPropertySchema.cs b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesCreateParameters/PropertySchema/RelationPropertySchema.cs index fa6a3249..4c278ac2 100644 --- a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesCreateParameters/PropertySchema/RelationPropertySchema.cs +++ b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesCreateParameters/PropertySchema/RelationPropertySchema.cs @@ -1,23 +1,10 @@ -using System; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Notion.Client { public class RelationPropertySchema : IPropertySchema { [JsonProperty("relation")] - public RelationInfo Relation { get; set; } - - public class RelationInfo - { - [JsonProperty("database_id")] - public Guid DatabaseId { get; set; } - - [JsonProperty("synced_property_id")] - public string SyncedPropertyId { get; set; } - - [JsonProperty("synced_property_name")] - public string SyncedPropertyName { get; set; } - } + public RelationData Relation { get; set; } } } diff --git a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/DatabasesUpdateParameters.cs b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/DatabasesUpdateParameters.cs index 41da2d17..5147a6b0 100644 --- a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/DatabasesUpdateParameters.cs +++ b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/DatabasesUpdateParameters.cs @@ -12,7 +12,7 @@ public class DatabasesUpdateParameters : IDatabasesUpdateBodyParameters public FileObject Cover { get; set; } - public bool Archived { get; set; } + public bool InTrash { get; set; } public bool? IsInline { get; set; } diff --git a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/IDatabasesUpdateBodyParameters.cs b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/IDatabasesUpdateBodyParameters.cs index b1dc419a..f37df431 100644 --- a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/IDatabasesUpdateBodyParameters.cs +++ b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/IDatabasesUpdateBodyParameters.cs @@ -17,8 +17,8 @@ public interface IDatabasesUpdateBodyParameters [JsonProperty("cover")] FileObject Cover { get; set; } - [JsonProperty("archived")] - bool Archived { get; set; } + [JsonProperty("in_trash")] + bool InTrash { get; set; } [JsonProperty("is_inline")] bool? IsInline { get; set; } diff --git a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/PropertySchema/RelationUpdatePropertySchema.cs b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/PropertySchema/RelationUpdatePropertySchema.cs index 4f6a74a3..25f65cd4 100644 --- a/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/PropertySchema/RelationUpdatePropertySchema.cs +++ b/Src/Notion.Client/Api/Databases/RequestParams/DatabasesUpdateParameters/PropertySchema/RelationUpdatePropertySchema.cs @@ -1,23 +1,10 @@ -using System; -using Newtonsoft.Json; +using Newtonsoft.Json; namespace Notion.Client { public class RelationUpdatePropertySchema : UpdatePropertySchema { [JsonProperty("relation")] - public RelationInfo Relation { get; set; } - - public class RelationInfo - { - [JsonProperty("database_id")] - public Guid DatabaseId { get; set; } - - [JsonProperty("synced_property_id")] - public string SyncedPropertyId { get; set; } - - [JsonProperty("synced_property_name")] - public string SyncedPropertyName { get; set; } - } + public RelationData Relation { get; set; } } } diff --git a/Src/Notion.Client/Api/Pages/IPagesClient.cs b/Src/Notion.Client/Api/Pages/IPagesClient.cs index 755b385f..8dc8f5ce 100644 --- a/Src/Notion.Client/Api/Pages/IPagesClient.cs +++ b/Src/Notion.Client/Api/Pages/IPagesClient.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Notion.Client @@ -15,7 +16,7 @@ public interface IPagesClient /// /// Create page parameters /// Created object. - Task CreateAsync(PagesCreateParameters pagesCreateParameters); + Task CreateAsync(PagesCreateParameters pagesCreateParameters, CancellationToken cancellationToken = default); /// /// Retrieves a Page object using the ID specified. @@ -24,7 +25,7 @@ public interface IPagesClient /// /// /// - Task RetrieveAsync(string pageId); + Task RetrieveAsync(string pageId, CancellationToken cancellationToken = default); /// /// Updates page property values for the specified page. @@ -38,7 +39,7 @@ public interface IPagesClient /// Updated object Task UpdatePropertiesAsync( string pageId, - IDictionary updatedProperties); + IDictionary updatedProperties, CancellationToken cancellationToken = default); /// /// Updates page property values for the specified page. @@ -47,7 +48,7 @@ Task UpdatePropertiesAsync( /// Identifier for a Notion page /// Update property parameters /// Updated object - Task UpdateAsync(string pageId, PagesUpdateParameters pagesUpdateParameters); + Task UpdateAsync(string pageId, PagesUpdateParameters pagesUpdateParameters, CancellationToken cancellationToken = default); /// /// Retrieves a property_item object for a given pageId and propertyId. Depending on the property type, the object @@ -58,6 +59,6 @@ Task UpdatePropertiesAsync( /// /// Task RetrievePagePropertyItemAsync( - RetrievePropertyItemParameters retrievePropertyItemParameters); + RetrievePropertyItemParameters retrievePropertyItemParameters, CancellationToken cancellationToken = default); } } diff --git a/Src/Notion.Client/Api/Pages/PagesClient.cs b/Src/Notion.Client/Api/Pages/PagesClient.cs index 43fe143e..76b3e41a 100644 --- a/Src/Notion.Client/Api/Pages/PagesClient.cs +++ b/Src/Notion.Client/Api/Pages/PagesClient.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; using static Notion.Client.ApiEndpoints; @@ -25,7 +26,7 @@ public PagesClient(IRestClient client) /// /// Create page parameters /// Created page. - public async Task CreateAsync(PagesCreateParameters pagesCreateParameters) + public async Task CreateAsync(PagesCreateParameters pagesCreateParameters, CancellationToken cancellationToken = default) { if (pagesCreateParameters is null) { @@ -44,18 +45,18 @@ public async Task CreateAsync(PagesCreateParameters pagesCreateParameters) throw new ArgumentNullException(nameof(bodyParameters.Properties), "Properties are required!"); } - return await _client.PostAsync(PagesApiUrls.Create(), bodyParameters); + return await _client.PostAsync(PagesApiUrls.Create(), bodyParameters, cancellationToken: cancellationToken); } - public async Task RetrieveAsync(string pageId) + public async Task RetrieveAsync(string pageId, CancellationToken cancellationToken = default) { var url = PagesApiUrls.Retrieve(pageId); - return await _client.GetAsync(url); + return await _client.GetAsync(url, cancellationToken: cancellationToken); } public async Task RetrievePagePropertyItemAsync( - RetrievePropertyItemParameters retrievePropertyItemParameters) + RetrievePropertyItemParameters retrievePropertyItemParameters, CancellationToken cancellationToken = default) { var pathParameters = (IRetrievePropertyItemPathParameters)retrievePropertyItemParameters; var queryParameters = (IRetrievePropertyQueryParameters)retrievePropertyItemParameters; @@ -68,27 +69,27 @@ public async Task RetrievePagePropertyItemAsync( { "page_size", queryParameters?.PageSize?.ToString() } }; - return await _client.GetAsync(url, queryParams); + return await _client.GetAsync(url, queryParams, cancellationToken: cancellationToken); } - public async Task UpdateAsync(string pageId, PagesUpdateParameters pagesUpdateParameters) + public async Task UpdateAsync(string pageId, PagesUpdateParameters pagesUpdateParameters, CancellationToken cancellationToken = default) { var url = PagesApiUrls.Update(pageId); var body = (IPagesUpdateBodyParameters)pagesUpdateParameters; - return await _client.PatchAsync(url, body); + return await _client.PatchAsync(url, body, cancellationToken: cancellationToken); } [Obsolete("This method is obsolete. Use UpdateAsync instead. This API will be removed in future release")] public async Task UpdatePropertiesAsync( string pageId, - IDictionary updatedProperties) + IDictionary updatedProperties, CancellationToken cancellationToken = default) { var url = PagesApiUrls.UpdateProperties(pageId); var body = new UpdatePropertiesParameters { Properties = updatedProperties }; - return await _client.PatchAsync(url, body); + return await _client.PatchAsync(url, body, cancellationToken: cancellationToken); } [SuppressMessage("ReSharper", "UnusedAutoPropertyAccessor.Local")] diff --git a/Src/Notion.Client/Api/Pages/RequestParams/IPagesUpdateBodyParameters.cs b/Src/Notion.Client/Api/Pages/RequestParams/IPagesUpdateBodyParameters.cs index b37dd1fd..b7ea1ea6 100644 --- a/Src/Notion.Client/Api/Pages/RequestParams/IPagesUpdateBodyParameters.cs +++ b/Src/Notion.Client/Api/Pages/RequestParams/IPagesUpdateBodyParameters.cs @@ -5,8 +5,8 @@ namespace Notion.Client { public interface IPagesUpdateBodyParameters { - [JsonProperty("archived")] - bool Archived { get; set; } + [JsonProperty("in_trash")] + bool InTrash { get; set; } [JsonProperty("properties")] IDictionary Properties { get; set; } diff --git a/Src/Notion.Client/Api/Pages/RequestParams/PagesUpdateParameters.cs b/Src/Notion.Client/Api/Pages/RequestParams/PagesUpdateParameters.cs index 742ef8dd..6845f27b 100644 --- a/Src/Notion.Client/Api/Pages/RequestParams/PagesUpdateParameters.cs +++ b/Src/Notion.Client/Api/Pages/RequestParams/PagesUpdateParameters.cs @@ -11,8 +11,8 @@ public class PagesUpdateParameters : IPagesUpdateBodyParameters [JsonProperty("cover")] public FileObject Cover { get; set; } - [JsonProperty("archived")] - public bool Archived { get; set; } + [JsonProperty("in_trash")] + public bool InTrash { get; set; } [JsonProperty("properties")] public IDictionary Properties { get; set; } diff --git a/Src/Notion.Client/Api/Search/ISearchClient.cs b/Src/Notion.Client/Api/Search/ISearchClient.cs index 835c14e4..accee1e1 100644 --- a/Src/Notion.Client/Api/Search/ISearchClient.cs +++ b/Src/Notion.Client/Api/Search/ISearchClient.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using System.Threading; using System.Threading.Tasks; namespace Notion.Client @@ -10,10 +11,14 @@ public interface ISearchClient /// Searches all original pages, databases, and child pages/databases that are shared with the integration. /// It will not return linked databases, since these duplicate their source databases. /// - /// Search filters and body parameters + /// Search filters and body parameters + /// /// /// /// - Task> SearchAsync(SearchParameters parameters); + Task SearchAsync( + SearchRequest request, + CancellationToken cancellationToken = default + ); } } diff --git a/Src/Notion.Client/Api/Search/Parameters/ISearchBodyParameters.cs b/Src/Notion.Client/Api/Search/Request/ISearchBodyParameters.cs similarity index 100% rename from Src/Notion.Client/Api/Search/Parameters/ISearchBodyParameters.cs rename to Src/Notion.Client/Api/Search/Request/ISearchBodyParameters.cs diff --git a/Src/Notion.Client/Api/Search/Parameters/SearchDirection.cs b/Src/Notion.Client/Api/Search/Request/SearchDirection.cs similarity index 100% rename from Src/Notion.Client/Api/Search/Parameters/SearchDirection.cs rename to Src/Notion.Client/Api/Search/Request/SearchDirection.cs diff --git a/Src/Notion.Client/Api/Search/Parameters/SearchFilter.cs b/Src/Notion.Client/Api/Search/Request/SearchFilter.cs similarity index 100% rename from Src/Notion.Client/Api/Search/Parameters/SearchFilter.cs rename to Src/Notion.Client/Api/Search/Request/SearchFilter.cs diff --git a/Src/Notion.Client/Api/Search/Parameters/SearchObjectType.cs b/Src/Notion.Client/Api/Search/Request/SearchObjectType.cs similarity index 100% rename from Src/Notion.Client/Api/Search/Parameters/SearchObjectType.cs rename to Src/Notion.Client/Api/Search/Request/SearchObjectType.cs diff --git a/Src/Notion.Client/Api/Search/Parameters/SearchParameters.cs b/Src/Notion.Client/Api/Search/Request/SearchRequest.cs similarity index 82% rename from Src/Notion.Client/Api/Search/Parameters/SearchParameters.cs rename to Src/Notion.Client/Api/Search/Request/SearchRequest.cs index e1c8ced8..a9ac887f 100644 --- a/Src/Notion.Client/Api/Search/Parameters/SearchParameters.cs +++ b/Src/Notion.Client/Api/Search/Request/SearchRequest.cs @@ -1,6 +1,6 @@ namespace Notion.Client { - public class SearchParameters : ISearchBodyParameters + public class SearchRequest : ISearchBodyParameters { public string Query { get; set; } diff --git a/Src/Notion.Client/Api/Search/Parameters/SearchSort.cs b/Src/Notion.Client/Api/Search/Request/SearchSort.cs similarity index 100% rename from Src/Notion.Client/Api/Search/Parameters/SearchSort.cs rename to Src/Notion.Client/Api/Search/Request/SearchSort.cs diff --git a/Src/Notion.Client/Api/Search/Response/SearchResponse.cs b/Src/Notion.Client/Api/Search/Response/SearchResponse.cs new file mode 100644 index 00000000..2be0e2f1 --- /dev/null +++ b/Src/Notion.Client/Api/Search/Response/SearchResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class SearchResponse : PaginatedList + { + [JsonProperty("page_or_database")] + public Dictionary PageOrDatabase { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Search/SearchClient.cs b/Src/Notion.Client/Api/Search/SearchClient.cs index bd73acfc..492e0940 100644 --- a/Src/Notion.Client/Api/Search/SearchClient.cs +++ b/Src/Notion.Client/Api/Search/SearchClient.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using static Notion.Client.ApiEndpoints; namespace Notion.Client { - public class SearchClient : ISearchClient + public sealed class SearchClient : ISearchClient { private readonly IRestClient _client; @@ -12,13 +13,15 @@ public SearchClient(IRestClient client) _client = client; } - public async Task> SearchAsync(SearchParameters parameters) + public async Task SearchAsync( + SearchRequest request, + CancellationToken cancellationToken = default) { var url = SearchApiUrls.Search(); - var body = (ISearchBodyParameters)parameters; + var body = (ISearchBodyParameters)request; - return await _client.PostAsync>(url, body); + return await _client.PostAsync(url, body, cancellationToken: cancellationToken); } } } diff --git a/Src/Notion.Client/Api/Users/IUsersClient.cs b/Src/Notion.Client/Api/Users/IUsersClient.cs index 69981022..bc0c3d8a 100644 --- a/Src/Notion.Client/Api/Users/IUsersClient.cs +++ b/Src/Notion.Client/Api/Users/IUsersClient.cs @@ -1,4 +1,6 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; +using Notion.Client.List.Request; namespace Notion.Client { @@ -11,16 +13,31 @@ public interface IUsersClient /// /// /// - Task RetrieveAsync(string userId); + Task RetrieveAsync(string userId, CancellationToken cancellationToken = default); /// /// Returns a paginated list of Users for the workspace. /// The response may contain fewer than page_size of results. /// + /// /// - /// + /// /// - Task> ListAsync(); + Task ListAsync(CancellationToken cancellationToken = default); + + /// + /// Returns a paginated list of Users for the workspace. + /// The response may contain fewer than page_size of results. + /// + /// + /// + /// + /// + /// + Task ListAsync( + ListUsersRequest listUsersRequest, + CancellationToken cancellationToken = default + ); /// /// Retrieves the bot User associated with the API token provided in the authorization header. @@ -29,6 +46,6 @@ public interface IUsersClient /// object of type bot having an owner field with information about the person who authorized /// the integration. /// - Task MeAsync(); + Task MeAsync(CancellationToken cancellationToken = default); } } diff --git a/Src/Notion.Client/Api/Users/List/Request/ListUsersRequest.cs b/Src/Notion.Client/Api/Users/List/Request/ListUsersRequest.cs new file mode 100644 index 00000000..f3684d09 --- /dev/null +++ b/Src/Notion.Client/Api/Users/List/Request/ListUsersRequest.cs @@ -0,0 +1,13 @@ +namespace Notion.Client.List.Request +{ + public interface IListUsersQueryParameters : IPaginationParameters + { + } + + public class ListUsersRequest : IListUsersQueryParameters + { + public string StartCursor { get; set; } + + public int? PageSize { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Users/List/Response/ListUsersResponse.cs b/Src/Notion.Client/Api/Users/List/Response/ListUsersResponse.cs new file mode 100644 index 00000000..2d30237f --- /dev/null +++ b/Src/Notion.Client/Api/Users/List/Response/ListUsersResponse.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class ListUsersResponse : PaginatedList + { + [JsonProperty("user")] + public Dictionary User { get; set; } + } +} diff --git a/Src/Notion.Client/Api/Users/List/UsersClient.cs b/Src/Notion.Client/Api/Users/List/UsersClient.cs new file mode 100644 index 00000000..ce13633a --- /dev/null +++ b/Src/Notion.Client/Api/Users/List/UsersClient.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Notion.Client.List.Request; + +namespace Notion.Client +{ + public partial class UsersClient + { + public async Task ListAsync(CancellationToken cancellationToken = default) + { + return await _client.GetAsync( + ApiEndpoints.UsersApiUrls.List(), + cancellationToken: cancellationToken + ); + } + + public async Task ListAsync( + ListUsersRequest listUsersRequest, + CancellationToken cancellationToken = default + ) + { + var queryParameters = (IListUsersQueryParameters)listUsersRequest; + + var queryParams = new Dictionary + { + { "start_cursor", queryParameters?.StartCursor }, + { "page_size", queryParameters?.PageSize?.ToString() } + }; + + return await _client.GetAsync( + ApiEndpoints.UsersApiUrls.List(), + queryParams, + cancellationToken: cancellationToken + ); + } + } +} diff --git a/Src/Notion.Client/Api/Users/UsersClient.cs b/Src/Notion.Client/Api/Users/UsersClient.cs index 957ac45e..24681b53 100644 --- a/Src/Notion.Client/Api/Users/UsersClient.cs +++ b/Src/Notion.Client/Api/Users/UsersClient.cs @@ -1,9 +1,10 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using static Notion.Client.ApiEndpoints; namespace Notion.Client { - public class UsersClient : IUsersClient + public partial class UsersClient : IUsersClient { private readonly IRestClient _client; @@ -12,14 +13,9 @@ public UsersClient(IRestClient client) _client = client; } - public async Task RetrieveAsync(string userId) + public async Task RetrieveAsync(string userId, CancellationToken cancellationToken = default) { - return await _client.GetAsync(UsersApiUrls.Retrieve(userId)); - } - - public async Task> ListAsync() - { - return await _client.GetAsync>(UsersApiUrls.List()); + return await _client.GetAsync(UsersApiUrls.Retrieve(userId), cancellationToken: cancellationToken); } /// @@ -29,9 +25,9 @@ public async Task> ListAsync() /// User object of type bot having an owner field with information about the person who authorized the /// integration. /// - public async Task MeAsync() + public async Task MeAsync(CancellationToken cancellationToken = default) { - return await _client.GetAsync(UsersApiUrls.Me()); + return await _client.GetAsync(UsersApiUrls.Me(), cancellationToken: cancellationToken); } } } diff --git a/Src/Notion.Client/Models/Blocks/Block.cs b/Src/Notion.Client/Models/Blocks/Block.cs index 883399df..fa1ef612 100644 --- a/Src/Notion.Client/Models/Blocks/Block.cs +++ b/Src/Notion.Client/Models/Blocks/Block.cs @@ -16,6 +16,8 @@ public abstract class Block : IBlock public virtual bool HasChildren { get; set; } + public bool InTrash { get; set; } + public PartialUser CreatedBy { get; set; } public PartialUser LastEditedBy { get; set; } diff --git a/Src/Notion.Client/Models/Blocks/HeadingOneBlock.cs b/Src/Notion.Client/Models/Blocks/HeadingOneBlock.cs index b0753122..1e8474a4 100644 --- a/Src/Notion.Client/Models/Blocks/HeadingOneBlock.cs +++ b/Src/Notion.Client/Models/Blocks/HeadingOneBlock.cs @@ -13,8 +13,6 @@ public class HeadingOneBlock : Block, IColumnChildrenBlock, INonColumnBlock public override BlockType Type => BlockType.Heading_1; - public override bool HasChildren => false; - public class Info { [JsonProperty("rich_text")] @@ -23,6 +21,9 @@ public class Info [JsonProperty("color")] [JsonConverter(typeof(StringEnumConverter))] public Color? Color { get; set; } + + [JsonProperty("is_toggleable")] + public bool IsToggleable { get; set; } } } } diff --git a/Src/Notion.Client/Models/Blocks/HeadingThreeeBlock.cs b/Src/Notion.Client/Models/Blocks/HeadingThreeBlock.cs similarity index 88% rename from Src/Notion.Client/Models/Blocks/HeadingThreeeBlock.cs rename to Src/Notion.Client/Models/Blocks/HeadingThreeBlock.cs index deada7de..e300a511 100644 --- a/Src/Notion.Client/Models/Blocks/HeadingThreeeBlock.cs +++ b/Src/Notion.Client/Models/Blocks/HeadingThreeBlock.cs @@ -13,8 +13,6 @@ public class HeadingThreeBlock : Block, IColumnChildrenBlock, INonColumnBlock public override BlockType Type => BlockType.Heading_3; - public override bool HasChildren => false; - public class Info { [JsonProperty("rich_text")] @@ -23,6 +21,9 @@ public class Info [JsonProperty("color")] [JsonConverter(typeof(StringEnumConverter))] public Color? Color { get; set; } + + [JsonProperty("is_toggleable")] + public bool IsToggleable { get; set; } } } } diff --git a/Src/Notion.Client/Models/Blocks/HeadingTwoBlock.cs b/Src/Notion.Client/Models/Blocks/HeadingTwoBlock.cs index f5ffd7ea..3be849a8 100644 --- a/Src/Notion.Client/Models/Blocks/HeadingTwoBlock.cs +++ b/Src/Notion.Client/Models/Blocks/HeadingTwoBlock.cs @@ -13,8 +13,6 @@ public class HeadingTwoBlock : Block, IColumnChildrenBlock, INonColumnBlock public override BlockType Type => BlockType.Heading_2; - public override bool HasChildren => false; - public class Info { [JsonProperty("rich_text")] @@ -23,6 +21,9 @@ public class Info [JsonProperty("color")] [JsonConverter(typeof(StringEnumConverter))] public Color? Color { get; set; } + + [JsonProperty("is_toggleable")] + public bool IsToggleable { get; set; } } } } diff --git a/Src/Notion.Client/Models/Blocks/IBlock.cs b/Src/Notion.Client/Models/Blocks/IBlock.cs index df625cf6..b06a3646 100644 --- a/Src/Notion.Client/Models/Blocks/IBlock.cs +++ b/Src/Notion.Client/Models/Blocks/IBlock.cs @@ -29,6 +29,7 @@ namespace Notion.Client [JsonSubtypes.KnownSubTypeAttribute(typeof(ParagraphBlock), BlockType.Paragraph)] [JsonSubtypes.KnownSubTypeAttribute(typeof(PDFBlock), BlockType.PDF)] [JsonSubtypes.KnownSubTypeAttribute(typeof(QuoteBlock), BlockType.Quote)] + [JsonSubtypes.KnownSubTypeAttribute(typeof(SyncedBlockBlock), BlockType.SyncedBlock)] [JsonSubtypes.KnownSubTypeAttribute(typeof(TableBlock), BlockType.Table)] [JsonSubtypes.KnownSubTypeAttribute(typeof(TableRowBlock), BlockType.TableRow)] [JsonSubtypes.KnownSubTypeAttribute(typeof(TableOfContentsBlock), BlockType.TableOfContents)] @@ -46,6 +47,9 @@ public interface IBlock : IObject, IObjectModificationData [JsonProperty("has_children")] bool HasChildren { get; set; } + [JsonProperty("in_trash")] + bool InTrash { get; set; } + [JsonProperty("parent")] IBlockParent Parent { get; set; } } diff --git a/Src/Notion.Client/Models/Blocks/Request/AudioBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/AudioBlockRequest.cs new file mode 100644 index 00000000..8378574e --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/AudioBlockRequest.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class AudioBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("audio")] + public FileObject Audio { get; set; } + + public override BlockType Type => BlockType.Audio; + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/BlockObjectRequest.cs b/Src/Notion.Client/Models/Blocks/Request/BlockObjectRequest.cs new file mode 100644 index 00000000..a3eb3182 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/BlockObjectRequest.cs @@ -0,0 +1,28 @@ +using System; + +namespace Notion.Client +{ + public abstract class BlockObjectRequest : IBlockObjectRequest + { + public ObjectType Object => ObjectType.Block; + + public string Id { get; set; } + + public virtual BlockType Type { get; set; } + + public DateTime CreatedTime { get; set; } + + public DateTime LastEditedTime { get; set; } + + public virtual bool HasChildren { get; set; } + + public PartialUser CreatedBy { get; set; } + + public PartialUser LastEditedBy { get; set; } + + /// + /// Information about the block's parent. + /// + public IBlockParent Parent { get; set; } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/BookmarkBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/BookmarkBlockRequest.cs new file mode 100644 index 00000000..0c7c0be2 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/BookmarkBlockRequest.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class BookmarkBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("bookmark")] + public Info Bookmark { get; set; } + + public override BlockType Type => BlockType.Bookmark; + + public class Info + { + [JsonProperty("url")] + public string Url { get; set; } + + [JsonProperty("caption")] + public IEnumerable Caption { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/BreadcrumbBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/BreadcrumbBlockRequest.cs new file mode 100644 index 00000000..f6039456 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/BreadcrumbBlockRequest.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class BreadcrumbBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("breadcrumb")] + public Data Breadcrumb { get; set; } + + public override BlockType Type => BlockType.Breadcrumb; + + public class Data + { + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/BulletedListItemBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/BulletedListItemBlockRequest.cs new file mode 100644 index 00000000..0aa8a183 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/BulletedListItemBlockRequest.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class BulletedListItemBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("bulleted_list_item")] + public Info BulletedListItem { get; set; } + + public override BlockType Type => BlockType.BulletedListItem; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/CalloutBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/CalloutBlockRequest.cs new file mode 100644 index 00000000..b8a56823 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/CalloutBlockRequest.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class CalloutBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("callout")] + public Info Callout { get; set; } + + public override BlockType Type => BlockType.Callout; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("icon")] + public IPageIcon Icon { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/ChildDatabaseBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/ChildDatabaseBlockRequest.cs new file mode 100644 index 00000000..34ff9387 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/ChildDatabaseBlockRequest.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class ChildDatabaseBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("child_database")] + public Info ChildDatabase { get; set; } + + public override BlockType Type => BlockType.ChildDatabase; + + public class Info + { + [JsonProperty("title")] + public string Title { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/ChildPageBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/ChildPageBlockRequest.cs new file mode 100644 index 00000000..43213b77 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/ChildPageBlockRequest.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class ChildPageBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("child_page")] + public Info ChildPage { get; set; } + + public override BlockType Type => BlockType.ChildPage; + + public class Info + { + [JsonProperty("title")] + public string Title { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/CodeBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/CodeBlockRequest.cs new file mode 100644 index 00000000..732e3208 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/CodeBlockRequest.cs @@ -0,0 +1,25 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class CodeBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("code")] + public Info Code { get; set; } + + public override BlockType Type => BlockType.Code; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("language")] + public string Language { get; set; } + + [JsonProperty("caption")] + public IEnumerable Caption { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/ColumnBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/ColumnBlockRequest.cs new file mode 100644 index 00000000..c4136294 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/ColumnBlockRequest.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class ColumnBlockRequest : BlockObjectRequest + { + public override BlockType Type => BlockType.Column; + + [JsonProperty("column")] + public Info Column { get; set; } + + public class Info + { + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/ColumnListBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/ColumnListBlockRequest.cs new file mode 100644 index 00000000..431acf8e --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/ColumnListBlockRequest.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class ColumnListBlockRequest : BlockObjectRequest, INonColumnBlockRequest + { + [JsonProperty("column_list")] + public Info ColumnList { get; set; } + + public override BlockType Type => BlockType.ColumnList; + + public class Info + { + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/DividerBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/DividerBlockRequest.cs new file mode 100644 index 00000000..49c81059 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/DividerBlockRequest.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class DividerBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("divider")] + public Data Divider { get; set; } + + public override BlockType Type => BlockType.Divider; + + public class Data + { + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/EmbedBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/EmbedBlockRequest.cs new file mode 100644 index 00000000..81cb2339 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/EmbedBlockRequest.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class EmbedBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("embed")] + public Info Embed { get; set; } + + public override BlockType Type => BlockType.Embed; + + public class Info + { + [JsonProperty("url")] + public string Url { get; set; } + + [JsonProperty("caption")] + public IEnumerable Caption { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/EquationBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/EquationBlockRequest.cs new file mode 100644 index 00000000..5c2ddb8a --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/EquationBlockRequest.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class EquationBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("equation")] + public Info Equation { get; set; } + + public override BlockType Type => BlockType.Equation; + + public class Info + { + [JsonProperty("expression")] + public string Expression { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/FileBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/FileBlockRequest.cs new file mode 100644 index 00000000..0539c41c --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/FileBlockRequest.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class FileBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("file")] + public FileObject File { get; set; } + + public override BlockType Type => BlockType.File; + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/HeadingOneBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/HeadingOneBlockRequest.cs new file mode 100644 index 00000000..dd91451a --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/HeadingOneBlockRequest.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class HeadingOneBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("heading_1")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public Info Heading_1 { get; set; } + + public override BlockType Type => BlockType.Heading_1; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("is_toggleable")] + public bool IsToggleable { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/HeadingThreeBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/HeadingThreeBlockRequest.cs new file mode 100644 index 00000000..9b78c80e --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/HeadingThreeBlockRequest.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class HeadingThreeBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("heading_3")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public Info Heading_3 { get; set; } + + public override BlockType Type => BlockType.Heading_3; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("is_toggleable")] + public bool IsToggleable { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/HeadingTwoBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/HeadingTwoBlockRequest.cs new file mode 100644 index 00000000..dfdeb9dd --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/HeadingTwoBlockRequest.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class HeadingTwoBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("heading_2")] + [SuppressMessage("ReSharper", "InconsistentNaming")] + public Info Heading_2 { get; set; } + + public override BlockType Type => BlockType.Heading_2; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("is_toggleable")] + public bool IsToggleable { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/IBlockObjectRequest.cs b/Src/Notion.Client/Models/Blocks/Request/IBlockObjectRequest.cs new file mode 100644 index 00000000..b3fbaf55 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/IBlockObjectRequest.cs @@ -0,0 +1,19 @@ +using JsonSubTypes; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public interface IBlockObjectRequest : IObject, IObjectModificationData + { + [JsonProperty("type")] + [JsonConverter(typeof(StringEnumConverter))] + BlockType Type { get; } + + [JsonProperty("has_children")] + bool HasChildren { get; set; } + + [JsonProperty("parent")] + IBlockParent Parent { get; set; } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/IColumnChildrenBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/IColumnChildrenBlockRequest.cs new file mode 100644 index 00000000..54e08fab --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/IColumnChildrenBlockRequest.cs @@ -0,0 +1,18 @@ +namespace Notion.Client +{ + public interface ITemplateChildrenBlockRequest : IBlockObjectRequest + { + } + + public interface ISyncedBlockChildrenRequest : IBlockObjectRequest + { + } + + public interface IColumnChildrenBlockRequest : ITemplateChildrenBlockRequest, ISyncedBlockChildrenRequest + { + } + + public interface INonColumnBlockRequest : IBlockObjectRequest + { + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/ImageBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/ImageBlockRequest.cs new file mode 100644 index 00000000..d3ec252b --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/ImageBlockRequest.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class ImageBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("image")] + public FileObject Image { get; set; } + + public override BlockType Type => BlockType.Image; + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/LinkPreviewBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/LinkPreviewBlockRequest.cs new file mode 100644 index 00000000..11a47592 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/LinkPreviewBlockRequest.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class LinkPreviewBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("link_preview")] + public Data LinkPreview { get; set; } + + public override BlockType Type => BlockType.LinkPreview; + + public class Data + { + [JsonProperty("url")] + public string Url { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/LinkToPageBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/LinkToPageBlockRequest.cs new file mode 100644 index 00000000..ec182df5 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/LinkToPageBlockRequest.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class LinkToPageBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("link_to_page")] + public IPageParent LinkToPage { get; set; } + + public override BlockType Type => BlockType.LinkToPage; + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/NumberedListItemBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/NumberedListItemBlockRequest.cs new file mode 100644 index 00000000..87342c1d --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/NumberedListItemBlockRequest.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class NumberedListItemBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("numbered_list_item")] + public Info NumberedListItem { get; set; } + + public override BlockType Type => BlockType.NumberedListItem; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/PDFBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/PDFBlockRequest.cs new file mode 100644 index 00000000..5590000d --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/PDFBlockRequest.cs @@ -0,0 +1,14 @@ +using System.Diagnostics.CodeAnalysis; +using Newtonsoft.Json; + +namespace Notion.Client +{ + [SuppressMessage("ReSharper", "InconsistentNaming")] + public class PDFBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("pdf")] + public FileObject PDF { get; set; } + + public override BlockType Type => BlockType.PDF; + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/ParagraphBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/ParagraphBlockRequest.cs new file mode 100644 index 00000000..8e8dfa51 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/ParagraphBlockRequest.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class ParagraphBlockRequest : Block, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("paragraph")] + public Info Paragraph { get; set; } + + public override BlockType Type => BlockType.Paragraph; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/QuoteBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/QuoteBlockRequest.cs new file mode 100644 index 00000000..3acd3130 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/QuoteBlockRequest.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class QuoteBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("quote")] + public Info Quote { get; set; } + + public override BlockType Type => BlockType.Quote; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/SyncedBlockBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/SyncedBlockBlockRequest.cs new file mode 100644 index 00000000..5338e5bb --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/SyncedBlockBlockRequest.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class SyncedBlockBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("synced_block")] + public Data SyncedBlock { get; set; } + + public override BlockType Type => BlockType.SyncedBlock; + + public class Data + { + [JsonProperty("synced_from")] + public SyncedFromBlockId SyncedFrom { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + + public class SyncedFromBlockId + { + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("block_id")] + public string BlockId { get; set; } + } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/TableBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/TableBlockRequest.cs new file mode 100644 index 00000000..1e966182 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/TableBlockRequest.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class TableBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("table")] + public Info Table { get; set; } + + public override BlockType Type => BlockType.Table; + + public class Info + { + [JsonProperty("table_width")] + public int TableWidth { get; set; } + + [JsonProperty("has_column_header")] + public bool HasColumnHeader { get; set; } + + [JsonProperty("has_row_header")] + public bool HasRowHeader { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/TableOfContentsBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/TableOfContentsBlockRequest.cs new file mode 100644 index 00000000..2aef499d --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/TableOfContentsBlockRequest.cs @@ -0,0 +1,20 @@ +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class TableOfContentsBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("table_of_contents")] + public Data TableOfContents { get; set; } + + public override BlockType Type => BlockType.TableOfContents; + + public class Data + { + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/TableRowBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/TableRowBlockRequest.cs new file mode 100644 index 00000000..ead85b33 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/TableRowBlockRequest.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class TableRowBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("table_row")] + public Info TableRow { get; set; } + + public override BlockType Type => BlockType.TableRow; + + public class Info + { + [JsonProperty("cells")] + public IEnumerable> Cells { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/TemplateBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/TemplateBlockRequest.cs new file mode 100644 index 00000000..04c161d2 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/TemplateBlockRequest.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class TemplateBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("template")] + public Data Template { get; set; } + + public override BlockType Type => BlockType.Template; + + public class Data + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/ToDoBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/ToDoBlockRequest.cs new file mode 100644 index 00000000..ed7b09c3 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/ToDoBlockRequest.cs @@ -0,0 +1,30 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class ToDoBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("to_do")] + public Info ToDo { get; set; } + + public override BlockType Type => BlockType.ToDo; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("checked")] + public bool IsChecked { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/ToggleBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/ToggleBlockRequest.cs new file mode 100644 index 00000000..fbbbcd37 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/ToggleBlockRequest.cs @@ -0,0 +1,27 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using Newtonsoft.Json.Converters; + +namespace Notion.Client +{ + public class ToggleBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("toggle")] + public Info Toggle { get; set; } + + public override BlockType Type => BlockType.Toggle; + + public class Info + { + [JsonProperty("rich_text")] + public IEnumerable RichText { get; set; } + + [JsonProperty("color")] + [JsonConverter(typeof(StringEnumConverter))] + public Color? Color { get; set; } + + [JsonProperty("children")] + public IEnumerable Children { get; set; } + } + } +} diff --git a/Src/Notion.Client/Models/Blocks/Request/VideoBlockRequest.cs b/Src/Notion.Client/Models/Blocks/Request/VideoBlockRequest.cs new file mode 100644 index 00000000..238b2d26 --- /dev/null +++ b/Src/Notion.Client/Models/Blocks/Request/VideoBlockRequest.cs @@ -0,0 +1,12 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class VideoBlockRequest : BlockObjectRequest, IColumnChildrenBlockRequest, INonColumnBlockRequest + { + [JsonProperty("video")] + public FileObject Video { get; set; } + + public override BlockType Type => BlockType.Video; + } +} diff --git a/Src/Notion.Client/Models/CustomEmoji.cs b/Src/Notion.Client/Models/CustomEmoji.cs new file mode 100644 index 00000000..0f7a4c48 --- /dev/null +++ b/Src/Notion.Client/Models/CustomEmoji.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class CustomEmoji + { + [JsonProperty("id")] + public string Id { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("url")] + public string Url { get; set; } + } +} diff --git a/Src/Notion.Client/Models/CustomEmojiObject.cs b/Src/Notion.Client/Models/CustomEmojiObject.cs new file mode 100644 index 00000000..1faa4d74 --- /dev/null +++ b/Src/Notion.Client/Models/CustomEmojiObject.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class CustomEmojiObject : IPageIcon + { + [JsonProperty("custom_emoji")] + public CustomEmoji CustomEmoji { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + } +} diff --git a/Src/Notion.Client/Models/Database/Database.cs b/Src/Notion.Client/Models/Database/Database.cs index 33da29b7..be288ea0 100644 --- a/Src/Notion.Client/Models/Database/Database.cs +++ b/Src/Notion.Client/Models/Database/Database.cs @@ -4,7 +4,7 @@ namespace Notion.Client { - public class Database : IObject, IObjectModificationData + public class Database : IObject, IObjectModificationData, IWikiDatabase { [JsonProperty("title")] public List Title { get; set; } @@ -27,11 +27,8 @@ public class Database : IObject, IObjectModificationData [JsonProperty("url")] public string Url { get; set; } - /// - /// The archived status of the database. - /// - [JsonProperty("archived")] - public bool Archived { get; set; } + [JsonProperty("in_trash")] + public bool InTrash { get; set; } [JsonProperty("is_inline")] public bool IsInline { get; set; } @@ -52,5 +49,11 @@ public class Database : IObject, IObjectModificationData public PartialUser CreatedBy { get; set; } public PartialUser LastEditedBy { get; set; } + + /// + /// The public page URL if the page has been published to the web. Otherwise, null. + /// + [JsonProperty("public_url")] + public string PublicUrl { get; set; } } } diff --git a/Src/Notion.Client/Models/Database/IWikiDatabase.cs b/Src/Notion.Client/Models/Database/IWikiDatabase.cs new file mode 100644 index 00000000..59969175 --- /dev/null +++ b/Src/Notion.Client/Models/Database/IWikiDatabase.cs @@ -0,0 +1,12 @@ +using JsonSubTypes; +using Newtonsoft.Json; + +namespace Notion.Client +{ + [JsonConverter(typeof(JsonSubtypes), "object")] + [JsonSubtypes.KnownSubTypeAttribute(typeof(Page), ObjectType.Page)] + [JsonSubtypes.KnownSubTypeAttribute(typeof(Database), ObjectType.Database)] + public interface IWikiDatabase : IObject + { + } +} diff --git a/Src/Notion.Client/Models/Database/Properties/ButtonProperty.cs b/Src/Notion.Client/Models/Database/Properties/ButtonProperty.cs new file mode 100644 index 00000000..983138e2 --- /dev/null +++ b/Src/Notion.Client/Models/Database/Properties/ButtonProperty.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; +using System.Threading.Tasks; + +namespace Notion.Client +{ + public class ButtonProperty : Property + { + public override PropertyType Type => PropertyType.Button; + + [JsonProperty("button")] + public Dictionary Button { get; set; } + } +} diff --git a/Src/Notion.Client/Models/Database/Properties/Property.cs b/Src/Notion.Client/Models/Database/Properties/Property.cs index 163b7ef0..6392bbaf 100644 --- a/Src/Notion.Client/Models/Database/Properties/Property.cs +++ b/Src/Notion.Client/Models/Database/Properties/Property.cs @@ -25,6 +25,8 @@ namespace Notion.Client [JsonSubtypes.KnownSubTypeAttribute(typeof(StatusProperty), PropertyType.Status)] [JsonSubtypes.KnownSubTypeAttribute(typeof(TitleProperty), PropertyType.Title)] [JsonSubtypes.KnownSubTypeAttribute(typeof(UrlProperty), PropertyType.Url)] + [JsonSubtypes.KnownSubTypeAttribute(typeof(UniqueIdProperty), PropertyType.UniqueId)] + [JsonSubtypes.KnownSubTypeAttribute(typeof(ButtonProperty), PropertyType.Button)] public class Property { [JsonProperty("id")] diff --git a/Src/Notion.Client/Models/Database/Properties/PropertyType.cs b/Src/Notion.Client/Models/Database/Properties/PropertyType.cs index e4db339a..206f9fe2 100644 --- a/Src/Notion.Client/Models/Database/Properties/PropertyType.cs +++ b/Src/Notion.Client/Models/Database/Properties/PropertyType.cs @@ -67,6 +67,12 @@ public enum PropertyType LastEditedTime, [EnumMember(Value = "status")] - Status + Status, + + [EnumMember(Value = "unique_id")] + UniqueId, + + [EnumMember(Value = "button")] + Button, } } diff --git a/Src/Notion.Client/Models/Database/Properties/UniqueIdProperty.cs b/Src/Notion.Client/Models/Database/Properties/UniqueIdProperty.cs new file mode 100644 index 00000000..4a9e9d5d --- /dev/null +++ b/Src/Notion.Client/Models/Database/Properties/UniqueIdProperty.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class UniqueIdProperty : Property + { + public override PropertyType Type => PropertyType.UniqueId; + + [JsonProperty("unique_id")] + public Dictionary UniqueId { get; set; } + } +} + diff --git a/Src/Notion.Client/Models/Database/RichText/RichTextMention.cs b/Src/Notion.Client/Models/Database/RichText/RichTextMention.cs index 02f6beb0..5a5b6d0c 100644 --- a/Src/Notion.Client/Models/Database/RichText/RichTextMention.cs +++ b/Src/Notion.Client/Models/Database/RichText/RichTextMention.cs @@ -25,7 +25,7 @@ public class Mention public ObjectId Database { get; set; } [JsonProperty("date")] - public DatePropertyValue Date { get; set; } + public Date Date { get; set; } } public class ObjectId diff --git a/Src/Notion.Client/Models/File/FIleInput/ExternalFileInput.cs b/Src/Notion.Client/Models/File/FIleInput/ExternalFileInput.cs index 9c258ceb..6e2627cf 100644 --- a/Src/Notion.Client/Models/File/FIleInput/ExternalFileInput.cs +++ b/Src/Notion.Client/Models/File/FIleInput/ExternalFileInput.cs @@ -1,4 +1,5 @@ -using Newtonsoft.Json; +using System.Collections.Generic; +using Newtonsoft.Json; namespace Notion.Client { @@ -7,6 +8,12 @@ public class ExternalFileInput : IFileObjectInput [JsonProperty("external")] public Data External { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("caption")] + public IEnumerable Caption { get; set; } + public class Data { [JsonProperty("url")] diff --git a/Src/Notion.Client/Models/File/FIleInput/IFileObjectInput.cs b/Src/Notion.Client/Models/File/FIleInput/IFileObjectInput.cs index 31df1573..5883b7c1 100644 --- a/Src/Notion.Client/Models/File/FIleInput/IFileObjectInput.cs +++ b/Src/Notion.Client/Models/File/FIleInput/IFileObjectInput.cs @@ -1,6 +1,14 @@ -namespace Notion.Client +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace Notion.Client { public interface IFileObjectInput { + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("caption")] + public IEnumerable Caption { get; set; } } } diff --git a/Src/Notion.Client/Models/File/FIleInput/UploadedFileInput.cs b/Src/Notion.Client/Models/File/FIleInput/UploadedFileInput.cs index 52d7934d..949df10c 100644 --- a/Src/Notion.Client/Models/File/FIleInput/UploadedFileInput.cs +++ b/Src/Notion.Client/Models/File/FIleInput/UploadedFileInput.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Newtonsoft.Json; namespace Notion.Client @@ -8,6 +9,12 @@ public class UploadedFileInput : IFileObjectInput [JsonProperty("file")] public Data File { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("caption")] + public IEnumerable Caption { get; set; } + public class Data { [JsonProperty("url")] diff --git a/Src/Notion.Client/Models/File/FileObject.cs b/Src/Notion.Client/Models/File/FileObject.cs index 9ed206de..0ac30a6c 100644 --- a/Src/Notion.Client/Models/File/FileObject.cs +++ b/Src/Notion.Client/Models/File/FileObject.cs @@ -12,6 +12,12 @@ public abstract class FileObject : IPageIcon [JsonProperty("caption")] public IEnumerable Caption { get; set; } + /// + /// The name of the file block, as shown in the Notion UI. Note that the UI may auto-append .pdf or other extensions. + /// + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("type")] public virtual string Type { get; set; } } diff --git a/Src/Notion.Client/Models/Filters/TimestampLastEditedTimeFilter.cs b/Src/Notion.Client/Models/Filters/TimestampLastEditedTimeFilter.cs index 695183cc..1cd22974 100644 --- a/Src/Notion.Client/Models/Filters/TimestampLastEditedTimeFilter.cs +++ b/Src/Notion.Client/Models/Filters/TimestampLastEditedTimeFilter.cs @@ -39,7 +39,7 @@ public TimestampLastEditedTimeFilter( } [JsonProperty("timestamp")] - public string Timestamp => "last_modified_time"; + public string Timestamp => "last_edited_time"; [JsonProperty("last_edited_time")] public DateFilter.Condition LastEditedTime { get; set; } diff --git a/Src/Notion.Client/Models/Page/IPageIcon.cs b/Src/Notion.Client/Models/Page/IPageIcon.cs index b9ebea56..5d692185 100644 --- a/Src/Notion.Client/Models/Page/IPageIcon.cs +++ b/Src/Notion.Client/Models/Page/IPageIcon.cs @@ -5,6 +5,7 @@ namespace Notion.Client { [JsonConverter(typeof(JsonSubtypes), "type")] [JsonSubtypes.KnownSubTypeAttribute(typeof(EmojiObject), "emoji")] + [JsonSubtypes.KnownSubTypeAttribute(typeof(CustomEmojiObject), "custom_emoji")] [JsonSubtypes.KnownSubTypeAttribute(typeof(FileObject), "file")] [JsonSubtypes.KnownSubTypeAttribute(typeof(FileObject), "external")] public interface IPageIcon diff --git a/Src/Notion.Client/Models/Page/Page.cs b/Src/Notion.Client/Models/Page/Page.cs index 996bdd46..fe4eb0de 100644 --- a/Src/Notion.Client/Models/Page/Page.cs +++ b/Src/Notion.Client/Models/Page/Page.cs @@ -4,7 +4,7 @@ namespace Notion.Client { - public class Page : IObject, IObjectModificationData + public class Page : IObject, IObjectModificationData, IWikiDatabase { /// /// The parent of this page. Can be a database, page, or workspace. @@ -13,10 +13,10 @@ public class Page : IObject, IObjectModificationData public IPageParent Parent { get; set; } /// - /// The archived status of the page. + /// Indicates whether the page is currently in the trash. /// - [JsonProperty("archived")] - public bool IsArchived { get; set; } + [JsonProperty("in_trash")] + public bool InTrash { get; set; } /// /// Property values of this page. @@ -67,5 +67,11 @@ public class Page : IObject, IObjectModificationData public PartialUser CreatedBy { get; set; } public PartialUser LastEditedBy { get; set; } + + /// + /// The public page URL if the page has been published to the web. Otherwise, null. + /// + [JsonProperty("public_url")] + public string PublicUrl { get; set; } } } diff --git a/Src/Notion.Client/Models/PaginatedList.cs b/Src/Notion.Client/Models/PaginatedList.cs index e126125a..d0b1107f 100644 --- a/Src/Notion.Client/Models/PaginatedList.cs +++ b/Src/Notion.Client/Models/PaginatedList.cs @@ -25,5 +25,8 @@ public class PaginatedList [JsonProperty("next_cursor")] public string NextCursor { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } } } diff --git a/Src/Notion.Client/Models/PropertyValue/ButtonPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/ButtonPropertyValue.cs new file mode 100644 index 00000000..61ae9fc4 --- /dev/null +++ b/Src/Notion.Client/Models/PropertyValue/ButtonPropertyValue.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class ButtonPropertyValue : PropertyValue + { + public override PropertyValueType Type => PropertyValueType.Button; + + [JsonProperty("button")] + public ButtonValue Button { get; set; } + + public class ButtonValue { } + } +} diff --git a/Src/Notion.Client/Models/PropertyValue/DateCustomConverter.cs b/Src/Notion.Client/Models/PropertyValue/DateCustomConverter.cs new file mode 100644 index 00000000..37b84bc5 --- /dev/null +++ b/Src/Notion.Client/Models/PropertyValue/DateCustomConverter.cs @@ -0,0 +1,81 @@ +using System; +using System.Globalization; +using Newtonsoft.Json; + +namespace Notion.Client +{ + public class DateCustomConverter : JsonConverter + { + private const string DateFormat = "yyyy-MM-dd"; + private const string DateTimeFormat = "yyyy-MM-ddTHH:mm:ssZ"; + + public override Date ReadJson(JsonReader reader, Type objectType, Date existingValue, bool hasExistingValue, + JsonSerializer serializer) + { + var jsonObject = serializer.Deserialize(reader); + + if (jsonObject == null) + { + return null; + } + + var date = new Date + { + Start = ParseDateTime(jsonObject.Start, out bool includeTime), + End = ParseDateTime(jsonObject.End, out _), + TimeZone = jsonObject.TimeZone, + IncludeTime = includeTime, + }; + + return date; + } + + public override void WriteJson(JsonWriter writer, Date value, JsonSerializer serializer) + { + if (value is null) + { + writer.WriteNull(); + + return; + } + + writer.WriteStartObject(); + + if (value.Start.HasValue) + { + string startFormat = value.IncludeTime ? DateTimeFormat : DateFormat; + writer.WritePropertyName("start"); + writer.WriteValue(value.Start.Value.ToString(startFormat, CultureInfo.InvariantCulture)); + } + + if (value.End.HasValue) + { + string endFormat = value.IncludeTime ? DateTimeFormat : DateFormat; + writer.WritePropertyName("end"); + writer.WriteValue(value.End.Value.ToString(endFormat, CultureInfo.InvariantCulture)); + } + + if (!string.IsNullOrEmpty(value.TimeZone)) + { + writer.WritePropertyName("time_zone"); + writer.WriteValue(value.TimeZone); + } + + writer.WriteEndObject(); + } + + private static DateTime? ParseDateTime(string dateTimeString, out bool includeTime) + { + includeTime = false; + + if (string.IsNullOrEmpty(dateTimeString)) + { + return null; + } + + includeTime = dateTimeString.Contains("T") || dateTimeString.Contains(" "); + + return DateTimeOffset.Parse(dateTimeString, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal).UtcDateTime; + } + } +} diff --git a/Src/Notion.Client/Models/PropertyValue/DateJsonObject.cs b/Src/Notion.Client/Models/PropertyValue/DateJsonObject.cs new file mode 100644 index 00000000..b33b58c7 --- /dev/null +++ b/Src/Notion.Client/Models/PropertyValue/DateJsonObject.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + internal class DateJsonObject + { + [JsonProperty("start")] + public string Start { get; set; } + + [JsonProperty("end")] + public string End { get; set; } + + [JsonProperty("time_zone")] + public string TimeZone { get; set; } + } +} diff --git a/Src/Notion.Client/Models/PropertyValue/DatePropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/DatePropertyValue.cs index 22715b2e..66f9c6b6 100644 --- a/Src/Notion.Client/Models/PropertyValue/DatePropertyValue.cs +++ b/Src/Notion.Client/Models/PropertyValue/DatePropertyValue.cs @@ -1,5 +1,6 @@ using System; using Newtonsoft.Json; +using Newtonsoft.Json.Converters; namespace Notion.Client { @@ -20,19 +21,22 @@ public class DatePropertyValue : PropertyValue /// /// Date value object. /// + [JsonConverter(typeof(DateCustomConverter))] public class Date { /// /// Start date with optional time. /// [JsonProperty("start")] - public DateTime? Start { get; set; } + [JsonConverter(typeof(IsoDateTimeConverter))] + public DateTimeOffset? Start { get; set; } /// /// End date with optional time. /// [JsonProperty("end")] - public DateTime? End { get; set; } + [JsonConverter(typeof(IsoDateTimeConverter))] + public DateTimeOffset? End { get; set; } /// /// Optional time zone information for start and end. Possible values are extracted from the IANA database and they are @@ -40,5 +44,11 @@ public class Date /// [JsonProperty("time_zone")] public string TimeZone { get; set; } + + /// + /// Whether to include time + /// + [JsonIgnore] + public bool IncludeTime { get; set; } = true; } } diff --git a/Src/Notion.Client/Models/PropertyValue/EmailPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/EmailPropertyValue.cs index 5a8e2c7a..a8ac4806 100644 --- a/Src/Notion.Client/Models/PropertyValue/EmailPropertyValue.cs +++ b/Src/Notion.Client/Models/PropertyValue/EmailPropertyValue.cs @@ -12,7 +12,7 @@ public class EmailPropertyValue : PropertyValue /// /// Describes an email address. /// - [JsonProperty("email")] + [JsonProperty("email", NullValueHandling = NullValueHandling.Include)] public string Email { get; set; } } } diff --git a/Src/Notion.Client/Models/PropertyValue/NumberPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/NumberPropertyValue.cs index 15f9c768..a709e4f5 100644 --- a/Src/Notion.Client/Models/PropertyValue/NumberPropertyValue.cs +++ b/Src/Notion.Client/Models/PropertyValue/NumberPropertyValue.cs @@ -12,7 +12,7 @@ public class NumberPropertyValue : PropertyValue /// /// Value of number /// - [JsonProperty("number")] + [JsonProperty("number", NullValueHandling = NullValueHandling.Include)] public double? Number { get; set; } } } diff --git a/Src/Notion.Client/Models/PropertyValue/PhoneNumberPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/PhoneNumberPropertyValue.cs index 79a24dac..f7c37a19 100644 --- a/Src/Notion.Client/Models/PropertyValue/PhoneNumberPropertyValue.cs +++ b/Src/Notion.Client/Models/PropertyValue/PhoneNumberPropertyValue.cs @@ -12,7 +12,7 @@ public class PhoneNumberPropertyValue : PropertyValue /// /// Phone number value /// - [JsonProperty("phone_number")] + [JsonProperty("phone_number", NullValueHandling = NullValueHandling.Include)] public string PhoneNumber { get; set; } } } diff --git a/Src/Notion.Client/Models/PropertyValue/PropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/PropertyValue.cs index c423be82..e1289046 100644 --- a/Src/Notion.Client/Models/PropertyValue/PropertyValue.cs +++ b/Src/Notion.Client/Models/PropertyValue/PropertyValue.cs @@ -29,6 +29,9 @@ namespace Notion.Client [JsonSubtypes.KnownSubTypeAttribute(typeof(StatusPropertyValue), PropertyValueType.Status)] [JsonSubtypes.KnownSubTypeAttribute(typeof(TitlePropertyValue), PropertyValueType.Title)] [JsonSubtypes.KnownSubTypeAttribute(typeof(UrlPropertyValue), PropertyValueType.Url)] + [JsonSubtypes.KnownSubTypeAttribute(typeof(UniqueIdPropertyValue), PropertyValueType.UniqueId)] + [JsonSubtypes.KnownSubTypeAttribute(typeof(ButtonPropertyValue), PropertyValueType.Button)] + [JsonSubtypes.KnownSubTypeAttribute(typeof(VerificationPropertyValue), PropertyValueType.Verification)] [SuppressMessage("ReSharper", "UnusedMember.Global")] [SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")] public class PropertyValue diff --git a/Src/Notion.Client/Models/PropertyValue/PropertyValueType.cs b/Src/Notion.Client/Models/PropertyValue/PropertyValueType.cs index 96ddaf77..574138e9 100644 --- a/Src/Notion.Client/Models/PropertyValue/PropertyValueType.cs +++ b/Src/Notion.Client/Models/PropertyValue/PropertyValueType.cs @@ -70,6 +70,15 @@ public enum PropertyValueType LastEditedBy, [EnumMember(Value = "status")] - Status + Status, + + [EnumMember(Value = "unique_id")] + UniqueId, + + [EnumMember(Value = "verification")] + Verification, + + [EnumMember(Value = "button")] + Button, } } diff --git a/Src/Notion.Client/Models/PropertyValue/RollupPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/RollupPropertyValue.cs index 2eb104fb..92020c61 100644 --- a/Src/Notion.Client/Models/PropertyValue/RollupPropertyValue.cs +++ b/Src/Notion.Client/Models/PropertyValue/RollupPropertyValue.cs @@ -35,7 +35,7 @@ public class RollupValue /// Date rollup property values contain a date property value. /// [JsonProperty("date")] - public DatePropertyValue Date { get; set; } + public Date Date { get; set; } /// /// Array rollup property values contain an array of element objects. diff --git a/Src/Notion.Client/Models/PropertyValue/StatusPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/StatusPropertyValue.cs index 29a64ef7..921ff810 100644 --- a/Src/Notion.Client/Models/PropertyValue/StatusPropertyValue.cs +++ b/Src/Notion.Client/Models/PropertyValue/StatusPropertyValue.cs @@ -47,7 +47,7 @@ public enum StatusColor public override PropertyValueType Type => PropertyValueType.Status; - [JsonProperty("status")] + [JsonProperty("status", NullValueHandling = NullValueHandling.Include)] public Data Status { get; set; } public class Data diff --git a/Src/Notion.Client/Models/PropertyValue/UniqueIdPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/UniqueIdPropertyValue.cs new file mode 100644 index 00000000..0ce1c704 --- /dev/null +++ b/Src/Notion.Client/Models/PropertyValue/UniqueIdPropertyValue.cs @@ -0,0 +1,28 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + /// + /// Unique Id property value object. + /// + public class UniqueIdPropertyValue : PropertyValue + { + public override PropertyValueType Type => PropertyValueType.UniqueId; + + /// + /// Unique Id property of database item + /// + [JsonProperty("unique_id")] + public UniqueIdValue UniqueId { get; set; } + } + + public class UniqueIdValue + { + [JsonProperty("prefix")] + public string Prefix { get; set; } + + [JsonProperty("number")] + public double? Number { get; set; } + } +} + diff --git a/Src/Notion.Client/Models/PropertyValue/UrlPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/UrlPropertyValue.cs index 5fc8f8bb..91b5f71c 100644 --- a/Src/Notion.Client/Models/PropertyValue/UrlPropertyValue.cs +++ b/Src/Notion.Client/Models/PropertyValue/UrlPropertyValue.cs @@ -12,7 +12,7 @@ public class UrlPropertyValue : PropertyValue /// /// Describes a web address /// - [JsonProperty("url")] + [JsonProperty("url", NullValueHandling = NullValueHandling.Include)] public string Url { get; set; } } } diff --git a/Src/Notion.Client/Models/PropertyValue/VerificationPropertyValue.cs b/Src/Notion.Client/Models/PropertyValue/VerificationPropertyValue.cs new file mode 100644 index 00000000..8cdb9bae --- /dev/null +++ b/Src/Notion.Client/Models/PropertyValue/VerificationPropertyValue.cs @@ -0,0 +1,39 @@ +using Newtonsoft.Json; + +namespace Notion.Client +{ + /// + /// Verification property value. + /// + public class VerificationPropertyValue : PropertyValue + { + public override PropertyValueType Type => PropertyValueType.Verification; + + /// + /// Object containing verification type-specific data. + /// + [JsonProperty("verification")] + public Info Verification { get; set; } + + public class Info + { + /// + /// The state of verification. Possible values are "verified" and "unverified". + /// + [JsonProperty("state")] + public string State { get; set; } + + /// + /// Describes the user who verified this page. + /// + [JsonProperty("verified_by")] + public User VerifiedBy { get; set; } + + /// + /// Date verification property values contain a date property value. + /// + [JsonProperty("date")] + public Date Date { get; set; } + } + } +} diff --git a/Src/Notion.Client/Notion.Client.csproj b/Src/Notion.Client/Notion.Client.csproj index 4a614c64..2a6555c3 100644 --- a/Src/Notion.Client/Notion.Client.csproj +++ b/Src/Notion.Client/Notion.Client.csproj @@ -1,7 +1,7 @@ - + - 4.1.0-preview + 4.3.0-preview netstandard2.0 9.0 @@ -16,7 +16,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Src/Notion.Client/NotionAPIErrorCode.cs b/Src/Notion.Client/NotionAPIErrorCode.cs index 1dc822bd..2ca2573f 100644 --- a/Src/Notion.Client/NotionAPIErrorCode.cs +++ b/Src/Notion.Client/NotionAPIErrorCode.cs @@ -15,6 +15,9 @@ public enum NotionAPIErrorCode [EnumMember(Value = "invalid_request")] InvalidRequest, + [EnumMember(Value = "invalid_grant")] + InvalidGrant, + [EnumMember(Value = "validation_error")] ValidationError, @@ -39,7 +42,16 @@ public enum NotionAPIErrorCode [EnumMember(Value = "internal_server_error")] InternalServerError, + [EnumMember(Value = "bad_gateway")] + BadGateway, + [EnumMember(Value = "service_unavailable")] - ServiceUnavailable + ServiceUnavailable, + + [EnumMember(Value = "database_connection_unavailable")] + DatabaseConnectionUnavailable, + + [EnumMember(Value = "gateway_timeout")] + GatewayTimeout } } diff --git a/Src/Notion.Client/NotionApiException.cs b/Src/Notion.Client/NotionApiException.cs index d11fea6f..cf62f1b6 100644 --- a/Src/Notion.Client/NotionApiException.cs +++ b/Src/Notion.Client/NotionApiException.cs @@ -5,7 +5,7 @@ namespace Notion.Client { [SuppressMessage("ReSharper", "MemberCanBePrivate.Global")] - public sealed class NotionApiException : Exception + public class NotionApiException : Exception { public NotionApiException(HttpStatusCode statusCode, NotionAPIErrorCode? notionAPIErrorCode, string message) : this(statusCode, notionAPIErrorCode, message, null) @@ -21,6 +21,11 @@ private NotionApiException( NotionAPIErrorCode = notionAPIErrorCode; StatusCode = statusCode; + InitializeData(); + } + + private void InitializeData() + { Data.Add("StatusCode", StatusCode); Data.Add("NotionApiErrorCode", NotionAPIErrorCode); } diff --git a/Src/Notion.Client/NotionApiRateLimitException.cs b/Src/Notion.Client/NotionApiRateLimitException.cs new file mode 100644 index 00000000..c10cce2d --- /dev/null +++ b/Src/Notion.Client/NotionApiRateLimitException.cs @@ -0,0 +1,22 @@ +using System; +using System.Net; + +namespace Notion.Client +{ + public sealed class NotionApiRateLimitException : NotionApiException + { + public TimeSpan? RetryAfter { get; } + + public NotionApiRateLimitException( + HttpStatusCode statusCode, + NotionAPIErrorCode? notionAPIErrorCode, + string message, + TimeSpan? retryAfter) + : base(statusCode, notionAPIErrorCode, message) + { + RetryAfter = retryAfter; + + Data.Add("RetryAfter", retryAfter); + } + } +} diff --git a/Src/Notion.Client/NotionClient.cs b/Src/Notion.Client/NotionClient.cs index ca0c1334..2fc6f479 100644 --- a/Src/Notion.Client/NotionClient.cs +++ b/Src/Notion.Client/NotionClient.cs @@ -5,6 +5,8 @@ namespace Notion.Client [SuppressMessage("ReSharper", "UnusedMemberInSuper.Global")] public interface INotionClient { + IAuthenticationClient AuthenticationClient { get; } + IUsersClient Users { get; } IDatabasesClient Databases { get; } @@ -29,7 +31,8 @@ public NotionClient( IPagesClient pages, ISearchClient search, ICommentsClient comments, - IBlocksClient blocks) + IBlocksClient blocks, + IAuthenticationClient authenticationClient) { RestClient = restClient; Users = users; @@ -38,8 +41,11 @@ public NotionClient( Search = search; Comments = comments; Blocks = blocks; + AuthenticationClient = authenticationClient; } + public IAuthenticationClient AuthenticationClient { get; } + public IUsersClient Users { get; } public IDatabasesClient Databases { get; } diff --git a/Src/Notion.Client/NotionClientFactory.cs b/Src/Notion.Client/NotionClientFactory.cs index e82ba2de..e66f0781 100644 --- a/Src/Notion.Client/NotionClientFactory.cs +++ b/Src/Notion.Client/NotionClientFactory.cs @@ -12,8 +12,9 @@ public static NotionClient Create(ClientOptions options) , new DatabasesClient(restClient) , new PagesClient(restClient) , new SearchClient(restClient) - , blocks: new BlocksClient(restClient) - , comments: new CommentsClient(restClient) + , new CommentsClient(restClient) + , new BlocksClient(restClient) + , new AuthenticationClient(restClient) ); } } diff --git a/Src/Notion.Client/RestClient/IRestClient.cs b/Src/Notion.Client/RestClient/IRestClient.cs index 7abb8553..b8ffe5b3 100644 --- a/Src/Notion.Client/RestClient/IRestClient.cs +++ b/Src/Notion.Client/RestClient/IRestClient.cs @@ -17,7 +17,7 @@ Task GetAsync( Task PostAsync( string uri, object body, - IDictionary queryParams = null, + IEnumerable> queryParams = null, IDictionary headers = null, JsonSerializerSettings serializerSettings = null, CancellationToken cancellationToken = default); diff --git a/Src/Notion.Client/RestClient/RestClient.cs b/Src/Notion.Client/RestClient/RestClient.cs index 307fcd7a..59a848f7 100644 --- a/Src/Notion.Client/RestClient/RestClient.cs +++ b/Src/Notion.Client/RestClient/RestClient.cs @@ -45,7 +45,7 @@ public async Task GetAsync( public async Task PostAsync( string uri, object body, - IDictionary queryParams = null, + IEnumerable> queryParams = null, IDictionary headers = null, JsonSerializerSettings serializerSettings = null, CancellationToken cancellationToken = default) @@ -112,6 +112,17 @@ private static async Task BuildException(HttpResponseMessage response try { errorResponse = JsonConvert.DeserializeObject(errorBody); + + if (errorResponse.ErrorCode == NotionAPIErrorCode.RateLimited) + { + var retryAfter = response.Headers.RetryAfter.Delta; + return new NotionApiRateLimitException( + response.StatusCode, + errorResponse.ErrorCode, + errorResponse.Message, + retryAfter + ); + } } catch (Exception ex) { @@ -125,7 +136,7 @@ private static async Task BuildException(HttpResponseMessage response private async Task SendAsync( string requestUri, HttpMethod httpMethod, - IDictionary queryParams = null, + IEnumerable> queryParams = null, IDictionary headers = null, Action attachContent = null, CancellationToken cancellationToken = default) @@ -176,7 +187,7 @@ private void EnsureHttpClient() _httpClient.BaseAddress = new Uri(_options.BaseUrl); } - private static string AddQueryString(string uri, IDictionary queryParams) + private static string AddQueryString(string uri, IEnumerable> queryParams) { return queryParams == null ? uri : QueryHelpers.AddQueryString(uri, queryParams); } diff --git a/Src/Notion.Client/http/QueryHelpers.cs b/Src/Notion.Client/http/QueryHelpers.cs index 530536f3..60de4e7b 100644 --- a/Src/Notion.Client/http/QueryHelpers.cs +++ b/Src/Notion.Client/http/QueryHelpers.cs @@ -45,7 +45,7 @@ public static string AddQueryString(string uri, IDictionary quer return AddQueryString(uri, (IEnumerable>)queryParams); } - private static string AddQueryString( + public static string AddQueryString( string uri, IEnumerable> queryParams) { diff --git a/Test/Notion.IntegrationTests/IBlocksClientTests.cs b/Test/Notion.IntegrationTests/BlocksClientTests.cs similarity index 67% rename from Test/Notion.IntegrationTests/IBlocksClientTests.cs rename to Test/Notion.IntegrationTests/BlocksClientTests.cs index 6f10189c..5f1ca25b 100644 --- a/Test/Notion.IntegrationTests/IBlocksClientTests.cs +++ b/Test/Notion.IntegrationTests/BlocksClientTests.cs @@ -8,29 +8,39 @@ namespace Notion.IntegrationTests; -public class IBlocksClientTests : IntegrationTestBase +public class IBlocksClientTests : IntegrationTestBase, IAsyncLifetime { - [Fact] - public async Task AppendChildrenAsync_AppendsBlocksGivenBlocks() + private Page _page = null!; + + public async Task InitializeAsync() { - var page = await Client.Pages.CreateAsync( + _page = await Client.Pages.CreateAsync( PagesCreateParametersBuilder.Create( new ParentPageInput { PageId = ParentPageId } ).Build() ); + } + + public async Task DisposeAsync() + { + await Client.Pages.UpdateAsync(_page.Id, new PagesUpdateParameters { InTrash = true }); + } + [Fact] + public async Task AppendChildrenAsync_AppendsBlocksGivenBlocks() + { var blocks = await Client.Blocks.AppendChildrenAsync( - page.Id, - new BlocksAppendChildrenParameters + new BlockAppendChildrenRequest { - Children = new List + BlockId = _page.Id, + Children = new List { - new BreadcrumbBlock { Breadcrumb = new BreadcrumbBlock.Data() }, - new DividerBlock { Divider = new DividerBlock.Data() }, - new TableOfContentsBlock { TableOfContents = new TableOfContentsBlock.Data() }, - new CalloutBlock + new BreadcrumbBlockRequest { Breadcrumb = new BreadcrumbBlockRequest.Data() }, + new DividerBlockRequest { Divider = new DividerBlockRequest.Data() }, + new TableOfContentsBlockRequest { TableOfContents = new TableOfContentsBlockRequest.Data() }, + new CalloutBlockRequest { - Callout = new CalloutBlock.Info + Callout = new CalloutBlockRequest.Info { RichText = new List { @@ -43,93 +53,73 @@ public async Task AppendChildrenAsync_AppendsBlocksGivenBlocks() ); blocks.Results.Should().HaveCount(4); - - // cleanup - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); } [Fact] public async Task UpdateBlockAsync_UpdatesGivenBlock() { - var page = await Client.Pages.CreateAsync( - PagesCreateParametersBuilder.Create( - new ParentPageInput { PageId = ParentPageId } - ).Build() - ); - var blocks = await Client.Blocks.AppendChildrenAsync( - page.Id, - new BlocksAppendChildrenParameters + new BlockAppendChildrenRequest { - Children = new List { new BreadcrumbBlock { Breadcrumb = new BreadcrumbBlock.Data() } } + BlockId = _page.Id, + Children = new List + { + new BreadcrumbBlockRequest { Breadcrumb = new BreadcrumbBlockRequest.Data() } + } } ); var blockId = blocks.Results.First().Id; await Client.Blocks.UpdateAsync(blockId, new BreadcrumbUpdateBlock()); - blocks = await Client.Blocks.RetrieveChildrenAsync(page.Id); - blocks.Results.Should().HaveCount(1); + var updatedBlocks = + await Client.Blocks.RetrieveChildrenAsync(new BlockRetrieveChildrenRequest { BlockId = _page.Id }); - // cleanup - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); + updatedBlocks.Results.Should().HaveCount(1); } [Fact] public async Task DeleteAsync_DeleteBlockWithGivenId() { - var page = await Client.Pages.CreateAsync( - PagesCreateParametersBuilder.Create( - new ParentPageInput { PageId = ParentPageId } - ).Build() - ); - var blocks = await Client.Blocks.AppendChildrenAsync( - page.Id, - new BlocksAppendChildrenParameters + new BlockAppendChildrenRequest { - Children = new List + BlockId = _page.Id, + Children = new List { - new DividerBlock { Divider = new DividerBlock.Data() }, - new TableOfContentsBlock { TableOfContents = new TableOfContentsBlock.Data() } + new DividerBlockRequest { Divider = new DividerBlockRequest.Data() }, + new TableOfContentsBlockRequest { TableOfContents = new TableOfContentsBlockRequest.Data() } } } ); blocks.Results.Should().HaveCount(2); - - // cleanup - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); } [Theory] [MemberData(nameof(BlockData))] public async Task UpdateAsync_UpdatesGivenBlock( - IBlock block, IUpdateBlock updateBlock, Action assert) + IBlockObjectRequest block, IUpdateBlock updateBlock, Action assert) { - var page = await Client.Pages.CreateAsync( - PagesCreateParametersBuilder.Create( - new ParentPageInput { PageId = ParentPageId } - ).Build() - ); - var blocks = await Client.Blocks.AppendChildrenAsync( - page.Id, - new BlocksAppendChildrenParameters { Children = new List { block } } + new BlockAppendChildrenRequest + { + BlockId = _page.Id, + Children = new List { block } + } ); var blockId = blocks.Results.First().Id; await Client.Blocks.UpdateAsync(blockId, updateBlock); - blocks = await Client.Blocks.RetrieveChildrenAsync(page.Id); - blocks.Results.Should().HaveCount(1); + var updatedBlocks = + await Client.Blocks.RetrieveChildrenAsync(new BlockRetrieveChildrenRequest { BlockId = _page.Id }); - var updatedBlock = blocks.Results.First(); + updatedBlocks.Results.Should().HaveCount(1); - assert.Invoke(updatedBlock, Client); + var updatedBlock = updatedBlocks.Results.First(); - // cleanup - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); + assert.Invoke(updatedBlock, Client); } private static IEnumerable BlockData() @@ -138,9 +128,9 @@ private static IEnumerable BlockData() { new object[] { - new BookmarkBlock + new BookmarkBlockRequest { - Bookmark = new BookmarkBlock.Info + Bookmark = new BookmarkBlockRequest.Info { Url = "https://developers.notion.com/reference/rich-text", Caption = new List @@ -160,7 +150,7 @@ private static IEnumerable BlockData() } } }, - new Action((block, client) => + new Action((block, _) => { var updatedBlock = (BookmarkBlock)block; Assert.Equal("https://github.com/notion-dotnet/notion-sdk-net", updatedBlock.Bookmark.Url); @@ -169,9 +159,9 @@ private static IEnumerable BlockData() }, new object[] { - new EquationBlock { Equation = new EquationBlock.Info { Expression = "e=mc^3" } }, + new EquationBlockRequest { Equation = new EquationBlockRequest.Info { Expression = "e=mc^3" } }, new EquationUpdateBlock { Equation = new EquationUpdateBlock.Info { Expression = "e=mc^2" } }, - new Action((block, client) => + new Action((block, _) => { var updatedBlock = (EquationBlock)block; Assert.Equal("e=mc^2", updatedBlock.Equation.Expression); @@ -179,16 +169,16 @@ private static IEnumerable BlockData() }, new object[] { - new DividerBlock { Divider = new DividerBlock.Data() }, new DividerUpdateBlock(), new Action( - block => - { - Assert.NotNull(block); - _ = Assert.IsType(block); - }) + new DividerBlockRequest { Divider = new DividerBlockRequest.Data() }, new DividerUpdateBlock(), + new Action((block, client) => + { + Assert.NotNull(block); + _ = Assert.IsType(block); + }) }, new object[] { - new AudioBlock + new AudioBlockRequest { Audio = new ExternalFile { @@ -208,10 +198,10 @@ private static IEnumerable BlockData() } } }, - new Action((block, client) => + new Action((block, _) => { block.Should().NotBeNull(); - + block.Should().BeOfType().Subject .Audio.Should().BeOfType().Subject .External.Url.Should().Be("https://www.soundhelix.com/examples/mp3/SoundHelix-Song-3.mp3"); @@ -219,7 +209,7 @@ private static IEnumerable BlockData() }, new object[] { - new TableOfContentsBlock { TableOfContents = new TableOfContentsBlock.Data() }, + new TableOfContentsBlockRequest { TableOfContents = new TableOfContentsBlockRequest.Data() }, new TableOfContentsUpdateBlock(), new Action((block, client) => { Assert.NotNull(block); @@ -228,9 +218,9 @@ private static IEnumerable BlockData() }, new object[] { - new CalloutBlock + new CalloutBlockRequest { - Callout = new CalloutBlock.Info + Callout = new CalloutBlockRequest.Info { RichText = new List { @@ -248,19 +238,19 @@ private static IEnumerable BlockData() } } }, - new Action((block, client) => + new Action((block, _) => { Assert.NotNull(block); var calloutBlock = Assert.IsType(block); - + Assert.Equal("Test 2", calloutBlock.Callout.RichText.OfType().First().Text.Content); }) }, new object[] { - new QuoteBlock + new QuoteBlockRequest { - Quote = new QuoteBlock.Info + Quote = new QuoteBlockRequest.Info { RichText = new List { @@ -278,17 +268,17 @@ private static IEnumerable BlockData() } } }, - new Action((block, client) => + new Action((block, _) => { Assert.NotNull(block); var quoteBlock = Assert.IsType(block); - + Assert.Equal("Test 2", quoteBlock.Quote.RichText.OfType().First().Text.Content); }) }, new object[] { - new ImageBlock + new ImageBlockRequest { Image = new ExternalFile { @@ -309,21 +299,21 @@ private static IEnumerable BlockData() } } }, - new Action((block, client) => + new Action((block, _) => { Assert.NotNull(block); var imageBlock = Assert.IsType(block); var imageFile = Assert.IsType(imageBlock.Image); - + Assert.Equal("https://www.iaspaper.net/wp-content/uploads/2017/09/TNEA-Online-Application.jpg", imageFile.External.Url); }) }, new object[] { - new EmbedBlock + new EmbedBlockRequest { - Embed = new EmbedBlock.Info + Embed = new EmbedBlockRequest.Info { Url = "https://zephoria.com/wp-content/uploads/2014/08/online-community.jpg" } @@ -335,63 +325,18 @@ private static IEnumerable BlockData() Url = "https://www.iaspaper.net/wp-content/uploads/2017/09/TNEA-Online-Application.jpg" } }, - new Action((block, client) => + new Action((block, _) => { Assert.NotNull(block); var embedBlock = Assert.IsType(block); - + Assert.Equal("https://www.iaspaper.net/wp-content/uploads/2017/09/TNEA-Online-Application.jpg", embedBlock.Embed.Url); }) }, new object[] { - new TemplateBlock - { - Template = new TemplateBlock.Data - { - RichText = new List - { - new RichTextText { Text = new Text { Content = "Test Template" } } - }, - Children = new List - { - new EmbedBlock - { - Embed = new EmbedBlock.Info - { - Url - = "https://zephoria.com/wp-content/uploads/2014/08/online-community.jpg" - } - } - } - } - }, - new TemplateUpdateBlock - { - Template = new TemplateUpdateBlock.Info - { - RichText = new List - { - new RichTextTextInput { Text = new Text { Content = "Test Template 2" } } - } - } - }, - new Action((block, client) => - { - Assert.NotNull(block); - var templateBlock = Assert.IsType(block); - - Assert.Single(templateBlock.Template.RichText); - Assert.Null(templateBlock.Template.Children); - - Assert.Equal("Test Template 2", - templateBlock.Template.RichText.OfType().First().Text.Content); - }) - }, - new object[] - { - new LinkToPageBlock + new LinkToPageBlockRequest { LinkToPage = new PageParent { @@ -403,13 +348,13 @@ private static IEnumerable BlockData() { LinkToPage = new ParentPageInput { PageId = "3c357473a28149a488c010d2b245a589" } }, - new Action((block, client) => + new Action((block, _) => { Assert.NotNull(block); var linkToPageBlock = Assert.IsType(block); - + var pageParent = Assert.IsType(linkToPageBlock.LinkToPage); - + // TODO: Currently the api doesn't allow to update the link_to_page block type // This will change to updated ID once api start to support Assert.Equal(Guid.Parse("533578e3edf14c0a91a9da6b09bac3ee"), Guid.Parse(pageParent.PageId)); @@ -417,16 +362,16 @@ private static IEnumerable BlockData() }, new object[] { - new TableBlock + new TableBlockRequest { - Table = new TableBlock.Info + Table = new TableBlockRequest.Info { TableWidth = 1, Children = new[] { - new TableRowBlock + new TableRowBlockRequest { - TableRow = new TableRowBlock.Info + TableRow = new TableRowBlockRequest.Info { Cells = new[] { @@ -443,7 +388,9 @@ private static IEnumerable BlockData() var tableBlock = block.Should().NotBeNull().And.BeOfType().Subject; tableBlock.HasChildren.Should().BeTrue(); - var children = client.Blocks.RetrieveChildrenAsync(tableBlock.Id).GetAwaiter().GetResult(); + var children = client.Blocks + .RetrieveChildrenAsync(new BlockRetrieveChildrenRequest { BlockId = tableBlock.Id }) + .GetAwaiter().GetResult(); children.Results.Should().ContainSingle() .Subject.Should().BeOfType() @@ -452,6 +399,54 @@ private static IEnumerable BlockData() .Subject.Should().BeOfType() .Subject.Text.Content.Should().Be("Data"); }) + }, + new object[] + { + new FileBlockRequest { + File = new ExternalFile + { + Name = "Test file", + External = new ExternalFile.Info + { + Url = "https://www.iaspaper.net/wp-content/uploads/2017/09/TNEA-Online-Application.jpg" + }, + Caption = new List + { + new RichTextTextInput { Text = new Text { Content = "Test file" } } + } + } + }, + new FileUpdateBlock + { + File = new ExternalFileInput + { + Name = "Test file name", + External = new ExternalFileInput.Data + { + Url = "https://www.iaspaper.net/wp-content/uploads/2017/09/TNEA-Online-Application.jpg" + }, + Caption = new List + { + new RichTextTextInput { Text = new Text { Content = "Test file caption" } } + } + } + }, + new Action((block, client) => + { + var fileBlock = block.Should().NotBeNull().And.BeOfType().Subject; + fileBlock.HasChildren.Should().BeFalse(); + + var file = fileBlock.File.Should().NotBeNull().And.BeOfType().Subject; + + // NOTE: The name of the file block, as shown in the Notion UI. Note that the UI may auto-append .pdf or other extensions. + file.Name.Should().Be("Test file name.jpg"); + + file.External.Should().NotBeNull(); + file.External.Url.Should().Be("https://www.iaspaper.net/wp-content/uploads/2017/09/TNEA-Online-Application.jpg"); + file.Caption.Should().NotBeNull().And.ContainSingle() + .Subject.Should().BeOfType().Subject + .Text.Content.Should().Be("Test file caption"); + }) } }; } diff --git a/Test/Notion.IntegrationTests/CommentsClientTests.cs b/Test/Notion.IntegrationTests/CommentsClientTests.cs index f2a84d21..fac1fce1 100644 --- a/Test/Notion.IntegrationTests/CommentsClientTests.cs +++ b/Test/Notion.IntegrationTests/CommentsClientTests.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Notion.Client; @@ -7,22 +6,22 @@ namespace Notion.IntegrationTests; -public class CommentsClientTests : IntegrationTestBase, IDisposable +public class CommentsClientTests : IntegrationTestBase, IAsyncLifetime { - private readonly Page _page; + private Page _page = null!; - public CommentsClientTests() + public async Task InitializeAsync() { - _page = Client.Pages.CreateAsync( + _page = await Client.Pages.CreateAsync( PagesCreateParametersBuilder.Create( new ParentPageInput { PageId = ParentPageId } ).Build() - ).Result; + ); } - public void Dispose() + public async Task DisposeAsync() { - Client.Pages.UpdateAsync(_page.Id, new PagesUpdateParameters { Archived = true }).Wait(); + await Client.Pages.UpdateAsync(_page.Id, new PagesUpdateParameters { InTrash = true }); } [Fact] @@ -78,7 +77,7 @@ public async Task ShouldCreateADiscussionComment() ); // Arrange - Assert.Null(response.Parent); + Assert.NotNull(response.Parent); Assert.NotNull(response.Id); Assert.Equal(comment.DiscussionId, response.DiscussionId); diff --git a/Test/Notion.IntegrationTests/DatabasesClientTests.cs b/Test/Notion.IntegrationTests/DatabasesClientTests.cs new file mode 100644 index 00000000..7abd7b4e --- /dev/null +++ b/Test/Notion.IntegrationTests/DatabasesClientTests.cs @@ -0,0 +1,188 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using FluentAssertions; +using Notion.Client; +using Xunit; + +namespace Notion.IntegrationTests; + +public class DatabasesClientTests : IntegrationTestBase, IAsyncLifetime +{ + private Page _page = null!; + + public async Task InitializeAsync() + { + _page = await Client.Pages.CreateAsync( + PagesCreateParametersBuilder.Create( + new ParentPageInput { PageId = ParentPageId } + ).Build() + ); + } + + public async Task DisposeAsync() + { + await Client.Pages.UpdateAsync(_page.Id, new PagesUpdateParameters { InTrash = true }); + } + + [Fact] + public async Task QueryDatabase() + { + // Arrange + var createdDatabase = await CreateDatabaseWithAPageAsync("Test List"); + + // Act + var response = await Client.Databases.QueryAsync(createdDatabase.Id, new DatabasesQueryParameters()); + + // Assert + response.Results.Should().NotBeNull(); + var page = response.Results.Should().ContainSingle().Subject.As(); + + page.Properties["Name"].As() + .Title.Cast().First() + .Text.Content.Should().Be("Test Title"); + } + + [Fact] + public async Task UpdateDatabaseRelationProperties() + { + // Arrange + var createdSourceDatabase = await CreateDatabaseWithAPageAsync("Test Relation Source"); + var createdDestinationDatabase = await CreateDatabaseWithAPageAsync("Test Relation Destination"); + + // Act + var response = await Client.Databases.UpdateAsync(createdDestinationDatabase.Id, + new DatabasesUpdateParameters + { + Properties = new Dictionary + { + { + "Single Relation", + new RelationUpdatePropertySchema + { + Relation = new SinglePropertyRelation + { + DatabaseId = createdSourceDatabase.Id, + SingleProperty = new Dictionary() + } + } + }, + { + "Dual Relation", + new RelationUpdatePropertySchema + { + Relation = new DualPropertyRelation + { + DatabaseId = createdSourceDatabase.Id, + DualProperty = new DualPropertyRelation.Data() + } + } + } + } + }); + + // Assert + response.Properties.Should().NotBeNull(); + + response.Properties.Should().ContainKey("Single Relation"); + var singleRelation = response.Properties["Single Relation"].As().Relation; + singleRelation.Should().BeEquivalentTo( + new SinglePropertyRelation + { + DatabaseId = createdSourceDatabase.Id, + SingleProperty = new Dictionary() + }); + + response.Properties.Should().ContainKey("Dual Relation"); + var dualRelation = response.Properties["Dual Relation"].As().Relation; + dualRelation.DatabaseId.Should().Be(createdSourceDatabase.Id); + dualRelation.Type.Should().Be(RelationType.Dual); + dualRelation.Should().BeOfType(); + } + + private async Task CreateDatabaseWithAPageAsync(string databaseName) + { + var createDbRequest = new DatabasesCreateParameters + { + Title = new List + { + new RichTextTextInput + { + Text = new Text + { + Content = databaseName, + Link = null + } + } + }, + Properties = new Dictionary + { + { "Name", new TitlePropertySchema { Title = new Dictionary() } }, + }, + Parent = new ParentPageInput { PageId = _page.Id } + }; + + var createdDatabase = await Client.Databases.CreateAsync(createDbRequest); + + var pagesCreateParameters = PagesCreateParametersBuilder + .Create(new DatabaseParentInput { DatabaseId = createdDatabase.Id }) + .AddProperty("Name", + new TitlePropertyValue + { + Title = new List + { + new RichTextText { Text = new Text { Content = "Test Title" } } + } + }) + .Build(); + + await Client.Pages.CreateAsync(pagesCreateParameters); + + return createdDatabase; + } + + [Fact] + public async Task Verify_mention_date_property_parsed_properly() + { + // Arrange + var createDbRequest = new DatabasesCreateParameters + { + Title = new List + { + new RichTextTextInput + { + Text = new Text + { + Content = "Test DB", + Link = null + } + }, + new RichTextMentionInput + { + Mention = new MentionInput + { + Date = new Date + { + Start = DateTime.UtcNow, + End = DateTime.UtcNow.AddDays(1) + } + } + } + }, + Properties = new Dictionary + { + { "Name", new TitlePropertySchema { Title = new Dictionary() } }, + }, + Parent = new ParentPageInput { PageId = _page.Id } + }; + + // Act + var createdDatabase = await Client.Databases.CreateAsync(createDbRequest); + + // Assert + var mention = createdDatabase.Title.OfType().First().Mention; + mention.Date.Start.Should().NotBeNull(); + mention.Date.End.Should().NotBeNull(); + } +} diff --git a/Test/Notion.IntegrationTests/Notion.IntegrationTests.csproj b/Test/Notion.IntegrationTests/Notion.IntegrationTests.csproj index 42a1975a..1284ee3f 100644 --- a/Test/Notion.IntegrationTests/Notion.IntegrationTests.csproj +++ b/Test/Notion.IntegrationTests/Notion.IntegrationTests.csproj @@ -15,7 +15,7 @@ - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Test/Notion.IntegrationTests/IPageClientTests.cs b/Test/Notion.IntegrationTests/PageClientTests.cs similarity index 62% rename from Test/Notion.IntegrationTests/IPageClientTests.cs rename to Test/Notion.IntegrationTests/PageClientTests.cs index f0833be5..4938bcfa 100644 --- a/Test/Notion.IntegrationTests/IPageClientTests.cs +++ b/Test/Notion.IntegrationTests/PageClientTests.cs @@ -8,13 +8,69 @@ namespace Notion.IntegrationTests; -public class IPageClientTests : IntegrationTestBase +public class PageClientTests : IntegrationTestBase, IAsyncLifetime { + private Page _page = null!; + private Database _database = null!; + + public async Task InitializeAsync() + { + // Create a page + _page = await Client.Pages.CreateAsync( + PagesCreateParametersBuilder.Create( + new ParentPageInput { PageId = ParentPageId } + ).Build() + ); + + // Create a database + var createDbRequest = new DatabasesCreateParameters + { + Title = new List + { + new RichTextTextInput + { + Text = new Text + { + Content = "Test List", + Link = null + } + } + }, + Properties = new Dictionary + { + { "Name", new TitlePropertySchema { Title = new Dictionary() } }, + { + "TestSelect", + new SelectPropertySchema + { + Select = new OptionWrapper + { + Options = new List + { + new() { Name = "Red" }, + new() { Name = "Blue" } + } + } + } + }, + { "Number", new NumberPropertySchema { Number = new Number { Format = "number" } } } + }, + Parent = new ParentPageInput { PageId = _page.Id } + }; + + _database = await Client.Databases.CreateAsync(createDbRequest); + } + + public async Task DisposeAsync() + { + await Client.Pages.UpdateAsync(_page.Id, new PagesUpdateParameters { InTrash = true }); + } + [Fact] public async Task CreateAsync_CreatesANewPage() { var pagesCreateParameters = PagesCreateParametersBuilder - .Create(new DatabaseParentInput { DatabaseId = ParentDatabaseId }) + .Create(new DatabaseParentInput { DatabaseId = _database.Id }) .AddProperty("Name", new TitlePropertyValue { @@ -30,10 +86,10 @@ public async Task CreateAsync_CreatesANewPage() page.Should().NotBeNull(); page.Parent.Should().BeOfType().Which - .DatabaseId.Should().Be(ParentDatabaseId); + .DatabaseId.Should().Be(_database.Id); page.Properties.Should().ContainKey("Name"); - var pageProperty = page.Properties["Name"].Should().BeOfType().Subject; + var pageProperty = page.Properties["Name"].Should().BeOfType().Subject; var titleProperty = (ListPropertyItem)await Client.Pages.RetrievePagePropertyItemAsync( @@ -44,15 +100,14 @@ var titleProperty }); titleProperty.Results.First().As().Title.PlainText.Should().Be("Test Page Title"); - - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); } [Fact] public async Task Bug_unable_to_create_page_with_select_property() { + // Arrange var pagesCreateParameters = PagesCreateParametersBuilder - .Create(new DatabaseParentInput { DatabaseId = ParentDatabaseId }) + .Create(new DatabaseParentInput { DatabaseId = _database.Id }) .AddProperty("Name", new TitlePropertyValue { @@ -62,37 +117,33 @@ public async Task Bug_unable_to_create_page_with_select_property() } }) .AddProperty("TestSelect", - new SelectPropertyValue { Select = new SelectOption { Id = "dfbfbe65-6f67-4876-9f75-699124334d06" } }) + new SelectPropertyValue { Select = new SelectOption { Name = "Blue" } }) .Build(); + // Act var page = await Client.Pages.CreateAsync(pagesCreateParameters); + // Asserts page.Should().NotBeNull(); page.Parent.Should().BeOfType().Which - .DatabaseId.Should().Be(ParentDatabaseId); + .DatabaseId.Should().Be(_database.Id); page.Properties.Should().ContainKey("Name"); - var pageProperty = page.Properties["Name"].Should().BeOfType().Subject; + var titlePropertyValue = page.Properties["Name"].Should().BeOfType().Subject; + titlePropertyValue.Title.First().PlainText.Should().Be("Test Page Title"); - var titleProperty - = (ListPropertyItem)await Client.Pages.RetrievePagePropertyItemAsync( - new RetrievePropertyItemParameters - { - PageId = page.Id, - PropertyId = pageProperty.Id - }); - - titleProperty.Results.First().As().Title.PlainText.Should().Be("Test Page Title"); - - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); + page.Properties.Should().ContainKey("TestSelect"); + var selectPropertyValue = page.Properties["TestSelect"].Should().BeOfType().Subject; + selectPropertyValue.Select.Name.Should().Be("Blue"); } [Fact] public async Task Test_RetrievePagePropertyItemAsync() { + // Arrange var pagesCreateParameters = PagesCreateParametersBuilder - .Create(new DatabaseParentInput { DatabaseId = ParentDatabaseId }) + .Create(new DatabaseParentInput { DatabaseId = _database.Id }) .AddProperty("Name", new TitlePropertyValue { @@ -105,12 +156,14 @@ public async Task Test_RetrievePagePropertyItemAsync() var page = await Client.Pages.CreateAsync(pagesCreateParameters); + // Act var property = await Client.Pages.RetrievePagePropertyItemAsync(new RetrievePropertyItemParameters { PageId = page.Id, PropertyId = "title" }); + // Assert property.Should().NotBeNull(); property.Should().BeOfType(); @@ -125,16 +178,14 @@ public async Task Test_RetrievePagePropertyItemAsync() titleProperty.Title.PlainText.Should().Be("Test Page Title"); }); - - // cleanup - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); } [Fact] public async Task Test_UpdatePageProperty_with_date_as_null() { - // setup - add property to db and create a page with the property having a date + // Arrange + // Add property Date property to database const string DatePropertyName = "Test Date Property"; var updateDatabaseParameters = new DatabasesUpdateParameters @@ -149,8 +200,9 @@ public async Task Test_UpdatePageProperty_with_date_as_null() } }; + // Create a page with the property having a date var pagesCreateParameters = PagesCreateParametersBuilder - .Create(new DatabaseParentInput { DatabaseId = ParentDatabaseId }) + .Create(new DatabaseParentInput { DatabaseId = _database.Id }) .AddProperty("Name", new TitlePropertyValue { @@ -164,16 +216,17 @@ public async Task Test_UpdatePageProperty_with_date_as_null() { Date = new Date { - Start = Convert.ToDateTime("2020-12-08T12:00:00Z"), - End = Convert.ToDateTime("2025-12-08T12:00:00Z") + Start = DateTimeOffset.Parse("2024-06-26T00:00:00.000+01:00"), + End = DateTimeOffset.Parse("2025-12-08").Date } }) .Build(); - await Client.Databases.UpdateAsync(ParentDatabaseId, updateDatabaseParameters); + await Client.Databases.UpdateAsync(_database.Id, updateDatabaseParameters); var page = await Client.Pages.CreateAsync(pagesCreateParameters); + // Act var setDate = (DatePropertyItem)await Client.Pages.RetrievePagePropertyItemAsync( new RetrievePropertyItemParameters { @@ -182,27 +235,29 @@ public async Task Test_UpdatePageProperty_with_date_as_null() } ); - setDate?.Date?.Start.Should().Be(Convert.ToDateTime("2020-12-08T12:00:00Z")); - - // verify - IDictionary testProps = new Dictionary(); + // Assert + setDate?.Date?.Start.Should().Be(DateTimeOffset.Parse("2024-06-26T00:00:00.000+01:00")); + setDate?.Date?.End.Should().Be(DateTimeOffset.Parse("2025-12-08T00:00:00.000+01:00")); - testProps.Add(DatePropertyName, new DatePropertyValue { Date = null }); + var pageUpdateParameters = new PagesUpdateParameters + { + Properties = new Dictionary + { + { DatePropertyName, new DatePropertyValue { Date = null } } + } + }; - var updatedPage = - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Properties = testProps }); + var updatedPage = await Client.Pages.UpdateAsync(page.Id, pageUpdateParameters); var verifyDate = (DatePropertyItem)await Client.Pages.RetrievePagePropertyItemAsync( new RetrievePropertyItemParameters { PageId = page.Id, PropertyId = updatedPage.Properties[DatePropertyName].Id - }); + } + ); verifyDate?.Date.Should().BeNull(); - - //cleanup - await Client.Blocks.DeleteAsync(page.Id); } [Fact] @@ -210,14 +265,16 @@ public async Task Bug_Unable_To_Parse_NumberPropertyItem() { // Arrange var pagesCreateParameters = PagesCreateParametersBuilder - .Create(new DatabaseParentInput { DatabaseId = ParentDatabaseId }).AddProperty("Name", + .Create(new DatabaseParentInput { DatabaseId = _database.Id }) + .AddProperty("Name", new TitlePropertyValue { Title = new List { new RichTextText { Text = new Text { Content = "Test Page Title" } } } - }).AddProperty("Number", new NumberPropertyValue { Number = 200.00 }).Build(); + }) + .AddProperty("Number", new NumberPropertyValue { Number = 200.00 }).Build(); // Act var page = await Client.Pages.CreateAsync(pagesCreateParameters); @@ -225,7 +282,7 @@ public async Task Bug_Unable_To_Parse_NumberPropertyItem() // Assert Assert.NotNull(page); var pageParent = Assert.IsType(page.Parent); - Assert.Equal(ParentDatabaseId, pageParent.DatabaseId); + Assert.Equal(_database.Id, pageParent.DatabaseId); var titleProperty = (ListPropertyItem)await Client.Pages.RetrievePagePropertyItemAsync( new RetrievePropertyItemParameters @@ -244,8 +301,6 @@ public async Task Bug_Unable_To_Parse_NumberPropertyItem() }); Assert.Equal(200.00, numberProperty.Number); - - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); } [Fact] @@ -255,19 +310,11 @@ public async Task Bug_exception_when_attempting_to_set_select_property_to_nothin var databaseCreateRequest = new DatabasesCreateParameters { Title = - new List - { - new RichTextTextInput() { Text = new Text { Content = "Test Database" } } - }, - Parent = new ParentPageInput() { PageId = ParentPageId }, + new List { new RichTextTextInput { Text = new Text { Content = "Test Database" } } }, + Parent = new ParentPageInput() { PageId = _page.Id }, Properties = new Dictionary { - { - "title", new TitlePropertySchema - { - Title = new Dictionary() - } - }, + { "title", new TitlePropertySchema { Title = new Dictionary() } }, { "Colors1", new SelectPropertySchema @@ -330,15 +377,58 @@ public async Task Bug_exception_when_attempting_to_set_select_property_to_nothin }; var updatedPage = await Client.Pages.UpdateAsync(page.Id, updatePageRequest); - + // Assert page.Properties["Colors1"].As().Select.Name.Should().Be("Red"); page.Properties["Colors2"].As().Select.Name.Should().Be("Green"); - + updatedPage.Properties["Colors1"].As().Select.Name.Should().Be("Blue"); updatedPage.Properties["Colors2"].As().Select.Should().BeNull(); + } + + [Fact] + public async Task Verify_date_property_is_parsed_correctly_in_mention_object() + { + var pageRequest = PagesCreateParametersBuilder + .Create(new DatabaseParentInput { DatabaseId = _database.Id }) + .AddProperty("Name", + new TitlePropertyValue + { + Title = new List + { + new RichTextMention() + { + Mention = new Mention() + { + Date = new Date() + { + Start = DateTime.UtcNow + } + } + } + } + }) + .Build(); + + var page = await Client.Pages.CreateAsync(pageRequest); + + page.Should().NotBeNull(); + + page.Parent.Should().BeOfType().Which + .DatabaseId.Should().Be(_database.Id); + + page.Properties.Should().ContainKey("Name"); + var pageProperty = page.Properties["Name"].Should().BeOfType().Subject; + + var titleProperty = (ListPropertyItem)await Client.Pages.RetrievePagePropertyItemAsync( + new RetrievePropertyItemParameters + { + PageId = page.Id, + PropertyId = pageProperty.Id + }); - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); - await Client.Databases.UpdateAsync(database.Id, new DatabasesUpdateParameters { Archived = true }); + var mention = titleProperty.Results.First().As().Title.As().Mention; + mention.Date.Start.Should().NotBeNull(); + mention.Date.End.Should().BeNull(); } } diff --git a/Test/Notion.IntegrationTests/PageWithPageParentTests.cs b/Test/Notion.IntegrationTests/PageWithPageParentTests.cs index cca3356b..3c6d3c6c 100644 --- a/Test/Notion.IntegrationTests/PageWithPageParentTests.cs +++ b/Test/Notion.IntegrationTests/PageWithPageParentTests.cs @@ -7,12 +7,12 @@ namespace Notion.IntegrationTests; -public class PageWithPageParentTests : IntegrationTestBase +public class PageWithPageParentTests : IntegrationTestBase, IAsyncLifetime { - [Fact] - public async Task Update_Title_Of_Page() + private Page _page = null!; + + public async Task InitializeAsync() { - // Arrange var pagesCreateParameters = PagesCreateParametersBuilder .Create(new ParentPageInput() { PageId = ParentPageId }) .AddProperty("title", @@ -24,8 +24,18 @@ public async Task Update_Title_Of_Page() } }).Build(); - var page = await Client.Pages.CreateAsync(pagesCreateParameters); + _page = await Client.Pages.CreateAsync(pagesCreateParameters); + } + public async Task DisposeAsync() + { + await Client.Pages.UpdateAsync(_page.Id, new PagesUpdateParameters { InTrash = true }); + } + + [Fact] + public async Task Update_Title_Of_Page() + { + // Arrange var updatePage = new PagesUpdateParameters() { Properties = new Dictionary @@ -44,7 +54,7 @@ public async Task Update_Title_Of_Page() }; // Act - var updatedPage = await Client.Pages.UpdateAsync(page.Id, updatePage); + var updatedPage = await Client.Pages.UpdateAsync(_page.Id, updatePage); // Assert var titleProperty = (ListPropertyItem)await Client.Pages.RetrievePagePropertyItemAsync( @@ -55,9 +65,6 @@ public async Task Update_Title_Of_Page() } ); - Assert.Equal("Page Title Updated", titleProperty.Results.First().As().Title.PlainText); - - // Clean Up - await Client.Pages.UpdateAsync(page.Id, new PagesUpdateParameters { Archived = true }); + titleProperty.Results.First().As().Title.PlainText.Should().Be("Page Title Updated"); } } diff --git a/Test/Notion.UnitTests/BlocksClientTests.cs b/Test/Notion.UnitTests/BlocksClientTests.cs index 5d436b97..e2cc8603 100644 --- a/Test/Notion.UnitTests/BlocksClientTests.cs +++ b/Test/Notion.UnitTests/BlocksClientTests.cs @@ -34,11 +34,14 @@ public async Task RetrieveBlockChildren() ); // Act - var childrenResult = await _client.RetrieveChildrenAsync(blockId, new BlocksRetrieveChildrenParameters()); + var childrenResult = await _client.RetrieveChildrenAsync(new BlockRetrieveChildrenRequest + { + BlockId = blockId + }); // Assert var children = childrenResult.Results; - children.Should().HaveCount(8); + children.Should().HaveCount(9); } [Fact] @@ -57,13 +60,14 @@ public async Task AppendBlockChildren() .WithBody(jsonData) ); - var parameters = new BlocksAppendChildrenParameters + var request = new BlockAppendChildrenRequest { - Children = new List + BlockId = blockId, + Children = new List { - new HeadingTwoBlock + new HeadingTwoBlockRequest { - Heading_2 = new HeadingTwoBlock.Info + Heading_2 = new HeadingTwoBlockRequest.Info { RichText = new List { @@ -71,9 +75,9 @@ public async Task AppendBlockChildren() } } }, - new ParagraphBlock + new ParagraphBlockRequest { - Paragraph = new ParagraphBlock.Info + Paragraph = new ParagraphBlockRequest.Info { RichText = new List { @@ -97,7 +101,7 @@ public async Task AppendBlockChildren() }; // Act - var blocksResult = await _client.AppendChildrenAsync(blockId, parameters); + var blocksResult = await _client.AppendChildrenAsync(request); // Assert var blocks = blocksResult.Results; @@ -180,6 +184,7 @@ public async Task UpdateAsync() block.Id.Should().Be(blockId); block.HasChildren.Should().BeFalse(); + block.InTrash.Should().BeFalse(); block.Type.Should().Be(BlockType.ToDo); var todoBlock = (ToDoBlock)block; diff --git a/Test/Notion.UnitTests/DatabasesClientTests.cs b/Test/Notion.UnitTests/DatabasesClientTests.cs index fc06ebc7..30deae75 100644 --- a/Test/Notion.UnitTests/DatabasesClientTests.cs +++ b/Test/Notion.UnitTests/DatabasesClientTests.cs @@ -64,8 +64,9 @@ public async Task QueryAsync() pagesPaginatedList.Results.Should().ContainSingle(); - foreach (var page in pagesPaginatedList.Results) + foreach (var iWikiDatabase in pagesPaginatedList.Results) { + var page = (Page)iWikiDatabase; page.Parent.Should().BeAssignableTo(); page.Object.Should().Be(ObjectType.Page); } @@ -476,8 +477,9 @@ var jsonData pagesPaginatedList.Results.Should().ContainSingle(); - foreach (var page in pagesPaginatedList.Results) + foreach (var iWikiDatabase in pagesPaginatedList.Results) { + var page = (Page)iWikiDatabase; page.Parent.Should().BeAssignableTo(); page.Object.Should().Be(ObjectType.Page); @@ -498,8 +500,22 @@ var jsonData }); //var formulaPropertyValue = (FormulaPropertyValue)page.Properties["FormulaProp"]; - formulaPropertyValue.Formula.Date.Start.Should().Be(DateTime.Parse("2021-06-28")); + formulaPropertyValue.Formula.Date.Start.Should().Be(DateTimeOffset.Parse("2021-06-28", null, System.Globalization.DateTimeStyles.AssumeUniversal).UtcDateTime); formulaPropertyValue.Formula.Date.End.Should().BeNull(); } } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public async Task RetrieveAsync_throws_argument_null_exception_if_database_id_is_null_or_empty(string databaseId) + { + // Arrange && Act + async Task Act() => await _client.RetrieveAsync(databaseId); + + // Assert + var exception = await Assert.ThrowsAsync(Act); + Assert.Equal("databaseId", exception.ParamName); + } } diff --git a/Test/Notion.UnitTests/DateCustomConverterTests.cs b/Test/Notion.UnitTests/DateCustomConverterTests.cs new file mode 100644 index 00000000..81c33f57 --- /dev/null +++ b/Test/Notion.UnitTests/DateCustomConverterTests.cs @@ -0,0 +1,219 @@ +using System; +using System.IO; +using Newtonsoft.Json; +using Notion.Client; +using Xunit; + +namespace NotionUnitTests.PropertyValue; + +public class DateCustomConverterTests +{ + private readonly DateCustomConverter _converter = new(); + private readonly JsonSerializer _serializer = new(); + + [Fact] + public void Serialize_null_writes_null() + { + // Arrange + Date date = null; + var stringWriter = new StringWriter(); + var jsonWriter = new JsonTextWriter(stringWriter); + + // Act + _converter.WriteJson(jsonWriter, date, _serializer); + jsonWriter.Flush(); + + // Assert + Assert.Equal("null", stringWriter.ToString()); + } + + [Fact] + public void Serialize_start_date_only_produces_correct_json() + { + // Arrange + var date = new Date + { + Start = new DateTimeOffset(2023, 5, 15, 0, 0, 0, TimeSpan.Zero), + IncludeTime = false + }; + + // Act + var json = JsonConvert.SerializeObject(date); + + // Assert + Assert.Contains("\"start\":\"2023-05-15\"", json); + Assert.DoesNotContain("\"end\":", json); + Assert.DoesNotContain("\"time_zone\":", json); + } + + [Fact] + public void Serialize_start_and_end_dates_produces_correct_json() + { + // Arrange + var date = new Date + { + Start = new DateTimeOffset(2023, 5, 15, 0, 0, 0, TimeSpan.Zero), + End = new DateTimeOffset(2023, 5, 20, 0, 0, 0, TimeSpan.Zero), + IncludeTime = false + }; + + // Act + var json = JsonConvert.SerializeObject(date); + + // Assert + Assert.Contains("\"start\":\"2023-05-15\"", json); + Assert.Contains("\"end\":\"2023-05-20\"", json); + Assert.DoesNotContain("\"time_zone\":", json); + } + + [Fact] + public void Serialize_with_time_included_formats_time_correctly() + { + // Arrange + var date = new Date + { + Start = new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero), + IncludeTime = true + }; + + // Act + var json = JsonConvert.SerializeObject(date); + + // Assert + Assert.Contains("\"start\":\"2023-05-15T14:30:45Z\"", json); + } + + [Fact] + public void Serialize_with_time_zone_includes_time_zone() + { + // Arrange + var date = new Date + { + Start = new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero), + TimeZone = "Europe/London", + IncludeTime = true + }; + + // Act + var json = JsonConvert.SerializeObject(date); + + // Assert + Assert.Contains("\"start\":\"2023-05-15T14:30:45Z\"", json); + Assert.Contains("\"time_zone\":\"Europe/London\"", json); + } + + [Fact] + public void Deserialize_null_returns_null() + { + // Arrange + const string Json = "null"; + + // Act + var result = JsonConvert.DeserializeObject(Json); + + // Assert + Assert.Null(result); + } + + [Fact] + public void Deserialize_start_date_only_returns_correct_date() + { + // Arrange + const string Json = "{\"start\":\"2023-05-15\"}"; + + // Act + var result = JsonConvert.DeserializeObject(Json); + + // Assert + Assert.NotNull(result); + Assert.Equal(new DateTimeOffset(2023, 5, 15, 0, 0, 0, TimeSpan.Zero), result.Start); + Assert.Null(result.End); + Assert.Null(result.TimeZone); + Assert.False(result.IncludeTime); + } + + [Fact] + public void Deserialize_with_time_sets_include_time_flag() + { + // Arrange + const string Json = "{\"start\":\"2023-05-15T14:30:45\"}"; + + // Act + var result = JsonConvert.DeserializeObject(Json); + + // Assert + Assert.NotNull(result); + Assert.Equal(new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero), result.Start); + Assert.True(result.IncludeTime); + } + + [Fact] + public void Deserialize_with_start_end_and_time_zone_returns_complete_date() + { + // Arrange + const string Json = "{\"start\":\"2023-05-15T14:30:45\",\"end\":\"2023-05-20T16:45:00\",\"time_zone\":\"America/New_York\"}"; + + // Act + var result = JsonConvert.DeserializeObject(Json); + + // Assert + Assert.NotNull(result); + Assert.Equal(new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero), result.Start); + Assert.Equal(new DateTimeOffset(2023, 5, 20, 16, 45, 0, TimeSpan.Zero), result.End); + Assert.Equal("America/New_York", result.TimeZone); + Assert.True(result.IncludeTime); + } + + [Fact] + public void Date_property_value_serialize_deserialize_maintains_data() + { + // Arrange + var datePropertyValue = new DatePropertyValue + { + Date = new Date + { + Start = new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero), + End = new DateTimeOffset(2023, 5, 20, 16, 45, 0, TimeSpan.Zero), + TimeZone = "Europe/Berlin", + IncludeTime = true + } + }; + + // Act + var json = JsonConvert.SerializeObject(datePropertyValue); + var result = JsonConvert.DeserializeObject(json); + + // Assert + Assert.NotNull(result); + Assert.Equal(PropertyValueType.Date, result.Type); + Assert.NotNull(result.Date); + Assert.Equal(datePropertyValue.Date.Start, result.Date.Start); + Assert.Equal(datePropertyValue.Date.End, result.Date.End); + Assert.Equal(datePropertyValue.Date.TimeZone, result.Date.TimeZone); + Assert.Equal(datePropertyValue.Date.IncludeTime, result.Date.IncludeTime); + } + + [Fact] + public void Round_trip_preserves_data() + { + // Arrange + var originalDate = new Date + { + Start = new DateTimeOffset(2023, 5, 15, 14, 30, 45, TimeSpan.Zero), + End = new DateTimeOffset(2023, 5, 20, 16, 45, 0, TimeSpan.Zero), + TimeZone = "Europe/Berlin", + IncludeTime = true + }; + + // Act + var json = JsonConvert.SerializeObject(originalDate); + var deserializedDate = JsonConvert.DeserializeObject(json); + + // Assert + Assert.NotNull(deserializedDate); + Assert.Equal(originalDate.Start, deserializedDate.Start); + Assert.Equal(originalDate.End, deserializedDate.End); + Assert.Equal(originalDate.TimeZone, deserializedDate.TimeZone); + Assert.True(deserializedDate.IncludeTime); + } +} diff --git a/Test/Notion.UnitTests/Notion.UnitTests.csproj b/Test/Notion.UnitTests/Notion.UnitTests.csproj index 30ff0791..6a33f5c0 100644 --- a/Test/Notion.UnitTests/Notion.UnitTests.csproj +++ b/Test/Notion.UnitTests/Notion.UnitTests.csproj @@ -8,15 +8,15 @@ - - + + - + runtime; build; native; contentfiles; analyzers; buildtransitive all - + runtime; build; native; contentfiles; analyzers; buildtransitive all @@ -51,7 +51,7 @@ Always - + Always diff --git a/Test/Notion.UnitTests/PagesClientTests.cs b/Test/Notion.UnitTests/PagesClientTests.cs index adbb9443..4dc75380 100644 --- a/Test/Notion.UnitTests/PagesClientTests.cs +++ b/Test/Notion.UnitTests/PagesClientTests.cs @@ -39,7 +39,7 @@ public async Task RetrieveAsync() page.Id.Should().Be(pageId); page.Parent.Type.Should().Be(ParentType.DatabaseId); ((DatabaseParent)page.Parent).DatabaseId.Should().Be("48f8fee9-cd79-4180-bc2f-ec0398253067"); - page.IsArchived.Should().BeFalse(); + page.InTrash.Should().BeFalse(); } [Fact] @@ -72,7 +72,7 @@ public async Task CreateAsync() page.Url.Should().NotBeNullOrEmpty(); page.Properties.Should().HaveCount(1); page.Properties.First().Key.Should().Be("Name"); - page.IsArchived.Should().BeFalse(); + page.InTrash.Should().BeFalse(); page.Parent.Should().NotBeNull(); ((DatabaseParent)page.Parent).DatabaseId.Should().Be("3c357473-a281-49a4-88c0-10d2b245a589"); } @@ -172,7 +172,7 @@ public async Task UpdatePageAsync() var page = await _client.UpdateAsync(pageId, pagesUpdateParameters); page.Id.Should().Be(pageId); - page.IsArchived.Should().BeFalse(); + page.InTrash.Should().BeFalse(); page.Properties.Should().HaveCount(2); var updatedProperty = page.Properties.First(x => x.Key == "In stock"); @@ -187,14 +187,14 @@ public async Task UpdatePageAsync() } [Fact] - public async Task ArchivePageAsync() + public async Task TrashPageAsync() { var pageId = "251d2b5f-268c-4de2-afe9-c71ff92ca95c"; var propertyId = "{>U;"; var path = ApiEndpoints.PagesApiUrls.UpdateProperties(pageId); - var jsonData = await File.ReadAllTextAsync("data/pages/ArchivePageResponse.json"); + var jsonData = await File.ReadAllTextAsync("data/pages/TrashPageResponse.json"); Server.Given(CreatePatchRequestBuilder(path)) .RespondWith( @@ -211,7 +211,7 @@ public async Task ArchivePageAsync() var pagesUpdateParameters = new PagesUpdateParameters { - Archived = true, + InTrash = true, Properties = new Dictionary { { "In stock", new CheckboxPropertyValue { Checkbox = true } } @@ -221,8 +221,8 @@ public async Task ArchivePageAsync() var page = await _client.UpdateAsync(pageId, pagesUpdateParameters); page.Id.Should().Be(pageId); - page.IsArchived.Should().BeTrue(); - page.Properties.Should().HaveCount(2); + page.InTrash.Should().BeTrue(); + page.Properties.Should().HaveCount(3); var updatedProperty = page.Properties.First(x => x.Key == "In stock"); var checkboxPropertyValue = (CheckboxPropertyItem)await _client.RetrievePagePropertyItemAsync( diff --git a/Test/Notion.UnitTests/PropertyTests.cs b/Test/Notion.UnitTests/PropertyTests.cs index 07949131..55f6d1c1 100644 --- a/Test/Notion.UnitTests/PropertyTests.cs +++ b/Test/Notion.UnitTests/PropertyTests.cs @@ -26,6 +26,8 @@ public class PropertyTests [InlineData(typeof(SelectProperty), PropertyType.Select)] [InlineData(typeof(TitleProperty), PropertyType.Title)] [InlineData(typeof(UrlProperty), PropertyType.Url)] + [InlineData(typeof(UniqueIdProperty), PropertyType.UniqueId)] + [InlineData(typeof(ButtonProperty),PropertyType.Button)] public void TestPropertyType(Type type, PropertyType expectedPropertyType) { var typeInstance = (Property)Activator.CreateInstance(type); @@ -54,6 +56,8 @@ public void TestPropertyType(Type type, PropertyType expectedPropertyType) [InlineData(typeof(SelectProperty), "select")] [InlineData(typeof(TitleProperty), "title")] [InlineData(typeof(UrlProperty), "url")] + [InlineData(typeof(UniqueIdProperty), "unique_id")] + [InlineData(typeof(ButtonProperty), "button")] public void TestPropertyTypeText(Type type, string expectedPropertyType) { var typeInstance = (Property)Activator.CreateInstance(type); diff --git a/Test/Notion.UnitTests/SearchClientTest.cs b/Test/Notion.UnitTests/SearchClientTest.cs index 63fb4a94..a5a477cb 100644 --- a/Test/Notion.UnitTests/SearchClientTest.cs +++ b/Test/Notion.UnitTests/SearchClientTest.cs @@ -30,7 +30,7 @@ public async Task Search() .WithBody(jsonData) ); - var searchParameters = new SearchParameters + var searchParameters = new SearchRequest { Query = "External tasks", Sort = new SearchSort @@ -59,7 +59,7 @@ public async Task Search() obj.Object.Should().Be(ObjectType.Page); var page = (Page)obj; - page.IsArchived.Should().BeFalse(); + page.InTrash.Should().BeFalse(); page.Properties.Should().HaveCount(1); page.Parent.Should().BeAssignableTo(); ((DatabaseParent)page.Parent).DatabaseId.Should().Be("e6c6f8ff-c70e-4970-91ba-98f03e0d7fc6"); diff --git a/Test/Notion.UnitTests/data/blocks/AppendBlockChildrenResponse.json b/Test/Notion.UnitTests/data/blocks/AppendBlockChildrenResponse.json index 32612405..13966312 100644 --- a/Test/Notion.UnitTests/data/blocks/AppendBlockChildrenResponse.json +++ b/Test/Notion.UnitTests/data/blocks/AppendBlockChildrenResponse.json @@ -7,6 +7,7 @@ "created_time": "2021-03-16T16:31:00.000Z", "last_edited_time": "2021-03-16T16:32:00.000Z", "has_children": false, + "in_trash" : false, "type": "heading_2", "heading_2": { "rich_text": [ diff --git a/Test/Notion.UnitTests/data/blocks/RetrieveBlockChildrenResponse.json b/Test/Notion.UnitTests/data/blocks/RetrieveBlockChildrenResponse.json index 649185e7..b16dfc35 100644 --- a/Test/Notion.UnitTests/data/blocks/RetrieveBlockChildrenResponse.json +++ b/Test/Notion.UnitTests/data/blocks/RetrieveBlockChildrenResponse.json @@ -7,7 +7,7 @@ "created_time": "2021-05-29T16:22:00.000Z", "last_edited_time": "2021-05-29T16:22:00.000Z", "has_children": false, - "archived": false, + "in_trash": false, "type": "paragraph", "paragraph": { "rich_text": [] @@ -19,7 +19,7 @@ "created_time": "2021-05-30T09:25:00.000Z", "last_edited_time": "2021-05-30T09:25:00.000Z", "has_children": false, - "archived": false, + "in_trash": false, "type": "heading_2", "heading_2": { "rich_text": [ @@ -49,7 +49,7 @@ "created_time": "2021-05-30T09:36:00.000Z", "last_edited_time": "2021-05-30T09:36:00.000Z", "has_children": false, - "archived": false, + "in_trash": false, "type": "heading_2", "heading_2": { "rich_text": [ @@ -79,7 +79,7 @@ "created_time": "2021-08-18T21:12:00.000Z", "last_edited_time": "2021-08-18T21:12:00.000Z", "has_children": false, - "archived": false, + "in_trash": false, "type": "paragraph", "paragraph": { "rich_text": [] @@ -91,7 +91,7 @@ "created_time": "2021-08-18T23:00:00.000Z", "last_edited_time": "2021-08-18T23:00:00.000Z", "has_children": false, - "archived": false, + "in_trash": false, "type": "child_page", "child_page": { "title": "" @@ -103,7 +103,7 @@ "created_time": "2021-08-18T23:00:00.000Z", "last_edited_time": "2021-08-18T23:00:00.000Z", "has_children": false, - "archived": false, + "in_trash": false, "type": "child_page", "child_page": { "title": "" @@ -115,7 +115,7 @@ "created_time": "2021-09-09T05:22:00.000Z", "last_edited_time": "2021-09-09T05:22:00.000Z", "has_children": false, - "archived": false, + "in_trash": false, "type": "paragraph", "paragraph": { "rich_text": [] @@ -127,7 +127,7 @@ "created_time": "2022-05-03T13:28:00.000Z", "last_edited_time": "2022-05-03T13:29:00.000Z", "has_children": false, - "archived": false, + "in_trash": false, "type": "image", "image": { "caption": [ @@ -155,6 +155,33 @@ "expiry_time": "2022-05-11T17:55:32.613Z" } } + }, + { + "object": "block", + "id": "570d1df0-56c9-42f7-b8b8-5315323f82de", + "parent": { + "type": "page_id", + "page_id": "9b02f058-bd87-46c2-95d2-7496fb9075bb" + }, + "created_time": "2023-02-03T13:59:00.000Z", + "last_edited_time": "2023-02-03T13:59:00.000Z", + "created_by": { + "object": "user", + "id": "92e803ec-193c-4bcd-b28e-88365858d562" + }, + "last_edited_by": { + "object": "user", + "id": "92e803ec-193c-4bcd-b28e-88365858d562" + }, + "has_children": true, + "in_trash": false, + "type": "synced_block", + "synced_block": { + "synced_from": { + "type": "block_id", + "block_id": "d2f7e3f3-c553-410d-a5f9-dfc0c335b169" + } + } } ], "next_cursor": null, diff --git a/Test/Notion.UnitTests/data/blocks/RetrieveBlockResponse.json b/Test/Notion.UnitTests/data/blocks/RetrieveBlockResponse.json index 9c7deabe..6df6ddb2 100644 --- a/Test/Notion.UnitTests/data/blocks/RetrieveBlockResponse.json +++ b/Test/Notion.UnitTests/data/blocks/RetrieveBlockResponse.json @@ -4,6 +4,7 @@ "created_time": "2021-03-16T16:31:00.000Z", "last_edited_time": "2021-03-16T16:32:00.000Z", "has_children": false, + "in_trash" : false, "type": "to_do", "to_do": { "rich_text": [ diff --git a/Test/Notion.UnitTests/data/blocks/UpdateBlockResponse.json b/Test/Notion.UnitTests/data/blocks/UpdateBlockResponse.json index 497aba34..c2d3a2c4 100644 --- a/Test/Notion.UnitTests/data/blocks/UpdateBlockResponse.json +++ b/Test/Notion.UnitTests/data/blocks/UpdateBlockResponse.json @@ -4,6 +4,7 @@ "created_time": "2021-03-16T16:31:00.000Z", "last_edited_time": "2021-03-16T16:32:00.000Z", "has_children": false, + "in_trash" : false, "type": "to_do", "to_do": { "rich_text": [ diff --git a/Test/Notion.UnitTests/data/databases/DatabaseRetrieveResponse.json b/Test/Notion.UnitTests/data/databases/DatabaseRetrieveResponse.json index 9b5cf45f..9e1acfb0 100644 --- a/Test/Notion.UnitTests/data/databases/DatabaseRetrieveResponse.json +++ b/Test/Notion.UnitTests/data/databases/DatabaseRetrieveResponse.json @@ -33,6 +33,7 @@ } }, "url": "https://www.notion.so/668d797c76fa49349b05ad288df2d136", + "in_trash" : false, "properties": { "Tags": { "id": "YG~h", @@ -89,6 +90,12 @@ } } }, + "SimpleButton": { + "id":"_ri%7C", + "name":"SimpleButton", + "type":"button", + "button": {} + }, "Name": { "id": "title", "name": "Name", diff --git a/Test/Notion.UnitTests/data/databases/DatabasesListResponse.json b/Test/Notion.UnitTests/data/databases/DatabasesListResponse.json index 713c0736..ad0e5497 100644 --- a/Test/Notion.UnitTests/data/databases/DatabasesListResponse.json +++ b/Test/Notion.UnitTests/data/databases/DatabasesListResponse.json @@ -81,6 +81,12 @@ } } }, + "SimpleButton": { + "id":"_ri%7C", + "name":"SimpleButton", + "type":"button", + "button": {} + }, "Name": { "id": "title", "name": "Name", diff --git a/Test/Notion.UnitTests/data/databases/DatabasesQueryResponse.json b/Test/Notion.UnitTests/data/databases/DatabasesQueryResponse.json index 27e70234..d291a5b2 100644 --- a/Test/Notion.UnitTests/data/databases/DatabasesQueryResponse.json +++ b/Test/Notion.UnitTests/data/databases/DatabasesQueryResponse.json @@ -10,7 +10,7 @@ "type": "database_id", "database_id": "897e5a76-ae52-4b48-9fdf-e71f5945d1af" }, - "archived": false, + "in_trash": false, "url": "https://www.notion.so/2e01e904febd43a0ad028eedb903a82c", "properties": { "Recipes": { @@ -45,6 +45,11 @@ "id": "{>U;", "type": "checkbox", "checkbox": false + }, + "Add Page Button": { + "id":"_ri%7C", + "type":"button", + "button": {} } } } diff --git a/Test/Notion.UnitTests/data/databases/Fix123QueryAsyncDateFormulaValueReturnsNullResponse.json b/Test/Notion.UnitTests/data/databases/Fix123QueryAsyncDateFormulaValueReturnsNullResponse.json index 8c1b95c0..00159d76 100644 --- a/Test/Notion.UnitTests/data/databases/Fix123QueryAsyncDateFormulaValueReturnsNullResponse.json +++ b/Test/Notion.UnitTests/data/databases/Fix123QueryAsyncDateFormulaValueReturnsNullResponse.json @@ -12,7 +12,7 @@ "type": "database_id", "database_id": "f86f2262-0751-40f2-8f63-e3f7a3c39fcb" }, - "archived": false, + "in_trash": false, "properties": { "Column": { "id": "B[\\E", diff --git a/Test/Notion.UnitTests/data/pages/CreatePageResponse.json b/Test/Notion.UnitTests/data/pages/CreatePageResponse.json index 89f0e58e..a801949b 100644 --- a/Test/Notion.UnitTests/data/pages/CreatePageResponse.json +++ b/Test/Notion.UnitTests/data/pages/CreatePageResponse.json @@ -3,7 +3,7 @@ "id": "251d2b5f-268c-4de2-afe9-c71ff92ca95c", "created_time": "2020-03-17T19:10:04.968Z", "last_edited_time": "2020-03-17T21:49:37.913Z", - "archived": false, + "in_trash": false, "url": "https://www.notion.so/Test-251d2b5f268c4de2afe9c71ff92ca95c", "properties": { "Name": { diff --git a/Test/Notion.UnitTests/data/pages/PageObjectShouldHaveUrlPropertyResponse.json b/Test/Notion.UnitTests/data/pages/PageObjectShouldHaveUrlPropertyResponse.json index 8f9d45ab..bf5ba373 100644 --- a/Test/Notion.UnitTests/data/pages/PageObjectShouldHaveUrlPropertyResponse.json +++ b/Test/Notion.UnitTests/data/pages/PageObjectShouldHaveUrlPropertyResponse.json @@ -7,7 +7,7 @@ "type": "database_id", "database_id": "48f8fee9-cd79-4180-bc2f-ec0398253067" }, - "archived": false, + "in_trash": false, "url": "https://www.notion.so/Avocado-251d2b5f268c4de2afe9c71ff92ca95c", "properties": { "Name": { diff --git a/Test/Notion.UnitTests/data/pages/ArchivePageResponse.json b/Test/Notion.UnitTests/data/pages/TrashPageResponse.json similarity index 87% rename from Test/Notion.UnitTests/data/pages/ArchivePageResponse.json rename to Test/Notion.UnitTests/data/pages/TrashPageResponse.json index 2f9af7c6..add1f546 100644 --- a/Test/Notion.UnitTests/data/pages/ArchivePageResponse.json +++ b/Test/Notion.UnitTests/data/pages/TrashPageResponse.json @@ -3,7 +3,7 @@ "id": "251d2b5f-268c-4de2-afe9-c71ff92ca95c", "created_time": "2020-03-17T19:10:04.968Z", "last_edited_time": "2020-03-17T21:49:37.913Z", - "archived": true, + "in_trash": true, "url": "https://www.notion.so/Test-251d2b5f268c4de2afe9c71ff92ca95c", "properties": { "In stock": { @@ -11,6 +11,11 @@ "type": "checkbox", "checkbox": true }, + "Add Page Button": { + "id":"_ri%7C", + "type":"button", + "button": {} + }, "Name": { "id": "title", "type": "title", diff --git a/Test/Notion.UnitTests/data/pages/UpdatePagePropertiesResponse.json b/Test/Notion.UnitTests/data/pages/UpdatePagePropertiesResponse.json index 12076368..70cc2164 100644 --- a/Test/Notion.UnitTests/data/pages/UpdatePagePropertiesResponse.json +++ b/Test/Notion.UnitTests/data/pages/UpdatePagePropertiesResponse.json @@ -3,7 +3,7 @@ "id": "251d2b5f-268c-4de2-afe9-c71ff92ca95c", "created_time": "2020-03-17T19:10:04.968Z", "last_edited_time": "2020-03-17T21:49:37.913Z", - "archived": false, + "in_trash": false, "url": "https://www.notion.so/Test-251d2b5f268c4de2afe9c71ff92ca95c", "properties": { "In stock": { diff --git a/Test/Notion.UnitTests/data/search/SearchResponse.json b/Test/Notion.UnitTests/data/search/SearchResponse.json index 107adcb3..53defa57 100644 --- a/Test/Notion.UnitTests/data/search/SearchResponse.json +++ b/Test/Notion.UnitTests/data/search/SearchResponse.json @@ -4,6 +4,7 @@ "object": "list", "results": [ { + "in_trash": false, "created_time": "2021-04-22T22:23:26.080Z", "id": "e6c6f8ff-c70e-4970-91ba-98f03e0d7fc6", "last_edited_time": "2021-04-23T04:21:00.000Z", @@ -43,7 +44,7 @@ ] }, { - "archived": false, + "in_trash": false, "created_time": "2021-04-23T04:21:00.000Z", "id": "4f555b50-3a9b-49cb-924c-3746f4ca5522", "last_edited_time": "2021-04-23T04:21:00.000Z", diff --git a/lgtm.yml b/lgtm.yml deleted file mode 100644 index 5d5e1e4f..00000000 --- a/lgtm.yml +++ /dev/null @@ -1,6 +0,0 @@ -extraction: - csharp: - index: - all_solutions: true - dotnet: - version: 6.0.400